From ba207ee5a573a6a6c8f133681b734e94f834613d Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 23 Jan 2020 13:24:35 -0700 Subject: [PATCH 001/645] start split workload from the main.cpp in openfpga --- openfpga/src/base/openfpga_context.h | 35 +++++++++++ openfpga/src/base/openfpga_title.cpp | 43 +++++++++++++ openfpga/src/base/openfpga_title.h | 14 +++++ openfpga/src/main.cpp | 94 ++++++---------------------- openfpga/src/vpr_cmd/vpr_main.cpp | 2 + 5 files changed, 114 insertions(+), 74 deletions(-) create mode 100644 openfpga/src/base/openfpga_context.h create mode 100644 openfpga/src/base/openfpga_title.cpp create mode 100644 openfpga/src/base/openfpga_title.h diff --git a/openfpga/src/base/openfpga_context.h b/openfpga/src/base/openfpga_context.h new file mode 100644 index 000000000..a0e6272ff --- /dev/null +++ b/openfpga/src/base/openfpga_context.h @@ -0,0 +1,35 @@ +#ifndef OPENFPGA_CONTEXT_H +#define OPENFPGA_CONTEXT_H + +#include "openfpga_arch.h" + +/******************************************************************** + * This file includes the declaration of the date structure + * OpenfpgaContext, which is used for data exchange between + * different modules in OpenFPGA shell environment + * + * If a command of OpenFPGA needs to exchange data with other commands, + * it must use this data structure to access/mutate. + * In such case, you must add data structures to OpenfpgaContext + * + * Note: + * Please respect to the following rules when using the OpenfpgaContext + * 1. This data structure will be created only once in the main() function + * The data structure is design to be large and contain all the + * data structure required by each module of OpenFPGA core engine. + * Do NOT create or duplicate in your own module! + * 2. Be clear in your mind if you want to access/mutate the data inside OpenfpgaContext + * Read-only data should be accessed by + * const OpenfpgaContext& + * Mutate should use reference + * OpenfpgaContext& + * 3. Please keep the definition of OpenfpgaContext short + * Do put ONLY well-modularized data structure under this root. + *******************************************************************/ +class OpenfpgaContext { + private: + /* Data structure to store information from read_openfpga_arch library */ + openfpga::Arch arch_; +}; + +#endif diff --git a/openfpga/src/base/openfpga_title.cpp b/openfpga/src/base/openfpga_title.cpp new file mode 100644 index 000000000..4a33bb72e --- /dev/null +++ b/openfpga/src/base/openfpga_title.cpp @@ -0,0 +1,43 @@ +/******************************************************************** + * This file includes the functions to create the title page + * which introduces generality of OpenFPGA framework + *******************************************************************/ +#include "openfpga_title.h" + +/******************************************************************** + * Generate a string of openfpga title introduction + * This is mainly used when launching OpenFPGA shell + *******************************************************************/ +const char* create_openfpga_title() { + std::string title; + + title += std::string("\n"); + title += std::string(" OpenFPGA: An Open-source FPGA IP Generator\n"); + title += std::string("\n"); + title += std::string("Contributors: Xifan Tang\tAurelien Alacchi\tBaudouin Chauviere\n"); + title += std::string("\n"); + title += std::string("The MIT License\n"); + title += std::string("\n"); + title += std::string("Copyright (c) 2018 LNIS - The University of Utah\n"); + title += std::string("\n"); + title += std::string("Permission is hereby granted, free of charge, to any person obtaining a copy\n"); + title += std::string("of this software and associated documentation files (the \"Software\"), to deal\n"); + title += std::string("in the Software without restriction, including without limitation the rights\n"); + title += std::string("to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n"); + title += std::string("copies of the Software, and to permit persons to whom the Software is\n"); + title += std::string("furnished to do so, subject to the following conditions:\n"); + title += std::string("\n"); + title += std::string("The above copyright notice and this permission notice shall be included in\n"); + title += std::string("all copies or substantial portions of the Software.\n"); + title += std::string("\n"); + title += std::string("THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"); + title += std::string("IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"); + title += std::string("FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n"); + title += std::string("AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n"); + title += std::string("LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n"); + title += std::string("OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n"); + title += std::string("THE SOFTWARE.\n"); + title += std::string("\n"); + + return title.c_str(); +} diff --git a/openfpga/src/base/openfpga_title.h b/openfpga/src/base/openfpga_title.h new file mode 100644 index 000000000..b4826964a --- /dev/null +++ b/openfpga/src/base/openfpga_title.h @@ -0,0 +1,14 @@ +#ifndef OPENFPGA_TITLE_H +#define OPENFPGA_TITLE_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include + +/******************************************************************** + * Function declaration + *******************************************************************/ +const char* create_openfpga_title(); + +#endif diff --git a/openfpga/src/main.cpp b/openfpga/src/main.cpp index ec445f46e..d3698d18d 100644 --- a/openfpga/src/main.cpp +++ b/openfpga/src/main.cpp @@ -1,34 +1,27 @@ /******************************************************************** * Build the OpenFPGA shell interface *******************************************************************/ +/* Header file from vtrutil library */ #include "vtr_log.h" + +/* Header file from libopenfpgashell library */ #include "command_parser.h" #include "command_echo.h" #include "shell.h" +/* Header file from openfpga */ #include "vpr_main.h" +#include "openfpga_title.h" +#include "openfpga_context.h" + using namespace openfpga; -class ShellContext { - public: - int a; -}; - -static -void shell_execute_set(ShellContext& context, - const Command& cmd, const CommandContext& cmd_context) { - CommandOptionId opt_id = cmd.option("value"); - /* Get the value of a in the command context */ - context.a = std::atoi(cmd_context.option_value(cmd, opt_id).c_str()); -} - -static -void shell_execute_print(ShellContext& context) { - VTR_LOG("a=%d\n", context.a); -} - +/******************************************************************** + * Main function to start OpenFPGA shell interface + *******************************************************************/ int main(int argc, char** argv) { + /* Create the command to launch shell in different modes */ Command start_cmd("OpenFPGA"); /* Add two options: @@ -47,70 +40,23 @@ int main(int argc, char** argv) { /* Create a shell object * Add two commands, which are - * 1. help - * 2. exit + * 1. exit + * 2. help. This must the last to add */ - Shell shell("OpenFPGA"); - std::string shell_title; + Shell shell("OpenFPGA"); - shell_title += std::string("\n"); - shell_title += std::string(" OpenFPGA: An Open-source FPGA IP Generator\n"); - shell_title += std::string("\n"); - shell_title += std::string("Contributors: Xifan Tang\tAurelien Alacchi\tBaudouin Chauviere\n"); - shell_title += std::string("\n"); - shell_title += std::string("The MIT License\n"); - shell_title += std::string("\n"); - shell_title += std::string("Copyright (c) 2018 LNIS - The University of Utah\n"); - shell_title += std::string("\n"); - shell_title += std::string("Permission is hereby granted, free of charge, to any person obtaining a copy\n"); - shell_title += std::string("of this software and associated documentation files (the \"Software\"), to deal\n"); - shell_title += std::string("in the Software without restriction, including without limitation the rights\n"); - shell_title += std::string("to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n"); - shell_title += std::string("copies of the Software, and to permit persons to whom the Software is\n"); - shell_title += std::string("furnished to do so, subject to the following conditions:\n"); - shell_title += std::string("\n"); - shell_title += std::string("The above copyright notice and this permission notice shall be included in\n"); - shell_title += std::string("all copies or substantial portions of the Software.\n"); - shell_title += std::string("\n"); - shell_title += std::string("THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"); - shell_title += std::string("IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"); - shell_title += std::string("FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n"); - shell_title += std::string("AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n"); - shell_title += std::string("LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n"); - shell_title += std::string("OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n"); - shell_title += std::string("THE SOFTWARE.\n"); - - shell.add_title(shell_title.c_str()); + shell.add_title(create_openfpga_title()); /* Add a new class of commands */ - ShellCommandClassId arith_cmd_class = shell.add_command_class("Arithmetic"); - - /* Create a command of 'set' with a required option 'value' with a value - * This function sets a value to an internal variable of ShellContext - */ - Command shell_cmd_set("set"); - CommandOptionId set_opt_value = shell_cmd_set.add_option("value", true, "value of variable"); - shell_cmd_set.set_option_require_value(set_opt_value, OPT_STRING); - ShellCommandId shell_cmd_set_id = shell.add_command(shell_cmd_set, "Set a value to internal variable 'a'"); - shell.set_command_class(shell_cmd_set_id, arith_cmd_class); - shell.set_command_execute_function(shell_cmd_set_id, shell_execute_set); - - /* Create a command of 'print' - * This function will print the value of an internal variable of ShellContext - */ - Command shell_cmd_print("print"); - ShellCommandId shell_cmd_print_id = shell.add_command(shell_cmd_print, "Print the value of internal variable 'a'"); - shell.set_command_class(shell_cmd_print_id, arith_cmd_class); - shell.set_command_execute_function(shell_cmd_print_id, shell_execute_print); + ShellCommandClassId arith_cmd_class = shell.add_command_class("VPR"); /* Create a macro command of 'vpr' which will call the main engine of vpr */ Command shell_cmd_vpr("vpr"); - ShellCommandId shell_cmd_vpr_id = shell.add_command(shell_cmd_vpr, "A macro function to print arguments"); + ShellCommandId shell_cmd_vpr_id = shell.add_command(shell_cmd_vpr, "Start VPR core engine to pack, place and route a BLIF design on a FPGA architecture"); shell.set_command_class(shell_cmd_vpr_id, arith_cmd_class); shell.set_command_execute_function(shell_cmd_vpr_id, vpr::vpr); - /* Add a new class of commands */ ShellCommandClassId basic_cmd_class = shell.add_command_class("Basic"); @@ -126,7 +72,7 @@ int main(int argc, char** argv) { shell.set_command_execute_function(shell_cmd_help_id, [shell](){shell.print_commands();}); /* Create the data base for the shell */ - ShellContext shell_context; + OpenfpgaContext openfpga_context; /* Parse the option, to avoid issues, we use the command name to replace the argv[0] */ std::vector cmd_opts; @@ -142,13 +88,13 @@ int main(int argc, char** argv) { } else { /* Parse succeed. Start a shell */ if (true == start_cmd_context.option_enable(start_cmd, opt_interactive)) { - shell.run_interactive_mode(shell_context); + shell.run_interactive_mode(openfpga_context); return 0; } if (true == start_cmd_context.option_enable(start_cmd, opt_script_mode)) { shell.run_script_mode(start_cmd_context.option_value(start_cmd, opt_script_mode).c_str(), - shell_context); + openfpga_context); return 0; } /* Reach here there is something wrong, show the help desk */ diff --git a/openfpga/src/vpr_cmd/vpr_main.cpp b/openfpga/src/vpr_cmd/vpr_main.cpp index 08b8348c4..5b9db0455 100644 --- a/openfpga/src/vpr_cmd/vpr_main.cpp +++ b/openfpga/src/vpr_cmd/vpr_main.cpp @@ -19,6 +19,8 @@ #include "vpr_signal_handler.h" #include "vpr_tatum_error.h" +#include "vpr_main.h" + #include "globals.h" namespace vpr { From 3cb16a2279bc277e706f4ee5cd15d105305b7891 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 23 Jan 2020 14:42:49 -0700 Subject: [PATCH 002/645] move basic commands to separated CXX files --- openfpga/src/base/shell_basic_cmd.cpp | 27 ++++++++++++++++++ openfpga/src/base/shell_basic_cmd.h | 21 ++++++++++++++ openfpga/src/main.cpp | 39 ++++++++------------------ openfpga/src/vpr_cmd/shell_vpr_cmd.cpp | 23 +++++++++++++++ openfpga/src/vpr_cmd/shell_vpr_cmd.h | 21 ++++++++++++++ 5 files changed, 104 insertions(+), 27 deletions(-) create mode 100644 openfpga/src/base/shell_basic_cmd.cpp create mode 100644 openfpga/src/base/shell_basic_cmd.h create mode 100644 openfpga/src/vpr_cmd/shell_vpr_cmd.cpp create mode 100644 openfpga/src/vpr_cmd/shell_vpr_cmd.h diff --git a/openfpga/src/base/shell_basic_cmd.cpp b/openfpga/src/base/shell_basic_cmd.cpp new file mode 100644 index 000000000..defb87f24 --- /dev/null +++ b/openfpga/src/base/shell_basic_cmd.cpp @@ -0,0 +1,27 @@ +/******************************************************************** + * Add basic commands to the OpenFPGA shell interface, including: + * - exit + * - help + *******************************************************************/ +#include "shell_basic_cmd.h" + +/* begin namespace openfpga */ +namespace openfpga { + +void add_basic_commands(openfpga::Shell& shell) { + /* Add a new class of commands */ + ShellCommandClassId basic_cmd_class = shell.add_command_class("Basic"); + + Command shell_cmd_exit("exit"); + ShellCommandId shell_cmd_exit_id = shell.add_command(shell_cmd_exit, "Exit the shell"); + shell.set_command_class(shell_cmd_exit_id, basic_cmd_class); + shell.set_command_execute_function(shell_cmd_exit_id, [shell](){shell.exit();}); + + /* Note: help must be the last to add because the linking to execute function will do a snapshot on the shell */ + Command shell_cmd_help("help"); + ShellCommandId shell_cmd_help_id = shell.add_command(shell_cmd_help, "Launch help desk"); + shell.set_command_class(shell_cmd_help_id, basic_cmd_class); + shell.set_command_execute_function(shell_cmd_help_id, [shell](){shell.print_commands();}); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/base/shell_basic_cmd.h b/openfpga/src/base/shell_basic_cmd.h new file mode 100644 index 000000000..1722c9c60 --- /dev/null +++ b/openfpga/src/base/shell_basic_cmd.h @@ -0,0 +1,21 @@ +#ifndef SHELL_BASIC_CMD_H +#define SHELL_BASIC_CMD_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "shell.h" +#include "openfpga_context.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void add_basic_commands(openfpga::Shell& shell); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/main.cpp b/openfpga/src/main.cpp index d3698d18d..7412684b6 100644 --- a/openfpga/src/main.cpp +++ b/openfpga/src/main.cpp @@ -10,7 +10,8 @@ #include "shell.h" /* Header file from openfpga */ -#include "vpr_main.h" +#include "shell_vpr_cmd.h" +#include "shell_basic_cmd.h" #include "openfpga_title.h" #include "openfpga_context.h" @@ -21,21 +22,20 @@ using namespace openfpga; * Main function to start OpenFPGA shell interface *******************************************************************/ int main(int argc, char** argv) { - /* Create the command to launch shell in different modes */ Command start_cmd("OpenFPGA"); /* Add two options: * '--interactive', -i': launch the interactive mode * '--file', -f': launch the script mode */ - CommandOptionId opt_interactive = start_cmd.add_option("interactive", false, "Launch OpenFPGA in interactive mode"); + openfpga::CommandOptionId opt_interactive = start_cmd.add_option("interactive", false, "Launch OpenFPGA in interactive mode"); start_cmd.set_option_short_name(opt_interactive, "i"); - CommandOptionId opt_script_mode = start_cmd.add_option("file", false, "Launch OpenFPGA in script mode"); - start_cmd.set_option_require_value(opt_script_mode, OPT_STRING); + openfpga::CommandOptionId opt_script_mode = start_cmd.add_option("file", false, "Launch OpenFPGA in script mode"); + start_cmd.set_option_require_value(opt_script_mode, openfpga::OPT_STRING); start_cmd.set_option_short_name(opt_script_mode, "f"); - CommandOptionId opt_help = start_cmd.add_option("help", false, "Help desk"); + openfpga::CommandOptionId opt_help = start_cmd.add_option("help", false, "Help desk"); start_cmd.set_option_short_name(opt_help, "h"); /* Create a shell object @@ -47,29 +47,14 @@ int main(int argc, char** argv) { shell.add_title(create_openfpga_title()); - /* Add a new class of commands */ - ShellCommandClassId arith_cmd_class = shell.add_command_class("VPR"); + /* Add vpr commands */ + add_vpr_commands(shell); - /* Create a macro command of 'vpr' which will call the main engine of vpr + /* Add basic commands: exit, help, etc. + * Note: + * This MUST be the last command group to be added! */ - Command shell_cmd_vpr("vpr"); - ShellCommandId shell_cmd_vpr_id = shell.add_command(shell_cmd_vpr, "Start VPR core engine to pack, place and route a BLIF design on a FPGA architecture"); - shell.set_command_class(shell_cmd_vpr_id, arith_cmd_class); - shell.set_command_execute_function(shell_cmd_vpr_id, vpr::vpr); - - /* Add a new class of commands */ - ShellCommandClassId basic_cmd_class = shell.add_command_class("Basic"); - - Command shell_cmd_exit("exit"); - ShellCommandId shell_cmd_exit_id = shell.add_command(shell_cmd_exit, "Exit the shell"); - shell.set_command_class(shell_cmd_exit_id, basic_cmd_class); - shell.set_command_execute_function(shell_cmd_exit_id, [shell](){shell.exit();}); - - /* Note: help must be the last to add because the linking to execute function will do a snapshot on the shell */ - Command shell_cmd_help("help"); - ShellCommandId shell_cmd_help_id = shell.add_command(shell_cmd_help, "Launch help desk"); - shell.set_command_class(shell_cmd_help_id, basic_cmd_class); - shell.set_command_execute_function(shell_cmd_help_id, [shell](){shell.print_commands();}); + add_basic_commands(shell); /* Create the data base for the shell */ OpenfpgaContext openfpga_context; diff --git a/openfpga/src/vpr_cmd/shell_vpr_cmd.cpp b/openfpga/src/vpr_cmd/shell_vpr_cmd.cpp new file mode 100644 index 000000000..22bace2c5 --- /dev/null +++ b/openfpga/src/vpr_cmd/shell_vpr_cmd.cpp @@ -0,0 +1,23 @@ +/******************************************************************** + * Add vpr-related commands to the OpenFPGA shell interface + *******************************************************************/ +#include "vpr_main.h" + +#include "shell_vpr_cmd.h" + +/* begin namespace openfpga */ +namespace openfpga { + +void add_vpr_commands(openfpga::Shell& shell) { + /* Add a new class of commands */ + ShellCommandClassId arith_cmd_class = shell.add_command_class("VPR"); + + /* Create a macro command of 'vpr' which will call the main engine of vpr + */ + Command shell_cmd_vpr("vpr"); + ShellCommandId shell_cmd_vpr_id = shell.add_command(shell_cmd_vpr, "Start VPR core engine to pack, place and route a BLIF design on a FPGA architecture"); + shell.set_command_class(shell_cmd_vpr_id, arith_cmd_class); + shell.set_command_execute_function(shell_cmd_vpr_id, vpr::vpr); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/vpr_cmd/shell_vpr_cmd.h b/openfpga/src/vpr_cmd/shell_vpr_cmd.h new file mode 100644 index 000000000..c3aebe6dd --- /dev/null +++ b/openfpga/src/vpr_cmd/shell_vpr_cmd.h @@ -0,0 +1,21 @@ +#ifndef SHELL_VPR_CMD_H +#define SHELL_VPR_CMD_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "shell.h" +#include "openfpga_context.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void add_vpr_commands(openfpga::Shell& shell); + +} /* end namespace openfpga */ + +#endif From 26e6c4012f2017c0937e7b447267f629249b1762 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 23 Jan 2020 14:50:03 -0700 Subject: [PATCH 003/645] add const context function execution for shell --- libopenfpga/libopenfpgashell/src/shell.h | 10 ++++++++- libopenfpga/libopenfpgashell/src/shell.tpp | 24 ++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/libopenfpga/libopenfpgashell/src/shell.h b/libopenfpga/libopenfpgashell/src/shell.h index 78bd74acb..934901796 100644 --- a/libopenfpga/libopenfpgashell/src/shell.h +++ b/libopenfpga/libopenfpgashell/src/shell.h @@ -58,7 +58,9 @@ class Shell { * Built-in commands have their own execute functions inside the shell */ enum e_exec_func_type { + CONST_STANDARD, STANDARD, + CONST_SHORT, SHORT, BUILTIN, MACRO, @@ -93,8 +95,12 @@ class Shell { * 4. Marco function, which directly call a macro function without command parsing * Users just need to specify the function object and its type will be automatically inferred */ + void set_command_execute_function(const ShellCommandId& cmd_id, + std::function exec_func); void set_command_execute_function(const ShellCommandId& cmd_id, std::function exec_func); + void set_command_execute_function(const ShellCommandId& cmd_id, + std::function exec_func); void set_command_execute_function(const ShellCommandId& cmd_id, std::function exec_func); void set_command_execute_function(const ShellCommandId& cmd_id, @@ -156,8 +162,10 @@ class Shell { * 3. Built-in function, including only the shell envoriment variables * 4. Marco function, which directly call a macro function without command parsing */ + vtr::vector> command_const_execute_functions_; vtr::vector> command_standard_execute_functions_; - vtr::vector> command_short_execute_functions_; + vtr::vector> command_short_const_execute_functions_; + vtr::vector> command_short_execute_functions_; vtr::vector> command_builtin_execute_functions_; vtr::vector> command_macro_execute_functions_; diff --git a/libopenfpga/libopenfpgashell/src/shell.tpp b/libopenfpga/libopenfpgashell/src/shell.tpp index 8e9c32860..20787905d 100644 --- a/libopenfpga/libopenfpgashell/src/shell.tpp +++ b/libopenfpga/libopenfpgashell/src/shell.tpp @@ -125,7 +125,9 @@ ShellCommandId Shell::add_command(const Command& cmd, const char* descr) { command_description_.push_back(descr); command_classes_.push_back(ShellCommandClassId::INVALID()); command_execute_function_types_.emplace_back(); + command_const_execute_functions_.emplace_back(); command_standard_execute_functions_.emplace_back(); + command_short_const_execute_functions_.emplace_back(); command_short_execute_functions_.emplace_back(); command_builtin_execute_functions_.emplace_back(); command_macro_execute_functions_.emplace_back(); @@ -150,6 +152,14 @@ void Shell::set_command_class(const ShellCommandId& cmd_id, const ShellComman } } +template +void Shell::set_command_execute_function(const ShellCommandId& cmd_id, + std::function exec_func) { + VTR_ASSERT(true == valid_command_id(cmd_id)); + command_execute_function_types_[cmd_id] = CONST_STANDARD; + command_standard_execute_functions_[cmd_id] = exec_func; +} + template void Shell::set_command_execute_function(const ShellCommandId& cmd_id, std::function exec_func) { @@ -158,6 +168,14 @@ void Shell::set_command_execute_function(const ShellCommandId& cmd_id, command_standard_execute_functions_[cmd_id] = exec_func; } +template +void Shell::set_command_execute_function(const ShellCommandId& cmd_id, + std::function exec_func) { + VTR_ASSERT(true == valid_command_id(cmd_id)); + command_execute_function_types_[cmd_id] = CONST_SHORT; + command_short_execute_functions_[cmd_id] = exec_func; +} + template void Shell::set_command_execute_function(const ShellCommandId& cmd_id, std::function exec_func) { @@ -379,9 +397,15 @@ void Shell::execute_command(const char* cmd_line, /* Execute the command depending on the type of function ! */ switch (command_execute_function_types_[cmd_id]) { + case CONST_STANDARD: + command_const_execute_functions_[cmd_id](common_context, commands_[cmd_id], command_contexts_[cmd_id]); + break; case STANDARD: command_standard_execute_functions_[cmd_id](common_context, commands_[cmd_id], command_contexts_[cmd_id]); break; + case CONST_SHORT: + command_short_const_execute_functions_[cmd_id](common_context); + break; case SHORT: command_short_execute_functions_[cmd_id](common_context); break; From e69aa5ba309d0bbccccf8796304e2ab345aef6bc Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 23 Jan 2020 14:57:53 -0700 Subject: [PATCH 004/645] change type casting for vpr macro --- libopenfpga/libopenfpgashell/src/shell.h | 4 ++-- libopenfpga/libopenfpgashell/src/shell.tpp | 4 ++-- libopenfpga/libopenfpgashell/test/test_shell.cpp | 3 +-- openfpga/src/vpr_cmd/vpr_main.cpp | 4 ++-- openfpga/src/vpr_cmd/vpr_main.h | 2 +- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/libopenfpga/libopenfpgashell/src/shell.h b/libopenfpga/libopenfpgashell/src/shell.h index 934901796..c053842a9 100644 --- a/libopenfpga/libopenfpgashell/src/shell.h +++ b/libopenfpga/libopenfpgashell/src/shell.h @@ -106,7 +106,7 @@ class Shell { void set_command_execute_function(const ShellCommandId& cmd_id, std::function exec_func); void set_command_execute_function(const ShellCommandId& cmd_id, - std::function exec_func); + std::function exec_func); void set_command_dependency(const ShellCommandId& cmd_id, const std::vector cmd_dependency); ShellCommandClassId add_command_class(const char* name); @@ -167,7 +167,7 @@ class Shell { vtr::vector> command_short_const_execute_functions_; vtr::vector> command_short_execute_functions_; vtr::vector> command_builtin_execute_functions_; - vtr::vector> command_macro_execute_functions_; + vtr::vector> command_macro_execute_functions_; /* Type of execute functions for each command. * This is supposed to be an internal data ONLY diff --git a/libopenfpga/libopenfpgashell/src/shell.tpp b/libopenfpga/libopenfpgashell/src/shell.tpp index 20787905d..1df5ef744 100644 --- a/libopenfpga/libopenfpgashell/src/shell.tpp +++ b/libopenfpga/libopenfpgashell/src/shell.tpp @@ -194,7 +194,7 @@ void Shell::set_command_execute_function(const ShellCommandId& cmd_id, template void Shell::set_command_execute_function(const ShellCommandId& cmd_id, - std::function exec_func) { + std::function exec_func) { VTR_ASSERT(true == valid_command_id(cmd_id)); command_execute_function_types_[cmd_id] = MACRO; command_macro_execute_functions_[cmd_id] = exec_func; @@ -376,7 +376,7 @@ void Shell::execute_command(const char* cmd_line, strcpy(argv[itok], tokens[itok].c_str()); } /* Execute the marco function */ - command_macro_execute_functions_[cmd_id](tokens.size(), (const char**)argv); + command_macro_execute_functions_[cmd_id](tokens.size(), argv); /* Free the argv */ for (size_t itok = 0; itok < tokens.size(); ++itok) { free(argv[itok]); diff --git a/libopenfpga/libopenfpgashell/test/test_shell.cpp b/libopenfpga/libopenfpgashell/test/test_shell.cpp index f20c6fded..154986be7 100644 --- a/libopenfpga/libopenfpgashell/test/test_shell.cpp +++ b/libopenfpga/libopenfpgashell/test/test_shell.cpp @@ -28,7 +28,7 @@ void shell_execute_print(ShellContext& context) { } static -int shell_execute_print_macro(int argc, const char** argv) { +int shell_execute_print_macro(int argc, char** argv) { VTR_LOG("Number of arguments: %d\n", argc); VTR_LOG("Detailed arguments:\n"); for (int iarg = 0; iarg < argc; ++iarg) { @@ -116,7 +116,6 @@ int main(int argc, char** argv) { shell.set_command_class(shell_cmd_print_macro_id, arith_cmd_class); shell.set_command_execute_function(shell_cmd_print_macro_id, shell_execute_print_macro); - /* Add a new class of commands */ ShellCommandClassId basic_cmd_class = shell.add_command_class("Basic"); diff --git a/openfpga/src/vpr_cmd/vpr_main.cpp b/openfpga/src/vpr_cmd/vpr_main.cpp index 5b9db0455..2af952584 100644 --- a/openfpga/src/vpr_cmd/vpr_main.cpp +++ b/openfpga/src/vpr_cmd/vpr_main.cpp @@ -37,7 +37,7 @@ namespace vpr { * 3. Place-and-route and timing analysis * 4. Clean up */ -int vpr(int argc, const char** argv) { +int vpr(int argc, char** argv) { vtr::ScopedFinishTimer t("The entire flow of VPR"); t_options Options = t_options(); @@ -48,7 +48,7 @@ int vpr(int argc, const char** argv) { vpr_install_signal_handler(); /* Read options, architecture, and circuit netlist */ - vpr_init(argc, argv, &Options, &vpr_setup, &Arch); + vpr_init(argc, (const char**)argv, &Options, &vpr_setup, &Arch); if (Options.show_version) { return SUCCESS_EXIT_CODE; diff --git a/openfpga/src/vpr_cmd/vpr_main.h b/openfpga/src/vpr_cmd/vpr_main.h index ede021f6b..056a4732f 100644 --- a/openfpga/src/vpr_cmd/vpr_main.h +++ b/openfpga/src/vpr_cmd/vpr_main.h @@ -4,7 +4,7 @@ /* Begin namespace vpr */ namespace vpr { -int vpr(int argc, const char** argv); +int vpr(int argc, char** argv); } /* End namespace vpr */ From cdb3b6de464595ba36a2642d2f32ce7251ed8509 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 23 Jan 2020 19:10:53 -0700 Subject: [PATCH 005/645] add read_openfpga_arch to OpenFPGA shell --- .../libarchopenfpga/arch/sample_arch.xml | 12 ++--- ...{shell_basic_cmd.cpp => basic_command.cpp} | 2 +- .../{shell_basic_cmd.h => basic_command.h} | 4 +- openfpga/src/base/openfpga_context.h | 13 ++++- openfpga/src/base/openfpga_read_arch.cpp | 49 +++++++++++++++++++ openfpga/src/base/openfpga_read_arch.h | 23 +++++++++ openfpga/src/base/openfpga_setup_command.cpp | 28 +++++++++++ openfpga/src/base/openfpga_setup_command.h | 21 ++++++++ openfpga/src/main.cpp | 24 ++++----- .../vpr_command.cpp} | 2 +- .../vpr_command.h} | 4 +- .../src/{vpr_cmd => vpr_wrapper}/vpr_main.cpp | 0 .../src/{vpr_cmd => vpr_wrapper}/vpr_main.h | 0 13 files changed, 157 insertions(+), 25 deletions(-) rename openfpga/src/base/{shell_basic_cmd.cpp => basic_command.cpp} (97%) rename openfpga/src/base/{shell_basic_cmd.h => basic_command.h} (91%) create mode 100644 openfpga/src/base/openfpga_read_arch.cpp create mode 100644 openfpga/src/base/openfpga_read_arch.h create mode 100644 openfpga/src/base/openfpga_setup_command.cpp create mode 100644 openfpga/src/base/openfpga_setup_command.h rename openfpga/src/{vpr_cmd/shell_vpr_cmd.cpp => vpr_wrapper/vpr_command.cpp} (97%) rename openfpga/src/{vpr_cmd/shell_vpr_cmd.h => vpr_wrapper/vpr_command.h} (92%) rename openfpga/src/{vpr_cmd => vpr_wrapper}/vpr_main.cpp (100%) rename openfpga/src/{vpr_cmd => vpr_wrapper}/vpr_main.h (100%) diff --git a/libopenfpga/libarchopenfpga/arch/sample_arch.xml b/libopenfpga/libarchopenfpga/arch/sample_arch.xml index 56a8da59b..6d6484f7e 100644 --- a/libopenfpga/libarchopenfpga/arch/sample_arch.xml +++ b/libopenfpga/libarchopenfpga/arch/sample_arch.xml @@ -274,12 +274,12 @@ - - - - - - + + + + + + diff --git a/openfpga/src/base/shell_basic_cmd.cpp b/openfpga/src/base/basic_command.cpp similarity index 97% rename from openfpga/src/base/shell_basic_cmd.cpp rename to openfpga/src/base/basic_command.cpp index defb87f24..397c9272c 100644 --- a/openfpga/src/base/shell_basic_cmd.cpp +++ b/openfpga/src/base/basic_command.cpp @@ -3,7 +3,7 @@ * - exit * - help *******************************************************************/ -#include "shell_basic_cmd.h" +#include "basic_command.h" /* begin namespace openfpga */ namespace openfpga { diff --git a/openfpga/src/base/shell_basic_cmd.h b/openfpga/src/base/basic_command.h similarity index 91% rename from openfpga/src/base/shell_basic_cmd.h rename to openfpga/src/base/basic_command.h index 1722c9c60..8aaf9110a 100644 --- a/openfpga/src/base/shell_basic_cmd.h +++ b/openfpga/src/base/basic_command.h @@ -1,5 +1,5 @@ -#ifndef SHELL_BASIC_CMD_H -#define SHELL_BASIC_CMD_H +#ifndef BASIC_COMMAND_H +#define BASIC_COMMAND_H /******************************************************************** * Include header files that are required by function declaration diff --git a/openfpga/src/base/openfpga_context.h b/openfpga/src/base/openfpga_context.h index a0e6272ff..c66012615 100644 --- a/openfpga/src/base/openfpga_context.h +++ b/openfpga/src/base/openfpga_context.h @@ -1,6 +1,7 @@ #ifndef OPENFPGA_CONTEXT_H #define OPENFPGA_CONTEXT_H +#include "vpr_context.h" #include "openfpga_arch.h" /******************************************************************** @@ -25,9 +26,17 @@ * OpenfpgaContext& * 3. Please keep the definition of OpenfpgaContext short * Do put ONLY well-modularized data structure under this root. + * 4. We build this data structure based on the Context from VPR + * which does NOT allow users to copy the internal members + * This is due to that the data structures in the OpenFPGA context + * are typically big in terms of memory *******************************************************************/ -class OpenfpgaContext { - private: +class OpenfpgaContext : public Context { + public: /* Public accessors */ + const openfpga::Arch& arch() const { return arch_; } + public: /* Public mutators */ + openfpga::Arch& mutable_arch() { return arch_; } + private: /* Internal data */ /* Data structure to store information from read_openfpga_arch library */ openfpga::Arch arch_; }; diff --git a/openfpga/src/base/openfpga_read_arch.cpp b/openfpga/src/base/openfpga_read_arch.cpp new file mode 100644 index 000000000..5d1f9ac8b --- /dev/null +++ b/openfpga/src/base/openfpga_read_arch.cpp @@ -0,0 +1,49 @@ +/******************************************************************** + * This file includes functions to read an OpenFPGA architecture file + * which are built on the libarchopenfpga library + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_log.h" + +/* Headers from archopenfpga library */ +#include "read_xml_openfpga_arch.h" +#include "check_circuit_library.h" + +#include "openfpga_read_arch.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Top-level function to read an OpenFPGA architecture file + * we use the APIs from the libarchopenfpga library + * + * The command will accept an option '--file' which is the architecture + * file provided by users + *******************************************************************/ +void read_arch(OpenfpgaContext& openfpga_context, + const Command& cmd, const CommandContext& cmd_context) { + /* Check the option '--file' is enabled or not + * Actually, it must be enabled as the shell interface will check + * before reaching this fuction + */ + CommandOptionId opt_file = cmd.option("file"); + VTR_ASSERT(true == cmd_context.option_enable(cmd, opt_file)); + VTR_ASSERT(false == cmd_context.option_value(cmd, opt_file).empty()); + + std::string arch_file_name = cmd_context.option_value(cmd, opt_file); + + VTR_LOG("Reading XML architecture '%s'...\n", + arch_file_name.c_str()); + openfpga_context.mutable_arch() = read_xml_openfpga_arch(arch_file_name.c_str()); + + /* Check the architecture: + * 1. Circuit library + * 2. Technology library (TODO) + * 3. Simulation settings (TODO) + */ + check_circuit_library(openfpga_context.arch().circuit_lib); +} + +} /* end namespace openfpga */ + diff --git a/openfpga/src/base/openfpga_read_arch.h b/openfpga/src/base/openfpga_read_arch.h new file mode 100644 index 000000000..8b300bd54 --- /dev/null +++ b/openfpga/src/base/openfpga_read_arch.h @@ -0,0 +1,23 @@ +#ifndef OPENFPGA_READ_ARCH_COMMAND_H +#define OPENFPGA_READ_ARCH_COMMAND_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "command.h" +#include "command_context.h" +#include "openfpga_context.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void read_arch(OpenfpgaContext& openfpga_context, + const Command& cmd, const CommandContext& cmd_context); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/base/openfpga_setup_command.cpp b/openfpga/src/base/openfpga_setup_command.cpp new file mode 100644 index 000000000..a974e2704 --- /dev/null +++ b/openfpga/src/base/openfpga_setup_command.cpp @@ -0,0 +1,28 @@ +/******************************************************************** + * Add commands to the OpenFPGA shell interface, + * in purpose of setting up OpenFPGA core engine, including: + * - read_openfpga_arch : read OpenFPGA architecture file + *******************************************************************/ +#include "openfpga_read_arch.h" +#include "openfpga_setup_command.h" + +/* begin namespace openfpga */ +namespace openfpga { + +void add_openfpga_setup_commands(openfpga::Shell& shell) { + /* Add a new class of commands */ + ShellCommandClassId openfpga_setup_cmd_class = shell.add_command_class("OpenFPGA setup"); + + /* Command 'read_openfpga_arch' */ + Command shell_cmd_read_arch("read_openfpga_arch"); + /* Add an option '--file' in short '-f'*/ + CommandOptionId read_arch_opt_file = shell_cmd_read_arch.add_option("file", true, "file path to the architecture XML"); + shell_cmd_read_arch.set_option_short_name(read_arch_opt_file, "f"); + shell_cmd_read_arch.set_option_require_value(read_arch_opt_file, openfpga::OPT_STRING); + + ShellCommandId shell_cmd_read_arch_id = shell.add_command(shell_cmd_read_arch, "read OpenFPGA architecture file"); + shell.set_command_class(shell_cmd_read_arch_id, openfpga_setup_cmd_class); + shell.set_command_execute_function(shell_cmd_read_arch_id, read_arch); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_setup_command.h b/openfpga/src/base/openfpga_setup_command.h new file mode 100644 index 000000000..03d4220f5 --- /dev/null +++ b/openfpga/src/base/openfpga_setup_command.h @@ -0,0 +1,21 @@ +#ifndef OPENFPGA_SETUP_COMMAND_H +#define OPENFPGA_SETUP_COMMAND_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "shell.h" +#include "openfpga_context.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void add_openfpga_setup_commands(openfpga::Shell& shell); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/main.cpp b/openfpga/src/main.cpp index 7412684b6..c7c31b00c 100644 --- a/openfpga/src/main.cpp +++ b/openfpga/src/main.cpp @@ -10,20 +10,19 @@ #include "shell.h" /* Header file from openfpga */ -#include "shell_vpr_cmd.h" -#include "shell_basic_cmd.h" +#include "vpr_command.h" +#include "openfpga_setup_command.h" +#include "basic_command.h" #include "openfpga_title.h" #include "openfpga_context.h" -using namespace openfpga; - /******************************************************************** * Main function to start OpenFPGA shell interface *******************************************************************/ int main(int argc, char** argv) { /* Create the command to launch shell in different modes */ - Command start_cmd("OpenFPGA"); + openfpga::Command start_cmd("OpenFPGA"); /* Add two options: * '--interactive', -i': launch the interactive mode * '--file', -f': launch the script mode @@ -43,18 +42,21 @@ int main(int argc, char** argv) { * 1. exit * 2. help. This must the last to add */ - Shell shell("OpenFPGA"); + openfpga::Shell shell("OpenFPGA"); shell.add_title(create_openfpga_title()); /* Add vpr commands */ - add_vpr_commands(shell); + openfpga::add_vpr_commands(shell); + + /* Add openfpga setup commands */ + openfpga::add_openfpga_setup_commands(shell); /* Add basic commands: exit, help, etc. * Note: * This MUST be the last command group to be added! */ - add_basic_commands(shell); + openfpga::add_basic_commands(shell); /* Create the data base for the shell */ OpenfpgaContext openfpga_context; @@ -66,10 +68,10 @@ int main(int argc, char** argv) { cmd_opts.push_back(std::string(argv[iarg])); } - CommandContext start_cmd_context(start_cmd); + openfpga::CommandContext start_cmd_context(start_cmd); if (false == parse_command(cmd_opts, start_cmd, start_cmd_context)) { /* Parse fail: Echo the command */ - print_command_options(start_cmd); + openfpga::print_command_options(start_cmd); } else { /* Parse succeed. Start a shell */ if (true == start_cmd_context.option_enable(start_cmd, opt_interactive)) { @@ -83,7 +85,7 @@ int main(int argc, char** argv) { return 0; } /* Reach here there is something wrong, show the help desk */ - print_command_options(start_cmd); + openfpga::print_command_options(start_cmd); } return 0; diff --git a/openfpga/src/vpr_cmd/shell_vpr_cmd.cpp b/openfpga/src/vpr_wrapper/vpr_command.cpp similarity index 97% rename from openfpga/src/vpr_cmd/shell_vpr_cmd.cpp rename to openfpga/src/vpr_wrapper/vpr_command.cpp index 22bace2c5..5baa1ec84 100644 --- a/openfpga/src/vpr_cmd/shell_vpr_cmd.cpp +++ b/openfpga/src/vpr_wrapper/vpr_command.cpp @@ -3,7 +3,7 @@ *******************************************************************/ #include "vpr_main.h" -#include "shell_vpr_cmd.h" +#include "vpr_command.h" /* begin namespace openfpga */ namespace openfpga { diff --git a/openfpga/src/vpr_cmd/shell_vpr_cmd.h b/openfpga/src/vpr_wrapper/vpr_command.h similarity index 92% rename from openfpga/src/vpr_cmd/shell_vpr_cmd.h rename to openfpga/src/vpr_wrapper/vpr_command.h index c3aebe6dd..467e416d0 100644 --- a/openfpga/src/vpr_cmd/shell_vpr_cmd.h +++ b/openfpga/src/vpr_wrapper/vpr_command.h @@ -1,5 +1,5 @@ -#ifndef SHELL_VPR_CMD_H -#define SHELL_VPR_CMD_H +#ifndef VPR_COMMAND_H +#define VPR_COMMAND_H /******************************************************************** * Include header files that are required by function declaration diff --git a/openfpga/src/vpr_cmd/vpr_main.cpp b/openfpga/src/vpr_wrapper/vpr_main.cpp similarity index 100% rename from openfpga/src/vpr_cmd/vpr_main.cpp rename to openfpga/src/vpr_wrapper/vpr_main.cpp diff --git a/openfpga/src/vpr_cmd/vpr_main.h b/openfpga/src/vpr_wrapper/vpr_main.h similarity index 100% rename from openfpga/src/vpr_cmd/vpr_main.h rename to openfpga/src/vpr_wrapper/vpr_main.h From a03f8aa3468f5156ce32707c8fe9b5ef6b864ea1 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 23 Jan 2020 20:12:30 -0700 Subject: [PATCH 006/645] add profiling for read arch --- libopenfpga/libarchopenfpga/src/check_circuit_library.cpp | 6 +++--- libopenfpga/libarchopenfpga/src/read_xml_openfpga_arch.cpp | 5 +++++ openfpga/src/base/openfpga_read_arch.cpp | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp b/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp index ce5211374..93d5ae737 100644 --- a/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp +++ b/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp @@ -6,10 +6,10 @@ * 3. if nay circuit model miss mandatory ports ***********************************************************************/ -/* Header files should be included in a sequence */ -/* Standard header files required go first */ +/* Headers from vtrutil library */ #include "vtr_assert.h" #include "vtr_log.h" +#include "vtr_time.h" #include "check_circuit_library.h" @@ -437,7 +437,7 @@ size_t check_circuit_library_ports(const CircuitLibrary& circuit_lib) { void check_circuit_library(const CircuitLibrary& circuit_lib) { size_t num_err = 0; - VTR_LOG("Checking circuit models...\n"); + vtr::ScopedStartFinishTimer timer("Check circuit library"); /* 1. Circuit models have unique names * For each circuit model, we always make sure it does not share any name with any circuit model locating after it diff --git a/libopenfpga/libarchopenfpga/src/read_xml_openfpga_arch.cpp b/libopenfpga/libarchopenfpga/src/read_xml_openfpga_arch.cpp index 33cf951aa..10605b860 100644 --- a/libopenfpga/libarchopenfpga/src/read_xml_openfpga_arch.cpp +++ b/libopenfpga/libarchopenfpga/src/read_xml_openfpga_arch.cpp @@ -9,6 +9,9 @@ #include "pugixml.hpp" #include "pugixml_util.hpp" +/* Headers from vtrutil library */ +#include "vtr_time.h" + /* Headers from libarchfpga */ #include "arch_error.h" #include "read_xml_util.h" @@ -26,6 +29,8 @@ * 1. circuit library *******************************************************************/ openfpga::Arch read_xml_openfpga_arch(const char* arch_file_name) { + vtr::ScopedStartFinishTimer timer("Read OpenFPGA architecture"); + openfpga::Arch openfpga_arch; pugi::xml_node Next; diff --git a/openfpga/src/base/openfpga_read_arch.cpp b/openfpga/src/base/openfpga_read_arch.cpp index 5d1f9ac8b..765241db8 100644 --- a/openfpga/src/base/openfpga_read_arch.cpp +++ b/openfpga/src/base/openfpga_read_arch.cpp @@ -32,7 +32,7 @@ void read_arch(OpenfpgaContext& openfpga_context, VTR_ASSERT(false == cmd_context.option_value(cmd, opt_file).empty()); std::string arch_file_name = cmd_context.option_value(cmd, opt_file); - + VTR_LOG("Reading XML architecture '%s'...\n", arch_file_name.c_str()); openfpga_context.mutable_arch() = read_xml_openfpga_arch(arch_file_name.c_str()); From 655f84b00eb12eb460e75d1e37c0742b02715146 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 23 Jan 2020 20:58:15 -0700 Subject: [PATCH 007/645] add write_openfpga_arch command to openfpga shell --- .../src/write_xml_openfpga_arch.cpp | 5 ++++ libopenfpga/libopenfpgashell/src/shell.h | 6 ++--- libopenfpga/libopenfpgashell/src/shell.tpp | 12 ++++----- openfpga/src/base/openfpga_read_arch.cpp | 25 +++++++++++++++++++ openfpga/src/base/openfpga_read_arch.h | 3 +++ openfpga/src/base/openfpga_setup_command.cpp | 13 ++++++++++ 6 files changed, 55 insertions(+), 9 deletions(-) diff --git a/libopenfpga/libarchopenfpga/src/write_xml_openfpga_arch.cpp b/libopenfpga/libarchopenfpga/src/write_xml_openfpga_arch.cpp index 6d6945a66..e262735e4 100644 --- a/libopenfpga/libarchopenfpga/src/write_xml_openfpga_arch.cpp +++ b/libopenfpga/libarchopenfpga/src/write_xml_openfpga_arch.cpp @@ -4,6 +4,9 @@ *******************************************************************/ #include +/* Headers from vtrutil library */ +#include "vtr_time.h" + /* Headers from openfpgautil library */ #include "openfpga_digest.h" @@ -20,6 +23,8 @@ *******************************************************************/ void write_xml_openfpga_arch(const char* fname, const openfpga::Arch& openfpga_arch) { + vtr::ScopedStartFinishTimer timer("Write OpenFPGA architecture"); + /* Create a file handler */ std::fstream fp; /* Open the file stream */ diff --git a/libopenfpga/libopenfpgashell/src/shell.h b/libopenfpga/libopenfpgashell/src/shell.h index c053842a9..2f67dd610 100644 --- a/libopenfpga/libopenfpgashell/src/shell.h +++ b/libopenfpga/libopenfpgashell/src/shell.h @@ -95,12 +95,12 @@ class Shell { * 4. Marco function, which directly call a macro function without command parsing * Users just need to specify the function object and its type will be automatically inferred */ - void set_command_execute_function(const ShellCommandId& cmd_id, + void set_command_const_execute_function(const ShellCommandId& cmd_id, std::function exec_func); void set_command_execute_function(const ShellCommandId& cmd_id, std::function exec_func); - void set_command_execute_function(const ShellCommandId& cmd_id, - std::function exec_func); + void set_command_const_execute_function(const ShellCommandId& cmd_id, + std::function exec_func); void set_command_execute_function(const ShellCommandId& cmd_id, std::function exec_func); void set_command_execute_function(const ShellCommandId& cmd_id, diff --git a/libopenfpga/libopenfpgashell/src/shell.tpp b/libopenfpga/libopenfpgashell/src/shell.tpp index 1df5ef744..30534cfeb 100644 --- a/libopenfpga/libopenfpgashell/src/shell.tpp +++ b/libopenfpga/libopenfpgashell/src/shell.tpp @@ -153,11 +153,11 @@ void Shell::set_command_class(const ShellCommandId& cmd_id, const ShellComman } template -void Shell::set_command_execute_function(const ShellCommandId& cmd_id, - std::function exec_func) { +void Shell::set_command_const_execute_function(const ShellCommandId& cmd_id, + std::function exec_func) { VTR_ASSERT(true == valid_command_id(cmd_id)); command_execute_function_types_[cmd_id] = CONST_STANDARD; - command_standard_execute_functions_[cmd_id] = exec_func; + command_const_execute_functions_[cmd_id] = exec_func; } template @@ -169,11 +169,11 @@ void Shell::set_command_execute_function(const ShellCommandId& cmd_id, } template -void Shell::set_command_execute_function(const ShellCommandId& cmd_id, - std::function exec_func) { +void Shell::set_command_const_execute_function(const ShellCommandId& cmd_id, + std::function exec_func) { VTR_ASSERT(true == valid_command_id(cmd_id)); command_execute_function_types_[cmd_id] = CONST_SHORT; - command_short_execute_functions_[cmd_id] = exec_func; + command_short_const_execute_functions_[cmd_id] = exec_func; } template diff --git a/openfpga/src/base/openfpga_read_arch.cpp b/openfpga/src/base/openfpga_read_arch.cpp index 765241db8..1a36e85a3 100644 --- a/openfpga/src/base/openfpga_read_arch.cpp +++ b/openfpga/src/base/openfpga_read_arch.cpp @@ -8,6 +8,7 @@ /* Headers from archopenfpga library */ #include "read_xml_openfpga_arch.h" #include "check_circuit_library.h" +#include "write_xml_openfpga_arch.h" #include "openfpga_read_arch.h" @@ -45,5 +46,29 @@ void read_arch(OpenfpgaContext& openfpga_context, check_circuit_library(openfpga_context.arch().circuit_lib); } +/******************************************************************** + * A function to write an OpenFPGA architecture file + * we use the APIs from the libarchopenfpga library + * + * The command will accept an option '--file' which is the architecture + * file provided by users + *******************************************************************/ +void write_arch(const OpenfpgaContext& openfpga_context, + const Command& cmd, const CommandContext& cmd_context) { + /* Check the option '--file' is enabled or not + * Actually, it must be enabled as the shell interface will check + * before reaching this fuction + */ + CommandOptionId opt_file = cmd.option("file"); + VTR_ASSERT(true == cmd_context.option_enable(cmd, opt_file)); + VTR_ASSERT(false == cmd_context.option_value(cmd, opt_file).empty()); + + std::string arch_file_name = cmd_context.option_value(cmd, opt_file); + + VTR_LOG("Writing XML architecture to '%s'...\n", + arch_file_name.c_str()); + write_xml_openfpga_arch(arch_file_name.c_str(), openfpga_context.arch()); +} + } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_read_arch.h b/openfpga/src/base/openfpga_read_arch.h index 8b300bd54..dcc04d77a 100644 --- a/openfpga/src/base/openfpga_read_arch.h +++ b/openfpga/src/base/openfpga_read_arch.h @@ -18,6 +18,9 @@ namespace openfpga { void read_arch(OpenfpgaContext& openfpga_context, const Command& cmd, const CommandContext& cmd_context); +void write_arch(const OpenfpgaContext& openfpga_context, + const Command& cmd, const CommandContext& cmd_context); + } /* end namespace openfpga */ #endif diff --git a/openfpga/src/base/openfpga_setup_command.cpp b/openfpga/src/base/openfpga_setup_command.cpp index a974e2704..99d827bfb 100644 --- a/openfpga/src/base/openfpga_setup_command.cpp +++ b/openfpga/src/base/openfpga_setup_command.cpp @@ -20,9 +20,22 @@ void add_openfpga_setup_commands(openfpga::Shell& shell) { shell_cmd_read_arch.set_option_short_name(read_arch_opt_file, "f"); shell_cmd_read_arch.set_option_require_value(read_arch_opt_file, openfpga::OPT_STRING); + /* Add command 'read_openfpga_arch' to the Shell */ ShellCommandId shell_cmd_read_arch_id = shell.add_command(shell_cmd_read_arch, "read OpenFPGA architecture file"); shell.set_command_class(shell_cmd_read_arch_id, openfpga_setup_cmd_class); shell.set_command_execute_function(shell_cmd_read_arch_id, read_arch); + + /* Command 'write_openfpga_arch' */ + Command shell_cmd_write_arch("write_openfpga_arch"); + /* Add an option '--file' in short '-f'*/ + CommandOptionId write_arch_opt_file = shell_cmd_write_arch.add_option("file", true, "file path to the architecture XML"); + shell_cmd_write_arch.set_option_short_name(write_arch_opt_file, "f"); + shell_cmd_write_arch.set_option_require_value(write_arch_opt_file, openfpga::OPT_STRING); + + /* Add command 'write_openfpga_arch' to the Shell */ + ShellCommandId shell_cmd_write_arch_id = shell.add_command(shell_cmd_write_arch, "write OpenFPGA architecture file"); + shell.set_command_class(shell_cmd_write_arch_id, openfpga_setup_cmd_class); + shell.set_command_const_execute_function(shell_cmd_write_arch_id, write_arch); } } /* end namespace openfpga */ From d1725de6069f775e9fbdddbcf637464dd78dcfda Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 24 Jan 2020 10:15:16 -0700 Subject: [PATCH 008/645] add ctags script to index openfpga source files --- openfpga/src/ctag_src.sh | 1 + 1 file changed, 1 insertion(+) create mode 100644 openfpga/src/ctag_src.sh diff --git a/openfpga/src/ctag_src.sh b/openfpga/src/ctag_src.sh new file mode 100644 index 000000000..e36d842ea --- /dev/null +++ b/openfpga/src/ctag_src.sh @@ -0,0 +1 @@ +ctags -R . ../../libopenfpga/*/src/* ../../libs/*/src/* ../../vpr/src/*/* From b641ae15d3d7aa4f5c55134cc7d52939c9ca08f1 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 24 Jan 2020 16:46:39 -0700 Subject: [PATCH 009/645] add command dependency in shell execution --- libopenfpga/libopenfpgashell/src/shell.h | 5 ++++- libopenfpga/libopenfpgashell/src/shell.tpp | 19 +++++++++++++++---- .../libopenfpgashell/test/test_shell.cpp | 2 ++ 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/libopenfpga/libopenfpgashell/src/shell.h b/libopenfpga/libopenfpgashell/src/shell.h index 2f67dd610..415fc1b1c 100644 --- a/libopenfpga/libopenfpgashell/src/shell.h +++ b/libopenfpga/libopenfpgashell/src/shell.h @@ -108,7 +108,7 @@ class Shell { void set_command_execute_function(const ShellCommandId& cmd_id, std::function exec_func); void set_command_dependency(const ShellCommandId& cmd_id, - const std::vector cmd_dependency); + const std::vector& cmd_dependency); ShellCommandClassId add_command_class(const char* name); public: /* Public validators */ bool valid_command_id(const ShellCommandId& cmd_id) const; @@ -174,6 +174,9 @@ class Shell { */ vtr::vector command_execute_function_types_; + /* A flag to indicate if the command has been executed */ + vtr::vector command_status_; + /* Dependency graph for different commands, * This helps the shell interface to check if a command need other commands to be run before its execution */ diff --git a/libopenfpga/libopenfpgashell/src/shell.tpp b/libopenfpga/libopenfpgashell/src/shell.tpp index 30534cfeb..0a52fda0b 100644 --- a/libopenfpga/libopenfpgashell/src/shell.tpp +++ b/libopenfpga/libopenfpgashell/src/shell.tpp @@ -131,6 +131,7 @@ ShellCommandId Shell::add_command(const Command& cmd, const char* descr) { command_short_execute_functions_.emplace_back(); command_builtin_execute_functions_.emplace_back(); command_macro_execute_functions_.emplace_back(); + command_status_.push_back(false); /* By default, the command should be marked as never executed */ command_dependencies_.emplace_back(); /* Register the name in the name2id map */ @@ -202,7 +203,7 @@ void Shell::set_command_execute_function(const ShellCommandId& cmd_id, template void Shell::set_command_dependency(const ShellCommandId& cmd_id, - const std::vector dependent_cmds) { + const std::vector& dependent_cmds) { /* Validate the command id as well as each of the command dependency */ VTR_ASSERT(true == valid_command_id(cmd_id)); for (ShellCommandId dependent_cmd : dependent_cmds) { @@ -362,7 +363,14 @@ void Shell::execute_command(const char* cmd_line, return; } - /* TODO: Check the dependency graph to see if all the prequistics have been met */ + /* Check the dependency graph to see if all the prequistics have been met */ + for (const ShellCommandId& dep_cmd : command_dependencies_[cmd_id]) { + if (false == command_status_[dep_cmd]) { + VTR_LOG("Command '%s' is required to be executed before command '%s'!\n", + commands_[dep_cmd].name().c_str(), commands_[cmd_id].name().c_str()); + return; + } + } /* Find the command! Parse the options * Note: @@ -391,10 +399,10 @@ void Shell::execute_command(const char* cmd_line, print_command_options(commands_[cmd_id]); return; } - + /* Parse succeed. Let user to confirm selected options */ print_command_context(commands_[cmd_id], command_contexts_[cmd_id]); - + /* Execute the command depending on the type of function ! */ switch (command_execute_function_types_[cmd_id]) { case CONST_STANDARD: @@ -420,6 +428,9 @@ void Shell::execute_command(const char* cmd_line, /* Exit the shell using the exit() function inside this class! */ exit(); } + + /* Change the status of the command */ + command_status_[cmd_id] = true; } /************************************************************************ diff --git a/libopenfpga/libopenfpgashell/test/test_shell.cpp b/libopenfpga/libopenfpgashell/test/test_shell.cpp index 154986be7..649859d85 100644 --- a/libopenfpga/libopenfpgashell/test/test_shell.cpp +++ b/libopenfpga/libopenfpgashell/test/test_shell.cpp @@ -102,11 +102,13 @@ int main(int argc, char** argv) { /* Create a command of 'print' * This function will print the value of an internal variable of ShellContext + * We set a dependency to this command as it MUST be executed after 'set' */ Command shell_cmd_print("print"); ShellCommandId shell_cmd_print_id = shell.add_command(shell_cmd_print, "Print the value of internal variable 'a'"); shell.set_command_class(shell_cmd_print_id, arith_cmd_class); shell.set_command_execute_function(shell_cmd_print_id, shell_execute_print); + shell.set_command_dependency(shell_cmd_print_id, std::vector(1, shell_cmd_set_id)); /* Create a macro command of 'print_macro' * This function will print the value of an internal variable of ShellContext From a7a1fe8a746c44a7a49d1c83fef5da201fa6f9b0 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 24 Jan 2020 16:57:14 -0700 Subject: [PATCH 010/645] simplify openfpga title page and add command dependency on read_arch and write_arch --- openfpga/src/base/openfpga_setup_command.cpp | 2 ++ openfpga/src/base/openfpga_title.cpp | 6 ++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openfpga/src/base/openfpga_setup_command.cpp b/openfpga/src/base/openfpga_setup_command.cpp index 99d827bfb..322378b07 100644 --- a/openfpga/src/base/openfpga_setup_command.cpp +++ b/openfpga/src/base/openfpga_setup_command.cpp @@ -36,6 +36,8 @@ void add_openfpga_setup_commands(openfpga::Shell& shell) { ShellCommandId shell_cmd_write_arch_id = shell.add_command(shell_cmd_write_arch, "write OpenFPGA architecture file"); shell.set_command_class(shell_cmd_write_arch_id, openfpga_setup_cmd_class); shell.set_command_const_execute_function(shell_cmd_write_arch_id, write_arch); + /* The 'write_openfpga_arch' command should be executed before 'read_openfpga_arch' */ + shell.set_command_dependency(shell_cmd_write_arch_id, std::vector(1, shell_cmd_read_arch_id)); } } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_title.cpp b/openfpga/src/base/openfpga_title.cpp index 4a33bb72e..8a3917ead 100644 --- a/openfpga/src/base/openfpga_title.cpp +++ b/openfpga/src/base/openfpga_title.cpp @@ -14,11 +14,9 @@ const char* create_openfpga_title() { title += std::string("\n"); title += std::string(" OpenFPGA: An Open-source FPGA IP Generator\n"); title += std::string("\n"); - title += std::string("Contributors: Xifan Tang\tAurelien Alacchi\tBaudouin Chauviere\n"); + title += std::string(" This is a free software under the MIT License\n"); title += std::string("\n"); - title += std::string("The MIT License\n"); - title += std::string("\n"); - title += std::string("Copyright (c) 2018 LNIS - The University of Utah\n"); + title += std::string(" Copyright (c) 2018 LNIS - The University of Utah\n"); title += std::string("\n"); title += std::string("Permission is hereby granted, free of charge, to any person obtaining a copy\n"); title += std::string("of this software and associated documentation files (the \"Software\"), to deal\n"); From 5039af2c2f90f0604a67d03fe20150389d317c71 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 24 Jan 2020 17:00:53 -0700 Subject: [PATCH 011/645] update title page --- openfpga/src/base/openfpga_title.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openfpga/src/base/openfpga_title.cpp b/openfpga/src/base/openfpga_title.cpp index 8a3917ead..ae4ce80c4 100644 --- a/openfpga/src/base/openfpga_title.cpp +++ b/openfpga/src/base/openfpga_title.cpp @@ -13,6 +13,11 @@ const char* create_openfpga_title() { title += std::string("\n"); title += std::string(" OpenFPGA: An Open-source FPGA IP Generator\n"); + title += std::string(" Versatile Place and Route (VPR)\n"); + title += std::string(" FPGA-Verilog\n"); + title += std::string(" FPGA-SPICE\n"); + title += std::string(" FPGA-SDC\n"); + title += std::string(" FPGA-Bitstream\n"); title += std::string("\n"); title += std::string(" This is a free software under the MIT License\n"); title += std::string("\n"); From 7feeee8c0e46aacf30639a7107bcac54a0b62426 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 25 Jan 2020 17:38:06 -0700 Subject: [PATCH 012/645] add full syntax to sample_arch.xml about the physical pb_type binding --- .../libarchopenfpga/arch/sample_arch.xml | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/libopenfpga/libarchopenfpga/arch/sample_arch.xml b/libopenfpga/libarchopenfpga/arch/sample_arch.xml index 6d6484f7e..c27233427 100644 --- a/libopenfpga/libarchopenfpga/arch/sample_arch.xml +++ b/libopenfpga/libarchopenfpga/arch/sample_arch.xml @@ -270,18 +270,39 @@ - - - + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + From b4f4bf62a2e5d044c1140c1ed11f56caf09fc886 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 25 Jan 2020 17:42:24 -0700 Subject: [PATCH 013/645] add comments to sample arch --- libopenfpga/libarchopenfpga/arch/sample_arch.xml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/libopenfpga/libarchopenfpga/arch/sample_arch.xml b/libopenfpga/libarchopenfpga/arch/sample_arch.xml index c27233427..8d19fc150 100644 --- a/libopenfpga/libarchopenfpga/arch/sample_arch.xml +++ b/libopenfpga/libarchopenfpga/arch/sample_arch.xml @@ -265,14 +265,18 @@ - + + + + + @@ -297,12 +301,14 @@ + + From f834954698f1165850cb3abc5538b0d8f5bb042f Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 25 Jan 2020 18:14:38 -0700 Subject: [PATCH 014/645] start developing the pb_type annotation --- .../libarchopenfpga/src/pb_annotation.h | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 libopenfpga/libarchopenfpga/src/pb_annotation.h diff --git a/libopenfpga/libarchopenfpga/src/pb_annotation.h b/libopenfpga/libarchopenfpga/src/pb_annotation.h new file mode 100644 index 000000000..2cd92d3a7 --- /dev/null +++ b/libopenfpga/libarchopenfpga/src/pb_annotation.h @@ -0,0 +1,52 @@ +#ifndef PB_PHYSICAL_ANNOTATION_H +#ifndef PB_PHYSICAL_ANNOTATION_H + +/******************************************************************** + * Include header files required by the data structure definition + *******************************************************************/ +#include +#include + +/******************************************************************** + * This file include the declaration of data structures + * to store physical pb_type annotation, including + * 1. the binding between operating pb_type and physical_pb_type + * this also cover the binding between pins of any pair of pb_types + * 2. the binding between physical pb_type and circuit models + * 3. the binding between interconnect under pb_types and circuit models + * This is only applicable to physical pb_types + * + * Each PhysicalPbAnnotation includes the binding for ONLY a pb_type + * defined in the architecture XML + * + * Note: + * 1. Keep this data structure as general as possible. It is supposed + * to contain the raw data from architecture XML! If you want to link + * to other data structures, please create another one in other header files + *******************************************************************/ +class PhysicalPbAnnotation { + private: /* Internal data */ + /* Binding between physical pb_type and operating pb_type + * both operating and physial pb_type names are the full names + * defined in the hierarchy of pb_types + * e.g. + * clb.fle[frac_logic].frac_lut6 + */ + std::string operating_pb_type_name_; + std::string physical_pb_type_name_; + std::string mode_bits_; + std::string circuit_model_name_; + int physical_pb_type_index_factor_; + int physical_pb_type_index_offset_; + + /* Link from the pins under an operating pb_type to physical pb_type */ + std::vector operating_pb_type_pin_names_; + std::vector physical_pb_type_pin_names_; + std::vector physical_pin_rotate_offset_; + + /* Link between the interconnects under this pb_type and circuit model names */ + std::vector> interconnect_circuit_model_names_; +}; + +#endif + From 9b4b6ae083d0a6cd58e45d43c6f51732eb0b5485 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 25 Jan 2020 18:17:00 -0700 Subject: [PATCH 015/645] rename pb_annotation and move it to openfpga namespace --- .../src/{pb_annotation.h => pb_type_annotation.h} | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) rename libopenfpga/libarchopenfpga/src/{pb_annotation.h => pb_type_annotation.h} (93%) diff --git a/libopenfpga/libarchopenfpga/src/pb_annotation.h b/libopenfpga/libarchopenfpga/src/pb_type_annotation.h similarity index 93% rename from libopenfpga/libarchopenfpga/src/pb_annotation.h rename to libopenfpga/libarchopenfpga/src/pb_type_annotation.h index 2cd92d3a7..bad6489b0 100644 --- a/libopenfpga/libarchopenfpga/src/pb_annotation.h +++ b/libopenfpga/libarchopenfpga/src/pb_type_annotation.h @@ -1,5 +1,5 @@ -#ifndef PB_PHYSICAL_ANNOTATION_H -#ifndef PB_PHYSICAL_ANNOTATION_H +#ifndef PB_TYPE_ANNOTATION_H +#ifndef PB_TYPE_ANNOTATION_H /******************************************************************** * Include header files required by the data structure definition @@ -7,6 +7,9 @@ #include #include +/* namespace openfpga begins */ +namespace openfpga { + /******************************************************************** * This file include the declaration of data structures * to store physical pb_type annotation, including @@ -48,5 +51,7 @@ class PhysicalPbAnnotation { std::vector> interconnect_circuit_model_names_; }; +} /* namespace openfpga ends */ + #endif From b6f96e5a8fe7b7244f83a53a2372a76545393e87 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 25 Jan 2020 20:46:21 -0700 Subject: [PATCH 016/645] add method functions to pb_type annotation --- .../libarchopenfpga/arch/sample_arch.xml | 17 +- .../src/pb_type_annotation.cpp | 181 ++++++++++++++++++ .../libarchopenfpga/src/pb_type_annotation.h | 105 +++++++++- 3 files changed, 289 insertions(+), 14 deletions(-) create mode 100644 libopenfpga/libarchopenfpga/src/pb_type_annotation.cpp diff --git a/libopenfpga/libarchopenfpga/arch/sample_arch.xml b/libopenfpga/libarchopenfpga/arch/sample_arch.xml index 8d19fc150..49eccdaa8 100644 --- a/libopenfpga/libarchopenfpga/arch/sample_arch.xml +++ b/libopenfpga/libarchopenfpga/arch/sample_arch.xml @@ -277,6 +277,16 @@ + + + + + + + + + + @@ -301,13 +311,6 @@ - - - - - - - diff --git a/libopenfpga/libarchopenfpga/src/pb_type_annotation.cpp b/libopenfpga/libarchopenfpga/src/pb_type_annotation.cpp new file mode 100644 index 000000000..dda74520b --- /dev/null +++ b/libopenfpga/libarchopenfpga/src/pb_type_annotation.cpp @@ -0,0 +1,181 @@ +/************************************************************************ + * Member functions for class PbTypeAnnotation + ***********************************************************************/ +#include "vtr_log.h" +#include "vtr_assert.h" +#include "pb_type_annotation.h" + +/* namespace openfpga begins */ +namespace openfpga { + +/************************************************************************ + * Constructors + ***********************************************************************/ +PbTypeAnnotation::PbTypeAnnotation() { + return; +} + +/************************************************************************ + * Public Accessors + ***********************************************************************/ +std::string PbTypeAnnotation::operating_pb_type_name() const { + return operating_pb_type_name_; +} + +std::vector PbTypeAnnotation::operating_parent_pb_type_names() const { + return operating_parent_pb_type_names_; +} + +std::vector PbTypeAnnotation::operating_parent_mode_names() const { + return operating_parent_mode_names_; +} + +bool PbTypeAnnotation::is_operating_pb_type() const { + return true == operating_pb_type_name_.empty(); +} + + +std::string PbTypeAnnotation::physical_pb_type_name() const { + return physical_pb_type_name_; +} + +std::vector PbTypeAnnotation::physical_parent_pb_type_names() const { + return physical_parent_pb_type_names_; +} + +std::vector PbTypeAnnotation::physical_parent_mode_names() const { + return physical_parent_mode_names_; +} + +bool PbTypeAnnotation::is_physical_pb_type() const { + return true == physical_pb_type_name_.empty(); +} + +std::string PbTypeAnnotation::mode_bits() const { + return mode_bits_; +} + +std::string PbTypeAnnotation::circuit_model_name() const { + return circuit_model_name_; +} + +int PbTypeAnnotation::physical_pb_type_index_factor() const { + return physical_pb_type_index_factor_; +} + +int PbTypeAnnotation::physical_pb_type_index_offset() const { + return physical_pb_type_index_offset_; +} + +BasicPort PbTypeAnnotation::physical_pb_type_ports(const std::string& port_name) const { + std::map::const_iterator it = operating_pb_type_ports_.find(port_name); + if (it == operating_pb_type_ports_.end()) { + /* Return an empty port */ + return BasicPort(); + } + return operating_pb_type_ports_.at(port_name); +} + +int PbTypeAnnotation::physical_pin_rotate_offsets(const std::string& port_name) const { + std::map::const_iterator it = physical_pin_rotate_offsets_.find(port_name); + if (it == physical_pin_rotate_offsets_.end()) { + /* Return a zero offset which is default */ + return 0; + } + return physical_pin_rotate_offsets_.at(port_name); +} + +std::string PbTypeAnnotation::interconnect_circuit_model_name(const std::string& interc_name) const { + std::map::const_iterator it = interconnect_circuit_model_names_.find(interc_name); + if (it == interconnect_circuit_model_names_.end()) { + return std::string(); + } + + return interconnect_circuit_model_names_.at(interc_name); +} + +/************************************************************************ + * Public Mutators + ***********************************************************************/ +void PbTypeAnnotation::set_operating_pb_type_name(const std::string& name) { + operating_pb_type_name_ = name; +} + +void PbTypeAnnotation::set_operating_parent_pb_type_names(const std::vector& names) { + operating_parent_pb_type_names_ = names; +} + +void PbTypeAnnotation::set_operating_parent_mode_names(const std::vector& names) { + operating_parent_mode_names_ = names; +} + +void PbTypeAnnotation::set_physical_pb_type_name(const std::string& name) { + physical_pb_type_name_ = name; +} + +void PbTypeAnnotation::set_physical_parent_pb_type_names(const std::vector& names) { + physical_parent_pb_type_names_ = names; +} + +void PbTypeAnnotation::set_physical_parent_mode_names(const std::vector& names) { + physical_parent_mode_names_ = names; +} + +void PbTypeAnnotation::set_mode_bits(const std::string& mode_bits) { + mode_bits_ = mode_bits; +} + +void PbTypeAnnotation::set_circuit_model_name(const std::string& name) { + VTR_ASSERT(true == is_physical_pb_type()); + circuit_model_name_ = name; +} + +void PbTypeAnnotation::physical_pb_type_index_factor(const int& value) { + VTR_ASSERT(true == is_operating_pb_type()); + physical_pb_type_index_factor_ = value; +} + +void PbTypeAnnotation::physical_pb_type_index_offset(const int& value) { + VTR_ASSERT(true == is_operating_pb_type()); + physical_pb_type_index_offset_ = value; +} + +void PbTypeAnnotation::add_pb_type_port_pair(const std::string& operating_pb_port_name, + const BasicPort& physical_pb_port) { + /* Give a warning if the operating_pb_port_name already exist */ + std::map::const_iterator it = operating_pb_type_ports_.find(operating_pb_port_name); + /* Give a warning if the interconnection name already exist */ + if (it != operating_pb_type_ports_.end()) { + VTR_LOG_WARN("Redefine operating pb type port '%s' with physical pb type port '%s'\n", + operating_pb_port_name.c_str(), physical_pb_port.get_name().c_str()); + } + + operating_pb_type_ports_[operating_pb_port_name] = physical_pb_port; +} + +void PbTypeAnnotation::set_physical_pin_rotate_offset(const std::string& operating_pb_port_name, + const int& physical_pin_rotate_offset) { + std::map::const_iterator it = operating_pb_type_ports_.find(operating_pb_port_name); + /* Give a warning if the interconnection name already exist */ + if (it == operating_pb_type_ports_.end()) { + VTR_LOG_ERROR("Operating pb type port '%s' does not exist! Ignore physical pin rotate offset '%d'\n", + operating_pb_port_name.c_str(), physical_pin_rotate_offset); + return; + } + + physical_pin_rotate_offsets_[operating_pb_port_name] = physical_pin_rotate_offset; +} + +void PbTypeAnnotation::add_interconnect_circuit_model_pair(const std::string& interc_name, + const std::string& circuit_model_name) { + std::map::const_iterator it = interconnect_circuit_model_names_.find(interc_name); + /* Give a warning if the interconnection name already exist */ + if (it != interconnect_circuit_model_names_.end()) { + VTR_LOG_WARN("Redefine interconnect '%s' with circuit model '%s'\n", + interc_name.c_str(), circuit_model_name.c_str()); + } + + interconnect_circuit_model_names_[interc_name] = circuit_model_name; +} + +} /* namespace openfpga ends */ diff --git a/libopenfpga/libarchopenfpga/src/pb_type_annotation.h b/libopenfpga/libarchopenfpga/src/pb_type_annotation.h index bad6489b0..e2ca62df6 100644 --- a/libopenfpga/libarchopenfpga/src/pb_type_annotation.h +++ b/libopenfpga/libarchopenfpga/src/pb_type_annotation.h @@ -1,5 +1,5 @@ #ifndef PB_TYPE_ANNOTATION_H -#ifndef PB_TYPE_ANNOTATION_H +#define PB_TYPE_ANNOTATION_H /******************************************************************** * Include header files required by the data structure definition @@ -7,6 +7,8 @@ #include #include +#include "openfpga_port.h" + /* namespace openfpga begins */ namespace openfpga { @@ -27,28 +29,117 @@ namespace openfpga { * to contain the raw data from architecture XML! If you want to link * to other data structures, please create another one in other header files *******************************************************************/ -class PhysicalPbAnnotation { +class PbTypeAnnotation { + public: /* Constructor */ + PbTypeAnnotation(); + public: /* Public accessors */ + std::string operating_pb_type_name() const; + std::vector operating_parent_pb_type_names() const; + std::vector operating_parent_mode_names() const; + bool is_operating_pb_type() const; + std::string physical_pb_type_name() const; + std::vector physical_parent_pb_type_names() const; + std::vector physical_parent_mode_names() const; + bool is_physical_pb_type() const; + std::string mode_bits() const; + std::string circuit_model_name() const; + int physical_pb_type_index_factor() const; + int physical_pb_type_index_offset() const; + BasicPort physical_pb_type_ports(const std::string& port_name) const; + int physical_pin_rotate_offsets(const std::string& port_name) const; + std::string interconnect_circuit_model_name(const std::string& interc_name) const; + public: /* Public mutators */ + void set_operating_pb_type_name(const std::string& name); + void set_operating_parent_pb_type_names(const std::vector& names); + void set_operating_parent_mode_names(const std::vector& names); + void set_physical_pb_type_name(const std::string& name); + void set_physical_parent_pb_type_names(const std::vector& names); + void set_physical_parent_mode_names(const std::vector& names); + void set_mode_bits(const std::string& mode_bits); + void set_circuit_model_name(const std::string& name); + void physical_pb_type_index_factor(const int& value); + void physical_pb_type_index_offset(const int& value); + void add_pb_type_port_pair(const std::string& operating_pb_port_name, + const BasicPort& physical_pb_port); + void set_physical_pin_rotate_offset(const std::string& operating_pb_port_name, + const int& physical_pin_rotate_offset); + void add_interconnect_circuit_model_pair(const std::string& interc_name, + const std::string& circuit_model_name); private: /* Internal data */ /* Binding between physical pb_type and operating pb_type - * both operating and physial pb_type names are the full names + * both operating and physial pb_type names contain the full names * defined in the hierarchy of pb_types * e.g. * clb.fle[frac_logic].frac_lut6 + * ^ + * | + * mode_name + * + * The pb_type name 'frac_lut6' will be stored in + * either operating or physical pb_type_name + * The parent pb_type and mode names will be stored in + * either operating or physical parent_pb_type_name and parent_mode_names + * */ std::string operating_pb_type_name_; + std::vector operating_parent_pb_type_names_; + std::vector operating_parent_mode_names_; + std::string physical_pb_type_name_; + std::vector physical_parent_pb_type_names_; + std::vector physical_parent_mode_names_; + + /* Configuration bits to select an operting mode for the circuit mode name */ std::string mode_bits_; + + /* Circuit mode name linked to a physical pb_type. + * This is only applicable to the physical pb_type + */ std::string circuit_model_name_; + + /* The factor aims to align the indices for pb_type between operating and physical modes, + * especially when an operating mode contains multiple pb_type (num_pb>1) + * that are linked to the same physical pb_type. + * When number of physical_pb_type is larger than 1, + * the index of pb_type will be multipled by the given factor. + * + * For example, a factor of 2 is used to map + * operating pb_type adder[5] with a full path clb.fle[arith].adder[5] + * to physical pb_type adder[10] with a full path clb.fle[physical].adder[10] + */ int physical_pb_type_index_factor_; + + /* The offset aims to align the indices for pb_type between operating and physical modes, + * especially when an operating mode contains multiple pb_type (num_pb>1) + * that are linked to the same physical pb_type. + * When number of physical_pb_type is larger than 1, + * the index of pb_type will be shifted by the given factor. + * + * For example, an offset of 1 is used to map + * operating pb_type adder[0] with a full path clb.fle[arith].adder[0] + * to physical pb_type adder[1] with a full path clb.fle[physical].adder[1] + */ int physical_pb_type_index_offset_; /* Link from the pins under an operating pb_type to physical pb_type */ - std::vector operating_pb_type_pin_names_; - std::vector physical_pb_type_pin_names_; - std::vector physical_pin_rotate_offset_; + std::map operating_pb_type_ports_; + + /* The offset aims to align the pin indices for port of pb_type + * between operating and physical modes, especially when an operating + * mode contains multiple pb_type (num_pb>1) that are linked to + * the same physical pb_type. + * When physical_mode_pin_rotate_offset is larger than zero, + * the pin index of pb_type (whose index is large than 1) + * will be shifted by the given offset. + * + * For example, an offset of 1 is used to map + * operating pb_type adder[0].pin[0] with a full path clb.fle[arith].adder[0] + * to physical pb_type adder[0].pin[1] with a full path clb.fle[physical].adder[0] + */ + std::map physical_pin_rotate_offsets_; /* Link between the interconnects under this pb_type and circuit model names */ - std::vector> interconnect_circuit_model_names_; + std::map interconnect_circuit_model_names_; }; } /* namespace openfpga ends */ From bafd866cfc37b34bc3123adc39fbf706a2a86b58 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 25 Jan 2020 21:19:08 -0700 Subject: [PATCH 017/645] start developing XML parser for pb_type annotation --- .../libarchopenfpga/arch/sample_arch.xml | 16 ++-- .../libarchopenfpga/src/openfpga_arch.h | 8 ++ .../src/read_xml_openfpga_arch.cpp | 6 +- .../src/read_xml_pb_type_annotation.cpp | 74 +++++++++++++++++++ .../src/read_xml_pb_type_annotation.h | 17 +++++ 5 files changed, 112 insertions(+), 9 deletions(-) create mode 100644 libopenfpga/libarchopenfpga/src/read_xml_pb_type_annotation.cpp create mode 100644 libopenfpga/libarchopenfpga/src/read_xml_pb_type_annotation.h diff --git a/libopenfpga/libarchopenfpga/arch/sample_arch.xml b/libopenfpga/libarchopenfpga/arch/sample_arch.xml index 49eccdaa8..d3b289b05 100644 --- a/libopenfpga/libarchopenfpga/arch/sample_arch.xml +++ b/libopenfpga/libarchopenfpga/arch/sample_arch.xml @@ -268,7 +268,7 @@ - + @@ -293,26 +293,26 @@ - - + + - - + + - - + + - + diff --git a/libopenfpga/libarchopenfpga/src/openfpga_arch.h b/libopenfpga/libarchopenfpga/src/openfpga_arch.h index 491d8471a..7f102dc85 100644 --- a/libopenfpga/libarchopenfpga/src/openfpga_arch.h +++ b/libopenfpga/libarchopenfpga/src/openfpga_arch.h @@ -1,12 +1,14 @@ #ifndef OPENFPGA_ARCH_H #define OPENFPGA_ARCH_H +#include #include #include "circuit_library.h" #include "technology_library.h" #include "simulation_setting.h" #include "config_protocol.h" +#include "pb_type_annotation.h" /* namespace openfpga begins */ namespace openfpga { @@ -47,6 +49,12 @@ struct Arch { * to circuit models in circuit library */ std::map direct2circuit; + + /* Pb type annotations + * Bind from operating to physical + * Bind from physical to circuit model + */ + std::vector pb_type_annotations; }; } /* namespace openfpga ends */ diff --git a/libopenfpga/libarchopenfpga/src/read_xml_openfpga_arch.cpp b/libopenfpga/libarchopenfpga/src/read_xml_openfpga_arch.cpp index 10605b860..f30ff4f1e 100644 --- a/libopenfpga/libarchopenfpga/src/read_xml_openfpga_arch.cpp +++ b/libopenfpga/libarchopenfpga/src/read_xml_openfpga_arch.cpp @@ -21,6 +21,7 @@ #include "read_xml_simulation_setting.h" #include "read_xml_config_protocol.h" #include "read_xml_routing_circuit.h" +#include "read_xml_pb_type_annotation.h" #include "read_xml_openfpga_arch.h" #include "openfpga_arch_linker.h" @@ -85,10 +86,13 @@ openfpga::Arch read_xml_openfpga_arch(const char* arch_file_name) { openfpga_arch.routing_seg2circuit = read_xml_routing_segment_circuit(xml_openfpga_arch, loc_data, openfpga_arch.circuit_lib); - /* Parse the routing segment circuit definition */ + /* Parse the direct circuit definition */ openfpga_arch.direct2circuit = read_xml_direct_circuit(xml_openfpga_arch, loc_data, openfpga_arch.circuit_lib); + /* Parse the pb_type annotation */ + openfpga_arch.pb_type_annotations = read_xml_pb_type_annotations(xml_openfpga_arch, loc_data); + /* Second node should be */ auto xml_simulation_settings = get_single_child(doc, "openfpga_simulation_setting", loc_data); diff --git a/libopenfpga/libarchopenfpga/src/read_xml_pb_type_annotation.cpp b/libopenfpga/libarchopenfpga/src/read_xml_pb_type_annotation.cpp new file mode 100644 index 000000000..9bf10a171 --- /dev/null +++ b/libopenfpga/libarchopenfpga/src/read_xml_pb_type_annotation.cpp @@ -0,0 +1,74 @@ +/******************************************************************** + * This file includes the top-level function of this library + * which reads an XML modeling OpenFPGA architecture to the associated + * data structures + *******************************************************************/ +#include + +/* Headers from pugi XML library */ +#include "pugixml.hpp" +#include "pugixml_util.hpp" + +/* Headers from vtr util library */ +#include "vtr_assert.h" + +/* Headers from libarchfpga */ +#include "arch_error.h" +#include "read_xml_util.h" + +#include "read_xml_pb_type_annotation.h" + +/******************************************************************** + * Parse XML description for a pb_type annotation under a XML node + *******************************************************************/ +static +void read_xml_pb_type_annotation(pugi::xml_node& xml_pb_type, + const pugiutil::loc_data& loc_data, + std::vector& pb_type_annotations) { + openfpga::PbTypeAnnotation pb_type_annotation; + + /* Find the name of pb_type */ + const std::string& name_attr = get_attribute(xml_pb_type, "name", loc_data).as_string(); + const std::string& physical_name_attr = get_attribute(xml_pb_type, "physical_pb_type_name", loc_data, pugiutil::ReqOpt::OPTIONAL).as_string(); + + /* If both names are not empty, this is a operating pb_type */ + if ( (false == name_attr.empty()) + && (false == physical_name_attr.empty()) ) { + /* Parse the attributes for operating pb_type */ + pb_type_annotation.set_operating_pb_type_name(name_attr); + pb_type_annotation.set_physical_pb_type_name(physical_name_attr); + } + + /* If there is only a name, this is a physical pb_type */ + if ( (false == name_attr.empty()) + && (true == physical_name_attr.empty()) ) { + pb_type_annotation.set_physical_pb_type_name(name_attr); + } + + /* Finish parsing and add it to the vector */ + pb_type_annotations.push_back(pb_type_annotation); +} + +/******************************************************************** + * Top function to parse XML description about pb_type annotation + *******************************************************************/ +std::vector read_xml_pb_type_annotations(pugi::xml_node& Node, + const pugiutil::loc_data& loc_data) { + std::vector pb_type_annotations; + + /* Parse configuration protocol root node */ + pugi::xml_node xml_annotations = get_single_child(Node, "pb_type_annotations", loc_data); + + /* Iterate over the children under this node, + * each child should be named after + */ + for (pugi::xml_node xml_pb_type : xml_annotations.children()) { + /* Error out if the XML child has an invalid name! */ + if (xml_pb_type.name() != std::string("pb_type")) { + bad_tag(xml_pb_type, loc_data, xml_annotations, {"pb_type"}); + } + read_xml_pb_type_annotation(xml_pb_type, loc_data, pb_type_annotations); + } + + return pb_type_annotations; +} diff --git a/libopenfpga/libarchopenfpga/src/read_xml_pb_type_annotation.h b/libopenfpga/libarchopenfpga/src/read_xml_pb_type_annotation.h new file mode 100644 index 000000000..98934a4b8 --- /dev/null +++ b/libopenfpga/libarchopenfpga/src/read_xml_pb_type_annotation.h @@ -0,0 +1,17 @@ +#ifndef READ_XML_PB_TYPE_ANNOTATION_H +#define READ_XML_PB_TYPE_ANNOTATION_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "pugixml_util.hpp" +#include "pugixml.hpp" +#include "pb_type_annotation.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ +std::vector read_xml_pb_type_annotations(pugi::xml_node& Node, + const pugiutil::loc_data& loc_data); + +#endif From a9f03ce21b27405eb364f3482dce48916774b26f Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 26 Jan 2020 10:19:47 -0700 Subject: [PATCH 018/645] add XML attribute parsing for physical and operating pb_type annotation --- libopenfpga/libarchopenfpga/arch/sample_arch.xml | 2 +- .../libarchopenfpga/src/pb_type_annotation.cpp | 16 ++++++++++++++++ .../libarchopenfpga/src/pb_type_annotation.h | 10 ++++++++++ .../src/read_xml_pb_type_annotation.cpp | 15 +++++++++++++++ 4 files changed, 42 insertions(+), 1 deletion(-) diff --git a/libopenfpga/libarchopenfpga/arch/sample_arch.xml b/libopenfpga/libarchopenfpga/arch/sample_arch.xml index d3b289b05..aaadd04ad 100644 --- a/libopenfpga/libarchopenfpga/arch/sample_arch.xml +++ b/libopenfpga/libarchopenfpga/arch/sample_arch.xml @@ -270,7 +270,7 @@ - + diff --git a/libopenfpga/libarchopenfpga/src/pb_type_annotation.cpp b/libopenfpga/libarchopenfpga/src/pb_type_annotation.cpp index dda74520b..806a76c44 100644 --- a/libopenfpga/libarchopenfpga/src/pb_type_annotation.cpp +++ b/libopenfpga/libarchopenfpga/src/pb_type_annotation.cpp @@ -51,6 +51,14 @@ bool PbTypeAnnotation::is_physical_pb_type() const { return true == physical_pb_type_name_.empty(); } +std::string PbTypeAnnotation::physical_mode_name() const { + return physical_mode_name_; +} + +std::string PbTypeAnnotation::idle_mode_name() const { + return idle_mode_name_; +} + std::string PbTypeAnnotation::mode_bits() const { return mode_bits_; } @@ -121,6 +129,14 @@ void PbTypeAnnotation::set_physical_parent_mode_names(const std::vector physical_parent_pb_type_names() const; std::vector physical_parent_mode_names() const; bool is_physical_pb_type() const; + std::string physical_mode_name() const; + std::string idle_mode_name() const; std::string mode_bits() const; std::string circuit_model_name() const; int physical_pb_type_index_factor() const; @@ -55,6 +57,8 @@ class PbTypeAnnotation { void set_physical_pb_type_name(const std::string& name); void set_physical_parent_pb_type_names(const std::vector& names); void set_physical_parent_mode_names(const std::vector& names); + void set_physical_mode_name(const std::string& name); + void set_idle_mode_name(const std::string& name); void set_mode_bits(const std::string& mode_bits); void set_circuit_model_name(const std::string& name); void physical_pb_type_index_factor(const int& value); @@ -89,6 +93,12 @@ class PbTypeAnnotation { std::vector physical_parent_pb_type_names_; std::vector physical_parent_mode_names_; + /* Identify which mode is the physical implementation of an operating pb_type */ + std::string physical_mode_name_; + + /* Identify in which mode is the pb_type will operate when it is not used */ + std::string idle_mode_name_; + /* Configuration bits to select an operting mode for the circuit mode name */ std::string mode_bits_; diff --git a/libopenfpga/libarchopenfpga/src/read_xml_pb_type_annotation.cpp b/libopenfpga/libarchopenfpga/src/read_xml_pb_type_annotation.cpp index 9bf10a171..7ac685436 100644 --- a/libopenfpga/libarchopenfpga/src/read_xml_pb_type_annotation.cpp +++ b/libopenfpga/libarchopenfpga/src/read_xml_pb_type_annotation.cpp @@ -30,6 +30,7 @@ void read_xml_pb_type_annotation(pugi::xml_node& xml_pb_type, /* Find the name of pb_type */ const std::string& name_attr = get_attribute(xml_pb_type, "name", loc_data).as_string(); const std::string& physical_name_attr = get_attribute(xml_pb_type, "physical_pb_type_name", loc_data, pugiutil::ReqOpt::OPTIONAL).as_string(); + const std::string& physical_mode_name_attr = get_attribute(xml_pb_type, "physical_mode_name", loc_data, pugiutil::ReqOpt::OPTIONAL).as_string(); /* If both names are not empty, this is a operating pb_type */ if ( (false == name_attr.empty()) @@ -45,6 +46,20 @@ void read_xml_pb_type_annotation(pugi::xml_node& xml_pb_type, pb_type_annotation.set_physical_pb_type_name(name_attr); } + /* Parse physical mode name which are applied to both pb_types */ + pb_type_annotation.set_physical_mode_name(get_attribute(xml_pb_type, "physical_mode_name", loc_data, pugiutil::ReqOpt::OPTIONAL).as_string()); + + /* Parse idle mode name which are applied to both pb_types */ + pb_type_annotation.set_idle_mode_name(get_attribute(xml_pb_type, "idle_mode_name", loc_data, pugiutil::ReqOpt::OPTIONAL).as_string()); + + /* Parse mode bits which are applied to both pb_types */ + pb_type_annotation.set_mode_bits(get_attribute(xml_pb_type, "mode_bits", loc_data, pugiutil::ReqOpt::OPTIONAL).as_string()); + + /* If this is a physical pb_type, circuit model name is a mandatory attribute */ + if (true == pb_type_annotation.is_physical_pb_type()) { + pb_type_annotation.set_circuit_model_name(get_attribute(xml_pb_type, "circuit_model_name", loc_data).as_string()); + } + /* Finish parsing and add it to the vector */ pb_type_annotations.push_back(pb_type_annotation); } From cd3565cf53086bae0509baeaa6c09e9b8dbf197b Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 26 Jan 2020 10:56:57 -0700 Subject: [PATCH 019/645] complete the XML parser for pb_type annotation --- .../libarchopenfpga/arch/sample_arch.xml | 12 +-- .../src/pb_type_annotation.cpp | 4 +- .../libarchopenfpga/src/pb_type_annotation.h | 4 +- .../src/read_xml_pb_type_annotation.cpp | 77 +++++++++++++++++++ 4 files changed, 87 insertions(+), 10 deletions(-) diff --git a/libopenfpga/libarchopenfpga/arch/sample_arch.xml b/libopenfpga/libarchopenfpga/arch/sample_arch.xml index aaadd04ad..dafaa83dd 100644 --- a/libopenfpga/libarchopenfpga/arch/sample_arch.xml +++ b/libopenfpga/libarchopenfpga/arch/sample_arch.xml @@ -293,21 +293,21 @@ - - + + - - + + - - + + diff --git a/libopenfpga/libarchopenfpga/src/pb_type_annotation.cpp b/libopenfpga/libarchopenfpga/src/pb_type_annotation.cpp index 806a76c44..98fad3dd0 100644 --- a/libopenfpga/libarchopenfpga/src/pb_type_annotation.cpp +++ b/libopenfpga/libarchopenfpga/src/pb_type_annotation.cpp @@ -146,12 +146,12 @@ void PbTypeAnnotation::set_circuit_model_name(const std::string& name) { circuit_model_name_ = name; } -void PbTypeAnnotation::physical_pb_type_index_factor(const int& value) { +void PbTypeAnnotation::set_physical_pb_type_index_factor(const int& value) { VTR_ASSERT(true == is_operating_pb_type()); physical_pb_type_index_factor_ = value; } -void PbTypeAnnotation::physical_pb_type_index_offset(const int& value) { +void PbTypeAnnotation::set_physical_pb_type_index_offset(const int& value) { VTR_ASSERT(true == is_operating_pb_type()); physical_pb_type_index_offset_ = value; } diff --git a/libopenfpga/libarchopenfpga/src/pb_type_annotation.h b/libopenfpga/libarchopenfpga/src/pb_type_annotation.h index 8bf4480a8..8849ef273 100644 --- a/libopenfpga/libarchopenfpga/src/pb_type_annotation.h +++ b/libopenfpga/libarchopenfpga/src/pb_type_annotation.h @@ -61,8 +61,8 @@ class PbTypeAnnotation { void set_idle_mode_name(const std::string& name); void set_mode_bits(const std::string& mode_bits); void set_circuit_model_name(const std::string& name); - void physical_pb_type_index_factor(const int& value); - void physical_pb_type_index_offset(const int& value); + void set_physical_pb_type_index_factor(const int& value); + void set_physical_pb_type_index_offset(const int& value); void add_pb_type_port_pair(const std::string& operating_pb_port_name, const BasicPort& physical_pb_port); void set_physical_pin_rotate_offset(const std::string& operating_pb_port_name, diff --git a/libopenfpga/libarchopenfpga/src/read_xml_pb_type_annotation.cpp b/libopenfpga/libarchopenfpga/src/read_xml_pb_type_annotation.cpp index 7ac685436..160c3183a 100644 --- a/libopenfpga/libarchopenfpga/src/read_xml_pb_type_annotation.cpp +++ b/libopenfpga/libarchopenfpga/src/read_xml_pb_type_annotation.cpp @@ -12,12 +12,56 @@ /* Headers from vtr util library */ #include "vtr_assert.h" +/* Headers from openfpga util library */ +#include "openfpga_port_parser.h" + /* Headers from libarchfpga */ #include "arch_error.h" #include "read_xml_util.h" #include "read_xml_pb_type_annotation.h" +/******************************************************************** + * Parse XML description for an interconnection annotation + * under a XML node + *******************************************************************/ +static +void read_xml_interc_annotation(pugi::xml_node& xml_interc, + const pugiutil::loc_data& loc_data, + openfpga::PbTypeAnnotation& pb_type_annotation) { + /* We have two mandatory XML attribute + * 1. name of the interconnect + * 2. circuit model name of the interconnect + */ + const std::string& name_attr = get_attribute(xml_interc, "name", loc_data).as_string(); + const std::string& circuit_model_name_attr = get_attribute(xml_interc, "circuit_model_name", loc_data).as_string(); + + pb_type_annotation.add_interconnect_circuit_model_pair(name_attr, circuit_model_name_attr); +} + +/******************************************************************** + * Parse XML description for a pb_type port annotation + * under a XML node + *******************************************************************/ +static +void read_xml_pb_port_annotation(pugi::xml_node& xml_port, + const pugiutil::loc_data& loc_data, + openfpga::PbTypeAnnotation& pb_type_annotation) { + /* We have two mandatory XML attribute + * 1. name of the port + * 2. name of the port to be binded in physical mode + */ + const std::string& name_attr = get_attribute(xml_port, "name", loc_data).as_string(); + const std::string& physical_mode_port_attr = get_attribute(xml_port, "physical_mode_port", loc_data).as_string(); + + /* Parse the mode port using openfpga port parser */ + openfpga::PortParser port_parser(physical_mode_port_attr); + pb_type_annotation.add_pb_type_port_pair(name_attr, port_parser.port()); + + /* We have an optional attribute: physical_mode_pin_rotate_offset */ + pb_type_annotation.set_physical_pin_rotate_offset(name_attr, get_attribute(xml_port, "physical_mode_pin_rotate_offset", loc_data, pugiutil::ReqOpt::OPTIONAL).as_int(0)); +} + /******************************************************************** * Parse XML description for a pb_type annotation under a XML node *******************************************************************/ @@ -60,6 +104,39 @@ void read_xml_pb_type_annotation(pugi::xml_node& xml_pb_type, pb_type_annotation.set_circuit_model_name(get_attribute(xml_pb_type, "circuit_model_name", loc_data).as_string()); } + /* If this is an operating pb_type, index factor and offset may be optional needed */ + if (true == pb_type_annotation.is_operating_pb_type()) { + pb_type_annotation.set_physical_pb_type_index_factor(get_attribute(xml_pb_type, "physical_pb_type_index_factor", loc_data, pugiutil::ReqOpt::OPTIONAL).as_int(1)); + pb_type_annotation.set_physical_pb_type_index_offset(get_attribute(xml_pb_type, "physical_pb_type_index_offset", loc_data, pugiutil::ReqOpt::OPTIONAL).as_int(0)); + } + + /* Parse all the interconnect-to-circuit binding under this node + * All the bindings are defined in child node + */ + size_t num_intercs = count_children(xml_pb_type, "interconnect", loc_data, pugiutil::ReqOpt::OPTIONAL); + if (0 < num_intercs) { + pugi::xml_node xml_interc = get_first_child(xml_pb_type, "interconnect", loc_data); + while (xml_interc) { + read_xml_interc_annotation(xml_interc, loc_data, pb_type_annotation); + xml_interc = xml_interc.next_sibling(xml_interc.name()); + } + } + + /* Parse all the port-to-port binding from operating pb_type to physical pb_type under this node + * All the bindings are defined in child node + * This is only applicable to operating pb_type + */ + if (true == pb_type_annotation.is_operating_pb_type()) { + size_t num_ports = count_children(xml_pb_type, "port", loc_data, pugiutil::ReqOpt::OPTIONAL); + if (0 < num_ports) { + pugi::xml_node xml_port = get_first_child(xml_pb_type, "port", loc_data); + while (xml_port) { + read_xml_pb_port_annotation(xml_port, loc_data, pb_type_annotation); + xml_port = xml_port.next_sibling(xml_port.name()); + } + } + } + /* Finish parsing and add it to the vector */ pb_type_annotations.push_back(pb_type_annotation); } From 1cba141dd0f8b1c32340998fb471486427ebc4a3 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 26 Jan 2020 11:52:58 -0700 Subject: [PATCH 020/645] add pb parser and support XML parsing for pb type name in full hiearchy --- .../src/read_xml_pb_type_annotation.cpp | 29 ++++- .../src/openfpga_pb_parser.cpp | 117 ++++++++++++++++++ .../libopenfpgautil/src/openfpga_pb_parser.h | 56 +++++++++ 3 files changed, 199 insertions(+), 3 deletions(-) create mode 100644 libopenfpga/libopenfpgautil/src/openfpga_pb_parser.cpp create mode 100644 libopenfpga/libopenfpgautil/src/openfpga_pb_parser.h diff --git a/libopenfpga/libarchopenfpga/src/read_xml_pb_type_annotation.cpp b/libopenfpga/libarchopenfpga/src/read_xml_pb_type_annotation.cpp index 160c3183a..25e1e12f8 100644 --- a/libopenfpga/libarchopenfpga/src/read_xml_pb_type_annotation.cpp +++ b/libopenfpga/libarchopenfpga/src/read_xml_pb_type_annotation.cpp @@ -14,6 +14,7 @@ /* Headers from openfpga util library */ #include "openfpga_port_parser.h" +#include "openfpga_pb_parser.h" /* Headers from libarchfpga */ #include "arch_error.h" @@ -80,14 +81,36 @@ void read_xml_pb_type_annotation(pugi::xml_node& xml_pb_type, if ( (false == name_attr.empty()) && (false == physical_name_attr.empty()) ) { /* Parse the attributes for operating pb_type */ - pb_type_annotation.set_operating_pb_type_name(name_attr); - pb_type_annotation.set_physical_pb_type_name(physical_name_attr); + openfpga::PbParser operating_pb_parser(name_attr); + pb_type_annotation.set_operating_pb_type_name(operating_pb_parser.leaf()); + if (0 < operating_pb_parser.parents().size()) { + pb_type_annotation.set_operating_parent_pb_type_names(operating_pb_parser.parents()); + } + if (0 < operating_pb_parser.modes().size()) { + pb_type_annotation.set_operating_parent_mode_names(operating_pb_parser.modes()); + } + + openfpga::PbParser physical_pb_parser(physical_name_attr); + pb_type_annotation.set_physical_pb_type_name(physical_pb_parser.leaf()); + if (0 < physical_pb_parser.parents().size()) { + pb_type_annotation.set_physical_parent_pb_type_names(physical_pb_parser.parents()); + } + if (0 < physical_pb_parser.modes().size()) { + pb_type_annotation.set_physical_parent_mode_names(physical_pb_parser.modes()); + } } /* If there is only a name, this is a physical pb_type */ if ( (false == name_attr.empty()) && (true == physical_name_attr.empty()) ) { - pb_type_annotation.set_physical_pb_type_name(name_attr); + openfpga::PbParser physical_pb_parser(name_attr); + pb_type_annotation.set_physical_pb_type_name(physical_pb_parser.leaf()); + if (0 < physical_pb_parser.parents().size()) { + pb_type_annotation.set_physical_parent_pb_type_names(physical_pb_parser.parents()); + } + if (0 < physical_pb_parser.modes().size()) { + pb_type_annotation.set_physical_parent_mode_names(physical_pb_parser.modes()); + } } /* Parse physical mode name which are applied to both pb_types */ diff --git a/libopenfpga/libopenfpgautil/src/openfpga_pb_parser.cpp b/libopenfpga/libopenfpgautil/src/openfpga_pb_parser.cpp new file mode 100644 index 000000000..ee674b928 --- /dev/null +++ b/libopenfpga/libopenfpgautil/src/openfpga_pb_parser.cpp @@ -0,0 +1,117 @@ +/************************************************************************ + * Member functions for Pb parsers + ***********************************************************************/ +#include + +#include "vtr_assert.h" + +#include "openfpga_tokenizer.h" + +#include "openfpga_pb_parser.h" + +/* namespace openfpga begins */ +namespace openfpga { + +/************************************************************************ + * Member functions for PbParser class + ***********************************************************************/ + +/************************************************************************ + * Constructors + ***********************************************************************/ +PbParser::PbParser (const std::string& data) { + set_default_bracket(); + set_default_delim(); + set_data(data); +} + +/************************************************************************ + * Public Accessors + ***********************************************************************/ +/* Get the data string */ +std::string PbParser::data() const { + return data_; +} + +std::string PbParser::leaf() const { + return leaf_; +} + +std::vector PbParser::parents() const { + return parents_; +} + +std::vector PbParser::modes() const { + return modes_; +} + +/************************************************************************ + * Public Mutators + ***********************************************************************/ +void PbParser::set_data(const std::string& data) { + data_ = data; + parse(); +} + +/************************************************************************ + * Internal Mutators + ***********************************************************************/ +/* Parse the data */ +void PbParser::parse() { + /* Create a tokenizer */ + StringToken tokenizer(data_); + + /* Split the data into [] */ + std::vector pb_tokens = tokenizer.split(delim_); + + /* The last pb is the leaf node. + * It should NOT be empty and should NOT contain any brackets! + */ + VTR_ASSERT(false == pb_tokens.back().empty()); + VTR_ASSERT(pb_tokens.back().find(bracket_.x()) == std::string::npos); + VTR_ASSERT(pb_tokens.back().find(bracket_.y()) == std::string::npos); + /* Pass the check, we assign the value */ + leaf_ = pb_tokens.back(); + + /* Split the rest of pb_tokens, split the pb_type and mode_name */ + for (size_t itok = 0; itok < pb_tokens.size() - 1; ++itok) { + /* Create a tokenizer */ + StringToken pb_tokenizer(pb_tokens[itok]); + std::vector tokens = pb_tokenizer.split(bracket_.x()); + /* Make sure we have a port name! */ + VTR_ASSERT_SAFE ((1 == tokens.size()) || (2 == tokens.size())); + /* Store the pb_type name */ + parents_.push_back(tokens[0]); + + /* If we only have one token, the mode name is the default mode + * Here we use the default keyword 'default' + * from VPR function ProcessMode() in libarchfpga + */ + if (1 == tokens.size()) { + modes_.push_back(std::string("default")); + continue; /* We can finish here */ + } + + /* If there is a mode name, extract it */ + VTR_ASSERT_SAFE (2 == tokens.size()); + + /* Chomp the ']' */ + pb_tokenizer.set_data(tokens[1]); + std::vector mode_tokens = pb_tokenizer.split(bracket_.y()); + + /* Make sure we have only one mode name inside */ + VTR_ASSERT(1 == mode_tokens.size()); + modes_.push_back(mode_tokens[0]); + } +} + +void PbParser::set_default_bracket() { + bracket_.set_x('['); + bracket_.set_y(']'); +} + +void PbParser::set_default_delim() { + delim_ = '.'; +} + +} /* namespace openfpga ends */ diff --git a/libopenfpga/libopenfpgautil/src/openfpga_pb_parser.h b/libopenfpga/libopenfpgautil/src/openfpga_pb_parser.h new file mode 100644 index 000000000..d59286136 --- /dev/null +++ b/libopenfpga/libopenfpgautil/src/openfpga_pb_parser.h @@ -0,0 +1,56 @@ +#ifndef OPENFPGA_PB_PARSER_H +#define OPENFPGA_PB_PARSER_H + +/******************************************************************** + * Include header files that are required by data structure declaration + *******************************************************************/ +#include +#include + +#include "vtr_geometry.h" + +#include "openfpga_tokenizer.h" + +/************************************************************************ + * This file includes parsers for pb_type definition in the architecture XML + * language. + ***********************************************************************/ + +/* namespace openfpga begins */ +namespace openfpga { + +/************************************************************************ + * Class PbParser: pb_type name with full hierarchy parser + * Supported pb_type definition: + * 1. [].[] ... . + * 2. . ... . + * where each pb_type may be specified by a mode or use the default mode + * (mode name not given) + ***********************************************************************/ +class PbParser{ + public : /* Constructors*/ + PbParser (const std::string& data); + public : /* Public Accessors */ + std::string data() const; + std::string leaf() const; + std::vector parents() const; + std::vector modes() const; + public : /* Public Mutators */ + void set_data(const std::string& data); + private : /* Private Mutators */ + void parse(); + void set_default_bracket(); + void set_default_delim(); + private: /* Internal data */ + std::string data_; /* Lines to be splited */ + vtr::Point bracket_; + char delim_; + std::vector parents_; + std::vector modes_; + std::string leaf_; +}; + +} /* namespace openfpga ends */ + +#endif + From 7d4b07421d949c945e4dc2b1118a55d31a636984 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 26 Jan 2020 15:54:49 -0700 Subject: [PATCH 021/645] finish XML parser and writer for pb_type annotation --- .../src/pb_type_annotation.cpp | 26 +- .../libarchopenfpga/src/pb_type_annotation.h | 6 +- .../src/read_xml_pb_type_annotation.cpp | 7 +- .../src/write_xml_openfpga_arch.cpp | 5 + .../src/write_xml_pb_type_annotation.cpp | 224 ++++++++++++++++++ .../src/write_xml_pb_type_annotation.h | 22 ++ .../libarchopenfpga/src/write_xml_utils.cpp | 15 ++ .../libarchopenfpga/src/write_xml_utils.h | 4 + .../src/openfpga_pb_parser.cpp | 1 + .../src/openfpga_port_parser.cpp | 21 +- 10 files changed, 312 insertions(+), 19 deletions(-) create mode 100644 libopenfpga/libarchopenfpga/src/write_xml_pb_type_annotation.cpp create mode 100644 libopenfpga/libarchopenfpga/src/write_xml_pb_type_annotation.h diff --git a/libopenfpga/libarchopenfpga/src/pb_type_annotation.cpp b/libopenfpga/libarchopenfpga/src/pb_type_annotation.cpp index 98fad3dd0..da494f77f 100644 --- a/libopenfpga/libarchopenfpga/src/pb_type_annotation.cpp +++ b/libopenfpga/libarchopenfpga/src/pb_type_annotation.cpp @@ -31,7 +31,8 @@ std::vector PbTypeAnnotation::operating_parent_mode_names() const { } bool PbTypeAnnotation::is_operating_pb_type() const { - return true == operating_pb_type_name_.empty(); + return (false == operating_pb_type_name_.empty()) + && (false == physical_pb_type_name_.empty()); } @@ -48,7 +49,8 @@ std::vector PbTypeAnnotation::physical_parent_mode_names() const { } bool PbTypeAnnotation::is_physical_pb_type() const { - return true == physical_pb_type_name_.empty(); + return (true == operating_pb_type_name_.empty()) + && (false == physical_pb_type_name_.empty()); } std::string PbTypeAnnotation::physical_mode_name() const { @@ -75,7 +77,15 @@ int PbTypeAnnotation::physical_pb_type_index_offset() const { return physical_pb_type_index_offset_; } -BasicPort PbTypeAnnotation::physical_pb_type_ports(const std::string& port_name) const { +std::vector PbTypeAnnotation::port_names() const { + std::vector keys; + for (auto const& element : operating_pb_type_ports_) { + keys.push_back(element.first); + } + return keys; +} + +BasicPort PbTypeAnnotation::physical_pb_type_port(const std::string& port_name) const { std::map::const_iterator it = operating_pb_type_ports_.find(port_name); if (it == operating_pb_type_ports_.end()) { /* Return an empty port */ @@ -84,7 +94,7 @@ BasicPort PbTypeAnnotation::physical_pb_type_ports(const std::string& port_name) return operating_pb_type_ports_.at(port_name); } -int PbTypeAnnotation::physical_pin_rotate_offsets(const std::string& port_name) const { +int PbTypeAnnotation::physical_pin_rotate_offset(const std::string& port_name) const { std::map::const_iterator it = physical_pin_rotate_offsets_.find(port_name); if (it == physical_pin_rotate_offsets_.end()) { /* Return a zero offset which is default */ @@ -93,6 +103,14 @@ int PbTypeAnnotation::physical_pin_rotate_offsets(const std::string& port_name) return physical_pin_rotate_offsets_.at(port_name); } +std::vector PbTypeAnnotation::interconnect_names() const { + std::vector keys; + for (auto const& element : interconnect_circuit_model_names_) { + keys.push_back(element.first); + } + return keys; +} + std::string PbTypeAnnotation::interconnect_circuit_model_name(const std::string& interc_name) const { std::map::const_iterator it = interconnect_circuit_model_names_.find(interc_name); if (it == interconnect_circuit_model_names_.end()) { diff --git a/libopenfpga/libarchopenfpga/src/pb_type_annotation.h b/libopenfpga/libarchopenfpga/src/pb_type_annotation.h index 8849ef273..e03201cde 100644 --- a/libopenfpga/libarchopenfpga/src/pb_type_annotation.h +++ b/libopenfpga/libarchopenfpga/src/pb_type_annotation.h @@ -47,8 +47,10 @@ class PbTypeAnnotation { std::string circuit_model_name() const; int physical_pb_type_index_factor() const; int physical_pb_type_index_offset() const; - BasicPort physical_pb_type_ports(const std::string& port_name) const; - int physical_pin_rotate_offsets(const std::string& port_name) const; + std::vector port_names() const; + BasicPort physical_pb_type_port(const std::string& port_name) const; + int physical_pin_rotate_offset(const std::string& port_name) const; + std::vector interconnect_names() const; std::string interconnect_circuit_model_name(const std::string& interc_name) const; public: /* Public mutators */ void set_operating_pb_type_name(const std::string& name); diff --git a/libopenfpga/libarchopenfpga/src/read_xml_pb_type_annotation.cpp b/libopenfpga/libarchopenfpga/src/read_xml_pb_type_annotation.cpp index 25e1e12f8..253053bf5 100644 --- a/libopenfpga/libarchopenfpga/src/read_xml_pb_type_annotation.cpp +++ b/libopenfpga/libarchopenfpga/src/read_xml_pb_type_annotation.cpp @@ -75,7 +75,6 @@ void read_xml_pb_type_annotation(pugi::xml_node& xml_pb_type, /* Find the name of pb_type */ const std::string& name_attr = get_attribute(xml_pb_type, "name", loc_data).as_string(); const std::string& physical_name_attr = get_attribute(xml_pb_type, "physical_pb_type_name", loc_data, pugiutil::ReqOpt::OPTIONAL).as_string(); - const std::string& physical_mode_name_attr = get_attribute(xml_pb_type, "physical_mode_name", loc_data, pugiutil::ReqOpt::OPTIONAL).as_string(); /* If both names are not empty, this is a operating pb_type */ if ( (false == name_attr.empty()) @@ -122,9 +121,11 @@ void read_xml_pb_type_annotation(pugi::xml_node& xml_pb_type, /* Parse mode bits which are applied to both pb_types */ pb_type_annotation.set_mode_bits(get_attribute(xml_pb_type, "mode_bits", loc_data, pugiutil::ReqOpt::OPTIONAL).as_string()); - /* If this is a physical pb_type, circuit model name is a mandatory attribute */ + /* If this is a physical pb_type, circuit model name is an optional attribute, + * which is applicable to leaf pb_type in the hierarchy + */ if (true == pb_type_annotation.is_physical_pb_type()) { - pb_type_annotation.set_circuit_model_name(get_attribute(xml_pb_type, "circuit_model_name", loc_data).as_string()); + pb_type_annotation.set_circuit_model_name(get_attribute(xml_pb_type, "circuit_model_name", loc_data, pugiutil::ReqOpt::OPTIONAL).as_string()); } /* If this is an operating pb_type, index factor and offset may be optional needed */ diff --git a/libopenfpga/libarchopenfpga/src/write_xml_openfpga_arch.cpp b/libopenfpga/libarchopenfpga/src/write_xml_openfpga_arch.cpp index e262735e4..b71853156 100644 --- a/libopenfpga/libarchopenfpga/src/write_xml_openfpga_arch.cpp +++ b/libopenfpga/libarchopenfpga/src/write_xml_openfpga_arch.cpp @@ -16,6 +16,7 @@ #include "write_xml_simulation_setting.h" #include "write_xml_config_protocol.h" #include "write_xml_routing_circuit.h" +#include "write_xml_pb_type_annotation.h" #include "write_xml_openfpga_arch.h" /******************************************************************** @@ -57,6 +58,10 @@ void write_xml_openfpga_arch(const char* fname, /* Write the direct connection circuit definition */ write_xml_direct_circuit(fp, fname, openfpga_arch.circuit_lib, openfpga_arch.direct2circuit); + + /* Write the pb_type annotations */ + openfpga::write_xml_pb_type_annotations(fp, fname, openfpga_arch. pb_type_annotations); + fp << "" << "\n"; /* Write the simulation */ diff --git a/libopenfpga/libarchopenfpga/src/write_xml_pb_type_annotation.cpp b/libopenfpga/libarchopenfpga/src/write_xml_pb_type_annotation.cpp new file mode 100644 index 000000000..a771c782d --- /dev/null +++ b/libopenfpga/libarchopenfpga/src/write_xml_pb_type_annotation.cpp @@ -0,0 +1,224 @@ +/******************************************************************** + * This file includes functions that outputs pb_type annotations to XML format + *******************************************************************/ +/* Headers from system goes first */ +#include +#include + +/* Headers from vtr util library */ +#include "vtr_assert.h" +#include "vtr_log.h" +#include "openfpga_digest.h" + +/* Headers from readarchopenfpga library */ +#include "write_xml_utils.h" +#include "write_xml_pb_type_annotation.h" + +/* namespace openfpga begins */ +namespace openfpga { + +/******************************************************************** + * Generate the full hierarchy name for a operating pb_type + *******************************************************************/ +static +std::string generate_operating_pb_type_hierarchy_name(const PbTypeAnnotation& pb_type_annotation) { + /* Iterate over the parent_pb_type and modes names, they should well match */ + VTR_ASSERT_SAFE(pb_type_annotation.operating_parent_pb_type_names().size() == pb_type_annotation.operating_parent_mode_names().size()); + + std::string hie_name; + + for (size_t i = 0 ; i < pb_type_annotation.operating_parent_pb_type_names().size(); ++i) { + hie_name += pb_type_annotation.operating_parent_pb_type_names()[i]; + hie_name += std::string("["); + hie_name += pb_type_annotation.operating_parent_mode_names()[i]; + hie_name += std::string("]"); + hie_name += std::string("."); + } + + /* Add the leaf pb_type */ + hie_name += pb_type_annotation.operating_pb_type_name(); + + return hie_name; +} + +/******************************************************************** + * Generate the full hierarchy name for a operating pb_type + *******************************************************************/ +static +std::string generate_physical_pb_type_hierarchy_name(const PbTypeAnnotation& pb_type_annotation) { + /* Iterate over the parent_pb_type and modes names, they should well match */ + VTR_ASSERT_SAFE(pb_type_annotation.physical_parent_pb_type_names().size() == pb_type_annotation.physical_parent_mode_names().size()); + + std::string hie_name; + + for (size_t i = 0 ; i < pb_type_annotation.physical_parent_pb_type_names().size(); ++i) { + hie_name += pb_type_annotation.physical_parent_pb_type_names()[i]; + hie_name += std::string("["); + hie_name += pb_type_annotation.physical_parent_mode_names()[i]; + hie_name += std::string("]"); + hie_name += std::string("."); + } + + /* Add the leaf pb_type */ + hie_name += pb_type_annotation.physical_pb_type_name(); + + return hie_name; +} + +/******************************************************************** + * Generate the full hierarchy name for a operating pb_type + *******************************************************************/ +static +std::string generate_physical_pb_port_name(const BasicPort& pb_port) { + std::string port_name; + + /* Output format: [:] */ + port_name += pb_port.get_name(); + port_name += std::string("["); + port_name += std::to_string(pb_port.get_lsb()); + port_name += std::string(":"); + port_name += std::to_string(pb_port.get_msb()); + port_name += std::string("]"); + + return port_name; +} + +/******************************************************************** + * A writer to output an pb_type interconnection annotation to XML format + *******************************************************************/ +static +void write_xml_interconnect_annotation(std::fstream& fp, + const char* fname, + const openfpga::PbTypeAnnotation& pb_type_annotation, + const std::string& interc_name) { + /* Validate the file stream */ + openfpga::check_file_stream(fname, fp); + + fp << "\t\t\t" << "" << "\n"; +} + +/******************************************************************** + * A writer to output an pb_type port annotation to XML format + *******************************************************************/ +static +void write_xml_pb_port_annotation(std::fstream& fp, + const char* fname, + const openfpga::PbTypeAnnotation& pb_type_annotation, + const std::string& port_name) { + /* Validate the file stream */ + openfpga::check_file_stream(fname, fp); + + fp << "\t\t\t" << "" << "\n"; +} + +/******************************************************************** + * A writer to output a device variation in a technology library to XML format + *******************************************************************/ +static +void write_xml_pb_type_annotation(std::fstream& fp, + const char* fname, + const openfpga::PbTypeAnnotation& pb_type_annotation) { + /* Validate the file stream */ + openfpga::check_file_stream(fname, fp); + + fp << "\t\t" << "" << "\n"; + return; + } + + fp << ">" << "\n"; + + /* Output interconnects if there are any */ + for (const std::string& interc_name : pb_type_annotation.interconnect_names()) { + write_xml_interconnect_annotation(fp, fname, pb_type_annotation, interc_name); + } + + /* Output pb_type ports if there are any */ + for (const std::string& port_name : pb_type_annotation.port_names()) { + write_xml_pb_port_annotation(fp, fname, pb_type_annotation, port_name); + } + + fp << "\t\t" << "\n"; +} + +/******************************************************************** + * A writer to output a number of pb_type annotations to XML format + *******************************************************************/ +void write_xml_pb_type_annotations(std::fstream& fp, + const char* fname, + const std::vector& pb_type_annotations) { + /* Validate the file stream */ + openfpga::check_file_stream(fname, fp); + + /* Write the root node for pb_type annotations, + * we apply a tab becuase pb_type annotations is just a subnode + * under the root node + */ + fp << "\t" << "" << "\n"; + + /* Write device model one by one */ + for (const PbTypeAnnotation& pb_type_annotation : pb_type_annotations) { + write_xml_pb_type_annotation(fp, fname, pb_type_annotation); + } + + /* Write the root node for pb_type annotations */ + fp << "\t" << "" << "\n"; +} + +} /* namespace openfpga ends */ diff --git a/libopenfpga/libarchopenfpga/src/write_xml_pb_type_annotation.h b/libopenfpga/libarchopenfpga/src/write_xml_pb_type_annotation.h new file mode 100644 index 000000000..e40b5ad4e --- /dev/null +++ b/libopenfpga/libarchopenfpga/src/write_xml_pb_type_annotation.h @@ -0,0 +1,22 @@ +#ifndef WRITE_XML_PB_TYPE_ANNOTATION_H +#define WRITE_XML_PB_TYPE_ANNOTATION_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include "pb_type_annotation.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ +/* namespace openfpga begins */ +namespace openfpga { + +void write_xml_pb_type_annotations(std::fstream& fp, + const char* fname, + const std::vector& pb_type_annotations); + +} /* namespace openfpga ends */ + +#endif diff --git a/libopenfpga/libarchopenfpga/src/write_xml_utils.cpp b/libopenfpga/libarchopenfpga/src/write_xml_utils.cpp index ad887e10c..e4d32e720 100644 --- a/libopenfpga/libarchopenfpga/src/write_xml_utils.cpp +++ b/libopenfpga/libarchopenfpga/src/write_xml_utils.cpp @@ -46,6 +46,21 @@ void write_xml_attribute(std::fstream& fp, fp << "\""; } +/******************************************************************** + * A most utilized function to write an XML attribute to file + * This accepts the value as a float + *******************************************************************/ +void write_xml_attribute(std::fstream& fp, + const char* attr, + const int& value) { + /* Validate the file stream */ + openfpga::valid_file_stream(fp); + + fp << " " << attr << "=\""; + fp << value; + fp << "\""; +} + /******************************************************************** * A most utilized function to write an XML attribute to file * This accepts the value as a float diff --git a/libopenfpga/libarchopenfpga/src/write_xml_utils.h b/libopenfpga/libarchopenfpga/src/write_xml_utils.h index a827727e4..3976d78b9 100644 --- a/libopenfpga/libarchopenfpga/src/write_xml_utils.h +++ b/libopenfpga/libarchopenfpga/src/write_xml_utils.h @@ -18,6 +18,10 @@ void write_xml_attribute(std::fstream& fp, const char* attr, const bool& value); +void write_xml_attribute(std::fstream& fp, + const char* attr, + const int& value); + void write_xml_attribute(std::fstream& fp, const char* attr, const float& value); diff --git a/libopenfpga/libopenfpgautil/src/openfpga_pb_parser.cpp b/libopenfpga/libopenfpgautil/src/openfpga_pb_parser.cpp index ee674b928..3c174f589 100644 --- a/libopenfpga/libopenfpgautil/src/openfpga_pb_parser.cpp +++ b/libopenfpga/libopenfpgautil/src/openfpga_pb_parser.cpp @@ -67,6 +67,7 @@ void PbParser::parse() { /* The last pb is the leaf node. * It should NOT be empty and should NOT contain any brackets! */ + VTR_ASSERT(0 < pb_tokens.size()); VTR_ASSERT(false == pb_tokens.back().empty()); VTR_ASSERT(pb_tokens.back().find(bracket_.x()) == std::string::npos); VTR_ASSERT(pb_tokens.back().find(bracket_.y()) == std::string::npos); diff --git a/libopenfpga/libopenfpgautil/src/openfpga_port_parser.cpp b/libopenfpga/libopenfpgautil/src/openfpga_port_parser.cpp index 78f357b82..2006275dc 100644 --- a/libopenfpga/libopenfpgautil/src/openfpga_port_parser.cpp +++ b/libopenfpga/libopenfpgautil/src/openfpga_port_parser.cpp @@ -64,7 +64,7 @@ void PortParser::parse() { /* If we only have one token */ if (1 == port_tokens.size()) { - port_.set_width(0); + port_.set_width(1); return; /* We can finish here */ } @@ -75,21 +75,22 @@ void PortParser::parse() { VTR_ASSERT_SAFE (1 == port_tokens.size()); /* Split the pin string now */ - tokenizer.set_data(port_tokens[0]); + tokenizer.set_data(pin_tokens[0]); pin_tokens = tokenizer.split(delim_); /* Check if we have LSB and MSB or just one */ if ( 1 == pin_tokens.size() ) { /* Single pin */ - port_.set_width(stoi(pin_tokens[0]), stoi(pin_tokens[0])); + port_.set_width(std::stoi(pin_tokens[0]), std::stoi(pin_tokens[0])); } else if ( 2 == pin_tokens.size() ) { - /* A number of pin */ - port_.set_width(stoi(pin_tokens[0]), stoi(pin_tokens[1])); - } - - /* Re-order to ensure LSB <= MSB */ - if (false == port_.is_valid()) { - port_.revert(); + /* A number of pins. + * Note that we always use the LSB for token[0] and MSB for token[1] + */ + if (std::stoi(pin_tokens[1]) < std::stoi(pin_tokens[0])) { + port_.set_width(std::stoi(pin_tokens[1]), std::stoi(pin_tokens[0])); + } else { + port_.set_width(std::stoi(pin_tokens[0]), std::stoi(pin_tokens[1])); + } } return; From 93ab4b8dd29b50d5698300d201a98b10a1557090 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 26 Jan 2020 17:30:46 -0700 Subject: [PATCH 022/645] add test case for openfpga shell --- ...rac_N10_frac_chain_depop50_mem32K_40nm.xml | 1438 ++ openfpga/test_blif/riscv_core_lut6.blif | 19187 ++++++++++++++++ openfpga/test_script/riscv_core.openfpga | 2 + 3 files changed, 20627 insertions(+) create mode 100644 openfpga/test_arch/k6_frac_N10_frac_chain_depop50_mem32K_40nm.xml create mode 100644 openfpga/test_blif/riscv_core_lut6.blif create mode 100644 openfpga/test_script/riscv_core.openfpga diff --git a/openfpga/test_arch/k6_frac_N10_frac_chain_depop50_mem32K_40nm.xml b/openfpga/test_arch/k6_frac_N10_frac_chain_depop50_mem32K_40nm.xml new file mode 100644 index 000000000..4594c6496 --- /dev/null +++ b/openfpga/test_arch/k6_frac_N10_frac_chain_depop50_mem32K_40nm.xml @@ -0,0 +1,1438 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + io.outpad io.inpad io.clock + io.outpad io.inpad io.clock + io.outpad io.inpad io.clock + io.outpad io.inpad io.clock + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 235e-12 + 235e-12 + 235e-12 + 235e-12 + 235e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 195e-12 + 195e-12 + 195e-12 + 195e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga/test_blif/riscv_core_lut6.blif b/openfpga/test_blif/riscv_core_lut6.blif new file mode 100644 index 000000000..a313bebc1 --- /dev/null +++ b/openfpga/test_blif/riscv_core_lut6.blif @@ -0,0 +1,19187 @@ +# Generated by Yosys 0.8+599 (git sha1 463f710, gcc 4.8.5 -fPIC -Os) + +.model riscv_core +.inputs clock reset bus_read_data[0] bus_read_data[1] bus_read_data[2] bus_read_data[3] bus_read_data[4] bus_read_data[5] bus_read_data[6] bus_read_data[7] bus_read_data[8] bus_read_data[9] bus_read_data[10] bus_read_data[11] bus_read_data[12] bus_read_data[13] bus_read_data[14] bus_read_data[15] bus_read_data[16] bus_read_data[17] bus_read_data[18] bus_read_data[19] bus_read_data[20] bus_read_data[21] bus_read_data[22] bus_read_data[23] bus_read_data[24] bus_read_data[25] bus_read_data[26] bus_read_data[27] bus_read_data[28] bus_read_data[29] bus_read_data[30] bus_read_data[31] inst[0] inst[1] inst[2] inst[3] inst[4] inst[5] inst[6] inst[7] inst[8] inst[9] inst[10] inst[11] inst[12] inst[13] inst[14] inst[15] inst[16] inst[17] inst[18] inst[19] inst[20] inst[21] inst[22] inst[23] inst[24] inst[25] inst[26] inst[27] inst[28] inst[29] inst[30] inst[31] rs1_data[0] rs1_data[1] rs1_data[2] rs1_data[3] rs1_data[4] rs1_data[5] rs1_data[6] rs1_data[7] rs1_data[8] rs1_data[9] rs1_data[10] rs1_data[11] rs1_data[12] rs1_data[13] rs1_data[14] rs1_data[15] rs1_data[16] rs1_data[17] rs1_data[18] rs1_data[19] rs1_data[20] rs1_data[21] rs1_data[22] rs1_data[23] rs1_data[24] rs1_data[25] rs1_data[26] rs1_data[27] rs1_data[28] rs1_data[29] rs1_data[30] rs1_data[31] rs2_data[0] rs2_data[1] rs2_data[2] rs2_data[3] rs2_data[4] rs2_data[5] rs2_data[6] rs2_data[7] rs2_data[8] rs2_data[9] rs2_data[10] rs2_data[11] rs2_data[12] rs2_data[13] rs2_data[14] rs2_data[15] rs2_data[16] rs2_data[17] rs2_data[18] rs2_data[19] rs2_data[20] rs2_data[21] rs2_data[22] rs2_data[23] rs2_data[24] rs2_data[25] rs2_data[26] rs2_data[27] rs2_data[28] rs2_data[29] rs2_data[30] rs2_data[31] +.outputs bus_address[0] bus_address[1] bus_address[2] bus_address[3] bus_address[4] bus_address[5] bus_address[6] bus_address[7] bus_address[8] bus_address[9] bus_address[10] bus_address[11] bus_address[12] bus_address[13] bus_address[14] bus_address[15] bus_address[16] bus_address[17] bus_address[18] bus_address[19] bus_address[20] bus_address[21] bus_address[22] bus_address[23] bus_address[24] bus_address[25] bus_address[26] bus_address[27] bus_address[28] bus_address[29] bus_address[30] bus_address[31] bus_write_data[0] bus_write_data[1] bus_write_data[2] bus_write_data[3] bus_write_data[4] bus_write_data[5] bus_write_data[6] bus_write_data[7] bus_write_data[8] bus_write_data[9] bus_write_data[10] bus_write_data[11] bus_write_data[12] bus_write_data[13] bus_write_data[14] bus_write_data[15] bus_write_data[16] bus_write_data[17] bus_write_data[18] bus_write_data[19] bus_write_data[20] bus_write_data[21] bus_write_data[22] bus_write_data[23] bus_write_data[24] bus_write_data[25] bus_write_data[26] bus_write_data[27] bus_write_data[28] bus_write_data[29] bus_write_data[30] bus_write_data[31] bus_byte_enable[0] bus_byte_enable[1] bus_byte_enable[2] bus_byte_enable[3] bus_read_enable bus_write_enable pc[0] pc[1] pc[2] pc[3] pc[4] pc[5] pc[6] pc[7] pc[8] pc[9] pc[10] pc[11] pc[12] pc[13] pc[14] pc[15] pc[16] pc[17] pc[18] pc[19] pc[20] pc[21] pc[22] pc[23] pc[24] pc[25] pc[26] pc[27] pc[28] pc[29] pc[30] pc[31] regfile_write_enable rd_address[0] rd_address[1] rd_address[2] rd_address[3] rd_address[4] rs1_address[0] rs1_address[1] rs1_address[2] rs1_address[3] rs1_address[4] rs2_address[0] rs2_address[1] rs2_address[2] rs2_address[3] rs2_address[4] rd_data[0] rd_data[1] rd_data[2] rd_data[3] rd_data[4] rd_data[5] rd_data[6] rd_data[7] rd_data[8] rd_data[9] rd_data[10] rd_data[11] rd_data[12] rd_data[13] rd_data[14] rd_data[15] rd_data[16] rd_data[17] rd_data[18] rd_data[19] rd_data[20] rd_data[21] rd_data[22] rd_data[23] rd_data[24] rd_data[25] rd_data[26] rd_data[27] rd_data[28] rd_data[29] rd_data[30] rd_data[31] +.names $false +.names $true +1 +.names $undef +.names $abc$8517$new_n299_ $abc$8517$new_n303_ inst[2] regfile_write_enable +000 1 +001 1 +010 1 +011 1 +110 1 +.names $abc$8517$new_n302_ $abc$8517$new_n301_ $abc$8517$new_n300_ $abc$8517$new_n299_ +000 1 +.names inst[1] inst[0] inst[2] inst[4] inst[3] inst[6] $abc$8517$new_n300_ +111100 1 +.names inst[1] inst[0] inst[4] inst[2] inst[3] inst[6] $abc$8517$new_n301_ +111000 1 +.names inst[1] inst[0] inst[2] inst[6] inst[5] inst[4] $abc$8517$new_n302_ +111110 1 +.names inst[1] inst[0] inst[3] inst[6] inst[5] inst[4] $abc$8517$new_n303_ +110000 1 +.names $abc$8517$new_n1349_ $abc$8517$new_n1352_ $abc$8517$new_n466_ $abc$8517$new_n493_ $abc$8517$new_n1344_ bus_address[0] +00000 1 +00001 1 +00010 1 +00011 1 +00100 1 +00101 1 +00110 1 +00111 1 +01100 1 +10000 1 +10001 1 +10010 1 +10011 1 +10100 1 +10101 1 +10110 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n307_ $abc$8517$new_n374_ $abc$8517$new_n407_ $abc$8517$new_n404_ $abc$8517$new_n409_ $abc$8517$new_n340_ $abc$8517$new_n306_ +100100 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n308_ $abc$8517$new_n322_ $abc$8517$new_n336_ $abc$8517$new_n307_ +110 1 +.names $abc$8517$new_n319_ $abc$8517$new_n316_ $abc$8517$new_n309_ $abc$8517$new_n308_ +000 1 +.names $abc$8517$new_n315_ $abc$8517$new_n310_ $abc$8517$new_n314_ inst[31] $abc$8517$new_n312_ rs2_data[19] $abc$8517$new_n309_ +000000 1 +000001 1 +000010 1 +000011 1 +000110 1 +000111 1 +010000 1 +010010 1 +010100 1 +010110 1 +011000 1 +011010 1 +011100 1 +011110 1 +100100 1 +100101 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110001 1 +110011 1 +110101 1 +110111 1 +111001 1 +111011 1 +111101 1 +111111 1 +.names $abc$8517$new_n311_ inst[4] inst[6] $abc$8517$new_n302_ inst[5] inst[2] $abc$8517$new_n310_ +000000 1 +000001 1 +000010 1 +000011 1 +001000 1 +001001 1 +001010 1 +001011 1 +010000 1 +010001 1 +010010 1 +010011 1 +011000 1 +011001 1 +011010 1 +011011 1 +100001 1 +100011 1 +101000 1 +101001 1 +101010 1 +101011 1 +110010 1 +110011 1 +111000 1 +111001 1 +111010 1 +111011 1 +.names inst[1] inst[0] inst[3] $abc$8517$new_n311_ +110 1 +.names $abc$8517$new_n313_ inst[3] inst[6] inst[5] inst[4] inst[2] $abc$8517$new_n312_ +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +000110 1 +000111 1 +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +100011 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101110 1 +101111 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names inst[1] inst[0] $abc$8517$new_n313_ +11 1 +.names inst[19] $abc$8517$new_n300_ inst[3] $abc$8517$new_n302_ $abc$8517$new_n314_ +1011 1 +1100 1 +1101 1 +1110 1 +1111 1 +.names rs1_data[19] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[19] $abc$8517$new_n315_ +000000 1 +000001 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001001 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011110 1 +100010 1 +100110 1 +101010 1 +101100 1 +101110 1 +111100 1 +111110 1 +.names $abc$8517$new_n318_ $abc$8517$new_n310_ $abc$8517$new_n317_ inst[31] $abc$8517$new_n312_ rs2_data[18] $abc$8517$new_n316_ +000000 1 +000001 1 +000010 1 +000011 1 +000110 1 +000111 1 +010000 1 +010010 1 +010100 1 +010110 1 +011000 1 +011010 1 +011100 1 +011110 1 +100100 1 +100101 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110001 1 +110011 1 +110101 1 +110111 1 +111001 1 +111011 1 +111101 1 +111111 1 +.names inst[18] $abc$8517$new_n300_ inst[3] $abc$8517$new_n302_ $abc$8517$new_n317_ +1011 1 +1100 1 +1101 1 +1110 1 +1111 1 +.names rs1_data[18] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[18] $abc$8517$new_n318_ +000000 1 +000001 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001001 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011110 1 +100010 1 +100110 1 +101010 1 +101100 1 +101110 1 +111100 1 +111110 1 +.names $abc$8517$new_n321_ $abc$8517$new_n310_ $abc$8517$new_n320_ inst[31] $abc$8517$new_n312_ rs2_data[17] $abc$8517$new_n319_ +000000 1 +000001 1 +000010 1 +000011 1 +000110 1 +000111 1 +010000 1 +010010 1 +010100 1 +010110 1 +011000 1 +011010 1 +011100 1 +011110 1 +100100 1 +100101 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110001 1 +110011 1 +110101 1 +110111 1 +111001 1 +111011 1 +111101 1 +111111 1 +.names inst[17] $abc$8517$new_n300_ inst[3] $abc$8517$new_n302_ $abc$8517$new_n320_ +1011 1 +1100 1 +1101 1 +1110 1 +1111 1 +.names rs1_data[17] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[17] $abc$8517$new_n321_ +000000 1 +000001 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001001 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011110 1 +100010 1 +100110 1 +101010 1 +101100 1 +101110 1 +111100 1 +111110 1 +.names $abc$8517$new_n333_ $abc$8517$new_n330_ $abc$8517$new_n327_ $abc$8517$new_n323_ $abc$8517$new_n322_ +0000 1 +.names $abc$8517$new_n326_ $abc$8517$new_n310_ $abc$8517$new_n325_ inst[31] $abc$8517$new_n324_ rs2_data[23] $abc$8517$new_n323_ +000000 1 +000001 1 +000010 1 +000011 1 +000110 1 +000111 1 +010000 1 +010010 1 +010100 1 +010110 1 +011000 1 +011010 1 +011100 1 +011110 1 +100100 1 +100101 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110001 1 +110011 1 +110101 1 +110111 1 +111001 1 +111011 1 +111101 1 +111111 1 +.names $abc$8517$new_n311_ inst[5] inst[2] inst[6] inst[4] $abc$8517$new_n302_ $abc$8517$new_n324_ +000000 1 +000010 1 +000100 1 +000110 1 +001000 1 +001010 1 +001100 1 +001110 1 +010000 1 +010010 1 +010100 1 +010110 1 +011000 1 +011010 1 +011100 1 +011110 1 +100100 1 +100110 1 +101010 1 +101100 1 +101110 1 +110010 1 +110110 1 +111010 1 +111100 1 +111110 1 +.names inst[23] $abc$8517$new_n300_ $abc$8517$new_n325_ +11 1 +.names rs1_data[23] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[23] $abc$8517$new_n326_ +000000 1 +000001 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001001 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011110 1 +100010 1 +100110 1 +101010 1 +101100 1 +101110 1 +111100 1 +111110 1 +.names $abc$8517$new_n329_ $abc$8517$new_n310_ $abc$8517$new_n328_ inst[31] $abc$8517$new_n324_ rs2_data[22] $abc$8517$new_n327_ +000000 1 +000001 1 +000010 1 +000011 1 +000110 1 +000111 1 +010000 1 +010010 1 +010100 1 +010110 1 +011000 1 +011010 1 +011100 1 +011110 1 +100100 1 +100101 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110001 1 +110011 1 +110101 1 +110111 1 +111001 1 +111011 1 +111101 1 +111111 1 +.names inst[22] $abc$8517$new_n300_ $abc$8517$new_n328_ +11 1 +.names rs1_data[22] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[22] $abc$8517$new_n329_ +000000 1 +000001 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001001 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011110 1 +100010 1 +100110 1 +101010 1 +101100 1 +101110 1 +111100 1 +111110 1 +.names $abc$8517$new_n332_ $abc$8517$new_n310_ $abc$8517$new_n331_ inst[31] $abc$8517$new_n324_ rs2_data[21] $abc$8517$new_n330_ +000000 1 +000001 1 +000010 1 +000011 1 +000110 1 +000111 1 +010000 1 +010010 1 +010100 1 +010110 1 +011000 1 +011010 1 +011100 1 +011110 1 +100100 1 +100101 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110001 1 +110011 1 +110101 1 +110111 1 +111001 1 +111011 1 +111101 1 +111111 1 +.names inst[21] $abc$8517$new_n300_ $abc$8517$new_n331_ +11 1 +.names rs1_data[21] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[21] $abc$8517$new_n332_ +000000 1 +000001 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001001 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011110 1 +100010 1 +100110 1 +101010 1 +101100 1 +101110 1 +111100 1 +111110 1 +.names $abc$8517$new_n335_ $abc$8517$new_n310_ $abc$8517$new_n334_ inst[31] $abc$8517$new_n324_ rs2_data[20] $abc$8517$new_n333_ +000000 1 +000001 1 +000010 1 +000011 1 +000110 1 +000111 1 +010000 1 +010010 1 +010100 1 +010110 1 +011000 1 +011010 1 +011100 1 +011110 1 +100100 1 +100101 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110001 1 +110011 1 +110101 1 +110111 1 +111001 1 +111011 1 +111101 1 +111111 1 +.names inst[20] $abc$8517$new_n300_ $abc$8517$new_n334_ +11 1 +.names rs1_data[20] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[20] $abc$8517$new_n335_ +000000 1 +000001 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001001 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011110 1 +100010 1 +100110 1 +101010 1 +101100 1 +101110 1 +111100 1 +111110 1 +.names $abc$8517$new_n339_ $abc$8517$new_n337_ $abc$8517$new_n336_ +01 1 +10 1 +.names $abc$8517$new_n310_ $abc$8517$new_n312_ rs2_data[16] inst[31] inst[16] $abc$8517$new_n337_ +00000 1 +00001 1 +00100 1 +00101 1 +01000 1 +01010 1 +01100 1 +01110 1 +10000 1 +10001 1 +10010 1 +10011 1 +11000 1 +11001 1 +11010 1 +11011 1 +.names $abc$8517$new_n300_ $abc$8517$new_n302_ inst[3] $abc$8517$new_n338_ +000 1 +001 1 +010 1 +.names rs1_data[16] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[16] $abc$8517$new_n339_ +000000 1 +000001 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001001 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011110 1 +100010 1 +100110 1 +101010 1 +101100 1 +101110 1 +111100 1 +111110 1 +.names $abc$8517$new_n369_ $abc$8517$new_n341_ $abc$8517$new_n367_ $abc$8517$new_n365_ $abc$8517$new_n351_ $abc$8517$new_n340_ +10000 1 +10001 1 +10010 1 +10011 1 +10100 1 +10101 1 +10110 1 +10111 1 +11100 1 +.names $abc$8517$new_n345_ $abc$8517$new_n350_ $abc$8517$new_n344_ $abc$8517$new_n342_ $abc$8517$new_n341_ +0000 1 +0010 1 +0011 1 +0100 1 +0110 1 +0111 1 +1010 1 +1100 1 +1110 1 +1111 1 +.names $abc$8517$new_n310_ rs2_data[5] inst[25] $abc$8517$new_n324_ $abc$8517$new_n342_ +0000 1 +0001 1 +0011 1 +0100 1 +0101 1 +0111 1 +1000 1 +1001 1 +1010 1 +1011 1 +.names $abc$8517$new_n344_ $abc$8517$new_n310_ rs2_data[5] inst[25] $abc$8517$new_n324_ $abc$8517$new_n343_ +10010 1 +10110 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names rs1_data[5] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[5] $abc$8517$new_n344_ +000000 1 +000001 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001001 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011110 1 +100010 1 +100110 1 +101010 1 +101100 1 +101110 1 +111100 1 +111110 1 +.names $abc$8517$new_n310_ rs2_data[4] $abc$8517$new_n346_ inst[11] inst[24] $abc$8517$new_n347_ $abc$8517$new_n345_ +000000 1 +000001 1 +000011 1 +001000 1 +001001 1 +001011 1 +001100 1 +001101 1 +001111 1 +010000 1 +010001 1 +010011 1 +011000 1 +011001 1 +011011 1 +011100 1 +011101 1 +011111 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +.names inst[5] $abc$8517$new_n311_ inst[4] inst[6] inst[2] $abc$8517$new_n346_ +00000 1 +00001 1 +00010 1 +00011 1 +00100 1 +00101 1 +00110 1 +00111 1 +01000 1 +01001 1 +01010 1 +01011 1 +01100 1 +01101 1 +01110 1 +01111 1 +10000 1 +10001 1 +10010 1 +10011 1 +10100 1 +10101 1 +10110 1 +10111 1 +11011 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n303_ $abc$8517$new_n302_ $abc$8517$new_n348_ $abc$8517$new_n349_ $abc$8517$new_n347_ +0000 1 +0001 1 +0010 1 +.names inst[1] inst[0] inst[2] inst[3] $abc$8517$new_n348_ +1100 1 +.names inst[6] inst[5] $abc$8517$new_n349_ +00 1 +.names rs1_data[4] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[4] $abc$8517$new_n350_ +000000 1 +000001 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001001 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011110 1 +100010 1 +100110 1 +101010 1 +101100 1 +101110 1 +111100 1 +111110 1 +.names $abc$8517$new_n364_ $abc$8517$new_n362_ $abc$8517$new_n359_ $abc$8517$new_n363_ $abc$8517$new_n356_ $abc$8517$new_n352_ $abc$8517$new_n351_ +000000 1 +000100 1 +000101 1 +000110 1 +110000 1 +110100 1 +110101 1 +110110 1 +.names $abc$8517$new_n310_ $abc$8517$new_n353_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n352_ +00000 1 +00100 1 +10000 1 +10001 1 +10010 1 +10011 1 +.names rs1_data[0] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[0] $abc$8517$new_n353_ +000000 1 +000001 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001001 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011110 1 +100010 1 +100110 1 +101010 1 +101100 1 +101110 1 +111100 1 +111110 1 +.names inst[7] $abc$8517$new_n311_ inst[5] inst[6] inst[4] $abc$8517$new_n354_ +11100 1 +.names inst[20] $abc$8517$new_n303_ $abc$8517$new_n349_ $abc$8517$new_n348_ $abc$8517$new_n302_ inst[3] $abc$8517$new_n355_ +100010 1 +100110 1 +101010 1 +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n310_ rs2_data[1] $abc$8517$new_n358_ $abc$8517$new_n357_ $abc$8517$new_n356_ +0000 1 +0100 1 +1000 1 +1001 1 +1010 1 +1011 1 +.names inst[8] inst[5] $abc$8517$new_n311_ inst[4] inst[6] inst[2] $abc$8517$new_n357_ +111000 1 +111001 1 +111010 1 +.names inst[21] $abc$8517$new_n303_ $abc$8517$new_n302_ $abc$8517$new_n349_ $abc$8517$new_n348_ $abc$8517$new_n358_ +10011 1 +10100 1 +10101 1 +10110 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n361_ $abc$8517$new_n310_ $abc$8517$new_n360_ inst[23] $abc$8517$new_n347_ rs2_data[3] $abc$8517$new_n359_ +000000 1 +000001 1 +000010 1 +000011 1 +000110 1 +000111 1 +010000 1 +010010 1 +010100 1 +010110 1 +011000 1 +011010 1 +011100 1 +011110 1 +100100 1 +100101 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110001 1 +110011 1 +110101 1 +110111 1 +111001 1 +111011 1 +111101 1 +111111 1 +.names inst[10] inst[5] $abc$8517$new_n311_ inst[4] inst[6] inst[2] $abc$8517$new_n360_ +111000 1 +111001 1 +111010 1 +.names rs1_data[3] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[3] $abc$8517$new_n361_ +000000 1 +000001 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001001 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011110 1 +100010 1 +100110 1 +101010 1 +101100 1 +101110 1 +111100 1 +111110 1 +.names $abc$8517$new_n310_ rs2_data[2] $abc$8517$new_n346_ inst[9] inst[22] $abc$8517$new_n347_ $abc$8517$new_n362_ +000000 1 +000001 1 +000011 1 +001000 1 +001001 1 +001011 1 +001100 1 +001101 1 +001111 1 +010000 1 +010001 1 +010011 1 +011000 1 +011001 1 +011011 1 +011100 1 +011101 1 +011111 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +.names rs1_data[1] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[1] $abc$8517$new_n363_ +000000 1 +000001 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001001 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011110 1 +100010 1 +100110 1 +101010 1 +101100 1 +101110 1 +111100 1 +111110 1 +.names rs1_data[2] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[2] $abc$8517$new_n364_ +000000 1 +000001 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001001 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011110 1 +100010 1 +100110 1 +101010 1 +101100 1 +101110 1 +111100 1 +111110 1 +.names $abc$8517$new_n364_ $abc$8517$new_n362_ $abc$8517$new_n366_ $abc$8517$new_n365_ +100 1 +.names $abc$8517$new_n310_ $abc$8517$new_n361_ $abc$8517$new_n360_ rs2_data[3] $abc$8517$new_n347_ inst[23] $abc$8517$new_n366_ +000000 1 +000010 1 +000011 1 +000100 1 +000110 1 +000111 1 +100000 1 +100001 1 +100010 1 +100011 1 +101000 1 +101001 1 +101010 1 +101011 1 +.names $abc$8517$new_n343_ $abc$8517$new_n345_ $abc$8517$new_n350_ $abc$8517$new_n361_ $abc$8517$new_n368_ $abc$8517$new_n367_ +00000 1 +00001 1 +00011 1 +01000 1 +01001 1 +01011 1 +01100 1 +01101 1 +01111 1 +.names $abc$8517$new_n310_ $abc$8517$new_n360_ rs2_data[3] $abc$8517$new_n347_ inst[23] $abc$8517$new_n368_ +00000 1 +00010 1 +00011 1 +00100 1 +00110 1 +00111 1 +10000 1 +10001 1 +10010 1 +10011 1 +11000 1 +11001 1 +11010 1 +11011 1 +.names $abc$8517$new_n370_ $abc$8517$new_n372_ $abc$8517$new_n373_ $abc$8517$new_n369_ +000 1 +010 1 +011 1 +.names $abc$8517$new_n371_ $abc$8517$new_n310_ rs2_data[7] inst[27] $abc$8517$new_n324_ $abc$8517$new_n370_ +10010 1 +10110 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names rs1_data[7] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[7] $abc$8517$new_n371_ +000000 1 +000001 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001001 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011110 1 +100010 1 +100110 1 +101010 1 +101100 1 +101110 1 +111100 1 +111110 1 +.names $abc$8517$new_n310_ rs2_data[6] inst[26] $abc$8517$new_n324_ $abc$8517$new_n372_ +0000 1 +0001 1 +0011 1 +0100 1 +0101 1 +0111 1 +1000 1 +1001 1 +1010 1 +1011 1 +.names rs1_data[6] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[6] $abc$8517$new_n373_ +000000 1 +000001 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001001 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011110 1 +100010 1 +100110 1 +101010 1 +101100 1 +101110 1 +111100 1 +111110 1 +.names $abc$8517$new_n375_ $abc$8517$new_n381_ $abc$8517$new_n829_ $abc$8517$new_n402_ $abc$8517$new_n395_ $abc$8517$new_n387_ $abc$8517$new_n374_ +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n380_ $abc$8517$new_n376_ $abc$8517$new_n377_ $abc$8517$new_n375_ +000 1 +110 1 +.names $abc$8517$new_n310_ $abc$8517$new_n312_ rs2_data[14] inst[31] inst[14] $abc$8517$new_n376_ +00000 1 +00001 1 +00100 1 +00101 1 +01000 1 +01010 1 +01100 1 +01110 1 +10000 1 +10001 1 +10010 1 +10011 1 +11000 1 +11001 1 +11010 1 +11011 1 +.names $abc$8517$new_n379_ $abc$8517$new_n310_ $abc$8517$new_n378_ inst[31] $abc$8517$new_n312_ rs2_data[15] $abc$8517$new_n377_ +000000 1 +000001 1 +000010 1 +000011 1 +000110 1 +000111 1 +010000 1 +010010 1 +010100 1 +010110 1 +011000 1 +011010 1 +011100 1 +011110 1 +100100 1 +100101 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110001 1 +110011 1 +110101 1 +110111 1 +111001 1 +111011 1 +111101 1 +111111 1 +.names inst[15] $abc$8517$new_n300_ inst[3] $abc$8517$new_n302_ $abc$8517$new_n378_ +1011 1 +1100 1 +1101 1 +1110 1 +1111 1 +.names rs1_data[15] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[15] $abc$8517$new_n379_ +000000 1 +000001 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001001 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011110 1 +100010 1 +100110 1 +101010 1 +101100 1 +101110 1 +111100 1 +111110 1 +.names rs1_data[14] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[14] $abc$8517$new_n380_ +000000 1 +000001 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001001 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011110 1 +100010 1 +100110 1 +101010 1 +101100 1 +101110 1 +111100 1 +111110 1 +.names $abc$8517$new_n386_ $abc$8517$new_n385_ $abc$8517$new_n382_ $abc$8517$new_n381_ +000 1 +110 1 +.names $abc$8517$new_n384_ $abc$8517$new_n310_ $abc$8517$new_n383_ inst[31] $abc$8517$new_n312_ rs2_data[13] $abc$8517$new_n382_ +000000 1 +000001 1 +000010 1 +000011 1 +000110 1 +000111 1 +010000 1 +010010 1 +010100 1 +010110 1 +011000 1 +011010 1 +011100 1 +011110 1 +100100 1 +100101 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110001 1 +110011 1 +110101 1 +110111 1 +111001 1 +111011 1 +111101 1 +111111 1 +.names inst[13] $abc$8517$new_n300_ inst[3] $abc$8517$new_n302_ $abc$8517$new_n383_ +1011 1 +1100 1 +1101 1 +1110 1 +1111 1 +.names rs1_data[13] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[13] $abc$8517$new_n384_ +000000 1 +000001 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001001 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011110 1 +100010 1 +100110 1 +101010 1 +101100 1 +101110 1 +111100 1 +111110 1 +.names $abc$8517$new_n310_ $abc$8517$new_n312_ rs2_data[12] inst[31] inst[12] $abc$8517$new_n385_ +00000 1 +00001 1 +00100 1 +00101 1 +01000 1 +01010 1 +01100 1 +01110 1 +10000 1 +10001 1 +10010 1 +10011 1 +11000 1 +11001 1 +11010 1 +11011 1 +.names rs1_data[12] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[12] $abc$8517$new_n386_ +000000 1 +000001 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001001 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011110 1 +100010 1 +100110 1 +101010 1 +101100 1 +101110 1 +111100 1 +111110 1 +.names $abc$8517$new_n394_ $abc$8517$new_n388_ $abc$8517$new_n392_ $abc$8517$new_n387_ +000 1 +110 1 +.names $abc$8517$new_n310_ $abc$8517$new_n389_ rs2_data[11] $abc$8517$new_n390_ inst[31] $abc$8517$new_n388_ +00000 1 +00010 1 +00100 1 +00110 1 +01000 1 +01001 1 +01100 1 +01101 1 +10000 1 +10001 1 +10010 1 +10011 1 +11000 1 +11001 1 +11010 1 +11011 1 +.names $abc$8517$new_n313_ inst[3] inst[2] inst[5] inst[6] inst[4] $abc$8517$new_n389_ +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +000110 1 +000111 1 +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100101 1 +100110 1 +100111 1 +101001 1 +101010 1 +101011 1 +101101 1 +101111 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names inst[20] inst[3] $abc$8517$new_n302_ $abc$8517$new_n390_ +111 1 +.names inst[7] inst[6] inst[5] $abc$8517$new_n348_ inst[4] $abc$8517$new_n391_ +11110 1 +.names $abc$8517$new_n393_ $abc$8517$new_n310_ inst[30] $abc$8517$new_n324_ rs2_data[10] $abc$8517$new_n392_ +00000 1 +00001 1 +00010 1 +00011 1 +00110 1 +00111 1 +01000 1 +01010 1 +01100 1 +01110 1 +10100 1 +10101 1 +11001 1 +11011 1 +11101 1 +11111 1 +.names rs1_data[10] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[10] $abc$8517$new_n393_ +000000 1 +000001 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001001 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011110 1 +100010 1 +100110 1 +101010 1 +101100 1 +101110 1 +111100 1 +111110 1 +.names rs1_data[11] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[11] $abc$8517$new_n394_ +000000 1 +000001 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001001 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011110 1 +100010 1 +100110 1 +101010 1 +101100 1 +101110 1 +111100 1 +111110 1 +.names $abc$8517$new_n399_ $abc$8517$new_n398_ $abc$8517$new_n397_ $abc$8517$new_n396_ $abc$8517$new_n395_ +0100 1 +0110 1 +1000 1 +1100 1 +1101 1 +1110 1 +.names $abc$8517$new_n310_ rs2_data[9] inst[29] $abc$8517$new_n324_ $abc$8517$new_n396_ +0000 1 +0001 1 +0011 1 +0100 1 +0101 1 +0111 1 +1000 1 +1001 1 +1010 1 +1011 1 +.names $abc$8517$new_n310_ rs2_data[8] inst[28] $abc$8517$new_n324_ $abc$8517$new_n397_ +0000 1 +0001 1 +0011 1 +0100 1 +0101 1 +0111 1 +1000 1 +1001 1 +1010 1 +1011 1 +.names rs1_data[9] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[9] $abc$8517$new_n398_ +000000 1 +000001 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001001 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011110 1 +100010 1 +100110 1 +101010 1 +101100 1 +101110 1 +111100 1 +111110 1 +.names rs1_data[8] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[8] $abc$8517$new_n399_ +000000 1 +000001 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001001 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011110 1 +100010 1 +100110 1 +101010 1 +101100 1 +101110 1 +111100 1 +111110 1 +.names $abc$8517$new_n310_ $abc$8517$new_n383_ rs2_data[13] $abc$8517$new_n312_ inst[31] $abc$8517$new_n401_ +00000 1 +00010 1 +00011 1 +00100 1 +00110 1 +00111 1 +10000 1 +10001 1 +10010 1 +10011 1 +11000 1 +11001 1 +11010 1 +11011 1 +.names $abc$8517$new_n394_ $abc$8517$new_n403_ $abc$8517$new_n388_ $abc$8517$new_n402_ +010 1 +100 1 +110 1 +111 1 +.names $abc$8517$new_n393_ $abc$8517$new_n310_ rs2_data[10] inst[30] $abc$8517$new_n324_ $abc$8517$new_n403_ +10010 1 +10110 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n405_ $abc$8517$new_n387_ $abc$8517$new_n381_ $abc$8517$new_n375_ $abc$8517$new_n404_ +1111 1 +.names $abc$8517$new_n399_ $abc$8517$new_n397_ $abc$8517$new_n406_ $abc$8517$new_n405_ +000 1 +110 1 +.names $abc$8517$new_n398_ $abc$8517$new_n310_ inst[29] $abc$8517$new_n324_ rs2_data[9] $abc$8517$new_n406_ +00000 1 +00001 1 +00010 1 +00011 1 +00110 1 +00111 1 +01000 1 +01010 1 +01100 1 +01110 1 +10100 1 +10101 1 +11001 1 +11011 1 +11101 1 +11111 1 +.names $abc$8517$new_n380_ $abc$8517$new_n379_ $abc$8517$new_n376_ $abc$8517$new_n408_ $abc$8517$new_n407_ +0100 1 +0110 1 +1000 1 +1100 1 +1101 1 +1110 1 +.names $abc$8517$new_n310_ $abc$8517$new_n378_ rs2_data[15] $abc$8517$new_n312_ inst[31] $abc$8517$new_n408_ +00000 1 +00010 1 +00011 1 +00100 1 +00110 1 +00111 1 +10000 1 +10001 1 +10010 1 +10011 1 +11000 1 +11001 1 +11010 1 +11011 1 +.names $abc$8517$new_n310_ $abc$8517$new_n371_ $abc$8517$new_n410_ inst[27] $abc$8517$new_n324_ rs2_data[7] $abc$8517$new_n409_ +000000 1 +000001 1 +000010 1 +000011 1 +000110 1 +000111 1 +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001110 1 +001111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011110 1 +011111 1 +100000 1 +100010 1 +100100 1 +100110 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +111000 1 +111010 1 +111100 1 +111110 1 +.names $abc$8517$new_n310_ $abc$8517$new_n373_ rs2_data[6] $abc$8517$new_n324_ inst[26] $abc$8517$new_n410_ +00000 1 +00010 1 +00011 1 +00100 1 +00110 1 +00111 1 +10000 1 +10001 1 +10010 1 +10011 1 +.names $abc$8517$new_n326_ $abc$8517$new_n428_ $abc$8517$new_n1347_ $abc$8517$new_n426_ $abc$8517$new_n411_ +0001 1 +0010 1 +0011 1 +0100 1 +0101 1 +0110 1 +0111 1 +1011 1 +1101 1 +1111 1 +.names $abc$8517$new_n310_ $abc$8517$new_n414_ rs2_data[22] $abc$8517$new_n413_ +010 1 +011 1 +100 1 +110 1 +.names $abc$8517$new_n328_ $abc$8517$new_n324_ inst[31] $abc$8517$new_n414_ +000 1 +010 1 +011 1 +.names $abc$8517$new_n418_ $abc$8517$new_n335_ $abc$8517$new_n332_ $abc$8517$new_n416_ $abc$8517$new_n415_ +0000 1 +0010 1 +0011 1 +0100 1 +0110 1 +0111 1 +1010 1 +1100 1 +1110 1 +1111 1 +.names $abc$8517$new_n310_ $abc$8517$new_n331_ rs2_data[21] $abc$8517$new_n324_ inst[31] $abc$8517$new_n416_ +00000 1 +00010 1 +00011 1 +00100 1 +00110 1 +00111 1 +10000 1 +10001 1 +10010 1 +10011 1 +11000 1 +11001 1 +11010 1 +11011 1 +.names $abc$8517$new_n310_ $abc$8517$new_n334_ rs2_data[20] $abc$8517$new_n324_ inst[31] $abc$8517$new_n418_ +00000 1 +00010 1 +00011 1 +00100 1 +00110 1 +00111 1 +10000 1 +10001 1 +10010 1 +10011 1 +11000 1 +11001 1 +11010 1 +11011 1 +.names $abc$8517$new_n316_ $abc$8517$new_n309_ $abc$8517$new_n339_ $abc$8517$new_n321_ $abc$8517$new_n337_ $abc$8517$new_n420_ $abc$8517$new_n419_ +000100 1 +000110 1 +001000 1 +001100 1 +001101 1 +001110 1 +.names $abc$8517$new_n310_ $abc$8517$new_n320_ rs2_data[17] $abc$8517$new_n312_ inst[31] $abc$8517$new_n420_ +00000 1 +00010 1 +00011 1 +00100 1 +00110 1 +00111 1 +10000 1 +10001 1 +10010 1 +10011 1 +11000 1 +11001 1 +11010 1 +11011 1 +.names $abc$8517$new_n310_ $abc$8517$new_n314_ rs2_data[19] $abc$8517$new_n312_ inst[31] $abc$8517$new_n422_ +00000 1 +00010 1 +00011 1 +00100 1 +00110 1 +00111 1 +10000 1 +10001 1 +10010 1 +10011 1 +11000 1 +11001 1 +11010 1 +11011 1 +.names $abc$8517$new_n310_ $abc$8517$new_n317_ rs2_data[18] $abc$8517$new_n312_ inst[31] $abc$8517$new_n423_ +00000 1 +00010 1 +00011 1 +00100 1 +00110 1 +00111 1 +10000 1 +10001 1 +10010 1 +10011 1 +11000 1 +11001 1 +11010 1 +11011 1 +.names $abc$8517$new_n310_ $abc$8517$new_n427_ rs2_data[23] $abc$8517$new_n426_ +010 1 +011 1 +100 1 +110 1 +.names $abc$8517$new_n325_ $abc$8517$new_n324_ inst[31] $abc$8517$new_n427_ +000 1 +010 1 +011 1 +.names $abc$8517$new_n310_ $abc$8517$new_n329_ $abc$8517$new_n414_ rs2_data[22] $abc$8517$new_n428_ +0010 1 +0011 1 +1000 1 +1010 1 +.names $abc$8517$new_n310_ $abc$8517$new_n324_ rs2_data[30] inst[31] inst[30] $abc$8517$new_n431_ +00000 1 +00001 1 +00100 1 +00101 1 +01000 1 +01010 1 +01100 1 +01110 1 +10000 1 +10001 1 +10010 1 +10011 1 +11000 1 +11001 1 +11010 1 +11011 1 +.names rs1_data[30] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[30] $abc$8517$new_n432_ +000000 1 +000001 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001001 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011110 1 +100010 1 +100110 1 +101010 1 +101100 1 +101110 1 +111100 1 +111110 1 +.names $abc$8517$new_n434_ $abc$8517$new_n310_ inst[31] rs2_data[31] $abc$8517$new_n433_ +0000 1 +0001 1 +0100 1 +0110 1 +1010 1 +1011 1 +1101 1 +1111 1 +.names rs1_data[31] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[31] $abc$8517$new_n434_ +000000 1 +000001 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001001 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011110 1 +100010 1 +100110 1 +101010 1 +101100 1 +101110 1 +111100 1 +111110 1 +.names $abc$8517$new_n441_ $abc$8517$new_n440_ $abc$8517$new_n438_ $abc$8517$new_n437_ $abc$8517$new_n436_ +0100 1 +0110 1 +1000 1 +1100 1 +1101 1 +1110 1 +.names $abc$8517$new_n310_ $abc$8517$new_n324_ rs2_data[29] inst[31] inst[29] $abc$8517$new_n437_ +00000 1 +00001 1 +00100 1 +00101 1 +01000 1 +01010 1 +01100 1 +01110 1 +10000 1 +10001 1 +10010 1 +10011 1 +11000 1 +11001 1 +11010 1 +11011 1 +.names $abc$8517$new_n310_ $abc$8517$new_n439_ rs2_data[28] $abc$8517$new_n324_ inst[31] $abc$8517$new_n438_ +00000 1 +00010 1 +00011 1 +00100 1 +00110 1 +00111 1 +10000 1 +10001 1 +10010 1 +10011 1 +11000 1 +11001 1 +11010 1 +11011 1 +.names inst[28] $abc$8517$new_n300_ $abc$8517$new_n439_ +11 1 +.names rs1_data[29] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[29] $abc$8517$new_n440_ +000000 1 +000001 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001001 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011110 1 +100010 1 +100110 1 +101010 1 +101100 1 +101110 1 +111100 1 +111110 1 +.names rs1_data[28] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[28] $abc$8517$new_n441_ +000000 1 +000001 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001001 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011110 1 +100010 1 +100110 1 +101010 1 +101100 1 +101110 1 +111100 1 +111110 1 +.names $abc$8517$new_n440_ $abc$8517$new_n437_ $abc$8517$new_n443_ $abc$8517$new_n442_ +000 1 +110 1 +.names $abc$8517$new_n441_ $abc$8517$new_n310_ $abc$8517$new_n439_ inst[31] $abc$8517$new_n324_ rs2_data[28] $abc$8517$new_n443_ +000000 1 +000001 1 +000010 1 +000011 1 +000110 1 +000111 1 +010000 1 +010010 1 +010100 1 +010110 1 +011000 1 +011010 1 +011100 1 +011110 1 +100100 1 +100101 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110001 1 +110011 1 +110101 1 +110111 1 +111001 1 +111011 1 +111101 1 +111111 1 +.names $abc$8517$new_n450_ $abc$8517$new_n449_ $abc$8517$new_n447_ $abc$8517$new_n445_ $abc$8517$new_n444_ +0100 1 +0110 1 +1000 1 +1100 1 +1101 1 +1110 1 +.names $abc$8517$new_n310_ $abc$8517$new_n446_ rs2_data[27] $abc$8517$new_n324_ inst[31] $abc$8517$new_n445_ +00000 1 +00010 1 +00011 1 +00100 1 +00110 1 +00111 1 +10000 1 +10001 1 +10010 1 +10011 1 +11000 1 +11001 1 +11010 1 +11011 1 +.names inst[27] $abc$8517$new_n300_ $abc$8517$new_n446_ +11 1 +.names $abc$8517$new_n310_ $abc$8517$new_n448_ rs2_data[26] $abc$8517$new_n324_ inst[31] $abc$8517$new_n447_ +00000 1 +00010 1 +00011 1 +00100 1 +00110 1 +00111 1 +10000 1 +10001 1 +10010 1 +10011 1 +11000 1 +11001 1 +11010 1 +11011 1 +.names inst[26] $abc$8517$new_n300_ $abc$8517$new_n448_ +11 1 +.names rs1_data[27] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[27] $abc$8517$new_n449_ +000000 1 +000001 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001001 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011110 1 +100010 1 +100110 1 +101010 1 +101100 1 +101110 1 +111100 1 +111110 1 +.names rs1_data[26] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[26] $abc$8517$new_n450_ +000000 1 +000001 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001001 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011110 1 +100010 1 +100110 1 +101010 1 +101100 1 +101110 1 +111100 1 +111110 1 +.names $abc$8517$new_n453_ $abc$8517$new_n452_ $abc$8517$new_n451_ +00 1 +.names $abc$8517$new_n449_ $abc$8517$new_n310_ $abc$8517$new_n446_ inst[31] $abc$8517$new_n324_ rs2_data[27] $abc$8517$new_n452_ +000000 1 +000001 1 +000010 1 +000011 1 +000110 1 +000111 1 +010000 1 +010010 1 +010100 1 +010110 1 +011000 1 +011010 1 +011100 1 +011110 1 +100100 1 +100101 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110001 1 +110011 1 +110101 1 +110111 1 +111001 1 +111011 1 +111101 1 +111111 1 +.names $abc$8517$new_n450_ $abc$8517$new_n310_ $abc$8517$new_n448_ inst[31] $abc$8517$new_n324_ rs2_data[26] $abc$8517$new_n453_ +000000 1 +000001 1 +000010 1 +000011 1 +000110 1 +000111 1 +010000 1 +010010 1 +010100 1 +010110 1 +011000 1 +011010 1 +011100 1 +011110 1 +100100 1 +100101 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110001 1 +110011 1 +110101 1 +110111 1 +111001 1 +111011 1 +111101 1 +111111 1 +.names $abc$8517$new_n460_ $abc$8517$new_n459_ $abc$8517$new_n457_ $abc$8517$new_n455_ $abc$8517$new_n454_ +0100 1 +0110 1 +1000 1 +1100 1 +1101 1 +1110 1 +.names $abc$8517$new_n310_ $abc$8517$new_n456_ rs2_data[25] $abc$8517$new_n324_ inst[31] $abc$8517$new_n455_ +00000 1 +00010 1 +00011 1 +00100 1 +00110 1 +00111 1 +10000 1 +10001 1 +10010 1 +10011 1 +11000 1 +11001 1 +11010 1 +11011 1 +.names inst[25] $abc$8517$new_n300_ $abc$8517$new_n456_ +11 1 +.names $abc$8517$new_n310_ $abc$8517$new_n458_ rs2_data[24] $abc$8517$new_n324_ inst[31] $abc$8517$new_n457_ +00000 1 +00010 1 +00011 1 +00100 1 +00110 1 +00111 1 +10000 1 +10001 1 +10010 1 +10011 1 +11000 1 +11001 1 +11010 1 +11011 1 +.names inst[24] $abc$8517$new_n300_ $abc$8517$new_n458_ +11 1 +.names rs1_data[25] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[25] $abc$8517$new_n459_ +000000 1 +000001 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001001 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011110 1 +100010 1 +100110 1 +101010 1 +101100 1 +101110 1 +111100 1 +111110 1 +.names rs1_data[24] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[24] $abc$8517$new_n460_ +000000 1 +000001 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001001 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011110 1 +100010 1 +100110 1 +101010 1 +101100 1 +101110 1 +111100 1 +111110 1 +.names $abc$8517$new_n462_ $abc$8517$new_n442_ $abc$8517$new_n461_ +11 1 +.names $abc$8517$new_n464_ $abc$8517$new_n463_ $abc$8517$new_n453_ $abc$8517$new_n452_ $abc$8517$new_n462_ +0000 1 +.names $abc$8517$new_n459_ $abc$8517$new_n310_ $abc$8517$new_n456_ inst[31] $abc$8517$new_n324_ rs2_data[25] $abc$8517$new_n463_ +000000 1 +000001 1 +000010 1 +000011 1 +000110 1 +000111 1 +010000 1 +010010 1 +010100 1 +010110 1 +011000 1 +011010 1 +011100 1 +011110 1 +100100 1 +100101 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110001 1 +110011 1 +110101 1 +110111 1 +111001 1 +111011 1 +111101 1 +111111 1 +.names $abc$8517$new_n460_ $abc$8517$new_n310_ $abc$8517$new_n458_ inst[31] $abc$8517$new_n324_ rs2_data[24] $abc$8517$new_n464_ +000000 1 +000001 1 +000010 1 +000011 1 +000110 1 +000111 1 +010000 1 +010010 1 +010100 1 +010110 1 +011000 1 +011010 1 +011100 1 +011110 1 +100100 1 +100101 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110001 1 +110011 1 +110101 1 +110111 1 +111001 1 +111011 1 +111101 1 +111111 1 +.names $abc$8517$new_n478_ $abc$8517$new_n489_ $abc$8517$new_n467_ $abc$8517$new_n466_ +100 1 +.names $abc$8517$new_n476_ $abc$8517$new_n470_ $abc$8517$new_n468_ $abc$8517$new_n404_ $abc$8517$new_n467_ +1111 1 +.names $abc$8517$new_n469_ $abc$8517$new_n442_ $abc$8517$new_n322_ $abc$8517$new_n308_ $abc$8517$new_n462_ $abc$8517$new_n336_ $abc$8517$new_n468_ +111110 1 +.names $abc$8517$new_n432_ $abc$8517$new_n431_ $abc$8517$new_n433_ $abc$8517$new_n469_ +000 1 +110 1 +.names $abc$8517$new_n474_ $abc$8517$new_n472_ $abc$8517$new_n359_ $abc$8517$new_n352_ $abc$8517$new_n471_ $abc$8517$new_n470_ +11000 1 +.names $abc$8517$new_n364_ $abc$8517$new_n362_ $abc$8517$new_n471_ +01 1 +10 1 +.names $abc$8517$new_n363_ $abc$8517$new_n356_ $abc$8517$new_n473_ $abc$8517$new_n472_ +000 1 +110 1 +.names $abc$8517$new_n353_ $abc$8517$new_n310_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n473_ +10001 1 +10010 1 +10011 1 +10101 1 +10110 1 +10111 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n373_ $abc$8517$new_n372_ $abc$8517$new_n475_ $abc$8517$new_n474_ +000 1 +110 1 +.names $abc$8517$new_n371_ $abc$8517$new_n310_ inst[27] $abc$8517$new_n324_ rs2_data[7] $abc$8517$new_n475_ +00000 1 +00001 1 +00010 1 +00011 1 +00110 1 +00111 1 +01000 1 +01010 1 +01100 1 +01110 1 +10100 1 +10101 1 +11001 1 +11011 1 +11101 1 +11111 1 +.names $abc$8517$new_n350_ $abc$8517$new_n345_ $abc$8517$new_n477_ $abc$8517$new_n476_ +000 1 +110 1 +.names $abc$8517$new_n344_ $abc$8517$new_n310_ inst[25] $abc$8517$new_n324_ rs2_data[5] $abc$8517$new_n477_ +00000 1 +00001 1 +00010 1 +00011 1 +00110 1 +00111 1 +01000 1 +01010 1 +01100 1 +01110 1 +10100 1 +10101 1 +11001 1 +11011 1 +11101 1 +11111 1 +.names $abc$8517$new_n486_ $abc$8517$new_n479_ $abc$8517$new_n478_ +00 1 +.names $abc$8517$new_n481_ $abc$8517$new_n484_ $abc$8517$new_n482_ $abc$8517$new_n485_ $abc$8517$new_n310_ $abc$8517$new_n479_ +00001 1 +00011 1 +00110 1 +00111 1 +01110 1 +01111 1 +10010 1 +10011 1 +10110 1 +10111 1 +11110 1 +11111 1 +.names $abc$8517$new_n311_ inst[2] inst[4] inst[5] inst[6] $abc$8517$new_n302_ $abc$8517$new_n481_ +000000 1 +000010 1 +000100 1 +000110 1 +001000 1 +001010 1 +001100 1 +001110 1 +010000 1 +010010 1 +010100 1 +010110 1 +011000 1 +011010 1 +011100 1 +011110 1 +100010 1 +101000 1 +101010 1 +101110 1 +110000 1 +110010 1 +110100 1 +110110 1 +111010 1 +111100 1 +111110 1 +.names $abc$8517$new_n301_ $abc$8517$new_n483_ $abc$8517$new_n482_ +10 1 +.names inst[30] inst[12] inst[13] $abc$8517$new_n483_ +100 1 +101 1 +111 1 +.names inst[5] $abc$8517$new_n348_ inst[14] inst[4] inst[13] $abc$8517$new_n484_ +11100 1 +.names inst[12] inst[14] inst[13] inst[30] $abc$8517$new_n485_ +0110 1 +0111 1 +1001 1 +1010 1 +1011 1 +1100 1 +.names $abc$8517$new_n487_ $abc$8517$new_n481_ $abc$8517$new_n488_ $abc$8517$new_n301_ $abc$8517$new_n483_ $abc$8517$new_n486_ +00000 1 +00001 1 +00010 1 +00100 1 +00101 1 +01000 1 +01001 1 +01010 1 +01011 1 +.names inst[6] inst[5] $abc$8517$new_n348_ inst[4] inst[13] inst[14] $abc$8517$new_n487_ +111000 1 +111001 1 +111010 1 +.names inst[13] inst[12] inst[14] $abc$8517$new_n488_ +010 1 +100 1 +101 1 +111 1 +.names $abc$8517$new_n487_ inst[14] inst[12] inst[13] $abc$8517$new_n482_ $abc$8517$new_n481_ $abc$8517$new_n489_ +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n469_ $abc$8517$new_n442_ $abc$8517$new_n436_ $abc$8517$new_n444_ $abc$8517$new_n454_ $abc$8517$new_n451_ $abc$8517$new_n492_ +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n434_ $abc$8517$new_n310_ $abc$8517$new_n494_ rs2_data[31] $abc$8517$new_n493_ +1000 1 +1001 1 +1101 1 +1111 1 +.names inst[31] $abc$8517$new_n312_ $abc$8517$new_n338_ $abc$8517$new_n494_ +000 1 +001 1 +010 1 +011 1 +111 1 +.names $abc$8517$new_n496_ $abc$8517$new_n470_ $abc$8517$new_n468_ $abc$8517$new_n404_ $abc$8517$new_n476_ $abc$8517$new_n495_ +10000 1 +10001 1 +10010 1 +10011 1 +10100 1 +10101 1 +10110 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +.names $abc$8517$new_n479_ $abc$8517$new_n486_ $abc$8517$new_n497_ $abc$8517$new_n496_ +110 1 +.names $abc$8517$new_n482_ $abc$8517$new_n481_ inst[14] inst[12] inst[13] $abc$8517$new_n498_ $abc$8517$new_n497_ +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +000110 1 +000111 1 +001000 1 +001010 1 +001100 1 +001101 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +011100 1 +011101 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +101100 1 +101101 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +111100 1 +111101 1 +.names inst[6] $abc$8517$new_n348_ inst[14] inst[13] $abc$8517$new_n498_ +1111 1 +.names $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n503_ $abc$8517$new_n508_ $abc$8517$new_n513_ $abc$8517$new_n518_ $abc$8517$new_n502_ +000100 1 +000101 1 +000110 1 +000111 1 +001100 1 +001101 1 +001110 1 +001111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +110000 1 +110010 1 +110100 1 +110110 1 +111000 1 +111010 1 +111100 1 +111110 1 +.names $abc$8517$new_n356_ $abc$8517$new_n505_ $abc$8517$new_n504_ $abc$8517$new_n507_ $abc$8517$new_n506_ $abc$8517$new_n503_ +00000 1 +00100 1 +01000 1 +01100 1 +10000 1 +10001 1 +10010 1 +10011 1 +.names $abc$8517$new_n310_ $abc$8517$new_n399_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n504_ +00000 1 +00100 1 +10000 1 +10001 1 +10010 1 +10011 1 +.names $abc$8517$new_n310_ $abc$8517$new_n398_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n505_ +00001 1 +00010 1 +00011 1 +00101 1 +00110 1 +00111 1 +10100 1 +10101 1 +10110 1 +10111 1 +.names $abc$8517$new_n310_ $abc$8517$new_n393_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n506_ +00000 1 +00100 1 +10000 1 +10001 1 +10010 1 +10011 1 +.names $abc$8517$new_n310_ $abc$8517$new_n394_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n507_ +00001 1 +00010 1 +00011 1 +00101 1 +00110 1 +00111 1 +10100 1 +10101 1 +10110 1 +10111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n510_ $abc$8517$new_n509_ $abc$8517$new_n512_ $abc$8517$new_n511_ $abc$8517$new_n508_ +00000 1 +00100 1 +01000 1 +01100 1 +10000 1 +10001 1 +10010 1 +10011 1 +.names $abc$8517$new_n310_ $abc$8517$new_n386_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n509_ +00000 1 +00100 1 +10000 1 +10001 1 +10010 1 +10011 1 +.names $abc$8517$new_n310_ $abc$8517$new_n384_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n510_ +00001 1 +00010 1 +00011 1 +00101 1 +00110 1 +00111 1 +10100 1 +10101 1 +10110 1 +10111 1 +.names $abc$8517$new_n310_ $abc$8517$new_n380_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n511_ +00000 1 +00100 1 +10000 1 +10001 1 +10010 1 +10011 1 +.names $abc$8517$new_n310_ $abc$8517$new_n379_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n512_ +00001 1 +00010 1 +00011 1 +00101 1 +00110 1 +00111 1 +10100 1 +10101 1 +10110 1 +10111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n515_ $abc$8517$new_n514_ $abc$8517$new_n517_ $abc$8517$new_n516_ $abc$8517$new_n513_ +00000 1 +00100 1 +01000 1 +01100 1 +10000 1 +10001 1 +10010 1 +10011 1 +.names $abc$8517$new_n310_ $abc$8517$new_n350_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n514_ +00000 1 +00100 1 +10000 1 +10001 1 +10010 1 +10011 1 +.names $abc$8517$new_n310_ $abc$8517$new_n344_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n515_ +00001 1 +00010 1 +00011 1 +00101 1 +00110 1 +00111 1 +10100 1 +10101 1 +10110 1 +10111 1 +.names $abc$8517$new_n310_ $abc$8517$new_n373_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n516_ +00000 1 +00100 1 +10000 1 +10001 1 +10010 1 +10011 1 +.names $abc$8517$new_n310_ $abc$8517$new_n371_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n517_ +00001 1 +00010 1 +00011 1 +00101 1 +00110 1 +00111 1 +10100 1 +10101 1 +10110 1 +10111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n353_ $abc$8517$new_n364_ $abc$8517$new_n363_ $abc$8517$new_n361_ $abc$8517$new_n518_ +000000 1 +000010 1 +000100 1 +000110 1 +001000 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +011000 1 +011001 1 +011010 1 +011011 1 +100000 1 +100001 1 +100100 1 +100101 1 +101000 1 +101001 1 +101100 1 +101101 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +.names $abc$8517$new_n310_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n519_ +0000 1 +0100 1 +1000 1 +1001 1 +1010 1 +1011 1 +.names $abc$8517$new_n362_ $abc$8517$new_n368_ $abc$8517$new_n521_ $abc$8517$new_n526_ $abc$8517$new_n531_ $abc$8517$new_n536_ $abc$8517$new_n520_ +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n523_ $abc$8517$new_n522_ $abc$8517$new_n525_ $abc$8517$new_n524_ $abc$8517$new_n521_ +00000 1 +00100 1 +01000 1 +01100 1 +10000 1 +10001 1 +10010 1 +10011 1 +.names $abc$8517$new_n310_ $abc$8517$new_n339_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n522_ +00000 1 +00100 1 +10000 1 +10001 1 +10010 1 +10011 1 +.names $abc$8517$new_n310_ $abc$8517$new_n321_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n523_ +00001 1 +00010 1 +00011 1 +00101 1 +00110 1 +00111 1 +10100 1 +10101 1 +10110 1 +10111 1 +.names $abc$8517$new_n310_ $abc$8517$new_n318_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n524_ +00000 1 +00100 1 +10000 1 +10001 1 +10010 1 +10011 1 +.names $abc$8517$new_n310_ $abc$8517$new_n315_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n525_ +00001 1 +00010 1 +00011 1 +00101 1 +00110 1 +00111 1 +10100 1 +10101 1 +10110 1 +10111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n528_ $abc$8517$new_n527_ $abc$8517$new_n530_ $abc$8517$new_n529_ $abc$8517$new_n526_ +00000 1 +00100 1 +01000 1 +01100 1 +10000 1 +10001 1 +10010 1 +10011 1 +.names $abc$8517$new_n310_ $abc$8517$new_n335_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n527_ +00000 1 +00100 1 +10000 1 +10001 1 +10010 1 +10011 1 +.names $abc$8517$new_n310_ $abc$8517$new_n332_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n528_ +00001 1 +00010 1 +00011 1 +00101 1 +00110 1 +00111 1 +10100 1 +10101 1 +10110 1 +10111 1 +.names $abc$8517$new_n310_ $abc$8517$new_n329_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n529_ +00000 1 +00100 1 +10000 1 +10001 1 +10010 1 +10011 1 +.names $abc$8517$new_n310_ $abc$8517$new_n326_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n530_ +00001 1 +00010 1 +00011 1 +00101 1 +00110 1 +00111 1 +10100 1 +10101 1 +10110 1 +10111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n533_ $abc$8517$new_n532_ $abc$8517$new_n535_ $abc$8517$new_n534_ $abc$8517$new_n531_ +00000 1 +00100 1 +01000 1 +01100 1 +10000 1 +10001 1 +10010 1 +10011 1 +.names $abc$8517$new_n310_ $abc$8517$new_n460_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n532_ +00000 1 +00100 1 +10000 1 +10001 1 +10010 1 +10011 1 +.names $abc$8517$new_n310_ $abc$8517$new_n459_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n533_ +00001 1 +00010 1 +00011 1 +00101 1 +00110 1 +00111 1 +10100 1 +10101 1 +10110 1 +10111 1 +.names $abc$8517$new_n310_ $abc$8517$new_n450_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n534_ +00000 1 +00100 1 +10000 1 +10001 1 +10010 1 +10011 1 +.names $abc$8517$new_n310_ $abc$8517$new_n449_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n535_ +00001 1 +00010 1 +00011 1 +00101 1 +00110 1 +00111 1 +10100 1 +10101 1 +10110 1 +10111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n538_ $abc$8517$new_n537_ $abc$8517$new_n540_ $abc$8517$new_n539_ $abc$8517$new_n536_ +00000 1 +00100 1 +01000 1 +01100 1 +10000 1 +10001 1 +10010 1 +10011 1 +.names $abc$8517$new_n310_ $abc$8517$new_n441_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n537_ +00000 1 +00100 1 +10000 1 +10001 1 +10010 1 +10011 1 +.names $abc$8517$new_n310_ $abc$8517$new_n440_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n538_ +00001 1 +00010 1 +00011 1 +00101 1 +00110 1 +00111 1 +10100 1 +10101 1 +10110 1 +10111 1 +.names $abc$8517$new_n310_ $abc$8517$new_n432_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n539_ +00000 1 +00100 1 +10000 1 +10001 1 +10010 1 +10011 1 +.names $abc$8517$new_n310_ $abc$8517$new_n434_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n540_ +00001 1 +00010 1 +00011 1 +00101 1 +00110 1 +00111 1 +10100 1 +10101 1 +10110 1 +10111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n352_ $abc$8517$new_n542_ +11 1 +.names $abc$8517$new_n497_ $abc$8517$new_n489_ $abc$8517$new_n345_ $abc$8517$new_n486_ $abc$8517$new_n479_ $abc$8517$new_n543_ +11100 1 +.names $abc$8517$new_n352_ $abc$8517$new_n473_ $abc$8517$new_n548_ $abc$8517$new_n547_ $abc$8517$new_n546_ $abc$8517$new_n545_ +01001 1 +01010 1 +01011 1 +01100 1 +01101 1 +01110 1 +01111 1 +10001 1 +10010 1 +10011 1 +10100 1 +10101 1 +10110 1 +10111 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n497_ $abc$8517$new_n486_ $abc$8517$new_n489_ $abc$8517$new_n546_ +111 1 +.names $abc$8517$new_n486_ $abc$8517$new_n497_ $abc$8517$new_n479_ $abc$8517$new_n547_ +100 1 +.names $abc$8517$new_n497_ $abc$8517$new_n489_ $abc$8517$new_n479_ $abc$8517$new_n548_ +111 1 +.names $abc$8517$new_n486_ $abc$8517$new_n550_ $abc$8517$new_n345_ $abc$8517$new_n549_ +111 1 +.names inst[14] inst[12] $abc$8517$new_n482_ $abc$8517$new_n481_ $abc$8517$new_n550_ +1101 1 +1110 1 +1111 1 +.names $abc$8517$new_n486_ $abc$8517$new_n550_ $abc$8517$new_n345_ $abc$8517$new_n551_ +110 1 +.names $abc$8517$new_n479_ $abc$8517$new_n486_ $abc$8517$new_n552_ +10 1 +.names $abc$8517$new_n368_ $abc$8517$new_n558_ $abc$8517$new_n567_ $abc$8517$new_n568_ $abc$8517$new_n555_ $abc$8517$new_n566_ $abc$8517$new_n554_ +000000 1 +000001 1 +000010 1 +000011 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010110 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +100000 1 +100001 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +110000 1 +110001 1 +110010 1 +110011 1 +110110 1 +110111 1 +111010 1 +111011 1 +111110 1 +111111 1 +.names $abc$8517$new_n362_ $abc$8517$new_n556_ $abc$8517$new_n557_ $abc$8517$new_n555_ +001 1 +011 1 +110 1 +111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n321_ $abc$8517$new_n315_ $abc$8517$new_n318_ $abc$8517$new_n335_ $abc$8517$new_n556_ +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n332_ $abc$8517$new_n326_ $abc$8517$new_n329_ $abc$8517$new_n460_ $abc$8517$new_n557_ +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n564_ $abc$8517$new_n559_ $abc$8517$new_n558_ +00 1 +.names $abc$8517$new_n356_ $abc$8517$new_n362_ $abc$8517$new_n561_ $abc$8517$new_n560_ $abc$8517$new_n562_ $abc$8517$new_n563_ $abc$8517$new_n559_ +010001 1 +010010 1 +010011 1 +010101 1 +010110 1 +010111 1 +011001 1 +011010 1 +011011 1 +011101 1 +011110 1 +011111 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n310_ $abc$8517$new_n459_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n560_ +00000 1 +00100 1 +10000 1 +10001 1 +10010 1 +10011 1 +.names $abc$8517$new_n310_ $abc$8517$new_n450_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n561_ +00001 1 +00010 1 +00011 1 +00101 1 +00110 1 +00111 1 +10100 1 +10101 1 +10110 1 +10111 1 +.names $abc$8517$new_n310_ $abc$8517$new_n449_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n562_ +00000 1 +00100 1 +10000 1 +10001 1 +10010 1 +10011 1 +.names $abc$8517$new_n310_ $abc$8517$new_n441_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n563_ +00001 1 +00010 1 +00011 1 +00101 1 +00110 1 +00111 1 +10100 1 +10101 1 +10110 1 +10111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n362_ $abc$8517$new_n440_ $abc$8517$new_n434_ $abc$8517$new_n432_ $abc$8517$new_n564_ +010000 1 +010001 1 +010100 1 +010101 1 +100000 1 +100010 1 +100100 1 +100110 1 +110000 1 +110001 1 +110010 1 +110011 1 +.names $abc$8517$new_n356_ $abc$8517$new_n362_ $abc$8517$new_n519_ $abc$8517$new_n434_ $abc$8517$new_n440_ $abc$8517$new_n432_ $abc$8517$new_n566_ +000000 1 +000001 1 +000010 1 +000011 1 +001000 1 +001001 1 +001010 1 +001011 1 +100000 1 +100010 1 +100100 1 +100110 1 +101000 1 +101001 1 +101100 1 +101101 1 +.names $abc$8517$new_n479_ $abc$8517$new_n550_ $abc$8517$new_n567_ +11 1 +.names $abc$8517$new_n486_ $abc$8517$new_n550_ $abc$8517$new_n479_ $abc$8517$new_n568_ +110 1 +.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n398_ $abc$8517$new_n394_ $abc$8517$new_n393_ $abc$8517$new_n386_ $abc$8517$new_n571_ +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n384_ $abc$8517$new_n379_ $abc$8517$new_n380_ $abc$8517$new_n339_ $abc$8517$new_n572_ +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n363_ $abc$8517$new_n361_ $abc$8517$new_n364_ $abc$8517$new_n350_ $abc$8517$new_n573_ +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n344_ $abc$8517$new_n371_ $abc$8517$new_n373_ $abc$8517$new_n399_ $abc$8517$new_n574_ +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n363_ $abc$8517$new_n356_ $abc$8517$new_n576_ $abc$8517$new_n577_ $abc$8517$new_n547_ $abc$8517$new_n546_ $abc$8517$new_n575_ +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000110 1 +010000 1 +010100 1 +010101 1 +100000 1 +100100 1 +100101 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110110 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111110 1 +.names $abc$8517$new_n552_ $abc$8517$new_n497_ $abc$8517$new_n576_ +10 1 +.names $abc$8517$new_n310_ $abc$8517$new_n353_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n577_ +00001 1 +00010 1 +00011 1 +00101 1 +00110 1 +00111 1 +10100 1 +10101 1 +10110 1 +10111 1 +.names $abc$8517$new_n363_ $abc$8517$new_n356_ $abc$8517$new_n548_ $abc$8517$new_n473_ $abc$8517$new_n580_ $abc$8517$new_n579_ $abc$8517$new_n578_ +000000 1 +000100 1 +001000 1 +010000 1 +010001 1 +010100 1 +010101 1 +011100 1 +011101 1 +100000 1 +100001 1 +100100 1 +100101 1 +101100 1 +101101 1 +110000 1 +110001 1 +110100 1 +110101 1 +111000 1 +111001 1 +.names $abc$8517$new_n478_ $abc$8517$new_n497_ $abc$8517$new_n579_ +10 1 +.names $abc$8517$new_n362_ $abc$8517$new_n368_ $abc$8517$new_n581_ $abc$8517$new_n543_ $abc$8517$new_n580_ +1111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n577_ $abc$8517$new_n519_ $abc$8517$new_n363_ $abc$8517$new_n581_ +1010 1 +1100 1 +1101 1 +1110 1 +1111 1 +.names $abc$8517$new_n597_ $abc$8517$new_n599_ $abc$8517$new_n590_ $abc$8517$new_n345_ $abc$8517$new_n583_ $abc$8517$new_n588_ bus_address[2] +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +000110 1 +000111 1 +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110001 1 +110010 1 +110011 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n567_ $abc$8517$new_n368_ $abc$8517$new_n584_ $abc$8517$new_n585_ $abc$8517$new_n587_ $abc$8517$new_n586_ $abc$8517$new_n583_ +100000 1 +100100 1 +101000 1 +101100 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n362_ $abc$8517$new_n525_ $abc$8517$new_n524_ $abc$8517$new_n527_ $abc$8517$new_n528_ $abc$8517$new_n584_ +010001 1 +010010 1 +010011 1 +010101 1 +010110 1 +010111 1 +011001 1 +011010 1 +011011 1 +011101 1 +011110 1 +011111 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n362_ $abc$8517$new_n530_ $abc$8517$new_n529_ $abc$8517$new_n532_ $abc$8517$new_n533_ $abc$8517$new_n585_ +000001 1 +000010 1 +000011 1 +000101 1 +000110 1 +000111 1 +001001 1 +001010 1 +001011 1 +001101 1 +001110 1 +001111 1 +100100 1 +100101 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +.names $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n535_ $abc$8517$new_n534_ $abc$8517$new_n538_ $abc$8517$new_n537_ $abc$8517$new_n586_ +100000 1 +100100 1 +101000 1 +101100 1 +110000 1 +110001 1 +110010 1 +110011 1 +.names $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n540_ $abc$8517$new_n539_ $abc$8517$new_n587_ +0000 1 +0001 1 +0010 1 +0011 1 +0100 1 +.names $abc$8517$new_n568_ $abc$8517$new_n368_ $abc$8517$new_n584_ $abc$8517$new_n585_ $abc$8517$new_n589_ $abc$8517$new_n586_ $abc$8517$new_n588_ +100000 1 +100100 1 +101000 1 +101100 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n434_ $abc$8517$new_n432_ $abc$8517$new_n589_ +00010 1 +00011 1 +00110 1 +00111 1 +01010 1 +01011 1 +01101 1 +01111 1 +.names $abc$8517$new_n368_ $abc$8517$new_n549_ $abc$8517$new_n594_ $abc$8517$new_n591_ $abc$8517$new_n595_ $abc$8517$new_n596_ $abc$8517$new_n590_ +010001 1 +010010 1 +010011 1 +010101 1 +010110 1 +010111 1 +011001 1 +011010 1 +011011 1 +011101 1 +011110 1 +011111 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n362_ $abc$8517$new_n593_ $abc$8517$new_n592_ $abc$8517$new_n515_ $abc$8517$new_n514_ $abc$8517$new_n591_ +010001 1 +010010 1 +010011 1 +010101 1 +010110 1 +010111 1 +011001 1 +011010 1 +011011 1 +011101 1 +011110 1 +011111 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n310_ $abc$8517$new_n364_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n592_ +0000 1 +0010 1 +1000 1 +1001 1 +.names $abc$8517$new_n310_ $abc$8517$new_n361_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n593_ +00001 1 +00010 1 +00011 1 +00101 1 +00110 1 +00111 1 +10100 1 +10101 1 +10110 1 +10111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n362_ $abc$8517$new_n517_ $abc$8517$new_n516_ $abc$8517$new_n505_ $abc$8517$new_n504_ $abc$8517$new_n594_ +000001 1 +000010 1 +000011 1 +000101 1 +000110 1 +000111 1 +001001 1 +001010 1 +001011 1 +001101 1 +001110 1 +001111 1 +100100 1 +100101 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n362_ $abc$8517$new_n507_ $abc$8517$new_n506_ $abc$8517$new_n509_ $abc$8517$new_n510_ $abc$8517$new_n595_ +010001 1 +010010 1 +010011 1 +010101 1 +010110 1 +010111 1 +011001 1 +011010 1 +011011 1 +011101 1 +011110 1 +011111 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n362_ $abc$8517$new_n512_ $abc$8517$new_n511_ $abc$8517$new_n522_ $abc$8517$new_n523_ $abc$8517$new_n596_ +000001 1 +000010 1 +000011 1 +000101 1 +000110 1 +000111 1 +001001 1 +001010 1 +001011 1 +001101 1 +001110 1 +001111 1 +100100 1 +100101 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +.names $abc$8517$new_n598_ $abc$8517$new_n471_ $abc$8517$new_n363_ $abc$8517$new_n577_ $abc$8517$new_n356_ $abc$8517$new_n546_ $abc$8517$new_n597_ +100000 1 +100010 1 +100011 1 +100100 1 +100110 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101110 1 +101111 1 +110000 1 +110001 1 +110010 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111010 1 +111100 1 +111101 1 +111110 1 +.names $abc$8517$new_n486_ $abc$8517$new_n497_ $abc$8517$new_n364_ $abc$8517$new_n362_ $abc$8517$new_n479_ $abc$8517$new_n598_ +00010 1 +00100 1 +00110 1 +00111 1 +01000 1 +01001 1 +01010 1 +01011 1 +01100 1 +01101 1 +01110 1 +01111 1 +10000 1 +10001 1 +10011 1 +10101 1 +10110 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n548_ $abc$8517$new_n601_ $abc$8517$new_n471_ $abc$8517$new_n368_ $abc$8517$new_n600_ $abc$8517$new_n543_ $abc$8517$new_n599_ +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +000110 1 +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +.names $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n364_ $abc$8517$new_n353_ $abc$8517$new_n363_ $abc$8517$new_n600_ +101000 1 +101001 1 +101100 1 +101101 1 +110000 1 +110010 1 +110100 1 +110110 1 +111000 1 +111001 1 +111010 1 +111011 1 +.names $abc$8517$new_n363_ $abc$8517$new_n473_ $abc$8517$new_n356_ $abc$8517$new_n601_ +010 1 +100 1 +110 1 +111 1 +.names $abc$8517$new_n345_ $abc$8517$new_n567_ $abc$8517$new_n604_ $abc$8517$new_n568_ $abc$8517$new_n609_ $abc$8517$new_n603_ +00010 1 +00110 1 +01000 1 +01001 1 +01010 1 +01011 1 +01110 1 +.names $abc$8517$new_n362_ $abc$8517$new_n368_ $abc$8517$new_n605_ $abc$8517$new_n606_ $abc$8517$new_n607_ $abc$8517$new_n608_ $abc$8517$new_n604_ +000000 1 +000010 1 +000100 1 +000110 1 +001000 1 +001010 1 +001100 1 +001110 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n315_ $abc$8517$new_n332_ $abc$8517$new_n335_ $abc$8517$new_n329_ $abc$8517$new_n605_ +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n326_ $abc$8517$new_n459_ $abc$8517$new_n460_ $abc$8517$new_n450_ $abc$8517$new_n606_ +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n449_ $abc$8517$new_n440_ $abc$8517$new_n441_ $abc$8517$new_n432_ $abc$8517$new_n607_ +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n519_ $abc$8517$new_n356_ $abc$8517$new_n434_ $abc$8517$new_n608_ +110 1 +.names $abc$8517$new_n362_ $abc$8517$new_n368_ $abc$8517$new_n605_ $abc$8517$new_n606_ $abc$8517$new_n607_ $abc$8517$new_n434_ $abc$8517$new_n609_ +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n361_ $abc$8517$new_n363_ $abc$8517$new_n364_ $abc$8517$new_n353_ $abc$8517$new_n612_ +000000 1 +000010 1 +000100 1 +000110 1 +001000 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +011000 1 +011001 1 +011010 1 +011011 1 +100000 1 +100001 1 +100100 1 +100101 1 +101000 1 +101001 1 +101100 1 +101101 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +.names $abc$8517$new_n363_ $abc$8517$new_n364_ $abc$8517$new_n577_ $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n616_ +00011 1 +01001 1 +01010 1 +01011 1 +01110 1 +01111 1 +10010 1 +10011 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n363_ $abc$8517$new_n473_ $abc$8517$new_n364_ $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n618_ +00100 1 +00101 1 +01000 1 +01100 1 +01101 1 +01110 1 +10000 1 +10100 1 +10101 1 +10110 1 +11000 1 +11001 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n394_ $abc$8517$new_n384_ $abc$8517$new_n386_ $abc$8517$new_n380_ $abc$8517$new_n620_ +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n379_ $abc$8517$new_n321_ $abc$8517$new_n339_ $abc$8517$new_n318_ $abc$8517$new_n621_ +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n361_ $abc$8517$new_n344_ $abc$8517$new_n350_ $abc$8517$new_n373_ $abc$8517$new_n623_ +000000 1 +000010 1 +000100 1 +000110 1 +001000 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +011000 1 +011001 1 +011010 1 +011011 1 +100000 1 +100001 1 +100100 1 +100101 1 +101000 1 +101001 1 +101100 1 +101101 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n371_ $abc$8517$new_n398_ $abc$8517$new_n399_ $abc$8517$new_n393_ $abc$8517$new_n624_ +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n637_ $abc$8517$new_n632_ $abc$8517$new_n626_ bus_address[4] +000 1 +001 1 +010 1 +011 1 +100 1 +101 1 +111 1 +.names $abc$8517$new_n345_ $abc$8517$new_n627_ $abc$8517$new_n567_ $abc$8517$new_n568_ $abc$8517$new_n628_ $abc$8517$new_n631_ $abc$8517$new_n626_ +000110 1 +000111 1 +001001 1 +001011 1 +001101 1 +001110 1 +001111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n526_ $abc$8517$new_n531_ $abc$8517$new_n627_ +1000 1 +1010 1 +1100 1 +1101 1 +.names $abc$8517$new_n368_ $abc$8517$new_n629_ $abc$8517$new_n630_ $abc$8517$new_n628_ +001 1 +010 1 +011 1 +.names $abc$8517$new_n434_ $abc$8517$new_n362_ $abc$8517$new_n629_ +00 1 +.names $abc$8517$new_n356_ $abc$8517$new_n362_ $abc$8517$new_n538_ $abc$8517$new_n537_ $abc$8517$new_n539_ $abc$8517$new_n540_ $abc$8517$new_n630_ +010001 1 +010010 1 +010011 1 +010101 1 +010110 1 +010111 1 +011001 1 +011010 1 +011011 1 +011101 1 +011110 1 +011111 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n630_ $abc$8517$new_n368_ $abc$8517$new_n631_ +10 1 +.names $abc$8517$new_n636_ $abc$8517$new_n549_ $abc$8517$new_n633_ $abc$8517$new_n368_ $abc$8517$new_n543_ $abc$8517$new_n634_ $abc$8517$new_n632_ +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111111 1 +.names $abc$8517$new_n362_ $abc$8517$new_n368_ $abc$8517$new_n503_ $abc$8517$new_n508_ $abc$8517$new_n513_ $abc$8517$new_n521_ $abc$8517$new_n633_ +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +100100 1 +100101 1 +100110 1 +100111 1 +101100 1 +101101 1 +101110 1 +101111 1 +110010 1 +110011 1 +110110 1 +110111 1 +111010 1 +111011 1 +111110 1 +111111 1 +.names $abc$8517$new_n362_ $abc$8517$new_n542_ $abc$8517$new_n635_ $abc$8517$new_n634_ +000 1 +001 1 +101 1 +111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n350_ $abc$8517$new_n364_ $abc$8517$new_n361_ $abc$8517$new_n363_ $abc$8517$new_n635_ +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n486_ $abc$8517$new_n497_ $abc$8517$new_n350_ $abc$8517$new_n345_ $abc$8517$new_n479_ $abc$8517$new_n636_ +00010 1 +00100 1 +00110 1 +00111 1 +01000 1 +01001 1 +01010 1 +01011 1 +01100 1 +01101 1 +01110 1 +01111 1 +10000 1 +10001 1 +10011 1 +10101 1 +10110 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n642_ $abc$8517$new_n546_ $abc$8517$new_n643_ $abc$8517$new_n641_ $abc$8517$new_n548_ $abc$8517$new_n638_ $abc$8517$new_n637_ +000000 1 +000001 1 +000011 1 +000100 1 +000101 1 +000111 1 +001000 1 +001001 1 +001011 1 +001100 1 +001101 1 +001111 1 +010000 1 +010001 1 +010011 1 +100000 1 +100001 1 +100010 1 +100100 1 +100101 1 +100110 1 +101000 1 +101001 1 +101010 1 +101100 1 +101101 1 +101110 1 +110100 1 +110101 1 +110110 1 +111000 1 +111001 1 +111010 1 +111100 1 +111101 1 +111110 1 +.names $abc$8517$new_n640_ $abc$8517$new_n639_ $abc$8517$new_n638_ +00 1 +.names $abc$8517$new_n361_ $abc$8517$new_n368_ $abc$8517$new_n639_ +10 1 +.names $abc$8517$new_n366_ $abc$8517$new_n363_ $abc$8517$new_n473_ $abc$8517$new_n364_ $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n640_ +000100 1 +000101 1 +001000 1 +001100 1 +001101 1 +001110 1 +010000 1 +010100 1 +010101 1 +010110 1 +011000 1 +011001 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n361_ $abc$8517$new_n368_ $abc$8517$new_n641_ +00 1 +.names $abc$8517$new_n350_ $abc$8517$new_n345_ $abc$8517$new_n642_ +01 1 +10 1 +.names $abc$8517$new_n359_ $abc$8517$new_n577_ $abc$8517$new_n363_ $abc$8517$new_n364_ $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n643_ +100000 1 +100001 1 +100010 1 +100100 1 +101000 1 +101001 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +111000 1 +111001 1 +111010 1 +111100 1 +.names $abc$8517$new_n548_ $abc$8517$new_n477_ $abc$8517$new_n350_ $abc$8517$new_n345_ $abc$8517$new_n638_ $abc$8517$new_n646_ +10000 1 +10100 1 +10101 1 +10110 1 +11001 1 +11010 1 +11011 1 +11111 1 +.names $abc$8517$new_n345_ $abc$8517$new_n568_ $abc$8517$new_n368_ $abc$8517$new_n650_ $abc$8517$new_n648_ $abc$8517$new_n652_ $abc$8517$new_n647_ +000100 1 +000101 1 +000110 1 +000111 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010010 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n362_ $abc$8517$new_n557_ $abc$8517$new_n649_ $abc$8517$new_n648_ +001 1 +011 1 +110 1 +111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n561_ $abc$8517$new_n560_ $abc$8517$new_n563_ $abc$8517$new_n562_ $abc$8517$new_n649_ +00000 1 +00100 1 +01000 1 +01100 1 +10000 1 +10001 1 +10010 1 +10011 1 +.names $abc$8517$new_n567_ $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n651_ $abc$8517$new_n557_ $abc$8517$new_n649_ $abc$8517$new_n650_ +100100 1 +100101 1 +100110 1 +100111 1 +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110010 1 +110100 1 +110110 1 +111000 1 +111001 1 +111100 1 +111101 1 +.names $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n440_ $abc$8517$new_n434_ $abc$8517$new_n432_ $abc$8517$new_n651_ +101000 1 +101001 1 +101100 1 +101101 1 +110000 1 +110010 1 +110100 1 +110110 1 +111000 1 +111001 1 +111010 1 +111011 1 +.names $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n434_ $abc$8517$new_n440_ $abc$8517$new_n432_ $abc$8517$new_n652_ +000100 1 +000101 1 +000110 1 +000111 1 +001100 1 +001101 1 +001110 1 +001111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100100 1 +100101 1 +100110 1 +100111 1 +101100 1 +101101 1 +101110 1 +101111 1 +110001 1 +110011 1 +110101 1 +110111 1 +111010 1 +111011 1 +111110 1 +111111 1 +.names $abc$8517$new_n368_ $abc$8517$new_n543_ $abc$8517$new_n654_ $abc$8517$new_n653_ +110 1 +.names $abc$8517$new_n362_ $abc$8517$new_n581_ $abc$8517$new_n655_ $abc$8517$new_n654_ +000 1 +001 1 +101 1 +111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n344_ $abc$8517$new_n361_ $abc$8517$new_n350_ $abc$8517$new_n364_ $abc$8517$new_n655_ +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n362_ $abc$8517$new_n368_ $abc$8517$new_n556_ $abc$8517$new_n574_ $abc$8517$new_n571_ $abc$8517$new_n572_ $abc$8517$new_n657_ +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001110 1 +001111 1 +010010 1 +010011 1 +010110 1 +010111 1 +011010 1 +011011 1 +011110 1 +011111 1 +100001 1 +100011 1 +100101 1 +100111 1 +101001 1 +101011 1 +101101 1 +101111 1 +110100 1 +110101 1 +110110 1 +110111 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n546_ $abc$8517$new_n477_ $abc$8517$new_n350_ $abc$8517$new_n345_ $abc$8517$new_n643_ $abc$8517$new_n641_ $abc$8517$new_n659_ +100000 1 +100001 1 +100010 1 +100011 1 +100101 1 +100110 1 +100111 1 +101001 1 +101010 1 +101011 1 +110100 1 +111000 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n344_ $abc$8517$new_n350_ $abc$8517$new_n643_ $abc$8517$new_n641_ $abc$8517$new_n345_ $abc$8517$new_n342_ $abc$8517$new_n662_ +000011 1 +010001 1 +010011 1 +010111 1 +011011 1 +011111 1 +100001 1 +100010 1 +100011 1 +100101 1 +100111 1 +101001 1 +101011 1 +101101 1 +101111 1 +110000 1 +110001 1 +110010 1 +110011 1 +110101 1 +110110 1 +110111 1 +111001 1 +111010 1 +111011 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n373_ $abc$8517$new_n372_ $abc$8517$new_n663_ +01 1 +10 1 +.names $abc$8517$new_n341_ $abc$8517$new_n638_ $abc$8517$new_n476_ $abc$8517$new_n664_ +100 1 +101 1 +110 1 +.names $abc$8517$new_n345_ $abc$8517$new_n568_ $abc$8517$new_n368_ $abc$8517$new_n669_ $abc$8517$new_n666_ $abc$8517$new_n671_ $abc$8517$new_n665_ +000100 1 +000101 1 +000110 1 +000111 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010010 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n362_ $abc$8517$new_n667_ $abc$8517$new_n668_ $abc$8517$new_n666_ +001 1 +011 1 +110 1 +111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n530_ $abc$8517$new_n529_ $abc$8517$new_n533_ $abc$8517$new_n532_ $abc$8517$new_n667_ +00000 1 +00100 1 +01000 1 +01100 1 +10000 1 +10001 1 +10010 1 +10011 1 +.names $abc$8517$new_n356_ $abc$8517$new_n535_ $abc$8517$new_n534_ $abc$8517$new_n538_ $abc$8517$new_n537_ $abc$8517$new_n668_ +00000 1 +00100 1 +01000 1 +01100 1 +10000 1 +10001 1 +10010 1 +10011 1 +.names $abc$8517$new_n567_ $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n670_ $abc$8517$new_n667_ $abc$8517$new_n668_ $abc$8517$new_n669_ +100100 1 +100101 1 +100110 1 +100111 1 +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110010 1 +110100 1 +110110 1 +111000 1 +111001 1 +111100 1 +111101 1 +.names $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n539_ $abc$8517$new_n540_ $abc$8517$new_n670_ +1101 1 +1110 1 +1111 1 +.names $abc$8517$new_n670_ $abc$8517$new_n434_ $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n671_ +0011 1 +0100 1 +0101 1 +0110 1 +0111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n364_ $abc$8517$new_n353_ $abc$8517$new_n363_ $abc$8517$new_n680_ +01000 1 +01001 1 +01100 1 +01101 1 +10000 1 +10010 1 +10100 1 +10110 1 +11000 1 +11001 1 +11010 1 +11011 1 +.names $abc$8517$new_n356_ $abc$8517$new_n516_ $abc$8517$new_n515_ $abc$8517$new_n593_ $abc$8517$new_n514_ $abc$8517$new_n681_ +00000 1 +00100 1 +01000 1 +01100 1 +10000 1 +10001 1 +10010 1 +10011 1 +.names $abc$8517$new_n486_ $abc$8517$new_n497_ $abc$8517$new_n373_ $abc$8517$new_n372_ $abc$8517$new_n479_ $abc$8517$new_n682_ +00010 1 +00100 1 +00110 1 +00111 1 +01000 1 +01001 1 +01010 1 +01011 1 +01100 1 +01101 1 +01110 1 +01111 1 +10000 1 +10001 1 +10011 1 +10101 1 +10110 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n695_ $abc$8517$new_n693_ $abc$8517$new_n687_ $abc$8517$new_n685_ $abc$8517$new_n684_ bus_address[7] +00000 1 +00001 1 +00010 1 +00011 1 +00100 1 +00101 1 +00110 1 +00111 1 +01000 1 +01001 1 +01010 1 +01011 1 +01100 1 +01101 1 +01110 1 +01111 1 +10001 1 +10010 1 +10011 1 +10100 1 +10101 1 +10110 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n546_ $abc$8517$new_n475_ $abc$8517$new_n373_ $abc$8517$new_n372_ $abc$8517$new_n662_ $abc$8517$new_n684_ +10000 1 +10001 1 +10010 1 +10100 1 +11011 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n548_ $abc$8517$new_n475_ $abc$8517$new_n410_ $abc$8517$new_n686_ $abc$8517$new_n685_ +1000 1 +1101 1 +1110 1 +1111 1 +.names $abc$8517$new_n663_ $abc$8517$new_n341_ $abc$8517$new_n476_ $abc$8517$new_n640_ $abc$8517$new_n639_ $abc$8517$new_n686_ +00000 1 +00001 1 +00010 1 +00011 1 +00100 1 +00101 1 +00110 1 +00111 1 +01100 1 +.names $abc$8517$new_n345_ $abc$8517$new_n692_ $abc$8517$new_n691_ $abc$8517$new_n688_ $abc$8517$new_n689_ $abc$8517$new_n687_ +00100 1 +01000 1 +01001 1 +01010 1 +01011 1 +01100 1 +01101 1 +01110 1 +01111 1 +.names $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n606_ $abc$8517$new_n607_ $abc$8517$new_n688_ +1001 1 +1011 1 +1110 1 +1111 1 +.names $abc$8517$new_n368_ $abc$8517$new_n608_ $abc$8517$new_n690_ $abc$8517$new_n689_ +000 1 +001 1 +010 1 +.names $abc$8517$new_n479_ $abc$8517$new_n362_ $abc$8517$new_n690_ +11 1 +.names $abc$8517$new_n486_ $abc$8517$new_n550_ $abc$8517$new_n691_ +11 1 +.names $abc$8517$new_n550_ $abc$8517$new_n486_ $abc$8517$new_n434_ $abc$8517$new_n479_ $abc$8517$new_n368_ $abc$8517$new_n692_ +11000 1 +.names $abc$8517$new_n549_ $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n694_ $abc$8517$new_n620_ $abc$8517$new_n624_ $abc$8517$new_n693_ +100000 1 +100001 1 +100010 1 +100011 1 +101000 1 +101001 1 +101010 1 +101011 1 +110000 1 +110001 1 +110100 1 +110101 1 +111000 1 +111010 1 +111100 1 +111110 1 +.names $abc$8517$new_n362_ $abc$8517$new_n605_ $abc$8517$new_n621_ $abc$8517$new_n694_ +010 1 +011 1 +101 1 +111 1 +.names $abc$8517$new_n696_ $abc$8517$new_n371_ $abc$8517$new_n576_ $abc$8517$new_n698_ $abc$8517$new_n547_ $abc$8517$new_n579_ $abc$8517$new_n695_ +000000 1 +000010 1 +000100 1 +000101 1 +010000 1 +010001 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n543_ $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n612_ $abc$8517$new_n697_ $abc$8517$new_n696_ +11010 1 +11011 1 +11100 1 +11110 1 +.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n371_ $abc$8517$new_n344_ $abc$8517$new_n373_ $abc$8517$new_n350_ $abc$8517$new_n697_ +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n310_ $abc$8517$new_n699_ rs2_data[7] $abc$8517$new_n698_ +000 1 +001 1 +100 1 +110 1 +.names inst[27] $abc$8517$new_n324_ $abc$8517$new_n699_ +10 1 +.names $abc$8517$new_n373_ $abc$8517$new_n372_ $abc$8517$new_n371_ $abc$8517$new_n698_ $abc$8517$new_n662_ $abc$8517$new_n701_ +00110 1 +00111 1 +01011 1 +01101 1 +01110 1 +01111 1 +10011 1 +10101 1 +10110 1 +10111 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n543_ $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n542_ $abc$8517$new_n635_ $abc$8517$new_n704_ $abc$8517$new_n703_ +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110001 1 +110100 1 +110101 1 +111000 1 +111010 1 +111100 1 +111110 1 +.names $abc$8517$new_n356_ $abc$8517$new_n517_ $abc$8517$new_n504_ $abc$8517$new_n516_ $abc$8517$new_n515_ $abc$8517$new_n704_ +00000 1 +00100 1 +01000 1 +01100 1 +10000 1 +10001 1 +10010 1 +10011 1 +.names $abc$8517$new_n551_ $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n692_ $abc$8517$new_n531_ $abc$8517$new_n536_ $abc$8517$new_n707_ +100100 1 +100101 1 +100110 1 +100111 1 +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110010 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n399_ $abc$8517$new_n397_ $abc$8517$new_n709_ +00 1 +.names $abc$8517$new_n474_ $abc$8517$new_n409_ $abc$8517$new_n341_ $abc$8517$new_n476_ $abc$8517$new_n640_ $abc$8517$new_n639_ $abc$8517$new_n711_ +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +000110 1 +000111 1 +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001110 1 +001111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101101 1 +101110 1 +101111 1 +.names $abc$8517$new_n399_ $abc$8517$new_n397_ $abc$8517$new_n712_ +01 1 +10 1 +.names $abc$8517$new_n712_ $abc$8517$new_n373_ $abc$8517$new_n372_ $abc$8517$new_n371_ $abc$8517$new_n698_ $abc$8517$new_n662_ $abc$8517$new_n714_ +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +101000 1 +101001 1 +101010 1 +101100 1 +110000 1 +110001 1 +110010 1 +110100 1 +111000 1 +111001 1 +.names $abc$8517$new_n548_ $abc$8517$new_n406_ $abc$8517$new_n399_ $abc$8517$new_n397_ $abc$8517$new_n711_ $abc$8517$new_n715_ +10001 1 +10100 1 +10101 1 +10111 1 +11000 1 +11010 1 +11011 1 +11110 1 +.names $abc$8517$new_n692_ $abc$8517$new_n559_ $abc$8517$new_n566_ $abc$8517$new_n718_ $abc$8517$new_n564_ $abc$8517$new_n719_ $abc$8517$new_n717_ +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +001000 1 +001010 1 +001100 1 +010000 1 +010010 1 +011000 1 +011010 1 +.names $abc$8517$new_n486_ $abc$8517$new_n550_ $abc$8517$new_n368_ $abc$8517$new_n718_ +111 1 +.names $abc$8517$new_n486_ $abc$8517$new_n368_ $abc$8517$new_n550_ $abc$8517$new_n479_ $abc$8517$new_n719_ +1110 1 +.names $abc$8517$new_n543_ $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n581_ $abc$8517$new_n655_ $abc$8517$new_n721_ $abc$8517$new_n720_ +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110001 1 +110100 1 +110101 1 +111000 1 +111010 1 +111100 1 +111110 1 +.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n398_ $abc$8517$new_n371_ $abc$8517$new_n399_ $abc$8517$new_n373_ $abc$8517$new_n721_ +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n486_ $abc$8517$new_n497_ $abc$8517$new_n398_ $abc$8517$new_n396_ $abc$8517$new_n479_ $abc$8517$new_n724_ +00010 1 +00100 1 +00110 1 +00111 1 +01000 1 +01001 1 +01010 1 +01011 1 +01100 1 +01101 1 +01110 1 +01111 1 +10000 1 +10001 1 +10011 1 +10101 1 +10110 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n730_ $abc$8517$new_n740_ $abc$8517$new_n737_ $abc$8517$new_n726_ $abc$8517$new_n729_ $abc$8517$new_n546_ bus_address[10] +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +000110 1 +000111 1 +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +100001 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n392_ $abc$8517$new_n728_ $abc$8517$new_n727_ $abc$8517$new_n714_ $abc$8517$new_n726_ +0010 1 +0100 1 +0101 1 +0110 1 +0111 1 +.names $abc$8517$new_n709_ $abc$8517$new_n396_ $abc$8517$new_n398_ $abc$8517$new_n727_ +001 1 +010 1 +011 1 +.names $abc$8517$new_n398_ $abc$8517$new_n396_ $abc$8517$new_n728_ +11 1 +.names $abc$8517$new_n392_ $abc$8517$new_n728_ $abc$8517$new_n714_ $abc$8517$new_n727_ $abc$8517$new_n729_ +1000 1 +1010 1 +1011 1 +.names $abc$8517$new_n731_ $abc$8517$new_n733_ $abc$8517$new_n368_ $abc$8517$new_n543_ $abc$8517$new_n734_ $abc$8517$new_n600_ $abc$8517$new_n730_ +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100110 1 +101000 1 +101001 1 +101010 1 +101011 1 +101110 1 +101111 1 +.names $abc$8517$new_n393_ $abc$8517$new_n576_ $abc$8517$new_n732_ $abc$8517$new_n547_ $abc$8517$new_n579_ $abc$8517$new_n731_ +00000 1 +00010 1 +00100 1 +00101 1 +10000 1 +10001 1 +10100 1 +10101 1 +10110 1 +10111 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n310_ rs2_data[10] inst[30] $abc$8517$new_n324_ $abc$8517$new_n732_ +0000 1 +0001 1 +0011 1 +0100 1 +0101 1 +0111 1 +1000 1 +1001 1 +1010 1 +1011 1 +.names $abc$8517$new_n368_ $abc$8517$new_n549_ $abc$8517$new_n585_ $abc$8517$new_n584_ $abc$8517$new_n595_ $abc$8517$new_n596_ $abc$8517$new_n733_ +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +110001 1 +110010 1 +110011 1 +110101 1 +110110 1 +110111 1 +111001 1 +111010 1 +111011 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n681_ $abc$8517$new_n735_ $abc$8517$new_n736_ $abc$8517$new_n734_ +00100 1 +00101 1 +00110 1 +00111 1 +01100 1 +01101 1 +01110 1 +01111 1 +10010 1 +10011 1 +10110 1 +10111 1 +11001 1 +11011 1 +11101 1 +11111 1 +.names $abc$8517$new_n517_ $abc$8517$new_n504_ $abc$8517$new_n735_ +00 1 +.names $abc$8517$new_n506_ $abc$8517$new_n505_ $abc$8517$new_n736_ +00 1 +.names $abc$8517$new_n548_ $abc$8517$new_n392_ $abc$8517$new_n738_ $abc$8517$new_n405_ $abc$8517$new_n711_ $abc$8517$new_n737_ +10100 1 +10101 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +11110 1 +.names $abc$8517$new_n398_ $abc$8517$new_n396_ $abc$8517$new_n739_ $abc$8517$new_n738_ +000 1 +100 1 +101 1 +110 1 +.names $abc$8517$new_n397_ $abc$8517$new_n399_ $abc$8517$new_n739_ +10 1 +.names $abc$8517$new_n345_ $abc$8517$new_n742_ $abc$8517$new_n692_ $abc$8517$new_n718_ $abc$8517$new_n741_ $abc$8517$new_n740_ +00011 1 +00100 1 +00101 1 +00110 1 +00111 1 +01000 1 +01001 1 +01010 1 +01011 1 +01100 1 +01101 1 +01110 1 +01111 1 +.names $abc$8517$new_n587_ $abc$8517$new_n586_ $abc$8517$new_n741_ +00 1 +.names $abc$8517$new_n719_ $abc$8517$new_n589_ $abc$8517$new_n586_ $abc$8517$new_n742_ +100 1 +.names $abc$8517$new_n546_ $abc$8517$new_n755_ $abc$8517$new_n757_ $abc$8517$new_n729_ $abc$8517$new_n1376_ bus_address[11] +00000 1 +00010 1 +00100 1 +00110 1 +01000 1 +01010 1 +01100 1 +01110 1 +10000 1 +10010 1 +10011 1 +10100 1 +10101 1 +10110 1 +10111 1 +11000 1 +11001 1 +11010 1 +11100 1 +11110 1 +.names $abc$8517$new_n392_ $abc$8517$new_n738_ $abc$8517$new_n405_ $abc$8517$new_n711_ $abc$8517$new_n745_ +0000 1 +0001 1 +0010 1 +0011 1 +0110 1 +.names $abc$8517$new_n345_ $abc$8517$new_n748_ $abc$8517$new_n692_ $abc$8517$new_n719_ $abc$8517$new_n747_ $abc$8517$new_n746_ +00010 1 +00100 1 +00101 1 +00110 1 +00111 1 +01000 1 +01001 1 +01010 1 +01011 1 +01100 1 +01101 1 +01110 1 +01111 1 +.names $abc$8517$new_n362_ $abc$8517$new_n434_ $abc$8517$new_n747_ +01 1 +10 1 +11 1 +.names $abc$8517$new_n718_ $abc$8517$new_n362_ $abc$8517$new_n607_ $abc$8517$new_n608_ $abc$8517$new_n748_ +1001 1 +1011 1 +1100 1 +1101 1 +.names $abc$8517$new_n543_ $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n612_ $abc$8517$new_n697_ $abc$8517$new_n753_ $abc$8517$new_n752_ +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110001 1 +110100 1 +110101 1 +111000 1 +111010 1 +111100 1 +111110 1 +.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n394_ $abc$8517$new_n398_ $abc$8517$new_n393_ $abc$8517$new_n399_ $abc$8517$new_n753_ +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n486_ $abc$8517$new_n497_ $abc$8517$new_n394_ $abc$8517$new_n388_ $abc$8517$new_n479_ $abc$8517$new_n754_ +00010 1 +00100 1 +00110 1 +00111 1 +01000 1 +01001 1 +01010 1 +01011 1 +01100 1 +01101 1 +01110 1 +01111 1 +10000 1 +10001 1 +10011 1 +10101 1 +10110 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n394_ $abc$8517$new_n388_ $abc$8517$new_n755_ +01 1 +10 1 +.names $abc$8517$new_n310_ $abc$8517$new_n393_ rs2_data[10] $abc$8517$new_n324_ inst[30] $abc$8517$new_n756_ +00000 1 +00010 1 +00011 1 +00100 1 +00110 1 +00111 1 +10000 1 +10001 1 +10010 1 +10011 1 +.names $abc$8517$new_n393_ $abc$8517$new_n732_ $abc$8517$new_n757_ +00 1 +.names $abc$8517$new_n546_ $abc$8517$new_n772_ $abc$8517$new_n759_ $abc$8517$new_n1379_ bus_address[12] +0000 1 +0010 1 +0100 1 +0110 1 +1000 1 +1010 1 +1011 1 +1100 1 +1101 1 +1110 1 +.names $abc$8517$new_n392_ $abc$8517$new_n755_ $abc$8517$new_n728_ $abc$8517$new_n760_ $abc$8517$new_n714_ $abc$8517$new_n727_ $abc$8517$new_n759_ +000000 1 +000001 1 +000010 1 +000011 1 +001000 1 +001001 1 +001010 1 +001011 1 +010000 1 +010001 1 +010010 1 +010011 1 +011000 1 +011001 1 +011010 1 +011011 1 +100000 1 +100001 1 +100010 1 +100011 1 +101000 1 +101001 1 +101010 1 +101011 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +.names $abc$8517$new_n394_ $abc$8517$new_n388_ $abc$8517$new_n757_ $abc$8517$new_n760_ +010 1 +100 1 +110 1 +111 1 +.names $abc$8517$new_n345_ $abc$8517$new_n630_ $abc$8517$new_n719_ $abc$8517$new_n692_ $abc$8517$new_n629_ $abc$8517$new_n718_ $abc$8517$new_n763_ +000100 1 +000101 1 +000110 1 +000111 1 +001010 1 +001011 1 +001100 1 +001101 1 +001110 1 +001111 1 +010001 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n542_ $abc$8517$new_n635_ $abc$8517$new_n704_ $abc$8517$new_n767_ $abc$8517$new_n766_ +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +000110 1 +000111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +110001 1 +110011 1 +110101 1 +110111 1 +111001 1 +111011 1 +111101 1 +111111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n509_ $abc$8517$new_n507_ $abc$8517$new_n505_ $abc$8517$new_n506_ $abc$8517$new_n767_ +00000 1 +00100 1 +01000 1 +01100 1 +10000 1 +10001 1 +10010 1 +10011 1 +.names $abc$8517$new_n486_ $abc$8517$new_n497_ $abc$8517$new_n386_ $abc$8517$new_n385_ $abc$8517$new_n479_ $abc$8517$new_n768_ +00010 1 +00100 1 +00110 1 +00111 1 +01000 1 +01001 1 +01010 1 +01011 1 +01100 1 +01101 1 +01110 1 +01111 1 +10000 1 +10001 1 +10011 1 +10101 1 +10110 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n405_ $abc$8517$new_n387_ $abc$8517$new_n769_ +11 1 +.names $abc$8517$new_n387_ $abc$8517$new_n398_ $abc$8517$new_n396_ $abc$8517$new_n739_ $abc$8517$new_n402_ $abc$8517$new_n770_ +00001 1 +00011 1 +00101 1 +00111 1 +01001 1 +01011 1 +01101 1 +01111 1 +10000 1 +10001 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +.names $abc$8517$new_n386_ $abc$8517$new_n385_ $abc$8517$new_n772_ +01 1 +10 1 +.names $abc$8517$new_n546_ $abc$8517$new_n382_ $abc$8517$new_n386_ $abc$8517$new_n385_ $abc$8517$new_n759_ $abc$8517$new_n1382_ bus_address[13] +000000 1 +000010 1 +000100 1 +000110 1 +001000 1 +001010 1 +001100 1 +001110 1 +010000 1 +010010 1 +010100 1 +010110 1 +011000 1 +011010 1 +011100 1 +011110 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100110 1 +100111 1 +101000 1 +101010 1 +101011 1 +101100 1 +101110 1 +110000 1 +110010 1 +110100 1 +110101 1 +110110 1 +111000 1 +111001 1 +111010 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n772_ $abc$8517$new_n770_ $abc$8517$new_n769_ $abc$8517$new_n711_ $abc$8517$new_n775_ +0000 1 +0001 1 +0010 1 +0011 1 +0110 1 +.names $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n581_ $abc$8517$new_n655_ $abc$8517$new_n721_ $abc$8517$new_n778_ $abc$8517$new_n777_ +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +000110 1 +000111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +110001 1 +110011 1 +110101 1 +110111 1 +111001 1 +111011 1 +111101 1 +111111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n384_ $abc$8517$new_n394_ $abc$8517$new_n386_ $abc$8517$new_n393_ $abc$8517$new_n778_ +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n345_ $abc$8517$new_n692_ $abc$8517$new_n718_ $abc$8517$new_n651_ $abc$8517$new_n719_ $abc$8517$new_n652_ $abc$8517$new_n781_ +000010 1 +000110 1 +001010 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n486_ $abc$8517$new_n497_ $abc$8517$new_n384_ $abc$8517$new_n401_ $abc$8517$new_n479_ $abc$8517$new_n782_ +00010 1 +00100 1 +00110 1 +00111 1 +01000 1 +01001 1 +01010 1 +01011 1 +01100 1 +01101 1 +01110 1 +01111 1 +10000 1 +10001 1 +10011 1 +10101 1 +10110 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n385_ $abc$8517$new_n386_ $abc$8517$new_n783_ +10 1 +.names $abc$8517$new_n797_ $abc$8517$new_n548_ $abc$8517$new_n787_ $abc$8517$new_n546_ $abc$8517$new_n785_ $abc$8517$new_n788_ bus_address[14] +000000 1 +000010 1 +000100 1 +000110 1 +000111 1 +001000 1 +001010 1 +001100 1 +001110 1 +001111 1 +010000 1 +010010 1 +010100 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +100000 1 +100010 1 +100100 1 +100101 1 +100110 1 +101000 1 +101010 1 +101100 1 +101101 1 +101110 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111010 1 +111100 1 +111101 1 +111110 1 +.names $abc$8517$new_n786_ $abc$8517$new_n759_ $abc$8517$new_n772_ $abc$8517$new_n382_ $abc$8517$new_n785_ +0000 1 +0001 1 +0010 1 +0011 1 +0100 1 +0101 1 +0110 1 +0111 1 +1111 1 +.names $abc$8517$new_n386_ $abc$8517$new_n385_ $abc$8517$new_n384_ $abc$8517$new_n401_ $abc$8517$new_n786_ +0011 1 +0101 1 +0110 1 +0111 1 +1001 1 +1010 1 +1011 1 +1101 1 +1110 1 +1111 1 +.names $abc$8517$new_n384_ $abc$8517$new_n401_ $abc$8517$new_n783_ $abc$8517$new_n775_ $abc$8517$new_n787_ +0000 1 +1000 1 +1001 1 +1010 1 +1100 1 +.names $abc$8517$new_n796_ $abc$8517$new_n794_ $abc$8517$new_n345_ $abc$8517$new_n543_ $abc$8517$new_n795_ $abc$8517$new_n789_ $abc$8517$new_n788_ +100010 1 +100011 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101101 1 +101111 1 +.names $abc$8517$new_n368_ $abc$8517$new_n790_ $abc$8517$new_n791_ $abc$8517$new_n789_ +010 1 +011 1 +101 1 +111 1 +.names $abc$8517$new_n362_ $abc$8517$new_n680_ $abc$8517$new_n681_ $abc$8517$new_n790_ +000 1 +001 1 +101 1 +111 1 +.names $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n735_ $abc$8517$new_n736_ $abc$8517$new_n792_ $abc$8517$new_n793_ $abc$8517$new_n791_ +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001110 1 +001111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +110001 1 +110011 1 +110101 1 +110111 1 +111001 1 +111011 1 +111101 1 +111111 1 +.names $abc$8517$new_n509_ $abc$8517$new_n507_ $abc$8517$new_n792_ +00 1 +.names $abc$8517$new_n511_ $abc$8517$new_n510_ $abc$8517$new_n793_ +00 1 +.names $abc$8517$new_n549_ $abc$8517$new_n368_ $abc$8517$new_n666_ $abc$8517$new_n1367_ $abc$8517$new_n794_ +1000 1 +1001 1 +1100 1 +1110 1 +.names $abc$8517$new_n692_ $abc$8517$new_n671_ $abc$8517$new_n719_ $abc$8517$new_n718_ $abc$8517$new_n670_ $abc$8517$new_n795_ +00000 1 +00001 1 +00010 1 +01000 1 +01001 1 +01010 1 +01100 1 +01101 1 +01110 1 +.names $abc$8517$new_n486_ $abc$8517$new_n497_ $abc$8517$new_n380_ $abc$8517$new_n376_ $abc$8517$new_n479_ $abc$8517$new_n796_ +00010 1 +00100 1 +00110 1 +00111 1 +01000 1 +01001 1 +01010 1 +01011 1 +01100 1 +01101 1 +01110 1 +01111 1 +10000 1 +10001 1 +10011 1 +10101 1 +10110 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n380_ $abc$8517$new_n376_ $abc$8517$new_n797_ +01 1 +10 1 +.names $abc$8517$new_n808_ $abc$8517$new_n802_ $abc$8517$new_n800_ $abc$8517$new_n377_ $abc$8517$new_n799_ $abc$8517$new_n546_ bus_address[15] +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +000110 1 +000111 1 +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +100001 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n380_ $abc$8517$new_n376_ $abc$8517$new_n382_ $abc$8517$new_n759_ $abc$8517$new_n772_ $abc$8517$new_n786_ $abc$8517$new_n799_ +010001 1 +010011 1 +010101 1 +010111 1 +011001 1 +011011 1 +011101 1 +100001 1 +100011 1 +100101 1 +100111 1 +101001 1 +101011 1 +101101 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n548_ $abc$8517$new_n377_ $abc$8517$new_n787_ $abc$8517$new_n376_ $abc$8517$new_n380_ $abc$8517$new_n800_ +10001 1 +10100 1 +10101 1 +10111 1 +11000 1 +11010 1 +11011 1 +11110 1 +.names $abc$8517$new_n543_ $abc$8517$new_n803_ $abc$8517$new_n802_ +10 1 +.names $abc$8517$new_n368_ $abc$8517$new_n804_ $abc$8517$new_n807_ $abc$8517$new_n803_ +001 1 +011 1 +110 1 +111 1 +.names $abc$8517$new_n362_ $abc$8517$new_n805_ $abc$8517$new_n753_ $abc$8517$new_n804_ +001 1 +011 1 +110 1 +111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n806_ $abc$8517$new_n384_ $abc$8517$new_n386_ $abc$8517$new_n805_ +00001 1 +00011 1 +00101 1 +00111 1 +01010 1 +01011 1 +01110 1 +01111 1 +10100 1 +10101 1 +10110 1 +10111 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n519_ $abc$8517$new_n379_ $abc$8517$new_n380_ $abc$8517$new_n806_ +001 1 +011 1 +110 1 +111 1 +.names $abc$8517$new_n362_ $abc$8517$new_n612_ $abc$8517$new_n697_ $abc$8517$new_n807_ +000 1 +001 1 +101 1 +111 1 +.names $abc$8517$new_n809_ $abc$8517$new_n368_ $abc$8517$new_n549_ $abc$8517$new_n812_ $abc$8517$new_n694_ $abc$8517$new_n808_ +10000 1 +10001 1 +10010 1 +10011 1 +10110 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +11101 1 +11111 1 +.names $abc$8517$new_n810_ $abc$8517$new_n811_ $abc$8517$new_n576_ $abc$8517$new_n379_ $abc$8517$new_n408_ $abc$8517$new_n579_ $abc$8517$new_n809_ +100000 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +100111 1 +101110 1 +101111 1 +.names $abc$8517$new_n377_ $abc$8517$new_n547_ $abc$8517$new_n368_ $abc$8517$new_n551_ $abc$8517$new_n690_ $abc$8517$new_n608_ $abc$8517$new_n810_ +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +000110 1 +000111 1 +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +.names $abc$8517$new_n568_ $abc$8517$new_n434_ $abc$8517$new_n345_ $abc$8517$new_n811_ +100 1 +.names $abc$8517$new_n362_ $abc$8517$new_n606_ $abc$8517$new_n607_ $abc$8517$new_n812_ +001 1 +011 1 +110 1 +111 1 +.names $abc$8517$new_n336_ $abc$8517$new_n548_ $abc$8517$new_n826_ $abc$8517$new_n546_ $abc$8517$new_n814_ $abc$8517$new_n817_ bus_address[16] +000000 1 +000010 1 +000100 1 +000110 1 +000111 1 +001000 1 +001010 1 +001100 1 +001110 1 +001111 1 +010000 1 +010010 1 +010100 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +100000 1 +100010 1 +100100 1 +100101 1 +100110 1 +101000 1 +101010 1 +101100 1 +101101 1 +101110 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111010 1 +111100 1 +111101 1 +111110 1 +.names $abc$8517$new_n816_ $abc$8517$new_n815_ $abc$8517$new_n814_ +00 1 +01 1 +11 1 +.names $abc$8517$new_n797_ $abc$8517$new_n377_ $abc$8517$new_n786_ $abc$8517$new_n759_ $abc$8517$new_n772_ $abc$8517$new_n382_ $abc$8517$new_n815_ +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111111 1 +.names $abc$8517$new_n380_ $abc$8517$new_n376_ $abc$8517$new_n379_ $abc$8517$new_n408_ $abc$8517$new_n816_ +0011 1 +0101 1 +0110 1 +0111 1 +1001 1 +1010 1 +1011 1 +1101 1 +1110 1 +1111 1 +.names $abc$8517$new_n818_ $abc$8517$new_n822_ $abc$8517$new_n811_ $abc$8517$new_n520_ $abc$8517$new_n549_ $abc$8517$new_n817_ +10000 1 +10010 1 +10011 1 +.names $abc$8517$new_n821_ $abc$8517$new_n819_ $abc$8517$new_n542_ $abc$8517$new_n362_ $abc$8517$new_n368_ $abc$8517$new_n818_ +10000 1 +10001 1 +10010 1 +10011 1 +10100 1 +10101 1 +10110 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +.names $abc$8517$new_n820_ $abc$8517$new_n345_ $abc$8517$new_n819_ +10 1 +.names $abc$8517$new_n497_ $abc$8517$new_n489_ $abc$8517$new_n478_ $abc$8517$new_n820_ +111 1 +.names $abc$8517$new_n486_ $abc$8517$new_n497_ $abc$8517$new_n339_ $abc$8517$new_n337_ $abc$8517$new_n479_ $abc$8517$new_n821_ +00010 1 +00100 1 +00110 1 +00111 1 +01000 1 +01001 1 +01010 1 +01011 1 +01100 1 +01101 1 +01110 1 +01111 1 +10000 1 +10001 1 +10011 1 +10101 1 +10110 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n543_ $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n823_ $abc$8517$new_n824_ $abc$8517$new_n767_ $abc$8517$new_n822_ +100000 1 +100001 1 +100010 1 +100011 1 +101000 1 +101001 1 +101010 1 +101011 1 +110000 1 +110010 1 +110100 1 +110110 1 +111000 1 +111001 1 +111100 1 +111101 1 +.names $abc$8517$new_n362_ $abc$8517$new_n635_ $abc$8517$new_n704_ $abc$8517$new_n823_ +010 1 +011 1 +101 1 +111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n793_ $abc$8517$new_n825_ $abc$8517$new_n824_ +010 1 +011 1 +101 1 +111 1 +.names $abc$8517$new_n522_ $abc$8517$new_n512_ $abc$8517$new_n825_ +00 1 +.names $abc$8517$new_n827_ $abc$8517$new_n828_ $abc$8517$new_n769_ $abc$8517$new_n711_ $abc$8517$new_n826_ +1000 1 +1001 1 +1010 1 +1011 1 +1100 1 +1101 1 +1111 1 +.names $abc$8517$new_n829_ $abc$8517$new_n770_ $abc$8517$new_n828_ $abc$8517$new_n827_ +100 1 +110 1 +111 1 +.names $abc$8517$new_n381_ $abc$8517$new_n375_ $abc$8517$new_n828_ +11 1 +.names $abc$8517$new_n375_ $abc$8517$new_n384_ $abc$8517$new_n401_ $abc$8517$new_n783_ $abc$8517$new_n407_ $abc$8517$new_n829_ +00001 1 +00011 1 +00101 1 +00111 1 +01001 1 +01011 1 +01101 1 +01111 1 +10000 1 +10001 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +.names $abc$8517$new_n319_ $abc$8517$new_n548_ $abc$8517$new_n833_ $abc$8517$new_n546_ $abc$8517$new_n832_ $abc$8517$new_n1384_ bus_address[17] +000000 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001010 1 +001100 1 +001101 1 +001110 1 +010000 1 +010010 1 +010100 1 +010101 1 +010110 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +100000 1 +100010 1 +100100 1 +100110 1 +100111 1 +101000 1 +101010 1 +101100 1 +101110 1 +101111 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111010 1 +111100 1 +111110 1 +111111 1 +.names $abc$8517$new_n816_ $abc$8517$new_n339_ $abc$8517$new_n815_ $abc$8517$new_n337_ $abc$8517$new_n832_ +0101 1 +0111 1 +1001 1 +1100 1 +1101 1 +1111 1 +.names $abc$8517$new_n834_ $abc$8517$new_n337_ $abc$8517$new_n339_ $abc$8517$new_n833_ +000 1 +001 1 +011 1 +.names $abc$8517$new_n336_ $abc$8517$new_n827_ $abc$8517$new_n828_ $abc$8517$new_n769_ $abc$8517$new_n711_ $abc$8517$new_n834_ +00000 1 +00001 1 +00010 1 +00011 1 +00100 1 +00101 1 +00110 1 +00111 1 +01110 1 +.names $abc$8517$new_n362_ $abc$8517$new_n655_ $abc$8517$new_n721_ $abc$8517$new_n837_ +010 1 +011 1 +101 1 +111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n806_ $abc$8517$new_n839_ $abc$8517$new_n838_ +010 1 +011 1 +101 1 +111 1 +.names $abc$8517$new_n519_ $abc$8517$new_n321_ $abc$8517$new_n339_ $abc$8517$new_n839_ +001 1 +011 1 +110 1 +111 1 +.names $abc$8517$new_n811_ $abc$8517$new_n321_ $abc$8517$new_n576_ $abc$8517$new_n420_ $abc$8517$new_n547_ $abc$8517$new_n579_ $abc$8517$new_n840_ +000000 1 +000010 1 +000100 1 +000101 1 +010000 1 +010001 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n816_ $abc$8517$new_n321_ $abc$8517$new_n339_ $abc$8517$new_n815_ $abc$8517$new_n337_ $abc$8517$new_n420_ $abc$8517$new_n843_ +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +000110 1 +000111 1 +001000 1 +001001 1 +001010 1 +001100 1 +001101 1 +001110 1 +010000 1 +010010 1 +010100 1 +010110 1 +011000 1 +011100 1 +100000 1 +100001 1 +100010 1 +100100 1 +100101 1 +100110 1 +100111 1 +101000 1 +101010 1 +101100 1 +101101 1 +101110 1 +110000 1 +110100 1 +110110 1 +111100 1 +.names $abc$8517$new_n548_ $abc$8517$new_n316_ $abc$8517$new_n846_ $abc$8517$new_n847_ $abc$8517$new_n834_ $abc$8517$new_n845_ +10010 1 +10100 1 +10101 1 +10110 1 +10111 1 +11000 1 +11001 1 +11011 1 +.names $abc$8517$new_n321_ $abc$8517$new_n420_ $abc$8517$new_n846_ +10 1 +.names $abc$8517$new_n420_ $abc$8517$new_n321_ $abc$8517$new_n337_ $abc$8517$new_n339_ $abc$8517$new_n847_ +0000 1 +0001 1 +0011 1 +0100 1 +0101 1 +0111 1 +1100 1 +1101 1 +1111 1 +.names $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n851_ $abc$8517$new_n792_ $abc$8517$new_n793_ $abc$8517$new_n850_ +00010 1 +00011 1 +00110 1 +00111 1 +01001 1 +01011 1 +01101 1 +01111 1 +10100 1 +10101 1 +10110 1 +10111 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n825_ $abc$8517$new_n852_ $abc$8517$new_n851_ +010 1 +011 1 +101 1 +111 1 +.names $abc$8517$new_n524_ $abc$8517$new_n523_ $abc$8517$new_n852_ +00 1 +.names $abc$8517$new_n811_ $abc$8517$new_n318_ $abc$8517$new_n576_ $abc$8517$new_n423_ $abc$8517$new_n547_ $abc$8517$new_n579_ $abc$8517$new_n853_ +000000 1 +000010 1 +000100 1 +000101 1 +010000 1 +010001 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n546_ $abc$8517$new_n309_ $abc$8517$new_n865_ $abc$8517$new_n316_ $abc$8517$new_n843_ $abc$8517$new_n855_ bus_address[19] +000000 1 +000010 1 +000100 1 +000110 1 +001000 1 +001010 1 +001100 1 +001110 1 +010000 1 +010010 1 +010100 1 +010110 1 +011000 1 +011010 1 +011100 1 +011110 1 +100000 1 +100010 1 +100100 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +111000 1 +111010 1 +111100 1 +111110 1 +.names $abc$8517$new_n857_ $abc$8517$new_n309_ $abc$8517$new_n856_ $abc$8517$new_n548_ $abc$8517$new_n855_ +1000 1 +1001 1 +1010 1 +1100 1 +1110 1 +1111 1 +.names $abc$8517$new_n318_ $abc$8517$new_n423_ $abc$8517$new_n847_ $abc$8517$new_n834_ $abc$8517$new_n846_ $abc$8517$new_n856_ +00001 1 +00011 1 +00100 1 +00101 1 +00111 1 +10000 1 +10001 1 +10010 1 +10011 1 +10100 1 +10101 1 +10110 1 +10111 1 +11001 1 +11011 1 +11100 1 +11101 1 +11111 1 +.names $abc$8517$new_n858_ $abc$8517$new_n864_ $abc$8517$new_n362_ $abc$8517$new_n368_ $abc$8517$new_n612_ $abc$8517$new_n819_ $abc$8517$new_n857_ +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +.names $abc$8517$new_n345_ $abc$8517$new_n820_ $abc$8517$new_n368_ $abc$8517$new_n859_ $abc$8517$new_n860_ $abc$8517$new_n863_ $abc$8517$new_n858_ +100000 1 +100001 1 +100010 1 +100011 1 +101000 1 +101001 1 +101010 1 +101011 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110110 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +.names $abc$8517$new_n567_ $abc$8517$new_n604_ $abc$8517$new_n568_ $abc$8517$new_n609_ $abc$8517$new_n859_ +0000 1 +0001 1 +0011 1 +0100 1 +0101 1 +0111 1 +1100 1 +1101 1 +1111 1 +.names $abc$8517$new_n362_ $abc$8517$new_n805_ $abc$8517$new_n861_ $abc$8517$new_n860_ +010 1 +011 1 +101 1 +111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n839_ $abc$8517$new_n862_ $abc$8517$new_n861_ +010 1 +011 1 +101 1 +111 1 +.names $abc$8517$new_n519_ $abc$8517$new_n315_ $abc$8517$new_n318_ $abc$8517$new_n862_ +001 1 +011 1 +110 1 +111 1 +.names $abc$8517$new_n362_ $abc$8517$new_n697_ $abc$8517$new_n753_ $abc$8517$new_n863_ +010 1 +011 1 +101 1 +111 1 +.names $abc$8517$new_n811_ $abc$8517$new_n315_ $abc$8517$new_n576_ $abc$8517$new_n422_ $abc$8517$new_n547_ $abc$8517$new_n579_ $abc$8517$new_n864_ +000000 1 +000010 1 +000100 1 +000101 1 +010000 1 +010001 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n318_ $abc$8517$new_n423_ $abc$8517$new_n865_ +00 1 +.names $abc$8517$new_n546_ $abc$8517$new_n333_ $abc$8517$new_n315_ $abc$8517$new_n422_ $abc$8517$new_n867_ $abc$8517$new_n1389_ bus_address[20] +000000 1 +000010 1 +000100 1 +000110 1 +001000 1 +001010 1 +001100 1 +001110 1 +010000 1 +010010 1 +010100 1 +010110 1 +011000 1 +011010 1 +011100 1 +011110 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +101000 1 +101001 1 +101010 1 +101100 1 +101110 1 +110000 1 +110010 1 +110100 1 +110110 1 +110111 1 +111000 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n865_ $abc$8517$new_n843_ $abc$8517$new_n316_ $abc$8517$new_n867_ +000 1 +001 1 +010 1 +.names $abc$8517$new_n870_ $abc$8517$new_n834_ $abc$8517$new_n308_ $abc$8517$new_n869_ +100 1 +101 1 +110 1 +.names $abc$8517$new_n871_ $abc$8517$new_n846_ $abc$8517$new_n847_ $abc$8517$new_n316_ $abc$8517$new_n309_ $abc$8517$new_n870_ +00001 1 +00010 1 +00011 1 +00100 1 +00101 1 +00110 1 +00111 1 +01000 1 +01001 1 +01010 1 +01011 1 +01100 1 +01101 1 +01110 1 +01111 1 +.names $abc$8517$new_n423_ $abc$8517$new_n422_ $abc$8517$new_n318_ $abc$8517$new_n315_ $abc$8517$new_n871_ +0100 1 +0110 1 +1000 1 +1100 1 +1101 1 +1110 1 +.names $abc$8517$new_n362_ $abc$8517$new_n824_ $abc$8517$new_n875_ $abc$8517$new_n874_ +010 1 +011 1 +101 1 +111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n852_ $abc$8517$new_n876_ $abc$8517$new_n875_ +010 1 +011 1 +101 1 +111 1 +.names $abc$8517$new_n527_ $abc$8517$new_n525_ $abc$8517$new_n876_ +00 1 +.names $abc$8517$new_n362_ $abc$8517$new_n704_ $abc$8517$new_n767_ $abc$8517$new_n877_ +010 1 +011 1 +101 1 +111 1 +.names $abc$8517$new_n811_ $abc$8517$new_n335_ $abc$8517$new_n576_ $abc$8517$new_n418_ $abc$8517$new_n547_ $abc$8517$new_n579_ $abc$8517$new_n878_ +000000 1 +000010 1 +000100 1 +000101 1 +010000 1 +010001 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n345_ $abc$8517$new_n627_ $abc$8517$new_n567_ $abc$8517$new_n568_ $abc$8517$new_n628_ $abc$8517$new_n631_ $abc$8517$new_n879_ +100110 1 +100111 1 +101001 1 +101011 1 +101101 1 +101110 1 +101111 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n546_ $abc$8517$new_n330_ $abc$8517$new_n892_ $abc$8517$new_n881_ $abc$8517$new_n882_ bus_address[21] +00000 1 +00010 1 +00100 1 +00110 1 +01000 1 +01010 1 +01100 1 +01110 1 +10000 1 +10010 1 +10011 1 +10100 1 +10101 1 +10110 1 +10111 1 +11000 1 +11001 1 +11010 1 +11100 1 +11110 1 +.names $abc$8517$new_n333_ $abc$8517$new_n315_ $abc$8517$new_n422_ $abc$8517$new_n316_ $abc$8517$new_n843_ $abc$8517$new_n865_ $abc$8517$new_n881_ +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +100111 1 +101001 1 +101011 1 +101101 1 +101110 1 +101111 1 +110001 1 +110011 1 +110101 1 +110110 1 +110111 1 +.names $abc$8517$new_n883_ $abc$8517$new_n333_ $abc$8517$new_n330_ rs1_data[20] $abc$8517$new_n869_ $abc$8517$new_n548_ $abc$8517$new_n882_ +100000 1 +100001 1 +100010 1 +100100 1 +100101 1 +100110 1 +101000 1 +101010 1 +101011 1 +101100 1 +101110 1 +101111 1 +110000 1 +110010 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111110 1 +.names $abc$8517$new_n890_ $abc$8517$new_n345_ $abc$8517$new_n884_ $abc$8517$new_n368_ $abc$8517$new_n819_ $abc$8517$new_n654_ $abc$8517$new_n883_ +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111111 1 +.names $abc$8517$new_n885_ $abc$8517$new_n368_ $abc$8517$new_n820_ $abc$8517$new_n886_ $abc$8517$new_n889_ $abc$8517$new_n884_ +10000 1 +10001 1 +10010 1 +10011 1 +10101 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +11110 1 +11111 1 +.names $abc$8517$new_n650_ $abc$8517$new_n368_ $abc$8517$new_n568_ $abc$8517$new_n648_ $abc$8517$new_n652_ $abc$8517$new_n885_ +00000 1 +00001 1 +00010 1 +00011 1 +00101 1 +00111 1 +01000 1 +01001 1 +01010 1 +01011 1 +01110 1 +01111 1 +.names $abc$8517$new_n362_ $abc$8517$new_n838_ $abc$8517$new_n887_ $abc$8517$new_n886_ +010 1 +011 1 +101 1 +111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n862_ $abc$8517$new_n888_ $abc$8517$new_n887_ +010 1 +011 1 +101 1 +111 1 +.names $abc$8517$new_n519_ $abc$8517$new_n332_ $abc$8517$new_n335_ $abc$8517$new_n888_ +001 1 +011 1 +110 1 +111 1 +.names $abc$8517$new_n362_ $abc$8517$new_n721_ $abc$8517$new_n778_ $abc$8517$new_n889_ +010 1 +011 1 +101 1 +111 1 +.names $abc$8517$new_n811_ $abc$8517$new_n332_ $abc$8517$new_n576_ $abc$8517$new_n416_ $abc$8517$new_n547_ $abc$8517$new_n579_ $abc$8517$new_n890_ +000000 1 +000010 1 +000100 1 +000101 1 +010000 1 +010001 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n335_ $abc$8517$new_n418_ $abc$8517$new_n892_ +00 1 +.names $abc$8517$new_n546_ $abc$8517$new_n327_ $abc$8517$new_n332_ $abc$8517$new_n416_ $abc$8517$new_n892_ $abc$8517$new_n881_ $abc$8517$new_n894_ +100000 1 +100001 1 +100010 1 +100011 1 +100101 1 +100110 1 +100111 1 +101001 1 +101010 1 +101011 1 +110100 1 +111000 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n548_ $abc$8517$new_n327_ $abc$8517$new_n415_ $abc$8517$new_n333_ $abc$8517$new_n330_ $abc$8517$new_n869_ $abc$8517$new_n896_ +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +.names $abc$8517$new_n820_ $abc$8517$new_n345_ $abc$8517$new_n368_ $abc$8517$new_n898_ $abc$8517$new_n790_ $abc$8517$new_n791_ $abc$8517$new_n897_ +101000 1 +101001 1 +101100 1 +101101 1 +110000 1 +110010 1 +110100 1 +110110 1 +111000 1 +111001 1 +111010 1 +111011 1 +.names $abc$8517$new_n362_ $abc$8517$new_n851_ $abc$8517$new_n899_ $abc$8517$new_n898_ +010 1 +011 1 +101 1 +111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n876_ $abc$8517$new_n900_ $abc$8517$new_n899_ +010 1 +011 1 +101 1 +111 1 +.names $abc$8517$new_n529_ $abc$8517$new_n528_ $abc$8517$new_n900_ +00 1 +.names $abc$8517$new_n345_ $abc$8517$new_n568_ $abc$8517$new_n368_ $abc$8517$new_n669_ $abc$8517$new_n666_ $abc$8517$new_n671_ $abc$8517$new_n901_ +100100 1 +100101 1 +100110 1 +100111 1 +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110010 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n323_ $abc$8517$new_n548_ $abc$8517$new_n906_ $abc$8517$new_n546_ $abc$8517$new_n904_ $abc$8517$new_n907_ bus_address[23] +000000 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001010 1 +001100 1 +001101 1 +001110 1 +010000 1 +010010 1 +010100 1 +010101 1 +010110 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +100000 1 +100010 1 +100100 1 +100110 1 +100111 1 +101000 1 +101010 1 +101100 1 +101110 1 +101111 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111010 1 +111100 1 +111110 1 +111111 1 +.names $abc$8517$new_n327_ $abc$8517$new_n332_ $abc$8517$new_n881_ $abc$8517$new_n892_ $abc$8517$new_n416_ $abc$8517$new_n329_ $abc$8517$new_n904_ +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +010001 1 +010011 1 +010101 1 +010111 1 +011001 1 +011011 1 +011101 1 +011111 1 +100010 1 +100011 1 +110000 1 +110001 1 +110010 1 +110011 1 +110110 1 +110111 1 +111010 1 +111011 1 +111110 1 +111111 1 +.names $abc$8517$new_n327_ $abc$8517$new_n428_ $abc$8517$new_n415_ $abc$8517$new_n333_ $abc$8517$new_n330_ $abc$8517$new_n869_ $abc$8517$new_n906_ +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001110 1 +001111 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +.names $abc$8517$new_n913_ $abc$8517$new_n908_ $abc$8517$new_n819_ $abc$8517$new_n807_ $abc$8517$new_n368_ $abc$8517$new_n907_ +10000 1 +10001 1 +10010 1 +10011 1 +10100 1 +10110 1 +10111 1 +.names $abc$8517$new_n345_ $abc$8517$new_n820_ $abc$8517$new_n368_ $abc$8517$new_n909_ $abc$8517$new_n804_ $abc$8517$new_n910_ $abc$8517$new_n908_ +100000 1 +100001 1 +100010 1 +100011 1 +101000 1 +101001 1 +101010 1 +101011 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111110 1 +.names $abc$8517$new_n692_ $abc$8517$new_n691_ $abc$8517$new_n688_ $abc$8517$new_n689_ $abc$8517$new_n909_ +0000 1 +0001 1 +0010 1 +0011 1 +0101 1 +0110 1 +0111 1 +.names $abc$8517$new_n362_ $abc$8517$new_n861_ $abc$8517$new_n911_ $abc$8517$new_n910_ +010 1 +011 1 +101 1 +111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n888_ $abc$8517$new_n912_ $abc$8517$new_n911_ +010 1 +011 1 +101 1 +111 1 +.names $abc$8517$new_n519_ $abc$8517$new_n326_ $abc$8517$new_n329_ $abc$8517$new_n912_ +001 1 +011 1 +110 1 +111 1 +.names $abc$8517$new_n811_ $abc$8517$new_n326_ $abc$8517$new_n576_ $abc$8517$new_n426_ $abc$8517$new_n547_ $abc$8517$new_n579_ $abc$8517$new_n913_ +000000 1 +000010 1 +000100 1 +000101 1 +010000 1 +010001 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n546_ $abc$8517$new_n464_ $abc$8517$new_n326_ $abc$8517$new_n426_ $abc$8517$new_n904_ $abc$8517$new_n915_ bus_address[24] +000000 1 +000010 1 +000100 1 +000110 1 +001000 1 +001010 1 +001100 1 +001110 1 +010000 1 +010010 1 +010100 1 +010110 1 +011000 1 +011010 1 +011100 1 +011110 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +101000 1 +101001 1 +101010 1 +101100 1 +101110 1 +110000 1 +110010 1 +110100 1 +110110 1 +110111 1 +111000 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n919_ $abc$8517$new_n464_ $abc$8517$new_n916_ $abc$8517$new_n548_ $abc$8517$new_n915_ +1000 1 +1001 1 +1010 1 +1100 1 +1110 1 +1111 1 +.names $abc$8517$new_n917_ $abc$8517$new_n918_ $abc$8517$new_n307_ $abc$8517$new_n322_ $abc$8517$new_n870_ $abc$8517$new_n826_ $abc$8517$new_n916_ +100000 1 +100001 1 +100010 1 +100011 1 +100110 1 +100111 1 +101001 1 +101011 1 +101111 1 +.names $abc$8517$new_n326_ $abc$8517$new_n428_ $abc$8517$new_n426_ $abc$8517$new_n917_ +000 1 +100 1 +101 1 +110 1 +.names $abc$8517$new_n327_ $abc$8517$new_n323_ $abc$8517$new_n415_ $abc$8517$new_n918_ +000 1 +.names $abc$8517$new_n921_ $abc$8517$new_n925_ $abc$8517$new_n920_ $abc$8517$new_n922_ $abc$8517$new_n543_ $abc$8517$new_n919_ +10000 1 +10001 1 +10010 1 +.names $abc$8517$new_n819_ $abc$8517$new_n368_ $abc$8517$new_n823_ $abc$8517$new_n362_ $abc$8517$new_n542_ $abc$8517$new_n920_ +10011 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +.names $abc$8517$new_n811_ $abc$8517$new_n460_ $abc$8517$new_n576_ $abc$8517$new_n457_ $abc$8517$new_n547_ $abc$8517$new_n579_ $abc$8517$new_n921_ +000000 1 +000010 1 +000100 1 +000101 1 +010000 1 +010001 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n824_ $abc$8517$new_n875_ $abc$8517$new_n923_ $abc$8517$new_n767_ $abc$8517$new_n922_ +000000 1 +000010 1 +000100 1 +000110 1 +001000 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +100000 1 +100001 1 +100010 1 +100011 1 +101000 1 +101001 1 +101010 1 +101011 1 +110000 1 +110001 1 +110100 1 +110101 1 +111000 1 +111001 1 +111100 1 +111101 1 +.names $abc$8517$new_n356_ $abc$8517$new_n900_ $abc$8517$new_n924_ $abc$8517$new_n923_ +010 1 +011 1 +101 1 +111 1 +.names $abc$8517$new_n532_ $abc$8517$new_n530_ $abc$8517$new_n924_ +00 1 +.names $abc$8517$new_n549_ $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n692_ $abc$8517$new_n531_ $abc$8517$new_n536_ $abc$8517$new_n925_ +100100 1 +100101 1 +100110 1 +100111 1 +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110010 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n463_ $abc$8517$new_n326_ $abc$8517$new_n426_ $abc$8517$new_n460_ $abc$8517$new_n457_ $abc$8517$new_n904_ $abc$8517$new_n927_ +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +001000 1 +001001 1 +001010 1 +001100 1 +010000 1 +010001 1 +010010 1 +010100 1 +011000 1 +011001 1 +100110 1 +100111 1 +101011 1 +101101 1 +101110 1 +101111 1 +110011 1 +110101 1 +110110 1 +110111 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n548_ $abc$8517$new_n463_ $abc$8517$new_n930_ $abc$8517$new_n464_ $abc$8517$new_n916_ $abc$8517$new_n929_ +10001 1 +10010 1 +10011 1 +11000 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n457_ $abc$8517$new_n460_ $abc$8517$new_n930_ +10 1 +.names $abc$8517$new_n819_ $abc$8517$new_n368_ $abc$8517$new_n837_ $abc$8517$new_n362_ $abc$8517$new_n581_ $abc$8517$new_n932_ +10011 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +.names $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n838_ $abc$8517$new_n887_ $abc$8517$new_n934_ $abc$8517$new_n778_ $abc$8517$new_n933_ +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +100100 1 +100101 1 +100110 1 +100111 1 +101100 1 +101101 1 +101110 1 +101111 1 +110010 1 +110011 1 +110110 1 +110111 1 +111010 1 +111011 1 +111110 1 +111111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n912_ $abc$8517$new_n935_ $abc$8517$new_n934_ +010 1 +011 1 +101 1 +111 1 +.names $abc$8517$new_n560_ $abc$8517$new_n519_ $abc$8517$new_n460_ $abc$8517$new_n935_ +001 1 +010 1 +011 1 +.names $abc$8517$new_n459_ $abc$8517$new_n576_ $abc$8517$new_n455_ $abc$8517$new_n547_ $abc$8517$new_n579_ $abc$8517$new_n936_ +00000 1 +00010 1 +00100 1 +00101 1 +10000 1 +10001 1 +10100 1 +10101 1 +10110 1 +10111 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n941_ $abc$8517$new_n939_ $abc$8517$new_n453_ $abc$8517$new_n948_ $abc$8517$new_n938_ $abc$8517$new_n546_ bus_address[26] +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +000110 1 +000111 1 +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +100011 1 +100101 1 +100111 1 +101001 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n463_ $abc$8517$new_n326_ $abc$8517$new_n426_ $abc$8517$new_n460_ $abc$8517$new_n457_ $abc$8517$new_n904_ $abc$8517$new_n938_ +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +101000 1 +101001 1 +101010 1 +101100 1 +110000 1 +110001 1 +110010 1 +110100 1 +111000 1 +111001 1 +.names $abc$8517$new_n548_ $abc$8517$new_n453_ $abc$8517$new_n940_ $abc$8517$new_n939_ +101 1 +110 1 +.names $abc$8517$new_n930_ $abc$8517$new_n459_ $abc$8517$new_n455_ $abc$8517$new_n464_ $abc$8517$new_n916_ $abc$8517$new_n940_ +00001 1 +00010 1 +00011 1 +01000 1 +01001 1 +01010 1 +01011 1 +01101 1 +01110 1 +01111 1 +11000 1 +11001 1 +11010 1 +11011 1 +.names $abc$8517$new_n945_ $abc$8517$new_n947_ $abc$8517$new_n942_ $abc$8517$new_n941_ +100 1 +.names $abc$8517$new_n543_ $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n850_ $abc$8517$new_n899_ $abc$8517$new_n943_ $abc$8517$new_n942_ +100000 1 +100001 1 +100010 1 +100011 1 +101000 1 +101001 1 +101010 1 +101011 1 +110000 1 +110001 1 +110100 1 +110101 1 +111000 1 +111010 1 +111100 1 +111110 1 +.names $abc$8517$new_n356_ $abc$8517$new_n924_ $abc$8517$new_n944_ $abc$8517$new_n943_ +010 1 +011 1 +101 1 +111 1 +.names $abc$8517$new_n534_ $abc$8517$new_n533_ $abc$8517$new_n944_ +00 1 +.names $abc$8517$new_n946_ $abc$8517$new_n368_ $abc$8517$new_n819_ $abc$8517$new_n734_ $abc$8517$new_n600_ $abc$8517$new_n945_ +10000 1 +10001 1 +10010 1 +10011 1 +10100 1 +10110 1 +11000 1 +11001 1 +11010 1 +11011 1 +11110 1 +11111 1 +.names $abc$8517$new_n811_ $abc$8517$new_n450_ $abc$8517$new_n576_ $abc$8517$new_n447_ $abc$8517$new_n547_ $abc$8517$new_n579_ $abc$8517$new_n946_ +000000 1 +000010 1 +000100 1 +000101 1 +010000 1 +010001 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n345_ $abc$8517$new_n742_ $abc$8517$new_n692_ $abc$8517$new_n718_ $abc$8517$new_n741_ $abc$8517$new_n947_ +10011 1 +10100 1 +10101 1 +10110 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n459_ $abc$8517$new_n455_ $abc$8517$new_n948_ +00 1 +.names $abc$8517$new_n1396_ $abc$8517$new_n950_ bus_address[27] +00 1 +01 1 +11 1 +.names $abc$8517$new_n546_ $abc$8517$new_n452_ $abc$8517$new_n450_ $abc$8517$new_n447_ $abc$8517$new_n948_ $abc$8517$new_n938_ $abc$8517$new_n950_ +100000 1 +100001 1 +100010 1 +100011 1 +100101 1 +100110 1 +100111 1 +101001 1 +101010 1 +101011 1 +110100 1 +111000 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n452_ $abc$8517$new_n953_ $abc$8517$new_n453_ $abc$8517$new_n940_ $abc$8517$new_n952_ +0001 1 +0010 1 +0011 1 +1000 1 +1100 1 +1101 1 +1110 1 +1111 1 +.names $abc$8517$new_n447_ $abc$8517$new_n450_ $abc$8517$new_n953_ +10 1 +.names $abc$8517$new_n543_ $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n860_ $abc$8517$new_n911_ $abc$8517$new_n957_ $abc$8517$new_n956_ +100000 1 +100001 1 +100010 1 +100011 1 +101000 1 +101001 1 +101010 1 +101011 1 +110000 1 +110001 1 +110100 1 +110101 1 +111000 1 +111010 1 +111100 1 +111110 1 +.names $abc$8517$new_n356_ $abc$8517$new_n935_ $abc$8517$new_n958_ $abc$8517$new_n957_ +010 1 +011 1 +101 1 +111 1 +.names $abc$8517$new_n562_ $abc$8517$new_n561_ $abc$8517$new_n958_ +00 1 +.names $abc$8517$new_n449_ $abc$8517$new_n576_ $abc$8517$new_n445_ $abc$8517$new_n547_ $abc$8517$new_n579_ $abc$8517$new_n959_ +00000 1 +00010 1 +00100 1 +00101 1 +10000 1 +10001 1 +10100 1 +10101 1 +10110 1 +10111 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n443_ $abc$8517$new_n548_ $abc$8517$new_n963_ $abc$8517$new_n546_ $abc$8517$new_n962_ $abc$8517$new_n964_ bus_address[28] +000000 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001010 1 +001100 1 +001101 1 +001110 1 +010000 1 +010010 1 +010100 1 +010101 1 +010110 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +100000 1 +100010 1 +100100 1 +100110 1 +100111 1 +101000 1 +101010 1 +101100 1 +101110 1 +101111 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111010 1 +111100 1 +111110 1 +111111 1 +.names $abc$8517$new_n449_ $abc$8517$new_n450_ $abc$8517$new_n948_ $abc$8517$new_n938_ $abc$8517$new_n447_ $abc$8517$new_n445_ $abc$8517$new_n962_ +000011 1 +010001 1 +010011 1 +010111 1 +011011 1 +011111 1 +100001 1 +100010 1 +100011 1 +100101 1 +100111 1 +101001 1 +101011 1 +101101 1 +101111 1 +110000 1 +110001 1 +110010 1 +110011 1 +110101 1 +110110 1 +110111 1 +111001 1 +111010 1 +111011 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n953_ $abc$8517$new_n449_ $abc$8517$new_n445_ $abc$8517$new_n453_ $abc$8517$new_n940_ $abc$8517$new_n963_ +00001 1 +00010 1 +00011 1 +01000 1 +01001 1 +01010 1 +01011 1 +01101 1 +01110 1 +01111 1 +11000 1 +11001 1 +11010 1 +11011 1 +.names $abc$8517$new_n965_ $abc$8517$new_n969_ $abc$8517$new_n345_ $abc$8517$new_n964_ +100 1 +110 1 +111 1 +.names $abc$8517$new_n968_ $abc$8517$new_n811_ $abc$8517$new_n966_ $abc$8517$new_n819_ $abc$8517$new_n766_ $abc$8517$new_n965_ +10000 1 +10001 1 +10011 1 +.names $abc$8517$new_n543_ $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n874_ $abc$8517$new_n923_ $abc$8517$new_n967_ $abc$8517$new_n966_ +100000 1 +100001 1 +100010 1 +100011 1 +101000 1 +101001 1 +101010 1 +101011 1 +110000 1 +110001 1 +110100 1 +110101 1 +111000 1 +111010 1 +111100 1 +111110 1 +.names $abc$8517$new_n356_ $abc$8517$new_n944_ $abc$8517$new_n537_ $abc$8517$new_n535_ $abc$8517$new_n967_ +0100 1 +0101 1 +0110 1 +0111 1 +1000 1 +1100 1 +.names $abc$8517$new_n441_ $abc$8517$new_n576_ $abc$8517$new_n438_ $abc$8517$new_n547_ $abc$8517$new_n579_ $abc$8517$new_n968_ +00000 1 +00010 1 +00100 1 +00101 1 +10000 1 +10001 1 +10100 1 +10101 1 +10110 1 +10111 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n692_ $abc$8517$new_n719_ $abc$8517$new_n630_ $abc$8517$new_n629_ $abc$8517$new_n718_ $abc$8517$new_n969_ +00000 1 +00001 1 +00010 1 +00011 1 +00100 1 +00110 1 +01000 1 +01001 1 +.names $abc$8517$new_n546_ $abc$8517$new_n973_ $abc$8517$new_n441_ $abc$8517$new_n438_ $abc$8517$new_n962_ $abc$8517$new_n971_ bus_address[29] +000000 1 +000010 1 +000100 1 +000110 1 +001000 1 +001010 1 +001100 1 +001110 1 +010000 1 +010010 1 +010100 1 +010110 1 +011000 1 +011010 1 +011100 1 +011110 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +101000 1 +101001 1 +101010 1 +101100 1 +101110 1 +110000 1 +110010 1 +110100 1 +110110 1 +110111 1 +111000 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n975_ $abc$8517$new_n978_ $abc$8517$new_n979_ $abc$8517$new_n972_ $abc$8517$new_n971_ +1100 1 +.names $abc$8517$new_n548_ $abc$8517$new_n973_ $abc$8517$new_n974_ $abc$8517$new_n443_ $abc$8517$new_n963_ $abc$8517$new_n972_ +10001 1 +10010 1 +10011 1 +11000 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n440_ $abc$8517$new_n437_ $abc$8517$new_n973_ +01 1 +10 1 +.names $abc$8517$new_n438_ $abc$8517$new_n441_ $abc$8517$new_n974_ +10 1 +.names $abc$8517$new_n543_ $abc$8517$new_n976_ $abc$8517$new_n777_ $abc$8517$new_n819_ $abc$8517$new_n886_ $abc$8517$new_n368_ $abc$8517$new_n975_ +000000 1 +000001 1 +000010 1 +000011 1 +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +101010 1 +101110 1 +110000 1 +110001 1 +110010 1 +110011 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n934_ $abc$8517$new_n958_ $abc$8517$new_n977_ $abc$8517$new_n976_ +100100 1 +100101 1 +100110 1 +100111 1 +101100 1 +101101 1 +101110 1 +101111 1 +110010 1 +110011 1 +110110 1 +110111 1 +111001 1 +111011 1 +111101 1 +111111 1 +.names $abc$8517$new_n563_ $abc$8517$new_n519_ $abc$8517$new_n440_ $abc$8517$new_n977_ +000 1 +001 1 +011 1 +.names $abc$8517$new_n811_ $abc$8517$new_n440_ $abc$8517$new_n576_ $abc$8517$new_n437_ $abc$8517$new_n547_ $abc$8517$new_n579_ $abc$8517$new_n978_ +000000 1 +000010 1 +000100 1 +000101 1 +010000 1 +010001 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n345_ $abc$8517$new_n692_ $abc$8517$new_n718_ $abc$8517$new_n651_ $abc$8517$new_n719_ $abc$8517$new_n652_ $abc$8517$new_n979_ +100010 1 +100110 1 +101010 1 +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n983_ $abc$8517$new_n546_ $abc$8517$new_n981_ bus_address[30] +000 1 +001 1 +010 1 +011 1 +111 1 +.names $abc$8517$new_n982_ $abc$8517$new_n441_ $abc$8517$new_n438_ $abc$8517$new_n440_ $abc$8517$new_n437_ $abc$8517$new_n962_ $abc$8517$new_n981_ +000110 1 +000111 1 +001011 1 +001101 1 +001110 1 +001111 1 +010011 1 +010101 1 +010110 1 +010111 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +101000 1 +101001 1 +101010 1 +101100 1 +110000 1 +110001 1 +110010 1 +110100 1 +111000 1 +111001 1 +.names $abc$8517$new_n432_ $abc$8517$new_n431_ $abc$8517$new_n982_ +00 1 +11 1 +.names $abc$8517$new_n548_ $abc$8517$new_n984_ $abc$8517$new_n982_ $abc$8517$new_n987_ $abc$8517$new_n543_ $abc$8517$new_n985_ $abc$8517$new_n983_ +000100 1 +000101 1 +000110 1 +001100 1 +001101 1 +001110 1 +010100 1 +010101 1 +010110 1 +011100 1 +011101 1 +011110 1 +101100 1 +101101 1 +101110 1 +110100 1 +110101 1 +110110 1 +.names $abc$8517$new_n437_ $abc$8517$new_n440_ $abc$8517$new_n974_ $abc$8517$new_n442_ $abc$8517$new_n963_ $abc$8517$new_n984_ +00000 1 +00001 1 +00011 1 +01000 1 +01001 1 +01011 1 +01100 1 +01101 1 +01111 1 +11000 1 +11001 1 +11011 1 +.names $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n898_ $abc$8517$new_n943_ $abc$8517$new_n986_ $abc$8517$new_n985_ +00000 1 +00001 1 +00010 1 +00011 1 +01000 1 +01001 1 +01010 1 +01011 1 +10000 1 +10001 1 +10100 1 +10101 1 +11001 1 +11011 1 +11101 1 +11111 1 +.names $abc$8517$new_n356_ $abc$8517$new_n539_ $abc$8517$new_n538_ $abc$8517$new_n535_ $abc$8517$new_n537_ $abc$8517$new_n986_ +00001 1 +00010 1 +00011 1 +00101 1 +00110 1 +00111 1 +01001 1 +01010 1 +01011 1 +01101 1 +01110 1 +01111 1 +10100 1 +10101 1 +10110 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n988_ $abc$8517$new_n789_ $abc$8517$new_n819_ $abc$8517$new_n345_ $abc$8517$new_n795_ $abc$8517$new_n987_ +10000 1 +10001 1 +10011 1 +11000 1 +11001 1 +11011 1 +11100 1 +11101 1 +11111 1 +.names $abc$8517$new_n811_ $abc$8517$new_n432_ $abc$8517$new_n576_ $abc$8517$new_n431_ $abc$8517$new_n547_ $abc$8517$new_n579_ $abc$8517$new_n988_ +000000 1 +000010 1 +000100 1 +000101 1 +010000 1 +010001 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n546_ $abc$8517$new_n433_ $abc$8517$new_n990_ $abc$8517$new_n432_ $abc$8517$new_n431_ $abc$8517$new_n991_ bus_address[31] +000000 1 +000010 1 +000100 1 +000110 1 +001000 1 +001010 1 +001100 1 +001110 1 +010000 1 +010010 1 +010100 1 +010110 1 +011000 1 +011010 1 +011100 1 +011110 1 +100000 1 +100001 1 +100010 1 +100100 1 +100110 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111010 1 +111100 1 +111110 1 +.names $abc$8517$new_n982_ $abc$8517$new_n441_ $abc$8517$new_n438_ $abc$8517$new_n440_ $abc$8517$new_n437_ $abc$8517$new_n962_ $abc$8517$new_n990_ +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +001000 1 +001001 1 +001010 1 +001100 1 +010000 1 +010001 1 +010010 1 +010100 1 +011000 1 +011001 1 +.names $abc$8517$new_n992_ $abc$8517$new_n433_ $abc$8517$new_n431_ $abc$8517$new_n432_ $abc$8517$new_n984_ $abc$8517$new_n548_ $abc$8517$new_n991_ +100000 1 +100001 1 +100010 1 +100100 1 +100110 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +110000 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111010 1 +111100 1 +111110 1 +111111 1 +.names $abc$8517$new_n996_ $abc$8517$new_n997_ $abc$8517$new_n995_ $abc$8517$new_n993_ $abc$8517$new_n803_ $abc$8517$new_n819_ $abc$8517$new_n992_ +100000 1 +100010 1 +100011 1 +.names $abc$8517$new_n543_ $abc$8517$new_n368_ $abc$8517$new_n994_ $abc$8517$new_n910_ $abc$8517$new_n957_ $abc$8517$new_n362_ $abc$8517$new_n993_ +100000 1 +100001 1 +100010 1 +100011 1 +101000 1 +101001 1 +101010 1 +101011 1 +110000 1 +110001 1 +110011 1 +110100 1 +110101 1 +110111 1 +.names $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n977_ $abc$8517$new_n434_ $abc$8517$new_n432_ $abc$8517$new_n994_ +100100 1 +100101 1 +100110 1 +100111 1 +101100 1 +101101 1 +101110 1 +101111 1 +110001 1 +110011 1 +110101 1 +110111 1 +111010 1 +111011 1 +111110 1 +111111 1 +.names $abc$8517$new_n310_ $abc$8517$new_n434_ $abc$8517$new_n576_ rs2_data[31] $abc$8517$new_n494_ $abc$8517$new_n547_ $abc$8517$new_n995_ +000011 1 +000111 1 +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001110 1 +001111 1 +010001 1 +010101 1 +011000 1 +011001 1 +011100 1 +011101 1 +100001 1 +100011 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110101 1 +110111 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n434_ $abc$8517$new_n579_ $abc$8517$new_n310_ $abc$8517$new_n568_ $abc$8517$new_n494_ rs2_data[31] $abc$8517$new_n996_ +000000 1 +000001 1 +000010 1 +000011 1 +001000 1 +001001 1 +001010 1 +001011 1 +010010 1 +010011 1 +011000 1 +011010 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n368_ $abc$8517$new_n690_ $abc$8517$new_n608_ $abc$8517$new_n549_ $abc$8517$new_n997_ +1111 1 +.names singlecycle_datapath.program_counter.value[0] reset $abc$8517$new_n1013_ $abc$8517$new_n1011_ $abc$8517$new_n999_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][0] +00000 1 +00001 1 +10001 1 +10011 1 +10100 1 +10101 1 +10111 1 +.names $abc$8517$new_n302_ $abc$8517$new_n1010_ $abc$8517$new_n1000_ bus_address[31] $abc$8517$new_n1009_ $abc$8517$new_n999_ +00000 1 +00010 1 +00100 1 +00101 1 +00110 1 +01000 1 +01001 1 +01010 1 +01011 1 +01100 1 +01110 1 +01111 1 +.names $abc$8517$new_n1001_ $abc$8517$new_n983_ bus_address[29] bus_address[28] $abc$8517$new_n981_ $abc$8517$new_n546_ $abc$8517$new_n1000_ +110000 1 +110001 1 +110010 1 +.names $abc$8517$new_n1396_ $abc$8517$new_n1002_ $abc$8517$new_n950_ bus_address[26] bus_address[25] $abc$8517$new_n1001_ +11000 1 +.names $abc$8517$new_n1003_ $abc$8517$new_n1007_ bus_address[21] bus_address[24] bus_address[23] bus_address[22] $abc$8517$new_n1002_ +110000 1 +.names $abc$8517$new_n1004_ bus_address[11] bus_address[14] bus_address[16] bus_address[20] $abc$8517$new_n1003_ +10000 1 +.names $abc$8517$new_n1005_ $abc$8517$new_n1006_ bus_address[9] bus_address[10] $abc$8517$new_n1004_ +1100 1 +.names $abc$8517$new_n1352_ $abc$8517$new_n1349_ bus_address[1] $abc$8517$new_n1344_ $abc$8517$new_n466_ $abc$8517$new_n493_ $abc$8517$new_n1005_ +100000 1 +100001 1 +100011 1 +100100 1 +100101 1 +100110 1 +100111 1 +.names bus_address[2] bus_address[8] bus_address[7] bus_address[5] bus_address[4] bus_address[3] $abc$8517$new_n1006_ +000000 1 +.names $abc$8517$new_n1008_ bus_address[12] bus_address[19] $abc$8517$new_n1007_ +100 1 +.names bus_address[6] bus_address[13] bus_address[15] bus_address[18] bus_address[17] $abc$8517$new_n1008_ +00000 1 +.names inst[6] inst[5] $abc$8517$new_n348_ inst[4] $abc$8517$new_n1009_ +1110 1 +.names inst[12] inst[13] inst[14] $abc$8517$new_n1010_ +100 1 +101 1 +111 1 +.names $abc$8517$new_n1012_ $abc$8517$new_n1010_ $abc$8517$new_n1000_ bus_address[31] $abc$8517$new_n1009_ $abc$8517$new_n1011_ +00000 1 +00010 1 +00100 1 +00101 1 +00110 1 +01000 1 +01001 1 +01010 1 +01011 1 +01100 1 +01110 1 +01111 1 +.names inst[3] $abc$8517$new_n302_ $abc$8517$new_n1012_ +11 1 +.names $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n1013_ +00 1 +.names reset $abc$8517$new_n1015_ singlecycle_datapath.program_counter.value[1] $abc$8517$new_n999_ $abc$8517$new_n1017_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][1] +000010 1 +000110 1 +001010 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n1016_ bus_address[1] $abc$8517$new_n1015_ +11 1 +.names $abc$8517$new_n302_ inst[3] $abc$8517$new_n1016_ +10 1 +.names singlecycle_datapath.program_counter.value[1] singlecycle_datapath.program_counter.value[0] $abc$8517$new_n358_ $abc$8517$new_n357_ $abc$8517$new_n1013_ $abc$8517$new_n1017_ +00010 1 +00011 1 +00100 1 +00101 1 +00110 1 +00111 1 +01000 1 +01011 1 +01101 1 +01111 1 +10000 1 +10001 1 +11001 1 +11010 1 +11100 1 +11110 1 +.names reset $abc$8517$new_n1019_ singlecycle_datapath.program_counter.value[2] $abc$8517$new_n999_ $abc$8517$new_n1020_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][2] +000010 1 +000100 1 +000101 1 +000110 1 +000111 1 +001010 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n1016_ bus_address[2] $abc$8517$new_n1019_ +11 1 +.names singlecycle_datapath.program_counter.value[2] $abc$8517$new_n1022_ $abc$8517$new_n1021_ $abc$8517$new_n1020_ +001 1 +010 1 +100 1 +111 1 +.names inst[9] $abc$8517$new_n346_ inst[22] $abc$8517$new_n347_ $abc$8517$new_n1021_ +0000 1 +0001 1 +0011 1 +0100 1 +0101 1 +0111 1 +1100 1 +1101 1 +1111 1 +.names $abc$8517$new_n358_ $abc$8517$new_n357_ singlecycle_datapath.program_counter.value[1] $abc$8517$new_n354_ $abc$8517$new_n355_ singlecycle_datapath.program_counter.value[0] $abc$8517$new_n1022_ +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +000110 1 +000111 1 +001000 1 +001001 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010100 1 +010110 1 +100000 1 +100001 1 +100010 1 +100100 1 +100110 1 +110000 1 +110001 1 +110010 1 +110100 1 +110110 1 +.names reset $abc$8517$new_n1024_ $abc$8517$new_n1028_ $abc$8517$new_n999_ $abc$8517$new_n1025_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][3] +000010 1 +000110 1 +001010 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n1016_ bus_address[3] $abc$8517$new_n1024_ +11 1 +.names singlecycle_datapath.program_counter.value[3] $abc$8517$new_n1027_ $abc$8517$new_n1026_ $abc$8517$new_n1025_ +001 1 +010 1 +100 1 +111 1 +.names $abc$8517$new_n1022_ singlecycle_datapath.program_counter.value[2] $abc$8517$new_n1021_ $abc$8517$new_n1026_ +001 1 +100 1 +101 1 +111 1 +.names $abc$8517$new_n360_ $abc$8517$new_n347_ inst[23] $abc$8517$new_n1027_ +000 1 +010 1 +011 1 +.names singlecycle_datapath.program_counter.value[3] singlecycle_datapath.program_counter.value[2] $abc$8517$new_n1028_ +01 1 +10 1 +.names reset $abc$8517$new_n1030_ $abc$8517$new_n1033_ $abc$8517$new_n999_ $abc$8517$new_n1031_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][4] +000010 1 +000110 1 +001010 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n1016_ bus_address[4] $abc$8517$new_n1030_ +11 1 +.names singlecycle_datapath.program_counter.value[4] $abc$8517$new_n1032_ singlecycle_datapath.program_counter.value[3] $abc$8517$new_n1027_ $abc$8517$new_n1026_ $abc$8517$new_n1031_ +00001 1 +00010 1 +00011 1 +00111 1 +01000 1 +01100 1 +01101 1 +01110 1 +10000 1 +10100 1 +10101 1 +10110 1 +11001 1 +11010 1 +11011 1 +11111 1 +.names inst[11] $abc$8517$new_n346_ inst[24] $abc$8517$new_n347_ $abc$8517$new_n1032_ +0000 1 +0001 1 +0011 1 +0100 1 +0101 1 +0111 1 +1100 1 +1101 1 +1111 1 +.names singlecycle_datapath.program_counter.value[4] singlecycle_datapath.program_counter.value[3] singlecycle_datapath.program_counter.value[2] $abc$8517$new_n1033_ +011 1 +100 1 +101 1 +110 1 +.names reset $abc$8517$new_n1035_ $abc$8517$new_n1042_ $abc$8517$new_n999_ $abc$8517$new_n1036_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][5] +000010 1 +000110 1 +001010 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n1016_ bus_address[5] $abc$8517$new_n1035_ +11 1 +.names $abc$8517$new_n1041_ $abc$8517$new_n1037_ $abc$8517$new_n1036_ +00 1 +11 1 +.names $abc$8517$new_n1039_ $abc$8517$new_n1038_ $abc$8517$new_n1037_ +00 1 +.names singlecycle_datapath.program_counter.value[4] $abc$8517$new_n1032_ $abc$8517$new_n1038_ +10 1 +.names $abc$8517$new_n1040_ singlecycle_datapath.program_counter.value[3] singlecycle_datapath.program_counter.value[2] $abc$8517$new_n1022_ $abc$8517$new_n1021_ $abc$8517$new_n1027_ $abc$8517$new_n1039_ +100000 1 +101000 1 +101010 1 +101100 1 +110000 1 +110001 1 +110010 1 +110100 1 +110110 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +.names singlecycle_datapath.program_counter.value[4] $abc$8517$new_n347_ inst[24] inst[11] $abc$8517$new_n1040_ +0010 1 +0011 1 +0101 1 +0111 1 +1000 1 +1001 1 +1100 1 +1101 1 +1110 1 +.names singlecycle_datapath.program_counter.value[5] inst[25] $abc$8517$new_n324_ $abc$8517$new_n1041_ +010 1 +100 1 +101 1 +111 1 +.names singlecycle_datapath.program_counter.value[5] singlecycle_datapath.program_counter.value[4] singlecycle_datapath.program_counter.value[3] singlecycle_datapath.program_counter.value[2] $abc$8517$new_n1042_ +0111 1 +1000 1 +1001 1 +1010 1 +1011 1 +1100 1 +1101 1 +1110 1 +.names reset $abc$8517$new_n1044_ $abc$8517$new_n1048_ $abc$8517$new_n999_ $abc$8517$new_n1045_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][6] +000010 1 +000110 1 +001010 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n1016_ bus_address[6] $abc$8517$new_n1044_ +11 1 +.names $abc$8517$new_n1047_ $abc$8517$new_n1046_ $abc$8517$new_n1041_ $abc$8517$new_n1037_ $abc$8517$new_n1045_ +0010 1 +0100 1 +0101 1 +0110 1 +0111 1 +1000 1 +1001 1 +1011 1 +.names inst[25] singlecycle_datapath.program_counter.value[5] $abc$8517$new_n324_ $abc$8517$new_n1046_ +110 1 +.names singlecycle_datapath.program_counter.value[6] inst[26] $abc$8517$new_n324_ $abc$8517$new_n1047_ +010 1 +100 1 +101 1 +111 1 +.names singlecycle_datapath.program_counter.value[6] singlecycle_datapath.program_counter.value[5] singlecycle_datapath.program_counter.value[4] singlecycle_datapath.program_counter.value[3] singlecycle_datapath.program_counter.value[2] $abc$8517$new_n1048_ +01111 1 +10000 1 +10001 1 +10010 1 +10011 1 +10100 1 +10101 1 +10110 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +.names reset $abc$8517$new_n1050_ $abc$8517$new_n1054_ $abc$8517$new_n999_ $abc$8517$new_n1016_ bus_address[7] $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][7] +000011 1 +000111 1 +001011 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n1051_ $abc$8517$new_n1009_ $abc$8517$new_n1010_ $abc$8517$new_n1000_ bus_address[31] $abc$8517$new_n1012_ $abc$8517$new_n1050_ +100001 1 +100011 1 +100101 1 +100111 1 +101001 1 +101011 1 +101101 1 +101111 1 +110000 1 +110001 1 +110010 1 +110011 1 +110101 1 +110110 1 +110111 1 +111001 1 +111011 1 +111100 1 +111101 1 +111111 1 +.names singlecycle_datapath.program_counter.value[7] $abc$8517$new_n699_ $abc$8517$new_n1052_ $abc$8517$new_n1051_ +000 1 +011 1 +101 1 +110 1 +.names $abc$8517$new_n1047_ $abc$8517$new_n1046_ singlecycle_datapath.program_counter.value[6] $abc$8517$new_n1041_ $abc$8517$new_n1038_ $abc$8517$new_n1039_ $abc$8517$new_n1052_ +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +000110 1 +000111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +.names singlecycle_datapath.program_counter.value[7] singlecycle_datapath.program_counter.value[6] singlecycle_datapath.program_counter.value[5] singlecycle_datapath.program_counter.value[4] singlecycle_datapath.program_counter.value[3] singlecycle_datapath.program_counter.value[2] $abc$8517$new_n1054_ +011111 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +.names reset $abc$8517$new_n1056_ $abc$8517$new_n1060_ $abc$8517$new_n999_ $abc$8517$new_n1057_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][8] +000010 1 +000110 1 +001010 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n1016_ bus_address[8] $abc$8517$new_n1056_ +11 1 +.names singlecycle_datapath.program_counter.value[8] $abc$8517$new_n1059_ $abc$8517$new_n1058_ $abc$8517$new_n1057_ +000 1 +011 1 +101 1 +110 1 +.names singlecycle_datapath.program_counter.value[7] $abc$8517$new_n699_ $abc$8517$new_n1052_ $abc$8517$new_n1058_ +000 1 +001 1 +011 1 +101 1 +.names inst[28] $abc$8517$new_n324_ $abc$8517$new_n1059_ +10 1 +.names singlecycle_datapath.program_counter.value[8] $abc$8517$new_n1061_ $abc$8517$new_n1060_ +01 1 +10 1 +.names singlecycle_datapath.program_counter.value[7] singlecycle_datapath.program_counter.value[6] singlecycle_datapath.program_counter.value[5] singlecycle_datapath.program_counter.value[4] singlecycle_datapath.program_counter.value[3] singlecycle_datapath.program_counter.value[2] $abc$8517$new_n1061_ +111111 1 +.names reset $abc$8517$new_n1063_ $abc$8517$new_n1067_ $abc$8517$new_n999_ $abc$8517$new_n1064_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][9] +000010 1 +000110 1 +001010 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n1016_ bus_address[9] $abc$8517$new_n1063_ +11 1 +.names $abc$8517$new_n1066_ $abc$8517$new_n1065_ singlecycle_datapath.program_counter.value[8] $abc$8517$new_n1059_ $abc$8517$new_n1058_ $abc$8517$new_n1064_ +00000 1 +00001 1 +00011 1 +00101 1 +01010 1 +01100 1 +01110 1 +01111 1 +10010 1 +10100 1 +10110 1 +10111 1 +11010 1 +11100 1 +11110 1 +11111 1 +.names inst[29] singlecycle_datapath.program_counter.value[9] $abc$8517$new_n324_ $abc$8517$new_n1065_ +110 1 +.names singlecycle_datapath.program_counter.value[9] $abc$8517$new_n324_ inst[29] $abc$8517$new_n1066_ +000 1 +010 1 +011 1 +.names singlecycle_datapath.program_counter.value[9] singlecycle_datapath.program_counter.value[8] $abc$8517$new_n1061_ $abc$8517$new_n1067_ +011 1 +100 1 +101 1 +110 1 +.names reset $abc$8517$new_n1069_ $abc$8517$new_n1073_ $abc$8517$new_n999_ $abc$8517$new_n1070_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][10] +000010 1 +000110 1 +001010 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n1016_ bus_address[10] $abc$8517$new_n1069_ +11 1 +.names $abc$8517$new_n1072_ $abc$8517$new_n1066_ $abc$8517$new_n1071_ $abc$8517$new_n1070_ +000 1 +101 1 +110 1 +111 1 +.names $abc$8517$new_n1065_ singlecycle_datapath.program_counter.value[7] $abc$8517$new_n699_ singlecycle_datapath.program_counter.value[8] $abc$8517$new_n1059_ $abc$8517$new_n1052_ $abc$8517$new_n1071_ +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +001000 1 +001001 1 +001011 1 +001101 1 +010000 1 +010001 1 +010011 1 +010101 1 +011000 1 +011001 1 +.names singlecycle_datapath.program_counter.value[10] inst[30] $abc$8517$new_n324_ $abc$8517$new_n1072_ +010 1 +100 1 +101 1 +111 1 +.names singlecycle_datapath.program_counter.value[10] singlecycle_datapath.program_counter.value[9] singlecycle_datapath.program_counter.value[8] $abc$8517$new_n1061_ $abc$8517$new_n1073_ +0111 1 +1000 1 +1001 1 +1010 1 +1011 1 +1100 1 +1101 1 +1110 1 +.names reset $abc$8517$new_n1075_ $abc$8517$new_n1079_ $abc$8517$new_n999_ $abc$8517$new_n1016_ bus_address[11] $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][11] +000011 1 +000111 1 +001011 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n1076_ $abc$8517$new_n1009_ $abc$8517$new_n1010_ $abc$8517$new_n1000_ bus_address[31] $abc$8517$new_n1012_ $abc$8517$new_n1075_ +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010101 1 +010110 1 +010111 1 +011001 1 +011011 1 +011100 1 +011101 1 +011111 1 +.names singlecycle_datapath.program_counter.value[11] $abc$8517$new_n1077_ $abc$8517$new_n1078_ $abc$8517$new_n1072_ $abc$8517$new_n1066_ $abc$8517$new_n1071_ $abc$8517$new_n1076_ +000100 1 +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010101 1 +010110 1 +010111 1 +100000 1 +100001 1 +100010 1 +100011 1 +100101 1 +100110 1 +100111 1 +110100 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n390_ $abc$8517$new_n391_ $abc$8517$new_n389_ inst[31] $abc$8517$new_n1077_ +0000 1 +0010 1 +0011 1 +.names singlecycle_datapath.program_counter.value[10] inst[30] $abc$8517$new_n324_ $abc$8517$new_n1078_ +110 1 +.names singlecycle_datapath.program_counter.value[11] singlecycle_datapath.program_counter.value[10] singlecycle_datapath.program_counter.value[9] singlecycle_datapath.program_counter.value[8] $abc$8517$new_n1061_ $abc$8517$new_n1079_ +01111 1 +10000 1 +10001 1 +10010 1 +10011 1 +10100 1 +10101 1 +10110 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +.names reset $abc$8517$new_n1081_ $abc$8517$new_n1086_ $abc$8517$new_n999_ $abc$8517$new_n1082_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][12] +000010 1 +000110 1 +001010 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n1016_ bus_address[12] $abc$8517$new_n1081_ +11 1 +.names singlecycle_datapath.program_counter.value[12] $abc$8517$new_n1084_ $abc$8517$new_n1083_ $abc$8517$new_n1082_ +000 1 +011 1 +101 1 +110 1 +.names singlecycle_datapath.program_counter.value[11] $abc$8517$new_n1077_ $abc$8517$new_n1072_ $abc$8517$new_n1071_ $abc$8517$new_n1066_ $abc$8517$new_n1078_ $abc$8517$new_n1083_ +000001 1 +000011 1 +000101 1 +000111 1 +001000 1 +001001 1 +001011 1 +001101 1 +001111 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110001 1 +110011 1 +110101 1 +110111 1 +111000 1 +111001 1 +111011 1 +111101 1 +111111 1 +.names $abc$8517$new_n1085_ $abc$8517$new_n338_ inst[12] $abc$8517$new_n1084_ +000 1 +010 1 +011 1 +.names inst[31] $abc$8517$new_n312_ $abc$8517$new_n1085_ +10 1 +.names singlecycle_datapath.program_counter.value[12] singlecycle_datapath.program_counter.value[11] singlecycle_datapath.program_counter.value[10] singlecycle_datapath.program_counter.value[9] singlecycle_datapath.program_counter.value[8] $abc$8517$new_n1061_ $abc$8517$new_n1086_ +011111 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +.names reset $abc$8517$new_n1091_ $abc$8517$new_n1092_ $abc$8517$new_n999_ $abc$8517$new_n1088_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][13] +000010 1 +000110 1 +001010 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names singlecycle_datapath.program_counter.value[13] $abc$8517$new_n1090_ $abc$8517$new_n1089_ $abc$8517$new_n1088_ +001 1 +010 1 +100 1 +111 1 +.names $abc$8517$new_n1084_ singlecycle_datapath.program_counter.value[12] $abc$8517$new_n1083_ $abc$8517$new_n1089_ +000 1 +100 1 +101 1 +110 1 +.names $abc$8517$new_n383_ $abc$8517$new_n1085_ $abc$8517$new_n1090_ +00 1 +.names $abc$8517$new_n1016_ bus_address[13] $abc$8517$new_n1091_ +11 1 +.names singlecycle_datapath.program_counter.value[13] $abc$8517$new_n1093_ $abc$8517$new_n1092_ +01 1 +10 1 +.names singlecycle_datapath.program_counter.value[12] singlecycle_datapath.program_counter.value[11] singlecycle_datapath.program_counter.value[10] singlecycle_datapath.program_counter.value[9] singlecycle_datapath.program_counter.value[8] $abc$8517$new_n1061_ $abc$8517$new_n1093_ +111111 1 +.names reset $abc$8517$new_n1095_ $abc$8517$new_n1098_ $abc$8517$new_n999_ $abc$8517$new_n1096_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][14] +000010 1 +000110 1 +001010 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n1016_ bus_address[14] $abc$8517$new_n1095_ +11 1 +.names singlecycle_datapath.program_counter.value[14] $abc$8517$new_n1097_ singlecycle_datapath.program_counter.value[13] $abc$8517$new_n1090_ $abc$8517$new_n1089_ $abc$8517$new_n1096_ +00001 1 +00010 1 +00011 1 +00111 1 +01000 1 +01100 1 +01101 1 +01110 1 +10000 1 +10100 1 +10101 1 +10110 1 +11001 1 +11010 1 +11011 1 +11111 1 +.names $abc$8517$new_n1085_ $abc$8517$new_n338_ inst[14] $abc$8517$new_n1097_ +000 1 +010 1 +011 1 +.names singlecycle_datapath.program_counter.value[14] singlecycle_datapath.program_counter.value[13] $abc$8517$new_n1093_ $abc$8517$new_n1098_ +011 1 +100 1 +101 1 +110 1 +.names reset $abc$8517$new_n1106_ $abc$8517$new_n1107_ $abc$8517$new_n999_ $abc$8517$new_n1011_ $abc$8517$new_n1100_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][15] +000000 1 +000100 1 +001000 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names singlecycle_datapath.program_counter.value[15] $abc$8517$new_n1105_ $abc$8517$new_n1101_ $abc$8517$new_n1100_ +000 1 +011 1 +101 1 +110 1 +.names $abc$8517$new_n1104_ $abc$8517$new_n1102_ $abc$8517$new_n1101_ +00 1 +.names $abc$8517$new_n1103_ singlecycle_datapath.program_counter.value[12] singlecycle_datapath.program_counter.value[13] $abc$8517$new_n1084_ $abc$8517$new_n1090_ $abc$8517$new_n1083_ $abc$8517$new_n1102_ +100001 1 +101000 1 +101001 1 +101011 1 +101100 1 +101101 1 +110000 1 +110001 1 +110101 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111111 1 +.names singlecycle_datapath.program_counter.value[14] $abc$8517$new_n1097_ $abc$8517$new_n1103_ +00 1 +11 1 +.names singlecycle_datapath.program_counter.value[14] $abc$8517$new_n1097_ $abc$8517$new_n1104_ +10 1 +.names $abc$8517$new_n378_ $abc$8517$new_n1085_ $abc$8517$new_n1105_ +00 1 +.names $abc$8517$new_n1016_ bus_address[15] $abc$8517$new_n1106_ +11 1 +.names singlecycle_datapath.program_counter.value[15] singlecycle_datapath.program_counter.value[14] singlecycle_datapath.program_counter.value[13] $abc$8517$new_n1093_ $abc$8517$new_n1107_ +0111 1 +1000 1 +1001 1 +1010 1 +1011 1 +1100 1 +1101 1 +1110 1 +.names reset $abc$8517$new_n1109_ $abc$8517$new_n1112_ $abc$8517$new_n999_ $abc$8517$new_n1110_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][16] +000010 1 +000110 1 +001010 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n1016_ bus_address[16] $abc$8517$new_n1109_ +11 1 +.names singlecycle_datapath.program_counter.value[16] $abc$8517$new_n1111_ singlecycle_datapath.program_counter.value[15] $abc$8517$new_n1105_ $abc$8517$new_n1101_ $abc$8517$new_n1110_ +00001 1 +00010 1 +00011 1 +00111 1 +01000 1 +01100 1 +01101 1 +01110 1 +10000 1 +10100 1 +10101 1 +10110 1 +11001 1 +11010 1 +11011 1 +11111 1 +.names $abc$8517$new_n1085_ $abc$8517$new_n338_ inst[16] $abc$8517$new_n1111_ +000 1 +010 1 +011 1 +.names singlecycle_datapath.program_counter.value[16] singlecycle_datapath.program_counter.value[15] singlecycle_datapath.program_counter.value[14] singlecycle_datapath.program_counter.value[13] $abc$8517$new_n1093_ $abc$8517$new_n1112_ +01111 1 +10000 1 +10001 1 +10010 1 +10011 1 +10100 1 +10101 1 +10110 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +.names reset $abc$8517$new_n1114_ $abc$8517$new_n1118_ $abc$8517$new_n999_ bus_address[17] $abc$8517$new_n1016_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][17] +000011 1 +000111 1 +001011 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n1115_ $abc$8517$new_n1009_ $abc$8517$new_n1010_ $abc$8517$new_n1000_ bus_address[31] $abc$8517$new_n1012_ $abc$8517$new_n1114_ +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010101 1 +010110 1 +010111 1 +011001 1 +011011 1 +011100 1 +011101 1 +011111 1 +.names singlecycle_datapath.program_counter.value[17] $abc$8517$new_n1117_ $abc$8517$new_n1116_ $abc$8517$new_n1115_ +000 1 +011 1 +101 1 +110 1 +.names $abc$8517$new_n1104_ $abc$8517$new_n1102_ singlecycle_datapath.program_counter.value[16] singlecycle_datapath.program_counter.value[15] $abc$8517$new_n1105_ $abc$8517$new_n1111_ $abc$8517$new_n1116_ +000000 1 +000001 1 +000010 1 +000011 1 +000101 1 +000110 1 +000111 1 +001001 1 +001011 1 +001111 1 +010001 1 +010010 1 +010011 1 +010101 1 +010111 1 +011011 1 +100001 1 +100010 1 +100011 1 +100101 1 +100111 1 +101011 1 +110001 1 +110010 1 +110011 1 +110101 1 +110111 1 +111011 1 +.names $abc$8517$new_n320_ $abc$8517$new_n1085_ $abc$8517$new_n1117_ +00 1 +.names singlecycle_datapath.program_counter.value[17] singlecycle_datapath.program_counter.value[16] singlecycle_datapath.program_counter.value[15] singlecycle_datapath.program_counter.value[14] singlecycle_datapath.program_counter.value[13] $abc$8517$new_n1093_ $abc$8517$new_n1118_ +011111 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +.names reset $abc$8517$new_n1123_ $abc$8517$new_n1124_ $abc$8517$new_n999_ $abc$8517$new_n1120_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][18] +000010 1 +000110 1 +001010 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names singlecycle_datapath.program_counter.value[18] $abc$8517$new_n1122_ $abc$8517$new_n1121_ $abc$8517$new_n1120_ +000 1 +011 1 +101 1 +110 1 +.names singlecycle_datapath.program_counter.value[17] $abc$8517$new_n1117_ $abc$8517$new_n1116_ $abc$8517$new_n1121_ +000 1 +100 1 +101 1 +110 1 +.names $abc$8517$new_n317_ $abc$8517$new_n1085_ $abc$8517$new_n1122_ +00 1 +.names $abc$8517$new_n1016_ bus_address[18] $abc$8517$new_n1123_ +11 1 +.names singlecycle_datapath.program_counter.value[18] $abc$8517$new_n1125_ $abc$8517$new_n1124_ +01 1 +10 1 +.names singlecycle_datapath.program_counter.value[17] singlecycle_datapath.program_counter.value[16] singlecycle_datapath.program_counter.value[15] singlecycle_datapath.program_counter.value[14] singlecycle_datapath.program_counter.value[13] $abc$8517$new_n1093_ $abc$8517$new_n1125_ +111111 1 +.names reset $abc$8517$new_n1129_ $abc$8517$new_n1130_ $abc$8517$new_n999_ $abc$8517$new_n1011_ $abc$8517$new_n1127_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][19] +000000 1 +000100 1 +001000 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names singlecycle_datapath.program_counter.value[19] $abc$8517$new_n1128_ singlecycle_datapath.program_counter.value[18] $abc$8517$new_n1122_ $abc$8517$new_n1121_ $abc$8517$new_n1127_ +00001 1 +00100 1 +00101 1 +00111 1 +01000 1 +01010 1 +01011 1 +01110 1 +10000 1 +10010 1 +10011 1 +10110 1 +11001 1 +11100 1 +11101 1 +11111 1 +.names $abc$8517$new_n314_ $abc$8517$new_n1085_ $abc$8517$new_n1128_ +00 1 +.names $abc$8517$new_n1016_ bus_address[19] $abc$8517$new_n1129_ +11 1 +.names singlecycle_datapath.program_counter.value[19] singlecycle_datapath.program_counter.value[18] $abc$8517$new_n1125_ $abc$8517$new_n1130_ +011 1 +100 1 +101 1 +110 1 +.names reset $abc$8517$new_n1132_ $abc$8517$new_n1138_ $abc$8517$new_n999_ $abc$8517$new_n1133_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][20] +000010 1 +000110 1 +001010 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n1016_ bus_address[20] $abc$8517$new_n1132_ +11 1 +.names singlecycle_datapath.program_counter.value[20] $abc$8517$new_n1137_ $abc$8517$new_n1136_ $abc$8517$new_n1134_ $abc$8517$new_n1133_ +0001 1 +0010 1 +0011 1 +0100 1 +1000 1 +1101 1 +1110 1 +1111 1 +.names $abc$8517$new_n1135_ $abc$8517$new_n1117_ singlecycle_datapath.program_counter.value[17] singlecycle_datapath.program_counter.value[18] $abc$8517$new_n1122_ $abc$8517$new_n1116_ $abc$8517$new_n1134_ +000001 1 +000010 1 +000011 1 +000111 1 +001010 1 +001011 1 +010000 1 +010001 1 +010010 1 +010011 1 +010110 1 +010111 1 +011001 1 +011010 1 +011011 1 +011111 1 +.names singlecycle_datapath.program_counter.value[19] $abc$8517$new_n1128_ $abc$8517$new_n1135_ +10 1 +.names $abc$8517$new_n1128_ singlecycle_datapath.program_counter.value[19] $abc$8517$new_n1136_ +10 1 +.names $abc$8517$new_n334_ $abc$8517$new_n324_ inst[31] $abc$8517$new_n1137_ +000 1 +010 1 +011 1 +.names singlecycle_datapath.program_counter.value[20] singlecycle_datapath.program_counter.value[19] singlecycle_datapath.program_counter.value[18] $abc$8517$new_n1125_ $abc$8517$new_n1138_ +0111 1 +1000 1 +1001 1 +1010 1 +1011 1 +1100 1 +1101 1 +1110 1 +.names reset $abc$8517$new_n1140_ $abc$8517$new_n1143_ $abc$8517$new_n999_ $abc$8517$new_n1141_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][21] +000000 1 +000100 1 +001000 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n1016_ bus_address[21] $abc$8517$new_n1140_ +11 1 +.names singlecycle_datapath.program_counter.value[21] $abc$8517$new_n1142_ singlecycle_datapath.program_counter.value[20] $abc$8517$new_n1137_ $abc$8517$new_n1136_ $abc$8517$new_n1134_ $abc$8517$new_n1141_ +000000 1 +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011101 1 +011110 1 +011111 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +100111 1 +101101 1 +101110 1 +101111 1 +110000 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +.names $abc$8517$new_n331_ $abc$8517$new_n324_ inst[31] $abc$8517$new_n1142_ +000 1 +010 1 +011 1 +.names singlecycle_datapath.program_counter.value[21] singlecycle_datapath.program_counter.value[20] singlecycle_datapath.program_counter.value[19] singlecycle_datapath.program_counter.value[18] $abc$8517$new_n1125_ $abc$8517$new_n1143_ +01111 1 +10000 1 +10001 1 +10010 1 +10011 1 +10100 1 +10101 1 +10110 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +.names $abc$8517$new_n999_ singlecycle_datapath.program_counter.value[22] $abc$8517$new_n1148_ $abc$8517$new_n1145_ $abc$8517$new_n1146_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][22] +000000 1 +000001 1 +000010 1 +000011 1 +000110 1 +001000 1 +001001 1 +001010 1 +001011 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010110 1 +011000 1 +011001 1 +011010 1 +011011 1 +011110 1 +100000 1 +100001 1 +100010 1 +100011 1 +100110 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111110 1 +.names reset bus_address[22] $abc$8517$new_n1016_ $abc$8517$new_n1145_ +000 1 +001 1 +010 1 +.names singlecycle_datapath.program_counter.value[22] $abc$8517$new_n414_ $abc$8517$new_n1147_ $abc$8517$new_n1146_ +000 1 +011 1 +101 1 +110 1 +.names singlecycle_datapath.program_counter.value[21] singlecycle_datapath.program_counter.value[20] $abc$8517$new_n1136_ $abc$8517$new_n1134_ $abc$8517$new_n1137_ $abc$8517$new_n1142_ $abc$8517$new_n1147_ +000000 1 +010000 1 +010010 1 +010100 1 +011000 1 +011100 1 +100000 1 +100001 1 +100010 1 +100100 1 +100110 1 +101000 1 +101010 1 +101100 1 +101110 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +111000 1 +111001 1 +111010 1 +111100 1 +111101 1 +111110 1 +.names singlecycle_datapath.program_counter.value[21] singlecycle_datapath.program_counter.value[20] singlecycle_datapath.program_counter.value[19] singlecycle_datapath.program_counter.value[18] $abc$8517$new_n1125_ $abc$8517$new_n1148_ +11111 1 +.names reset $abc$8517$new_n1152_ $abc$8517$new_n1153_ $abc$8517$new_n999_ $abc$8517$new_n1011_ $abc$8517$new_n1150_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][23] +000000 1 +000100 1 +001000 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names singlecycle_datapath.program_counter.value[23] $abc$8517$new_n427_ $abc$8517$new_n1151_ $abc$8517$new_n1150_ +000 1 +011 1 +101 1 +110 1 +.names $abc$8517$new_n414_ singlecycle_datapath.program_counter.value[22] $abc$8517$new_n1147_ $abc$8517$new_n1151_ +000 1 +100 1 +101 1 +110 1 +.names $abc$8517$new_n1016_ bus_address[23] $abc$8517$new_n1152_ +11 1 +.names singlecycle_datapath.program_counter.value[23] $abc$8517$new_n1154_ $abc$8517$new_n1153_ +01 1 +10 1 +.names singlecycle_datapath.program_counter.value[22] $abc$8517$new_n1148_ $abc$8517$new_n1154_ +11 1 +.names reset $abc$8517$new_n1158_ $abc$8517$new_n1159_ $abc$8517$new_n999_ $abc$8517$new_n1156_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][24] +000010 1 +000110 1 +001010 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names singlecycle_datapath.program_counter.value[24] $abc$8517$new_n1157_ singlecycle_datapath.program_counter.value[23] $abc$8517$new_n427_ $abc$8517$new_n1151_ $abc$8517$new_n1156_ +00001 1 +00010 1 +00011 1 +00111 1 +01000 1 +01100 1 +01101 1 +01110 1 +10000 1 +10100 1 +10101 1 +10110 1 +11001 1 +11010 1 +11011 1 +11111 1 +.names $abc$8517$new_n458_ $abc$8517$new_n324_ inst[31] $abc$8517$new_n1157_ +000 1 +010 1 +011 1 +.names $abc$8517$new_n1016_ bus_address[24] $abc$8517$new_n1158_ +11 1 +.names singlecycle_datapath.program_counter.value[24] $abc$8517$new_n1160_ $abc$8517$new_n1159_ +01 1 +10 1 +.names singlecycle_datapath.program_counter.value[23] $abc$8517$new_n1154_ $abc$8517$new_n1160_ +11 1 +.names reset $abc$8517$new_n1168_ $abc$8517$new_n1169_ $abc$8517$new_n999_ $abc$8517$new_n1162_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][25] +000010 1 +000110 1 +001010 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names singlecycle_datapath.program_counter.value[25] $abc$8517$new_n1167_ $abc$8517$new_n1163_ $abc$8517$new_n1162_ +001 1 +010 1 +100 1 +111 1 +.names $abc$8517$new_n1166_ $abc$8517$new_n1164_ $abc$8517$new_n1163_ +00 1 +.names $abc$8517$new_n1165_ singlecycle_datapath.program_counter.value[22] singlecycle_datapath.program_counter.value[23] $abc$8517$new_n414_ $abc$8517$new_n427_ $abc$8517$new_n1147_ $abc$8517$new_n1164_ +100001 1 +101000 1 +101001 1 +101011 1 +101100 1 +101101 1 +110000 1 +110001 1 +110101 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111111 1 +.names singlecycle_datapath.program_counter.value[24] $abc$8517$new_n1157_ $abc$8517$new_n1165_ +00 1 +11 1 +.names singlecycle_datapath.program_counter.value[24] $abc$8517$new_n1157_ $abc$8517$new_n1166_ +10 1 +.names $abc$8517$new_n456_ $abc$8517$new_n324_ inst[31] $abc$8517$new_n1167_ +000 1 +010 1 +011 1 +.names $abc$8517$new_n1016_ bus_address[25] $abc$8517$new_n1168_ +11 1 +.names singlecycle_datapath.program_counter.value[25] singlecycle_datapath.program_counter.value[24] $abc$8517$new_n1160_ $abc$8517$new_n1169_ +011 1 +100 1 +101 1 +110 1 +.names reset $abc$8517$new_n1173_ $abc$8517$new_n1174_ $abc$8517$new_n999_ $abc$8517$new_n1171_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][26] +000010 1 +000110 1 +001010 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names singlecycle_datapath.program_counter.value[26] $abc$8517$new_n1172_ singlecycle_datapath.program_counter.value[25] $abc$8517$new_n1167_ $abc$8517$new_n1163_ $abc$8517$new_n1171_ +00001 1 +00010 1 +00011 1 +00111 1 +01000 1 +01100 1 +01101 1 +01110 1 +10000 1 +10100 1 +10101 1 +10110 1 +11001 1 +11010 1 +11011 1 +11111 1 +.names $abc$8517$new_n448_ $abc$8517$new_n324_ inst[31] $abc$8517$new_n1172_ +000 1 +010 1 +011 1 +.names $abc$8517$new_n1016_ bus_address[26] $abc$8517$new_n1173_ +11 1 +.names singlecycle_datapath.program_counter.value[26] singlecycle_datapath.program_counter.value[25] singlecycle_datapath.program_counter.value[24] $abc$8517$new_n1160_ $abc$8517$new_n1174_ +0111 1 +1000 1 +1001 1 +1010 1 +1011 1 +1100 1 +1101 1 +1110 1 +.names reset $abc$8517$new_n1176_ $abc$8517$new_n1180_ $abc$8517$new_n999_ $abc$8517$new_n1177_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][27] +000000 1 +000100 1 +001000 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n1016_ bus_address[27] $abc$8517$new_n1176_ +11 1 +.names singlecycle_datapath.program_counter.value[27] $abc$8517$new_n1179_ $abc$8517$new_n1178_ $abc$8517$new_n1177_ +000 1 +011 1 +101 1 +110 1 +.names $abc$8517$new_n1166_ $abc$8517$new_n1164_ singlecycle_datapath.program_counter.value[26] singlecycle_datapath.program_counter.value[25] $abc$8517$new_n1167_ $abc$8517$new_n1172_ $abc$8517$new_n1178_ +000000 1 +000001 1 +000010 1 +000011 1 +000101 1 +000110 1 +000111 1 +001001 1 +001011 1 +001111 1 +010001 1 +010010 1 +010011 1 +010101 1 +010111 1 +011011 1 +100001 1 +100010 1 +100011 1 +100101 1 +100111 1 +101011 1 +110001 1 +110010 1 +110011 1 +110101 1 +110111 1 +111011 1 +.names $abc$8517$new_n446_ $abc$8517$new_n324_ inst[31] $abc$8517$new_n1179_ +000 1 +010 1 +011 1 +.names singlecycle_datapath.program_counter.value[27] singlecycle_datapath.program_counter.value[26] singlecycle_datapath.program_counter.value[25] singlecycle_datapath.program_counter.value[24] $abc$8517$new_n1160_ $abc$8517$new_n1180_ +01111 1 +10000 1 +10001 1 +10010 1 +10011 1 +10100 1 +10101 1 +10110 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +.names reset $abc$8517$new_n1182_ $abc$8517$new_n1185_ $abc$8517$new_n999_ bus_address[28] $abc$8517$new_n1016_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][28] +000011 1 +000111 1 +001011 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n1183_ $abc$8517$new_n1009_ $abc$8517$new_n1010_ $abc$8517$new_n1000_ bus_address[31] $abc$8517$new_n1012_ $abc$8517$new_n1182_ +100001 1 +100011 1 +100101 1 +100111 1 +101001 1 +101011 1 +101101 1 +101111 1 +110000 1 +110001 1 +110010 1 +110011 1 +110101 1 +110110 1 +110111 1 +111001 1 +111011 1 +111100 1 +111101 1 +111111 1 +.names singlecycle_datapath.program_counter.value[28] $abc$8517$new_n1184_ singlecycle_datapath.program_counter.value[27] $abc$8517$new_n1179_ $abc$8517$new_n1178_ $abc$8517$new_n1183_ +00001 1 +00010 1 +00011 1 +00111 1 +01000 1 +01100 1 +01101 1 +01110 1 +10000 1 +10100 1 +10101 1 +10110 1 +11001 1 +11010 1 +11011 1 +11111 1 +.names $abc$8517$new_n439_ $abc$8517$new_n324_ inst[31] $abc$8517$new_n1184_ +000 1 +010 1 +011 1 +.names singlecycle_datapath.program_counter.value[28] singlecycle_datapath.program_counter.value[27] singlecycle_datapath.program_counter.value[26] singlecycle_datapath.program_counter.value[25] singlecycle_datapath.program_counter.value[24] $abc$8517$new_n1160_ $abc$8517$new_n1185_ +011111 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +.names reset $abc$8517$new_n1190_ $abc$8517$new_n1191_ $abc$8517$new_n999_ $abc$8517$new_n1011_ $abc$8517$new_n1187_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][29] +000000 1 +000100 1 +001000 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names singlecycle_datapath.program_counter.value[29] $abc$8517$new_n1189_ $abc$8517$new_n1188_ $abc$8517$new_n1187_ +000 1 +011 1 +101 1 +110 1 +.names $abc$8517$new_n1179_ singlecycle_datapath.program_counter.value[27] singlecycle_datapath.program_counter.value[28] $abc$8517$new_n1184_ $abc$8517$new_n1178_ $abc$8517$new_n1188_ +00001 1 +00010 1 +00011 1 +00111 1 +01010 1 +01011 1 +10000 1 +10001 1 +10010 1 +10011 1 +10110 1 +10111 1 +11001 1 +11010 1 +11011 1 +11111 1 +.names inst[31] $abc$8517$new_n324_ $abc$8517$new_n300_ inst[29] $abc$8517$new_n1189_ +0000 1 +0001 1 +0010 1 +0100 1 +0101 1 +0110 1 +1100 1 +1101 1 +1110 1 +.names $abc$8517$new_n1016_ bus_address[29] $abc$8517$new_n1190_ +11 1 +.names singlecycle_datapath.program_counter.value[29] $abc$8517$new_n1192_ $abc$8517$new_n1191_ +01 1 +10 1 +.names singlecycle_datapath.program_counter.value[28] singlecycle_datapath.program_counter.value[27] singlecycle_datapath.program_counter.value[26] singlecycle_datapath.program_counter.value[25] singlecycle_datapath.program_counter.value[24] $abc$8517$new_n1160_ $abc$8517$new_n1192_ +111111 1 +.names reset $abc$8517$new_n1194_ $abc$8517$new_n1197_ $abc$8517$new_n999_ $abc$8517$new_n1195_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][30] +000010 1 +000110 1 +001010 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n1016_ bus_address[30] $abc$8517$new_n1194_ +11 1 +.names singlecycle_datapath.program_counter.value[30] $abc$8517$new_n1196_ singlecycle_datapath.program_counter.value[29] $abc$8517$new_n1189_ $abc$8517$new_n1188_ $abc$8517$new_n1195_ +00001 1 +00010 1 +00011 1 +00111 1 +01000 1 +01100 1 +01101 1 +01110 1 +10000 1 +10100 1 +10101 1 +10110 1 +11001 1 +11010 1 +11011 1 +11111 1 +.names inst[31] $abc$8517$new_n324_ $abc$8517$new_n300_ inst[30] $abc$8517$new_n1196_ +0000 1 +0001 1 +0010 1 +0100 1 +0101 1 +0110 1 +1100 1 +1101 1 +1110 1 +.names singlecycle_datapath.program_counter.value[30] singlecycle_datapath.program_counter.value[29] $abc$8517$new_n1192_ $abc$8517$new_n1197_ +011 1 +100 1 +101 1 +110 1 +.names reset $abc$8517$new_n999_ singlecycle_datapath.program_counter.value[31] $abc$8517$new_n1203_ $abc$8517$new_n1199_ $abc$8517$new_n1202_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][31] +000001 1 +000010 1 +000011 1 +000101 1 +000110 1 +000111 1 +001001 1 +001010 1 +001011 1 +001101 1 +001110 1 +001111 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n1200_ $abc$8517$new_n1009_ $abc$8517$new_n1010_ $abc$8517$new_n1000_ bus_address[31] $abc$8517$new_n1012_ $abc$8517$new_n1199_ +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010101 1 +010110 1 +010111 1 +011001 1 +011011 1 +011100 1 +011101 1 +011111 1 +.names $abc$8517$new_n1201_ $abc$8517$new_n1189_ singlecycle_datapath.program_counter.value[29] singlecycle_datapath.program_counter.value[30] $abc$8517$new_n1196_ $abc$8517$new_n1188_ $abc$8517$new_n1200_ +000001 1 +000010 1 +000011 1 +000111 1 +001010 1 +001011 1 +010000 1 +010001 1 +010010 1 +010011 1 +010110 1 +010111 1 +011001 1 +011010 1 +011011 1 +011111 1 +100000 1 +100100 1 +100101 1 +100110 1 +101000 1 +101001 1 +101100 1 +101101 1 +101110 1 +101111 1 +110100 1 +110101 1 +111000 1 +111100 1 +111101 1 +111110 1 +.names singlecycle_datapath.program_counter.value[31] $abc$8517$new_n494_ $abc$8517$new_n1201_ +00 1 +11 1 +.names $abc$8517$new_n1016_ bus_address[31] $abc$8517$new_n1202_ +11 1 +.names singlecycle_datapath.program_counter.value[30] singlecycle_datapath.program_counter.value[29] $abc$8517$new_n1192_ $abc$8517$new_n1203_ +111 1 +.names $abc$8517$new_n1005_ inst[13] inst[12] bus_byte_enable[0] +100 1 +101 1 +110 1 +.names inst[12] bus_address[1] inst[13] bus_address[0] bus_byte_enable[1] +0001 1 +0010 1 +0011 1 +1000 1 +1001 1 +.names inst[13] inst[12] bus_address[0] bus_address[1] bus_byte_enable[2] +0001 1 +0101 1 +0110 1 +1000 1 +1001 1 +1010 1 +.names inst[12] bus_address[1] inst[13] bus_address[0] bus_byte_enable[3] +0010 1 +0011 1 +0101 1 +0110 1 +0111 1 +1100 1 +1101 1 +.names $abc$8517$new_n299_ $abc$8517$new_n1209_ bus_address[0] $abc$8517$new_n1210_ singlecycle_datapath.program_counter.value[0] $abc$8517$new_n302_ rd_data[0] +000011 1 +000111 1 +001000 1 +001001 1 +001010 1 +001011 1 +001111 1 +010011 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011111 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110011 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111111 1 +.names bus_address[1] bus_address[0] bus_read_data[24] bus_read_data[8] bus_read_data[16] bus_read_data[0] $abc$8517$new_n1209_ +000000 1 +000010 1 +000100 1 +000110 1 +001000 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +011000 1 +011001 1 +011010 1 +011011 1 +100000 1 +100001 1 +100100 1 +100101 1 +101000 1 +101001 1 +101100 1 +101101 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +.names $abc$8517$new_n301_ $abc$8517$new_n300_ inst[5] $abc$8517$new_n1210_ +000 1 +001 1 +011 1 +.names $abc$8517$new_n299_ $abc$8517$new_n1212_ bus_address[1] $abc$8517$new_n1210_ singlecycle_datapath.program_counter.value[1] $abc$8517$new_n302_ rd_data[1] +000011 1 +000111 1 +001000 1 +001001 1 +001010 1 +001011 1 +001111 1 +010011 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011111 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110011 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111111 1 +.names bus_address[1] bus_address[0] bus_read_data[25] bus_read_data[9] bus_read_data[17] bus_read_data[1] $abc$8517$new_n1212_ +000000 1 +000010 1 +000100 1 +000110 1 +001000 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +011000 1 +011001 1 +011010 1 +011011 1 +100000 1 +100001 1 +100100 1 +100101 1 +101000 1 +101001 1 +101100 1 +101101 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +.names $abc$8517$new_n299_ $abc$8517$new_n1214_ bus_address[2] $abc$8517$new_n1210_ $abc$8517$new_n302_ singlecycle_datapath.program_counter.value[2] rd_data[2] +000010 1 +000110 1 +001000 1 +001001 1 +001010 1 +001011 1 +001110 1 +010010 1 +010110 1 +011000 1 +011001 1 +011010 1 +011011 1 +011110 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110010 1 +110110 1 +111000 1 +111001 1 +111010 1 +111011 1 +111110 1 +.names bus_address[1] bus_address[0] bus_read_data[26] bus_read_data[10] bus_read_data[18] bus_read_data[2] $abc$8517$new_n1214_ +000000 1 +000010 1 +000100 1 +000110 1 +001000 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +011000 1 +011001 1 +011010 1 +011011 1 +100000 1 +100001 1 +100100 1 +100101 1 +101000 1 +101001 1 +101100 1 +101101 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +.names $abc$8517$new_n299_ $abc$8517$new_n1216_ bus_address[3] $abc$8517$new_n1210_ $abc$8517$new_n1028_ $abc$8517$new_n302_ rd_data[3] +000011 1 +000111 1 +001000 1 +001001 1 +001010 1 +001011 1 +001111 1 +010011 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011111 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110011 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111111 1 +.names bus_address[1] bus_address[0] bus_read_data[27] bus_read_data[11] bus_read_data[19] bus_read_data[3] $abc$8517$new_n1216_ +000000 1 +000010 1 +000100 1 +000110 1 +001000 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +011000 1 +011001 1 +011010 1 +011011 1 +100000 1 +100001 1 +100100 1 +100101 1 +101000 1 +101001 1 +101100 1 +101101 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +.names $abc$8517$new_n299_ $abc$8517$new_n1218_ bus_address[4] $abc$8517$new_n1210_ $abc$8517$new_n1033_ $abc$8517$new_n302_ rd_data[4] +000011 1 +000111 1 +001000 1 +001001 1 +001010 1 +001011 1 +001111 1 +010011 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011111 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110011 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111111 1 +.names bus_address[1] bus_address[0] bus_read_data[28] bus_read_data[12] bus_read_data[20] bus_read_data[4] $abc$8517$new_n1218_ +000000 1 +000010 1 +000100 1 +000110 1 +001000 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +011000 1 +011001 1 +011010 1 +011011 1 +100000 1 +100001 1 +100100 1 +100101 1 +101000 1 +101001 1 +101100 1 +101101 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +.names $abc$8517$new_n299_ $abc$8517$new_n1220_ bus_address[5] $abc$8517$new_n1210_ $abc$8517$new_n1042_ $abc$8517$new_n302_ rd_data[5] +000011 1 +000111 1 +001000 1 +001001 1 +001010 1 +001011 1 +001111 1 +010011 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011111 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110011 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111111 1 +.names bus_address[1] bus_address[0] bus_read_data[29] bus_read_data[13] bus_read_data[21] bus_read_data[5] $abc$8517$new_n1220_ +000000 1 +000010 1 +000100 1 +000110 1 +001000 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +011000 1 +011001 1 +011010 1 +011011 1 +100000 1 +100001 1 +100100 1 +100101 1 +101000 1 +101001 1 +101100 1 +101101 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +.names $abc$8517$new_n299_ $abc$8517$new_n1222_ bus_address[6] $abc$8517$new_n1210_ $abc$8517$new_n1048_ $abc$8517$new_n302_ rd_data[6] +000011 1 +000111 1 +001000 1 +001001 1 +001010 1 +001011 1 +001111 1 +010011 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011111 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110011 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111111 1 +.names bus_address[1] bus_address[0] bus_read_data[30] bus_read_data[14] bus_read_data[22] bus_read_data[6] $abc$8517$new_n1222_ +000000 1 +000010 1 +000100 1 +000110 1 +001000 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +011000 1 +011001 1 +011010 1 +011011 1 +100000 1 +100001 1 +100100 1 +100101 1 +101000 1 +101001 1 +101100 1 +101101 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +.names $abc$8517$new_n299_ $abc$8517$new_n1224_ bus_address[7] $abc$8517$new_n1210_ $abc$8517$new_n1054_ $abc$8517$new_n302_ rd_data[7] +000011 1 +000111 1 +001000 1 +001001 1 +001010 1 +001011 1 +001111 1 +010011 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011111 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110011 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111111 1 +.names bus_address[1] bus_address[0] bus_read_data[31] bus_read_data[15] bus_read_data[23] bus_read_data[7] $abc$8517$new_n1224_ +000000 1 +000010 1 +000100 1 +000110 1 +001000 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +011000 1 +011001 1 +011010 1 +011011 1 +100000 1 +100001 1 +100100 1 +100101 1 +101000 1 +101001 1 +101100 1 +101101 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +.names $abc$8517$new_n1226_ $abc$8517$new_n1210_ bus_address[8] $abc$8517$new_n1060_ $abc$8517$new_n302_ rd_data[8] +00011 1 +00100 1 +00101 1 +00110 1 +00111 1 +01011 1 +01111 1 +10000 1 +10001 1 +10010 1 +10011 1 +10100 1 +10101 1 +10110 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n299_ inst[12] inst[13] $abc$8517$new_n1228_ $abc$8517$new_n1227_ $abc$8517$new_n1226_ +10001 1 +10011 1 +10100 1 +10101 1 +10111 1 +11000 1 +11001 1 +11011 1 +11101 1 +11111 1 +.names inst[13] inst[12] inst[14] $abc$8517$new_n1224_ $abc$8517$new_n1227_ +0000 1 +1100 1 +.names bus_address[1] bus_address[0] bus_read_data[16] bus_read_data[24] bus_read_data[8] $abc$8517$new_n1228_ +00000 1 +00010 1 +00100 1 +00110 1 +01000 1 +01001 1 +01010 1 +01011 1 +10000 1 +10001 1 +10100 1 +10101 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n1230_ $abc$8517$new_n1210_ bus_address[9] $abc$8517$new_n1067_ $abc$8517$new_n302_ rd_data[9] +00011 1 +00100 1 +00101 1 +00110 1 +00111 1 +01011 1 +01111 1 +10000 1 +10001 1 +10010 1 +10011 1 +10100 1 +10101 1 +10110 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n299_ inst[12] inst[13] $abc$8517$new_n1231_ $abc$8517$new_n1227_ $abc$8517$new_n1230_ +10001 1 +10011 1 +10100 1 +10101 1 +10111 1 +11000 1 +11001 1 +11011 1 +11101 1 +11111 1 +.names bus_address[1] bus_address[0] bus_read_data[17] bus_read_data[25] bus_read_data[9] $abc$8517$new_n1231_ +00000 1 +00010 1 +00100 1 +00110 1 +01000 1 +01001 1 +01010 1 +01011 1 +10000 1 +10001 1 +10100 1 +10101 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n1233_ $abc$8517$new_n1210_ bus_address[10] $abc$8517$new_n1073_ $abc$8517$new_n302_ rd_data[10] +00011 1 +00100 1 +00101 1 +00110 1 +00111 1 +01011 1 +01111 1 +10000 1 +10001 1 +10010 1 +10011 1 +10100 1 +10101 1 +10110 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n299_ inst[12] inst[13] $abc$8517$new_n1234_ $abc$8517$new_n1227_ $abc$8517$new_n1233_ +10001 1 +10011 1 +10100 1 +10101 1 +10111 1 +11000 1 +11001 1 +11011 1 +11101 1 +11111 1 +.names bus_address[1] bus_address[0] bus_read_data[18] bus_read_data[26] bus_read_data[10] $abc$8517$new_n1234_ +00000 1 +00010 1 +00100 1 +00110 1 +01000 1 +01001 1 +01010 1 +01011 1 +10000 1 +10001 1 +10100 1 +10101 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n1236_ $abc$8517$new_n1210_ bus_address[11] $abc$8517$new_n1079_ $abc$8517$new_n302_ rd_data[11] +00011 1 +00100 1 +00101 1 +00110 1 +00111 1 +01011 1 +01111 1 +10000 1 +10001 1 +10010 1 +10011 1 +10100 1 +10101 1 +10110 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n299_ inst[12] inst[13] $abc$8517$new_n1237_ $abc$8517$new_n1227_ $abc$8517$new_n1236_ +10001 1 +10011 1 +10100 1 +10101 1 +10111 1 +11000 1 +11001 1 +11011 1 +11101 1 +11111 1 +.names bus_address[1] bus_address[0] bus_read_data[19] bus_read_data[27] bus_read_data[11] $abc$8517$new_n1237_ +00000 1 +00010 1 +00100 1 +00110 1 +01000 1 +01001 1 +01010 1 +01011 1 +10000 1 +10001 1 +10100 1 +10101 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n1239_ $abc$8517$new_n302_ $abc$8517$new_n1086_ rd_data[12] +000 1 +001 1 +010 1 +011 1 +111 1 +.names $abc$8517$new_n1241_ $abc$8517$new_n1227_ inst[12] inst[13] $abc$8517$new_n299_ $abc$8517$new_n1240_ $abc$8517$new_n1239_ +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100111 1 +101000 1 +101001 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110001 1 +110100 1 +110101 1 +111000 1 +111001 1 +111100 1 +111101 1 +.names bus_address[1] bus_address[0] bus_read_data[20] bus_read_data[28] bus_read_data[12] $abc$8517$new_n1240_ +00000 1 +00010 1 +00100 1 +00110 1 +01000 1 +01001 1 +01010 1 +01011 1 +10000 1 +10001 1 +10100 1 +10101 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n1210_ bus_address[12] $abc$8517$new_n300_ $abc$8517$new_n1084_ $abc$8517$new_n1241_ +0000 1 +0001 1 +0010 1 +0011 1 +1000 1 +1001 1 +1011 1 +1100 1 +1101 1 +1111 1 +.names $abc$8517$new_n1243_ bus_address[13] $abc$8517$new_n1210_ rd_data[13] +000 1 +001 1 +010 1 +011 1 +110 1 +.names $abc$8517$new_n1245_ $abc$8517$new_n1227_ inst[12] inst[13] $abc$8517$new_n299_ $abc$8517$new_n1244_ $abc$8517$new_n1243_ +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100111 1 +101000 1 +101001 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110001 1 +110100 1 +110101 1 +111000 1 +111001 1 +111100 1 +111101 1 +.names bus_address[1] bus_address[0] bus_read_data[21] bus_read_data[29] bus_read_data[13] $abc$8517$new_n1244_ +00000 1 +00010 1 +00100 1 +00110 1 +01000 1 +01001 1 +01010 1 +01011 1 +10000 1 +10001 1 +10100 1 +10101 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n1092_ $abc$8517$new_n302_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n1090_ $abc$8517$new_n1245_ +00000 1 +00001 1 +00010 1 +00011 1 +00100 1 +00101 1 +00111 1 +01000 1 +01001 1 +01010 1 +01011 1 +01100 1 +01101 1 +01111 1 +10000 1 +10001 1 +10010 1 +10011 1 +10100 1 +10101 1 +10111 1 +.names $abc$8517$new_n1247_ bus_address[14] $abc$8517$new_n1210_ rd_data[14] +000 1 +001 1 +010 1 +011 1 +110 1 +.names $abc$8517$new_n1249_ $abc$8517$new_n1227_ inst[12] inst[13] $abc$8517$new_n299_ $abc$8517$new_n1248_ $abc$8517$new_n1247_ +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100111 1 +101000 1 +101001 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110001 1 +110100 1 +110101 1 +111000 1 +111001 1 +111100 1 +111101 1 +.names bus_address[1] bus_address[0] bus_read_data[22] bus_read_data[30] bus_read_data[14] $abc$8517$new_n1248_ +00000 1 +00010 1 +00100 1 +00110 1 +01000 1 +01001 1 +01010 1 +01011 1 +10000 1 +10001 1 +10100 1 +10101 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n1098_ $abc$8517$new_n302_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n1097_ $abc$8517$new_n1249_ +00000 1 +00001 1 +00010 1 +00011 1 +00100 1 +00101 1 +00111 1 +01000 1 +01001 1 +01010 1 +01011 1 +01100 1 +01101 1 +01111 1 +10000 1 +10001 1 +10010 1 +10011 1 +10100 1 +10101 1 +10111 1 +.names $abc$8517$new_n1251_ bus_address[15] $abc$8517$new_n1210_ rd_data[15] +000 1 +001 1 +010 1 +011 1 +110 1 +.names $abc$8517$new_n1253_ $abc$8517$new_n1227_ inst[12] inst[13] $abc$8517$new_n299_ $abc$8517$new_n1252_ $abc$8517$new_n1251_ +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100111 1 +101000 1 +101001 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110001 1 +110100 1 +110101 1 +111000 1 +111001 1 +111100 1 +111101 1 +.names bus_address[1] bus_address[0] bus_read_data[23] bus_read_data[31] bus_read_data[15] $abc$8517$new_n1252_ +00000 1 +00010 1 +00100 1 +00110 1 +01000 1 +01001 1 +01010 1 +01011 1 +10000 1 +10001 1 +10100 1 +10101 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n1107_ $abc$8517$new_n302_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n1105_ $abc$8517$new_n1253_ +00000 1 +00001 1 +00010 1 +00011 1 +00100 1 +00101 1 +00111 1 +01000 1 +01001 1 +01010 1 +01011 1 +01100 1 +01101 1 +01111 1 +10000 1 +10001 1 +10010 1 +10011 1 +10100 1 +10101 1 +10111 1 +.names $abc$8517$new_n1255_ bus_address[16] $abc$8517$new_n1210_ rd_data[16] +000 1 +001 1 +010 1 +011 1 +110 1 +.names $abc$8517$new_n1256_ $abc$8517$new_n302_ $abc$8517$new_n1112_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n1111_ $abc$8517$new_n1255_ +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +000111 1 +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010111 1 +.names $abc$8517$new_n299_ $abc$8517$new_n1258_ bus_address[0] $abc$8517$new_n1257_ bus_read_data[24] bus_read_data[16] $abc$8517$new_n1256_ +100000 1 +100001 1 +100010 1 +100011 1 +101000 1 +101001 1 +101010 1 +101011 1 +110000 1 +110001 1 +110010 1 +110011 1 +110101 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111110 1 +111111 1 +.names inst[12] inst[13] inst[14] $abc$8517$new_n1224_ $abc$8517$new_n1252_ $abc$8517$new_n1257_ +00010 1 +00011 1 +00100 1 +00101 1 +00110 1 +00111 1 +01000 1 +01001 1 +01010 1 +01011 1 +01100 1 +01101 1 +01110 1 +01111 1 +10001 1 +10011 1 +10100 1 +10101 1 +10110 1 +10111 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names inst[13] inst[12] bus_address[1] $abc$8517$new_n1258_ +100 1 +.names $abc$8517$new_n1260_ $abc$8517$new_n302_ $abc$8517$new_n1118_ rd_data[17] +000 1 +001 1 +010 1 +011 1 +111 1 +.names $abc$8517$new_n1261_ bus_address[17] $abc$8517$new_n1210_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n1117_ $abc$8517$new_n1260_ +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +000111 1 +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011111 1 +.names $abc$8517$new_n299_ $abc$8517$new_n1258_ bus_address[0] $abc$8517$new_n1257_ bus_read_data[25] bus_read_data[17] $abc$8517$new_n1261_ +100000 1 +100001 1 +100010 1 +100011 1 +101000 1 +101001 1 +101010 1 +101011 1 +110000 1 +110001 1 +110010 1 +110011 1 +110101 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111110 1 +111111 1 +.names $abc$8517$new_n1263_ bus_address[18] $abc$8517$new_n1210_ rd_data[18] +000 1 +001 1 +010 1 +011 1 +110 1 +.names $abc$8517$new_n1264_ $abc$8517$new_n302_ $abc$8517$new_n1124_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n1122_ $abc$8517$new_n1263_ +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +000111 1 +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010111 1 +.names $abc$8517$new_n299_ $abc$8517$new_n1258_ bus_address[0] $abc$8517$new_n1257_ bus_read_data[26] bus_read_data[18] $abc$8517$new_n1264_ +100000 1 +100001 1 +100010 1 +100011 1 +101000 1 +101001 1 +101010 1 +101011 1 +110000 1 +110001 1 +110010 1 +110011 1 +110101 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111110 1 +111111 1 +.names $abc$8517$new_n1266_ bus_address[19] $abc$8517$new_n1210_ rd_data[19] +000 1 +001 1 +010 1 +011 1 +110 1 +.names $abc$8517$new_n1267_ $abc$8517$new_n302_ $abc$8517$new_n1130_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n1128_ $abc$8517$new_n1266_ +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +000111 1 +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010111 1 +.names $abc$8517$new_n299_ $abc$8517$new_n1258_ bus_address[0] $abc$8517$new_n1257_ bus_read_data[27] bus_read_data[19] $abc$8517$new_n1267_ +100000 1 +100001 1 +100010 1 +100011 1 +101000 1 +101001 1 +101010 1 +101011 1 +110000 1 +110001 1 +110010 1 +110011 1 +110101 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111110 1 +111111 1 +.names $abc$8517$new_n1269_ bus_address[20] $abc$8517$new_n1210_ rd_data[20] +000 1 +001 1 +010 1 +011 1 +110 1 +.names $abc$8517$new_n1270_ $abc$8517$new_n302_ $abc$8517$new_n1138_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n1137_ $abc$8517$new_n1269_ +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +000111 1 +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010111 1 +.names $abc$8517$new_n299_ $abc$8517$new_n1258_ bus_address[0] $abc$8517$new_n1257_ bus_read_data[28] bus_read_data[20] $abc$8517$new_n1270_ +100000 1 +100001 1 +100010 1 +100011 1 +101000 1 +101001 1 +101010 1 +101011 1 +110000 1 +110001 1 +110010 1 +110011 1 +110101 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111110 1 +111111 1 +.names $abc$8517$new_n1272_ bus_address[21] $abc$8517$new_n1210_ rd_data[21] +000 1 +001 1 +010 1 +011 1 +110 1 +.names $abc$8517$new_n1273_ $abc$8517$new_n302_ $abc$8517$new_n1143_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n1142_ $abc$8517$new_n1272_ +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +000111 1 +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010111 1 +.names $abc$8517$new_n299_ $abc$8517$new_n1258_ bus_address[0] $abc$8517$new_n1257_ bus_read_data[29] bus_read_data[21] $abc$8517$new_n1273_ +100000 1 +100001 1 +100010 1 +100011 1 +101000 1 +101001 1 +101010 1 +101011 1 +110000 1 +110001 1 +110010 1 +110011 1 +110101 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111110 1 +111111 1 +.names $abc$8517$new_n1276_ $abc$8517$new_n1275_ bus_address[22] $abc$8517$new_n1210_ rd_data[22] +0000 1 +0001 1 +0010 1 +0011 1 +0100 1 +0101 1 +0110 1 +0111 1 +1010 1 +1100 1 +1101 1 +1110 1 +1111 1 +.names $abc$8517$new_n299_ $abc$8517$new_n1258_ bus_address[0] $abc$8517$new_n1257_ bus_read_data[30] bus_read_data[22] $abc$8517$new_n1275_ +100000 1 +100001 1 +100010 1 +100011 1 +101000 1 +101001 1 +101010 1 +101011 1 +110000 1 +110001 1 +110010 1 +110011 1 +110101 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111110 1 +111111 1 +.names $abc$8517$new_n302_ singlecycle_datapath.program_counter.value[22] $abc$8517$new_n1148_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n414_ $abc$8517$new_n1276_ +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +000111 1 +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011111 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111111 1 +.names $abc$8517$new_n1278_ bus_address[23] $abc$8517$new_n1210_ rd_data[23] +000 1 +001 1 +010 1 +011 1 +110 1 +.names $abc$8517$new_n1279_ $abc$8517$new_n302_ $abc$8517$new_n1153_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n427_ $abc$8517$new_n1278_ +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +000111 1 +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010111 1 +.names $abc$8517$new_n299_ $abc$8517$new_n1258_ bus_address[0] $abc$8517$new_n1257_ bus_read_data[31] bus_read_data[23] $abc$8517$new_n1279_ +100000 1 +100001 1 +100010 1 +100011 1 +101000 1 +101001 1 +101010 1 +101011 1 +110000 1 +110001 1 +110010 1 +110011 1 +110101 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111110 1 +111111 1 +.names $abc$8517$new_n1281_ bus_address[24] $abc$8517$new_n1210_ rd_data[24] +000 1 +001 1 +010 1 +011 1 +110 1 +.names $abc$8517$new_n1283_ $abc$8517$new_n1257_ $abc$8517$new_n299_ $abc$8517$new_n1282_ bus_read_data[24] $abc$8517$new_n1281_ +10000 1 +10001 1 +10010 1 +10011 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +.names $abc$8517$new_n1258_ bus_address[0] $abc$8517$new_n1282_ +10 1 +.names $abc$8517$new_n1159_ $abc$8517$new_n302_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n1157_ $abc$8517$new_n1283_ +00000 1 +00001 1 +00010 1 +00011 1 +00100 1 +00101 1 +00111 1 +01000 1 +01001 1 +01010 1 +01011 1 +01100 1 +01101 1 +01111 1 +10000 1 +10001 1 +10010 1 +10011 1 +10100 1 +10101 1 +10111 1 +.names $abc$8517$new_n1285_ bus_address[25] $abc$8517$new_n1210_ rd_data[25] +000 1 +001 1 +010 1 +011 1 +110 1 +.names $abc$8517$new_n1286_ $abc$8517$new_n1257_ $abc$8517$new_n299_ $abc$8517$new_n1282_ bus_read_data[25] $abc$8517$new_n1285_ +10000 1 +10001 1 +10010 1 +10011 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +.names $abc$8517$new_n1169_ $abc$8517$new_n302_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n1167_ $abc$8517$new_n1286_ +00000 1 +00001 1 +00010 1 +00011 1 +00100 1 +00101 1 +00111 1 +01000 1 +01001 1 +01010 1 +01011 1 +01100 1 +01101 1 +01111 1 +10000 1 +10001 1 +10010 1 +10011 1 +10100 1 +10101 1 +10111 1 +.names $abc$8517$new_n1288_ bus_address[26] $abc$8517$new_n1210_ rd_data[26] +000 1 +001 1 +010 1 +011 1 +110 1 +.names $abc$8517$new_n1289_ $abc$8517$new_n1257_ $abc$8517$new_n299_ $abc$8517$new_n1282_ bus_read_data[26] $abc$8517$new_n1288_ +10000 1 +10001 1 +10010 1 +10011 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +.names $abc$8517$new_n1174_ $abc$8517$new_n302_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n1172_ $abc$8517$new_n1289_ +00000 1 +00001 1 +00010 1 +00011 1 +00100 1 +00101 1 +00111 1 +01000 1 +01001 1 +01010 1 +01011 1 +01100 1 +01101 1 +01111 1 +10000 1 +10001 1 +10010 1 +10011 1 +10100 1 +10101 1 +10111 1 +.names $abc$8517$new_n1291_ bus_address[27] $abc$8517$new_n1210_ rd_data[27] +000 1 +001 1 +010 1 +011 1 +110 1 +.names $abc$8517$new_n1292_ $abc$8517$new_n1257_ $abc$8517$new_n299_ $abc$8517$new_n1282_ bus_read_data[27] $abc$8517$new_n1291_ +10000 1 +10001 1 +10010 1 +10011 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +.names $abc$8517$new_n1180_ $abc$8517$new_n302_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n1179_ $abc$8517$new_n1292_ +00000 1 +00001 1 +00010 1 +00011 1 +00100 1 +00101 1 +00111 1 +01000 1 +01001 1 +01010 1 +01011 1 +01100 1 +01101 1 +01111 1 +10000 1 +10001 1 +10010 1 +10011 1 +10100 1 +10101 1 +10111 1 +.names $abc$8517$new_n1294_ bus_address[28] $abc$8517$new_n1210_ rd_data[28] +000 1 +001 1 +010 1 +011 1 +110 1 +.names $abc$8517$new_n1295_ $abc$8517$new_n1257_ $abc$8517$new_n299_ $abc$8517$new_n1282_ bus_read_data[28] $abc$8517$new_n1294_ +10000 1 +10001 1 +10010 1 +10011 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +.names $abc$8517$new_n1185_ $abc$8517$new_n302_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n1184_ $abc$8517$new_n1295_ +00000 1 +00001 1 +00010 1 +00011 1 +00100 1 +00101 1 +00111 1 +01000 1 +01001 1 +01010 1 +01011 1 +01100 1 +01101 1 +01111 1 +10000 1 +10001 1 +10010 1 +10011 1 +10100 1 +10101 1 +10111 1 +.names $abc$8517$new_n1297_ bus_address[29] $abc$8517$new_n1210_ rd_data[29] +000 1 +001 1 +010 1 +011 1 +110 1 +.names $abc$8517$new_n1298_ $abc$8517$new_n1257_ $abc$8517$new_n299_ $abc$8517$new_n1282_ bus_read_data[29] $abc$8517$new_n1297_ +10000 1 +10001 1 +10010 1 +10011 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +.names $abc$8517$new_n1191_ $abc$8517$new_n302_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n1189_ $abc$8517$new_n1298_ +00000 1 +00001 1 +00010 1 +00011 1 +00100 1 +00101 1 +00111 1 +01000 1 +01001 1 +01010 1 +01011 1 +01100 1 +01101 1 +01111 1 +10000 1 +10001 1 +10010 1 +10011 1 +10100 1 +10101 1 +10111 1 +.names $abc$8517$new_n1300_ bus_address[30] $abc$8517$new_n1210_ rd_data[30] +000 1 +001 1 +010 1 +011 1 +110 1 +.names $abc$8517$new_n1301_ $abc$8517$new_n1257_ $abc$8517$new_n299_ $abc$8517$new_n1282_ bus_read_data[30] $abc$8517$new_n1300_ +10000 1 +10001 1 +10010 1 +10011 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +.names $abc$8517$new_n1197_ $abc$8517$new_n302_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n1196_ $abc$8517$new_n1301_ +00000 1 +00001 1 +00010 1 +00011 1 +00100 1 +00101 1 +00111 1 +01000 1 +01001 1 +01010 1 +01011 1 +01100 1 +01101 1 +01111 1 +10000 1 +10001 1 +10010 1 +10011 1 +10100 1 +10101 1 +10111 1 +.names $abc$8517$new_n1304_ $abc$8517$new_n1303_ bus_address[31] $abc$8517$new_n1210_ rd_data[31] +0000 1 +0001 1 +0010 1 +0011 1 +0100 1 +0101 1 +0110 1 +0111 1 +1010 1 +1100 1 +1101 1 +1110 1 +1111 1 +.names $abc$8517$new_n299_ $abc$8517$new_n1257_ bus_read_data[31] $abc$8517$new_n1282_ $abc$8517$new_n1303_ +1000 1 +1001 1 +1010 1 +1011 1 +1111 1 +.names $abc$8517$new_n302_ singlecycle_datapath.program_counter.value[31] $abc$8517$new_n1203_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n494_ $abc$8517$new_n1304_ +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +000111 1 +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011111 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111111 1 +.names $abc$8517$new_n299_ $abc$8517$new_n1009_ $abc$8517$new_n348_ $abc$8517$new_n1307_ $abc$8517$new_n1306_ inst[3] bus_write_enable +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +101000 1 +101001 1 +101010 1 +101011 1 +.names inst[2] $abc$8517$new_n313_ $abc$8517$new_n1306_ +11 1 +.names inst[6] inst[5] inst[4] $abc$8517$new_n1307_ +000 1 +.names $abc$8517$new_n299_ $abc$8517$new_n1306_ $abc$8517$new_n346_ $abc$8517$new_n1310_ bus_read_enable +1010 1 +1011 1 +1100 1 +1110 1 +.names inst[3] $abc$8517$new_n1307_ $abc$8517$new_n1310_ +11 1 +.names rs2_data[0] $abc$8517$new_n1005_ bus_write_data[0] +11 1 +.names rs2_data[1] $abc$8517$new_n1005_ bus_write_data[1] +11 1 +.names rs2_data[2] $abc$8517$new_n1005_ bus_write_data[2] +11 1 +.names rs2_data[3] $abc$8517$new_n1005_ bus_write_data[3] +11 1 +.names rs2_data[4] $abc$8517$new_n1005_ bus_write_data[4] +11 1 +.names rs2_data[5] $abc$8517$new_n1005_ bus_write_data[5] +11 1 +.names rs2_data[6] $abc$8517$new_n1005_ bus_write_data[6] +11 1 +.names rs2_data[7] $abc$8517$new_n1005_ bus_write_data[7] +11 1 +.names bus_address[0] bus_address[1] rs2_data[0] rs2_data[8] bus_write_data[8] +0001 1 +0011 1 +1010 1 +1011 1 +.names bus_address[0] bus_address[1] rs2_data[1] rs2_data[9] bus_write_data[9] +0001 1 +0011 1 +1010 1 +1011 1 +.names bus_address[0] bus_address[1] rs2_data[2] rs2_data[10] bus_write_data[10] +0001 1 +0011 1 +1010 1 +1011 1 +.names bus_address[0] bus_address[1] rs2_data[3] rs2_data[11] bus_write_data[11] +0001 1 +0011 1 +1010 1 +1011 1 +.names bus_address[0] bus_address[1] rs2_data[4] rs2_data[12] bus_write_data[12] +0001 1 +0011 1 +1010 1 +1011 1 +.names bus_address[0] bus_address[1] rs2_data[5] rs2_data[13] bus_write_data[13] +0001 1 +0011 1 +1010 1 +1011 1 +.names bus_address[0] bus_address[1] rs2_data[6] rs2_data[14] bus_write_data[14] +0001 1 +0011 1 +1010 1 +1011 1 +.names bus_address[0] bus_address[1] rs2_data[7] rs2_data[15] bus_write_data[15] +0001 1 +0011 1 +1010 1 +1011 1 +.names bus_address[1] bus_address[0] rs2_data[8] rs2_data[0] rs2_data[16] bus_write_data[16] +00001 1 +00011 1 +00101 1 +00111 1 +01100 1 +01101 1 +01110 1 +01111 1 +10010 1 +10011 1 +10110 1 +10111 1 +.names bus_address[1] bus_address[0] rs2_data[9] rs2_data[1] rs2_data[17] bus_write_data[17] +00001 1 +00011 1 +00101 1 +00111 1 +01100 1 +01101 1 +01110 1 +01111 1 +10010 1 +10011 1 +10110 1 +10111 1 +.names bus_address[1] bus_address[0] rs2_data[10] rs2_data[2] rs2_data[18] bus_write_data[18] +00001 1 +00011 1 +00101 1 +00111 1 +01100 1 +01101 1 +01110 1 +01111 1 +10010 1 +10011 1 +10110 1 +10111 1 +.names bus_address[1] bus_address[0] rs2_data[11] rs2_data[3] rs2_data[19] bus_write_data[19] +00001 1 +00011 1 +00101 1 +00111 1 +01100 1 +01101 1 +01110 1 +01111 1 +10010 1 +10011 1 +10110 1 +10111 1 +.names bus_address[1] bus_address[0] rs2_data[12] rs2_data[4] rs2_data[20] bus_write_data[20] +00001 1 +00011 1 +00101 1 +00111 1 +01100 1 +01101 1 +01110 1 +01111 1 +10010 1 +10011 1 +10110 1 +10111 1 +.names bus_address[1] bus_address[0] rs2_data[13] rs2_data[5] rs2_data[21] bus_write_data[21] +00001 1 +00011 1 +00101 1 +00111 1 +01100 1 +01101 1 +01110 1 +01111 1 +10010 1 +10011 1 +10110 1 +10111 1 +.names bus_address[1] bus_address[0] rs2_data[14] rs2_data[6] rs2_data[22] bus_write_data[22] +00001 1 +00011 1 +00101 1 +00111 1 +01100 1 +01101 1 +01110 1 +01111 1 +10010 1 +10011 1 +10110 1 +10111 1 +.names bus_address[1] bus_address[0] rs2_data[15] rs2_data[7] rs2_data[23] bus_write_data[23] +00001 1 +00011 1 +00101 1 +00111 1 +01100 1 +01101 1 +01110 1 +01111 1 +10010 1 +10011 1 +10110 1 +10111 1 +.names bus_address[1] bus_address[0] rs2_data[0] rs2_data[16] rs2_data[8] rs2_data[24] bus_write_data[24] +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names bus_address[1] bus_address[0] rs2_data[1] rs2_data[17] rs2_data[9] rs2_data[25] bus_write_data[25] +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names bus_address[1] bus_address[0] rs2_data[2] rs2_data[18] rs2_data[10] rs2_data[26] bus_write_data[26] +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names bus_address[1] bus_address[0] rs2_data[3] rs2_data[19] rs2_data[11] rs2_data[27] bus_write_data[27] +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names bus_address[1] bus_address[0] rs2_data[4] rs2_data[20] rs2_data[12] rs2_data[28] bus_write_data[28] +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names bus_address[1] bus_address[0] rs2_data[5] rs2_data[21] rs2_data[13] rs2_data[29] bus_write_data[29] +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names bus_address[1] bus_address[0] rs2_data[6] rs2_data[22] rs2_data[14] rs2_data[30] bus_write_data[30] +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names bus_address[1] bus_address[0] rs2_data[7] rs2_data[23] rs2_data[15] rs2_data[31] bus_write_data[31] +000001 1 +000011 1 +000101 1 +000111 1 +001001 1 +001011 1 +001101 1 +001111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100011 1 +100110 1 +100111 1 +101010 1 +101011 1 +101110 1 +101111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n462_ $abc$8517$new_n444_ $abc$8517$new_n306_ $abc$8517$new_n411_ $abc$8517$new_n454_ $abc$8517$new_n451_ $abc$8517$new_n1343_ +000011 1 +000111 1 +001011 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +100000 1 +100001 1 +100010 1 +100011 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n433_ $abc$8517$new_n436_ $abc$8517$new_n432_ $abc$8517$new_n431_ $abc$8517$new_n442_ $abc$8517$new_n1343_ $abc$8517$new_n1344_ +000000 1 +000001 1 +000010 1 +000100 1 +000101 1 +000110 1 +000111 1 +001100 1 +001101 1 +001110 1 +010100 1 +010101 1 +010110 1 +010111 1 +.names $abc$8517$new_n310_ rs2_data[19] $abc$8517$new_n314_ inst[31] $abc$8517$new_n312_ $abc$8517$new_n1345_ +00010 1 +00100 1 +00101 1 +00110 1 +00111 1 +01010 1 +01100 1 +01101 1 +01110 1 +01111 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n318_ $abc$8517$new_n315_ $abc$8517$new_n423_ $abc$8517$new_n1345_ $abc$8517$new_n335_ $abc$8517$new_n418_ $abc$8517$new_n1346_ +000000 1 +000001 1 +000011 1 +000100 1 +000101 1 +000111 1 +001000 1 +001001 1 +001011 1 +001100 1 +001101 1 +001111 1 +010000 1 +010001 1 +010011 1 +011000 1 +011001 1 +011011 1 +100000 1 +100001 1 +100011 1 +101000 1 +101001 1 +101011 1 +101100 1 +101101 1 +101111 1 +111000 1 +111001 1 +111011 1 +.names $abc$8517$new_n1346_ $abc$8517$new_n413_ $abc$8517$new_n419_ $abc$8517$new_n330_ $abc$8517$new_n329_ $abc$8517$new_n415_ $abc$8517$new_n1347_ +000000 1 +000100 1 +001000 1 +001100 1 +010000 1 +010010 1 +010100 1 +010110 1 +011000 1 +011010 1 +011100 1 +011110 1 +100000 1 +100001 1 +100100 1 +101000 1 +101100 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110110 1 +111000 1 +111010 1 +111100 1 +111110 1 +.names $abc$8517$new_n433_ $abc$8517$new_n461_ $abc$8517$new_n432_ $abc$8517$new_n431_ $abc$8517$new_n411_ $abc$8517$new_n306_ $abc$8517$new_n1348_ +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +000110 1 +000111 1 +001100 1 +001101 1 +001110 1 +001111 1 +010010 1 +010100 1 +010101 1 +010110 1 +010111 1 +011110 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n495_ $abc$8517$new_n1348_ $abc$8517$new_n493_ $abc$8517$new_n492_ $abc$8517$new_n1349_ +1000 1 +1001 1 +1010 1 +1011 1 +1101 1 +1110 1 +1111 1 +.names $abc$8517$new_n479_ $abc$8517$new_n497_ $abc$8517$new_n519_ $abc$8517$new_n353_ $abc$8517$new_n467_ $abc$8517$new_n489_ $abc$8517$new_n1350_ +000000 1 +000001 1 +000010 1 +000011 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101110 1 +110010 1 +110110 1 +111010 1 +111110 1 +.names $abc$8517$new_n551_ $abc$8517$new_n520_ $abc$8517$new_n368_ $abc$8517$new_n543_ $abc$8517$new_n542_ $abc$8517$new_n362_ $abc$8517$new_n1351_ +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +000110 1 +000111 1 +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +.names $abc$8517$new_n1351_ $abc$8517$new_n545_ $abc$8517$new_n486_ $abc$8517$new_n549_ $abc$8517$new_n502_ $abc$8517$new_n1350_ $abc$8517$new_n1352_ +100000 1 +100010 1 +100110 1 +101000 1 +101001 1 +101010 1 +101011 1 +101110 1 +101111 1 +.names $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n573_ $abc$8517$new_n571_ $abc$8517$new_n574_ $abc$8517$new_n572_ $abc$8517$new_n1353_ +000000 1 +000010 1 +000100 1 +000110 1 +001000 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +011000 1 +011001 1 +011010 1 +011011 1 +100000 1 +100001 1 +100100 1 +100101 1 +101000 1 +101001 1 +101100 1 +101101 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +.names $abc$8517$new_n575_ $abc$8517$new_n578_ $abc$8517$new_n345_ $abc$8517$new_n554_ $abc$8517$new_n1353_ $abc$8517$new_n549_ bus_address[1] +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +000110 1 +000111 1 +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110001 1 +110010 1 +110011 1 +110111 1 +111011 1 +111111 1 +.names $abc$8517$new_n359_ $abc$8517$new_n548_ $abc$8517$new_n618_ $abc$8517$new_n546_ $abc$8517$new_n616_ $abc$8517$new_n1358_ $abc$8517$new_n1355_ +000001 1 +000011 1 +000111 1 +001001 1 +001011 1 +001111 1 +010001 1 +010011 1 +010111 1 +100001 1 +100011 1 +100101 1 +101001 1 +101011 1 +101101 1 +111001 1 +111011 1 +111101 1 +.names $abc$8517$new_n486_ $abc$8517$new_n497_ $abc$8517$new_n368_ $abc$8517$new_n361_ $abc$8517$new_n479_ $abc$8517$new_n1357_ +00010 1 +00100 1 +00110 1 +00111 1 +01000 1 +01001 1 +01010 1 +01011 1 +01100 1 +01101 1 +01110 1 +01111 1 +10000 1 +10001 1 +10011 1 +10101 1 +10110 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +11111 1 +.names $abc$8517$new_n1357_ $abc$8517$new_n612_ $abc$8517$new_n543_ $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n1358_ +10000 1 +10001 1 +10010 1 +10011 1 +10100 1 +10101 1 +10110 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +.names $abc$8517$new_n659_ $abc$8517$new_n547_ $abc$8517$new_n344_ $abc$8517$new_n342_ $abc$8517$new_n576_ $abc$8517$new_n579_ $abc$8517$new_n1359_ +000000 1 +000100 1 +000101 1 +001000 1 +001001 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010010 1 +011100 1 +011101 1 +011110 1 +011111 1 +.names $abc$8517$new_n1359_ $abc$8517$new_n653_ $abc$8517$new_n647_ $abc$8517$new_n646_ $abc$8517$new_n549_ $abc$8517$new_n657_ bus_address[5] +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +000110 1 +000111 1 +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +100010 1 +100100 1 +100101 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n543_ $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n682_ $abc$8517$new_n681_ $abc$8517$new_n680_ $abc$8517$new_n1361_ +000100 1 +000101 1 +000110 1 +000111 1 +001100 1 +001101 1 +001110 1 +001111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011100 1 +011101 1 +011110 1 +011111 1 +100100 1 +100101 1 +100110 1 +100111 1 +101100 1 +101101 1 +101110 1 +101111 1 +110100 1 +110110 1 +111110 1 +111111 1 +.names $abc$8517$new_n1361_ $abc$8517$new_n665_ $abc$8517$new_n368_ $abc$8517$new_n549_ $abc$8517$new_n1365_ $abc$8517$new_n1367_ $abc$8517$new_n1362_ +100000 1 +100001 1 +100010 1 +100011 1 +100101 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101110 1 +101111 1 +.names $abc$8517$new_n663_ $abc$8517$new_n548_ $abc$8517$new_n664_ $abc$8517$new_n546_ $abc$8517$new_n662_ $abc$8517$new_n1362_ bus_address[6] +000000 1 +000010 1 +000100 1 +000101 1 +000110 1 +001000 1 +001010 1 +001100 1 +001101 1 +001110 1 +010000 1 +010010 1 +010100 1 +010101 1 +010110 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +100000 1 +100010 1 +100100 1 +100110 1 +100111 1 +101000 1 +101010 1 +101100 1 +101110 1 +101111 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111010 1 +111100 1 +111110 1 +111111 1 +.names $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n517_ $abc$8517$new_n516_ $abc$8517$new_n507_ $abc$8517$new_n506_ $abc$8517$new_n1364_ +010000 1 +010100 1 +011000 1 +011100 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110001 1 +110010 1 +110011 1 +.names $abc$8517$new_n1364_ $abc$8517$new_n356_ $abc$8517$new_n505_ $abc$8517$new_n504_ $abc$8517$new_n510_ $abc$8517$new_n509_ $abc$8517$new_n1365_ +000000 1 +000100 1 +001000 1 +001100 1 +100000 1 +100001 1 +100010 1 +100011 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n512_ $abc$8517$new_n511_ $abc$8517$new_n525_ $abc$8517$new_n524_ $abc$8517$new_n1366_ +010000 1 +010100 1 +011000 1 +011100 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110001 1 +110010 1 +110011 1 +.names $abc$8517$new_n1366_ $abc$8517$new_n356_ $abc$8517$new_n523_ $abc$8517$new_n522_ $abc$8517$new_n528_ $abc$8517$new_n527_ $abc$8517$new_n1367_ +000000 1 +000100 1 +001000 1 +001100 1 +100000 1 +100001 1 +100010 1 +100011 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n497_ $abc$8517$new_n486_ $abc$8517$new_n399_ $abc$8517$new_n479_ $abc$8517$new_n397_ $abc$8517$new_n489_ $abc$8517$new_n1368_ +000010 1 +000011 1 +001000 1 +001001 1 +001010 1 +001011 1 +001110 1 +001111 1 +010000 1 +010001 1 +010100 1 +010101 1 +010110 1 +010111 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +110001 1 +110011 1 +110101 1 +110111 1 +111001 1 +111011 1 +111101 1 +111111 1 +.names $abc$8517$new_n712_ $abc$8517$new_n1368_ $abc$8517$new_n497_ $abc$8517$new_n701_ $abc$8517$new_n548_ $abc$8517$new_n711_ $abc$8517$new_n1369_ +001000 1 +001001 1 +001010 1 +001100 1 +001101 1 +001110 1 +010000 1 +010001 1 +010010 1 +010100 1 +010101 1 +010110 1 +011100 1 +011101 1 +011110 1 +101000 1 +101001 1 +101011 1 +101100 1 +101101 1 +101111 1 +110000 1 +110001 1 +110011 1 +110100 1 +110101 1 +110111 1 +111000 1 +111001 1 +111011 1 +.names $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n571_ $abc$8517$new_n556_ $abc$8517$new_n572_ $abc$8517$new_n557_ $abc$8517$new_n1371_ +000000 1 +000010 1 +000100 1 +000110 1 +001000 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +011000 1 +011001 1 +011010 1 +011011 1 +100000 1 +100001 1 +100100 1 +100101 1 +101000 1 +101001 1 +101100 1 +101101 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +.names $abc$8517$new_n724_ $abc$8517$new_n720_ $abc$8517$new_n717_ $abc$8517$new_n549_ $abc$8517$new_n1371_ $abc$8517$new_n345_ $abc$8517$new_n1372_ +100001 1 +100011 1 +100101 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +.names $abc$8517$new_n1372_ $abc$8517$new_n715_ $abc$8517$new_n406_ $abc$8517$new_n714_ $abc$8517$new_n709_ $abc$8517$new_n546_ bus_address[9] +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +000110 1 +000111 1 +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +100011 1 +100101 1 +100111 1 +101001 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n620_ $abc$8517$new_n605_ $abc$8517$new_n621_ $abc$8517$new_n606_ $abc$8517$new_n1374_ +000000 1 +000010 1 +000100 1 +000110 1 +001000 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +011000 1 +011001 1 +011010 1 +011011 1 +100000 1 +100001 1 +100100 1 +100101 1 +101000 1 +101001 1 +101100 1 +101101 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +.names $abc$8517$new_n754_ $abc$8517$new_n752_ $abc$8517$new_n755_ $abc$8517$new_n756_ $abc$8517$new_n745_ $abc$8517$new_n548_ $abc$8517$new_n1375_ +100000 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101100 1 +101110 1 +.names $abc$8517$new_n1375_ $abc$8517$new_n746_ $abc$8517$new_n1374_ $abc$8517$new_n549_ $abc$8517$new_n1376_ +1000 1 +1001 1 +1010 1 +.names $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n508_ $abc$8517$new_n526_ $abc$8517$new_n521_ $abc$8517$new_n531_ $abc$8517$new_n1377_ +000000 1 +000010 1 +000100 1 +000110 1 +001000 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +011000 1 +011001 1 +011010 1 +011011 1 +100000 1 +100001 1 +100100 1 +100101 1 +101000 1 +101001 1 +101100 1 +101101 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +.names $abc$8517$new_n768_ $abc$8517$new_n772_ $abc$8517$new_n770_ $abc$8517$new_n769_ $abc$8517$new_n711_ $abc$8517$new_n548_ $abc$8517$new_n1378_ +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +100111 1 +101000 1 +101010 1 +101100 1 +101101 1 +101110 1 +110000 1 +110010 1 +110100 1 +110110 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111110 1 +111111 1 +.names $abc$8517$new_n1378_ $abc$8517$new_n763_ $abc$8517$new_n766_ $abc$8517$new_n549_ $abc$8517$new_n543_ $abc$8517$new_n1377_ $abc$8517$new_n1379_ +100000 1 +100001 1 +100100 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101110 1 +.names $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n572_ $abc$8517$new_n557_ $abc$8517$new_n556_ $abc$8517$new_n649_ $abc$8517$new_n1380_ +000000 1 +000010 1 +000100 1 +000110 1 +001000 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +011000 1 +011001 1 +011010 1 +011011 1 +100000 1 +100001 1 +100100 1 +100101 1 +101000 1 +101001 1 +101100 1 +101101 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +.names $abc$8517$new_n782_ $abc$8517$new_n781_ $abc$8517$new_n777_ $abc$8517$new_n549_ $abc$8517$new_n543_ $abc$8517$new_n1380_ $abc$8517$new_n1381_ +100000 1 +100001 1 +100100 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101110 1 +.names $abc$8517$new_n1381_ $abc$8517$new_n382_ $abc$8517$new_n783_ $abc$8517$new_n775_ $abc$8517$new_n548_ $abc$8517$new_n1382_ +10000 1 +10010 1 +10011 1 +10100 1 +10101 1 +10110 1 +10111 1 +11000 1 +11001 1 +11010 1 +11100 1 +11110 1 +.names $abc$8517$new_n368_ $abc$8517$new_n345_ $abc$8517$new_n362_ $abc$8517$new_n837_ $abc$8517$new_n838_ $abc$8517$new_n778_ $abc$8517$new_n1383_ +010000 1 +010001 1 +010010 1 +010011 1 +011000 1 +011001 1 +011010 1 +011011 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110010 1 +110100 1 +110110 1 +111000 1 +111001 1 +111100 1 +111101 1 +.names $abc$8517$new_n1383_ $abc$8517$new_n820_ $abc$8517$new_n345_ $abc$8517$new_n840_ $abc$8517$new_n554_ $abc$8517$new_n581_ $abc$8517$new_n1384_ +000100 1 +000101 1 +000110 1 +000111 1 +001110 1 +001111 1 +010100 1 +010101 1 +010110 1 +010111 1 +011110 1 +011111 1 +100100 1 +100101 1 +100110 1 +100111 1 +101110 1 +101111 1 +110100 1 +110110 1 +.names $abc$8517$new_n368_ $abc$8517$new_n820_ $abc$8517$new_n588_ $abc$8517$new_n345_ $abc$8517$new_n734_ $abc$8517$new_n850_ $abc$8517$new_n1385_ +000100 1 +000101 1 +000110 1 +000111 1 +010110 1 +010111 1 +100100 1 +100101 1 +100110 1 +100111 1 +110000 1 +110001 1 +110010 1 +110011 1 +110101 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +.names $abc$8517$new_n853_ $abc$8517$new_n345_ $abc$8517$new_n845_ $abc$8517$new_n1385_ $abc$8517$new_n600_ $abc$8517$new_n583_ $abc$8517$new_n1386_ +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +110100 1 +110110 1 +.names $abc$8517$new_n546_ $abc$8517$new_n843_ $abc$8517$new_n316_ $abc$8517$new_n1386_ bus_address[18] +0000 1 +0010 1 +0100 1 +0110 1 +1000 1 +1010 1 +1011 1 +1100 1 +1101 1 +1110 1 +.names $abc$8517$new_n368_ $abc$8517$new_n543_ $abc$8517$new_n874_ $abc$8517$new_n819_ $abc$8517$new_n634_ $abc$8517$new_n877_ $abc$8517$new_n1388_ +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +000110 1 +000111 1 +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001110 1 +001111 1 +010001 1 +010011 1 +010101 1 +010111 1 +011001 1 +011011 1 +011101 1 +011111 1 +100000 1 +100001 1 +100010 1 +100011 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101110 1 +101111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111110 1 +111111 1 +.names $abc$8517$new_n1388_ $abc$8517$new_n878_ $abc$8517$new_n879_ $abc$8517$new_n869_ $abc$8517$new_n333_ $abc$8517$new_n548_ $abc$8517$new_n1389_ +110000 1 +110001 1 +110010 1 +110100 1 +110110 1 +110111 1 +.names $abc$8517$new_n413_ $abc$8517$new_n329_ $abc$8517$new_n497_ $abc$8517$new_n552_ $abc$8517$new_n547_ $abc$8517$new_n478_ $abc$8517$new_n1390_ +000000 1 +000010 1 +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +011000 1 +011001 1 +011100 1 +011101 1 +100000 1 +100001 1 +101000 1 +101001 1 +101100 1 +101101 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n1390_ $abc$8517$new_n901_ $abc$8517$new_n897_ $abc$8517$new_n896_ $abc$8517$new_n894_ $abc$8517$new_n811_ bus_address[22] +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +000110 1 +000111 1 +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n345_ $abc$8517$new_n717_ $abc$8517$new_n933_ $abc$8517$new_n543_ $abc$8517$new_n568_ $abc$8517$new_n434_ $abc$8517$new_n1392_ +000000 1 +000001 1 +000011 1 +001000 1 +001001 1 +001011 1 +001100 1 +001101 1 +001111 1 +010000 1 +010001 1 +010011 1 +011000 1 +011001 1 +011011 1 +011100 1 +011101 1 +011111 1 +110000 1 +110001 1 +110010 1 +110011 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n936_ $abc$8517$new_n1392_ $abc$8517$new_n932_ $abc$8517$new_n929_ $abc$8517$new_n546_ $abc$8517$new_n927_ bus_address[25] +000000 1 +000001 1 +000010 1 +000011 1 +000100 1 +000101 1 +000110 1 +000111 1 +001000 1 +001001 1 +001010 1 +001011 1 +001100 1 +001101 1 +001110 1 +001111 1 +010000 1 +010001 1 +010010 1 +010011 1 +010100 1 +010101 1 +010110 1 +010111 1 +011000 1 +011001 1 +011010 1 +011011 1 +011100 1 +011101 1 +011110 1 +011111 1 +100000 1 +100001 1 +100010 1 +100011 1 +100100 1 +100101 1 +100110 1 +100111 1 +101000 1 +101001 1 +101010 1 +101011 1 +101100 1 +101101 1 +101110 1 +101111 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n820_ $abc$8517$new_n368_ $abc$8517$new_n863_ $abc$8517$new_n362_ $abc$8517$new_n612_ $abc$8517$new_n1394_ +10011 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +.names $abc$8517$new_n345_ $abc$8517$new_n748_ $abc$8517$new_n692_ $abc$8517$new_n1394_ $abc$8517$new_n719_ $abc$8517$new_n747_ $abc$8517$new_n1395_ +000000 1 +000001 1 +000010 1 +000011 1 +001000 1 +001001 1 +001010 1 +001011 1 +010000 1 +010001 1 +010010 1 +010011 1 +011000 1 +011001 1 +011010 1 +011011 1 +100000 1 +100001 1 +100011 1 +100100 1 +100101 1 +100111 1 +.names $abc$8517$new_n959_ $abc$8517$new_n1395_ $abc$8517$new_n956_ $abc$8517$new_n811_ $abc$8517$new_n548_ $abc$8517$new_n952_ $abc$8517$new_n1396_ +110000 1 +110001 1 +110010 1 +.names $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n623_ $abc$8517$new_n620_ $abc$8517$new_n624_ $abc$8517$new_n621_ $abc$8517$new_n1397_ +000000 1 +000010 1 +000100 1 +000110 1 +001000 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +011000 1 +011001 1 +011010 1 +011011 1 +100000 1 +100001 1 +100100 1 +100101 1 +101000 1 +101001 1 +101100 1 +101101 1 +111000 1 +111001 1 +111010 1 +111011 1 +111100 1 +111101 1 +111110 1 +111111 1 +.names $abc$8517$new_n1355_ $abc$8517$new_n603_ $abc$8517$new_n1397_ $abc$8517$new_n549_ bus_address[3] +0000 1 +0001 1 +0010 1 +0011 1 +0100 1 +0101 1 +0110 1 +0111 1 +1011 1 +1100 1 +1101 1 +1110 1 +1111 1 +.names $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n503_ $abc$8517$new_n521_ $abc$8517$new_n508_ $abc$8517$new_n526_ $abc$8517$new_n1399_ +000000 1 +000010 1 +000100 1 +000110 1 +001000 1 +001010 1 +001100 1 +001110 1 +010000 1 +010001 1 +010010 1 +010011 1 +011000 1 +011001 1 +011010 1 +011011 1 +100000 1 +100001 1 +100100 1 +100101 1 +101000 1 +101001 1 +101100 1 +101101 1 +110000 1 +110001 1 +110010 1 +110011 1 +110100 1 +110101 1 +110110 1 +110111 1 +.names $abc$8517$new_n1369_ $abc$8517$new_n707_ $abc$8517$new_n703_ $abc$8517$new_n1399_ $abc$8517$new_n549_ bus_address[8] +00000 1 +00001 1 +00010 1 +00011 1 +00100 1 +00101 1 +00110 1 +00111 1 +01000 1 +01001 1 +01010 1 +01011 1 +01100 1 +01101 1 +01110 1 +01111 1 +10011 1 +10100 1 +10101 1 +10110 1 +10111 1 +11000 1 +11001 1 +11010 1 +11011 1 +11100 1 +11101 1 +11110 1 +11111 1 +.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][0] singlecycle_datapath.program_counter.value[0] re clock 2 +.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][1] singlecycle_datapath.program_counter.value[1] re clock 2 +.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][2] singlecycle_datapath.program_counter.value[2] re clock 2 +.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][3] singlecycle_datapath.program_counter.value[3] re clock 2 +.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][4] singlecycle_datapath.program_counter.value[4] re clock 2 +.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][5] singlecycle_datapath.program_counter.value[5] re clock 2 +.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][6] singlecycle_datapath.program_counter.value[6] re clock 2 +.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][7] singlecycle_datapath.program_counter.value[7] re clock 2 +.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][8] singlecycle_datapath.program_counter.value[8] re clock 2 +.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][9] singlecycle_datapath.program_counter.value[9] re clock 2 +.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][10] singlecycle_datapath.program_counter.value[10] re clock 2 +.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][11] singlecycle_datapath.program_counter.value[11] re clock 2 +.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][12] singlecycle_datapath.program_counter.value[12] re clock 2 +.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][13] singlecycle_datapath.program_counter.value[13] re clock 2 +.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][14] singlecycle_datapath.program_counter.value[14] re clock 2 +.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][15] singlecycle_datapath.program_counter.value[15] re clock 2 +.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][16] singlecycle_datapath.program_counter.value[16] re clock 2 +.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][17] singlecycle_datapath.program_counter.value[17] re clock 2 +.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][18] singlecycle_datapath.program_counter.value[18] re clock 2 +.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][19] singlecycle_datapath.program_counter.value[19] re clock 2 +.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][20] singlecycle_datapath.program_counter.value[20] re clock 2 +.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][21] singlecycle_datapath.program_counter.value[21] re clock 2 +.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][22] singlecycle_datapath.program_counter.value[22] re clock 2 +.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][23] singlecycle_datapath.program_counter.value[23] re clock 2 +.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][24] singlecycle_datapath.program_counter.value[24] re clock 2 +.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][25] singlecycle_datapath.program_counter.value[25] re clock 2 +.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][26] singlecycle_datapath.program_counter.value[26] re clock 2 +.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][27] singlecycle_datapath.program_counter.value[27] re clock 2 +.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][28] singlecycle_datapath.program_counter.value[28] re clock 2 +.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][29] singlecycle_datapath.program_counter.value[29] re clock 2 +.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][30] singlecycle_datapath.program_counter.value[30] re clock 2 +.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][31] singlecycle_datapath.program_counter.value[31] re clock 2 +.names bus_address[0] address[0] +1 1 +.names bus_address[1] address[1] +1 1 +.names bus_address[2] address[2] +1 1 +.names bus_address[3] address[3] +1 1 +.names bus_address[4] address[4] +1 1 +.names bus_address[5] address[5] +1 1 +.names bus_address[6] address[6] +1 1 +.names bus_address[7] address[7] +1 1 +.names bus_address[8] address[8] +1 1 +.names bus_address[9] address[9] +1 1 +.names bus_address[10] address[10] +1 1 +.names bus_address[11] address[11] +1 1 +.names bus_address[12] address[12] +1 1 +.names bus_address[13] address[13] +1 1 +.names bus_address[14] address[14] +1 1 +.names bus_address[15] address[15] +1 1 +.names bus_address[16] address[16] +1 1 +.names bus_address[17] address[17] +1 1 +.names bus_address[18] address[18] +1 1 +.names bus_address[19] address[19] +1 1 +.names bus_address[20] address[20] +1 1 +.names bus_address[21] address[21] +1 1 +.names bus_address[22] address[22] +1 1 +.names bus_address[23] address[23] +1 1 +.names bus_address[24] address[24] +1 1 +.names bus_address[25] address[25] +1 1 +.names bus_address[26] address[26] +1 1 +.names bus_address[27] address[27] +1 1 +.names bus_address[28] address[28] +1 1 +.names bus_address[29] address[29] +1 1 +.names bus_address[30] address[30] +1 1 +.names bus_address[31] address[31] +1 1 +.names $false alu_function[4] +1 1 +.names bus_address[0] data_memory_interface.address[0] +1 1 +.names bus_address[1] data_memory_interface.address[1] +1 1 +.names bus_address[2] data_memory_interface.address[2] +1 1 +.names bus_address[3] data_memory_interface.address[3] +1 1 +.names bus_address[4] data_memory_interface.address[4] +1 1 +.names bus_address[5] data_memory_interface.address[5] +1 1 +.names bus_address[6] data_memory_interface.address[6] +1 1 +.names bus_address[7] data_memory_interface.address[7] +1 1 +.names bus_address[8] data_memory_interface.address[8] +1 1 +.names bus_address[9] data_memory_interface.address[9] +1 1 +.names bus_address[10] data_memory_interface.address[10] +1 1 +.names bus_address[11] data_memory_interface.address[11] +1 1 +.names bus_address[12] data_memory_interface.address[12] +1 1 +.names bus_address[13] data_memory_interface.address[13] +1 1 +.names bus_address[14] data_memory_interface.address[14] +1 1 +.names bus_address[15] data_memory_interface.address[15] +1 1 +.names bus_address[16] data_memory_interface.address[16] +1 1 +.names bus_address[17] data_memory_interface.address[17] +1 1 +.names bus_address[18] data_memory_interface.address[18] +1 1 +.names bus_address[19] data_memory_interface.address[19] +1 1 +.names bus_address[20] data_memory_interface.address[20] +1 1 +.names bus_address[21] data_memory_interface.address[21] +1 1 +.names bus_address[22] data_memory_interface.address[22] +1 1 +.names bus_address[23] data_memory_interface.address[23] +1 1 +.names bus_address[24] data_memory_interface.address[24] +1 1 +.names bus_address[25] data_memory_interface.address[25] +1 1 +.names bus_address[26] data_memory_interface.address[26] +1 1 +.names bus_address[27] data_memory_interface.address[27] +1 1 +.names bus_address[28] data_memory_interface.address[28] +1 1 +.names bus_address[29] data_memory_interface.address[29] +1 1 +.names bus_address[30] data_memory_interface.address[30] +1 1 +.names bus_address[31] data_memory_interface.address[31] +1 1 +.names bus_address[0] data_memory_interface.bus_address[0] +1 1 +.names bus_address[1] data_memory_interface.bus_address[1] +1 1 +.names bus_address[2] data_memory_interface.bus_address[2] +1 1 +.names bus_address[3] data_memory_interface.bus_address[3] +1 1 +.names bus_address[4] data_memory_interface.bus_address[4] +1 1 +.names bus_address[5] data_memory_interface.bus_address[5] +1 1 +.names bus_address[6] data_memory_interface.bus_address[6] +1 1 +.names bus_address[7] data_memory_interface.bus_address[7] +1 1 +.names bus_address[8] data_memory_interface.bus_address[8] +1 1 +.names bus_address[9] data_memory_interface.bus_address[9] +1 1 +.names bus_address[10] data_memory_interface.bus_address[10] +1 1 +.names bus_address[11] data_memory_interface.bus_address[11] +1 1 +.names bus_address[12] data_memory_interface.bus_address[12] +1 1 +.names bus_address[13] data_memory_interface.bus_address[13] +1 1 +.names bus_address[14] data_memory_interface.bus_address[14] +1 1 +.names bus_address[15] data_memory_interface.bus_address[15] +1 1 +.names bus_address[16] data_memory_interface.bus_address[16] +1 1 +.names bus_address[17] data_memory_interface.bus_address[17] +1 1 +.names bus_address[18] data_memory_interface.bus_address[18] +1 1 +.names bus_address[19] data_memory_interface.bus_address[19] +1 1 +.names bus_address[20] data_memory_interface.bus_address[20] +1 1 +.names bus_address[21] data_memory_interface.bus_address[21] +1 1 +.names bus_address[22] data_memory_interface.bus_address[22] +1 1 +.names bus_address[23] data_memory_interface.bus_address[23] +1 1 +.names bus_address[24] data_memory_interface.bus_address[24] +1 1 +.names bus_address[25] data_memory_interface.bus_address[25] +1 1 +.names bus_address[26] data_memory_interface.bus_address[26] +1 1 +.names bus_address[27] data_memory_interface.bus_address[27] +1 1 +.names bus_address[28] data_memory_interface.bus_address[28] +1 1 +.names bus_address[29] data_memory_interface.bus_address[29] +1 1 +.names bus_address[30] data_memory_interface.bus_address[30] +1 1 +.names bus_address[31] data_memory_interface.bus_address[31] +1 1 +.names bus_byte_enable[0] data_memory_interface.bus_byte_enable[0] +1 1 +.names bus_byte_enable[1] data_memory_interface.bus_byte_enable[1] +1 1 +.names bus_byte_enable[2] data_memory_interface.bus_byte_enable[2] +1 1 +.names bus_byte_enable[3] data_memory_interface.bus_byte_enable[3] +1 1 +.names bus_read_data[0] data_memory_interface.bus_read_data[0] +1 1 +.names bus_read_data[1] data_memory_interface.bus_read_data[1] +1 1 +.names bus_read_data[2] data_memory_interface.bus_read_data[2] +1 1 +.names bus_read_data[3] data_memory_interface.bus_read_data[3] +1 1 +.names bus_read_data[4] data_memory_interface.bus_read_data[4] +1 1 +.names bus_read_data[5] data_memory_interface.bus_read_data[5] +1 1 +.names bus_read_data[6] data_memory_interface.bus_read_data[6] +1 1 +.names bus_read_data[7] data_memory_interface.bus_read_data[7] +1 1 +.names bus_read_data[8] data_memory_interface.bus_read_data[8] +1 1 +.names bus_read_data[9] data_memory_interface.bus_read_data[9] +1 1 +.names bus_read_data[10] data_memory_interface.bus_read_data[10] +1 1 +.names bus_read_data[11] data_memory_interface.bus_read_data[11] +1 1 +.names bus_read_data[12] data_memory_interface.bus_read_data[12] +1 1 +.names bus_read_data[13] data_memory_interface.bus_read_data[13] +1 1 +.names bus_read_data[14] data_memory_interface.bus_read_data[14] +1 1 +.names bus_read_data[15] data_memory_interface.bus_read_data[15] +1 1 +.names bus_read_data[16] data_memory_interface.bus_read_data[16] +1 1 +.names bus_read_data[17] data_memory_interface.bus_read_data[17] +1 1 +.names bus_read_data[18] data_memory_interface.bus_read_data[18] +1 1 +.names bus_read_data[19] data_memory_interface.bus_read_data[19] +1 1 +.names bus_read_data[20] data_memory_interface.bus_read_data[20] +1 1 +.names bus_read_data[21] data_memory_interface.bus_read_data[21] +1 1 +.names bus_read_data[22] data_memory_interface.bus_read_data[22] +1 1 +.names bus_read_data[23] data_memory_interface.bus_read_data[23] +1 1 +.names bus_read_data[24] data_memory_interface.bus_read_data[24] +1 1 +.names bus_read_data[25] data_memory_interface.bus_read_data[25] +1 1 +.names bus_read_data[26] data_memory_interface.bus_read_data[26] +1 1 +.names bus_read_data[27] data_memory_interface.bus_read_data[27] +1 1 +.names bus_read_data[28] data_memory_interface.bus_read_data[28] +1 1 +.names bus_read_data[29] data_memory_interface.bus_read_data[29] +1 1 +.names bus_read_data[30] data_memory_interface.bus_read_data[30] +1 1 +.names bus_read_data[31] data_memory_interface.bus_read_data[31] +1 1 +.names bus_read_enable data_memory_interface.bus_read_enable +1 1 +.names bus_write_data[0] data_memory_interface.bus_write_data[0] +1 1 +.names bus_write_data[1] data_memory_interface.bus_write_data[1] +1 1 +.names bus_write_data[2] data_memory_interface.bus_write_data[2] +1 1 +.names bus_write_data[3] data_memory_interface.bus_write_data[3] +1 1 +.names bus_write_data[4] data_memory_interface.bus_write_data[4] +1 1 +.names bus_write_data[5] data_memory_interface.bus_write_data[5] +1 1 +.names bus_write_data[6] data_memory_interface.bus_write_data[6] +1 1 +.names bus_write_data[7] data_memory_interface.bus_write_data[7] +1 1 +.names bus_write_data[8] data_memory_interface.bus_write_data[8] +1 1 +.names bus_write_data[9] data_memory_interface.bus_write_data[9] +1 1 +.names bus_write_data[10] data_memory_interface.bus_write_data[10] +1 1 +.names bus_write_data[11] data_memory_interface.bus_write_data[11] +1 1 +.names bus_write_data[12] data_memory_interface.bus_write_data[12] +1 1 +.names bus_write_data[13] data_memory_interface.bus_write_data[13] +1 1 +.names bus_write_data[14] data_memory_interface.bus_write_data[14] +1 1 +.names bus_write_data[15] data_memory_interface.bus_write_data[15] +1 1 +.names bus_write_data[16] data_memory_interface.bus_write_data[16] +1 1 +.names bus_write_data[17] data_memory_interface.bus_write_data[17] +1 1 +.names bus_write_data[18] data_memory_interface.bus_write_data[18] +1 1 +.names bus_write_data[19] data_memory_interface.bus_write_data[19] +1 1 +.names bus_write_data[20] data_memory_interface.bus_write_data[20] +1 1 +.names bus_write_data[21] data_memory_interface.bus_write_data[21] +1 1 +.names bus_write_data[22] data_memory_interface.bus_write_data[22] +1 1 +.names bus_write_data[23] data_memory_interface.bus_write_data[23] +1 1 +.names bus_write_data[24] data_memory_interface.bus_write_data[24] +1 1 +.names bus_write_data[25] data_memory_interface.bus_write_data[25] +1 1 +.names bus_write_data[26] data_memory_interface.bus_write_data[26] +1 1 +.names bus_write_data[27] data_memory_interface.bus_write_data[27] +1 1 +.names bus_write_data[28] data_memory_interface.bus_write_data[28] +1 1 +.names bus_write_data[29] data_memory_interface.bus_write_data[29] +1 1 +.names bus_write_data[30] data_memory_interface.bus_write_data[30] +1 1 +.names bus_write_data[31] data_memory_interface.bus_write_data[31] +1 1 +.names bus_write_enable data_memory_interface.bus_write_enable +1 1 +.names clock data_memory_interface.clock +1 1 +.names inst[12] data_memory_interface.data_format[0] +1 1 +.names inst[13] data_memory_interface.data_format[1] +1 1 +.names inst[14] data_memory_interface.data_format[2] +1 1 +.names bus_read_enable data_memory_interface.read_enable +1 1 +.names rs2_data[0] data_memory_interface.write_data[0] +1 1 +.names rs2_data[1] data_memory_interface.write_data[1] +1 1 +.names rs2_data[2] data_memory_interface.write_data[2] +1 1 +.names rs2_data[3] data_memory_interface.write_data[3] +1 1 +.names rs2_data[4] data_memory_interface.write_data[4] +1 1 +.names rs2_data[5] data_memory_interface.write_data[5] +1 1 +.names rs2_data[6] data_memory_interface.write_data[6] +1 1 +.names rs2_data[7] data_memory_interface.write_data[7] +1 1 +.names rs2_data[8] data_memory_interface.write_data[8] +1 1 +.names rs2_data[9] data_memory_interface.write_data[9] +1 1 +.names rs2_data[10] data_memory_interface.write_data[10] +1 1 +.names rs2_data[11] data_memory_interface.write_data[11] +1 1 +.names rs2_data[12] data_memory_interface.write_data[12] +1 1 +.names rs2_data[13] data_memory_interface.write_data[13] +1 1 +.names rs2_data[14] data_memory_interface.write_data[14] +1 1 +.names rs2_data[15] data_memory_interface.write_data[15] +1 1 +.names rs2_data[16] data_memory_interface.write_data[16] +1 1 +.names rs2_data[17] data_memory_interface.write_data[17] +1 1 +.names rs2_data[18] data_memory_interface.write_data[18] +1 1 +.names rs2_data[19] data_memory_interface.write_data[19] +1 1 +.names rs2_data[20] data_memory_interface.write_data[20] +1 1 +.names rs2_data[21] data_memory_interface.write_data[21] +1 1 +.names rs2_data[22] data_memory_interface.write_data[22] +1 1 +.names rs2_data[23] data_memory_interface.write_data[23] +1 1 +.names rs2_data[24] data_memory_interface.write_data[24] +1 1 +.names rs2_data[25] data_memory_interface.write_data[25] +1 1 +.names rs2_data[26] data_memory_interface.write_data[26] +1 1 +.names rs2_data[27] data_memory_interface.write_data[27] +1 1 +.names rs2_data[28] data_memory_interface.write_data[28] +1 1 +.names rs2_data[29] data_memory_interface.write_data[29] +1 1 +.names rs2_data[30] data_memory_interface.write_data[30] +1 1 +.names rs2_data[31] data_memory_interface.write_data[31] +1 1 +.names bus_write_enable data_memory_interface.write_enable +1 1 +.names inst[12] inst_funct3[0] +1 1 +.names inst[13] inst_funct3[1] +1 1 +.names inst[14] inst_funct3[2] +1 1 +.names inst[25] inst_funct7[0] +1 1 +.names inst[26] inst_funct7[1] +1 1 +.names inst[27] inst_funct7[2] +1 1 +.names inst[28] inst_funct7[3] +1 1 +.names inst[29] inst_funct7[4] +1 1 +.names inst[30] inst_funct7[5] +1 1 +.names inst[31] inst_funct7[6] +1 1 +.names inst[0] inst_opcode[0] +1 1 +.names inst[1] inst_opcode[1] +1 1 +.names inst[2] inst_opcode[2] +1 1 +.names inst[3] inst_opcode[3] +1 1 +.names inst[4] inst_opcode[4] +1 1 +.names inst[5] inst_opcode[5] +1 1 +.names inst[6] inst_opcode[6] +1 1 +.names singlecycle_datapath.program_counter.value[0] pc[0] +1 1 +.names singlecycle_datapath.program_counter.value[1] pc[1] +1 1 +.names singlecycle_datapath.program_counter.value[2] pc[2] +1 1 +.names singlecycle_datapath.program_counter.value[3] pc[3] +1 1 +.names singlecycle_datapath.program_counter.value[4] pc[4] +1 1 +.names singlecycle_datapath.program_counter.value[5] pc[5] +1 1 +.names singlecycle_datapath.program_counter.value[6] pc[6] +1 1 +.names singlecycle_datapath.program_counter.value[7] pc[7] +1 1 +.names singlecycle_datapath.program_counter.value[8] pc[8] +1 1 +.names singlecycle_datapath.program_counter.value[9] pc[9] +1 1 +.names singlecycle_datapath.program_counter.value[10] pc[10] +1 1 +.names singlecycle_datapath.program_counter.value[11] pc[11] +1 1 +.names singlecycle_datapath.program_counter.value[12] pc[12] +1 1 +.names singlecycle_datapath.program_counter.value[13] pc[13] +1 1 +.names singlecycle_datapath.program_counter.value[14] pc[14] +1 1 +.names singlecycle_datapath.program_counter.value[15] pc[15] +1 1 +.names singlecycle_datapath.program_counter.value[16] pc[16] +1 1 +.names singlecycle_datapath.program_counter.value[17] pc[17] +1 1 +.names singlecycle_datapath.program_counter.value[18] pc[18] +1 1 +.names singlecycle_datapath.program_counter.value[19] pc[19] +1 1 +.names singlecycle_datapath.program_counter.value[20] pc[20] +1 1 +.names singlecycle_datapath.program_counter.value[21] pc[21] +1 1 +.names singlecycle_datapath.program_counter.value[22] pc[22] +1 1 +.names singlecycle_datapath.program_counter.value[23] pc[23] +1 1 +.names singlecycle_datapath.program_counter.value[24] pc[24] +1 1 +.names singlecycle_datapath.program_counter.value[25] pc[25] +1 1 +.names singlecycle_datapath.program_counter.value[26] pc[26] +1 1 +.names singlecycle_datapath.program_counter.value[27] pc[27] +1 1 +.names singlecycle_datapath.program_counter.value[28] pc[28] +1 1 +.names singlecycle_datapath.program_counter.value[29] pc[29] +1 1 +.names singlecycle_datapath.program_counter.value[30] pc[30] +1 1 +.names singlecycle_datapath.program_counter.value[31] pc[31] +1 1 +.names $true pc_write_enable +1 1 +.names inst[7] rd_address[0] +1 1 +.names $false rd_address[1] +1 1 +.names $false rd_address[2] +1 1 +.names $false rd_address[3] +1 1 +.names $false rd_address[4] +1 1 +.names bus_read_enable read_enable +1 1 +.names inst[15] rs1_address[0] +1 1 +.names $false rs1_address[1] +1 1 +.names $false rs1_address[2] +1 1 +.names $false rs1_address[3] +1 1 +.names $false rs1_address[4] +1 1 +.names inst[20] rs2_address[0] +1 1 +.names $false rs2_address[1] +1 1 +.names $false rs2_address[2] +1 1 +.names $false rs2_address[3] +1 1 +.names $false rs2_address[4] +1 1 +.names alu_function[0] singlecycle_ctlpath.alu_control.alu_function[0] +1 1 +.names alu_function[1] singlecycle_ctlpath.alu_control.alu_function[1] +1 1 +.names alu_function[2] singlecycle_ctlpath.alu_control.alu_function[2] +1 1 +.names alu_function[3] singlecycle_ctlpath.alu_control.alu_function[3] +1 1 +.names $false singlecycle_ctlpath.alu_control.alu_function[4] +1 1 +.names singlecycle_ctlpath.alu_control.branch_funct[2] singlecycle_ctlpath.alu_control.branch_funct[1] +1 1 +.names inst[12] singlecycle_ctlpath.alu_control.inst_funct3[0] +1 1 +.names inst[13] singlecycle_ctlpath.alu_control.inst_funct3[1] +1 1 +.names inst[14] singlecycle_ctlpath.alu_control.inst_funct3[2] +1 1 +.names inst[25] singlecycle_ctlpath.alu_control.inst_funct7[0] +1 1 +.names inst[26] singlecycle_ctlpath.alu_control.inst_funct7[1] +1 1 +.names inst[27] singlecycle_ctlpath.alu_control.inst_funct7[2] +1 1 +.names inst[28] singlecycle_ctlpath.alu_control.inst_funct7[3] +1 1 +.names inst[29] singlecycle_ctlpath.alu_control.inst_funct7[4] +1 1 +.names inst[30] singlecycle_ctlpath.alu_control.inst_funct7[5] +1 1 +.names inst[31] singlecycle_ctlpath.alu_control.inst_funct7[6] +1 1 +.names $false singlecycle_ctlpath.alu_control.op_funct[4] +1 1 +.names $false singlecycle_ctlpath.alu_control.op_imm_funct[4] +1 1 +.names alu_function[0] singlecycle_ctlpath.alu_function[0] +1 1 +.names alu_function[1] singlecycle_ctlpath.alu_function[1] +1 1 +.names alu_function[2] singlecycle_ctlpath.alu_function[2] +1 1 +.names alu_function[3] singlecycle_ctlpath.alu_function[3] +1 1 +.names $false singlecycle_ctlpath.alu_function[4] +1 1 +.names inst[12] singlecycle_ctlpath.control_transfer.inst_funct3[0] +1 1 +.names inst[13] singlecycle_ctlpath.control_transfer.inst_funct3[1] +1 1 +.names inst[14] singlecycle_ctlpath.control_transfer.inst_funct3[2] +1 1 +.names bus_read_enable singlecycle_ctlpath.data_mem_read_enable +1 1 +.names bus_write_enable singlecycle_ctlpath.data_mem_write_enable +1 1 +.names inst[12] singlecycle_ctlpath.inst_funct3[0] +1 1 +.names inst[13] singlecycle_ctlpath.inst_funct3[1] +1 1 +.names inst[14] singlecycle_ctlpath.inst_funct3[2] +1 1 +.names inst[25] singlecycle_ctlpath.inst_funct7[0] +1 1 +.names inst[26] singlecycle_ctlpath.inst_funct7[1] +1 1 +.names inst[27] singlecycle_ctlpath.inst_funct7[2] +1 1 +.names inst[28] singlecycle_ctlpath.inst_funct7[3] +1 1 +.names inst[29] singlecycle_ctlpath.inst_funct7[4] +1 1 +.names inst[30] singlecycle_ctlpath.inst_funct7[5] +1 1 +.names inst[31] singlecycle_ctlpath.inst_funct7[6] +1 1 +.names inst[0] singlecycle_ctlpath.inst_opcode[0] +1 1 +.names inst[1] singlecycle_ctlpath.inst_opcode[1] +1 1 +.names inst[2] singlecycle_ctlpath.inst_opcode[2] +1 1 +.names inst[3] singlecycle_ctlpath.inst_opcode[3] +1 1 +.names inst[4] singlecycle_ctlpath.inst_opcode[4] +1 1 +.names inst[5] singlecycle_ctlpath.inst_opcode[5] +1 1 +.names inst[6] singlecycle_ctlpath.inst_opcode[6] +1 1 +.names $true singlecycle_ctlpath.pc_write_enable +1 1 +.names $false singlecycle_ctlpath.reg_writeback_select[2] +1 1 +.names regfile_write_enable singlecycle_ctlpath.regfile_write_enable +1 1 +.names bus_read_enable singlecycle_ctlpath.singlecycle_control.data_mem_read_enable +1 1 +.names bus_write_enable singlecycle_ctlpath.singlecycle_control.data_mem_write_enable +1 1 +.names inst[0] singlecycle_ctlpath.singlecycle_control.inst_opcode[0] +1 1 +.names inst[1] singlecycle_ctlpath.singlecycle_control.inst_opcode[1] +1 1 +.names inst[2] singlecycle_ctlpath.singlecycle_control.inst_opcode[2] +1 1 +.names inst[3] singlecycle_ctlpath.singlecycle_control.inst_opcode[3] +1 1 +.names inst[4] singlecycle_ctlpath.singlecycle_control.inst_opcode[4] +1 1 +.names inst[5] singlecycle_ctlpath.singlecycle_control.inst_opcode[5] +1 1 +.names inst[6] singlecycle_ctlpath.singlecycle_control.inst_opcode[6] +1 1 +.names $true singlecycle_ctlpath.singlecycle_control.pc_write_enable +1 1 +.names $false singlecycle_ctlpath.singlecycle_control.reg_writeback_select[2] +1 1 +.names regfile_write_enable singlecycle_ctlpath.singlecycle_control.regfile_write_enable +1 1 +.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[0] +1 1 +.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[1] +1 1 +.names $true singlecycle_datapath.adder_pc_plus_4.operand_a[2] +1 1 +.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[3] +1 1 +.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[4] +1 1 +.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[5] +1 1 +.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[6] +1 1 +.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[7] +1 1 +.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[8] +1 1 +.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[9] +1 1 +.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[10] +1 1 +.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[11] +1 1 +.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[12] +1 1 +.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[13] +1 1 +.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[14] +1 1 +.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[15] +1 1 +.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[16] +1 1 +.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[17] +1 1 +.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[18] +1 1 +.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[19] +1 1 +.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[20] +1 1 +.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[21] +1 1 +.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[22] +1 1 +.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[23] +1 1 +.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[24] +1 1 +.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[25] +1 1 +.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[26] +1 1 +.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[27] +1 1 +.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[28] +1 1 +.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[29] +1 1 +.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[30] +1 1 +.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[31] +1 1 +.names singlecycle_datapath.program_counter.value[0] singlecycle_datapath.adder_pc_plus_4.operand_b[0] +1 1 +.names singlecycle_datapath.program_counter.value[1] singlecycle_datapath.adder_pc_plus_4.operand_b[1] +1 1 +.names singlecycle_datapath.program_counter.value[2] singlecycle_datapath.adder_pc_plus_4.operand_b[2] +1 1 +.names singlecycle_datapath.program_counter.value[3] singlecycle_datapath.adder_pc_plus_4.operand_b[3] +1 1 +.names singlecycle_datapath.program_counter.value[4] singlecycle_datapath.adder_pc_plus_4.operand_b[4] +1 1 +.names singlecycle_datapath.program_counter.value[5] singlecycle_datapath.adder_pc_plus_4.operand_b[5] +1 1 +.names singlecycle_datapath.program_counter.value[6] singlecycle_datapath.adder_pc_plus_4.operand_b[6] +1 1 +.names singlecycle_datapath.program_counter.value[7] singlecycle_datapath.adder_pc_plus_4.operand_b[7] +1 1 +.names singlecycle_datapath.program_counter.value[8] singlecycle_datapath.adder_pc_plus_4.operand_b[8] +1 1 +.names singlecycle_datapath.program_counter.value[9] singlecycle_datapath.adder_pc_plus_4.operand_b[9] +1 1 +.names singlecycle_datapath.program_counter.value[10] singlecycle_datapath.adder_pc_plus_4.operand_b[10] +1 1 +.names singlecycle_datapath.program_counter.value[11] singlecycle_datapath.adder_pc_plus_4.operand_b[11] +1 1 +.names singlecycle_datapath.program_counter.value[12] singlecycle_datapath.adder_pc_plus_4.operand_b[12] +1 1 +.names singlecycle_datapath.program_counter.value[13] singlecycle_datapath.adder_pc_plus_4.operand_b[13] +1 1 +.names singlecycle_datapath.program_counter.value[14] singlecycle_datapath.adder_pc_plus_4.operand_b[14] +1 1 +.names singlecycle_datapath.program_counter.value[15] singlecycle_datapath.adder_pc_plus_4.operand_b[15] +1 1 +.names singlecycle_datapath.program_counter.value[16] singlecycle_datapath.adder_pc_plus_4.operand_b[16] +1 1 +.names singlecycle_datapath.program_counter.value[17] singlecycle_datapath.adder_pc_plus_4.operand_b[17] +1 1 +.names singlecycle_datapath.program_counter.value[18] singlecycle_datapath.adder_pc_plus_4.operand_b[18] +1 1 +.names singlecycle_datapath.program_counter.value[19] singlecycle_datapath.adder_pc_plus_4.operand_b[19] +1 1 +.names singlecycle_datapath.program_counter.value[20] singlecycle_datapath.adder_pc_plus_4.operand_b[20] +1 1 +.names singlecycle_datapath.program_counter.value[21] singlecycle_datapath.adder_pc_plus_4.operand_b[21] +1 1 +.names singlecycle_datapath.program_counter.value[22] singlecycle_datapath.adder_pc_plus_4.operand_b[22] +1 1 +.names singlecycle_datapath.program_counter.value[23] singlecycle_datapath.adder_pc_plus_4.operand_b[23] +1 1 +.names singlecycle_datapath.program_counter.value[24] singlecycle_datapath.adder_pc_plus_4.operand_b[24] +1 1 +.names singlecycle_datapath.program_counter.value[25] singlecycle_datapath.adder_pc_plus_4.operand_b[25] +1 1 +.names singlecycle_datapath.program_counter.value[26] singlecycle_datapath.adder_pc_plus_4.operand_b[26] +1 1 +.names singlecycle_datapath.program_counter.value[27] singlecycle_datapath.adder_pc_plus_4.operand_b[27] +1 1 +.names singlecycle_datapath.program_counter.value[28] singlecycle_datapath.adder_pc_plus_4.operand_b[28] +1 1 +.names singlecycle_datapath.program_counter.value[29] singlecycle_datapath.adder_pc_plus_4.operand_b[29] +1 1 +.names singlecycle_datapath.program_counter.value[30] singlecycle_datapath.adder_pc_plus_4.operand_b[30] +1 1 +.names singlecycle_datapath.program_counter.value[31] singlecycle_datapath.adder_pc_plus_4.operand_b[31] +1 1 +.names singlecycle_datapath.program_counter.value[0] singlecycle_datapath.adder_pc_plus_4.result[0] +1 1 +.names singlecycle_datapath.program_counter.value[1] singlecycle_datapath.adder_pc_plus_4.result[1] +1 1 +.names singlecycle_datapath.program_counter.value[0] singlecycle_datapath.adder_pc_plus_immediate.operand_a[0] +1 1 +.names singlecycle_datapath.program_counter.value[1] singlecycle_datapath.adder_pc_plus_immediate.operand_a[1] +1 1 +.names singlecycle_datapath.program_counter.value[2] singlecycle_datapath.adder_pc_plus_immediate.operand_a[2] +1 1 +.names singlecycle_datapath.program_counter.value[3] singlecycle_datapath.adder_pc_plus_immediate.operand_a[3] +1 1 +.names singlecycle_datapath.program_counter.value[4] singlecycle_datapath.adder_pc_plus_immediate.operand_a[4] +1 1 +.names singlecycle_datapath.program_counter.value[5] singlecycle_datapath.adder_pc_plus_immediate.operand_a[5] +1 1 +.names singlecycle_datapath.program_counter.value[6] singlecycle_datapath.adder_pc_plus_immediate.operand_a[6] +1 1 +.names singlecycle_datapath.program_counter.value[7] singlecycle_datapath.adder_pc_plus_immediate.operand_a[7] +1 1 +.names singlecycle_datapath.program_counter.value[8] singlecycle_datapath.adder_pc_plus_immediate.operand_a[8] +1 1 +.names singlecycle_datapath.program_counter.value[9] singlecycle_datapath.adder_pc_plus_immediate.operand_a[9] +1 1 +.names singlecycle_datapath.program_counter.value[10] singlecycle_datapath.adder_pc_plus_immediate.operand_a[10] +1 1 +.names singlecycle_datapath.program_counter.value[11] singlecycle_datapath.adder_pc_plus_immediate.operand_a[11] +1 1 +.names singlecycle_datapath.program_counter.value[12] singlecycle_datapath.adder_pc_plus_immediate.operand_a[12] +1 1 +.names singlecycle_datapath.program_counter.value[13] singlecycle_datapath.adder_pc_plus_immediate.operand_a[13] +1 1 +.names singlecycle_datapath.program_counter.value[14] singlecycle_datapath.adder_pc_plus_immediate.operand_a[14] +1 1 +.names singlecycle_datapath.program_counter.value[15] singlecycle_datapath.adder_pc_plus_immediate.operand_a[15] +1 1 +.names singlecycle_datapath.program_counter.value[16] singlecycle_datapath.adder_pc_plus_immediate.operand_a[16] +1 1 +.names singlecycle_datapath.program_counter.value[17] singlecycle_datapath.adder_pc_plus_immediate.operand_a[17] +1 1 +.names singlecycle_datapath.program_counter.value[18] singlecycle_datapath.adder_pc_plus_immediate.operand_a[18] +1 1 +.names singlecycle_datapath.program_counter.value[19] singlecycle_datapath.adder_pc_plus_immediate.operand_a[19] +1 1 +.names singlecycle_datapath.program_counter.value[20] singlecycle_datapath.adder_pc_plus_immediate.operand_a[20] +1 1 +.names singlecycle_datapath.program_counter.value[21] singlecycle_datapath.adder_pc_plus_immediate.operand_a[21] +1 1 +.names singlecycle_datapath.program_counter.value[22] singlecycle_datapath.adder_pc_plus_immediate.operand_a[22] +1 1 +.names singlecycle_datapath.program_counter.value[23] singlecycle_datapath.adder_pc_plus_immediate.operand_a[23] +1 1 +.names singlecycle_datapath.program_counter.value[24] singlecycle_datapath.adder_pc_plus_immediate.operand_a[24] +1 1 +.names singlecycle_datapath.program_counter.value[25] singlecycle_datapath.adder_pc_plus_immediate.operand_a[25] +1 1 +.names singlecycle_datapath.program_counter.value[26] singlecycle_datapath.adder_pc_plus_immediate.operand_a[26] +1 1 +.names singlecycle_datapath.program_counter.value[27] singlecycle_datapath.adder_pc_plus_immediate.operand_a[27] +1 1 +.names singlecycle_datapath.program_counter.value[28] singlecycle_datapath.adder_pc_plus_immediate.operand_a[28] +1 1 +.names singlecycle_datapath.program_counter.value[29] singlecycle_datapath.adder_pc_plus_immediate.operand_a[29] +1 1 +.names singlecycle_datapath.program_counter.value[30] singlecycle_datapath.adder_pc_plus_immediate.operand_a[30] +1 1 +.names singlecycle_datapath.program_counter.value[31] singlecycle_datapath.adder_pc_plus_immediate.operand_a[31] +1 1 +.names alu_function[0] singlecycle_datapath.alu.alu_function[0] +1 1 +.names alu_function[1] singlecycle_datapath.alu.alu_function[1] +1 1 +.names alu_function[2] singlecycle_datapath.alu.alu_function[2] +1 1 +.names alu_function[3] singlecycle_datapath.alu.alu_function[3] +1 1 +.names $false singlecycle_datapath.alu.alu_function[4] +1 1 +.names bus_address[0] singlecycle_datapath.alu.result[0] +1 1 +.names bus_address[1] singlecycle_datapath.alu.result[1] +1 1 +.names bus_address[2] singlecycle_datapath.alu.result[2] +1 1 +.names bus_address[3] singlecycle_datapath.alu.result[3] +1 1 +.names bus_address[4] singlecycle_datapath.alu.result[4] +1 1 +.names bus_address[5] singlecycle_datapath.alu.result[5] +1 1 +.names bus_address[6] singlecycle_datapath.alu.result[6] +1 1 +.names bus_address[7] singlecycle_datapath.alu.result[7] +1 1 +.names bus_address[8] singlecycle_datapath.alu.result[8] +1 1 +.names bus_address[9] singlecycle_datapath.alu.result[9] +1 1 +.names bus_address[10] singlecycle_datapath.alu.result[10] +1 1 +.names bus_address[11] singlecycle_datapath.alu.result[11] +1 1 +.names bus_address[12] singlecycle_datapath.alu.result[12] +1 1 +.names bus_address[13] singlecycle_datapath.alu.result[13] +1 1 +.names bus_address[14] singlecycle_datapath.alu.result[14] +1 1 +.names bus_address[15] singlecycle_datapath.alu.result[15] +1 1 +.names bus_address[16] singlecycle_datapath.alu.result[16] +1 1 +.names bus_address[17] singlecycle_datapath.alu.result[17] +1 1 +.names bus_address[18] singlecycle_datapath.alu.result[18] +1 1 +.names bus_address[19] singlecycle_datapath.alu.result[19] +1 1 +.names bus_address[20] singlecycle_datapath.alu.result[20] +1 1 +.names bus_address[21] singlecycle_datapath.alu.result[21] +1 1 +.names bus_address[22] singlecycle_datapath.alu.result[22] +1 1 +.names bus_address[23] singlecycle_datapath.alu.result[23] +1 1 +.names bus_address[24] singlecycle_datapath.alu.result[24] +1 1 +.names bus_address[25] singlecycle_datapath.alu.result[25] +1 1 +.names bus_address[26] singlecycle_datapath.alu.result[26] +1 1 +.names bus_address[27] singlecycle_datapath.alu.result[27] +1 1 +.names bus_address[28] singlecycle_datapath.alu.result[28] +1 1 +.names bus_address[29] singlecycle_datapath.alu.result[29] +1 1 +.names bus_address[30] singlecycle_datapath.alu.result[30] +1 1 +.names bus_address[31] singlecycle_datapath.alu.result[31] +1 1 +.names alu_function[0] singlecycle_datapath.alu_function[0] +1 1 +.names alu_function[1] singlecycle_datapath.alu_function[1] +1 1 +.names alu_function[2] singlecycle_datapath.alu_function[2] +1 1 +.names alu_function[3] singlecycle_datapath.alu_function[3] +1 1 +.names $false singlecycle_datapath.alu_function[4] +1 1 +.names bus_address[0] singlecycle_datapath.alu_result[0] +1 1 +.names bus_address[1] singlecycle_datapath.alu_result[1] +1 1 +.names bus_address[2] singlecycle_datapath.alu_result[2] +1 1 +.names bus_address[3] singlecycle_datapath.alu_result[3] +1 1 +.names bus_address[4] singlecycle_datapath.alu_result[4] +1 1 +.names bus_address[5] singlecycle_datapath.alu_result[5] +1 1 +.names bus_address[6] singlecycle_datapath.alu_result[6] +1 1 +.names bus_address[7] singlecycle_datapath.alu_result[7] +1 1 +.names bus_address[8] singlecycle_datapath.alu_result[8] +1 1 +.names bus_address[9] singlecycle_datapath.alu_result[9] +1 1 +.names bus_address[10] singlecycle_datapath.alu_result[10] +1 1 +.names bus_address[11] singlecycle_datapath.alu_result[11] +1 1 +.names bus_address[12] singlecycle_datapath.alu_result[12] +1 1 +.names bus_address[13] singlecycle_datapath.alu_result[13] +1 1 +.names bus_address[14] singlecycle_datapath.alu_result[14] +1 1 +.names bus_address[15] singlecycle_datapath.alu_result[15] +1 1 +.names bus_address[16] singlecycle_datapath.alu_result[16] +1 1 +.names bus_address[17] singlecycle_datapath.alu_result[17] +1 1 +.names bus_address[18] singlecycle_datapath.alu_result[18] +1 1 +.names bus_address[19] singlecycle_datapath.alu_result[19] +1 1 +.names bus_address[20] singlecycle_datapath.alu_result[20] +1 1 +.names bus_address[21] singlecycle_datapath.alu_result[21] +1 1 +.names bus_address[22] singlecycle_datapath.alu_result[22] +1 1 +.names bus_address[23] singlecycle_datapath.alu_result[23] +1 1 +.names bus_address[24] singlecycle_datapath.alu_result[24] +1 1 +.names bus_address[25] singlecycle_datapath.alu_result[25] +1 1 +.names bus_address[26] singlecycle_datapath.alu_result[26] +1 1 +.names bus_address[27] singlecycle_datapath.alu_result[27] +1 1 +.names bus_address[28] singlecycle_datapath.alu_result[28] +1 1 +.names bus_address[29] singlecycle_datapath.alu_result[29] +1 1 +.names bus_address[30] singlecycle_datapath.alu_result[30] +1 1 +.names bus_address[31] singlecycle_datapath.alu_result[31] +1 1 +.names clock singlecycle_datapath.clock +1 1 +.names bus_address[0] singlecycle_datapath.data_mem_address[0] +1 1 +.names bus_address[1] singlecycle_datapath.data_mem_address[1] +1 1 +.names bus_address[2] singlecycle_datapath.data_mem_address[2] +1 1 +.names bus_address[3] singlecycle_datapath.data_mem_address[3] +1 1 +.names bus_address[4] singlecycle_datapath.data_mem_address[4] +1 1 +.names bus_address[5] singlecycle_datapath.data_mem_address[5] +1 1 +.names bus_address[6] singlecycle_datapath.data_mem_address[6] +1 1 +.names bus_address[7] singlecycle_datapath.data_mem_address[7] +1 1 +.names bus_address[8] singlecycle_datapath.data_mem_address[8] +1 1 +.names bus_address[9] singlecycle_datapath.data_mem_address[9] +1 1 +.names bus_address[10] singlecycle_datapath.data_mem_address[10] +1 1 +.names bus_address[11] singlecycle_datapath.data_mem_address[11] +1 1 +.names bus_address[12] singlecycle_datapath.data_mem_address[12] +1 1 +.names bus_address[13] singlecycle_datapath.data_mem_address[13] +1 1 +.names bus_address[14] singlecycle_datapath.data_mem_address[14] +1 1 +.names bus_address[15] singlecycle_datapath.data_mem_address[15] +1 1 +.names bus_address[16] singlecycle_datapath.data_mem_address[16] +1 1 +.names bus_address[17] singlecycle_datapath.data_mem_address[17] +1 1 +.names bus_address[18] singlecycle_datapath.data_mem_address[18] +1 1 +.names bus_address[19] singlecycle_datapath.data_mem_address[19] +1 1 +.names bus_address[20] singlecycle_datapath.data_mem_address[20] +1 1 +.names bus_address[21] singlecycle_datapath.data_mem_address[21] +1 1 +.names bus_address[22] singlecycle_datapath.data_mem_address[22] +1 1 +.names bus_address[23] singlecycle_datapath.data_mem_address[23] +1 1 +.names bus_address[24] singlecycle_datapath.data_mem_address[24] +1 1 +.names bus_address[25] singlecycle_datapath.data_mem_address[25] +1 1 +.names bus_address[26] singlecycle_datapath.data_mem_address[26] +1 1 +.names bus_address[27] singlecycle_datapath.data_mem_address[27] +1 1 +.names bus_address[28] singlecycle_datapath.data_mem_address[28] +1 1 +.names bus_address[29] singlecycle_datapath.data_mem_address[29] +1 1 +.names bus_address[30] singlecycle_datapath.data_mem_address[30] +1 1 +.names bus_address[31] singlecycle_datapath.data_mem_address[31] +1 1 +.names rs2_data[0] singlecycle_datapath.data_mem_write_data[0] +1 1 +.names rs2_data[1] singlecycle_datapath.data_mem_write_data[1] +1 1 +.names rs2_data[2] singlecycle_datapath.data_mem_write_data[2] +1 1 +.names rs2_data[3] singlecycle_datapath.data_mem_write_data[3] +1 1 +.names rs2_data[4] singlecycle_datapath.data_mem_write_data[4] +1 1 +.names rs2_data[5] singlecycle_datapath.data_mem_write_data[5] +1 1 +.names rs2_data[6] singlecycle_datapath.data_mem_write_data[6] +1 1 +.names rs2_data[7] singlecycle_datapath.data_mem_write_data[7] +1 1 +.names rs2_data[8] singlecycle_datapath.data_mem_write_data[8] +1 1 +.names rs2_data[9] singlecycle_datapath.data_mem_write_data[9] +1 1 +.names rs2_data[10] singlecycle_datapath.data_mem_write_data[10] +1 1 +.names rs2_data[11] singlecycle_datapath.data_mem_write_data[11] +1 1 +.names rs2_data[12] singlecycle_datapath.data_mem_write_data[12] +1 1 +.names rs2_data[13] singlecycle_datapath.data_mem_write_data[13] +1 1 +.names rs2_data[14] singlecycle_datapath.data_mem_write_data[14] +1 1 +.names rs2_data[15] singlecycle_datapath.data_mem_write_data[15] +1 1 +.names rs2_data[16] singlecycle_datapath.data_mem_write_data[16] +1 1 +.names rs2_data[17] singlecycle_datapath.data_mem_write_data[17] +1 1 +.names rs2_data[18] singlecycle_datapath.data_mem_write_data[18] +1 1 +.names rs2_data[19] singlecycle_datapath.data_mem_write_data[19] +1 1 +.names rs2_data[20] singlecycle_datapath.data_mem_write_data[20] +1 1 +.names rs2_data[21] singlecycle_datapath.data_mem_write_data[21] +1 1 +.names rs2_data[22] singlecycle_datapath.data_mem_write_data[22] +1 1 +.names rs2_data[23] singlecycle_datapath.data_mem_write_data[23] +1 1 +.names rs2_data[24] singlecycle_datapath.data_mem_write_data[24] +1 1 +.names rs2_data[25] singlecycle_datapath.data_mem_write_data[25] +1 1 +.names rs2_data[26] singlecycle_datapath.data_mem_write_data[26] +1 1 +.names rs2_data[27] singlecycle_datapath.data_mem_write_data[27] +1 1 +.names rs2_data[28] singlecycle_datapath.data_mem_write_data[28] +1 1 +.names rs2_data[29] singlecycle_datapath.data_mem_write_data[29] +1 1 +.names rs2_data[30] singlecycle_datapath.data_mem_write_data[30] +1 1 +.names rs2_data[31] singlecycle_datapath.data_mem_write_data[31] +1 1 +.names inst[0] singlecycle_datapath.immediate_generator.inst[0] +1 1 +.names inst[1] singlecycle_datapath.immediate_generator.inst[1] +1 1 +.names inst[2] singlecycle_datapath.immediate_generator.inst[2] +1 1 +.names inst[3] singlecycle_datapath.immediate_generator.inst[3] +1 1 +.names inst[4] singlecycle_datapath.immediate_generator.inst[4] +1 1 +.names inst[5] singlecycle_datapath.immediate_generator.inst[5] +1 1 +.names inst[6] singlecycle_datapath.immediate_generator.inst[6] +1 1 +.names inst[7] singlecycle_datapath.immediate_generator.inst[7] +1 1 +.names inst[8] singlecycle_datapath.immediate_generator.inst[8] +1 1 +.names inst[9] singlecycle_datapath.immediate_generator.inst[9] +1 1 +.names inst[10] singlecycle_datapath.immediate_generator.inst[10] +1 1 +.names inst[11] singlecycle_datapath.immediate_generator.inst[11] +1 1 +.names inst[12] singlecycle_datapath.immediate_generator.inst[12] +1 1 +.names inst[13] singlecycle_datapath.immediate_generator.inst[13] +1 1 +.names inst[14] singlecycle_datapath.immediate_generator.inst[14] +1 1 +.names inst[15] singlecycle_datapath.immediate_generator.inst[15] +1 1 +.names inst[16] singlecycle_datapath.immediate_generator.inst[16] +1 1 +.names inst[17] singlecycle_datapath.immediate_generator.inst[17] +1 1 +.names inst[18] singlecycle_datapath.immediate_generator.inst[18] +1 1 +.names inst[19] singlecycle_datapath.immediate_generator.inst[19] +1 1 +.names inst[20] singlecycle_datapath.immediate_generator.inst[20] +1 1 +.names inst[21] singlecycle_datapath.immediate_generator.inst[21] +1 1 +.names inst[22] singlecycle_datapath.immediate_generator.inst[22] +1 1 +.names inst[23] singlecycle_datapath.immediate_generator.inst[23] +1 1 +.names inst[24] singlecycle_datapath.immediate_generator.inst[24] +1 1 +.names inst[25] singlecycle_datapath.immediate_generator.inst[25] +1 1 +.names inst[26] singlecycle_datapath.immediate_generator.inst[26] +1 1 +.names inst[27] singlecycle_datapath.immediate_generator.inst[27] +1 1 +.names inst[28] singlecycle_datapath.immediate_generator.inst[28] +1 1 +.names inst[29] singlecycle_datapath.immediate_generator.inst[29] +1 1 +.names inst[30] singlecycle_datapath.immediate_generator.inst[30] +1 1 +.names inst[31] singlecycle_datapath.immediate_generator.inst[31] +1 1 +.names inst[0] singlecycle_datapath.inst[0] +1 1 +.names inst[1] singlecycle_datapath.inst[1] +1 1 +.names inst[2] singlecycle_datapath.inst[2] +1 1 +.names inst[3] singlecycle_datapath.inst[3] +1 1 +.names inst[4] singlecycle_datapath.inst[4] +1 1 +.names inst[5] singlecycle_datapath.inst[5] +1 1 +.names inst[6] singlecycle_datapath.inst[6] +1 1 +.names inst[7] singlecycle_datapath.inst[7] +1 1 +.names inst[8] singlecycle_datapath.inst[8] +1 1 +.names inst[9] singlecycle_datapath.inst[9] +1 1 +.names inst[10] singlecycle_datapath.inst[10] +1 1 +.names inst[11] singlecycle_datapath.inst[11] +1 1 +.names inst[12] singlecycle_datapath.inst[12] +1 1 +.names inst[13] singlecycle_datapath.inst[13] +1 1 +.names inst[14] singlecycle_datapath.inst[14] +1 1 +.names inst[15] singlecycle_datapath.inst[15] +1 1 +.names inst[16] singlecycle_datapath.inst[16] +1 1 +.names inst[17] singlecycle_datapath.inst[17] +1 1 +.names inst[18] singlecycle_datapath.inst[18] +1 1 +.names inst[19] singlecycle_datapath.inst[19] +1 1 +.names inst[20] singlecycle_datapath.inst[20] +1 1 +.names inst[21] singlecycle_datapath.inst[21] +1 1 +.names inst[22] singlecycle_datapath.inst[22] +1 1 +.names inst[23] singlecycle_datapath.inst[23] +1 1 +.names inst[24] singlecycle_datapath.inst[24] +1 1 +.names inst[25] singlecycle_datapath.inst[25] +1 1 +.names inst[26] singlecycle_datapath.inst[26] +1 1 +.names inst[27] singlecycle_datapath.inst[27] +1 1 +.names inst[28] singlecycle_datapath.inst[28] +1 1 +.names inst[29] singlecycle_datapath.inst[29] +1 1 +.names inst[30] singlecycle_datapath.inst[30] +1 1 +.names inst[31] singlecycle_datapath.inst[31] +1 1 +.names inst[12] singlecycle_datapath.inst_funct3[0] +1 1 +.names inst[13] singlecycle_datapath.inst_funct3[1] +1 1 +.names inst[14] singlecycle_datapath.inst_funct3[2] +1 1 +.names inst[25] singlecycle_datapath.inst_funct7[0] +1 1 +.names inst[26] singlecycle_datapath.inst_funct7[1] +1 1 +.names inst[27] singlecycle_datapath.inst_funct7[2] +1 1 +.names inst[28] singlecycle_datapath.inst_funct7[3] +1 1 +.names inst[29] singlecycle_datapath.inst_funct7[4] +1 1 +.names inst[30] singlecycle_datapath.inst_funct7[5] +1 1 +.names inst[31] singlecycle_datapath.inst_funct7[6] +1 1 +.names inst[0] singlecycle_datapath.inst_opcode[0] +1 1 +.names inst[1] singlecycle_datapath.inst_opcode[1] +1 1 +.names inst[2] singlecycle_datapath.inst_opcode[2] +1 1 +.names inst[3] singlecycle_datapath.inst_opcode[3] +1 1 +.names inst[4] singlecycle_datapath.inst_opcode[4] +1 1 +.names inst[5] singlecycle_datapath.inst_opcode[5] +1 1 +.names inst[6] singlecycle_datapath.inst_opcode[6] +1 1 +.names inst[7] singlecycle_datapath.inst_rd +1 1 +.names inst[15] singlecycle_datapath.inst_rs1 +1 1 +.names inst[20] singlecycle_datapath.inst_rs2 +1 1 +.names inst[0] singlecycle_datapath.instruction_decoder.inst[0] +1 1 +.names inst[1] singlecycle_datapath.instruction_decoder.inst[1] +1 1 +.names inst[2] singlecycle_datapath.instruction_decoder.inst[2] +1 1 +.names inst[3] singlecycle_datapath.instruction_decoder.inst[3] +1 1 +.names inst[4] singlecycle_datapath.instruction_decoder.inst[4] +1 1 +.names inst[5] singlecycle_datapath.instruction_decoder.inst[5] +1 1 +.names inst[6] singlecycle_datapath.instruction_decoder.inst[6] +1 1 +.names inst[7] singlecycle_datapath.instruction_decoder.inst[7] +1 1 +.names inst[8] singlecycle_datapath.instruction_decoder.inst[8] +1 1 +.names inst[9] singlecycle_datapath.instruction_decoder.inst[9] +1 1 +.names inst[10] singlecycle_datapath.instruction_decoder.inst[10] +1 1 +.names inst[11] singlecycle_datapath.instruction_decoder.inst[11] +1 1 +.names inst[12] singlecycle_datapath.instruction_decoder.inst[12] +1 1 +.names inst[13] singlecycle_datapath.instruction_decoder.inst[13] +1 1 +.names inst[14] singlecycle_datapath.instruction_decoder.inst[14] +1 1 +.names inst[15] singlecycle_datapath.instruction_decoder.inst[15] +1 1 +.names inst[16] singlecycle_datapath.instruction_decoder.inst[16] +1 1 +.names inst[17] singlecycle_datapath.instruction_decoder.inst[17] +1 1 +.names inst[18] singlecycle_datapath.instruction_decoder.inst[18] +1 1 +.names inst[19] singlecycle_datapath.instruction_decoder.inst[19] +1 1 +.names inst[20] singlecycle_datapath.instruction_decoder.inst[20] +1 1 +.names inst[21] singlecycle_datapath.instruction_decoder.inst[21] +1 1 +.names inst[22] singlecycle_datapath.instruction_decoder.inst[22] +1 1 +.names inst[23] singlecycle_datapath.instruction_decoder.inst[23] +1 1 +.names inst[24] singlecycle_datapath.instruction_decoder.inst[24] +1 1 +.names inst[25] singlecycle_datapath.instruction_decoder.inst[25] +1 1 +.names inst[26] singlecycle_datapath.instruction_decoder.inst[26] +1 1 +.names inst[27] singlecycle_datapath.instruction_decoder.inst[27] +1 1 +.names inst[28] singlecycle_datapath.instruction_decoder.inst[28] +1 1 +.names inst[29] singlecycle_datapath.instruction_decoder.inst[29] +1 1 +.names inst[30] singlecycle_datapath.instruction_decoder.inst[30] +1 1 +.names inst[31] singlecycle_datapath.instruction_decoder.inst[31] +1 1 +.names inst[12] singlecycle_datapath.instruction_decoder.inst_funct3[0] +1 1 +.names inst[13] singlecycle_datapath.instruction_decoder.inst_funct3[1] +1 1 +.names inst[14] singlecycle_datapath.instruction_decoder.inst_funct3[2] +1 1 +.names inst[25] singlecycle_datapath.instruction_decoder.inst_funct7[0] +1 1 +.names inst[26] singlecycle_datapath.instruction_decoder.inst_funct7[1] +1 1 +.names inst[27] singlecycle_datapath.instruction_decoder.inst_funct7[2] +1 1 +.names inst[28] singlecycle_datapath.instruction_decoder.inst_funct7[3] +1 1 +.names inst[29] singlecycle_datapath.instruction_decoder.inst_funct7[4] +1 1 +.names inst[30] singlecycle_datapath.instruction_decoder.inst_funct7[5] +1 1 +.names inst[31] singlecycle_datapath.instruction_decoder.inst_funct7[6] +1 1 +.names inst[0] singlecycle_datapath.instruction_decoder.inst_opcode[0] +1 1 +.names inst[1] singlecycle_datapath.instruction_decoder.inst_opcode[1] +1 1 +.names inst[2] singlecycle_datapath.instruction_decoder.inst_opcode[2] +1 1 +.names inst[3] singlecycle_datapath.instruction_decoder.inst_opcode[3] +1 1 +.names inst[4] singlecycle_datapath.instruction_decoder.inst_opcode[4] +1 1 +.names inst[5] singlecycle_datapath.instruction_decoder.inst_opcode[5] +1 1 +.names inst[6] singlecycle_datapath.instruction_decoder.inst_opcode[6] +1 1 +.names inst[7] singlecycle_datapath.instruction_decoder.inst_rd[0] +1 1 +.names inst[8] singlecycle_datapath.instruction_decoder.inst_rd[1] +1 1 +.names inst[9] singlecycle_datapath.instruction_decoder.inst_rd[2] +1 1 +.names inst[10] singlecycle_datapath.instruction_decoder.inst_rd[3] +1 1 +.names inst[11] singlecycle_datapath.instruction_decoder.inst_rd[4] +1 1 +.names inst[15] singlecycle_datapath.instruction_decoder.inst_rs1[0] +1 1 +.names inst[16] singlecycle_datapath.instruction_decoder.inst_rs1[1] +1 1 +.names inst[17] singlecycle_datapath.instruction_decoder.inst_rs1[2] +1 1 +.names inst[18] singlecycle_datapath.instruction_decoder.inst_rs1[3] +1 1 +.names inst[19] singlecycle_datapath.instruction_decoder.inst_rs1[4] +1 1 +.names inst[20] singlecycle_datapath.instruction_decoder.inst_rs2[0] +1 1 +.names inst[21] singlecycle_datapath.instruction_decoder.inst_rs2[1] +1 1 +.names inst[22] singlecycle_datapath.instruction_decoder.inst_rs2[2] +1 1 +.names inst[23] singlecycle_datapath.instruction_decoder.inst_rs2[3] +1 1 +.names inst[24] singlecycle_datapath.instruction_decoder.inst_rs2[4] +1 1 +.names singlecycle_datapath.program_counter.value[0] singlecycle_datapath.mux_next_pc_select.in0[0] +1 1 +.names singlecycle_datapath.program_counter.value[1] singlecycle_datapath.mux_next_pc_select.in0[1] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[2] singlecycle_datapath.mux_next_pc_select.in0[2] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[3] singlecycle_datapath.mux_next_pc_select.in0[3] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[4] singlecycle_datapath.mux_next_pc_select.in0[4] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[5] singlecycle_datapath.mux_next_pc_select.in0[5] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[6] singlecycle_datapath.mux_next_pc_select.in0[6] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[7] singlecycle_datapath.mux_next_pc_select.in0[7] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[8] singlecycle_datapath.mux_next_pc_select.in0[8] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[9] singlecycle_datapath.mux_next_pc_select.in0[9] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[10] singlecycle_datapath.mux_next_pc_select.in0[10] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[11] singlecycle_datapath.mux_next_pc_select.in0[11] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[12] singlecycle_datapath.mux_next_pc_select.in0[12] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[13] singlecycle_datapath.mux_next_pc_select.in0[13] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[14] singlecycle_datapath.mux_next_pc_select.in0[14] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[15] singlecycle_datapath.mux_next_pc_select.in0[15] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[16] singlecycle_datapath.mux_next_pc_select.in0[16] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[17] singlecycle_datapath.mux_next_pc_select.in0[17] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[18] singlecycle_datapath.mux_next_pc_select.in0[18] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[19] singlecycle_datapath.mux_next_pc_select.in0[19] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[20] singlecycle_datapath.mux_next_pc_select.in0[20] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[21] singlecycle_datapath.mux_next_pc_select.in0[21] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[22] singlecycle_datapath.mux_next_pc_select.in0[22] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[23] singlecycle_datapath.mux_next_pc_select.in0[23] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[24] singlecycle_datapath.mux_next_pc_select.in0[24] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[25] singlecycle_datapath.mux_next_pc_select.in0[25] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[26] singlecycle_datapath.mux_next_pc_select.in0[26] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[27] singlecycle_datapath.mux_next_pc_select.in0[27] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[28] singlecycle_datapath.mux_next_pc_select.in0[28] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[29] singlecycle_datapath.mux_next_pc_select.in0[29] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[30] singlecycle_datapath.mux_next_pc_select.in0[30] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[31] singlecycle_datapath.mux_next_pc_select.in0[31] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.in2[0] +1 1 +.names bus_address[1] singlecycle_datapath.mux_next_pc_select.in2[1] +1 1 +.names bus_address[2] singlecycle_datapath.mux_next_pc_select.in2[2] +1 1 +.names bus_address[3] singlecycle_datapath.mux_next_pc_select.in2[3] +1 1 +.names bus_address[4] singlecycle_datapath.mux_next_pc_select.in2[4] +1 1 +.names bus_address[5] singlecycle_datapath.mux_next_pc_select.in2[5] +1 1 +.names bus_address[6] singlecycle_datapath.mux_next_pc_select.in2[6] +1 1 +.names bus_address[7] singlecycle_datapath.mux_next_pc_select.in2[7] +1 1 +.names bus_address[8] singlecycle_datapath.mux_next_pc_select.in2[8] +1 1 +.names bus_address[9] singlecycle_datapath.mux_next_pc_select.in2[9] +1 1 +.names bus_address[10] singlecycle_datapath.mux_next_pc_select.in2[10] +1 1 +.names bus_address[11] singlecycle_datapath.mux_next_pc_select.in2[11] +1 1 +.names bus_address[12] singlecycle_datapath.mux_next_pc_select.in2[12] +1 1 +.names bus_address[13] singlecycle_datapath.mux_next_pc_select.in2[13] +1 1 +.names bus_address[14] singlecycle_datapath.mux_next_pc_select.in2[14] +1 1 +.names bus_address[15] singlecycle_datapath.mux_next_pc_select.in2[15] +1 1 +.names bus_address[16] singlecycle_datapath.mux_next_pc_select.in2[16] +1 1 +.names bus_address[17] singlecycle_datapath.mux_next_pc_select.in2[17] +1 1 +.names bus_address[18] singlecycle_datapath.mux_next_pc_select.in2[18] +1 1 +.names bus_address[19] singlecycle_datapath.mux_next_pc_select.in2[19] +1 1 +.names bus_address[20] singlecycle_datapath.mux_next_pc_select.in2[20] +1 1 +.names bus_address[21] singlecycle_datapath.mux_next_pc_select.in2[21] +1 1 +.names bus_address[22] singlecycle_datapath.mux_next_pc_select.in2[22] +1 1 +.names bus_address[23] singlecycle_datapath.mux_next_pc_select.in2[23] +1 1 +.names bus_address[24] singlecycle_datapath.mux_next_pc_select.in2[24] +1 1 +.names bus_address[25] singlecycle_datapath.mux_next_pc_select.in2[25] +1 1 +.names bus_address[26] singlecycle_datapath.mux_next_pc_select.in2[26] +1 1 +.names bus_address[27] singlecycle_datapath.mux_next_pc_select.in2[27] +1 1 +.names bus_address[28] singlecycle_datapath.mux_next_pc_select.in2[28] +1 1 +.names bus_address[29] singlecycle_datapath.mux_next_pc_select.in2[29] +1 1 +.names bus_address[30] singlecycle_datapath.mux_next_pc_select.in2[30] +1 1 +.names bus_address[31] singlecycle_datapath.mux_next_pc_select.in2[31] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.in3[0] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.in3[1] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.in3[2] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.in3[3] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.in3[4] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.in3[5] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.in3[6] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.in3[7] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.in3[8] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.in3[9] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.in3[10] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.in3[11] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.in3[12] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.in3[13] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.in3[14] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.in3[15] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.in3[16] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.in3[17] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.in3[18] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.in3[19] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.in3[20] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.in3[21] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.in3[22] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.in3[23] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.in3[24] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.in3[25] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.in3[26] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.in3[27] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.in3[28] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.in3[29] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.in3[30] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.in3[31] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[0] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[1] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[2] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[3] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[4] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[5] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[6] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[7] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[8] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[9] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[10] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[11] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[12] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[13] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[14] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[15] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[16] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[17] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[18] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[19] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[20] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[21] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[22] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[23] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[24] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[25] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[26] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[27] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[28] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[29] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[30] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[31] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[32] +1 1 +.names bus_address[1] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[33] +1 1 +.names bus_address[2] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[34] +1 1 +.names bus_address[3] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[35] +1 1 +.names bus_address[4] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[36] +1 1 +.names bus_address[5] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[37] +1 1 +.names bus_address[6] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[38] +1 1 +.names bus_address[7] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[39] +1 1 +.names bus_address[8] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[40] +1 1 +.names bus_address[9] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[41] +1 1 +.names bus_address[10] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[42] +1 1 +.names bus_address[11] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[43] +1 1 +.names bus_address[12] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[44] +1 1 +.names bus_address[13] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[45] +1 1 +.names bus_address[14] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[46] +1 1 +.names bus_address[15] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[47] +1 1 +.names bus_address[16] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[48] +1 1 +.names bus_address[17] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[49] +1 1 +.names bus_address[18] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[50] +1 1 +.names bus_address[19] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[51] +1 1 +.names bus_address[20] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[52] +1 1 +.names bus_address[21] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[53] +1 1 +.names bus_address[22] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[54] +1 1 +.names bus_address[23] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[55] +1 1 +.names bus_address[24] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[56] +1 1 +.names bus_address[25] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[57] +1 1 +.names bus_address[26] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[58] +1 1 +.names bus_address[27] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[59] +1 1 +.names bus_address[28] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[60] +1 1 +.names bus_address[29] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[61] +1 1 +.names bus_address[30] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[62] +1 1 +.names bus_address[31] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[63] +1 1 +.names singlecycle_datapath.program_counter.value[0] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[96] +1 1 +.names singlecycle_datapath.program_counter.value[1] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[97] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[2] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[98] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[3] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[99] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[4] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[100] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[5] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[101] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[6] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[102] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[7] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[103] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[8] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[104] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[9] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[105] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[10] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[106] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[11] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[107] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[12] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[108] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[13] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[109] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[14] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[110] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[15] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[111] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[16] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[112] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[17] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[113] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[18] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[114] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[19] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[115] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[20] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[116] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[21] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[117] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[22] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[118] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[23] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[119] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[24] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[120] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[25] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[121] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[26] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[122] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[27] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[123] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[28] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[124] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[29] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[125] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[30] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[126] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[31] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[127] +1 1 +.names singlecycle_datapath.program_counter.value[0] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][0] +1 1 +.names singlecycle_datapath.program_counter.value[1] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][1] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[2] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][2] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[3] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][3] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[4] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][4] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[5] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][5] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[6] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][6] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[7] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][7] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[8] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][8] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[9] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][9] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[10] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][10] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[11] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][11] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[12] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][12] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[13] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][13] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[14] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][14] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[15] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][15] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[16] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][16] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[17] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][17] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[18] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][18] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[19] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][19] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[20] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][20] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[21] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][21] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[22] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][22] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[23] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][23] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[24] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][24] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[25] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][25] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[26] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][26] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[27] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][27] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[28] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][28] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[29] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][29] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[30] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][30] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[31] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][31] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][0] +1 1 +.names bus_address[1] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][1] +1 1 +.names bus_address[2] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][2] +1 1 +.names bus_address[3] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][3] +1 1 +.names bus_address[4] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][4] +1 1 +.names bus_address[5] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][5] +1 1 +.names bus_address[6] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][6] +1 1 +.names bus_address[7] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][7] +1 1 +.names bus_address[8] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][8] +1 1 +.names bus_address[9] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][9] +1 1 +.names bus_address[10] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][10] +1 1 +.names bus_address[11] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][11] +1 1 +.names bus_address[12] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][12] +1 1 +.names bus_address[13] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][13] +1 1 +.names bus_address[14] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][14] +1 1 +.names bus_address[15] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][15] +1 1 +.names bus_address[16] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][16] +1 1 +.names bus_address[17] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][17] +1 1 +.names bus_address[18] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][18] +1 1 +.names bus_address[19] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][19] +1 1 +.names bus_address[20] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][20] +1 1 +.names bus_address[21] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][21] +1 1 +.names bus_address[22] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][22] +1 1 +.names bus_address[23] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][23] +1 1 +.names bus_address[24] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][24] +1 1 +.names bus_address[25] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][25] +1 1 +.names bus_address[26] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][26] +1 1 +.names bus_address[27] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][27] +1 1 +.names bus_address[28] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][28] +1 1 +.names bus_address[29] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][29] +1 1 +.names bus_address[30] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][30] +1 1 +.names bus_address[31] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][31] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][0] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][1] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][2] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][3] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][4] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][5] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][6] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][7] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][8] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][9] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][10] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][11] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][12] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][13] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][14] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][15] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][16] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][17] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][18] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][19] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][20] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][21] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][22] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][23] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][24] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][25] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][26] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][27] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][28] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][29] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][30] +1 1 +.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][31] +1 1 +.names rs1_data[0] singlecycle_datapath.mux_operand_a.in0[0] +1 1 +.names rs1_data[1] singlecycle_datapath.mux_operand_a.in0[1] +1 1 +.names rs1_data[2] singlecycle_datapath.mux_operand_a.in0[2] +1 1 +.names rs1_data[3] singlecycle_datapath.mux_operand_a.in0[3] +1 1 +.names rs1_data[4] singlecycle_datapath.mux_operand_a.in0[4] +1 1 +.names rs1_data[5] singlecycle_datapath.mux_operand_a.in0[5] +1 1 +.names rs1_data[6] singlecycle_datapath.mux_operand_a.in0[6] +1 1 +.names rs1_data[7] singlecycle_datapath.mux_operand_a.in0[7] +1 1 +.names rs1_data[8] singlecycle_datapath.mux_operand_a.in0[8] +1 1 +.names rs1_data[9] singlecycle_datapath.mux_operand_a.in0[9] +1 1 +.names rs1_data[10] singlecycle_datapath.mux_operand_a.in0[10] +1 1 +.names rs1_data[11] singlecycle_datapath.mux_operand_a.in0[11] +1 1 +.names rs1_data[12] singlecycle_datapath.mux_operand_a.in0[12] +1 1 +.names rs1_data[13] singlecycle_datapath.mux_operand_a.in0[13] +1 1 +.names rs1_data[14] singlecycle_datapath.mux_operand_a.in0[14] +1 1 +.names rs1_data[15] singlecycle_datapath.mux_operand_a.in0[15] +1 1 +.names rs1_data[16] singlecycle_datapath.mux_operand_a.in0[16] +1 1 +.names rs1_data[17] singlecycle_datapath.mux_operand_a.in0[17] +1 1 +.names rs1_data[18] singlecycle_datapath.mux_operand_a.in0[18] +1 1 +.names rs1_data[19] singlecycle_datapath.mux_operand_a.in0[19] +1 1 +.names rs1_data[20] singlecycle_datapath.mux_operand_a.in0[20] +1 1 +.names rs1_data[21] singlecycle_datapath.mux_operand_a.in0[21] +1 1 +.names rs1_data[22] singlecycle_datapath.mux_operand_a.in0[22] +1 1 +.names rs1_data[23] singlecycle_datapath.mux_operand_a.in0[23] +1 1 +.names rs1_data[24] singlecycle_datapath.mux_operand_a.in0[24] +1 1 +.names rs1_data[25] singlecycle_datapath.mux_operand_a.in0[25] +1 1 +.names rs1_data[26] singlecycle_datapath.mux_operand_a.in0[26] +1 1 +.names rs1_data[27] singlecycle_datapath.mux_operand_a.in0[27] +1 1 +.names rs1_data[28] singlecycle_datapath.mux_operand_a.in0[28] +1 1 +.names rs1_data[29] singlecycle_datapath.mux_operand_a.in0[29] +1 1 +.names rs1_data[30] singlecycle_datapath.mux_operand_a.in0[30] +1 1 +.names rs1_data[31] singlecycle_datapath.mux_operand_a.in0[31] +1 1 +.names singlecycle_datapath.program_counter.value[0] singlecycle_datapath.mux_operand_a.in1[0] +1 1 +.names singlecycle_datapath.program_counter.value[1] singlecycle_datapath.mux_operand_a.in1[1] +1 1 +.names singlecycle_datapath.program_counter.value[2] singlecycle_datapath.mux_operand_a.in1[2] +1 1 +.names singlecycle_datapath.program_counter.value[3] singlecycle_datapath.mux_operand_a.in1[3] +1 1 +.names singlecycle_datapath.program_counter.value[4] singlecycle_datapath.mux_operand_a.in1[4] +1 1 +.names singlecycle_datapath.program_counter.value[5] singlecycle_datapath.mux_operand_a.in1[5] +1 1 +.names singlecycle_datapath.program_counter.value[6] singlecycle_datapath.mux_operand_a.in1[6] +1 1 +.names singlecycle_datapath.program_counter.value[7] singlecycle_datapath.mux_operand_a.in1[7] +1 1 +.names singlecycle_datapath.program_counter.value[8] singlecycle_datapath.mux_operand_a.in1[8] +1 1 +.names singlecycle_datapath.program_counter.value[9] singlecycle_datapath.mux_operand_a.in1[9] +1 1 +.names singlecycle_datapath.program_counter.value[10] singlecycle_datapath.mux_operand_a.in1[10] +1 1 +.names singlecycle_datapath.program_counter.value[11] singlecycle_datapath.mux_operand_a.in1[11] +1 1 +.names singlecycle_datapath.program_counter.value[12] singlecycle_datapath.mux_operand_a.in1[12] +1 1 +.names singlecycle_datapath.program_counter.value[13] singlecycle_datapath.mux_operand_a.in1[13] +1 1 +.names singlecycle_datapath.program_counter.value[14] singlecycle_datapath.mux_operand_a.in1[14] +1 1 +.names singlecycle_datapath.program_counter.value[15] singlecycle_datapath.mux_operand_a.in1[15] +1 1 +.names singlecycle_datapath.program_counter.value[16] singlecycle_datapath.mux_operand_a.in1[16] +1 1 +.names singlecycle_datapath.program_counter.value[17] singlecycle_datapath.mux_operand_a.in1[17] +1 1 +.names singlecycle_datapath.program_counter.value[18] singlecycle_datapath.mux_operand_a.in1[18] +1 1 +.names singlecycle_datapath.program_counter.value[19] singlecycle_datapath.mux_operand_a.in1[19] +1 1 +.names singlecycle_datapath.program_counter.value[20] singlecycle_datapath.mux_operand_a.in1[20] +1 1 +.names singlecycle_datapath.program_counter.value[21] singlecycle_datapath.mux_operand_a.in1[21] +1 1 +.names singlecycle_datapath.program_counter.value[22] singlecycle_datapath.mux_operand_a.in1[22] +1 1 +.names singlecycle_datapath.program_counter.value[23] singlecycle_datapath.mux_operand_a.in1[23] +1 1 +.names singlecycle_datapath.program_counter.value[24] singlecycle_datapath.mux_operand_a.in1[24] +1 1 +.names singlecycle_datapath.program_counter.value[25] singlecycle_datapath.mux_operand_a.in1[25] +1 1 +.names singlecycle_datapath.program_counter.value[26] singlecycle_datapath.mux_operand_a.in1[26] +1 1 +.names singlecycle_datapath.program_counter.value[27] singlecycle_datapath.mux_operand_a.in1[27] +1 1 +.names singlecycle_datapath.program_counter.value[28] singlecycle_datapath.mux_operand_a.in1[28] +1 1 +.names singlecycle_datapath.program_counter.value[29] singlecycle_datapath.mux_operand_a.in1[29] +1 1 +.names singlecycle_datapath.program_counter.value[30] singlecycle_datapath.mux_operand_a.in1[30] +1 1 +.names singlecycle_datapath.program_counter.value[31] singlecycle_datapath.mux_operand_a.in1[31] +1 1 +.names singlecycle_datapath.program_counter.value[0] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[0] +1 1 +.names singlecycle_datapath.program_counter.value[1] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[1] +1 1 +.names singlecycle_datapath.program_counter.value[2] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[2] +1 1 +.names singlecycle_datapath.program_counter.value[3] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[3] +1 1 +.names singlecycle_datapath.program_counter.value[4] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[4] +1 1 +.names singlecycle_datapath.program_counter.value[5] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[5] +1 1 +.names singlecycle_datapath.program_counter.value[6] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[6] +1 1 +.names singlecycle_datapath.program_counter.value[7] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[7] +1 1 +.names singlecycle_datapath.program_counter.value[8] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[8] +1 1 +.names singlecycle_datapath.program_counter.value[9] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[9] +1 1 +.names singlecycle_datapath.program_counter.value[10] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[10] +1 1 +.names singlecycle_datapath.program_counter.value[11] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[11] +1 1 +.names singlecycle_datapath.program_counter.value[12] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[12] +1 1 +.names singlecycle_datapath.program_counter.value[13] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[13] +1 1 +.names singlecycle_datapath.program_counter.value[14] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[14] +1 1 +.names singlecycle_datapath.program_counter.value[15] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[15] +1 1 +.names singlecycle_datapath.program_counter.value[16] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[16] +1 1 +.names singlecycle_datapath.program_counter.value[17] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[17] +1 1 +.names singlecycle_datapath.program_counter.value[18] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[18] +1 1 +.names singlecycle_datapath.program_counter.value[19] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[19] +1 1 +.names singlecycle_datapath.program_counter.value[20] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[20] +1 1 +.names singlecycle_datapath.program_counter.value[21] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[21] +1 1 +.names singlecycle_datapath.program_counter.value[22] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[22] +1 1 +.names singlecycle_datapath.program_counter.value[23] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[23] +1 1 +.names singlecycle_datapath.program_counter.value[24] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[24] +1 1 +.names singlecycle_datapath.program_counter.value[25] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[25] +1 1 +.names singlecycle_datapath.program_counter.value[26] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[26] +1 1 +.names singlecycle_datapath.program_counter.value[27] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[27] +1 1 +.names singlecycle_datapath.program_counter.value[28] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[28] +1 1 +.names singlecycle_datapath.program_counter.value[29] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[29] +1 1 +.names singlecycle_datapath.program_counter.value[30] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[30] +1 1 +.names singlecycle_datapath.program_counter.value[31] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[31] +1 1 +.names rs1_data[0] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[32] +1 1 +.names rs1_data[1] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[33] +1 1 +.names rs1_data[2] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[34] +1 1 +.names rs1_data[3] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[35] +1 1 +.names rs1_data[4] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[36] +1 1 +.names rs1_data[5] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[37] +1 1 +.names rs1_data[6] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[38] +1 1 +.names rs1_data[7] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[39] +1 1 +.names rs1_data[8] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[40] +1 1 +.names rs1_data[9] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[41] +1 1 +.names rs1_data[10] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[42] +1 1 +.names rs1_data[11] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[43] +1 1 +.names rs1_data[12] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[44] +1 1 +.names rs1_data[13] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[45] +1 1 +.names rs1_data[14] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[46] +1 1 +.names rs1_data[15] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[47] +1 1 +.names rs1_data[16] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[48] +1 1 +.names rs1_data[17] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[49] +1 1 +.names rs1_data[18] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[50] +1 1 +.names rs1_data[19] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[51] +1 1 +.names rs1_data[20] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[52] +1 1 +.names rs1_data[21] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[53] +1 1 +.names rs1_data[22] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[54] +1 1 +.names rs1_data[23] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[55] +1 1 +.names rs1_data[24] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[56] +1 1 +.names rs1_data[25] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[57] +1 1 +.names rs1_data[26] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[58] +1 1 +.names rs1_data[27] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[59] +1 1 +.names rs1_data[28] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[60] +1 1 +.names rs1_data[29] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[61] +1 1 +.names rs1_data[30] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[62] +1 1 +.names rs1_data[31] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[63] +1 1 +.names rs1_data[0] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][0] +1 1 +.names rs1_data[1] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][1] +1 1 +.names rs1_data[2] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][2] +1 1 +.names rs1_data[3] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][3] +1 1 +.names rs1_data[4] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][4] +1 1 +.names rs1_data[5] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][5] +1 1 +.names rs1_data[6] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][6] +1 1 +.names rs1_data[7] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][7] +1 1 +.names rs1_data[8] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][8] +1 1 +.names rs1_data[9] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][9] +1 1 +.names rs1_data[10] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][10] +1 1 +.names rs1_data[11] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][11] +1 1 +.names rs1_data[12] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][12] +1 1 +.names rs1_data[13] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][13] +1 1 +.names rs1_data[14] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][14] +1 1 +.names rs1_data[15] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][15] +1 1 +.names rs1_data[16] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][16] +1 1 +.names rs1_data[17] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][17] +1 1 +.names rs1_data[18] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][18] +1 1 +.names rs1_data[19] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][19] +1 1 +.names rs1_data[20] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][20] +1 1 +.names rs1_data[21] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][21] +1 1 +.names rs1_data[22] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][22] +1 1 +.names rs1_data[23] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][23] +1 1 +.names rs1_data[24] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][24] +1 1 +.names rs1_data[25] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][25] +1 1 +.names rs1_data[26] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][26] +1 1 +.names rs1_data[27] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][27] +1 1 +.names rs1_data[28] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][28] +1 1 +.names rs1_data[29] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][29] +1 1 +.names rs1_data[30] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][30] +1 1 +.names rs1_data[31] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][31] +1 1 +.names singlecycle_datapath.program_counter.value[0] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][0] +1 1 +.names singlecycle_datapath.program_counter.value[1] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][1] +1 1 +.names singlecycle_datapath.program_counter.value[2] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][2] +1 1 +.names singlecycle_datapath.program_counter.value[3] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][3] +1 1 +.names singlecycle_datapath.program_counter.value[4] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][4] +1 1 +.names singlecycle_datapath.program_counter.value[5] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][5] +1 1 +.names singlecycle_datapath.program_counter.value[6] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][6] +1 1 +.names singlecycle_datapath.program_counter.value[7] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][7] +1 1 +.names singlecycle_datapath.program_counter.value[8] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][8] +1 1 +.names singlecycle_datapath.program_counter.value[9] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][9] +1 1 +.names singlecycle_datapath.program_counter.value[10] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][10] +1 1 +.names singlecycle_datapath.program_counter.value[11] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][11] +1 1 +.names singlecycle_datapath.program_counter.value[12] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][12] +1 1 +.names singlecycle_datapath.program_counter.value[13] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][13] +1 1 +.names singlecycle_datapath.program_counter.value[14] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][14] +1 1 +.names singlecycle_datapath.program_counter.value[15] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][15] +1 1 +.names singlecycle_datapath.program_counter.value[16] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][16] +1 1 +.names singlecycle_datapath.program_counter.value[17] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][17] +1 1 +.names singlecycle_datapath.program_counter.value[18] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][18] +1 1 +.names singlecycle_datapath.program_counter.value[19] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][19] +1 1 +.names singlecycle_datapath.program_counter.value[20] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][20] +1 1 +.names singlecycle_datapath.program_counter.value[21] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][21] +1 1 +.names singlecycle_datapath.program_counter.value[22] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][22] +1 1 +.names singlecycle_datapath.program_counter.value[23] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][23] +1 1 +.names singlecycle_datapath.program_counter.value[24] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][24] +1 1 +.names singlecycle_datapath.program_counter.value[25] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][25] +1 1 +.names singlecycle_datapath.program_counter.value[26] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][26] +1 1 +.names singlecycle_datapath.program_counter.value[27] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][27] +1 1 +.names singlecycle_datapath.program_counter.value[28] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][28] +1 1 +.names singlecycle_datapath.program_counter.value[29] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][29] +1 1 +.names singlecycle_datapath.program_counter.value[30] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][30] +1 1 +.names singlecycle_datapath.program_counter.value[31] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][31] +1 1 +.names rs2_data[0] singlecycle_datapath.mux_operand_b.in0[0] +1 1 +.names rs2_data[1] singlecycle_datapath.mux_operand_b.in0[1] +1 1 +.names rs2_data[2] singlecycle_datapath.mux_operand_b.in0[2] +1 1 +.names rs2_data[3] singlecycle_datapath.mux_operand_b.in0[3] +1 1 +.names rs2_data[4] singlecycle_datapath.mux_operand_b.in0[4] +1 1 +.names rs2_data[5] singlecycle_datapath.mux_operand_b.in0[5] +1 1 +.names rs2_data[6] singlecycle_datapath.mux_operand_b.in0[6] +1 1 +.names rs2_data[7] singlecycle_datapath.mux_operand_b.in0[7] +1 1 +.names rs2_data[8] singlecycle_datapath.mux_operand_b.in0[8] +1 1 +.names rs2_data[9] singlecycle_datapath.mux_operand_b.in0[9] +1 1 +.names rs2_data[10] singlecycle_datapath.mux_operand_b.in0[10] +1 1 +.names rs2_data[11] singlecycle_datapath.mux_operand_b.in0[11] +1 1 +.names rs2_data[12] singlecycle_datapath.mux_operand_b.in0[12] +1 1 +.names rs2_data[13] singlecycle_datapath.mux_operand_b.in0[13] +1 1 +.names rs2_data[14] singlecycle_datapath.mux_operand_b.in0[14] +1 1 +.names rs2_data[15] singlecycle_datapath.mux_operand_b.in0[15] +1 1 +.names rs2_data[16] singlecycle_datapath.mux_operand_b.in0[16] +1 1 +.names rs2_data[17] singlecycle_datapath.mux_operand_b.in0[17] +1 1 +.names rs2_data[18] singlecycle_datapath.mux_operand_b.in0[18] +1 1 +.names rs2_data[19] singlecycle_datapath.mux_operand_b.in0[19] +1 1 +.names rs2_data[20] singlecycle_datapath.mux_operand_b.in0[20] +1 1 +.names rs2_data[21] singlecycle_datapath.mux_operand_b.in0[21] +1 1 +.names rs2_data[22] singlecycle_datapath.mux_operand_b.in0[22] +1 1 +.names rs2_data[23] singlecycle_datapath.mux_operand_b.in0[23] +1 1 +.names rs2_data[24] singlecycle_datapath.mux_operand_b.in0[24] +1 1 +.names rs2_data[25] singlecycle_datapath.mux_operand_b.in0[25] +1 1 +.names rs2_data[26] singlecycle_datapath.mux_operand_b.in0[26] +1 1 +.names rs2_data[27] singlecycle_datapath.mux_operand_b.in0[27] +1 1 +.names rs2_data[28] singlecycle_datapath.mux_operand_b.in0[28] +1 1 +.names rs2_data[29] singlecycle_datapath.mux_operand_b.in0[29] +1 1 +.names rs2_data[30] singlecycle_datapath.mux_operand_b.in0[30] +1 1 +.names rs2_data[31] singlecycle_datapath.mux_operand_b.in0[31] +1 1 +.names rs2_data[0] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[32] +1 1 +.names rs2_data[1] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[33] +1 1 +.names rs2_data[2] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[34] +1 1 +.names rs2_data[3] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[35] +1 1 +.names rs2_data[4] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[36] +1 1 +.names rs2_data[5] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[37] +1 1 +.names rs2_data[6] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[38] +1 1 +.names rs2_data[7] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[39] +1 1 +.names rs2_data[8] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[40] +1 1 +.names rs2_data[9] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[41] +1 1 +.names rs2_data[10] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[42] +1 1 +.names rs2_data[11] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[43] +1 1 +.names rs2_data[12] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[44] +1 1 +.names rs2_data[13] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[45] +1 1 +.names rs2_data[14] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[46] +1 1 +.names rs2_data[15] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[47] +1 1 +.names rs2_data[16] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[48] +1 1 +.names rs2_data[17] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[49] +1 1 +.names rs2_data[18] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[50] +1 1 +.names rs2_data[19] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[51] +1 1 +.names rs2_data[20] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[52] +1 1 +.names rs2_data[21] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[53] +1 1 +.names rs2_data[22] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[54] +1 1 +.names rs2_data[23] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[55] +1 1 +.names rs2_data[24] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[56] +1 1 +.names rs2_data[25] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[57] +1 1 +.names rs2_data[26] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[58] +1 1 +.names rs2_data[27] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[59] +1 1 +.names rs2_data[28] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[60] +1 1 +.names rs2_data[29] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[61] +1 1 +.names rs2_data[30] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[62] +1 1 +.names rs2_data[31] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[63] +1 1 +.names rs2_data[0] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][0] +1 1 +.names rs2_data[1] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][1] +1 1 +.names rs2_data[2] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][2] +1 1 +.names rs2_data[3] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][3] +1 1 +.names rs2_data[4] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][4] +1 1 +.names rs2_data[5] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][5] +1 1 +.names rs2_data[6] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][6] +1 1 +.names rs2_data[7] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][7] +1 1 +.names rs2_data[8] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][8] +1 1 +.names rs2_data[9] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][9] +1 1 +.names rs2_data[10] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][10] +1 1 +.names rs2_data[11] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][11] +1 1 +.names rs2_data[12] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][12] +1 1 +.names rs2_data[13] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][13] +1 1 +.names rs2_data[14] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][14] +1 1 +.names rs2_data[15] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][15] +1 1 +.names rs2_data[16] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][16] +1 1 +.names rs2_data[17] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][17] +1 1 +.names rs2_data[18] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][18] +1 1 +.names rs2_data[19] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][19] +1 1 +.names rs2_data[20] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][20] +1 1 +.names rs2_data[21] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][21] +1 1 +.names rs2_data[22] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][22] +1 1 +.names rs2_data[23] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][23] +1 1 +.names rs2_data[24] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][24] +1 1 +.names rs2_data[25] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][25] +1 1 +.names rs2_data[26] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][26] +1 1 +.names rs2_data[27] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][27] +1 1 +.names rs2_data[28] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][28] +1 1 +.names rs2_data[29] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][29] +1 1 +.names rs2_data[30] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][30] +1 1 +.names rs2_data[31] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][31] +1 1 +.names bus_address[0] singlecycle_datapath.mux_reg_writeback.in0[0] +1 1 +.names bus_address[1] singlecycle_datapath.mux_reg_writeback.in0[1] +1 1 +.names bus_address[2] singlecycle_datapath.mux_reg_writeback.in0[2] +1 1 +.names bus_address[3] singlecycle_datapath.mux_reg_writeback.in0[3] +1 1 +.names bus_address[4] singlecycle_datapath.mux_reg_writeback.in0[4] +1 1 +.names bus_address[5] singlecycle_datapath.mux_reg_writeback.in0[5] +1 1 +.names bus_address[6] singlecycle_datapath.mux_reg_writeback.in0[6] +1 1 +.names bus_address[7] singlecycle_datapath.mux_reg_writeback.in0[7] +1 1 +.names bus_address[8] singlecycle_datapath.mux_reg_writeback.in0[8] +1 1 +.names bus_address[9] singlecycle_datapath.mux_reg_writeback.in0[9] +1 1 +.names bus_address[10] singlecycle_datapath.mux_reg_writeback.in0[10] +1 1 +.names bus_address[11] singlecycle_datapath.mux_reg_writeback.in0[11] +1 1 +.names bus_address[12] singlecycle_datapath.mux_reg_writeback.in0[12] +1 1 +.names bus_address[13] singlecycle_datapath.mux_reg_writeback.in0[13] +1 1 +.names bus_address[14] singlecycle_datapath.mux_reg_writeback.in0[14] +1 1 +.names bus_address[15] singlecycle_datapath.mux_reg_writeback.in0[15] +1 1 +.names bus_address[16] singlecycle_datapath.mux_reg_writeback.in0[16] +1 1 +.names bus_address[17] singlecycle_datapath.mux_reg_writeback.in0[17] +1 1 +.names bus_address[18] singlecycle_datapath.mux_reg_writeback.in0[18] +1 1 +.names bus_address[19] singlecycle_datapath.mux_reg_writeback.in0[19] +1 1 +.names bus_address[20] singlecycle_datapath.mux_reg_writeback.in0[20] +1 1 +.names bus_address[21] singlecycle_datapath.mux_reg_writeback.in0[21] +1 1 +.names bus_address[22] singlecycle_datapath.mux_reg_writeback.in0[22] +1 1 +.names bus_address[23] singlecycle_datapath.mux_reg_writeback.in0[23] +1 1 +.names bus_address[24] singlecycle_datapath.mux_reg_writeback.in0[24] +1 1 +.names bus_address[25] singlecycle_datapath.mux_reg_writeback.in0[25] +1 1 +.names bus_address[26] singlecycle_datapath.mux_reg_writeback.in0[26] +1 1 +.names bus_address[27] singlecycle_datapath.mux_reg_writeback.in0[27] +1 1 +.names bus_address[28] singlecycle_datapath.mux_reg_writeback.in0[28] +1 1 +.names bus_address[29] singlecycle_datapath.mux_reg_writeback.in0[29] +1 1 +.names bus_address[30] singlecycle_datapath.mux_reg_writeback.in0[30] +1 1 +.names bus_address[31] singlecycle_datapath.mux_reg_writeback.in0[31] +1 1 +.names singlecycle_datapath.program_counter.value[0] singlecycle_datapath.mux_reg_writeback.in2[0] +1 1 +.names singlecycle_datapath.program_counter.value[1] singlecycle_datapath.mux_reg_writeback.in2[1] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[2] singlecycle_datapath.mux_reg_writeback.in2[2] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[3] singlecycle_datapath.mux_reg_writeback.in2[3] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[4] singlecycle_datapath.mux_reg_writeback.in2[4] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[5] singlecycle_datapath.mux_reg_writeback.in2[5] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[6] singlecycle_datapath.mux_reg_writeback.in2[6] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[7] singlecycle_datapath.mux_reg_writeback.in2[7] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[8] singlecycle_datapath.mux_reg_writeback.in2[8] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[9] singlecycle_datapath.mux_reg_writeback.in2[9] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[10] singlecycle_datapath.mux_reg_writeback.in2[10] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[11] singlecycle_datapath.mux_reg_writeback.in2[11] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[12] singlecycle_datapath.mux_reg_writeback.in2[12] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[13] singlecycle_datapath.mux_reg_writeback.in2[13] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[14] singlecycle_datapath.mux_reg_writeback.in2[14] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[15] singlecycle_datapath.mux_reg_writeback.in2[15] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[16] singlecycle_datapath.mux_reg_writeback.in2[16] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[17] singlecycle_datapath.mux_reg_writeback.in2[17] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[18] singlecycle_datapath.mux_reg_writeback.in2[18] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[19] singlecycle_datapath.mux_reg_writeback.in2[19] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[20] singlecycle_datapath.mux_reg_writeback.in2[20] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[21] singlecycle_datapath.mux_reg_writeback.in2[21] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[22] singlecycle_datapath.mux_reg_writeback.in2[22] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[23] singlecycle_datapath.mux_reg_writeback.in2[23] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[24] singlecycle_datapath.mux_reg_writeback.in2[24] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[25] singlecycle_datapath.mux_reg_writeback.in2[25] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[26] singlecycle_datapath.mux_reg_writeback.in2[26] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[27] singlecycle_datapath.mux_reg_writeback.in2[27] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[28] singlecycle_datapath.mux_reg_writeback.in2[28] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[29] singlecycle_datapath.mux_reg_writeback.in2[29] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[30] singlecycle_datapath.mux_reg_writeback.in2[30] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[31] singlecycle_datapath.mux_reg_writeback.in2[31] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in4[0] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in4[1] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in4[2] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in4[3] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in4[4] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in4[5] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in4[6] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in4[7] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in4[8] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in4[9] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in4[10] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in4[11] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in4[12] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in4[13] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in4[14] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in4[15] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in4[16] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in4[17] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in4[18] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in4[19] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in4[20] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in4[21] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in4[22] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in4[23] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in4[24] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in4[25] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in4[26] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in4[27] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in4[28] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in4[29] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in4[30] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in4[31] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in5[0] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in5[1] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in5[2] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in5[3] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in5[4] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in5[5] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in5[6] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in5[7] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in5[8] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in5[9] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in5[10] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in5[11] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in5[12] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in5[13] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in5[14] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in5[15] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in5[16] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in5[17] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in5[18] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in5[19] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in5[20] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in5[21] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in5[22] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in5[23] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in5[24] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in5[25] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in5[26] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in5[27] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in5[28] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in5[29] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in5[30] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in5[31] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in6[0] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in6[1] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in6[2] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in6[3] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in6[4] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in6[5] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in6[6] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in6[7] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in6[8] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in6[9] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in6[10] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in6[11] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in6[12] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in6[13] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in6[14] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in6[15] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in6[16] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in6[17] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in6[18] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in6[19] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in6[20] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in6[21] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in6[22] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in6[23] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in6[24] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in6[25] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in6[26] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in6[27] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in6[28] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in6[29] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in6[30] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in6[31] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in7[0] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in7[1] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in7[2] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in7[3] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in7[4] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in7[5] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in7[6] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in7[7] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in7[8] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in7[9] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in7[10] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in7[11] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in7[12] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in7[13] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in7[14] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in7[15] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in7[16] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in7[17] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in7[18] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in7[19] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in7[20] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in7[21] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in7[22] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in7[23] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in7[24] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in7[25] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in7[26] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in7[27] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in7[28] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in7[29] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in7[30] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.in7[31] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[0] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[1] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[2] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[3] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[4] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[5] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[6] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[7] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[8] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[9] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[10] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[11] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[12] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[13] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[14] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[15] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[16] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[17] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[18] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[19] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[20] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[21] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[22] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[23] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[24] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[25] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[26] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[27] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[28] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[29] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[30] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[31] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[32] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[33] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[34] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[35] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[36] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[37] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[38] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[39] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[40] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[41] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[42] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[43] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[44] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[45] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[46] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[47] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[48] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[49] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[50] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[51] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[52] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[53] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[54] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[55] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[56] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[57] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[58] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[59] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[60] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[61] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[62] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[63] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[64] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[65] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[66] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[67] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[68] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[69] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[70] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[71] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[72] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[73] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[74] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[75] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[76] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[77] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[78] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[79] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[80] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[81] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[82] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[83] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[84] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[85] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[86] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[87] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[88] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[89] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[90] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[91] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[92] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[93] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[94] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[95] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[96] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[97] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[98] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[99] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[100] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[101] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[102] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[103] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[104] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[105] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[106] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[107] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[108] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[109] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[110] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[111] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[112] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[113] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[114] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[115] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[116] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[117] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[118] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[119] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[120] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[121] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[122] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[123] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[124] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[125] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[126] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[127] +1 1 +.names singlecycle_datapath.program_counter.value[0] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[160] +1 1 +.names singlecycle_datapath.program_counter.value[1] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[161] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[2] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[162] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[3] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[163] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[4] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[164] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[5] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[165] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[6] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[166] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[7] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[167] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[8] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[168] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[9] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[169] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[10] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[170] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[11] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[171] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[12] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[172] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[13] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[173] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[14] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[174] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[15] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[175] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[16] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[176] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[17] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[177] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[18] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[178] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[19] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[179] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[20] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[180] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[21] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[181] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[22] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[182] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[23] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[183] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[24] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[184] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[25] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[185] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[26] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[186] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[27] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[187] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[28] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[188] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[29] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[189] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[30] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[190] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[31] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[191] +1 1 +.names bus_address[0] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[224] +1 1 +.names bus_address[1] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[225] +1 1 +.names bus_address[2] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[226] +1 1 +.names bus_address[3] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[227] +1 1 +.names bus_address[4] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[228] +1 1 +.names bus_address[5] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[229] +1 1 +.names bus_address[6] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[230] +1 1 +.names bus_address[7] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[231] +1 1 +.names bus_address[8] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[232] +1 1 +.names bus_address[9] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[233] +1 1 +.names bus_address[10] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[234] +1 1 +.names bus_address[11] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[235] +1 1 +.names bus_address[12] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[236] +1 1 +.names bus_address[13] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[237] +1 1 +.names bus_address[14] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[238] +1 1 +.names bus_address[15] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[239] +1 1 +.names bus_address[16] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[240] +1 1 +.names bus_address[17] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[241] +1 1 +.names bus_address[18] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[242] +1 1 +.names bus_address[19] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[243] +1 1 +.names bus_address[20] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[244] +1 1 +.names bus_address[21] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[245] +1 1 +.names bus_address[22] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[246] +1 1 +.names bus_address[23] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[247] +1 1 +.names bus_address[24] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[248] +1 1 +.names bus_address[25] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[249] +1 1 +.names bus_address[26] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[250] +1 1 +.names bus_address[27] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[251] +1 1 +.names bus_address[28] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[252] +1 1 +.names bus_address[29] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[253] +1 1 +.names bus_address[30] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[254] +1 1 +.names bus_address[31] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[255] +1 1 +.names bus_address[0] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][0] +1 1 +.names bus_address[1] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][1] +1 1 +.names bus_address[2] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][2] +1 1 +.names bus_address[3] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][3] +1 1 +.names bus_address[4] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][4] +1 1 +.names bus_address[5] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][5] +1 1 +.names bus_address[6] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][6] +1 1 +.names bus_address[7] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][7] +1 1 +.names bus_address[8] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][8] +1 1 +.names bus_address[9] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][9] +1 1 +.names bus_address[10] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][10] +1 1 +.names bus_address[11] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][11] +1 1 +.names bus_address[12] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][12] +1 1 +.names bus_address[13] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][13] +1 1 +.names bus_address[14] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][14] +1 1 +.names bus_address[15] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][15] +1 1 +.names bus_address[16] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][16] +1 1 +.names bus_address[17] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][17] +1 1 +.names bus_address[18] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][18] +1 1 +.names bus_address[19] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][19] +1 1 +.names bus_address[20] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][20] +1 1 +.names bus_address[21] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][21] +1 1 +.names bus_address[22] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][22] +1 1 +.names bus_address[23] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][23] +1 1 +.names bus_address[24] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][24] +1 1 +.names bus_address[25] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][25] +1 1 +.names bus_address[26] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][26] +1 1 +.names bus_address[27] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][27] +1 1 +.names bus_address[28] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][28] +1 1 +.names bus_address[29] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][29] +1 1 +.names bus_address[30] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][30] +1 1 +.names bus_address[31] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][31] +1 1 +.names singlecycle_datapath.program_counter.value[0] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][0] +1 1 +.names singlecycle_datapath.program_counter.value[1] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][1] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[2] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][2] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[3] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][3] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[4] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][4] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[5] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][5] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[6] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][6] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[7] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][7] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[8] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][8] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[9] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][9] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[10] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][10] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[11] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][11] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[12] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][12] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[13] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][13] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[14] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][14] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[15] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][15] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[16] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][16] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[17] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][17] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[18] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][18] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[19] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][19] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[20] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][20] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[21] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][21] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[22] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][22] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[23] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][23] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[24] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][24] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[25] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][25] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[26] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][26] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[27] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][27] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[28] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][28] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[29] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][29] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[30] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][30] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[31] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][31] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][0] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][1] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][2] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][3] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][4] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][5] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][6] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][7] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][8] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][9] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][10] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][11] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][12] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][13] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][14] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][15] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][16] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][17] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][18] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][19] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][20] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][21] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][22] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][23] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][24] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][25] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][26] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][27] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][28] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][29] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][30] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][31] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][0] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][1] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][2] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][3] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][4] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][5] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][6] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][7] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][8] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][9] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][10] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][11] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][12] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][13] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][14] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][15] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][16] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][17] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][18] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][19] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][20] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][21] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][22] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][23] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][24] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][25] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][26] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][27] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][28] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][29] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][30] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][31] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][0] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][1] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][2] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][3] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][4] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][5] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][6] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][7] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][8] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][9] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][10] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][11] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][12] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][13] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][14] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][15] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][16] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][17] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][18] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][19] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][20] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][21] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][22] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][23] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][24] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][25] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][26] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][27] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][28] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][29] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][30] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][31] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][0] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][1] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][2] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][3] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][4] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][5] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][6] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][7] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][8] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][9] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][10] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][11] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][12] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][13] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][14] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][15] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][16] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][17] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][18] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][19] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][20] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][21] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][22] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][23] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][24] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][25] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][26] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][27] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][28] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][29] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][30] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][31] +1 1 +.names rd_data[0] singlecycle_datapath.mux_reg_writeback.multiplexer.out[0] +1 1 +.names rd_data[1] singlecycle_datapath.mux_reg_writeback.multiplexer.out[1] +1 1 +.names rd_data[2] singlecycle_datapath.mux_reg_writeback.multiplexer.out[2] +1 1 +.names rd_data[3] singlecycle_datapath.mux_reg_writeback.multiplexer.out[3] +1 1 +.names rd_data[4] singlecycle_datapath.mux_reg_writeback.multiplexer.out[4] +1 1 +.names rd_data[5] singlecycle_datapath.mux_reg_writeback.multiplexer.out[5] +1 1 +.names rd_data[6] singlecycle_datapath.mux_reg_writeback.multiplexer.out[6] +1 1 +.names rd_data[7] singlecycle_datapath.mux_reg_writeback.multiplexer.out[7] +1 1 +.names rd_data[8] singlecycle_datapath.mux_reg_writeback.multiplexer.out[8] +1 1 +.names rd_data[9] singlecycle_datapath.mux_reg_writeback.multiplexer.out[9] +1 1 +.names rd_data[10] singlecycle_datapath.mux_reg_writeback.multiplexer.out[10] +1 1 +.names rd_data[11] singlecycle_datapath.mux_reg_writeback.multiplexer.out[11] +1 1 +.names rd_data[12] singlecycle_datapath.mux_reg_writeback.multiplexer.out[12] +1 1 +.names rd_data[13] singlecycle_datapath.mux_reg_writeback.multiplexer.out[13] +1 1 +.names rd_data[14] singlecycle_datapath.mux_reg_writeback.multiplexer.out[14] +1 1 +.names rd_data[15] singlecycle_datapath.mux_reg_writeback.multiplexer.out[15] +1 1 +.names rd_data[16] singlecycle_datapath.mux_reg_writeback.multiplexer.out[16] +1 1 +.names rd_data[17] singlecycle_datapath.mux_reg_writeback.multiplexer.out[17] +1 1 +.names rd_data[18] singlecycle_datapath.mux_reg_writeback.multiplexer.out[18] +1 1 +.names rd_data[19] singlecycle_datapath.mux_reg_writeback.multiplexer.out[19] +1 1 +.names rd_data[20] singlecycle_datapath.mux_reg_writeback.multiplexer.out[20] +1 1 +.names rd_data[21] singlecycle_datapath.mux_reg_writeback.multiplexer.out[21] +1 1 +.names rd_data[22] singlecycle_datapath.mux_reg_writeback.multiplexer.out[22] +1 1 +.names rd_data[23] singlecycle_datapath.mux_reg_writeback.multiplexer.out[23] +1 1 +.names rd_data[24] singlecycle_datapath.mux_reg_writeback.multiplexer.out[24] +1 1 +.names rd_data[25] singlecycle_datapath.mux_reg_writeback.multiplexer.out[25] +1 1 +.names rd_data[26] singlecycle_datapath.mux_reg_writeback.multiplexer.out[26] +1 1 +.names rd_data[27] singlecycle_datapath.mux_reg_writeback.multiplexer.out[27] +1 1 +.names rd_data[28] singlecycle_datapath.mux_reg_writeback.multiplexer.out[28] +1 1 +.names rd_data[29] singlecycle_datapath.mux_reg_writeback.multiplexer.out[29] +1 1 +.names rd_data[30] singlecycle_datapath.mux_reg_writeback.multiplexer.out[30] +1 1 +.names rd_data[31] singlecycle_datapath.mux_reg_writeback.multiplexer.out[31] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.sel[2] +1 1 +.names rd_data[0] singlecycle_datapath.mux_reg_writeback.out[0] +1 1 +.names rd_data[1] singlecycle_datapath.mux_reg_writeback.out[1] +1 1 +.names rd_data[2] singlecycle_datapath.mux_reg_writeback.out[2] +1 1 +.names rd_data[3] singlecycle_datapath.mux_reg_writeback.out[3] +1 1 +.names rd_data[4] singlecycle_datapath.mux_reg_writeback.out[4] +1 1 +.names rd_data[5] singlecycle_datapath.mux_reg_writeback.out[5] +1 1 +.names rd_data[6] singlecycle_datapath.mux_reg_writeback.out[6] +1 1 +.names rd_data[7] singlecycle_datapath.mux_reg_writeback.out[7] +1 1 +.names rd_data[8] singlecycle_datapath.mux_reg_writeback.out[8] +1 1 +.names rd_data[9] singlecycle_datapath.mux_reg_writeback.out[9] +1 1 +.names rd_data[10] singlecycle_datapath.mux_reg_writeback.out[10] +1 1 +.names rd_data[11] singlecycle_datapath.mux_reg_writeback.out[11] +1 1 +.names rd_data[12] singlecycle_datapath.mux_reg_writeback.out[12] +1 1 +.names rd_data[13] singlecycle_datapath.mux_reg_writeback.out[13] +1 1 +.names rd_data[14] singlecycle_datapath.mux_reg_writeback.out[14] +1 1 +.names rd_data[15] singlecycle_datapath.mux_reg_writeback.out[15] +1 1 +.names rd_data[16] singlecycle_datapath.mux_reg_writeback.out[16] +1 1 +.names rd_data[17] singlecycle_datapath.mux_reg_writeback.out[17] +1 1 +.names rd_data[18] singlecycle_datapath.mux_reg_writeback.out[18] +1 1 +.names rd_data[19] singlecycle_datapath.mux_reg_writeback.out[19] +1 1 +.names rd_data[20] singlecycle_datapath.mux_reg_writeback.out[20] +1 1 +.names rd_data[21] singlecycle_datapath.mux_reg_writeback.out[21] +1 1 +.names rd_data[22] singlecycle_datapath.mux_reg_writeback.out[22] +1 1 +.names rd_data[23] singlecycle_datapath.mux_reg_writeback.out[23] +1 1 +.names rd_data[24] singlecycle_datapath.mux_reg_writeback.out[24] +1 1 +.names rd_data[25] singlecycle_datapath.mux_reg_writeback.out[25] +1 1 +.names rd_data[26] singlecycle_datapath.mux_reg_writeback.out[26] +1 1 +.names rd_data[27] singlecycle_datapath.mux_reg_writeback.out[27] +1 1 +.names rd_data[28] singlecycle_datapath.mux_reg_writeback.out[28] +1 1 +.names rd_data[29] singlecycle_datapath.mux_reg_writeback.out[29] +1 1 +.names rd_data[30] singlecycle_datapath.mux_reg_writeback.out[30] +1 1 +.names rd_data[31] singlecycle_datapath.mux_reg_writeback.out[31] +1 1 +.names $false singlecycle_datapath.mux_reg_writeback.sel[2] +1 1 +.names singlecycle_datapath.program_counter.value[0] singlecycle_datapath.pc[0] +1 1 +.names singlecycle_datapath.program_counter.value[1] singlecycle_datapath.pc[1] +1 1 +.names singlecycle_datapath.program_counter.value[2] singlecycle_datapath.pc[2] +1 1 +.names singlecycle_datapath.program_counter.value[3] singlecycle_datapath.pc[3] +1 1 +.names singlecycle_datapath.program_counter.value[4] singlecycle_datapath.pc[4] +1 1 +.names singlecycle_datapath.program_counter.value[5] singlecycle_datapath.pc[5] +1 1 +.names singlecycle_datapath.program_counter.value[6] singlecycle_datapath.pc[6] +1 1 +.names singlecycle_datapath.program_counter.value[7] singlecycle_datapath.pc[7] +1 1 +.names singlecycle_datapath.program_counter.value[8] singlecycle_datapath.pc[8] +1 1 +.names singlecycle_datapath.program_counter.value[9] singlecycle_datapath.pc[9] +1 1 +.names singlecycle_datapath.program_counter.value[10] singlecycle_datapath.pc[10] +1 1 +.names singlecycle_datapath.program_counter.value[11] singlecycle_datapath.pc[11] +1 1 +.names singlecycle_datapath.program_counter.value[12] singlecycle_datapath.pc[12] +1 1 +.names singlecycle_datapath.program_counter.value[13] singlecycle_datapath.pc[13] +1 1 +.names singlecycle_datapath.program_counter.value[14] singlecycle_datapath.pc[14] +1 1 +.names singlecycle_datapath.program_counter.value[15] singlecycle_datapath.pc[15] +1 1 +.names singlecycle_datapath.program_counter.value[16] singlecycle_datapath.pc[16] +1 1 +.names singlecycle_datapath.program_counter.value[17] singlecycle_datapath.pc[17] +1 1 +.names singlecycle_datapath.program_counter.value[18] singlecycle_datapath.pc[18] +1 1 +.names singlecycle_datapath.program_counter.value[19] singlecycle_datapath.pc[19] +1 1 +.names singlecycle_datapath.program_counter.value[20] singlecycle_datapath.pc[20] +1 1 +.names singlecycle_datapath.program_counter.value[21] singlecycle_datapath.pc[21] +1 1 +.names singlecycle_datapath.program_counter.value[22] singlecycle_datapath.pc[22] +1 1 +.names singlecycle_datapath.program_counter.value[23] singlecycle_datapath.pc[23] +1 1 +.names singlecycle_datapath.program_counter.value[24] singlecycle_datapath.pc[24] +1 1 +.names singlecycle_datapath.program_counter.value[25] singlecycle_datapath.pc[25] +1 1 +.names singlecycle_datapath.program_counter.value[26] singlecycle_datapath.pc[26] +1 1 +.names singlecycle_datapath.program_counter.value[27] singlecycle_datapath.pc[27] +1 1 +.names singlecycle_datapath.program_counter.value[28] singlecycle_datapath.pc[28] +1 1 +.names singlecycle_datapath.program_counter.value[29] singlecycle_datapath.pc[29] +1 1 +.names singlecycle_datapath.program_counter.value[30] singlecycle_datapath.pc[30] +1 1 +.names singlecycle_datapath.program_counter.value[31] singlecycle_datapath.pc[31] +1 1 +.names singlecycle_datapath.program_counter.value[0] singlecycle_datapath.pc_plus_4[0] +1 1 +.names singlecycle_datapath.program_counter.value[1] singlecycle_datapath.pc_plus_4[1] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[2] singlecycle_datapath.pc_plus_4[2] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[3] singlecycle_datapath.pc_plus_4[3] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[4] singlecycle_datapath.pc_plus_4[4] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[5] singlecycle_datapath.pc_plus_4[5] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[6] singlecycle_datapath.pc_plus_4[6] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[7] singlecycle_datapath.pc_plus_4[7] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[8] singlecycle_datapath.pc_plus_4[8] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[9] singlecycle_datapath.pc_plus_4[9] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[10] singlecycle_datapath.pc_plus_4[10] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[11] singlecycle_datapath.pc_plus_4[11] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[12] singlecycle_datapath.pc_plus_4[12] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[13] singlecycle_datapath.pc_plus_4[13] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[14] singlecycle_datapath.pc_plus_4[14] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[15] singlecycle_datapath.pc_plus_4[15] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[16] singlecycle_datapath.pc_plus_4[16] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[17] singlecycle_datapath.pc_plus_4[17] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[18] singlecycle_datapath.pc_plus_4[18] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[19] singlecycle_datapath.pc_plus_4[19] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[20] singlecycle_datapath.pc_plus_4[20] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[21] singlecycle_datapath.pc_plus_4[21] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[22] singlecycle_datapath.pc_plus_4[22] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[23] singlecycle_datapath.pc_plus_4[23] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[24] singlecycle_datapath.pc_plus_4[24] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[25] singlecycle_datapath.pc_plus_4[25] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[26] singlecycle_datapath.pc_plus_4[26] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[27] singlecycle_datapath.pc_plus_4[27] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[28] singlecycle_datapath.pc_plus_4[28] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[29] singlecycle_datapath.pc_plus_4[29] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[30] singlecycle_datapath.pc_plus_4[30] +1 1 +.names singlecycle_datapath.adder_pc_plus_4.result[31] singlecycle_datapath.pc_plus_4[31] +1 1 +.names $true singlecycle_datapath.pc_write_enable +1 1 +.names clock singlecycle_datapath.program_counter.clock +1 1 +.names reset singlecycle_datapath.program_counter.reset +1 1 +.names $true singlecycle_datapath.program_counter.write_enable +1 1 +.names inst[7] singlecycle_datapath.rd_address[0] +1 1 +.names $false singlecycle_datapath.rd_address[1] +1 1 +.names $false singlecycle_datapath.rd_address[2] +1 1 +.names $false singlecycle_datapath.rd_address[3] +1 1 +.names $false singlecycle_datapath.rd_address[4] +1 1 +.names rd_data[0] singlecycle_datapath.rd_data[0] +1 1 +.names rd_data[1] singlecycle_datapath.rd_data[1] +1 1 +.names rd_data[2] singlecycle_datapath.rd_data[2] +1 1 +.names rd_data[3] singlecycle_datapath.rd_data[3] +1 1 +.names rd_data[4] singlecycle_datapath.rd_data[4] +1 1 +.names rd_data[5] singlecycle_datapath.rd_data[5] +1 1 +.names rd_data[6] singlecycle_datapath.rd_data[6] +1 1 +.names rd_data[7] singlecycle_datapath.rd_data[7] +1 1 +.names rd_data[8] singlecycle_datapath.rd_data[8] +1 1 +.names rd_data[9] singlecycle_datapath.rd_data[9] +1 1 +.names rd_data[10] singlecycle_datapath.rd_data[10] +1 1 +.names rd_data[11] singlecycle_datapath.rd_data[11] +1 1 +.names rd_data[12] singlecycle_datapath.rd_data[12] +1 1 +.names rd_data[13] singlecycle_datapath.rd_data[13] +1 1 +.names rd_data[14] singlecycle_datapath.rd_data[14] +1 1 +.names rd_data[15] singlecycle_datapath.rd_data[15] +1 1 +.names rd_data[16] singlecycle_datapath.rd_data[16] +1 1 +.names rd_data[17] singlecycle_datapath.rd_data[17] +1 1 +.names rd_data[18] singlecycle_datapath.rd_data[18] +1 1 +.names rd_data[19] singlecycle_datapath.rd_data[19] +1 1 +.names rd_data[20] singlecycle_datapath.rd_data[20] +1 1 +.names rd_data[21] singlecycle_datapath.rd_data[21] +1 1 +.names rd_data[22] singlecycle_datapath.rd_data[22] +1 1 +.names rd_data[23] singlecycle_datapath.rd_data[23] +1 1 +.names rd_data[24] singlecycle_datapath.rd_data[24] +1 1 +.names rd_data[25] singlecycle_datapath.rd_data[25] +1 1 +.names rd_data[26] singlecycle_datapath.rd_data[26] +1 1 +.names rd_data[27] singlecycle_datapath.rd_data[27] +1 1 +.names rd_data[28] singlecycle_datapath.rd_data[28] +1 1 +.names rd_data[29] singlecycle_datapath.rd_data[29] +1 1 +.names rd_data[30] singlecycle_datapath.rd_data[30] +1 1 +.names rd_data[31] singlecycle_datapath.rd_data[31] +1 1 +.names $false singlecycle_datapath.reg_writeback_select[2] +1 1 +.names reset singlecycle_datapath.reset +1 1 +.names inst[15] singlecycle_datapath.rs1_address[0] +1 1 +.names $false singlecycle_datapath.rs1_address[1] +1 1 +.names $false singlecycle_datapath.rs1_address[2] +1 1 +.names $false singlecycle_datapath.rs1_address[3] +1 1 +.names $false singlecycle_datapath.rs1_address[4] +1 1 +.names rs1_data[0] singlecycle_datapath.rs1_data[0] +1 1 +.names rs1_data[1] singlecycle_datapath.rs1_data[1] +1 1 +.names rs1_data[2] singlecycle_datapath.rs1_data[2] +1 1 +.names rs1_data[3] singlecycle_datapath.rs1_data[3] +1 1 +.names rs1_data[4] singlecycle_datapath.rs1_data[4] +1 1 +.names rs1_data[5] singlecycle_datapath.rs1_data[5] +1 1 +.names rs1_data[6] singlecycle_datapath.rs1_data[6] +1 1 +.names rs1_data[7] singlecycle_datapath.rs1_data[7] +1 1 +.names rs1_data[8] singlecycle_datapath.rs1_data[8] +1 1 +.names rs1_data[9] singlecycle_datapath.rs1_data[9] +1 1 +.names rs1_data[10] singlecycle_datapath.rs1_data[10] +1 1 +.names rs1_data[11] singlecycle_datapath.rs1_data[11] +1 1 +.names rs1_data[12] singlecycle_datapath.rs1_data[12] +1 1 +.names rs1_data[13] singlecycle_datapath.rs1_data[13] +1 1 +.names rs1_data[14] singlecycle_datapath.rs1_data[14] +1 1 +.names rs1_data[15] singlecycle_datapath.rs1_data[15] +1 1 +.names rs1_data[16] singlecycle_datapath.rs1_data[16] +1 1 +.names rs1_data[17] singlecycle_datapath.rs1_data[17] +1 1 +.names rs1_data[18] singlecycle_datapath.rs1_data[18] +1 1 +.names rs1_data[19] singlecycle_datapath.rs1_data[19] +1 1 +.names rs1_data[20] singlecycle_datapath.rs1_data[20] +1 1 +.names rs1_data[21] singlecycle_datapath.rs1_data[21] +1 1 +.names rs1_data[22] singlecycle_datapath.rs1_data[22] +1 1 +.names rs1_data[23] singlecycle_datapath.rs1_data[23] +1 1 +.names rs1_data[24] singlecycle_datapath.rs1_data[24] +1 1 +.names rs1_data[25] singlecycle_datapath.rs1_data[25] +1 1 +.names rs1_data[26] singlecycle_datapath.rs1_data[26] +1 1 +.names rs1_data[27] singlecycle_datapath.rs1_data[27] +1 1 +.names rs1_data[28] singlecycle_datapath.rs1_data[28] +1 1 +.names rs1_data[29] singlecycle_datapath.rs1_data[29] +1 1 +.names rs1_data[30] singlecycle_datapath.rs1_data[30] +1 1 +.names rs1_data[31] singlecycle_datapath.rs1_data[31] +1 1 +.names inst[20] singlecycle_datapath.rs2_address[0] +1 1 +.names $false singlecycle_datapath.rs2_address[1] +1 1 +.names $false singlecycle_datapath.rs2_address[2] +1 1 +.names $false singlecycle_datapath.rs2_address[3] +1 1 +.names $false singlecycle_datapath.rs2_address[4] +1 1 +.names rs2_data[0] singlecycle_datapath.rs2_data[0] +1 1 +.names rs2_data[1] singlecycle_datapath.rs2_data[1] +1 1 +.names rs2_data[2] singlecycle_datapath.rs2_data[2] +1 1 +.names rs2_data[3] singlecycle_datapath.rs2_data[3] +1 1 +.names rs2_data[4] singlecycle_datapath.rs2_data[4] +1 1 +.names rs2_data[5] singlecycle_datapath.rs2_data[5] +1 1 +.names rs2_data[6] singlecycle_datapath.rs2_data[6] +1 1 +.names rs2_data[7] singlecycle_datapath.rs2_data[7] +1 1 +.names rs2_data[8] singlecycle_datapath.rs2_data[8] +1 1 +.names rs2_data[9] singlecycle_datapath.rs2_data[9] +1 1 +.names rs2_data[10] singlecycle_datapath.rs2_data[10] +1 1 +.names rs2_data[11] singlecycle_datapath.rs2_data[11] +1 1 +.names rs2_data[12] singlecycle_datapath.rs2_data[12] +1 1 +.names rs2_data[13] singlecycle_datapath.rs2_data[13] +1 1 +.names rs2_data[14] singlecycle_datapath.rs2_data[14] +1 1 +.names rs2_data[15] singlecycle_datapath.rs2_data[15] +1 1 +.names rs2_data[16] singlecycle_datapath.rs2_data[16] +1 1 +.names rs2_data[17] singlecycle_datapath.rs2_data[17] +1 1 +.names rs2_data[18] singlecycle_datapath.rs2_data[18] +1 1 +.names rs2_data[19] singlecycle_datapath.rs2_data[19] +1 1 +.names rs2_data[20] singlecycle_datapath.rs2_data[20] +1 1 +.names rs2_data[21] singlecycle_datapath.rs2_data[21] +1 1 +.names rs2_data[22] singlecycle_datapath.rs2_data[22] +1 1 +.names rs2_data[23] singlecycle_datapath.rs2_data[23] +1 1 +.names rs2_data[24] singlecycle_datapath.rs2_data[24] +1 1 +.names rs2_data[25] singlecycle_datapath.rs2_data[25] +1 1 +.names rs2_data[26] singlecycle_datapath.rs2_data[26] +1 1 +.names rs2_data[27] singlecycle_datapath.rs2_data[27] +1 1 +.names rs2_data[28] singlecycle_datapath.rs2_data[28] +1 1 +.names rs2_data[29] singlecycle_datapath.rs2_data[29] +1 1 +.names rs2_data[30] singlecycle_datapath.rs2_data[30] +1 1 +.names rs2_data[31] singlecycle_datapath.rs2_data[31] +1 1 +.names rs2_data[0] write_data[0] +1 1 +.names rs2_data[1] write_data[1] +1 1 +.names rs2_data[2] write_data[2] +1 1 +.names rs2_data[3] write_data[3] +1 1 +.names rs2_data[4] write_data[4] +1 1 +.names rs2_data[5] write_data[5] +1 1 +.names rs2_data[6] write_data[6] +1 1 +.names rs2_data[7] write_data[7] +1 1 +.names rs2_data[8] write_data[8] +1 1 +.names rs2_data[9] write_data[9] +1 1 +.names rs2_data[10] write_data[10] +1 1 +.names rs2_data[11] write_data[11] +1 1 +.names rs2_data[12] write_data[12] +1 1 +.names rs2_data[13] write_data[13] +1 1 +.names rs2_data[14] write_data[14] +1 1 +.names rs2_data[15] write_data[15] +1 1 +.names rs2_data[16] write_data[16] +1 1 +.names rs2_data[17] write_data[17] +1 1 +.names rs2_data[18] write_data[18] +1 1 +.names rs2_data[19] write_data[19] +1 1 +.names rs2_data[20] write_data[20] +1 1 +.names rs2_data[21] write_data[21] +1 1 +.names rs2_data[22] write_data[22] +1 1 +.names rs2_data[23] write_data[23] +1 1 +.names rs2_data[24] write_data[24] +1 1 +.names rs2_data[25] write_data[25] +1 1 +.names rs2_data[26] write_data[26] +1 1 +.names rs2_data[27] write_data[27] +1 1 +.names rs2_data[28] write_data[28] +1 1 +.names rs2_data[29] write_data[29] +1 1 +.names rs2_data[30] write_data[30] +1 1 +.names rs2_data[31] write_data[31] +1 1 +.names bus_write_enable write_enable +1 1 +.end diff --git a/openfpga/test_script/riscv_core.openfpga b/openfpga/test_script/riscv_core.openfpga new file mode 100644 index 000000000..d2614df44 --- /dev/null +++ b/openfpga/test_script/riscv_core.openfpga @@ -0,0 +1,2 @@ +# Run VPR for the RISC-V core +vpr ./test_arch/k6_frac_N10_frac_chain_depop50_mem32K_40nm.xml ./test_blif/riscv_core_lut6.blif From 2e41138633257245ad6fa6feb1179a61f0a9ab2f Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 26 Jan 2020 17:56:22 -0700 Subject: [PATCH 023/645] use a micro benchmark for vpr quick-run --- openfpga/test_blif/riscv_core_lut6.blif | 19187 ---------------- openfpga/test_blif/s298.blif | 90 + .../{riscv_core.openfpga => s298.openfpga} | 2 +- 3 files changed, 91 insertions(+), 19188 deletions(-) delete mode 100644 openfpga/test_blif/riscv_core_lut6.blif create mode 100644 openfpga/test_blif/s298.blif rename openfpga/test_script/{riscv_core.openfpga => s298.openfpga} (74%) diff --git a/openfpga/test_blif/riscv_core_lut6.blif b/openfpga/test_blif/riscv_core_lut6.blif deleted file mode 100644 index a313bebc1..000000000 --- a/openfpga/test_blif/riscv_core_lut6.blif +++ /dev/null @@ -1,19187 +0,0 @@ -# Generated by Yosys 0.8+599 (git sha1 463f710, gcc 4.8.5 -fPIC -Os) - -.model riscv_core -.inputs clock reset bus_read_data[0] bus_read_data[1] bus_read_data[2] bus_read_data[3] bus_read_data[4] bus_read_data[5] bus_read_data[6] bus_read_data[7] bus_read_data[8] bus_read_data[9] bus_read_data[10] bus_read_data[11] bus_read_data[12] bus_read_data[13] bus_read_data[14] bus_read_data[15] bus_read_data[16] bus_read_data[17] bus_read_data[18] bus_read_data[19] bus_read_data[20] bus_read_data[21] bus_read_data[22] bus_read_data[23] bus_read_data[24] bus_read_data[25] bus_read_data[26] bus_read_data[27] bus_read_data[28] bus_read_data[29] bus_read_data[30] bus_read_data[31] inst[0] inst[1] inst[2] inst[3] inst[4] inst[5] inst[6] inst[7] inst[8] inst[9] inst[10] inst[11] inst[12] inst[13] inst[14] inst[15] inst[16] inst[17] inst[18] inst[19] inst[20] inst[21] inst[22] inst[23] inst[24] inst[25] inst[26] inst[27] inst[28] inst[29] inst[30] inst[31] rs1_data[0] rs1_data[1] rs1_data[2] rs1_data[3] rs1_data[4] rs1_data[5] rs1_data[6] rs1_data[7] rs1_data[8] rs1_data[9] rs1_data[10] rs1_data[11] rs1_data[12] rs1_data[13] rs1_data[14] rs1_data[15] rs1_data[16] rs1_data[17] rs1_data[18] rs1_data[19] rs1_data[20] rs1_data[21] rs1_data[22] rs1_data[23] rs1_data[24] rs1_data[25] rs1_data[26] rs1_data[27] rs1_data[28] rs1_data[29] rs1_data[30] rs1_data[31] rs2_data[0] rs2_data[1] rs2_data[2] rs2_data[3] rs2_data[4] rs2_data[5] rs2_data[6] rs2_data[7] rs2_data[8] rs2_data[9] rs2_data[10] rs2_data[11] rs2_data[12] rs2_data[13] rs2_data[14] rs2_data[15] rs2_data[16] rs2_data[17] rs2_data[18] rs2_data[19] rs2_data[20] rs2_data[21] rs2_data[22] rs2_data[23] rs2_data[24] rs2_data[25] rs2_data[26] rs2_data[27] rs2_data[28] rs2_data[29] rs2_data[30] rs2_data[31] -.outputs bus_address[0] bus_address[1] bus_address[2] bus_address[3] bus_address[4] bus_address[5] bus_address[6] bus_address[7] bus_address[8] bus_address[9] bus_address[10] bus_address[11] bus_address[12] bus_address[13] bus_address[14] bus_address[15] bus_address[16] bus_address[17] bus_address[18] bus_address[19] bus_address[20] bus_address[21] bus_address[22] bus_address[23] bus_address[24] bus_address[25] bus_address[26] bus_address[27] bus_address[28] bus_address[29] bus_address[30] bus_address[31] bus_write_data[0] bus_write_data[1] bus_write_data[2] bus_write_data[3] bus_write_data[4] bus_write_data[5] bus_write_data[6] bus_write_data[7] bus_write_data[8] bus_write_data[9] bus_write_data[10] bus_write_data[11] bus_write_data[12] bus_write_data[13] bus_write_data[14] bus_write_data[15] bus_write_data[16] bus_write_data[17] bus_write_data[18] bus_write_data[19] bus_write_data[20] bus_write_data[21] bus_write_data[22] bus_write_data[23] bus_write_data[24] bus_write_data[25] bus_write_data[26] bus_write_data[27] bus_write_data[28] bus_write_data[29] bus_write_data[30] bus_write_data[31] bus_byte_enable[0] bus_byte_enable[1] bus_byte_enable[2] bus_byte_enable[3] bus_read_enable bus_write_enable pc[0] pc[1] pc[2] pc[3] pc[4] pc[5] pc[6] pc[7] pc[8] pc[9] pc[10] pc[11] pc[12] pc[13] pc[14] pc[15] pc[16] pc[17] pc[18] pc[19] pc[20] pc[21] pc[22] pc[23] pc[24] pc[25] pc[26] pc[27] pc[28] pc[29] pc[30] pc[31] regfile_write_enable rd_address[0] rd_address[1] rd_address[2] rd_address[3] rd_address[4] rs1_address[0] rs1_address[1] rs1_address[2] rs1_address[3] rs1_address[4] rs2_address[0] rs2_address[1] rs2_address[2] rs2_address[3] rs2_address[4] rd_data[0] rd_data[1] rd_data[2] rd_data[3] rd_data[4] rd_data[5] rd_data[6] rd_data[7] rd_data[8] rd_data[9] rd_data[10] rd_data[11] rd_data[12] rd_data[13] rd_data[14] rd_data[15] rd_data[16] rd_data[17] rd_data[18] rd_data[19] rd_data[20] rd_data[21] rd_data[22] rd_data[23] rd_data[24] rd_data[25] rd_data[26] rd_data[27] rd_data[28] rd_data[29] rd_data[30] rd_data[31] -.names $false -.names $true -1 -.names $undef -.names $abc$8517$new_n299_ $abc$8517$new_n303_ inst[2] regfile_write_enable -000 1 -001 1 -010 1 -011 1 -110 1 -.names $abc$8517$new_n302_ $abc$8517$new_n301_ $abc$8517$new_n300_ $abc$8517$new_n299_ -000 1 -.names inst[1] inst[0] inst[2] inst[4] inst[3] inst[6] $abc$8517$new_n300_ -111100 1 -.names inst[1] inst[0] inst[4] inst[2] inst[3] inst[6] $abc$8517$new_n301_ -111000 1 -.names inst[1] inst[0] inst[2] inst[6] inst[5] inst[4] $abc$8517$new_n302_ -111110 1 -.names inst[1] inst[0] inst[3] inst[6] inst[5] inst[4] $abc$8517$new_n303_ -110000 1 -.names $abc$8517$new_n1349_ $abc$8517$new_n1352_ $abc$8517$new_n466_ $abc$8517$new_n493_ $abc$8517$new_n1344_ bus_address[0] -00000 1 -00001 1 -00010 1 -00011 1 -00100 1 -00101 1 -00110 1 -00111 1 -01100 1 -10000 1 -10001 1 -10010 1 -10011 1 -10100 1 -10101 1 -10110 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n307_ $abc$8517$new_n374_ $abc$8517$new_n407_ $abc$8517$new_n404_ $abc$8517$new_n409_ $abc$8517$new_n340_ $abc$8517$new_n306_ -100100 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n308_ $abc$8517$new_n322_ $abc$8517$new_n336_ $abc$8517$new_n307_ -110 1 -.names $abc$8517$new_n319_ $abc$8517$new_n316_ $abc$8517$new_n309_ $abc$8517$new_n308_ -000 1 -.names $abc$8517$new_n315_ $abc$8517$new_n310_ $abc$8517$new_n314_ inst[31] $abc$8517$new_n312_ rs2_data[19] $abc$8517$new_n309_ -000000 1 -000001 1 -000010 1 -000011 1 -000110 1 -000111 1 -010000 1 -010010 1 -010100 1 -010110 1 -011000 1 -011010 1 -011100 1 -011110 1 -100100 1 -100101 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110001 1 -110011 1 -110101 1 -110111 1 -111001 1 -111011 1 -111101 1 -111111 1 -.names $abc$8517$new_n311_ inst[4] inst[6] $abc$8517$new_n302_ inst[5] inst[2] $abc$8517$new_n310_ -000000 1 -000001 1 -000010 1 -000011 1 -001000 1 -001001 1 -001010 1 -001011 1 -010000 1 -010001 1 -010010 1 -010011 1 -011000 1 -011001 1 -011010 1 -011011 1 -100001 1 -100011 1 -101000 1 -101001 1 -101010 1 -101011 1 -110010 1 -110011 1 -111000 1 -111001 1 -111010 1 -111011 1 -.names inst[1] inst[0] inst[3] $abc$8517$new_n311_ -110 1 -.names $abc$8517$new_n313_ inst[3] inst[6] inst[5] inst[4] inst[2] $abc$8517$new_n312_ -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -000110 1 -000111 1 -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -100011 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101110 1 -101111 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names inst[1] inst[0] $abc$8517$new_n313_ -11 1 -.names inst[19] $abc$8517$new_n300_ inst[3] $abc$8517$new_n302_ $abc$8517$new_n314_ -1011 1 -1100 1 -1101 1 -1110 1 -1111 1 -.names rs1_data[19] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[19] $abc$8517$new_n315_ -000000 1 -000001 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001001 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011110 1 -100010 1 -100110 1 -101010 1 -101100 1 -101110 1 -111100 1 -111110 1 -.names $abc$8517$new_n318_ $abc$8517$new_n310_ $abc$8517$new_n317_ inst[31] $abc$8517$new_n312_ rs2_data[18] $abc$8517$new_n316_ -000000 1 -000001 1 -000010 1 -000011 1 -000110 1 -000111 1 -010000 1 -010010 1 -010100 1 -010110 1 -011000 1 -011010 1 -011100 1 -011110 1 -100100 1 -100101 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110001 1 -110011 1 -110101 1 -110111 1 -111001 1 -111011 1 -111101 1 -111111 1 -.names inst[18] $abc$8517$new_n300_ inst[3] $abc$8517$new_n302_ $abc$8517$new_n317_ -1011 1 -1100 1 -1101 1 -1110 1 -1111 1 -.names rs1_data[18] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[18] $abc$8517$new_n318_ -000000 1 -000001 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001001 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011110 1 -100010 1 -100110 1 -101010 1 -101100 1 -101110 1 -111100 1 -111110 1 -.names $abc$8517$new_n321_ $abc$8517$new_n310_ $abc$8517$new_n320_ inst[31] $abc$8517$new_n312_ rs2_data[17] $abc$8517$new_n319_ -000000 1 -000001 1 -000010 1 -000011 1 -000110 1 -000111 1 -010000 1 -010010 1 -010100 1 -010110 1 -011000 1 -011010 1 -011100 1 -011110 1 -100100 1 -100101 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110001 1 -110011 1 -110101 1 -110111 1 -111001 1 -111011 1 -111101 1 -111111 1 -.names inst[17] $abc$8517$new_n300_ inst[3] $abc$8517$new_n302_ $abc$8517$new_n320_ -1011 1 -1100 1 -1101 1 -1110 1 -1111 1 -.names rs1_data[17] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[17] $abc$8517$new_n321_ -000000 1 -000001 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001001 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011110 1 -100010 1 -100110 1 -101010 1 -101100 1 -101110 1 -111100 1 -111110 1 -.names $abc$8517$new_n333_ $abc$8517$new_n330_ $abc$8517$new_n327_ $abc$8517$new_n323_ $abc$8517$new_n322_ -0000 1 -.names $abc$8517$new_n326_ $abc$8517$new_n310_ $abc$8517$new_n325_ inst[31] $abc$8517$new_n324_ rs2_data[23] $abc$8517$new_n323_ -000000 1 -000001 1 -000010 1 -000011 1 -000110 1 -000111 1 -010000 1 -010010 1 -010100 1 -010110 1 -011000 1 -011010 1 -011100 1 -011110 1 -100100 1 -100101 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110001 1 -110011 1 -110101 1 -110111 1 -111001 1 -111011 1 -111101 1 -111111 1 -.names $abc$8517$new_n311_ inst[5] inst[2] inst[6] inst[4] $abc$8517$new_n302_ $abc$8517$new_n324_ -000000 1 -000010 1 -000100 1 -000110 1 -001000 1 -001010 1 -001100 1 -001110 1 -010000 1 -010010 1 -010100 1 -010110 1 -011000 1 -011010 1 -011100 1 -011110 1 -100100 1 -100110 1 -101010 1 -101100 1 -101110 1 -110010 1 -110110 1 -111010 1 -111100 1 -111110 1 -.names inst[23] $abc$8517$new_n300_ $abc$8517$new_n325_ -11 1 -.names rs1_data[23] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[23] $abc$8517$new_n326_ -000000 1 -000001 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001001 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011110 1 -100010 1 -100110 1 -101010 1 -101100 1 -101110 1 -111100 1 -111110 1 -.names $abc$8517$new_n329_ $abc$8517$new_n310_ $abc$8517$new_n328_ inst[31] $abc$8517$new_n324_ rs2_data[22] $abc$8517$new_n327_ -000000 1 -000001 1 -000010 1 -000011 1 -000110 1 -000111 1 -010000 1 -010010 1 -010100 1 -010110 1 -011000 1 -011010 1 -011100 1 -011110 1 -100100 1 -100101 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110001 1 -110011 1 -110101 1 -110111 1 -111001 1 -111011 1 -111101 1 -111111 1 -.names inst[22] $abc$8517$new_n300_ $abc$8517$new_n328_ -11 1 -.names rs1_data[22] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[22] $abc$8517$new_n329_ -000000 1 -000001 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001001 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011110 1 -100010 1 -100110 1 -101010 1 -101100 1 -101110 1 -111100 1 -111110 1 -.names $abc$8517$new_n332_ $abc$8517$new_n310_ $abc$8517$new_n331_ inst[31] $abc$8517$new_n324_ rs2_data[21] $abc$8517$new_n330_ -000000 1 -000001 1 -000010 1 -000011 1 -000110 1 -000111 1 -010000 1 -010010 1 -010100 1 -010110 1 -011000 1 -011010 1 -011100 1 -011110 1 -100100 1 -100101 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110001 1 -110011 1 -110101 1 -110111 1 -111001 1 -111011 1 -111101 1 -111111 1 -.names inst[21] $abc$8517$new_n300_ $abc$8517$new_n331_ -11 1 -.names rs1_data[21] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[21] $abc$8517$new_n332_ -000000 1 -000001 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001001 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011110 1 -100010 1 -100110 1 -101010 1 -101100 1 -101110 1 -111100 1 -111110 1 -.names $abc$8517$new_n335_ $abc$8517$new_n310_ $abc$8517$new_n334_ inst[31] $abc$8517$new_n324_ rs2_data[20] $abc$8517$new_n333_ -000000 1 -000001 1 -000010 1 -000011 1 -000110 1 -000111 1 -010000 1 -010010 1 -010100 1 -010110 1 -011000 1 -011010 1 -011100 1 -011110 1 -100100 1 -100101 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110001 1 -110011 1 -110101 1 -110111 1 -111001 1 -111011 1 -111101 1 -111111 1 -.names inst[20] $abc$8517$new_n300_ $abc$8517$new_n334_ -11 1 -.names rs1_data[20] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[20] $abc$8517$new_n335_ -000000 1 -000001 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001001 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011110 1 -100010 1 -100110 1 -101010 1 -101100 1 -101110 1 -111100 1 -111110 1 -.names $abc$8517$new_n339_ $abc$8517$new_n337_ $abc$8517$new_n336_ -01 1 -10 1 -.names $abc$8517$new_n310_ $abc$8517$new_n312_ rs2_data[16] inst[31] inst[16] $abc$8517$new_n337_ -00000 1 -00001 1 -00100 1 -00101 1 -01000 1 -01010 1 -01100 1 -01110 1 -10000 1 -10001 1 -10010 1 -10011 1 -11000 1 -11001 1 -11010 1 -11011 1 -.names $abc$8517$new_n300_ $abc$8517$new_n302_ inst[3] $abc$8517$new_n338_ -000 1 -001 1 -010 1 -.names rs1_data[16] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[16] $abc$8517$new_n339_ -000000 1 -000001 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001001 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011110 1 -100010 1 -100110 1 -101010 1 -101100 1 -101110 1 -111100 1 -111110 1 -.names $abc$8517$new_n369_ $abc$8517$new_n341_ $abc$8517$new_n367_ $abc$8517$new_n365_ $abc$8517$new_n351_ $abc$8517$new_n340_ -10000 1 -10001 1 -10010 1 -10011 1 -10100 1 -10101 1 -10110 1 -10111 1 -11100 1 -.names $abc$8517$new_n345_ $abc$8517$new_n350_ $abc$8517$new_n344_ $abc$8517$new_n342_ $abc$8517$new_n341_ -0000 1 -0010 1 -0011 1 -0100 1 -0110 1 -0111 1 -1010 1 -1100 1 -1110 1 -1111 1 -.names $abc$8517$new_n310_ rs2_data[5] inst[25] $abc$8517$new_n324_ $abc$8517$new_n342_ -0000 1 -0001 1 -0011 1 -0100 1 -0101 1 -0111 1 -1000 1 -1001 1 -1010 1 -1011 1 -.names $abc$8517$new_n344_ $abc$8517$new_n310_ rs2_data[5] inst[25] $abc$8517$new_n324_ $abc$8517$new_n343_ -10010 1 -10110 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names rs1_data[5] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[5] $abc$8517$new_n344_ -000000 1 -000001 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001001 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011110 1 -100010 1 -100110 1 -101010 1 -101100 1 -101110 1 -111100 1 -111110 1 -.names $abc$8517$new_n310_ rs2_data[4] $abc$8517$new_n346_ inst[11] inst[24] $abc$8517$new_n347_ $abc$8517$new_n345_ -000000 1 -000001 1 -000011 1 -001000 1 -001001 1 -001011 1 -001100 1 -001101 1 -001111 1 -010000 1 -010001 1 -010011 1 -011000 1 -011001 1 -011011 1 -011100 1 -011101 1 -011111 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -.names inst[5] $abc$8517$new_n311_ inst[4] inst[6] inst[2] $abc$8517$new_n346_ -00000 1 -00001 1 -00010 1 -00011 1 -00100 1 -00101 1 -00110 1 -00111 1 -01000 1 -01001 1 -01010 1 -01011 1 -01100 1 -01101 1 -01110 1 -01111 1 -10000 1 -10001 1 -10010 1 -10011 1 -10100 1 -10101 1 -10110 1 -10111 1 -11011 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n303_ $abc$8517$new_n302_ $abc$8517$new_n348_ $abc$8517$new_n349_ $abc$8517$new_n347_ -0000 1 -0001 1 -0010 1 -.names inst[1] inst[0] inst[2] inst[3] $abc$8517$new_n348_ -1100 1 -.names inst[6] inst[5] $abc$8517$new_n349_ -00 1 -.names rs1_data[4] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[4] $abc$8517$new_n350_ -000000 1 -000001 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001001 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011110 1 -100010 1 -100110 1 -101010 1 -101100 1 -101110 1 -111100 1 -111110 1 -.names $abc$8517$new_n364_ $abc$8517$new_n362_ $abc$8517$new_n359_ $abc$8517$new_n363_ $abc$8517$new_n356_ $abc$8517$new_n352_ $abc$8517$new_n351_ -000000 1 -000100 1 -000101 1 -000110 1 -110000 1 -110100 1 -110101 1 -110110 1 -.names $abc$8517$new_n310_ $abc$8517$new_n353_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n352_ -00000 1 -00100 1 -10000 1 -10001 1 -10010 1 -10011 1 -.names rs1_data[0] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[0] $abc$8517$new_n353_ -000000 1 -000001 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001001 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011110 1 -100010 1 -100110 1 -101010 1 -101100 1 -101110 1 -111100 1 -111110 1 -.names inst[7] $abc$8517$new_n311_ inst[5] inst[6] inst[4] $abc$8517$new_n354_ -11100 1 -.names inst[20] $abc$8517$new_n303_ $abc$8517$new_n349_ $abc$8517$new_n348_ $abc$8517$new_n302_ inst[3] $abc$8517$new_n355_ -100010 1 -100110 1 -101010 1 -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n310_ rs2_data[1] $abc$8517$new_n358_ $abc$8517$new_n357_ $abc$8517$new_n356_ -0000 1 -0100 1 -1000 1 -1001 1 -1010 1 -1011 1 -.names inst[8] inst[5] $abc$8517$new_n311_ inst[4] inst[6] inst[2] $abc$8517$new_n357_ -111000 1 -111001 1 -111010 1 -.names inst[21] $abc$8517$new_n303_ $abc$8517$new_n302_ $abc$8517$new_n349_ $abc$8517$new_n348_ $abc$8517$new_n358_ -10011 1 -10100 1 -10101 1 -10110 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n361_ $abc$8517$new_n310_ $abc$8517$new_n360_ inst[23] $abc$8517$new_n347_ rs2_data[3] $abc$8517$new_n359_ -000000 1 -000001 1 -000010 1 -000011 1 -000110 1 -000111 1 -010000 1 -010010 1 -010100 1 -010110 1 -011000 1 -011010 1 -011100 1 -011110 1 -100100 1 -100101 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110001 1 -110011 1 -110101 1 -110111 1 -111001 1 -111011 1 -111101 1 -111111 1 -.names inst[10] inst[5] $abc$8517$new_n311_ inst[4] inst[6] inst[2] $abc$8517$new_n360_ -111000 1 -111001 1 -111010 1 -.names rs1_data[3] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[3] $abc$8517$new_n361_ -000000 1 -000001 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001001 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011110 1 -100010 1 -100110 1 -101010 1 -101100 1 -101110 1 -111100 1 -111110 1 -.names $abc$8517$new_n310_ rs2_data[2] $abc$8517$new_n346_ inst[9] inst[22] $abc$8517$new_n347_ $abc$8517$new_n362_ -000000 1 -000001 1 -000011 1 -001000 1 -001001 1 -001011 1 -001100 1 -001101 1 -001111 1 -010000 1 -010001 1 -010011 1 -011000 1 -011001 1 -011011 1 -011100 1 -011101 1 -011111 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -.names rs1_data[1] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[1] $abc$8517$new_n363_ -000000 1 -000001 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001001 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011110 1 -100010 1 -100110 1 -101010 1 -101100 1 -101110 1 -111100 1 -111110 1 -.names rs1_data[2] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[2] $abc$8517$new_n364_ -000000 1 -000001 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001001 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011110 1 -100010 1 -100110 1 -101010 1 -101100 1 -101110 1 -111100 1 -111110 1 -.names $abc$8517$new_n364_ $abc$8517$new_n362_ $abc$8517$new_n366_ $abc$8517$new_n365_ -100 1 -.names $abc$8517$new_n310_ $abc$8517$new_n361_ $abc$8517$new_n360_ rs2_data[3] $abc$8517$new_n347_ inst[23] $abc$8517$new_n366_ -000000 1 -000010 1 -000011 1 -000100 1 -000110 1 -000111 1 -100000 1 -100001 1 -100010 1 -100011 1 -101000 1 -101001 1 -101010 1 -101011 1 -.names $abc$8517$new_n343_ $abc$8517$new_n345_ $abc$8517$new_n350_ $abc$8517$new_n361_ $abc$8517$new_n368_ $abc$8517$new_n367_ -00000 1 -00001 1 -00011 1 -01000 1 -01001 1 -01011 1 -01100 1 -01101 1 -01111 1 -.names $abc$8517$new_n310_ $abc$8517$new_n360_ rs2_data[3] $abc$8517$new_n347_ inst[23] $abc$8517$new_n368_ -00000 1 -00010 1 -00011 1 -00100 1 -00110 1 -00111 1 -10000 1 -10001 1 -10010 1 -10011 1 -11000 1 -11001 1 -11010 1 -11011 1 -.names $abc$8517$new_n370_ $abc$8517$new_n372_ $abc$8517$new_n373_ $abc$8517$new_n369_ -000 1 -010 1 -011 1 -.names $abc$8517$new_n371_ $abc$8517$new_n310_ rs2_data[7] inst[27] $abc$8517$new_n324_ $abc$8517$new_n370_ -10010 1 -10110 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names rs1_data[7] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[7] $abc$8517$new_n371_ -000000 1 -000001 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001001 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011110 1 -100010 1 -100110 1 -101010 1 -101100 1 -101110 1 -111100 1 -111110 1 -.names $abc$8517$new_n310_ rs2_data[6] inst[26] $abc$8517$new_n324_ $abc$8517$new_n372_ -0000 1 -0001 1 -0011 1 -0100 1 -0101 1 -0111 1 -1000 1 -1001 1 -1010 1 -1011 1 -.names rs1_data[6] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[6] $abc$8517$new_n373_ -000000 1 -000001 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001001 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011110 1 -100010 1 -100110 1 -101010 1 -101100 1 -101110 1 -111100 1 -111110 1 -.names $abc$8517$new_n375_ $abc$8517$new_n381_ $abc$8517$new_n829_ $abc$8517$new_n402_ $abc$8517$new_n395_ $abc$8517$new_n387_ $abc$8517$new_n374_ -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n380_ $abc$8517$new_n376_ $abc$8517$new_n377_ $abc$8517$new_n375_ -000 1 -110 1 -.names $abc$8517$new_n310_ $abc$8517$new_n312_ rs2_data[14] inst[31] inst[14] $abc$8517$new_n376_ -00000 1 -00001 1 -00100 1 -00101 1 -01000 1 -01010 1 -01100 1 -01110 1 -10000 1 -10001 1 -10010 1 -10011 1 -11000 1 -11001 1 -11010 1 -11011 1 -.names $abc$8517$new_n379_ $abc$8517$new_n310_ $abc$8517$new_n378_ inst[31] $abc$8517$new_n312_ rs2_data[15] $abc$8517$new_n377_ -000000 1 -000001 1 -000010 1 -000011 1 -000110 1 -000111 1 -010000 1 -010010 1 -010100 1 -010110 1 -011000 1 -011010 1 -011100 1 -011110 1 -100100 1 -100101 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110001 1 -110011 1 -110101 1 -110111 1 -111001 1 -111011 1 -111101 1 -111111 1 -.names inst[15] $abc$8517$new_n300_ inst[3] $abc$8517$new_n302_ $abc$8517$new_n378_ -1011 1 -1100 1 -1101 1 -1110 1 -1111 1 -.names rs1_data[15] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[15] $abc$8517$new_n379_ -000000 1 -000001 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001001 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011110 1 -100010 1 -100110 1 -101010 1 -101100 1 -101110 1 -111100 1 -111110 1 -.names rs1_data[14] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[14] $abc$8517$new_n380_ -000000 1 -000001 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001001 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011110 1 -100010 1 -100110 1 -101010 1 -101100 1 -101110 1 -111100 1 -111110 1 -.names $abc$8517$new_n386_ $abc$8517$new_n385_ $abc$8517$new_n382_ $abc$8517$new_n381_ -000 1 -110 1 -.names $abc$8517$new_n384_ $abc$8517$new_n310_ $abc$8517$new_n383_ inst[31] $abc$8517$new_n312_ rs2_data[13] $abc$8517$new_n382_ -000000 1 -000001 1 -000010 1 -000011 1 -000110 1 -000111 1 -010000 1 -010010 1 -010100 1 -010110 1 -011000 1 -011010 1 -011100 1 -011110 1 -100100 1 -100101 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110001 1 -110011 1 -110101 1 -110111 1 -111001 1 -111011 1 -111101 1 -111111 1 -.names inst[13] $abc$8517$new_n300_ inst[3] $abc$8517$new_n302_ $abc$8517$new_n383_ -1011 1 -1100 1 -1101 1 -1110 1 -1111 1 -.names rs1_data[13] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[13] $abc$8517$new_n384_ -000000 1 -000001 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001001 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011110 1 -100010 1 -100110 1 -101010 1 -101100 1 -101110 1 -111100 1 -111110 1 -.names $abc$8517$new_n310_ $abc$8517$new_n312_ rs2_data[12] inst[31] inst[12] $abc$8517$new_n385_ -00000 1 -00001 1 -00100 1 -00101 1 -01000 1 -01010 1 -01100 1 -01110 1 -10000 1 -10001 1 -10010 1 -10011 1 -11000 1 -11001 1 -11010 1 -11011 1 -.names rs1_data[12] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[12] $abc$8517$new_n386_ -000000 1 -000001 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001001 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011110 1 -100010 1 -100110 1 -101010 1 -101100 1 -101110 1 -111100 1 -111110 1 -.names $abc$8517$new_n394_ $abc$8517$new_n388_ $abc$8517$new_n392_ $abc$8517$new_n387_ -000 1 -110 1 -.names $abc$8517$new_n310_ $abc$8517$new_n389_ rs2_data[11] $abc$8517$new_n390_ inst[31] $abc$8517$new_n388_ -00000 1 -00010 1 -00100 1 -00110 1 -01000 1 -01001 1 -01100 1 -01101 1 -10000 1 -10001 1 -10010 1 -10011 1 -11000 1 -11001 1 -11010 1 -11011 1 -.names $abc$8517$new_n313_ inst[3] inst[2] inst[5] inst[6] inst[4] $abc$8517$new_n389_ -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -000110 1 -000111 1 -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100101 1 -100110 1 -100111 1 -101001 1 -101010 1 -101011 1 -101101 1 -101111 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names inst[20] inst[3] $abc$8517$new_n302_ $abc$8517$new_n390_ -111 1 -.names inst[7] inst[6] inst[5] $abc$8517$new_n348_ inst[4] $abc$8517$new_n391_ -11110 1 -.names $abc$8517$new_n393_ $abc$8517$new_n310_ inst[30] $abc$8517$new_n324_ rs2_data[10] $abc$8517$new_n392_ -00000 1 -00001 1 -00010 1 -00011 1 -00110 1 -00111 1 -01000 1 -01010 1 -01100 1 -01110 1 -10100 1 -10101 1 -11001 1 -11011 1 -11101 1 -11111 1 -.names rs1_data[10] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[10] $abc$8517$new_n393_ -000000 1 -000001 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001001 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011110 1 -100010 1 -100110 1 -101010 1 -101100 1 -101110 1 -111100 1 -111110 1 -.names rs1_data[11] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[11] $abc$8517$new_n394_ -000000 1 -000001 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001001 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011110 1 -100010 1 -100110 1 -101010 1 -101100 1 -101110 1 -111100 1 -111110 1 -.names $abc$8517$new_n399_ $abc$8517$new_n398_ $abc$8517$new_n397_ $abc$8517$new_n396_ $abc$8517$new_n395_ -0100 1 -0110 1 -1000 1 -1100 1 -1101 1 -1110 1 -.names $abc$8517$new_n310_ rs2_data[9] inst[29] $abc$8517$new_n324_ $abc$8517$new_n396_ -0000 1 -0001 1 -0011 1 -0100 1 -0101 1 -0111 1 -1000 1 -1001 1 -1010 1 -1011 1 -.names $abc$8517$new_n310_ rs2_data[8] inst[28] $abc$8517$new_n324_ $abc$8517$new_n397_ -0000 1 -0001 1 -0011 1 -0100 1 -0101 1 -0111 1 -1000 1 -1001 1 -1010 1 -1011 1 -.names rs1_data[9] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[9] $abc$8517$new_n398_ -000000 1 -000001 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001001 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011110 1 -100010 1 -100110 1 -101010 1 -101100 1 -101110 1 -111100 1 -111110 1 -.names rs1_data[8] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[8] $abc$8517$new_n399_ -000000 1 -000001 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001001 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011110 1 -100010 1 -100110 1 -101010 1 -101100 1 -101110 1 -111100 1 -111110 1 -.names $abc$8517$new_n310_ $abc$8517$new_n383_ rs2_data[13] $abc$8517$new_n312_ inst[31] $abc$8517$new_n401_ -00000 1 -00010 1 -00011 1 -00100 1 -00110 1 -00111 1 -10000 1 -10001 1 -10010 1 -10011 1 -11000 1 -11001 1 -11010 1 -11011 1 -.names $abc$8517$new_n394_ $abc$8517$new_n403_ $abc$8517$new_n388_ $abc$8517$new_n402_ -010 1 -100 1 -110 1 -111 1 -.names $abc$8517$new_n393_ $abc$8517$new_n310_ rs2_data[10] inst[30] $abc$8517$new_n324_ $abc$8517$new_n403_ -10010 1 -10110 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n405_ $abc$8517$new_n387_ $abc$8517$new_n381_ $abc$8517$new_n375_ $abc$8517$new_n404_ -1111 1 -.names $abc$8517$new_n399_ $abc$8517$new_n397_ $abc$8517$new_n406_ $abc$8517$new_n405_ -000 1 -110 1 -.names $abc$8517$new_n398_ $abc$8517$new_n310_ inst[29] $abc$8517$new_n324_ rs2_data[9] $abc$8517$new_n406_ -00000 1 -00001 1 -00010 1 -00011 1 -00110 1 -00111 1 -01000 1 -01010 1 -01100 1 -01110 1 -10100 1 -10101 1 -11001 1 -11011 1 -11101 1 -11111 1 -.names $abc$8517$new_n380_ $abc$8517$new_n379_ $abc$8517$new_n376_ $abc$8517$new_n408_ $abc$8517$new_n407_ -0100 1 -0110 1 -1000 1 -1100 1 -1101 1 -1110 1 -.names $abc$8517$new_n310_ $abc$8517$new_n378_ rs2_data[15] $abc$8517$new_n312_ inst[31] $abc$8517$new_n408_ -00000 1 -00010 1 -00011 1 -00100 1 -00110 1 -00111 1 -10000 1 -10001 1 -10010 1 -10011 1 -11000 1 -11001 1 -11010 1 -11011 1 -.names $abc$8517$new_n310_ $abc$8517$new_n371_ $abc$8517$new_n410_ inst[27] $abc$8517$new_n324_ rs2_data[7] $abc$8517$new_n409_ -000000 1 -000001 1 -000010 1 -000011 1 -000110 1 -000111 1 -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001110 1 -001111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011110 1 -011111 1 -100000 1 -100010 1 -100100 1 -100110 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -111000 1 -111010 1 -111100 1 -111110 1 -.names $abc$8517$new_n310_ $abc$8517$new_n373_ rs2_data[6] $abc$8517$new_n324_ inst[26] $abc$8517$new_n410_ -00000 1 -00010 1 -00011 1 -00100 1 -00110 1 -00111 1 -10000 1 -10001 1 -10010 1 -10011 1 -.names $abc$8517$new_n326_ $abc$8517$new_n428_ $abc$8517$new_n1347_ $abc$8517$new_n426_ $abc$8517$new_n411_ -0001 1 -0010 1 -0011 1 -0100 1 -0101 1 -0110 1 -0111 1 -1011 1 -1101 1 -1111 1 -.names $abc$8517$new_n310_ $abc$8517$new_n414_ rs2_data[22] $abc$8517$new_n413_ -010 1 -011 1 -100 1 -110 1 -.names $abc$8517$new_n328_ $abc$8517$new_n324_ inst[31] $abc$8517$new_n414_ -000 1 -010 1 -011 1 -.names $abc$8517$new_n418_ $abc$8517$new_n335_ $abc$8517$new_n332_ $abc$8517$new_n416_ $abc$8517$new_n415_ -0000 1 -0010 1 -0011 1 -0100 1 -0110 1 -0111 1 -1010 1 -1100 1 -1110 1 -1111 1 -.names $abc$8517$new_n310_ $abc$8517$new_n331_ rs2_data[21] $abc$8517$new_n324_ inst[31] $abc$8517$new_n416_ -00000 1 -00010 1 -00011 1 -00100 1 -00110 1 -00111 1 -10000 1 -10001 1 -10010 1 -10011 1 -11000 1 -11001 1 -11010 1 -11011 1 -.names $abc$8517$new_n310_ $abc$8517$new_n334_ rs2_data[20] $abc$8517$new_n324_ inst[31] $abc$8517$new_n418_ -00000 1 -00010 1 -00011 1 -00100 1 -00110 1 -00111 1 -10000 1 -10001 1 -10010 1 -10011 1 -11000 1 -11001 1 -11010 1 -11011 1 -.names $abc$8517$new_n316_ $abc$8517$new_n309_ $abc$8517$new_n339_ $abc$8517$new_n321_ $abc$8517$new_n337_ $abc$8517$new_n420_ $abc$8517$new_n419_ -000100 1 -000110 1 -001000 1 -001100 1 -001101 1 -001110 1 -.names $abc$8517$new_n310_ $abc$8517$new_n320_ rs2_data[17] $abc$8517$new_n312_ inst[31] $abc$8517$new_n420_ -00000 1 -00010 1 -00011 1 -00100 1 -00110 1 -00111 1 -10000 1 -10001 1 -10010 1 -10011 1 -11000 1 -11001 1 -11010 1 -11011 1 -.names $abc$8517$new_n310_ $abc$8517$new_n314_ rs2_data[19] $abc$8517$new_n312_ inst[31] $abc$8517$new_n422_ -00000 1 -00010 1 -00011 1 -00100 1 -00110 1 -00111 1 -10000 1 -10001 1 -10010 1 -10011 1 -11000 1 -11001 1 -11010 1 -11011 1 -.names $abc$8517$new_n310_ $abc$8517$new_n317_ rs2_data[18] $abc$8517$new_n312_ inst[31] $abc$8517$new_n423_ -00000 1 -00010 1 -00011 1 -00100 1 -00110 1 -00111 1 -10000 1 -10001 1 -10010 1 -10011 1 -11000 1 -11001 1 -11010 1 -11011 1 -.names $abc$8517$new_n310_ $abc$8517$new_n427_ rs2_data[23] $abc$8517$new_n426_ -010 1 -011 1 -100 1 -110 1 -.names $abc$8517$new_n325_ $abc$8517$new_n324_ inst[31] $abc$8517$new_n427_ -000 1 -010 1 -011 1 -.names $abc$8517$new_n310_ $abc$8517$new_n329_ $abc$8517$new_n414_ rs2_data[22] $abc$8517$new_n428_ -0010 1 -0011 1 -1000 1 -1010 1 -.names $abc$8517$new_n310_ $abc$8517$new_n324_ rs2_data[30] inst[31] inst[30] $abc$8517$new_n431_ -00000 1 -00001 1 -00100 1 -00101 1 -01000 1 -01010 1 -01100 1 -01110 1 -10000 1 -10001 1 -10010 1 -10011 1 -11000 1 -11001 1 -11010 1 -11011 1 -.names rs1_data[30] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[30] $abc$8517$new_n432_ -000000 1 -000001 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001001 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011110 1 -100010 1 -100110 1 -101010 1 -101100 1 -101110 1 -111100 1 -111110 1 -.names $abc$8517$new_n434_ $abc$8517$new_n310_ inst[31] rs2_data[31] $abc$8517$new_n433_ -0000 1 -0001 1 -0100 1 -0110 1 -1010 1 -1011 1 -1101 1 -1111 1 -.names rs1_data[31] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[31] $abc$8517$new_n434_ -000000 1 -000001 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001001 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011110 1 -100010 1 -100110 1 -101010 1 -101100 1 -101110 1 -111100 1 -111110 1 -.names $abc$8517$new_n441_ $abc$8517$new_n440_ $abc$8517$new_n438_ $abc$8517$new_n437_ $abc$8517$new_n436_ -0100 1 -0110 1 -1000 1 -1100 1 -1101 1 -1110 1 -.names $abc$8517$new_n310_ $abc$8517$new_n324_ rs2_data[29] inst[31] inst[29] $abc$8517$new_n437_ -00000 1 -00001 1 -00100 1 -00101 1 -01000 1 -01010 1 -01100 1 -01110 1 -10000 1 -10001 1 -10010 1 -10011 1 -11000 1 -11001 1 -11010 1 -11011 1 -.names $abc$8517$new_n310_ $abc$8517$new_n439_ rs2_data[28] $abc$8517$new_n324_ inst[31] $abc$8517$new_n438_ -00000 1 -00010 1 -00011 1 -00100 1 -00110 1 -00111 1 -10000 1 -10001 1 -10010 1 -10011 1 -11000 1 -11001 1 -11010 1 -11011 1 -.names inst[28] $abc$8517$new_n300_ $abc$8517$new_n439_ -11 1 -.names rs1_data[29] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[29] $abc$8517$new_n440_ -000000 1 -000001 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001001 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011110 1 -100010 1 -100110 1 -101010 1 -101100 1 -101110 1 -111100 1 -111110 1 -.names rs1_data[28] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[28] $abc$8517$new_n441_ -000000 1 -000001 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001001 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011110 1 -100010 1 -100110 1 -101010 1 -101100 1 -101110 1 -111100 1 -111110 1 -.names $abc$8517$new_n440_ $abc$8517$new_n437_ $abc$8517$new_n443_ $abc$8517$new_n442_ -000 1 -110 1 -.names $abc$8517$new_n441_ $abc$8517$new_n310_ $abc$8517$new_n439_ inst[31] $abc$8517$new_n324_ rs2_data[28] $abc$8517$new_n443_ -000000 1 -000001 1 -000010 1 -000011 1 -000110 1 -000111 1 -010000 1 -010010 1 -010100 1 -010110 1 -011000 1 -011010 1 -011100 1 -011110 1 -100100 1 -100101 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110001 1 -110011 1 -110101 1 -110111 1 -111001 1 -111011 1 -111101 1 -111111 1 -.names $abc$8517$new_n450_ $abc$8517$new_n449_ $abc$8517$new_n447_ $abc$8517$new_n445_ $abc$8517$new_n444_ -0100 1 -0110 1 -1000 1 -1100 1 -1101 1 -1110 1 -.names $abc$8517$new_n310_ $abc$8517$new_n446_ rs2_data[27] $abc$8517$new_n324_ inst[31] $abc$8517$new_n445_ -00000 1 -00010 1 -00011 1 -00100 1 -00110 1 -00111 1 -10000 1 -10001 1 -10010 1 -10011 1 -11000 1 -11001 1 -11010 1 -11011 1 -.names inst[27] $abc$8517$new_n300_ $abc$8517$new_n446_ -11 1 -.names $abc$8517$new_n310_ $abc$8517$new_n448_ rs2_data[26] $abc$8517$new_n324_ inst[31] $abc$8517$new_n447_ -00000 1 -00010 1 -00011 1 -00100 1 -00110 1 -00111 1 -10000 1 -10001 1 -10010 1 -10011 1 -11000 1 -11001 1 -11010 1 -11011 1 -.names inst[26] $abc$8517$new_n300_ $abc$8517$new_n448_ -11 1 -.names rs1_data[27] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[27] $abc$8517$new_n449_ -000000 1 -000001 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001001 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011110 1 -100010 1 -100110 1 -101010 1 -101100 1 -101110 1 -111100 1 -111110 1 -.names rs1_data[26] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[26] $abc$8517$new_n450_ -000000 1 -000001 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001001 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011110 1 -100010 1 -100110 1 -101010 1 -101100 1 -101110 1 -111100 1 -111110 1 -.names $abc$8517$new_n453_ $abc$8517$new_n452_ $abc$8517$new_n451_ -00 1 -.names $abc$8517$new_n449_ $abc$8517$new_n310_ $abc$8517$new_n446_ inst[31] $abc$8517$new_n324_ rs2_data[27] $abc$8517$new_n452_ -000000 1 -000001 1 -000010 1 -000011 1 -000110 1 -000111 1 -010000 1 -010010 1 -010100 1 -010110 1 -011000 1 -011010 1 -011100 1 -011110 1 -100100 1 -100101 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110001 1 -110011 1 -110101 1 -110111 1 -111001 1 -111011 1 -111101 1 -111111 1 -.names $abc$8517$new_n450_ $abc$8517$new_n310_ $abc$8517$new_n448_ inst[31] $abc$8517$new_n324_ rs2_data[26] $abc$8517$new_n453_ -000000 1 -000001 1 -000010 1 -000011 1 -000110 1 -000111 1 -010000 1 -010010 1 -010100 1 -010110 1 -011000 1 -011010 1 -011100 1 -011110 1 -100100 1 -100101 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110001 1 -110011 1 -110101 1 -110111 1 -111001 1 -111011 1 -111101 1 -111111 1 -.names $abc$8517$new_n460_ $abc$8517$new_n459_ $abc$8517$new_n457_ $abc$8517$new_n455_ $abc$8517$new_n454_ -0100 1 -0110 1 -1000 1 -1100 1 -1101 1 -1110 1 -.names $abc$8517$new_n310_ $abc$8517$new_n456_ rs2_data[25] $abc$8517$new_n324_ inst[31] $abc$8517$new_n455_ -00000 1 -00010 1 -00011 1 -00100 1 -00110 1 -00111 1 -10000 1 -10001 1 -10010 1 -10011 1 -11000 1 -11001 1 -11010 1 -11011 1 -.names inst[25] $abc$8517$new_n300_ $abc$8517$new_n456_ -11 1 -.names $abc$8517$new_n310_ $abc$8517$new_n458_ rs2_data[24] $abc$8517$new_n324_ inst[31] $abc$8517$new_n457_ -00000 1 -00010 1 -00011 1 -00100 1 -00110 1 -00111 1 -10000 1 -10001 1 -10010 1 -10011 1 -11000 1 -11001 1 -11010 1 -11011 1 -.names inst[24] $abc$8517$new_n300_ $abc$8517$new_n458_ -11 1 -.names rs1_data[25] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[25] $abc$8517$new_n459_ -000000 1 -000001 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001001 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011110 1 -100010 1 -100110 1 -101010 1 -101100 1 -101110 1 -111100 1 -111110 1 -.names rs1_data[24] inst[5] inst[3] $abc$8517$new_n302_ $abc$8517$new_n300_ singlecycle_datapath.program_counter.value[24] $abc$8517$new_n460_ -000000 1 -000001 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001001 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011110 1 -100010 1 -100110 1 -101010 1 -101100 1 -101110 1 -111100 1 -111110 1 -.names $abc$8517$new_n462_ $abc$8517$new_n442_ $abc$8517$new_n461_ -11 1 -.names $abc$8517$new_n464_ $abc$8517$new_n463_ $abc$8517$new_n453_ $abc$8517$new_n452_ $abc$8517$new_n462_ -0000 1 -.names $abc$8517$new_n459_ $abc$8517$new_n310_ $abc$8517$new_n456_ inst[31] $abc$8517$new_n324_ rs2_data[25] $abc$8517$new_n463_ -000000 1 -000001 1 -000010 1 -000011 1 -000110 1 -000111 1 -010000 1 -010010 1 -010100 1 -010110 1 -011000 1 -011010 1 -011100 1 -011110 1 -100100 1 -100101 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110001 1 -110011 1 -110101 1 -110111 1 -111001 1 -111011 1 -111101 1 -111111 1 -.names $abc$8517$new_n460_ $abc$8517$new_n310_ $abc$8517$new_n458_ inst[31] $abc$8517$new_n324_ rs2_data[24] $abc$8517$new_n464_ -000000 1 -000001 1 -000010 1 -000011 1 -000110 1 -000111 1 -010000 1 -010010 1 -010100 1 -010110 1 -011000 1 -011010 1 -011100 1 -011110 1 -100100 1 -100101 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110001 1 -110011 1 -110101 1 -110111 1 -111001 1 -111011 1 -111101 1 -111111 1 -.names $abc$8517$new_n478_ $abc$8517$new_n489_ $abc$8517$new_n467_ $abc$8517$new_n466_ -100 1 -.names $abc$8517$new_n476_ $abc$8517$new_n470_ $abc$8517$new_n468_ $abc$8517$new_n404_ $abc$8517$new_n467_ -1111 1 -.names $abc$8517$new_n469_ $abc$8517$new_n442_ $abc$8517$new_n322_ $abc$8517$new_n308_ $abc$8517$new_n462_ $abc$8517$new_n336_ $abc$8517$new_n468_ -111110 1 -.names $abc$8517$new_n432_ $abc$8517$new_n431_ $abc$8517$new_n433_ $abc$8517$new_n469_ -000 1 -110 1 -.names $abc$8517$new_n474_ $abc$8517$new_n472_ $abc$8517$new_n359_ $abc$8517$new_n352_ $abc$8517$new_n471_ $abc$8517$new_n470_ -11000 1 -.names $abc$8517$new_n364_ $abc$8517$new_n362_ $abc$8517$new_n471_ -01 1 -10 1 -.names $abc$8517$new_n363_ $abc$8517$new_n356_ $abc$8517$new_n473_ $abc$8517$new_n472_ -000 1 -110 1 -.names $abc$8517$new_n353_ $abc$8517$new_n310_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n473_ -10001 1 -10010 1 -10011 1 -10101 1 -10110 1 -10111 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n373_ $abc$8517$new_n372_ $abc$8517$new_n475_ $abc$8517$new_n474_ -000 1 -110 1 -.names $abc$8517$new_n371_ $abc$8517$new_n310_ inst[27] $abc$8517$new_n324_ rs2_data[7] $abc$8517$new_n475_ -00000 1 -00001 1 -00010 1 -00011 1 -00110 1 -00111 1 -01000 1 -01010 1 -01100 1 -01110 1 -10100 1 -10101 1 -11001 1 -11011 1 -11101 1 -11111 1 -.names $abc$8517$new_n350_ $abc$8517$new_n345_ $abc$8517$new_n477_ $abc$8517$new_n476_ -000 1 -110 1 -.names $abc$8517$new_n344_ $abc$8517$new_n310_ inst[25] $abc$8517$new_n324_ rs2_data[5] $abc$8517$new_n477_ -00000 1 -00001 1 -00010 1 -00011 1 -00110 1 -00111 1 -01000 1 -01010 1 -01100 1 -01110 1 -10100 1 -10101 1 -11001 1 -11011 1 -11101 1 -11111 1 -.names $abc$8517$new_n486_ $abc$8517$new_n479_ $abc$8517$new_n478_ -00 1 -.names $abc$8517$new_n481_ $abc$8517$new_n484_ $abc$8517$new_n482_ $abc$8517$new_n485_ $abc$8517$new_n310_ $abc$8517$new_n479_ -00001 1 -00011 1 -00110 1 -00111 1 -01110 1 -01111 1 -10010 1 -10011 1 -10110 1 -10111 1 -11110 1 -11111 1 -.names $abc$8517$new_n311_ inst[2] inst[4] inst[5] inst[6] $abc$8517$new_n302_ $abc$8517$new_n481_ -000000 1 -000010 1 -000100 1 -000110 1 -001000 1 -001010 1 -001100 1 -001110 1 -010000 1 -010010 1 -010100 1 -010110 1 -011000 1 -011010 1 -011100 1 -011110 1 -100010 1 -101000 1 -101010 1 -101110 1 -110000 1 -110010 1 -110100 1 -110110 1 -111010 1 -111100 1 -111110 1 -.names $abc$8517$new_n301_ $abc$8517$new_n483_ $abc$8517$new_n482_ -10 1 -.names inst[30] inst[12] inst[13] $abc$8517$new_n483_ -100 1 -101 1 -111 1 -.names inst[5] $abc$8517$new_n348_ inst[14] inst[4] inst[13] $abc$8517$new_n484_ -11100 1 -.names inst[12] inst[14] inst[13] inst[30] $abc$8517$new_n485_ -0110 1 -0111 1 -1001 1 -1010 1 -1011 1 -1100 1 -.names $abc$8517$new_n487_ $abc$8517$new_n481_ $abc$8517$new_n488_ $abc$8517$new_n301_ $abc$8517$new_n483_ $abc$8517$new_n486_ -00000 1 -00001 1 -00010 1 -00100 1 -00101 1 -01000 1 -01001 1 -01010 1 -01011 1 -.names inst[6] inst[5] $abc$8517$new_n348_ inst[4] inst[13] inst[14] $abc$8517$new_n487_ -111000 1 -111001 1 -111010 1 -.names inst[13] inst[12] inst[14] $abc$8517$new_n488_ -010 1 -100 1 -101 1 -111 1 -.names $abc$8517$new_n487_ inst[14] inst[12] inst[13] $abc$8517$new_n482_ $abc$8517$new_n481_ $abc$8517$new_n489_ -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n469_ $abc$8517$new_n442_ $abc$8517$new_n436_ $abc$8517$new_n444_ $abc$8517$new_n454_ $abc$8517$new_n451_ $abc$8517$new_n492_ -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n434_ $abc$8517$new_n310_ $abc$8517$new_n494_ rs2_data[31] $abc$8517$new_n493_ -1000 1 -1001 1 -1101 1 -1111 1 -.names inst[31] $abc$8517$new_n312_ $abc$8517$new_n338_ $abc$8517$new_n494_ -000 1 -001 1 -010 1 -011 1 -111 1 -.names $abc$8517$new_n496_ $abc$8517$new_n470_ $abc$8517$new_n468_ $abc$8517$new_n404_ $abc$8517$new_n476_ $abc$8517$new_n495_ -10000 1 -10001 1 -10010 1 -10011 1 -10100 1 -10101 1 -10110 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -.names $abc$8517$new_n479_ $abc$8517$new_n486_ $abc$8517$new_n497_ $abc$8517$new_n496_ -110 1 -.names $abc$8517$new_n482_ $abc$8517$new_n481_ inst[14] inst[12] inst[13] $abc$8517$new_n498_ $abc$8517$new_n497_ -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -000110 1 -000111 1 -001000 1 -001010 1 -001100 1 -001101 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -011100 1 -011101 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -101100 1 -101101 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -111100 1 -111101 1 -.names inst[6] $abc$8517$new_n348_ inst[14] inst[13] $abc$8517$new_n498_ -1111 1 -.names $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n503_ $abc$8517$new_n508_ $abc$8517$new_n513_ $abc$8517$new_n518_ $abc$8517$new_n502_ -000100 1 -000101 1 -000110 1 -000111 1 -001100 1 -001101 1 -001110 1 -001111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -110000 1 -110010 1 -110100 1 -110110 1 -111000 1 -111010 1 -111100 1 -111110 1 -.names $abc$8517$new_n356_ $abc$8517$new_n505_ $abc$8517$new_n504_ $abc$8517$new_n507_ $abc$8517$new_n506_ $abc$8517$new_n503_ -00000 1 -00100 1 -01000 1 -01100 1 -10000 1 -10001 1 -10010 1 -10011 1 -.names $abc$8517$new_n310_ $abc$8517$new_n399_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n504_ -00000 1 -00100 1 -10000 1 -10001 1 -10010 1 -10011 1 -.names $abc$8517$new_n310_ $abc$8517$new_n398_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n505_ -00001 1 -00010 1 -00011 1 -00101 1 -00110 1 -00111 1 -10100 1 -10101 1 -10110 1 -10111 1 -.names $abc$8517$new_n310_ $abc$8517$new_n393_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n506_ -00000 1 -00100 1 -10000 1 -10001 1 -10010 1 -10011 1 -.names $abc$8517$new_n310_ $abc$8517$new_n394_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n507_ -00001 1 -00010 1 -00011 1 -00101 1 -00110 1 -00111 1 -10100 1 -10101 1 -10110 1 -10111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n510_ $abc$8517$new_n509_ $abc$8517$new_n512_ $abc$8517$new_n511_ $abc$8517$new_n508_ -00000 1 -00100 1 -01000 1 -01100 1 -10000 1 -10001 1 -10010 1 -10011 1 -.names $abc$8517$new_n310_ $abc$8517$new_n386_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n509_ -00000 1 -00100 1 -10000 1 -10001 1 -10010 1 -10011 1 -.names $abc$8517$new_n310_ $abc$8517$new_n384_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n510_ -00001 1 -00010 1 -00011 1 -00101 1 -00110 1 -00111 1 -10100 1 -10101 1 -10110 1 -10111 1 -.names $abc$8517$new_n310_ $abc$8517$new_n380_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n511_ -00000 1 -00100 1 -10000 1 -10001 1 -10010 1 -10011 1 -.names $abc$8517$new_n310_ $abc$8517$new_n379_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n512_ -00001 1 -00010 1 -00011 1 -00101 1 -00110 1 -00111 1 -10100 1 -10101 1 -10110 1 -10111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n515_ $abc$8517$new_n514_ $abc$8517$new_n517_ $abc$8517$new_n516_ $abc$8517$new_n513_ -00000 1 -00100 1 -01000 1 -01100 1 -10000 1 -10001 1 -10010 1 -10011 1 -.names $abc$8517$new_n310_ $abc$8517$new_n350_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n514_ -00000 1 -00100 1 -10000 1 -10001 1 -10010 1 -10011 1 -.names $abc$8517$new_n310_ $abc$8517$new_n344_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n515_ -00001 1 -00010 1 -00011 1 -00101 1 -00110 1 -00111 1 -10100 1 -10101 1 -10110 1 -10111 1 -.names $abc$8517$new_n310_ $abc$8517$new_n373_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n516_ -00000 1 -00100 1 -10000 1 -10001 1 -10010 1 -10011 1 -.names $abc$8517$new_n310_ $abc$8517$new_n371_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n517_ -00001 1 -00010 1 -00011 1 -00101 1 -00110 1 -00111 1 -10100 1 -10101 1 -10110 1 -10111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n353_ $abc$8517$new_n364_ $abc$8517$new_n363_ $abc$8517$new_n361_ $abc$8517$new_n518_ -000000 1 -000010 1 -000100 1 -000110 1 -001000 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -011000 1 -011001 1 -011010 1 -011011 1 -100000 1 -100001 1 -100100 1 -100101 1 -101000 1 -101001 1 -101100 1 -101101 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -.names $abc$8517$new_n310_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n519_ -0000 1 -0100 1 -1000 1 -1001 1 -1010 1 -1011 1 -.names $abc$8517$new_n362_ $abc$8517$new_n368_ $abc$8517$new_n521_ $abc$8517$new_n526_ $abc$8517$new_n531_ $abc$8517$new_n536_ $abc$8517$new_n520_ -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n523_ $abc$8517$new_n522_ $abc$8517$new_n525_ $abc$8517$new_n524_ $abc$8517$new_n521_ -00000 1 -00100 1 -01000 1 -01100 1 -10000 1 -10001 1 -10010 1 -10011 1 -.names $abc$8517$new_n310_ $abc$8517$new_n339_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n522_ -00000 1 -00100 1 -10000 1 -10001 1 -10010 1 -10011 1 -.names $abc$8517$new_n310_ $abc$8517$new_n321_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n523_ -00001 1 -00010 1 -00011 1 -00101 1 -00110 1 -00111 1 -10100 1 -10101 1 -10110 1 -10111 1 -.names $abc$8517$new_n310_ $abc$8517$new_n318_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n524_ -00000 1 -00100 1 -10000 1 -10001 1 -10010 1 -10011 1 -.names $abc$8517$new_n310_ $abc$8517$new_n315_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n525_ -00001 1 -00010 1 -00011 1 -00101 1 -00110 1 -00111 1 -10100 1 -10101 1 -10110 1 -10111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n528_ $abc$8517$new_n527_ $abc$8517$new_n530_ $abc$8517$new_n529_ $abc$8517$new_n526_ -00000 1 -00100 1 -01000 1 -01100 1 -10000 1 -10001 1 -10010 1 -10011 1 -.names $abc$8517$new_n310_ $abc$8517$new_n335_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n527_ -00000 1 -00100 1 -10000 1 -10001 1 -10010 1 -10011 1 -.names $abc$8517$new_n310_ $abc$8517$new_n332_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n528_ -00001 1 -00010 1 -00011 1 -00101 1 -00110 1 -00111 1 -10100 1 -10101 1 -10110 1 -10111 1 -.names $abc$8517$new_n310_ $abc$8517$new_n329_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n529_ -00000 1 -00100 1 -10000 1 -10001 1 -10010 1 -10011 1 -.names $abc$8517$new_n310_ $abc$8517$new_n326_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n530_ -00001 1 -00010 1 -00011 1 -00101 1 -00110 1 -00111 1 -10100 1 -10101 1 -10110 1 -10111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n533_ $abc$8517$new_n532_ $abc$8517$new_n535_ $abc$8517$new_n534_ $abc$8517$new_n531_ -00000 1 -00100 1 -01000 1 -01100 1 -10000 1 -10001 1 -10010 1 -10011 1 -.names $abc$8517$new_n310_ $abc$8517$new_n460_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n532_ -00000 1 -00100 1 -10000 1 -10001 1 -10010 1 -10011 1 -.names $abc$8517$new_n310_ $abc$8517$new_n459_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n533_ -00001 1 -00010 1 -00011 1 -00101 1 -00110 1 -00111 1 -10100 1 -10101 1 -10110 1 -10111 1 -.names $abc$8517$new_n310_ $abc$8517$new_n450_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n534_ -00000 1 -00100 1 -10000 1 -10001 1 -10010 1 -10011 1 -.names $abc$8517$new_n310_ $abc$8517$new_n449_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n535_ -00001 1 -00010 1 -00011 1 -00101 1 -00110 1 -00111 1 -10100 1 -10101 1 -10110 1 -10111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n538_ $abc$8517$new_n537_ $abc$8517$new_n540_ $abc$8517$new_n539_ $abc$8517$new_n536_ -00000 1 -00100 1 -01000 1 -01100 1 -10000 1 -10001 1 -10010 1 -10011 1 -.names $abc$8517$new_n310_ $abc$8517$new_n441_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n537_ -00000 1 -00100 1 -10000 1 -10001 1 -10010 1 -10011 1 -.names $abc$8517$new_n310_ $abc$8517$new_n440_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n538_ -00001 1 -00010 1 -00011 1 -00101 1 -00110 1 -00111 1 -10100 1 -10101 1 -10110 1 -10111 1 -.names $abc$8517$new_n310_ $abc$8517$new_n432_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n539_ -00000 1 -00100 1 -10000 1 -10001 1 -10010 1 -10011 1 -.names $abc$8517$new_n310_ $abc$8517$new_n434_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n540_ -00001 1 -00010 1 -00011 1 -00101 1 -00110 1 -00111 1 -10100 1 -10101 1 -10110 1 -10111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n352_ $abc$8517$new_n542_ -11 1 -.names $abc$8517$new_n497_ $abc$8517$new_n489_ $abc$8517$new_n345_ $abc$8517$new_n486_ $abc$8517$new_n479_ $abc$8517$new_n543_ -11100 1 -.names $abc$8517$new_n352_ $abc$8517$new_n473_ $abc$8517$new_n548_ $abc$8517$new_n547_ $abc$8517$new_n546_ $abc$8517$new_n545_ -01001 1 -01010 1 -01011 1 -01100 1 -01101 1 -01110 1 -01111 1 -10001 1 -10010 1 -10011 1 -10100 1 -10101 1 -10110 1 -10111 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n497_ $abc$8517$new_n486_ $abc$8517$new_n489_ $abc$8517$new_n546_ -111 1 -.names $abc$8517$new_n486_ $abc$8517$new_n497_ $abc$8517$new_n479_ $abc$8517$new_n547_ -100 1 -.names $abc$8517$new_n497_ $abc$8517$new_n489_ $abc$8517$new_n479_ $abc$8517$new_n548_ -111 1 -.names $abc$8517$new_n486_ $abc$8517$new_n550_ $abc$8517$new_n345_ $abc$8517$new_n549_ -111 1 -.names inst[14] inst[12] $abc$8517$new_n482_ $abc$8517$new_n481_ $abc$8517$new_n550_ -1101 1 -1110 1 -1111 1 -.names $abc$8517$new_n486_ $abc$8517$new_n550_ $abc$8517$new_n345_ $abc$8517$new_n551_ -110 1 -.names $abc$8517$new_n479_ $abc$8517$new_n486_ $abc$8517$new_n552_ -10 1 -.names $abc$8517$new_n368_ $abc$8517$new_n558_ $abc$8517$new_n567_ $abc$8517$new_n568_ $abc$8517$new_n555_ $abc$8517$new_n566_ $abc$8517$new_n554_ -000000 1 -000001 1 -000010 1 -000011 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010110 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -100000 1 -100001 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -110000 1 -110001 1 -110010 1 -110011 1 -110110 1 -110111 1 -111010 1 -111011 1 -111110 1 -111111 1 -.names $abc$8517$new_n362_ $abc$8517$new_n556_ $abc$8517$new_n557_ $abc$8517$new_n555_ -001 1 -011 1 -110 1 -111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n321_ $abc$8517$new_n315_ $abc$8517$new_n318_ $abc$8517$new_n335_ $abc$8517$new_n556_ -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n332_ $abc$8517$new_n326_ $abc$8517$new_n329_ $abc$8517$new_n460_ $abc$8517$new_n557_ -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n564_ $abc$8517$new_n559_ $abc$8517$new_n558_ -00 1 -.names $abc$8517$new_n356_ $abc$8517$new_n362_ $abc$8517$new_n561_ $abc$8517$new_n560_ $abc$8517$new_n562_ $abc$8517$new_n563_ $abc$8517$new_n559_ -010001 1 -010010 1 -010011 1 -010101 1 -010110 1 -010111 1 -011001 1 -011010 1 -011011 1 -011101 1 -011110 1 -011111 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n310_ $abc$8517$new_n459_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n560_ -00000 1 -00100 1 -10000 1 -10001 1 -10010 1 -10011 1 -.names $abc$8517$new_n310_ $abc$8517$new_n450_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n561_ -00001 1 -00010 1 -00011 1 -00101 1 -00110 1 -00111 1 -10100 1 -10101 1 -10110 1 -10111 1 -.names $abc$8517$new_n310_ $abc$8517$new_n449_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n562_ -00000 1 -00100 1 -10000 1 -10001 1 -10010 1 -10011 1 -.names $abc$8517$new_n310_ $abc$8517$new_n441_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n563_ -00001 1 -00010 1 -00011 1 -00101 1 -00110 1 -00111 1 -10100 1 -10101 1 -10110 1 -10111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n362_ $abc$8517$new_n440_ $abc$8517$new_n434_ $abc$8517$new_n432_ $abc$8517$new_n564_ -010000 1 -010001 1 -010100 1 -010101 1 -100000 1 -100010 1 -100100 1 -100110 1 -110000 1 -110001 1 -110010 1 -110011 1 -.names $abc$8517$new_n356_ $abc$8517$new_n362_ $abc$8517$new_n519_ $abc$8517$new_n434_ $abc$8517$new_n440_ $abc$8517$new_n432_ $abc$8517$new_n566_ -000000 1 -000001 1 -000010 1 -000011 1 -001000 1 -001001 1 -001010 1 -001011 1 -100000 1 -100010 1 -100100 1 -100110 1 -101000 1 -101001 1 -101100 1 -101101 1 -.names $abc$8517$new_n479_ $abc$8517$new_n550_ $abc$8517$new_n567_ -11 1 -.names $abc$8517$new_n486_ $abc$8517$new_n550_ $abc$8517$new_n479_ $abc$8517$new_n568_ -110 1 -.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n398_ $abc$8517$new_n394_ $abc$8517$new_n393_ $abc$8517$new_n386_ $abc$8517$new_n571_ -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n384_ $abc$8517$new_n379_ $abc$8517$new_n380_ $abc$8517$new_n339_ $abc$8517$new_n572_ -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n363_ $abc$8517$new_n361_ $abc$8517$new_n364_ $abc$8517$new_n350_ $abc$8517$new_n573_ -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n344_ $abc$8517$new_n371_ $abc$8517$new_n373_ $abc$8517$new_n399_ $abc$8517$new_n574_ -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n363_ $abc$8517$new_n356_ $abc$8517$new_n576_ $abc$8517$new_n577_ $abc$8517$new_n547_ $abc$8517$new_n546_ $abc$8517$new_n575_ -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000110 1 -010000 1 -010100 1 -010101 1 -100000 1 -100100 1 -100101 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110110 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111110 1 -.names $abc$8517$new_n552_ $abc$8517$new_n497_ $abc$8517$new_n576_ -10 1 -.names $abc$8517$new_n310_ $abc$8517$new_n353_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n577_ -00001 1 -00010 1 -00011 1 -00101 1 -00110 1 -00111 1 -10100 1 -10101 1 -10110 1 -10111 1 -.names $abc$8517$new_n363_ $abc$8517$new_n356_ $abc$8517$new_n548_ $abc$8517$new_n473_ $abc$8517$new_n580_ $abc$8517$new_n579_ $abc$8517$new_n578_ -000000 1 -000100 1 -001000 1 -010000 1 -010001 1 -010100 1 -010101 1 -011100 1 -011101 1 -100000 1 -100001 1 -100100 1 -100101 1 -101100 1 -101101 1 -110000 1 -110001 1 -110100 1 -110101 1 -111000 1 -111001 1 -.names $abc$8517$new_n478_ $abc$8517$new_n497_ $abc$8517$new_n579_ -10 1 -.names $abc$8517$new_n362_ $abc$8517$new_n368_ $abc$8517$new_n581_ $abc$8517$new_n543_ $abc$8517$new_n580_ -1111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n577_ $abc$8517$new_n519_ $abc$8517$new_n363_ $abc$8517$new_n581_ -1010 1 -1100 1 -1101 1 -1110 1 -1111 1 -.names $abc$8517$new_n597_ $abc$8517$new_n599_ $abc$8517$new_n590_ $abc$8517$new_n345_ $abc$8517$new_n583_ $abc$8517$new_n588_ bus_address[2] -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -000110 1 -000111 1 -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110001 1 -110010 1 -110011 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n567_ $abc$8517$new_n368_ $abc$8517$new_n584_ $abc$8517$new_n585_ $abc$8517$new_n587_ $abc$8517$new_n586_ $abc$8517$new_n583_ -100000 1 -100100 1 -101000 1 -101100 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n362_ $abc$8517$new_n525_ $abc$8517$new_n524_ $abc$8517$new_n527_ $abc$8517$new_n528_ $abc$8517$new_n584_ -010001 1 -010010 1 -010011 1 -010101 1 -010110 1 -010111 1 -011001 1 -011010 1 -011011 1 -011101 1 -011110 1 -011111 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n362_ $abc$8517$new_n530_ $abc$8517$new_n529_ $abc$8517$new_n532_ $abc$8517$new_n533_ $abc$8517$new_n585_ -000001 1 -000010 1 -000011 1 -000101 1 -000110 1 -000111 1 -001001 1 -001010 1 -001011 1 -001101 1 -001110 1 -001111 1 -100100 1 -100101 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -.names $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n535_ $abc$8517$new_n534_ $abc$8517$new_n538_ $abc$8517$new_n537_ $abc$8517$new_n586_ -100000 1 -100100 1 -101000 1 -101100 1 -110000 1 -110001 1 -110010 1 -110011 1 -.names $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n540_ $abc$8517$new_n539_ $abc$8517$new_n587_ -0000 1 -0001 1 -0010 1 -0011 1 -0100 1 -.names $abc$8517$new_n568_ $abc$8517$new_n368_ $abc$8517$new_n584_ $abc$8517$new_n585_ $abc$8517$new_n589_ $abc$8517$new_n586_ $abc$8517$new_n588_ -100000 1 -100100 1 -101000 1 -101100 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n434_ $abc$8517$new_n432_ $abc$8517$new_n589_ -00010 1 -00011 1 -00110 1 -00111 1 -01010 1 -01011 1 -01101 1 -01111 1 -.names $abc$8517$new_n368_ $abc$8517$new_n549_ $abc$8517$new_n594_ $abc$8517$new_n591_ $abc$8517$new_n595_ $abc$8517$new_n596_ $abc$8517$new_n590_ -010001 1 -010010 1 -010011 1 -010101 1 -010110 1 -010111 1 -011001 1 -011010 1 -011011 1 -011101 1 -011110 1 -011111 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n362_ $abc$8517$new_n593_ $abc$8517$new_n592_ $abc$8517$new_n515_ $abc$8517$new_n514_ $abc$8517$new_n591_ -010001 1 -010010 1 -010011 1 -010101 1 -010110 1 -010111 1 -011001 1 -011010 1 -011011 1 -011101 1 -011110 1 -011111 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n310_ $abc$8517$new_n364_ rs2_data[0] $abc$8517$new_n355_ $abc$8517$new_n592_ -0000 1 -0010 1 -1000 1 -1001 1 -.names $abc$8517$new_n310_ $abc$8517$new_n361_ rs2_data[0] $abc$8517$new_n354_ $abc$8517$new_n355_ $abc$8517$new_n593_ -00001 1 -00010 1 -00011 1 -00101 1 -00110 1 -00111 1 -10100 1 -10101 1 -10110 1 -10111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n362_ $abc$8517$new_n517_ $abc$8517$new_n516_ $abc$8517$new_n505_ $abc$8517$new_n504_ $abc$8517$new_n594_ -000001 1 -000010 1 -000011 1 -000101 1 -000110 1 -000111 1 -001001 1 -001010 1 -001011 1 -001101 1 -001110 1 -001111 1 -100100 1 -100101 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n362_ $abc$8517$new_n507_ $abc$8517$new_n506_ $abc$8517$new_n509_ $abc$8517$new_n510_ $abc$8517$new_n595_ -010001 1 -010010 1 -010011 1 -010101 1 -010110 1 -010111 1 -011001 1 -011010 1 -011011 1 -011101 1 -011110 1 -011111 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n362_ $abc$8517$new_n512_ $abc$8517$new_n511_ $abc$8517$new_n522_ $abc$8517$new_n523_ $abc$8517$new_n596_ -000001 1 -000010 1 -000011 1 -000101 1 -000110 1 -000111 1 -001001 1 -001010 1 -001011 1 -001101 1 -001110 1 -001111 1 -100100 1 -100101 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -.names $abc$8517$new_n598_ $abc$8517$new_n471_ $abc$8517$new_n363_ $abc$8517$new_n577_ $abc$8517$new_n356_ $abc$8517$new_n546_ $abc$8517$new_n597_ -100000 1 -100010 1 -100011 1 -100100 1 -100110 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101110 1 -101111 1 -110000 1 -110001 1 -110010 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111010 1 -111100 1 -111101 1 -111110 1 -.names $abc$8517$new_n486_ $abc$8517$new_n497_ $abc$8517$new_n364_ $abc$8517$new_n362_ $abc$8517$new_n479_ $abc$8517$new_n598_ -00010 1 -00100 1 -00110 1 -00111 1 -01000 1 -01001 1 -01010 1 -01011 1 -01100 1 -01101 1 -01110 1 -01111 1 -10000 1 -10001 1 -10011 1 -10101 1 -10110 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n548_ $abc$8517$new_n601_ $abc$8517$new_n471_ $abc$8517$new_n368_ $abc$8517$new_n600_ $abc$8517$new_n543_ $abc$8517$new_n599_ -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -000110 1 -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -.names $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n364_ $abc$8517$new_n353_ $abc$8517$new_n363_ $abc$8517$new_n600_ -101000 1 -101001 1 -101100 1 -101101 1 -110000 1 -110010 1 -110100 1 -110110 1 -111000 1 -111001 1 -111010 1 -111011 1 -.names $abc$8517$new_n363_ $abc$8517$new_n473_ $abc$8517$new_n356_ $abc$8517$new_n601_ -010 1 -100 1 -110 1 -111 1 -.names $abc$8517$new_n345_ $abc$8517$new_n567_ $abc$8517$new_n604_ $abc$8517$new_n568_ $abc$8517$new_n609_ $abc$8517$new_n603_ -00010 1 -00110 1 -01000 1 -01001 1 -01010 1 -01011 1 -01110 1 -.names $abc$8517$new_n362_ $abc$8517$new_n368_ $abc$8517$new_n605_ $abc$8517$new_n606_ $abc$8517$new_n607_ $abc$8517$new_n608_ $abc$8517$new_n604_ -000000 1 -000010 1 -000100 1 -000110 1 -001000 1 -001010 1 -001100 1 -001110 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n315_ $abc$8517$new_n332_ $abc$8517$new_n335_ $abc$8517$new_n329_ $abc$8517$new_n605_ -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n326_ $abc$8517$new_n459_ $abc$8517$new_n460_ $abc$8517$new_n450_ $abc$8517$new_n606_ -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n449_ $abc$8517$new_n440_ $abc$8517$new_n441_ $abc$8517$new_n432_ $abc$8517$new_n607_ -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n519_ $abc$8517$new_n356_ $abc$8517$new_n434_ $abc$8517$new_n608_ -110 1 -.names $abc$8517$new_n362_ $abc$8517$new_n368_ $abc$8517$new_n605_ $abc$8517$new_n606_ $abc$8517$new_n607_ $abc$8517$new_n434_ $abc$8517$new_n609_ -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n361_ $abc$8517$new_n363_ $abc$8517$new_n364_ $abc$8517$new_n353_ $abc$8517$new_n612_ -000000 1 -000010 1 -000100 1 -000110 1 -001000 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -011000 1 -011001 1 -011010 1 -011011 1 -100000 1 -100001 1 -100100 1 -100101 1 -101000 1 -101001 1 -101100 1 -101101 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -.names $abc$8517$new_n363_ $abc$8517$new_n364_ $abc$8517$new_n577_ $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n616_ -00011 1 -01001 1 -01010 1 -01011 1 -01110 1 -01111 1 -10010 1 -10011 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n363_ $abc$8517$new_n473_ $abc$8517$new_n364_ $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n618_ -00100 1 -00101 1 -01000 1 -01100 1 -01101 1 -01110 1 -10000 1 -10100 1 -10101 1 -10110 1 -11000 1 -11001 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n394_ $abc$8517$new_n384_ $abc$8517$new_n386_ $abc$8517$new_n380_ $abc$8517$new_n620_ -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n379_ $abc$8517$new_n321_ $abc$8517$new_n339_ $abc$8517$new_n318_ $abc$8517$new_n621_ -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n361_ $abc$8517$new_n344_ $abc$8517$new_n350_ $abc$8517$new_n373_ $abc$8517$new_n623_ -000000 1 -000010 1 -000100 1 -000110 1 -001000 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -011000 1 -011001 1 -011010 1 -011011 1 -100000 1 -100001 1 -100100 1 -100101 1 -101000 1 -101001 1 -101100 1 -101101 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n371_ $abc$8517$new_n398_ $abc$8517$new_n399_ $abc$8517$new_n393_ $abc$8517$new_n624_ -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n637_ $abc$8517$new_n632_ $abc$8517$new_n626_ bus_address[4] -000 1 -001 1 -010 1 -011 1 -100 1 -101 1 -111 1 -.names $abc$8517$new_n345_ $abc$8517$new_n627_ $abc$8517$new_n567_ $abc$8517$new_n568_ $abc$8517$new_n628_ $abc$8517$new_n631_ $abc$8517$new_n626_ -000110 1 -000111 1 -001001 1 -001011 1 -001101 1 -001110 1 -001111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n526_ $abc$8517$new_n531_ $abc$8517$new_n627_ -1000 1 -1010 1 -1100 1 -1101 1 -.names $abc$8517$new_n368_ $abc$8517$new_n629_ $abc$8517$new_n630_ $abc$8517$new_n628_ -001 1 -010 1 -011 1 -.names $abc$8517$new_n434_ $abc$8517$new_n362_ $abc$8517$new_n629_ -00 1 -.names $abc$8517$new_n356_ $abc$8517$new_n362_ $abc$8517$new_n538_ $abc$8517$new_n537_ $abc$8517$new_n539_ $abc$8517$new_n540_ $abc$8517$new_n630_ -010001 1 -010010 1 -010011 1 -010101 1 -010110 1 -010111 1 -011001 1 -011010 1 -011011 1 -011101 1 -011110 1 -011111 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n630_ $abc$8517$new_n368_ $abc$8517$new_n631_ -10 1 -.names $abc$8517$new_n636_ $abc$8517$new_n549_ $abc$8517$new_n633_ $abc$8517$new_n368_ $abc$8517$new_n543_ $abc$8517$new_n634_ $abc$8517$new_n632_ -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111111 1 -.names $abc$8517$new_n362_ $abc$8517$new_n368_ $abc$8517$new_n503_ $abc$8517$new_n508_ $abc$8517$new_n513_ $abc$8517$new_n521_ $abc$8517$new_n633_ -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -100100 1 -100101 1 -100110 1 -100111 1 -101100 1 -101101 1 -101110 1 -101111 1 -110010 1 -110011 1 -110110 1 -110111 1 -111010 1 -111011 1 -111110 1 -111111 1 -.names $abc$8517$new_n362_ $abc$8517$new_n542_ $abc$8517$new_n635_ $abc$8517$new_n634_ -000 1 -001 1 -101 1 -111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n350_ $abc$8517$new_n364_ $abc$8517$new_n361_ $abc$8517$new_n363_ $abc$8517$new_n635_ -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n486_ $abc$8517$new_n497_ $abc$8517$new_n350_ $abc$8517$new_n345_ $abc$8517$new_n479_ $abc$8517$new_n636_ -00010 1 -00100 1 -00110 1 -00111 1 -01000 1 -01001 1 -01010 1 -01011 1 -01100 1 -01101 1 -01110 1 -01111 1 -10000 1 -10001 1 -10011 1 -10101 1 -10110 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n642_ $abc$8517$new_n546_ $abc$8517$new_n643_ $abc$8517$new_n641_ $abc$8517$new_n548_ $abc$8517$new_n638_ $abc$8517$new_n637_ -000000 1 -000001 1 -000011 1 -000100 1 -000101 1 -000111 1 -001000 1 -001001 1 -001011 1 -001100 1 -001101 1 -001111 1 -010000 1 -010001 1 -010011 1 -100000 1 -100001 1 -100010 1 -100100 1 -100101 1 -100110 1 -101000 1 -101001 1 -101010 1 -101100 1 -101101 1 -101110 1 -110100 1 -110101 1 -110110 1 -111000 1 -111001 1 -111010 1 -111100 1 -111101 1 -111110 1 -.names $abc$8517$new_n640_ $abc$8517$new_n639_ $abc$8517$new_n638_ -00 1 -.names $abc$8517$new_n361_ $abc$8517$new_n368_ $abc$8517$new_n639_ -10 1 -.names $abc$8517$new_n366_ $abc$8517$new_n363_ $abc$8517$new_n473_ $abc$8517$new_n364_ $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n640_ -000100 1 -000101 1 -001000 1 -001100 1 -001101 1 -001110 1 -010000 1 -010100 1 -010101 1 -010110 1 -011000 1 -011001 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n361_ $abc$8517$new_n368_ $abc$8517$new_n641_ -00 1 -.names $abc$8517$new_n350_ $abc$8517$new_n345_ $abc$8517$new_n642_ -01 1 -10 1 -.names $abc$8517$new_n359_ $abc$8517$new_n577_ $abc$8517$new_n363_ $abc$8517$new_n364_ $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n643_ -100000 1 -100001 1 -100010 1 -100100 1 -101000 1 -101001 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -111000 1 -111001 1 -111010 1 -111100 1 -.names $abc$8517$new_n548_ $abc$8517$new_n477_ $abc$8517$new_n350_ $abc$8517$new_n345_ $abc$8517$new_n638_ $abc$8517$new_n646_ -10000 1 -10100 1 -10101 1 -10110 1 -11001 1 -11010 1 -11011 1 -11111 1 -.names $abc$8517$new_n345_ $abc$8517$new_n568_ $abc$8517$new_n368_ $abc$8517$new_n650_ $abc$8517$new_n648_ $abc$8517$new_n652_ $abc$8517$new_n647_ -000100 1 -000101 1 -000110 1 -000111 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010010 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n362_ $abc$8517$new_n557_ $abc$8517$new_n649_ $abc$8517$new_n648_ -001 1 -011 1 -110 1 -111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n561_ $abc$8517$new_n560_ $abc$8517$new_n563_ $abc$8517$new_n562_ $abc$8517$new_n649_ -00000 1 -00100 1 -01000 1 -01100 1 -10000 1 -10001 1 -10010 1 -10011 1 -.names $abc$8517$new_n567_ $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n651_ $abc$8517$new_n557_ $abc$8517$new_n649_ $abc$8517$new_n650_ -100100 1 -100101 1 -100110 1 -100111 1 -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110010 1 -110100 1 -110110 1 -111000 1 -111001 1 -111100 1 -111101 1 -.names $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n440_ $abc$8517$new_n434_ $abc$8517$new_n432_ $abc$8517$new_n651_ -101000 1 -101001 1 -101100 1 -101101 1 -110000 1 -110010 1 -110100 1 -110110 1 -111000 1 -111001 1 -111010 1 -111011 1 -.names $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n434_ $abc$8517$new_n440_ $abc$8517$new_n432_ $abc$8517$new_n652_ -000100 1 -000101 1 -000110 1 -000111 1 -001100 1 -001101 1 -001110 1 -001111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100100 1 -100101 1 -100110 1 -100111 1 -101100 1 -101101 1 -101110 1 -101111 1 -110001 1 -110011 1 -110101 1 -110111 1 -111010 1 -111011 1 -111110 1 -111111 1 -.names $abc$8517$new_n368_ $abc$8517$new_n543_ $abc$8517$new_n654_ $abc$8517$new_n653_ -110 1 -.names $abc$8517$new_n362_ $abc$8517$new_n581_ $abc$8517$new_n655_ $abc$8517$new_n654_ -000 1 -001 1 -101 1 -111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n344_ $abc$8517$new_n361_ $abc$8517$new_n350_ $abc$8517$new_n364_ $abc$8517$new_n655_ -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n362_ $abc$8517$new_n368_ $abc$8517$new_n556_ $abc$8517$new_n574_ $abc$8517$new_n571_ $abc$8517$new_n572_ $abc$8517$new_n657_ -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001110 1 -001111 1 -010010 1 -010011 1 -010110 1 -010111 1 -011010 1 -011011 1 -011110 1 -011111 1 -100001 1 -100011 1 -100101 1 -100111 1 -101001 1 -101011 1 -101101 1 -101111 1 -110100 1 -110101 1 -110110 1 -110111 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n546_ $abc$8517$new_n477_ $abc$8517$new_n350_ $abc$8517$new_n345_ $abc$8517$new_n643_ $abc$8517$new_n641_ $abc$8517$new_n659_ -100000 1 -100001 1 -100010 1 -100011 1 -100101 1 -100110 1 -100111 1 -101001 1 -101010 1 -101011 1 -110100 1 -111000 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n344_ $abc$8517$new_n350_ $abc$8517$new_n643_ $abc$8517$new_n641_ $abc$8517$new_n345_ $abc$8517$new_n342_ $abc$8517$new_n662_ -000011 1 -010001 1 -010011 1 -010111 1 -011011 1 -011111 1 -100001 1 -100010 1 -100011 1 -100101 1 -100111 1 -101001 1 -101011 1 -101101 1 -101111 1 -110000 1 -110001 1 -110010 1 -110011 1 -110101 1 -110110 1 -110111 1 -111001 1 -111010 1 -111011 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n373_ $abc$8517$new_n372_ $abc$8517$new_n663_ -01 1 -10 1 -.names $abc$8517$new_n341_ $abc$8517$new_n638_ $abc$8517$new_n476_ $abc$8517$new_n664_ -100 1 -101 1 -110 1 -.names $abc$8517$new_n345_ $abc$8517$new_n568_ $abc$8517$new_n368_ $abc$8517$new_n669_ $abc$8517$new_n666_ $abc$8517$new_n671_ $abc$8517$new_n665_ -000100 1 -000101 1 -000110 1 -000111 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010010 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n362_ $abc$8517$new_n667_ $abc$8517$new_n668_ $abc$8517$new_n666_ -001 1 -011 1 -110 1 -111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n530_ $abc$8517$new_n529_ $abc$8517$new_n533_ $abc$8517$new_n532_ $abc$8517$new_n667_ -00000 1 -00100 1 -01000 1 -01100 1 -10000 1 -10001 1 -10010 1 -10011 1 -.names $abc$8517$new_n356_ $abc$8517$new_n535_ $abc$8517$new_n534_ $abc$8517$new_n538_ $abc$8517$new_n537_ $abc$8517$new_n668_ -00000 1 -00100 1 -01000 1 -01100 1 -10000 1 -10001 1 -10010 1 -10011 1 -.names $abc$8517$new_n567_ $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n670_ $abc$8517$new_n667_ $abc$8517$new_n668_ $abc$8517$new_n669_ -100100 1 -100101 1 -100110 1 -100111 1 -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110010 1 -110100 1 -110110 1 -111000 1 -111001 1 -111100 1 -111101 1 -.names $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n539_ $abc$8517$new_n540_ $abc$8517$new_n670_ -1101 1 -1110 1 -1111 1 -.names $abc$8517$new_n670_ $abc$8517$new_n434_ $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n671_ -0011 1 -0100 1 -0101 1 -0110 1 -0111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n364_ $abc$8517$new_n353_ $abc$8517$new_n363_ $abc$8517$new_n680_ -01000 1 -01001 1 -01100 1 -01101 1 -10000 1 -10010 1 -10100 1 -10110 1 -11000 1 -11001 1 -11010 1 -11011 1 -.names $abc$8517$new_n356_ $abc$8517$new_n516_ $abc$8517$new_n515_ $abc$8517$new_n593_ $abc$8517$new_n514_ $abc$8517$new_n681_ -00000 1 -00100 1 -01000 1 -01100 1 -10000 1 -10001 1 -10010 1 -10011 1 -.names $abc$8517$new_n486_ $abc$8517$new_n497_ $abc$8517$new_n373_ $abc$8517$new_n372_ $abc$8517$new_n479_ $abc$8517$new_n682_ -00010 1 -00100 1 -00110 1 -00111 1 -01000 1 -01001 1 -01010 1 -01011 1 -01100 1 -01101 1 -01110 1 -01111 1 -10000 1 -10001 1 -10011 1 -10101 1 -10110 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n695_ $abc$8517$new_n693_ $abc$8517$new_n687_ $abc$8517$new_n685_ $abc$8517$new_n684_ bus_address[7] -00000 1 -00001 1 -00010 1 -00011 1 -00100 1 -00101 1 -00110 1 -00111 1 -01000 1 -01001 1 -01010 1 -01011 1 -01100 1 -01101 1 -01110 1 -01111 1 -10001 1 -10010 1 -10011 1 -10100 1 -10101 1 -10110 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n546_ $abc$8517$new_n475_ $abc$8517$new_n373_ $abc$8517$new_n372_ $abc$8517$new_n662_ $abc$8517$new_n684_ -10000 1 -10001 1 -10010 1 -10100 1 -11011 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n548_ $abc$8517$new_n475_ $abc$8517$new_n410_ $abc$8517$new_n686_ $abc$8517$new_n685_ -1000 1 -1101 1 -1110 1 -1111 1 -.names $abc$8517$new_n663_ $abc$8517$new_n341_ $abc$8517$new_n476_ $abc$8517$new_n640_ $abc$8517$new_n639_ $abc$8517$new_n686_ -00000 1 -00001 1 -00010 1 -00011 1 -00100 1 -00101 1 -00110 1 -00111 1 -01100 1 -.names $abc$8517$new_n345_ $abc$8517$new_n692_ $abc$8517$new_n691_ $abc$8517$new_n688_ $abc$8517$new_n689_ $abc$8517$new_n687_ -00100 1 -01000 1 -01001 1 -01010 1 -01011 1 -01100 1 -01101 1 -01110 1 -01111 1 -.names $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n606_ $abc$8517$new_n607_ $abc$8517$new_n688_ -1001 1 -1011 1 -1110 1 -1111 1 -.names $abc$8517$new_n368_ $abc$8517$new_n608_ $abc$8517$new_n690_ $abc$8517$new_n689_ -000 1 -001 1 -010 1 -.names $abc$8517$new_n479_ $abc$8517$new_n362_ $abc$8517$new_n690_ -11 1 -.names $abc$8517$new_n486_ $abc$8517$new_n550_ $abc$8517$new_n691_ -11 1 -.names $abc$8517$new_n550_ $abc$8517$new_n486_ $abc$8517$new_n434_ $abc$8517$new_n479_ $abc$8517$new_n368_ $abc$8517$new_n692_ -11000 1 -.names $abc$8517$new_n549_ $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n694_ $abc$8517$new_n620_ $abc$8517$new_n624_ $abc$8517$new_n693_ -100000 1 -100001 1 -100010 1 -100011 1 -101000 1 -101001 1 -101010 1 -101011 1 -110000 1 -110001 1 -110100 1 -110101 1 -111000 1 -111010 1 -111100 1 -111110 1 -.names $abc$8517$new_n362_ $abc$8517$new_n605_ $abc$8517$new_n621_ $abc$8517$new_n694_ -010 1 -011 1 -101 1 -111 1 -.names $abc$8517$new_n696_ $abc$8517$new_n371_ $abc$8517$new_n576_ $abc$8517$new_n698_ $abc$8517$new_n547_ $abc$8517$new_n579_ $abc$8517$new_n695_ -000000 1 -000010 1 -000100 1 -000101 1 -010000 1 -010001 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n543_ $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n612_ $abc$8517$new_n697_ $abc$8517$new_n696_ -11010 1 -11011 1 -11100 1 -11110 1 -.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n371_ $abc$8517$new_n344_ $abc$8517$new_n373_ $abc$8517$new_n350_ $abc$8517$new_n697_ -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n310_ $abc$8517$new_n699_ rs2_data[7] $abc$8517$new_n698_ -000 1 -001 1 -100 1 -110 1 -.names inst[27] $abc$8517$new_n324_ $abc$8517$new_n699_ -10 1 -.names $abc$8517$new_n373_ $abc$8517$new_n372_ $abc$8517$new_n371_ $abc$8517$new_n698_ $abc$8517$new_n662_ $abc$8517$new_n701_ -00110 1 -00111 1 -01011 1 -01101 1 -01110 1 -01111 1 -10011 1 -10101 1 -10110 1 -10111 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n543_ $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n542_ $abc$8517$new_n635_ $abc$8517$new_n704_ $abc$8517$new_n703_ -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110001 1 -110100 1 -110101 1 -111000 1 -111010 1 -111100 1 -111110 1 -.names $abc$8517$new_n356_ $abc$8517$new_n517_ $abc$8517$new_n504_ $abc$8517$new_n516_ $abc$8517$new_n515_ $abc$8517$new_n704_ -00000 1 -00100 1 -01000 1 -01100 1 -10000 1 -10001 1 -10010 1 -10011 1 -.names $abc$8517$new_n551_ $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n692_ $abc$8517$new_n531_ $abc$8517$new_n536_ $abc$8517$new_n707_ -100100 1 -100101 1 -100110 1 -100111 1 -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110010 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n399_ $abc$8517$new_n397_ $abc$8517$new_n709_ -00 1 -.names $abc$8517$new_n474_ $abc$8517$new_n409_ $abc$8517$new_n341_ $abc$8517$new_n476_ $abc$8517$new_n640_ $abc$8517$new_n639_ $abc$8517$new_n711_ -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -000110 1 -000111 1 -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001110 1 -001111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101101 1 -101110 1 -101111 1 -.names $abc$8517$new_n399_ $abc$8517$new_n397_ $abc$8517$new_n712_ -01 1 -10 1 -.names $abc$8517$new_n712_ $abc$8517$new_n373_ $abc$8517$new_n372_ $abc$8517$new_n371_ $abc$8517$new_n698_ $abc$8517$new_n662_ $abc$8517$new_n714_ -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -101000 1 -101001 1 -101010 1 -101100 1 -110000 1 -110001 1 -110010 1 -110100 1 -111000 1 -111001 1 -.names $abc$8517$new_n548_ $abc$8517$new_n406_ $abc$8517$new_n399_ $abc$8517$new_n397_ $abc$8517$new_n711_ $abc$8517$new_n715_ -10001 1 -10100 1 -10101 1 -10111 1 -11000 1 -11010 1 -11011 1 -11110 1 -.names $abc$8517$new_n692_ $abc$8517$new_n559_ $abc$8517$new_n566_ $abc$8517$new_n718_ $abc$8517$new_n564_ $abc$8517$new_n719_ $abc$8517$new_n717_ -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -001000 1 -001010 1 -001100 1 -010000 1 -010010 1 -011000 1 -011010 1 -.names $abc$8517$new_n486_ $abc$8517$new_n550_ $abc$8517$new_n368_ $abc$8517$new_n718_ -111 1 -.names $abc$8517$new_n486_ $abc$8517$new_n368_ $abc$8517$new_n550_ $abc$8517$new_n479_ $abc$8517$new_n719_ -1110 1 -.names $abc$8517$new_n543_ $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n581_ $abc$8517$new_n655_ $abc$8517$new_n721_ $abc$8517$new_n720_ -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110001 1 -110100 1 -110101 1 -111000 1 -111010 1 -111100 1 -111110 1 -.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n398_ $abc$8517$new_n371_ $abc$8517$new_n399_ $abc$8517$new_n373_ $abc$8517$new_n721_ -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n486_ $abc$8517$new_n497_ $abc$8517$new_n398_ $abc$8517$new_n396_ $abc$8517$new_n479_ $abc$8517$new_n724_ -00010 1 -00100 1 -00110 1 -00111 1 -01000 1 -01001 1 -01010 1 -01011 1 -01100 1 -01101 1 -01110 1 -01111 1 -10000 1 -10001 1 -10011 1 -10101 1 -10110 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n730_ $abc$8517$new_n740_ $abc$8517$new_n737_ $abc$8517$new_n726_ $abc$8517$new_n729_ $abc$8517$new_n546_ bus_address[10] -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -000110 1 -000111 1 -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -100001 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n392_ $abc$8517$new_n728_ $abc$8517$new_n727_ $abc$8517$new_n714_ $abc$8517$new_n726_ -0010 1 -0100 1 -0101 1 -0110 1 -0111 1 -.names $abc$8517$new_n709_ $abc$8517$new_n396_ $abc$8517$new_n398_ $abc$8517$new_n727_ -001 1 -010 1 -011 1 -.names $abc$8517$new_n398_ $abc$8517$new_n396_ $abc$8517$new_n728_ -11 1 -.names $abc$8517$new_n392_ $abc$8517$new_n728_ $abc$8517$new_n714_ $abc$8517$new_n727_ $abc$8517$new_n729_ -1000 1 -1010 1 -1011 1 -.names $abc$8517$new_n731_ $abc$8517$new_n733_ $abc$8517$new_n368_ $abc$8517$new_n543_ $abc$8517$new_n734_ $abc$8517$new_n600_ $abc$8517$new_n730_ -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100110 1 -101000 1 -101001 1 -101010 1 -101011 1 -101110 1 -101111 1 -.names $abc$8517$new_n393_ $abc$8517$new_n576_ $abc$8517$new_n732_ $abc$8517$new_n547_ $abc$8517$new_n579_ $abc$8517$new_n731_ -00000 1 -00010 1 -00100 1 -00101 1 -10000 1 -10001 1 -10100 1 -10101 1 -10110 1 -10111 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n310_ rs2_data[10] inst[30] $abc$8517$new_n324_ $abc$8517$new_n732_ -0000 1 -0001 1 -0011 1 -0100 1 -0101 1 -0111 1 -1000 1 -1001 1 -1010 1 -1011 1 -.names $abc$8517$new_n368_ $abc$8517$new_n549_ $abc$8517$new_n585_ $abc$8517$new_n584_ $abc$8517$new_n595_ $abc$8517$new_n596_ $abc$8517$new_n733_ -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -110001 1 -110010 1 -110011 1 -110101 1 -110110 1 -110111 1 -111001 1 -111010 1 -111011 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n681_ $abc$8517$new_n735_ $abc$8517$new_n736_ $abc$8517$new_n734_ -00100 1 -00101 1 -00110 1 -00111 1 -01100 1 -01101 1 -01110 1 -01111 1 -10010 1 -10011 1 -10110 1 -10111 1 -11001 1 -11011 1 -11101 1 -11111 1 -.names $abc$8517$new_n517_ $abc$8517$new_n504_ $abc$8517$new_n735_ -00 1 -.names $abc$8517$new_n506_ $abc$8517$new_n505_ $abc$8517$new_n736_ -00 1 -.names $abc$8517$new_n548_ $abc$8517$new_n392_ $abc$8517$new_n738_ $abc$8517$new_n405_ $abc$8517$new_n711_ $abc$8517$new_n737_ -10100 1 -10101 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -11110 1 -.names $abc$8517$new_n398_ $abc$8517$new_n396_ $abc$8517$new_n739_ $abc$8517$new_n738_ -000 1 -100 1 -101 1 -110 1 -.names $abc$8517$new_n397_ $abc$8517$new_n399_ $abc$8517$new_n739_ -10 1 -.names $abc$8517$new_n345_ $abc$8517$new_n742_ $abc$8517$new_n692_ $abc$8517$new_n718_ $abc$8517$new_n741_ $abc$8517$new_n740_ -00011 1 -00100 1 -00101 1 -00110 1 -00111 1 -01000 1 -01001 1 -01010 1 -01011 1 -01100 1 -01101 1 -01110 1 -01111 1 -.names $abc$8517$new_n587_ $abc$8517$new_n586_ $abc$8517$new_n741_ -00 1 -.names $abc$8517$new_n719_ $abc$8517$new_n589_ $abc$8517$new_n586_ $abc$8517$new_n742_ -100 1 -.names $abc$8517$new_n546_ $abc$8517$new_n755_ $abc$8517$new_n757_ $abc$8517$new_n729_ $abc$8517$new_n1376_ bus_address[11] -00000 1 -00010 1 -00100 1 -00110 1 -01000 1 -01010 1 -01100 1 -01110 1 -10000 1 -10010 1 -10011 1 -10100 1 -10101 1 -10110 1 -10111 1 -11000 1 -11001 1 -11010 1 -11100 1 -11110 1 -.names $abc$8517$new_n392_ $abc$8517$new_n738_ $abc$8517$new_n405_ $abc$8517$new_n711_ $abc$8517$new_n745_ -0000 1 -0001 1 -0010 1 -0011 1 -0110 1 -.names $abc$8517$new_n345_ $abc$8517$new_n748_ $abc$8517$new_n692_ $abc$8517$new_n719_ $abc$8517$new_n747_ $abc$8517$new_n746_ -00010 1 -00100 1 -00101 1 -00110 1 -00111 1 -01000 1 -01001 1 -01010 1 -01011 1 -01100 1 -01101 1 -01110 1 -01111 1 -.names $abc$8517$new_n362_ $abc$8517$new_n434_ $abc$8517$new_n747_ -01 1 -10 1 -11 1 -.names $abc$8517$new_n718_ $abc$8517$new_n362_ $abc$8517$new_n607_ $abc$8517$new_n608_ $abc$8517$new_n748_ -1001 1 -1011 1 -1100 1 -1101 1 -.names $abc$8517$new_n543_ $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n612_ $abc$8517$new_n697_ $abc$8517$new_n753_ $abc$8517$new_n752_ -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110001 1 -110100 1 -110101 1 -111000 1 -111010 1 -111100 1 -111110 1 -.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n394_ $abc$8517$new_n398_ $abc$8517$new_n393_ $abc$8517$new_n399_ $abc$8517$new_n753_ -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n486_ $abc$8517$new_n497_ $abc$8517$new_n394_ $abc$8517$new_n388_ $abc$8517$new_n479_ $abc$8517$new_n754_ -00010 1 -00100 1 -00110 1 -00111 1 -01000 1 -01001 1 -01010 1 -01011 1 -01100 1 -01101 1 -01110 1 -01111 1 -10000 1 -10001 1 -10011 1 -10101 1 -10110 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n394_ $abc$8517$new_n388_ $abc$8517$new_n755_ -01 1 -10 1 -.names $abc$8517$new_n310_ $abc$8517$new_n393_ rs2_data[10] $abc$8517$new_n324_ inst[30] $abc$8517$new_n756_ -00000 1 -00010 1 -00011 1 -00100 1 -00110 1 -00111 1 -10000 1 -10001 1 -10010 1 -10011 1 -.names $abc$8517$new_n393_ $abc$8517$new_n732_ $abc$8517$new_n757_ -00 1 -.names $abc$8517$new_n546_ $abc$8517$new_n772_ $abc$8517$new_n759_ $abc$8517$new_n1379_ bus_address[12] -0000 1 -0010 1 -0100 1 -0110 1 -1000 1 -1010 1 -1011 1 -1100 1 -1101 1 -1110 1 -.names $abc$8517$new_n392_ $abc$8517$new_n755_ $abc$8517$new_n728_ $abc$8517$new_n760_ $abc$8517$new_n714_ $abc$8517$new_n727_ $abc$8517$new_n759_ -000000 1 -000001 1 -000010 1 -000011 1 -001000 1 -001001 1 -001010 1 -001011 1 -010000 1 -010001 1 -010010 1 -010011 1 -011000 1 -011001 1 -011010 1 -011011 1 -100000 1 -100001 1 -100010 1 -100011 1 -101000 1 -101001 1 -101010 1 -101011 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -.names $abc$8517$new_n394_ $abc$8517$new_n388_ $abc$8517$new_n757_ $abc$8517$new_n760_ -010 1 -100 1 -110 1 -111 1 -.names $abc$8517$new_n345_ $abc$8517$new_n630_ $abc$8517$new_n719_ $abc$8517$new_n692_ $abc$8517$new_n629_ $abc$8517$new_n718_ $abc$8517$new_n763_ -000100 1 -000101 1 -000110 1 -000111 1 -001010 1 -001011 1 -001100 1 -001101 1 -001110 1 -001111 1 -010001 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n542_ $abc$8517$new_n635_ $abc$8517$new_n704_ $abc$8517$new_n767_ $abc$8517$new_n766_ -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -000110 1 -000111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -110001 1 -110011 1 -110101 1 -110111 1 -111001 1 -111011 1 -111101 1 -111111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n509_ $abc$8517$new_n507_ $abc$8517$new_n505_ $abc$8517$new_n506_ $abc$8517$new_n767_ -00000 1 -00100 1 -01000 1 -01100 1 -10000 1 -10001 1 -10010 1 -10011 1 -.names $abc$8517$new_n486_ $abc$8517$new_n497_ $abc$8517$new_n386_ $abc$8517$new_n385_ $abc$8517$new_n479_ $abc$8517$new_n768_ -00010 1 -00100 1 -00110 1 -00111 1 -01000 1 -01001 1 -01010 1 -01011 1 -01100 1 -01101 1 -01110 1 -01111 1 -10000 1 -10001 1 -10011 1 -10101 1 -10110 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n405_ $abc$8517$new_n387_ $abc$8517$new_n769_ -11 1 -.names $abc$8517$new_n387_ $abc$8517$new_n398_ $abc$8517$new_n396_ $abc$8517$new_n739_ $abc$8517$new_n402_ $abc$8517$new_n770_ -00001 1 -00011 1 -00101 1 -00111 1 -01001 1 -01011 1 -01101 1 -01111 1 -10000 1 -10001 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -.names $abc$8517$new_n386_ $abc$8517$new_n385_ $abc$8517$new_n772_ -01 1 -10 1 -.names $abc$8517$new_n546_ $abc$8517$new_n382_ $abc$8517$new_n386_ $abc$8517$new_n385_ $abc$8517$new_n759_ $abc$8517$new_n1382_ bus_address[13] -000000 1 -000010 1 -000100 1 -000110 1 -001000 1 -001010 1 -001100 1 -001110 1 -010000 1 -010010 1 -010100 1 -010110 1 -011000 1 -011010 1 -011100 1 -011110 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100110 1 -100111 1 -101000 1 -101010 1 -101011 1 -101100 1 -101110 1 -110000 1 -110010 1 -110100 1 -110101 1 -110110 1 -111000 1 -111001 1 -111010 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n772_ $abc$8517$new_n770_ $abc$8517$new_n769_ $abc$8517$new_n711_ $abc$8517$new_n775_ -0000 1 -0001 1 -0010 1 -0011 1 -0110 1 -.names $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n581_ $abc$8517$new_n655_ $abc$8517$new_n721_ $abc$8517$new_n778_ $abc$8517$new_n777_ -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -000110 1 -000111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -110001 1 -110011 1 -110101 1 -110111 1 -111001 1 -111011 1 -111101 1 -111111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n384_ $abc$8517$new_n394_ $abc$8517$new_n386_ $abc$8517$new_n393_ $abc$8517$new_n778_ -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n345_ $abc$8517$new_n692_ $abc$8517$new_n718_ $abc$8517$new_n651_ $abc$8517$new_n719_ $abc$8517$new_n652_ $abc$8517$new_n781_ -000010 1 -000110 1 -001010 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n486_ $abc$8517$new_n497_ $abc$8517$new_n384_ $abc$8517$new_n401_ $abc$8517$new_n479_ $abc$8517$new_n782_ -00010 1 -00100 1 -00110 1 -00111 1 -01000 1 -01001 1 -01010 1 -01011 1 -01100 1 -01101 1 -01110 1 -01111 1 -10000 1 -10001 1 -10011 1 -10101 1 -10110 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n385_ $abc$8517$new_n386_ $abc$8517$new_n783_ -10 1 -.names $abc$8517$new_n797_ $abc$8517$new_n548_ $abc$8517$new_n787_ $abc$8517$new_n546_ $abc$8517$new_n785_ $abc$8517$new_n788_ bus_address[14] -000000 1 -000010 1 -000100 1 -000110 1 -000111 1 -001000 1 -001010 1 -001100 1 -001110 1 -001111 1 -010000 1 -010010 1 -010100 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -100000 1 -100010 1 -100100 1 -100101 1 -100110 1 -101000 1 -101010 1 -101100 1 -101101 1 -101110 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111010 1 -111100 1 -111101 1 -111110 1 -.names $abc$8517$new_n786_ $abc$8517$new_n759_ $abc$8517$new_n772_ $abc$8517$new_n382_ $abc$8517$new_n785_ -0000 1 -0001 1 -0010 1 -0011 1 -0100 1 -0101 1 -0110 1 -0111 1 -1111 1 -.names $abc$8517$new_n386_ $abc$8517$new_n385_ $abc$8517$new_n384_ $abc$8517$new_n401_ $abc$8517$new_n786_ -0011 1 -0101 1 -0110 1 -0111 1 -1001 1 -1010 1 -1011 1 -1101 1 -1110 1 -1111 1 -.names $abc$8517$new_n384_ $abc$8517$new_n401_ $abc$8517$new_n783_ $abc$8517$new_n775_ $abc$8517$new_n787_ -0000 1 -1000 1 -1001 1 -1010 1 -1100 1 -.names $abc$8517$new_n796_ $abc$8517$new_n794_ $abc$8517$new_n345_ $abc$8517$new_n543_ $abc$8517$new_n795_ $abc$8517$new_n789_ $abc$8517$new_n788_ -100010 1 -100011 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101101 1 -101111 1 -.names $abc$8517$new_n368_ $abc$8517$new_n790_ $abc$8517$new_n791_ $abc$8517$new_n789_ -010 1 -011 1 -101 1 -111 1 -.names $abc$8517$new_n362_ $abc$8517$new_n680_ $abc$8517$new_n681_ $abc$8517$new_n790_ -000 1 -001 1 -101 1 -111 1 -.names $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n735_ $abc$8517$new_n736_ $abc$8517$new_n792_ $abc$8517$new_n793_ $abc$8517$new_n791_ -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001110 1 -001111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -110001 1 -110011 1 -110101 1 -110111 1 -111001 1 -111011 1 -111101 1 -111111 1 -.names $abc$8517$new_n509_ $abc$8517$new_n507_ $abc$8517$new_n792_ -00 1 -.names $abc$8517$new_n511_ $abc$8517$new_n510_ $abc$8517$new_n793_ -00 1 -.names $abc$8517$new_n549_ $abc$8517$new_n368_ $abc$8517$new_n666_ $abc$8517$new_n1367_ $abc$8517$new_n794_ -1000 1 -1001 1 -1100 1 -1110 1 -.names $abc$8517$new_n692_ $abc$8517$new_n671_ $abc$8517$new_n719_ $abc$8517$new_n718_ $abc$8517$new_n670_ $abc$8517$new_n795_ -00000 1 -00001 1 -00010 1 -01000 1 -01001 1 -01010 1 -01100 1 -01101 1 -01110 1 -.names $abc$8517$new_n486_ $abc$8517$new_n497_ $abc$8517$new_n380_ $abc$8517$new_n376_ $abc$8517$new_n479_ $abc$8517$new_n796_ -00010 1 -00100 1 -00110 1 -00111 1 -01000 1 -01001 1 -01010 1 -01011 1 -01100 1 -01101 1 -01110 1 -01111 1 -10000 1 -10001 1 -10011 1 -10101 1 -10110 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n380_ $abc$8517$new_n376_ $abc$8517$new_n797_ -01 1 -10 1 -.names $abc$8517$new_n808_ $abc$8517$new_n802_ $abc$8517$new_n800_ $abc$8517$new_n377_ $abc$8517$new_n799_ $abc$8517$new_n546_ bus_address[15] -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -000110 1 -000111 1 -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -100001 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n380_ $abc$8517$new_n376_ $abc$8517$new_n382_ $abc$8517$new_n759_ $abc$8517$new_n772_ $abc$8517$new_n786_ $abc$8517$new_n799_ -010001 1 -010011 1 -010101 1 -010111 1 -011001 1 -011011 1 -011101 1 -100001 1 -100011 1 -100101 1 -100111 1 -101001 1 -101011 1 -101101 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n548_ $abc$8517$new_n377_ $abc$8517$new_n787_ $abc$8517$new_n376_ $abc$8517$new_n380_ $abc$8517$new_n800_ -10001 1 -10100 1 -10101 1 -10111 1 -11000 1 -11010 1 -11011 1 -11110 1 -.names $abc$8517$new_n543_ $abc$8517$new_n803_ $abc$8517$new_n802_ -10 1 -.names $abc$8517$new_n368_ $abc$8517$new_n804_ $abc$8517$new_n807_ $abc$8517$new_n803_ -001 1 -011 1 -110 1 -111 1 -.names $abc$8517$new_n362_ $abc$8517$new_n805_ $abc$8517$new_n753_ $abc$8517$new_n804_ -001 1 -011 1 -110 1 -111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n806_ $abc$8517$new_n384_ $abc$8517$new_n386_ $abc$8517$new_n805_ -00001 1 -00011 1 -00101 1 -00111 1 -01010 1 -01011 1 -01110 1 -01111 1 -10100 1 -10101 1 -10110 1 -10111 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n519_ $abc$8517$new_n379_ $abc$8517$new_n380_ $abc$8517$new_n806_ -001 1 -011 1 -110 1 -111 1 -.names $abc$8517$new_n362_ $abc$8517$new_n612_ $abc$8517$new_n697_ $abc$8517$new_n807_ -000 1 -001 1 -101 1 -111 1 -.names $abc$8517$new_n809_ $abc$8517$new_n368_ $abc$8517$new_n549_ $abc$8517$new_n812_ $abc$8517$new_n694_ $abc$8517$new_n808_ -10000 1 -10001 1 -10010 1 -10011 1 -10110 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -11101 1 -11111 1 -.names $abc$8517$new_n810_ $abc$8517$new_n811_ $abc$8517$new_n576_ $abc$8517$new_n379_ $abc$8517$new_n408_ $abc$8517$new_n579_ $abc$8517$new_n809_ -100000 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -100111 1 -101110 1 -101111 1 -.names $abc$8517$new_n377_ $abc$8517$new_n547_ $abc$8517$new_n368_ $abc$8517$new_n551_ $abc$8517$new_n690_ $abc$8517$new_n608_ $abc$8517$new_n810_ -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -000110 1 -000111 1 -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -.names $abc$8517$new_n568_ $abc$8517$new_n434_ $abc$8517$new_n345_ $abc$8517$new_n811_ -100 1 -.names $abc$8517$new_n362_ $abc$8517$new_n606_ $abc$8517$new_n607_ $abc$8517$new_n812_ -001 1 -011 1 -110 1 -111 1 -.names $abc$8517$new_n336_ $abc$8517$new_n548_ $abc$8517$new_n826_ $abc$8517$new_n546_ $abc$8517$new_n814_ $abc$8517$new_n817_ bus_address[16] -000000 1 -000010 1 -000100 1 -000110 1 -000111 1 -001000 1 -001010 1 -001100 1 -001110 1 -001111 1 -010000 1 -010010 1 -010100 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -100000 1 -100010 1 -100100 1 -100101 1 -100110 1 -101000 1 -101010 1 -101100 1 -101101 1 -101110 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111010 1 -111100 1 -111101 1 -111110 1 -.names $abc$8517$new_n816_ $abc$8517$new_n815_ $abc$8517$new_n814_ -00 1 -01 1 -11 1 -.names $abc$8517$new_n797_ $abc$8517$new_n377_ $abc$8517$new_n786_ $abc$8517$new_n759_ $abc$8517$new_n772_ $abc$8517$new_n382_ $abc$8517$new_n815_ -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111111 1 -.names $abc$8517$new_n380_ $abc$8517$new_n376_ $abc$8517$new_n379_ $abc$8517$new_n408_ $abc$8517$new_n816_ -0011 1 -0101 1 -0110 1 -0111 1 -1001 1 -1010 1 -1011 1 -1101 1 -1110 1 -1111 1 -.names $abc$8517$new_n818_ $abc$8517$new_n822_ $abc$8517$new_n811_ $abc$8517$new_n520_ $abc$8517$new_n549_ $abc$8517$new_n817_ -10000 1 -10010 1 -10011 1 -.names $abc$8517$new_n821_ $abc$8517$new_n819_ $abc$8517$new_n542_ $abc$8517$new_n362_ $abc$8517$new_n368_ $abc$8517$new_n818_ -10000 1 -10001 1 -10010 1 -10011 1 -10100 1 -10101 1 -10110 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -.names $abc$8517$new_n820_ $abc$8517$new_n345_ $abc$8517$new_n819_ -10 1 -.names $abc$8517$new_n497_ $abc$8517$new_n489_ $abc$8517$new_n478_ $abc$8517$new_n820_ -111 1 -.names $abc$8517$new_n486_ $abc$8517$new_n497_ $abc$8517$new_n339_ $abc$8517$new_n337_ $abc$8517$new_n479_ $abc$8517$new_n821_ -00010 1 -00100 1 -00110 1 -00111 1 -01000 1 -01001 1 -01010 1 -01011 1 -01100 1 -01101 1 -01110 1 -01111 1 -10000 1 -10001 1 -10011 1 -10101 1 -10110 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n543_ $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n823_ $abc$8517$new_n824_ $abc$8517$new_n767_ $abc$8517$new_n822_ -100000 1 -100001 1 -100010 1 -100011 1 -101000 1 -101001 1 -101010 1 -101011 1 -110000 1 -110010 1 -110100 1 -110110 1 -111000 1 -111001 1 -111100 1 -111101 1 -.names $abc$8517$new_n362_ $abc$8517$new_n635_ $abc$8517$new_n704_ $abc$8517$new_n823_ -010 1 -011 1 -101 1 -111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n793_ $abc$8517$new_n825_ $abc$8517$new_n824_ -010 1 -011 1 -101 1 -111 1 -.names $abc$8517$new_n522_ $abc$8517$new_n512_ $abc$8517$new_n825_ -00 1 -.names $abc$8517$new_n827_ $abc$8517$new_n828_ $abc$8517$new_n769_ $abc$8517$new_n711_ $abc$8517$new_n826_ -1000 1 -1001 1 -1010 1 -1011 1 -1100 1 -1101 1 -1111 1 -.names $abc$8517$new_n829_ $abc$8517$new_n770_ $abc$8517$new_n828_ $abc$8517$new_n827_ -100 1 -110 1 -111 1 -.names $abc$8517$new_n381_ $abc$8517$new_n375_ $abc$8517$new_n828_ -11 1 -.names $abc$8517$new_n375_ $abc$8517$new_n384_ $abc$8517$new_n401_ $abc$8517$new_n783_ $abc$8517$new_n407_ $abc$8517$new_n829_ -00001 1 -00011 1 -00101 1 -00111 1 -01001 1 -01011 1 -01101 1 -01111 1 -10000 1 -10001 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -.names $abc$8517$new_n319_ $abc$8517$new_n548_ $abc$8517$new_n833_ $abc$8517$new_n546_ $abc$8517$new_n832_ $abc$8517$new_n1384_ bus_address[17] -000000 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001010 1 -001100 1 -001101 1 -001110 1 -010000 1 -010010 1 -010100 1 -010101 1 -010110 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -100000 1 -100010 1 -100100 1 -100110 1 -100111 1 -101000 1 -101010 1 -101100 1 -101110 1 -101111 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111010 1 -111100 1 -111110 1 -111111 1 -.names $abc$8517$new_n816_ $abc$8517$new_n339_ $abc$8517$new_n815_ $abc$8517$new_n337_ $abc$8517$new_n832_ -0101 1 -0111 1 -1001 1 -1100 1 -1101 1 -1111 1 -.names $abc$8517$new_n834_ $abc$8517$new_n337_ $abc$8517$new_n339_ $abc$8517$new_n833_ -000 1 -001 1 -011 1 -.names $abc$8517$new_n336_ $abc$8517$new_n827_ $abc$8517$new_n828_ $abc$8517$new_n769_ $abc$8517$new_n711_ $abc$8517$new_n834_ -00000 1 -00001 1 -00010 1 -00011 1 -00100 1 -00101 1 -00110 1 -00111 1 -01110 1 -.names $abc$8517$new_n362_ $abc$8517$new_n655_ $abc$8517$new_n721_ $abc$8517$new_n837_ -010 1 -011 1 -101 1 -111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n806_ $abc$8517$new_n839_ $abc$8517$new_n838_ -010 1 -011 1 -101 1 -111 1 -.names $abc$8517$new_n519_ $abc$8517$new_n321_ $abc$8517$new_n339_ $abc$8517$new_n839_ -001 1 -011 1 -110 1 -111 1 -.names $abc$8517$new_n811_ $abc$8517$new_n321_ $abc$8517$new_n576_ $abc$8517$new_n420_ $abc$8517$new_n547_ $abc$8517$new_n579_ $abc$8517$new_n840_ -000000 1 -000010 1 -000100 1 -000101 1 -010000 1 -010001 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n816_ $abc$8517$new_n321_ $abc$8517$new_n339_ $abc$8517$new_n815_ $abc$8517$new_n337_ $abc$8517$new_n420_ $abc$8517$new_n843_ -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -000110 1 -000111 1 -001000 1 -001001 1 -001010 1 -001100 1 -001101 1 -001110 1 -010000 1 -010010 1 -010100 1 -010110 1 -011000 1 -011100 1 -100000 1 -100001 1 -100010 1 -100100 1 -100101 1 -100110 1 -100111 1 -101000 1 -101010 1 -101100 1 -101101 1 -101110 1 -110000 1 -110100 1 -110110 1 -111100 1 -.names $abc$8517$new_n548_ $abc$8517$new_n316_ $abc$8517$new_n846_ $abc$8517$new_n847_ $abc$8517$new_n834_ $abc$8517$new_n845_ -10010 1 -10100 1 -10101 1 -10110 1 -10111 1 -11000 1 -11001 1 -11011 1 -.names $abc$8517$new_n321_ $abc$8517$new_n420_ $abc$8517$new_n846_ -10 1 -.names $abc$8517$new_n420_ $abc$8517$new_n321_ $abc$8517$new_n337_ $abc$8517$new_n339_ $abc$8517$new_n847_ -0000 1 -0001 1 -0011 1 -0100 1 -0101 1 -0111 1 -1100 1 -1101 1 -1111 1 -.names $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n851_ $abc$8517$new_n792_ $abc$8517$new_n793_ $abc$8517$new_n850_ -00010 1 -00011 1 -00110 1 -00111 1 -01001 1 -01011 1 -01101 1 -01111 1 -10100 1 -10101 1 -10110 1 -10111 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n825_ $abc$8517$new_n852_ $abc$8517$new_n851_ -010 1 -011 1 -101 1 -111 1 -.names $abc$8517$new_n524_ $abc$8517$new_n523_ $abc$8517$new_n852_ -00 1 -.names $abc$8517$new_n811_ $abc$8517$new_n318_ $abc$8517$new_n576_ $abc$8517$new_n423_ $abc$8517$new_n547_ $abc$8517$new_n579_ $abc$8517$new_n853_ -000000 1 -000010 1 -000100 1 -000101 1 -010000 1 -010001 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n546_ $abc$8517$new_n309_ $abc$8517$new_n865_ $abc$8517$new_n316_ $abc$8517$new_n843_ $abc$8517$new_n855_ bus_address[19] -000000 1 -000010 1 -000100 1 -000110 1 -001000 1 -001010 1 -001100 1 -001110 1 -010000 1 -010010 1 -010100 1 -010110 1 -011000 1 -011010 1 -011100 1 -011110 1 -100000 1 -100010 1 -100100 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -111000 1 -111010 1 -111100 1 -111110 1 -.names $abc$8517$new_n857_ $abc$8517$new_n309_ $abc$8517$new_n856_ $abc$8517$new_n548_ $abc$8517$new_n855_ -1000 1 -1001 1 -1010 1 -1100 1 -1110 1 -1111 1 -.names $abc$8517$new_n318_ $abc$8517$new_n423_ $abc$8517$new_n847_ $abc$8517$new_n834_ $abc$8517$new_n846_ $abc$8517$new_n856_ -00001 1 -00011 1 -00100 1 -00101 1 -00111 1 -10000 1 -10001 1 -10010 1 -10011 1 -10100 1 -10101 1 -10110 1 -10111 1 -11001 1 -11011 1 -11100 1 -11101 1 -11111 1 -.names $abc$8517$new_n858_ $abc$8517$new_n864_ $abc$8517$new_n362_ $abc$8517$new_n368_ $abc$8517$new_n612_ $abc$8517$new_n819_ $abc$8517$new_n857_ -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -.names $abc$8517$new_n345_ $abc$8517$new_n820_ $abc$8517$new_n368_ $abc$8517$new_n859_ $abc$8517$new_n860_ $abc$8517$new_n863_ $abc$8517$new_n858_ -100000 1 -100001 1 -100010 1 -100011 1 -101000 1 -101001 1 -101010 1 -101011 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110110 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -.names $abc$8517$new_n567_ $abc$8517$new_n604_ $abc$8517$new_n568_ $abc$8517$new_n609_ $abc$8517$new_n859_ -0000 1 -0001 1 -0011 1 -0100 1 -0101 1 -0111 1 -1100 1 -1101 1 -1111 1 -.names $abc$8517$new_n362_ $abc$8517$new_n805_ $abc$8517$new_n861_ $abc$8517$new_n860_ -010 1 -011 1 -101 1 -111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n839_ $abc$8517$new_n862_ $abc$8517$new_n861_ -010 1 -011 1 -101 1 -111 1 -.names $abc$8517$new_n519_ $abc$8517$new_n315_ $abc$8517$new_n318_ $abc$8517$new_n862_ -001 1 -011 1 -110 1 -111 1 -.names $abc$8517$new_n362_ $abc$8517$new_n697_ $abc$8517$new_n753_ $abc$8517$new_n863_ -010 1 -011 1 -101 1 -111 1 -.names $abc$8517$new_n811_ $abc$8517$new_n315_ $abc$8517$new_n576_ $abc$8517$new_n422_ $abc$8517$new_n547_ $abc$8517$new_n579_ $abc$8517$new_n864_ -000000 1 -000010 1 -000100 1 -000101 1 -010000 1 -010001 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n318_ $abc$8517$new_n423_ $abc$8517$new_n865_ -00 1 -.names $abc$8517$new_n546_ $abc$8517$new_n333_ $abc$8517$new_n315_ $abc$8517$new_n422_ $abc$8517$new_n867_ $abc$8517$new_n1389_ bus_address[20] -000000 1 -000010 1 -000100 1 -000110 1 -001000 1 -001010 1 -001100 1 -001110 1 -010000 1 -010010 1 -010100 1 -010110 1 -011000 1 -011010 1 -011100 1 -011110 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -101000 1 -101001 1 -101010 1 -101100 1 -101110 1 -110000 1 -110010 1 -110100 1 -110110 1 -110111 1 -111000 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n865_ $abc$8517$new_n843_ $abc$8517$new_n316_ $abc$8517$new_n867_ -000 1 -001 1 -010 1 -.names $abc$8517$new_n870_ $abc$8517$new_n834_ $abc$8517$new_n308_ $abc$8517$new_n869_ -100 1 -101 1 -110 1 -.names $abc$8517$new_n871_ $abc$8517$new_n846_ $abc$8517$new_n847_ $abc$8517$new_n316_ $abc$8517$new_n309_ $abc$8517$new_n870_ -00001 1 -00010 1 -00011 1 -00100 1 -00101 1 -00110 1 -00111 1 -01000 1 -01001 1 -01010 1 -01011 1 -01100 1 -01101 1 -01110 1 -01111 1 -.names $abc$8517$new_n423_ $abc$8517$new_n422_ $abc$8517$new_n318_ $abc$8517$new_n315_ $abc$8517$new_n871_ -0100 1 -0110 1 -1000 1 -1100 1 -1101 1 -1110 1 -.names $abc$8517$new_n362_ $abc$8517$new_n824_ $abc$8517$new_n875_ $abc$8517$new_n874_ -010 1 -011 1 -101 1 -111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n852_ $abc$8517$new_n876_ $abc$8517$new_n875_ -010 1 -011 1 -101 1 -111 1 -.names $abc$8517$new_n527_ $abc$8517$new_n525_ $abc$8517$new_n876_ -00 1 -.names $abc$8517$new_n362_ $abc$8517$new_n704_ $abc$8517$new_n767_ $abc$8517$new_n877_ -010 1 -011 1 -101 1 -111 1 -.names $abc$8517$new_n811_ $abc$8517$new_n335_ $abc$8517$new_n576_ $abc$8517$new_n418_ $abc$8517$new_n547_ $abc$8517$new_n579_ $abc$8517$new_n878_ -000000 1 -000010 1 -000100 1 -000101 1 -010000 1 -010001 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n345_ $abc$8517$new_n627_ $abc$8517$new_n567_ $abc$8517$new_n568_ $abc$8517$new_n628_ $abc$8517$new_n631_ $abc$8517$new_n879_ -100110 1 -100111 1 -101001 1 -101011 1 -101101 1 -101110 1 -101111 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n546_ $abc$8517$new_n330_ $abc$8517$new_n892_ $abc$8517$new_n881_ $abc$8517$new_n882_ bus_address[21] -00000 1 -00010 1 -00100 1 -00110 1 -01000 1 -01010 1 -01100 1 -01110 1 -10000 1 -10010 1 -10011 1 -10100 1 -10101 1 -10110 1 -10111 1 -11000 1 -11001 1 -11010 1 -11100 1 -11110 1 -.names $abc$8517$new_n333_ $abc$8517$new_n315_ $abc$8517$new_n422_ $abc$8517$new_n316_ $abc$8517$new_n843_ $abc$8517$new_n865_ $abc$8517$new_n881_ -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -100111 1 -101001 1 -101011 1 -101101 1 -101110 1 -101111 1 -110001 1 -110011 1 -110101 1 -110110 1 -110111 1 -.names $abc$8517$new_n883_ $abc$8517$new_n333_ $abc$8517$new_n330_ rs1_data[20] $abc$8517$new_n869_ $abc$8517$new_n548_ $abc$8517$new_n882_ -100000 1 -100001 1 -100010 1 -100100 1 -100101 1 -100110 1 -101000 1 -101010 1 -101011 1 -101100 1 -101110 1 -101111 1 -110000 1 -110010 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111110 1 -.names $abc$8517$new_n890_ $abc$8517$new_n345_ $abc$8517$new_n884_ $abc$8517$new_n368_ $abc$8517$new_n819_ $abc$8517$new_n654_ $abc$8517$new_n883_ -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111111 1 -.names $abc$8517$new_n885_ $abc$8517$new_n368_ $abc$8517$new_n820_ $abc$8517$new_n886_ $abc$8517$new_n889_ $abc$8517$new_n884_ -10000 1 -10001 1 -10010 1 -10011 1 -10101 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -11110 1 -11111 1 -.names $abc$8517$new_n650_ $abc$8517$new_n368_ $abc$8517$new_n568_ $abc$8517$new_n648_ $abc$8517$new_n652_ $abc$8517$new_n885_ -00000 1 -00001 1 -00010 1 -00011 1 -00101 1 -00111 1 -01000 1 -01001 1 -01010 1 -01011 1 -01110 1 -01111 1 -.names $abc$8517$new_n362_ $abc$8517$new_n838_ $abc$8517$new_n887_ $abc$8517$new_n886_ -010 1 -011 1 -101 1 -111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n862_ $abc$8517$new_n888_ $abc$8517$new_n887_ -010 1 -011 1 -101 1 -111 1 -.names $abc$8517$new_n519_ $abc$8517$new_n332_ $abc$8517$new_n335_ $abc$8517$new_n888_ -001 1 -011 1 -110 1 -111 1 -.names $abc$8517$new_n362_ $abc$8517$new_n721_ $abc$8517$new_n778_ $abc$8517$new_n889_ -010 1 -011 1 -101 1 -111 1 -.names $abc$8517$new_n811_ $abc$8517$new_n332_ $abc$8517$new_n576_ $abc$8517$new_n416_ $abc$8517$new_n547_ $abc$8517$new_n579_ $abc$8517$new_n890_ -000000 1 -000010 1 -000100 1 -000101 1 -010000 1 -010001 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n335_ $abc$8517$new_n418_ $abc$8517$new_n892_ -00 1 -.names $abc$8517$new_n546_ $abc$8517$new_n327_ $abc$8517$new_n332_ $abc$8517$new_n416_ $abc$8517$new_n892_ $abc$8517$new_n881_ $abc$8517$new_n894_ -100000 1 -100001 1 -100010 1 -100011 1 -100101 1 -100110 1 -100111 1 -101001 1 -101010 1 -101011 1 -110100 1 -111000 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n548_ $abc$8517$new_n327_ $abc$8517$new_n415_ $abc$8517$new_n333_ $abc$8517$new_n330_ $abc$8517$new_n869_ $abc$8517$new_n896_ -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -.names $abc$8517$new_n820_ $abc$8517$new_n345_ $abc$8517$new_n368_ $abc$8517$new_n898_ $abc$8517$new_n790_ $abc$8517$new_n791_ $abc$8517$new_n897_ -101000 1 -101001 1 -101100 1 -101101 1 -110000 1 -110010 1 -110100 1 -110110 1 -111000 1 -111001 1 -111010 1 -111011 1 -.names $abc$8517$new_n362_ $abc$8517$new_n851_ $abc$8517$new_n899_ $abc$8517$new_n898_ -010 1 -011 1 -101 1 -111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n876_ $abc$8517$new_n900_ $abc$8517$new_n899_ -010 1 -011 1 -101 1 -111 1 -.names $abc$8517$new_n529_ $abc$8517$new_n528_ $abc$8517$new_n900_ -00 1 -.names $abc$8517$new_n345_ $abc$8517$new_n568_ $abc$8517$new_n368_ $abc$8517$new_n669_ $abc$8517$new_n666_ $abc$8517$new_n671_ $abc$8517$new_n901_ -100100 1 -100101 1 -100110 1 -100111 1 -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110010 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n323_ $abc$8517$new_n548_ $abc$8517$new_n906_ $abc$8517$new_n546_ $abc$8517$new_n904_ $abc$8517$new_n907_ bus_address[23] -000000 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001010 1 -001100 1 -001101 1 -001110 1 -010000 1 -010010 1 -010100 1 -010101 1 -010110 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -100000 1 -100010 1 -100100 1 -100110 1 -100111 1 -101000 1 -101010 1 -101100 1 -101110 1 -101111 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111010 1 -111100 1 -111110 1 -111111 1 -.names $abc$8517$new_n327_ $abc$8517$new_n332_ $abc$8517$new_n881_ $abc$8517$new_n892_ $abc$8517$new_n416_ $abc$8517$new_n329_ $abc$8517$new_n904_ -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -010001 1 -010011 1 -010101 1 -010111 1 -011001 1 -011011 1 -011101 1 -011111 1 -100010 1 -100011 1 -110000 1 -110001 1 -110010 1 -110011 1 -110110 1 -110111 1 -111010 1 -111011 1 -111110 1 -111111 1 -.names $abc$8517$new_n327_ $abc$8517$new_n428_ $abc$8517$new_n415_ $abc$8517$new_n333_ $abc$8517$new_n330_ $abc$8517$new_n869_ $abc$8517$new_n906_ -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001110 1 -001111 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -.names $abc$8517$new_n913_ $abc$8517$new_n908_ $abc$8517$new_n819_ $abc$8517$new_n807_ $abc$8517$new_n368_ $abc$8517$new_n907_ -10000 1 -10001 1 -10010 1 -10011 1 -10100 1 -10110 1 -10111 1 -.names $abc$8517$new_n345_ $abc$8517$new_n820_ $abc$8517$new_n368_ $abc$8517$new_n909_ $abc$8517$new_n804_ $abc$8517$new_n910_ $abc$8517$new_n908_ -100000 1 -100001 1 -100010 1 -100011 1 -101000 1 -101001 1 -101010 1 -101011 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111110 1 -.names $abc$8517$new_n692_ $abc$8517$new_n691_ $abc$8517$new_n688_ $abc$8517$new_n689_ $abc$8517$new_n909_ -0000 1 -0001 1 -0010 1 -0011 1 -0101 1 -0110 1 -0111 1 -.names $abc$8517$new_n362_ $abc$8517$new_n861_ $abc$8517$new_n911_ $abc$8517$new_n910_ -010 1 -011 1 -101 1 -111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n888_ $abc$8517$new_n912_ $abc$8517$new_n911_ -010 1 -011 1 -101 1 -111 1 -.names $abc$8517$new_n519_ $abc$8517$new_n326_ $abc$8517$new_n329_ $abc$8517$new_n912_ -001 1 -011 1 -110 1 -111 1 -.names $abc$8517$new_n811_ $abc$8517$new_n326_ $abc$8517$new_n576_ $abc$8517$new_n426_ $abc$8517$new_n547_ $abc$8517$new_n579_ $abc$8517$new_n913_ -000000 1 -000010 1 -000100 1 -000101 1 -010000 1 -010001 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n546_ $abc$8517$new_n464_ $abc$8517$new_n326_ $abc$8517$new_n426_ $abc$8517$new_n904_ $abc$8517$new_n915_ bus_address[24] -000000 1 -000010 1 -000100 1 -000110 1 -001000 1 -001010 1 -001100 1 -001110 1 -010000 1 -010010 1 -010100 1 -010110 1 -011000 1 -011010 1 -011100 1 -011110 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -101000 1 -101001 1 -101010 1 -101100 1 -101110 1 -110000 1 -110010 1 -110100 1 -110110 1 -110111 1 -111000 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n919_ $abc$8517$new_n464_ $abc$8517$new_n916_ $abc$8517$new_n548_ $abc$8517$new_n915_ -1000 1 -1001 1 -1010 1 -1100 1 -1110 1 -1111 1 -.names $abc$8517$new_n917_ $abc$8517$new_n918_ $abc$8517$new_n307_ $abc$8517$new_n322_ $abc$8517$new_n870_ $abc$8517$new_n826_ $abc$8517$new_n916_ -100000 1 -100001 1 -100010 1 -100011 1 -100110 1 -100111 1 -101001 1 -101011 1 -101111 1 -.names $abc$8517$new_n326_ $abc$8517$new_n428_ $abc$8517$new_n426_ $abc$8517$new_n917_ -000 1 -100 1 -101 1 -110 1 -.names $abc$8517$new_n327_ $abc$8517$new_n323_ $abc$8517$new_n415_ $abc$8517$new_n918_ -000 1 -.names $abc$8517$new_n921_ $abc$8517$new_n925_ $abc$8517$new_n920_ $abc$8517$new_n922_ $abc$8517$new_n543_ $abc$8517$new_n919_ -10000 1 -10001 1 -10010 1 -.names $abc$8517$new_n819_ $abc$8517$new_n368_ $abc$8517$new_n823_ $abc$8517$new_n362_ $abc$8517$new_n542_ $abc$8517$new_n920_ -10011 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -.names $abc$8517$new_n811_ $abc$8517$new_n460_ $abc$8517$new_n576_ $abc$8517$new_n457_ $abc$8517$new_n547_ $abc$8517$new_n579_ $abc$8517$new_n921_ -000000 1 -000010 1 -000100 1 -000101 1 -010000 1 -010001 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n824_ $abc$8517$new_n875_ $abc$8517$new_n923_ $abc$8517$new_n767_ $abc$8517$new_n922_ -000000 1 -000010 1 -000100 1 -000110 1 -001000 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -100000 1 -100001 1 -100010 1 -100011 1 -101000 1 -101001 1 -101010 1 -101011 1 -110000 1 -110001 1 -110100 1 -110101 1 -111000 1 -111001 1 -111100 1 -111101 1 -.names $abc$8517$new_n356_ $abc$8517$new_n900_ $abc$8517$new_n924_ $abc$8517$new_n923_ -010 1 -011 1 -101 1 -111 1 -.names $abc$8517$new_n532_ $abc$8517$new_n530_ $abc$8517$new_n924_ -00 1 -.names $abc$8517$new_n549_ $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n692_ $abc$8517$new_n531_ $abc$8517$new_n536_ $abc$8517$new_n925_ -100100 1 -100101 1 -100110 1 -100111 1 -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110010 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n463_ $abc$8517$new_n326_ $abc$8517$new_n426_ $abc$8517$new_n460_ $abc$8517$new_n457_ $abc$8517$new_n904_ $abc$8517$new_n927_ -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -001000 1 -001001 1 -001010 1 -001100 1 -010000 1 -010001 1 -010010 1 -010100 1 -011000 1 -011001 1 -100110 1 -100111 1 -101011 1 -101101 1 -101110 1 -101111 1 -110011 1 -110101 1 -110110 1 -110111 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n548_ $abc$8517$new_n463_ $abc$8517$new_n930_ $abc$8517$new_n464_ $abc$8517$new_n916_ $abc$8517$new_n929_ -10001 1 -10010 1 -10011 1 -11000 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n457_ $abc$8517$new_n460_ $abc$8517$new_n930_ -10 1 -.names $abc$8517$new_n819_ $abc$8517$new_n368_ $abc$8517$new_n837_ $abc$8517$new_n362_ $abc$8517$new_n581_ $abc$8517$new_n932_ -10011 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -.names $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n838_ $abc$8517$new_n887_ $abc$8517$new_n934_ $abc$8517$new_n778_ $abc$8517$new_n933_ -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -100100 1 -100101 1 -100110 1 -100111 1 -101100 1 -101101 1 -101110 1 -101111 1 -110010 1 -110011 1 -110110 1 -110111 1 -111010 1 -111011 1 -111110 1 -111111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n912_ $abc$8517$new_n935_ $abc$8517$new_n934_ -010 1 -011 1 -101 1 -111 1 -.names $abc$8517$new_n560_ $abc$8517$new_n519_ $abc$8517$new_n460_ $abc$8517$new_n935_ -001 1 -010 1 -011 1 -.names $abc$8517$new_n459_ $abc$8517$new_n576_ $abc$8517$new_n455_ $abc$8517$new_n547_ $abc$8517$new_n579_ $abc$8517$new_n936_ -00000 1 -00010 1 -00100 1 -00101 1 -10000 1 -10001 1 -10100 1 -10101 1 -10110 1 -10111 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n941_ $abc$8517$new_n939_ $abc$8517$new_n453_ $abc$8517$new_n948_ $abc$8517$new_n938_ $abc$8517$new_n546_ bus_address[26] -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -000110 1 -000111 1 -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -100011 1 -100101 1 -100111 1 -101001 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n463_ $abc$8517$new_n326_ $abc$8517$new_n426_ $abc$8517$new_n460_ $abc$8517$new_n457_ $abc$8517$new_n904_ $abc$8517$new_n938_ -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -101000 1 -101001 1 -101010 1 -101100 1 -110000 1 -110001 1 -110010 1 -110100 1 -111000 1 -111001 1 -.names $abc$8517$new_n548_ $abc$8517$new_n453_ $abc$8517$new_n940_ $abc$8517$new_n939_ -101 1 -110 1 -.names $abc$8517$new_n930_ $abc$8517$new_n459_ $abc$8517$new_n455_ $abc$8517$new_n464_ $abc$8517$new_n916_ $abc$8517$new_n940_ -00001 1 -00010 1 -00011 1 -01000 1 -01001 1 -01010 1 -01011 1 -01101 1 -01110 1 -01111 1 -11000 1 -11001 1 -11010 1 -11011 1 -.names $abc$8517$new_n945_ $abc$8517$new_n947_ $abc$8517$new_n942_ $abc$8517$new_n941_ -100 1 -.names $abc$8517$new_n543_ $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n850_ $abc$8517$new_n899_ $abc$8517$new_n943_ $abc$8517$new_n942_ -100000 1 -100001 1 -100010 1 -100011 1 -101000 1 -101001 1 -101010 1 -101011 1 -110000 1 -110001 1 -110100 1 -110101 1 -111000 1 -111010 1 -111100 1 -111110 1 -.names $abc$8517$new_n356_ $abc$8517$new_n924_ $abc$8517$new_n944_ $abc$8517$new_n943_ -010 1 -011 1 -101 1 -111 1 -.names $abc$8517$new_n534_ $abc$8517$new_n533_ $abc$8517$new_n944_ -00 1 -.names $abc$8517$new_n946_ $abc$8517$new_n368_ $abc$8517$new_n819_ $abc$8517$new_n734_ $abc$8517$new_n600_ $abc$8517$new_n945_ -10000 1 -10001 1 -10010 1 -10011 1 -10100 1 -10110 1 -11000 1 -11001 1 -11010 1 -11011 1 -11110 1 -11111 1 -.names $abc$8517$new_n811_ $abc$8517$new_n450_ $abc$8517$new_n576_ $abc$8517$new_n447_ $abc$8517$new_n547_ $abc$8517$new_n579_ $abc$8517$new_n946_ -000000 1 -000010 1 -000100 1 -000101 1 -010000 1 -010001 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n345_ $abc$8517$new_n742_ $abc$8517$new_n692_ $abc$8517$new_n718_ $abc$8517$new_n741_ $abc$8517$new_n947_ -10011 1 -10100 1 -10101 1 -10110 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n459_ $abc$8517$new_n455_ $abc$8517$new_n948_ -00 1 -.names $abc$8517$new_n1396_ $abc$8517$new_n950_ bus_address[27] -00 1 -01 1 -11 1 -.names $abc$8517$new_n546_ $abc$8517$new_n452_ $abc$8517$new_n450_ $abc$8517$new_n447_ $abc$8517$new_n948_ $abc$8517$new_n938_ $abc$8517$new_n950_ -100000 1 -100001 1 -100010 1 -100011 1 -100101 1 -100110 1 -100111 1 -101001 1 -101010 1 -101011 1 -110100 1 -111000 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n452_ $abc$8517$new_n953_ $abc$8517$new_n453_ $abc$8517$new_n940_ $abc$8517$new_n952_ -0001 1 -0010 1 -0011 1 -1000 1 -1100 1 -1101 1 -1110 1 -1111 1 -.names $abc$8517$new_n447_ $abc$8517$new_n450_ $abc$8517$new_n953_ -10 1 -.names $abc$8517$new_n543_ $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n860_ $abc$8517$new_n911_ $abc$8517$new_n957_ $abc$8517$new_n956_ -100000 1 -100001 1 -100010 1 -100011 1 -101000 1 -101001 1 -101010 1 -101011 1 -110000 1 -110001 1 -110100 1 -110101 1 -111000 1 -111010 1 -111100 1 -111110 1 -.names $abc$8517$new_n356_ $abc$8517$new_n935_ $abc$8517$new_n958_ $abc$8517$new_n957_ -010 1 -011 1 -101 1 -111 1 -.names $abc$8517$new_n562_ $abc$8517$new_n561_ $abc$8517$new_n958_ -00 1 -.names $abc$8517$new_n449_ $abc$8517$new_n576_ $abc$8517$new_n445_ $abc$8517$new_n547_ $abc$8517$new_n579_ $abc$8517$new_n959_ -00000 1 -00010 1 -00100 1 -00101 1 -10000 1 -10001 1 -10100 1 -10101 1 -10110 1 -10111 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n443_ $abc$8517$new_n548_ $abc$8517$new_n963_ $abc$8517$new_n546_ $abc$8517$new_n962_ $abc$8517$new_n964_ bus_address[28] -000000 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001010 1 -001100 1 -001101 1 -001110 1 -010000 1 -010010 1 -010100 1 -010101 1 -010110 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -100000 1 -100010 1 -100100 1 -100110 1 -100111 1 -101000 1 -101010 1 -101100 1 -101110 1 -101111 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111010 1 -111100 1 -111110 1 -111111 1 -.names $abc$8517$new_n449_ $abc$8517$new_n450_ $abc$8517$new_n948_ $abc$8517$new_n938_ $abc$8517$new_n447_ $abc$8517$new_n445_ $abc$8517$new_n962_ -000011 1 -010001 1 -010011 1 -010111 1 -011011 1 -011111 1 -100001 1 -100010 1 -100011 1 -100101 1 -100111 1 -101001 1 -101011 1 -101101 1 -101111 1 -110000 1 -110001 1 -110010 1 -110011 1 -110101 1 -110110 1 -110111 1 -111001 1 -111010 1 -111011 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n953_ $abc$8517$new_n449_ $abc$8517$new_n445_ $abc$8517$new_n453_ $abc$8517$new_n940_ $abc$8517$new_n963_ -00001 1 -00010 1 -00011 1 -01000 1 -01001 1 -01010 1 -01011 1 -01101 1 -01110 1 -01111 1 -11000 1 -11001 1 -11010 1 -11011 1 -.names $abc$8517$new_n965_ $abc$8517$new_n969_ $abc$8517$new_n345_ $abc$8517$new_n964_ -100 1 -110 1 -111 1 -.names $abc$8517$new_n968_ $abc$8517$new_n811_ $abc$8517$new_n966_ $abc$8517$new_n819_ $abc$8517$new_n766_ $abc$8517$new_n965_ -10000 1 -10001 1 -10011 1 -.names $abc$8517$new_n543_ $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n874_ $abc$8517$new_n923_ $abc$8517$new_n967_ $abc$8517$new_n966_ -100000 1 -100001 1 -100010 1 -100011 1 -101000 1 -101001 1 -101010 1 -101011 1 -110000 1 -110001 1 -110100 1 -110101 1 -111000 1 -111010 1 -111100 1 -111110 1 -.names $abc$8517$new_n356_ $abc$8517$new_n944_ $abc$8517$new_n537_ $abc$8517$new_n535_ $abc$8517$new_n967_ -0100 1 -0101 1 -0110 1 -0111 1 -1000 1 -1100 1 -.names $abc$8517$new_n441_ $abc$8517$new_n576_ $abc$8517$new_n438_ $abc$8517$new_n547_ $abc$8517$new_n579_ $abc$8517$new_n968_ -00000 1 -00010 1 -00100 1 -00101 1 -10000 1 -10001 1 -10100 1 -10101 1 -10110 1 -10111 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n692_ $abc$8517$new_n719_ $abc$8517$new_n630_ $abc$8517$new_n629_ $abc$8517$new_n718_ $abc$8517$new_n969_ -00000 1 -00001 1 -00010 1 -00011 1 -00100 1 -00110 1 -01000 1 -01001 1 -.names $abc$8517$new_n546_ $abc$8517$new_n973_ $abc$8517$new_n441_ $abc$8517$new_n438_ $abc$8517$new_n962_ $abc$8517$new_n971_ bus_address[29] -000000 1 -000010 1 -000100 1 -000110 1 -001000 1 -001010 1 -001100 1 -001110 1 -010000 1 -010010 1 -010100 1 -010110 1 -011000 1 -011010 1 -011100 1 -011110 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -101000 1 -101001 1 -101010 1 -101100 1 -101110 1 -110000 1 -110010 1 -110100 1 -110110 1 -110111 1 -111000 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n975_ $abc$8517$new_n978_ $abc$8517$new_n979_ $abc$8517$new_n972_ $abc$8517$new_n971_ -1100 1 -.names $abc$8517$new_n548_ $abc$8517$new_n973_ $abc$8517$new_n974_ $abc$8517$new_n443_ $abc$8517$new_n963_ $abc$8517$new_n972_ -10001 1 -10010 1 -10011 1 -11000 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n440_ $abc$8517$new_n437_ $abc$8517$new_n973_ -01 1 -10 1 -.names $abc$8517$new_n438_ $abc$8517$new_n441_ $abc$8517$new_n974_ -10 1 -.names $abc$8517$new_n543_ $abc$8517$new_n976_ $abc$8517$new_n777_ $abc$8517$new_n819_ $abc$8517$new_n886_ $abc$8517$new_n368_ $abc$8517$new_n975_ -000000 1 -000001 1 -000010 1 -000011 1 -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -101010 1 -101110 1 -110000 1 -110001 1 -110010 1 -110011 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n934_ $abc$8517$new_n958_ $abc$8517$new_n977_ $abc$8517$new_n976_ -100100 1 -100101 1 -100110 1 -100111 1 -101100 1 -101101 1 -101110 1 -101111 1 -110010 1 -110011 1 -110110 1 -110111 1 -111001 1 -111011 1 -111101 1 -111111 1 -.names $abc$8517$new_n563_ $abc$8517$new_n519_ $abc$8517$new_n440_ $abc$8517$new_n977_ -000 1 -001 1 -011 1 -.names $abc$8517$new_n811_ $abc$8517$new_n440_ $abc$8517$new_n576_ $abc$8517$new_n437_ $abc$8517$new_n547_ $abc$8517$new_n579_ $abc$8517$new_n978_ -000000 1 -000010 1 -000100 1 -000101 1 -010000 1 -010001 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n345_ $abc$8517$new_n692_ $abc$8517$new_n718_ $abc$8517$new_n651_ $abc$8517$new_n719_ $abc$8517$new_n652_ $abc$8517$new_n979_ -100010 1 -100110 1 -101010 1 -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n983_ $abc$8517$new_n546_ $abc$8517$new_n981_ bus_address[30] -000 1 -001 1 -010 1 -011 1 -111 1 -.names $abc$8517$new_n982_ $abc$8517$new_n441_ $abc$8517$new_n438_ $abc$8517$new_n440_ $abc$8517$new_n437_ $abc$8517$new_n962_ $abc$8517$new_n981_ -000110 1 -000111 1 -001011 1 -001101 1 -001110 1 -001111 1 -010011 1 -010101 1 -010110 1 -010111 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -101000 1 -101001 1 -101010 1 -101100 1 -110000 1 -110001 1 -110010 1 -110100 1 -111000 1 -111001 1 -.names $abc$8517$new_n432_ $abc$8517$new_n431_ $abc$8517$new_n982_ -00 1 -11 1 -.names $abc$8517$new_n548_ $abc$8517$new_n984_ $abc$8517$new_n982_ $abc$8517$new_n987_ $abc$8517$new_n543_ $abc$8517$new_n985_ $abc$8517$new_n983_ -000100 1 -000101 1 -000110 1 -001100 1 -001101 1 -001110 1 -010100 1 -010101 1 -010110 1 -011100 1 -011101 1 -011110 1 -101100 1 -101101 1 -101110 1 -110100 1 -110101 1 -110110 1 -.names $abc$8517$new_n437_ $abc$8517$new_n440_ $abc$8517$new_n974_ $abc$8517$new_n442_ $abc$8517$new_n963_ $abc$8517$new_n984_ -00000 1 -00001 1 -00011 1 -01000 1 -01001 1 -01011 1 -01100 1 -01101 1 -01111 1 -11000 1 -11001 1 -11011 1 -.names $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n898_ $abc$8517$new_n943_ $abc$8517$new_n986_ $abc$8517$new_n985_ -00000 1 -00001 1 -00010 1 -00011 1 -01000 1 -01001 1 -01010 1 -01011 1 -10000 1 -10001 1 -10100 1 -10101 1 -11001 1 -11011 1 -11101 1 -11111 1 -.names $abc$8517$new_n356_ $abc$8517$new_n539_ $abc$8517$new_n538_ $abc$8517$new_n535_ $abc$8517$new_n537_ $abc$8517$new_n986_ -00001 1 -00010 1 -00011 1 -00101 1 -00110 1 -00111 1 -01001 1 -01010 1 -01011 1 -01101 1 -01110 1 -01111 1 -10100 1 -10101 1 -10110 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n988_ $abc$8517$new_n789_ $abc$8517$new_n819_ $abc$8517$new_n345_ $abc$8517$new_n795_ $abc$8517$new_n987_ -10000 1 -10001 1 -10011 1 -11000 1 -11001 1 -11011 1 -11100 1 -11101 1 -11111 1 -.names $abc$8517$new_n811_ $abc$8517$new_n432_ $abc$8517$new_n576_ $abc$8517$new_n431_ $abc$8517$new_n547_ $abc$8517$new_n579_ $abc$8517$new_n988_ -000000 1 -000010 1 -000100 1 -000101 1 -010000 1 -010001 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n546_ $abc$8517$new_n433_ $abc$8517$new_n990_ $abc$8517$new_n432_ $abc$8517$new_n431_ $abc$8517$new_n991_ bus_address[31] -000000 1 -000010 1 -000100 1 -000110 1 -001000 1 -001010 1 -001100 1 -001110 1 -010000 1 -010010 1 -010100 1 -010110 1 -011000 1 -011010 1 -011100 1 -011110 1 -100000 1 -100001 1 -100010 1 -100100 1 -100110 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111010 1 -111100 1 -111110 1 -.names $abc$8517$new_n982_ $abc$8517$new_n441_ $abc$8517$new_n438_ $abc$8517$new_n440_ $abc$8517$new_n437_ $abc$8517$new_n962_ $abc$8517$new_n990_ -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -001000 1 -001001 1 -001010 1 -001100 1 -010000 1 -010001 1 -010010 1 -010100 1 -011000 1 -011001 1 -.names $abc$8517$new_n992_ $abc$8517$new_n433_ $abc$8517$new_n431_ $abc$8517$new_n432_ $abc$8517$new_n984_ $abc$8517$new_n548_ $abc$8517$new_n991_ -100000 1 -100001 1 -100010 1 -100100 1 -100110 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -110000 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111010 1 -111100 1 -111110 1 -111111 1 -.names $abc$8517$new_n996_ $abc$8517$new_n997_ $abc$8517$new_n995_ $abc$8517$new_n993_ $abc$8517$new_n803_ $abc$8517$new_n819_ $abc$8517$new_n992_ -100000 1 -100010 1 -100011 1 -.names $abc$8517$new_n543_ $abc$8517$new_n368_ $abc$8517$new_n994_ $abc$8517$new_n910_ $abc$8517$new_n957_ $abc$8517$new_n362_ $abc$8517$new_n993_ -100000 1 -100001 1 -100010 1 -100011 1 -101000 1 -101001 1 -101010 1 -101011 1 -110000 1 -110001 1 -110011 1 -110100 1 -110101 1 -110111 1 -.names $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n519_ $abc$8517$new_n977_ $abc$8517$new_n434_ $abc$8517$new_n432_ $abc$8517$new_n994_ -100100 1 -100101 1 -100110 1 -100111 1 -101100 1 -101101 1 -101110 1 -101111 1 -110001 1 -110011 1 -110101 1 -110111 1 -111010 1 -111011 1 -111110 1 -111111 1 -.names $abc$8517$new_n310_ $abc$8517$new_n434_ $abc$8517$new_n576_ rs2_data[31] $abc$8517$new_n494_ $abc$8517$new_n547_ $abc$8517$new_n995_ -000011 1 -000111 1 -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001110 1 -001111 1 -010001 1 -010101 1 -011000 1 -011001 1 -011100 1 -011101 1 -100001 1 -100011 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110101 1 -110111 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n434_ $abc$8517$new_n579_ $abc$8517$new_n310_ $abc$8517$new_n568_ $abc$8517$new_n494_ rs2_data[31] $abc$8517$new_n996_ -000000 1 -000001 1 -000010 1 -000011 1 -001000 1 -001001 1 -001010 1 -001011 1 -010010 1 -010011 1 -011000 1 -011010 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n368_ $abc$8517$new_n690_ $abc$8517$new_n608_ $abc$8517$new_n549_ $abc$8517$new_n997_ -1111 1 -.names singlecycle_datapath.program_counter.value[0] reset $abc$8517$new_n1013_ $abc$8517$new_n1011_ $abc$8517$new_n999_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][0] -00000 1 -00001 1 -10001 1 -10011 1 -10100 1 -10101 1 -10111 1 -.names $abc$8517$new_n302_ $abc$8517$new_n1010_ $abc$8517$new_n1000_ bus_address[31] $abc$8517$new_n1009_ $abc$8517$new_n999_ -00000 1 -00010 1 -00100 1 -00101 1 -00110 1 -01000 1 -01001 1 -01010 1 -01011 1 -01100 1 -01110 1 -01111 1 -.names $abc$8517$new_n1001_ $abc$8517$new_n983_ bus_address[29] bus_address[28] $abc$8517$new_n981_ $abc$8517$new_n546_ $abc$8517$new_n1000_ -110000 1 -110001 1 -110010 1 -.names $abc$8517$new_n1396_ $abc$8517$new_n1002_ $abc$8517$new_n950_ bus_address[26] bus_address[25] $abc$8517$new_n1001_ -11000 1 -.names $abc$8517$new_n1003_ $abc$8517$new_n1007_ bus_address[21] bus_address[24] bus_address[23] bus_address[22] $abc$8517$new_n1002_ -110000 1 -.names $abc$8517$new_n1004_ bus_address[11] bus_address[14] bus_address[16] bus_address[20] $abc$8517$new_n1003_ -10000 1 -.names $abc$8517$new_n1005_ $abc$8517$new_n1006_ bus_address[9] bus_address[10] $abc$8517$new_n1004_ -1100 1 -.names $abc$8517$new_n1352_ $abc$8517$new_n1349_ bus_address[1] $abc$8517$new_n1344_ $abc$8517$new_n466_ $abc$8517$new_n493_ $abc$8517$new_n1005_ -100000 1 -100001 1 -100011 1 -100100 1 -100101 1 -100110 1 -100111 1 -.names bus_address[2] bus_address[8] bus_address[7] bus_address[5] bus_address[4] bus_address[3] $abc$8517$new_n1006_ -000000 1 -.names $abc$8517$new_n1008_ bus_address[12] bus_address[19] $abc$8517$new_n1007_ -100 1 -.names bus_address[6] bus_address[13] bus_address[15] bus_address[18] bus_address[17] $abc$8517$new_n1008_ -00000 1 -.names inst[6] inst[5] $abc$8517$new_n348_ inst[4] $abc$8517$new_n1009_ -1110 1 -.names inst[12] inst[13] inst[14] $abc$8517$new_n1010_ -100 1 -101 1 -111 1 -.names $abc$8517$new_n1012_ $abc$8517$new_n1010_ $abc$8517$new_n1000_ bus_address[31] $abc$8517$new_n1009_ $abc$8517$new_n1011_ -00000 1 -00010 1 -00100 1 -00101 1 -00110 1 -01000 1 -01001 1 -01010 1 -01011 1 -01100 1 -01110 1 -01111 1 -.names inst[3] $abc$8517$new_n302_ $abc$8517$new_n1012_ -11 1 -.names $abc$8517$new_n355_ $abc$8517$new_n354_ $abc$8517$new_n1013_ -00 1 -.names reset $abc$8517$new_n1015_ singlecycle_datapath.program_counter.value[1] $abc$8517$new_n999_ $abc$8517$new_n1017_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][1] -000010 1 -000110 1 -001010 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n1016_ bus_address[1] $abc$8517$new_n1015_ -11 1 -.names $abc$8517$new_n302_ inst[3] $abc$8517$new_n1016_ -10 1 -.names singlecycle_datapath.program_counter.value[1] singlecycle_datapath.program_counter.value[0] $abc$8517$new_n358_ $abc$8517$new_n357_ $abc$8517$new_n1013_ $abc$8517$new_n1017_ -00010 1 -00011 1 -00100 1 -00101 1 -00110 1 -00111 1 -01000 1 -01011 1 -01101 1 -01111 1 -10000 1 -10001 1 -11001 1 -11010 1 -11100 1 -11110 1 -.names reset $abc$8517$new_n1019_ singlecycle_datapath.program_counter.value[2] $abc$8517$new_n999_ $abc$8517$new_n1020_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][2] -000010 1 -000100 1 -000101 1 -000110 1 -000111 1 -001010 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n1016_ bus_address[2] $abc$8517$new_n1019_ -11 1 -.names singlecycle_datapath.program_counter.value[2] $abc$8517$new_n1022_ $abc$8517$new_n1021_ $abc$8517$new_n1020_ -001 1 -010 1 -100 1 -111 1 -.names inst[9] $abc$8517$new_n346_ inst[22] $abc$8517$new_n347_ $abc$8517$new_n1021_ -0000 1 -0001 1 -0011 1 -0100 1 -0101 1 -0111 1 -1100 1 -1101 1 -1111 1 -.names $abc$8517$new_n358_ $abc$8517$new_n357_ singlecycle_datapath.program_counter.value[1] $abc$8517$new_n354_ $abc$8517$new_n355_ singlecycle_datapath.program_counter.value[0] $abc$8517$new_n1022_ -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -000110 1 -000111 1 -001000 1 -001001 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010100 1 -010110 1 -100000 1 -100001 1 -100010 1 -100100 1 -100110 1 -110000 1 -110001 1 -110010 1 -110100 1 -110110 1 -.names reset $abc$8517$new_n1024_ $abc$8517$new_n1028_ $abc$8517$new_n999_ $abc$8517$new_n1025_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][3] -000010 1 -000110 1 -001010 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n1016_ bus_address[3] $abc$8517$new_n1024_ -11 1 -.names singlecycle_datapath.program_counter.value[3] $abc$8517$new_n1027_ $abc$8517$new_n1026_ $abc$8517$new_n1025_ -001 1 -010 1 -100 1 -111 1 -.names $abc$8517$new_n1022_ singlecycle_datapath.program_counter.value[2] $abc$8517$new_n1021_ $abc$8517$new_n1026_ -001 1 -100 1 -101 1 -111 1 -.names $abc$8517$new_n360_ $abc$8517$new_n347_ inst[23] $abc$8517$new_n1027_ -000 1 -010 1 -011 1 -.names singlecycle_datapath.program_counter.value[3] singlecycle_datapath.program_counter.value[2] $abc$8517$new_n1028_ -01 1 -10 1 -.names reset $abc$8517$new_n1030_ $abc$8517$new_n1033_ $abc$8517$new_n999_ $abc$8517$new_n1031_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][4] -000010 1 -000110 1 -001010 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n1016_ bus_address[4] $abc$8517$new_n1030_ -11 1 -.names singlecycle_datapath.program_counter.value[4] $abc$8517$new_n1032_ singlecycle_datapath.program_counter.value[3] $abc$8517$new_n1027_ $abc$8517$new_n1026_ $abc$8517$new_n1031_ -00001 1 -00010 1 -00011 1 -00111 1 -01000 1 -01100 1 -01101 1 -01110 1 -10000 1 -10100 1 -10101 1 -10110 1 -11001 1 -11010 1 -11011 1 -11111 1 -.names inst[11] $abc$8517$new_n346_ inst[24] $abc$8517$new_n347_ $abc$8517$new_n1032_ -0000 1 -0001 1 -0011 1 -0100 1 -0101 1 -0111 1 -1100 1 -1101 1 -1111 1 -.names singlecycle_datapath.program_counter.value[4] singlecycle_datapath.program_counter.value[3] singlecycle_datapath.program_counter.value[2] $abc$8517$new_n1033_ -011 1 -100 1 -101 1 -110 1 -.names reset $abc$8517$new_n1035_ $abc$8517$new_n1042_ $abc$8517$new_n999_ $abc$8517$new_n1036_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][5] -000010 1 -000110 1 -001010 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n1016_ bus_address[5] $abc$8517$new_n1035_ -11 1 -.names $abc$8517$new_n1041_ $abc$8517$new_n1037_ $abc$8517$new_n1036_ -00 1 -11 1 -.names $abc$8517$new_n1039_ $abc$8517$new_n1038_ $abc$8517$new_n1037_ -00 1 -.names singlecycle_datapath.program_counter.value[4] $abc$8517$new_n1032_ $abc$8517$new_n1038_ -10 1 -.names $abc$8517$new_n1040_ singlecycle_datapath.program_counter.value[3] singlecycle_datapath.program_counter.value[2] $abc$8517$new_n1022_ $abc$8517$new_n1021_ $abc$8517$new_n1027_ $abc$8517$new_n1039_ -100000 1 -101000 1 -101010 1 -101100 1 -110000 1 -110001 1 -110010 1 -110100 1 -110110 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -.names singlecycle_datapath.program_counter.value[4] $abc$8517$new_n347_ inst[24] inst[11] $abc$8517$new_n1040_ -0010 1 -0011 1 -0101 1 -0111 1 -1000 1 -1001 1 -1100 1 -1101 1 -1110 1 -.names singlecycle_datapath.program_counter.value[5] inst[25] $abc$8517$new_n324_ $abc$8517$new_n1041_ -010 1 -100 1 -101 1 -111 1 -.names singlecycle_datapath.program_counter.value[5] singlecycle_datapath.program_counter.value[4] singlecycle_datapath.program_counter.value[3] singlecycle_datapath.program_counter.value[2] $abc$8517$new_n1042_ -0111 1 -1000 1 -1001 1 -1010 1 -1011 1 -1100 1 -1101 1 -1110 1 -.names reset $abc$8517$new_n1044_ $abc$8517$new_n1048_ $abc$8517$new_n999_ $abc$8517$new_n1045_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][6] -000010 1 -000110 1 -001010 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n1016_ bus_address[6] $abc$8517$new_n1044_ -11 1 -.names $abc$8517$new_n1047_ $abc$8517$new_n1046_ $abc$8517$new_n1041_ $abc$8517$new_n1037_ $abc$8517$new_n1045_ -0010 1 -0100 1 -0101 1 -0110 1 -0111 1 -1000 1 -1001 1 -1011 1 -.names inst[25] singlecycle_datapath.program_counter.value[5] $abc$8517$new_n324_ $abc$8517$new_n1046_ -110 1 -.names singlecycle_datapath.program_counter.value[6] inst[26] $abc$8517$new_n324_ $abc$8517$new_n1047_ -010 1 -100 1 -101 1 -111 1 -.names singlecycle_datapath.program_counter.value[6] singlecycle_datapath.program_counter.value[5] singlecycle_datapath.program_counter.value[4] singlecycle_datapath.program_counter.value[3] singlecycle_datapath.program_counter.value[2] $abc$8517$new_n1048_ -01111 1 -10000 1 -10001 1 -10010 1 -10011 1 -10100 1 -10101 1 -10110 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -.names reset $abc$8517$new_n1050_ $abc$8517$new_n1054_ $abc$8517$new_n999_ $abc$8517$new_n1016_ bus_address[7] $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][7] -000011 1 -000111 1 -001011 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n1051_ $abc$8517$new_n1009_ $abc$8517$new_n1010_ $abc$8517$new_n1000_ bus_address[31] $abc$8517$new_n1012_ $abc$8517$new_n1050_ -100001 1 -100011 1 -100101 1 -100111 1 -101001 1 -101011 1 -101101 1 -101111 1 -110000 1 -110001 1 -110010 1 -110011 1 -110101 1 -110110 1 -110111 1 -111001 1 -111011 1 -111100 1 -111101 1 -111111 1 -.names singlecycle_datapath.program_counter.value[7] $abc$8517$new_n699_ $abc$8517$new_n1052_ $abc$8517$new_n1051_ -000 1 -011 1 -101 1 -110 1 -.names $abc$8517$new_n1047_ $abc$8517$new_n1046_ singlecycle_datapath.program_counter.value[6] $abc$8517$new_n1041_ $abc$8517$new_n1038_ $abc$8517$new_n1039_ $abc$8517$new_n1052_ -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -000110 1 -000111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -.names singlecycle_datapath.program_counter.value[7] singlecycle_datapath.program_counter.value[6] singlecycle_datapath.program_counter.value[5] singlecycle_datapath.program_counter.value[4] singlecycle_datapath.program_counter.value[3] singlecycle_datapath.program_counter.value[2] $abc$8517$new_n1054_ -011111 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -.names reset $abc$8517$new_n1056_ $abc$8517$new_n1060_ $abc$8517$new_n999_ $abc$8517$new_n1057_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][8] -000010 1 -000110 1 -001010 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n1016_ bus_address[8] $abc$8517$new_n1056_ -11 1 -.names singlecycle_datapath.program_counter.value[8] $abc$8517$new_n1059_ $abc$8517$new_n1058_ $abc$8517$new_n1057_ -000 1 -011 1 -101 1 -110 1 -.names singlecycle_datapath.program_counter.value[7] $abc$8517$new_n699_ $abc$8517$new_n1052_ $abc$8517$new_n1058_ -000 1 -001 1 -011 1 -101 1 -.names inst[28] $abc$8517$new_n324_ $abc$8517$new_n1059_ -10 1 -.names singlecycle_datapath.program_counter.value[8] $abc$8517$new_n1061_ $abc$8517$new_n1060_ -01 1 -10 1 -.names singlecycle_datapath.program_counter.value[7] singlecycle_datapath.program_counter.value[6] singlecycle_datapath.program_counter.value[5] singlecycle_datapath.program_counter.value[4] singlecycle_datapath.program_counter.value[3] singlecycle_datapath.program_counter.value[2] $abc$8517$new_n1061_ -111111 1 -.names reset $abc$8517$new_n1063_ $abc$8517$new_n1067_ $abc$8517$new_n999_ $abc$8517$new_n1064_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][9] -000010 1 -000110 1 -001010 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n1016_ bus_address[9] $abc$8517$new_n1063_ -11 1 -.names $abc$8517$new_n1066_ $abc$8517$new_n1065_ singlecycle_datapath.program_counter.value[8] $abc$8517$new_n1059_ $abc$8517$new_n1058_ $abc$8517$new_n1064_ -00000 1 -00001 1 -00011 1 -00101 1 -01010 1 -01100 1 -01110 1 -01111 1 -10010 1 -10100 1 -10110 1 -10111 1 -11010 1 -11100 1 -11110 1 -11111 1 -.names inst[29] singlecycle_datapath.program_counter.value[9] $abc$8517$new_n324_ $abc$8517$new_n1065_ -110 1 -.names singlecycle_datapath.program_counter.value[9] $abc$8517$new_n324_ inst[29] $abc$8517$new_n1066_ -000 1 -010 1 -011 1 -.names singlecycle_datapath.program_counter.value[9] singlecycle_datapath.program_counter.value[8] $abc$8517$new_n1061_ $abc$8517$new_n1067_ -011 1 -100 1 -101 1 -110 1 -.names reset $abc$8517$new_n1069_ $abc$8517$new_n1073_ $abc$8517$new_n999_ $abc$8517$new_n1070_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][10] -000010 1 -000110 1 -001010 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n1016_ bus_address[10] $abc$8517$new_n1069_ -11 1 -.names $abc$8517$new_n1072_ $abc$8517$new_n1066_ $abc$8517$new_n1071_ $abc$8517$new_n1070_ -000 1 -101 1 -110 1 -111 1 -.names $abc$8517$new_n1065_ singlecycle_datapath.program_counter.value[7] $abc$8517$new_n699_ singlecycle_datapath.program_counter.value[8] $abc$8517$new_n1059_ $abc$8517$new_n1052_ $abc$8517$new_n1071_ -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -001000 1 -001001 1 -001011 1 -001101 1 -010000 1 -010001 1 -010011 1 -010101 1 -011000 1 -011001 1 -.names singlecycle_datapath.program_counter.value[10] inst[30] $abc$8517$new_n324_ $abc$8517$new_n1072_ -010 1 -100 1 -101 1 -111 1 -.names singlecycle_datapath.program_counter.value[10] singlecycle_datapath.program_counter.value[9] singlecycle_datapath.program_counter.value[8] $abc$8517$new_n1061_ $abc$8517$new_n1073_ -0111 1 -1000 1 -1001 1 -1010 1 -1011 1 -1100 1 -1101 1 -1110 1 -.names reset $abc$8517$new_n1075_ $abc$8517$new_n1079_ $abc$8517$new_n999_ $abc$8517$new_n1016_ bus_address[11] $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][11] -000011 1 -000111 1 -001011 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n1076_ $abc$8517$new_n1009_ $abc$8517$new_n1010_ $abc$8517$new_n1000_ bus_address[31] $abc$8517$new_n1012_ $abc$8517$new_n1075_ -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010101 1 -010110 1 -010111 1 -011001 1 -011011 1 -011100 1 -011101 1 -011111 1 -.names singlecycle_datapath.program_counter.value[11] $abc$8517$new_n1077_ $abc$8517$new_n1078_ $abc$8517$new_n1072_ $abc$8517$new_n1066_ $abc$8517$new_n1071_ $abc$8517$new_n1076_ -000100 1 -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010101 1 -010110 1 -010111 1 -100000 1 -100001 1 -100010 1 -100011 1 -100101 1 -100110 1 -100111 1 -110100 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n390_ $abc$8517$new_n391_ $abc$8517$new_n389_ inst[31] $abc$8517$new_n1077_ -0000 1 -0010 1 -0011 1 -.names singlecycle_datapath.program_counter.value[10] inst[30] $abc$8517$new_n324_ $abc$8517$new_n1078_ -110 1 -.names singlecycle_datapath.program_counter.value[11] singlecycle_datapath.program_counter.value[10] singlecycle_datapath.program_counter.value[9] singlecycle_datapath.program_counter.value[8] $abc$8517$new_n1061_ $abc$8517$new_n1079_ -01111 1 -10000 1 -10001 1 -10010 1 -10011 1 -10100 1 -10101 1 -10110 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -.names reset $abc$8517$new_n1081_ $abc$8517$new_n1086_ $abc$8517$new_n999_ $abc$8517$new_n1082_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][12] -000010 1 -000110 1 -001010 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n1016_ bus_address[12] $abc$8517$new_n1081_ -11 1 -.names singlecycle_datapath.program_counter.value[12] $abc$8517$new_n1084_ $abc$8517$new_n1083_ $abc$8517$new_n1082_ -000 1 -011 1 -101 1 -110 1 -.names singlecycle_datapath.program_counter.value[11] $abc$8517$new_n1077_ $abc$8517$new_n1072_ $abc$8517$new_n1071_ $abc$8517$new_n1066_ $abc$8517$new_n1078_ $abc$8517$new_n1083_ -000001 1 -000011 1 -000101 1 -000111 1 -001000 1 -001001 1 -001011 1 -001101 1 -001111 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110001 1 -110011 1 -110101 1 -110111 1 -111000 1 -111001 1 -111011 1 -111101 1 -111111 1 -.names $abc$8517$new_n1085_ $abc$8517$new_n338_ inst[12] $abc$8517$new_n1084_ -000 1 -010 1 -011 1 -.names inst[31] $abc$8517$new_n312_ $abc$8517$new_n1085_ -10 1 -.names singlecycle_datapath.program_counter.value[12] singlecycle_datapath.program_counter.value[11] singlecycle_datapath.program_counter.value[10] singlecycle_datapath.program_counter.value[9] singlecycle_datapath.program_counter.value[8] $abc$8517$new_n1061_ $abc$8517$new_n1086_ -011111 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -.names reset $abc$8517$new_n1091_ $abc$8517$new_n1092_ $abc$8517$new_n999_ $abc$8517$new_n1088_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][13] -000010 1 -000110 1 -001010 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names singlecycle_datapath.program_counter.value[13] $abc$8517$new_n1090_ $abc$8517$new_n1089_ $abc$8517$new_n1088_ -001 1 -010 1 -100 1 -111 1 -.names $abc$8517$new_n1084_ singlecycle_datapath.program_counter.value[12] $abc$8517$new_n1083_ $abc$8517$new_n1089_ -000 1 -100 1 -101 1 -110 1 -.names $abc$8517$new_n383_ $abc$8517$new_n1085_ $abc$8517$new_n1090_ -00 1 -.names $abc$8517$new_n1016_ bus_address[13] $abc$8517$new_n1091_ -11 1 -.names singlecycle_datapath.program_counter.value[13] $abc$8517$new_n1093_ $abc$8517$new_n1092_ -01 1 -10 1 -.names singlecycle_datapath.program_counter.value[12] singlecycle_datapath.program_counter.value[11] singlecycle_datapath.program_counter.value[10] singlecycle_datapath.program_counter.value[9] singlecycle_datapath.program_counter.value[8] $abc$8517$new_n1061_ $abc$8517$new_n1093_ -111111 1 -.names reset $abc$8517$new_n1095_ $abc$8517$new_n1098_ $abc$8517$new_n999_ $abc$8517$new_n1096_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][14] -000010 1 -000110 1 -001010 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n1016_ bus_address[14] $abc$8517$new_n1095_ -11 1 -.names singlecycle_datapath.program_counter.value[14] $abc$8517$new_n1097_ singlecycle_datapath.program_counter.value[13] $abc$8517$new_n1090_ $abc$8517$new_n1089_ $abc$8517$new_n1096_ -00001 1 -00010 1 -00011 1 -00111 1 -01000 1 -01100 1 -01101 1 -01110 1 -10000 1 -10100 1 -10101 1 -10110 1 -11001 1 -11010 1 -11011 1 -11111 1 -.names $abc$8517$new_n1085_ $abc$8517$new_n338_ inst[14] $abc$8517$new_n1097_ -000 1 -010 1 -011 1 -.names singlecycle_datapath.program_counter.value[14] singlecycle_datapath.program_counter.value[13] $abc$8517$new_n1093_ $abc$8517$new_n1098_ -011 1 -100 1 -101 1 -110 1 -.names reset $abc$8517$new_n1106_ $abc$8517$new_n1107_ $abc$8517$new_n999_ $abc$8517$new_n1011_ $abc$8517$new_n1100_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][15] -000000 1 -000100 1 -001000 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names singlecycle_datapath.program_counter.value[15] $abc$8517$new_n1105_ $abc$8517$new_n1101_ $abc$8517$new_n1100_ -000 1 -011 1 -101 1 -110 1 -.names $abc$8517$new_n1104_ $abc$8517$new_n1102_ $abc$8517$new_n1101_ -00 1 -.names $abc$8517$new_n1103_ singlecycle_datapath.program_counter.value[12] singlecycle_datapath.program_counter.value[13] $abc$8517$new_n1084_ $abc$8517$new_n1090_ $abc$8517$new_n1083_ $abc$8517$new_n1102_ -100001 1 -101000 1 -101001 1 -101011 1 -101100 1 -101101 1 -110000 1 -110001 1 -110101 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111111 1 -.names singlecycle_datapath.program_counter.value[14] $abc$8517$new_n1097_ $abc$8517$new_n1103_ -00 1 -11 1 -.names singlecycle_datapath.program_counter.value[14] $abc$8517$new_n1097_ $abc$8517$new_n1104_ -10 1 -.names $abc$8517$new_n378_ $abc$8517$new_n1085_ $abc$8517$new_n1105_ -00 1 -.names $abc$8517$new_n1016_ bus_address[15] $abc$8517$new_n1106_ -11 1 -.names singlecycle_datapath.program_counter.value[15] singlecycle_datapath.program_counter.value[14] singlecycle_datapath.program_counter.value[13] $abc$8517$new_n1093_ $abc$8517$new_n1107_ -0111 1 -1000 1 -1001 1 -1010 1 -1011 1 -1100 1 -1101 1 -1110 1 -.names reset $abc$8517$new_n1109_ $abc$8517$new_n1112_ $abc$8517$new_n999_ $abc$8517$new_n1110_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][16] -000010 1 -000110 1 -001010 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n1016_ bus_address[16] $abc$8517$new_n1109_ -11 1 -.names singlecycle_datapath.program_counter.value[16] $abc$8517$new_n1111_ singlecycle_datapath.program_counter.value[15] $abc$8517$new_n1105_ $abc$8517$new_n1101_ $abc$8517$new_n1110_ -00001 1 -00010 1 -00011 1 -00111 1 -01000 1 -01100 1 -01101 1 -01110 1 -10000 1 -10100 1 -10101 1 -10110 1 -11001 1 -11010 1 -11011 1 -11111 1 -.names $abc$8517$new_n1085_ $abc$8517$new_n338_ inst[16] $abc$8517$new_n1111_ -000 1 -010 1 -011 1 -.names singlecycle_datapath.program_counter.value[16] singlecycle_datapath.program_counter.value[15] singlecycle_datapath.program_counter.value[14] singlecycle_datapath.program_counter.value[13] $abc$8517$new_n1093_ $abc$8517$new_n1112_ -01111 1 -10000 1 -10001 1 -10010 1 -10011 1 -10100 1 -10101 1 -10110 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -.names reset $abc$8517$new_n1114_ $abc$8517$new_n1118_ $abc$8517$new_n999_ bus_address[17] $abc$8517$new_n1016_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][17] -000011 1 -000111 1 -001011 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n1115_ $abc$8517$new_n1009_ $abc$8517$new_n1010_ $abc$8517$new_n1000_ bus_address[31] $abc$8517$new_n1012_ $abc$8517$new_n1114_ -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010101 1 -010110 1 -010111 1 -011001 1 -011011 1 -011100 1 -011101 1 -011111 1 -.names singlecycle_datapath.program_counter.value[17] $abc$8517$new_n1117_ $abc$8517$new_n1116_ $abc$8517$new_n1115_ -000 1 -011 1 -101 1 -110 1 -.names $abc$8517$new_n1104_ $abc$8517$new_n1102_ singlecycle_datapath.program_counter.value[16] singlecycle_datapath.program_counter.value[15] $abc$8517$new_n1105_ $abc$8517$new_n1111_ $abc$8517$new_n1116_ -000000 1 -000001 1 -000010 1 -000011 1 -000101 1 -000110 1 -000111 1 -001001 1 -001011 1 -001111 1 -010001 1 -010010 1 -010011 1 -010101 1 -010111 1 -011011 1 -100001 1 -100010 1 -100011 1 -100101 1 -100111 1 -101011 1 -110001 1 -110010 1 -110011 1 -110101 1 -110111 1 -111011 1 -.names $abc$8517$new_n320_ $abc$8517$new_n1085_ $abc$8517$new_n1117_ -00 1 -.names singlecycle_datapath.program_counter.value[17] singlecycle_datapath.program_counter.value[16] singlecycle_datapath.program_counter.value[15] singlecycle_datapath.program_counter.value[14] singlecycle_datapath.program_counter.value[13] $abc$8517$new_n1093_ $abc$8517$new_n1118_ -011111 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -.names reset $abc$8517$new_n1123_ $abc$8517$new_n1124_ $abc$8517$new_n999_ $abc$8517$new_n1120_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][18] -000010 1 -000110 1 -001010 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names singlecycle_datapath.program_counter.value[18] $abc$8517$new_n1122_ $abc$8517$new_n1121_ $abc$8517$new_n1120_ -000 1 -011 1 -101 1 -110 1 -.names singlecycle_datapath.program_counter.value[17] $abc$8517$new_n1117_ $abc$8517$new_n1116_ $abc$8517$new_n1121_ -000 1 -100 1 -101 1 -110 1 -.names $abc$8517$new_n317_ $abc$8517$new_n1085_ $abc$8517$new_n1122_ -00 1 -.names $abc$8517$new_n1016_ bus_address[18] $abc$8517$new_n1123_ -11 1 -.names singlecycle_datapath.program_counter.value[18] $abc$8517$new_n1125_ $abc$8517$new_n1124_ -01 1 -10 1 -.names singlecycle_datapath.program_counter.value[17] singlecycle_datapath.program_counter.value[16] singlecycle_datapath.program_counter.value[15] singlecycle_datapath.program_counter.value[14] singlecycle_datapath.program_counter.value[13] $abc$8517$new_n1093_ $abc$8517$new_n1125_ -111111 1 -.names reset $abc$8517$new_n1129_ $abc$8517$new_n1130_ $abc$8517$new_n999_ $abc$8517$new_n1011_ $abc$8517$new_n1127_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][19] -000000 1 -000100 1 -001000 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names singlecycle_datapath.program_counter.value[19] $abc$8517$new_n1128_ singlecycle_datapath.program_counter.value[18] $abc$8517$new_n1122_ $abc$8517$new_n1121_ $abc$8517$new_n1127_ -00001 1 -00100 1 -00101 1 -00111 1 -01000 1 -01010 1 -01011 1 -01110 1 -10000 1 -10010 1 -10011 1 -10110 1 -11001 1 -11100 1 -11101 1 -11111 1 -.names $abc$8517$new_n314_ $abc$8517$new_n1085_ $abc$8517$new_n1128_ -00 1 -.names $abc$8517$new_n1016_ bus_address[19] $abc$8517$new_n1129_ -11 1 -.names singlecycle_datapath.program_counter.value[19] singlecycle_datapath.program_counter.value[18] $abc$8517$new_n1125_ $abc$8517$new_n1130_ -011 1 -100 1 -101 1 -110 1 -.names reset $abc$8517$new_n1132_ $abc$8517$new_n1138_ $abc$8517$new_n999_ $abc$8517$new_n1133_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][20] -000010 1 -000110 1 -001010 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n1016_ bus_address[20] $abc$8517$new_n1132_ -11 1 -.names singlecycle_datapath.program_counter.value[20] $abc$8517$new_n1137_ $abc$8517$new_n1136_ $abc$8517$new_n1134_ $abc$8517$new_n1133_ -0001 1 -0010 1 -0011 1 -0100 1 -1000 1 -1101 1 -1110 1 -1111 1 -.names $abc$8517$new_n1135_ $abc$8517$new_n1117_ singlecycle_datapath.program_counter.value[17] singlecycle_datapath.program_counter.value[18] $abc$8517$new_n1122_ $abc$8517$new_n1116_ $abc$8517$new_n1134_ -000001 1 -000010 1 -000011 1 -000111 1 -001010 1 -001011 1 -010000 1 -010001 1 -010010 1 -010011 1 -010110 1 -010111 1 -011001 1 -011010 1 -011011 1 -011111 1 -.names singlecycle_datapath.program_counter.value[19] $abc$8517$new_n1128_ $abc$8517$new_n1135_ -10 1 -.names $abc$8517$new_n1128_ singlecycle_datapath.program_counter.value[19] $abc$8517$new_n1136_ -10 1 -.names $abc$8517$new_n334_ $abc$8517$new_n324_ inst[31] $abc$8517$new_n1137_ -000 1 -010 1 -011 1 -.names singlecycle_datapath.program_counter.value[20] singlecycle_datapath.program_counter.value[19] singlecycle_datapath.program_counter.value[18] $abc$8517$new_n1125_ $abc$8517$new_n1138_ -0111 1 -1000 1 -1001 1 -1010 1 -1011 1 -1100 1 -1101 1 -1110 1 -.names reset $abc$8517$new_n1140_ $abc$8517$new_n1143_ $abc$8517$new_n999_ $abc$8517$new_n1141_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][21] -000000 1 -000100 1 -001000 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n1016_ bus_address[21] $abc$8517$new_n1140_ -11 1 -.names singlecycle_datapath.program_counter.value[21] $abc$8517$new_n1142_ singlecycle_datapath.program_counter.value[20] $abc$8517$new_n1137_ $abc$8517$new_n1136_ $abc$8517$new_n1134_ $abc$8517$new_n1141_ -000000 1 -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011101 1 -011110 1 -011111 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -100111 1 -101101 1 -101110 1 -101111 1 -110000 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -.names $abc$8517$new_n331_ $abc$8517$new_n324_ inst[31] $abc$8517$new_n1142_ -000 1 -010 1 -011 1 -.names singlecycle_datapath.program_counter.value[21] singlecycle_datapath.program_counter.value[20] singlecycle_datapath.program_counter.value[19] singlecycle_datapath.program_counter.value[18] $abc$8517$new_n1125_ $abc$8517$new_n1143_ -01111 1 -10000 1 -10001 1 -10010 1 -10011 1 -10100 1 -10101 1 -10110 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -.names $abc$8517$new_n999_ singlecycle_datapath.program_counter.value[22] $abc$8517$new_n1148_ $abc$8517$new_n1145_ $abc$8517$new_n1146_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][22] -000000 1 -000001 1 -000010 1 -000011 1 -000110 1 -001000 1 -001001 1 -001010 1 -001011 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010110 1 -011000 1 -011001 1 -011010 1 -011011 1 -011110 1 -100000 1 -100001 1 -100010 1 -100011 1 -100110 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111110 1 -.names reset bus_address[22] $abc$8517$new_n1016_ $abc$8517$new_n1145_ -000 1 -001 1 -010 1 -.names singlecycle_datapath.program_counter.value[22] $abc$8517$new_n414_ $abc$8517$new_n1147_ $abc$8517$new_n1146_ -000 1 -011 1 -101 1 -110 1 -.names singlecycle_datapath.program_counter.value[21] singlecycle_datapath.program_counter.value[20] $abc$8517$new_n1136_ $abc$8517$new_n1134_ $abc$8517$new_n1137_ $abc$8517$new_n1142_ $abc$8517$new_n1147_ -000000 1 -010000 1 -010010 1 -010100 1 -011000 1 -011100 1 -100000 1 -100001 1 -100010 1 -100100 1 -100110 1 -101000 1 -101010 1 -101100 1 -101110 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -111000 1 -111001 1 -111010 1 -111100 1 -111101 1 -111110 1 -.names singlecycle_datapath.program_counter.value[21] singlecycle_datapath.program_counter.value[20] singlecycle_datapath.program_counter.value[19] singlecycle_datapath.program_counter.value[18] $abc$8517$new_n1125_ $abc$8517$new_n1148_ -11111 1 -.names reset $abc$8517$new_n1152_ $abc$8517$new_n1153_ $abc$8517$new_n999_ $abc$8517$new_n1011_ $abc$8517$new_n1150_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][23] -000000 1 -000100 1 -001000 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names singlecycle_datapath.program_counter.value[23] $abc$8517$new_n427_ $abc$8517$new_n1151_ $abc$8517$new_n1150_ -000 1 -011 1 -101 1 -110 1 -.names $abc$8517$new_n414_ singlecycle_datapath.program_counter.value[22] $abc$8517$new_n1147_ $abc$8517$new_n1151_ -000 1 -100 1 -101 1 -110 1 -.names $abc$8517$new_n1016_ bus_address[23] $abc$8517$new_n1152_ -11 1 -.names singlecycle_datapath.program_counter.value[23] $abc$8517$new_n1154_ $abc$8517$new_n1153_ -01 1 -10 1 -.names singlecycle_datapath.program_counter.value[22] $abc$8517$new_n1148_ $abc$8517$new_n1154_ -11 1 -.names reset $abc$8517$new_n1158_ $abc$8517$new_n1159_ $abc$8517$new_n999_ $abc$8517$new_n1156_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][24] -000010 1 -000110 1 -001010 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names singlecycle_datapath.program_counter.value[24] $abc$8517$new_n1157_ singlecycle_datapath.program_counter.value[23] $abc$8517$new_n427_ $abc$8517$new_n1151_ $abc$8517$new_n1156_ -00001 1 -00010 1 -00011 1 -00111 1 -01000 1 -01100 1 -01101 1 -01110 1 -10000 1 -10100 1 -10101 1 -10110 1 -11001 1 -11010 1 -11011 1 -11111 1 -.names $abc$8517$new_n458_ $abc$8517$new_n324_ inst[31] $abc$8517$new_n1157_ -000 1 -010 1 -011 1 -.names $abc$8517$new_n1016_ bus_address[24] $abc$8517$new_n1158_ -11 1 -.names singlecycle_datapath.program_counter.value[24] $abc$8517$new_n1160_ $abc$8517$new_n1159_ -01 1 -10 1 -.names singlecycle_datapath.program_counter.value[23] $abc$8517$new_n1154_ $abc$8517$new_n1160_ -11 1 -.names reset $abc$8517$new_n1168_ $abc$8517$new_n1169_ $abc$8517$new_n999_ $abc$8517$new_n1162_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][25] -000010 1 -000110 1 -001010 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names singlecycle_datapath.program_counter.value[25] $abc$8517$new_n1167_ $abc$8517$new_n1163_ $abc$8517$new_n1162_ -001 1 -010 1 -100 1 -111 1 -.names $abc$8517$new_n1166_ $abc$8517$new_n1164_ $abc$8517$new_n1163_ -00 1 -.names $abc$8517$new_n1165_ singlecycle_datapath.program_counter.value[22] singlecycle_datapath.program_counter.value[23] $abc$8517$new_n414_ $abc$8517$new_n427_ $abc$8517$new_n1147_ $abc$8517$new_n1164_ -100001 1 -101000 1 -101001 1 -101011 1 -101100 1 -101101 1 -110000 1 -110001 1 -110101 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111111 1 -.names singlecycle_datapath.program_counter.value[24] $abc$8517$new_n1157_ $abc$8517$new_n1165_ -00 1 -11 1 -.names singlecycle_datapath.program_counter.value[24] $abc$8517$new_n1157_ $abc$8517$new_n1166_ -10 1 -.names $abc$8517$new_n456_ $abc$8517$new_n324_ inst[31] $abc$8517$new_n1167_ -000 1 -010 1 -011 1 -.names $abc$8517$new_n1016_ bus_address[25] $abc$8517$new_n1168_ -11 1 -.names singlecycle_datapath.program_counter.value[25] singlecycle_datapath.program_counter.value[24] $abc$8517$new_n1160_ $abc$8517$new_n1169_ -011 1 -100 1 -101 1 -110 1 -.names reset $abc$8517$new_n1173_ $abc$8517$new_n1174_ $abc$8517$new_n999_ $abc$8517$new_n1171_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][26] -000010 1 -000110 1 -001010 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names singlecycle_datapath.program_counter.value[26] $abc$8517$new_n1172_ singlecycle_datapath.program_counter.value[25] $abc$8517$new_n1167_ $abc$8517$new_n1163_ $abc$8517$new_n1171_ -00001 1 -00010 1 -00011 1 -00111 1 -01000 1 -01100 1 -01101 1 -01110 1 -10000 1 -10100 1 -10101 1 -10110 1 -11001 1 -11010 1 -11011 1 -11111 1 -.names $abc$8517$new_n448_ $abc$8517$new_n324_ inst[31] $abc$8517$new_n1172_ -000 1 -010 1 -011 1 -.names $abc$8517$new_n1016_ bus_address[26] $abc$8517$new_n1173_ -11 1 -.names singlecycle_datapath.program_counter.value[26] singlecycle_datapath.program_counter.value[25] singlecycle_datapath.program_counter.value[24] $abc$8517$new_n1160_ $abc$8517$new_n1174_ -0111 1 -1000 1 -1001 1 -1010 1 -1011 1 -1100 1 -1101 1 -1110 1 -.names reset $abc$8517$new_n1176_ $abc$8517$new_n1180_ $abc$8517$new_n999_ $abc$8517$new_n1177_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][27] -000000 1 -000100 1 -001000 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n1016_ bus_address[27] $abc$8517$new_n1176_ -11 1 -.names singlecycle_datapath.program_counter.value[27] $abc$8517$new_n1179_ $abc$8517$new_n1178_ $abc$8517$new_n1177_ -000 1 -011 1 -101 1 -110 1 -.names $abc$8517$new_n1166_ $abc$8517$new_n1164_ singlecycle_datapath.program_counter.value[26] singlecycle_datapath.program_counter.value[25] $abc$8517$new_n1167_ $abc$8517$new_n1172_ $abc$8517$new_n1178_ -000000 1 -000001 1 -000010 1 -000011 1 -000101 1 -000110 1 -000111 1 -001001 1 -001011 1 -001111 1 -010001 1 -010010 1 -010011 1 -010101 1 -010111 1 -011011 1 -100001 1 -100010 1 -100011 1 -100101 1 -100111 1 -101011 1 -110001 1 -110010 1 -110011 1 -110101 1 -110111 1 -111011 1 -.names $abc$8517$new_n446_ $abc$8517$new_n324_ inst[31] $abc$8517$new_n1179_ -000 1 -010 1 -011 1 -.names singlecycle_datapath.program_counter.value[27] singlecycle_datapath.program_counter.value[26] singlecycle_datapath.program_counter.value[25] singlecycle_datapath.program_counter.value[24] $abc$8517$new_n1160_ $abc$8517$new_n1180_ -01111 1 -10000 1 -10001 1 -10010 1 -10011 1 -10100 1 -10101 1 -10110 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -.names reset $abc$8517$new_n1182_ $abc$8517$new_n1185_ $abc$8517$new_n999_ bus_address[28] $abc$8517$new_n1016_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][28] -000011 1 -000111 1 -001011 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n1183_ $abc$8517$new_n1009_ $abc$8517$new_n1010_ $abc$8517$new_n1000_ bus_address[31] $abc$8517$new_n1012_ $abc$8517$new_n1182_ -100001 1 -100011 1 -100101 1 -100111 1 -101001 1 -101011 1 -101101 1 -101111 1 -110000 1 -110001 1 -110010 1 -110011 1 -110101 1 -110110 1 -110111 1 -111001 1 -111011 1 -111100 1 -111101 1 -111111 1 -.names singlecycle_datapath.program_counter.value[28] $abc$8517$new_n1184_ singlecycle_datapath.program_counter.value[27] $abc$8517$new_n1179_ $abc$8517$new_n1178_ $abc$8517$new_n1183_ -00001 1 -00010 1 -00011 1 -00111 1 -01000 1 -01100 1 -01101 1 -01110 1 -10000 1 -10100 1 -10101 1 -10110 1 -11001 1 -11010 1 -11011 1 -11111 1 -.names $abc$8517$new_n439_ $abc$8517$new_n324_ inst[31] $abc$8517$new_n1184_ -000 1 -010 1 -011 1 -.names singlecycle_datapath.program_counter.value[28] singlecycle_datapath.program_counter.value[27] singlecycle_datapath.program_counter.value[26] singlecycle_datapath.program_counter.value[25] singlecycle_datapath.program_counter.value[24] $abc$8517$new_n1160_ $abc$8517$new_n1185_ -011111 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -.names reset $abc$8517$new_n1190_ $abc$8517$new_n1191_ $abc$8517$new_n999_ $abc$8517$new_n1011_ $abc$8517$new_n1187_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][29] -000000 1 -000100 1 -001000 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names singlecycle_datapath.program_counter.value[29] $abc$8517$new_n1189_ $abc$8517$new_n1188_ $abc$8517$new_n1187_ -000 1 -011 1 -101 1 -110 1 -.names $abc$8517$new_n1179_ singlecycle_datapath.program_counter.value[27] singlecycle_datapath.program_counter.value[28] $abc$8517$new_n1184_ $abc$8517$new_n1178_ $abc$8517$new_n1188_ -00001 1 -00010 1 -00011 1 -00111 1 -01010 1 -01011 1 -10000 1 -10001 1 -10010 1 -10011 1 -10110 1 -10111 1 -11001 1 -11010 1 -11011 1 -11111 1 -.names inst[31] $abc$8517$new_n324_ $abc$8517$new_n300_ inst[29] $abc$8517$new_n1189_ -0000 1 -0001 1 -0010 1 -0100 1 -0101 1 -0110 1 -1100 1 -1101 1 -1110 1 -.names $abc$8517$new_n1016_ bus_address[29] $abc$8517$new_n1190_ -11 1 -.names singlecycle_datapath.program_counter.value[29] $abc$8517$new_n1192_ $abc$8517$new_n1191_ -01 1 -10 1 -.names singlecycle_datapath.program_counter.value[28] singlecycle_datapath.program_counter.value[27] singlecycle_datapath.program_counter.value[26] singlecycle_datapath.program_counter.value[25] singlecycle_datapath.program_counter.value[24] $abc$8517$new_n1160_ $abc$8517$new_n1192_ -111111 1 -.names reset $abc$8517$new_n1194_ $abc$8517$new_n1197_ $abc$8517$new_n999_ $abc$8517$new_n1195_ $abc$8517$new_n1011_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][30] -000010 1 -000110 1 -001010 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n1016_ bus_address[30] $abc$8517$new_n1194_ -11 1 -.names singlecycle_datapath.program_counter.value[30] $abc$8517$new_n1196_ singlecycle_datapath.program_counter.value[29] $abc$8517$new_n1189_ $abc$8517$new_n1188_ $abc$8517$new_n1195_ -00001 1 -00010 1 -00011 1 -00111 1 -01000 1 -01100 1 -01101 1 -01110 1 -10000 1 -10100 1 -10101 1 -10110 1 -11001 1 -11010 1 -11011 1 -11111 1 -.names inst[31] $abc$8517$new_n324_ $abc$8517$new_n300_ inst[30] $abc$8517$new_n1196_ -0000 1 -0001 1 -0010 1 -0100 1 -0101 1 -0110 1 -1100 1 -1101 1 -1110 1 -.names singlecycle_datapath.program_counter.value[30] singlecycle_datapath.program_counter.value[29] $abc$8517$new_n1192_ $abc$8517$new_n1197_ -011 1 -100 1 -101 1 -110 1 -.names reset $abc$8517$new_n999_ singlecycle_datapath.program_counter.value[31] $abc$8517$new_n1203_ $abc$8517$new_n1199_ $abc$8517$new_n1202_ $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][31] -000001 1 -000010 1 -000011 1 -000101 1 -000110 1 -000111 1 -001001 1 -001010 1 -001011 1 -001101 1 -001110 1 -001111 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n1200_ $abc$8517$new_n1009_ $abc$8517$new_n1010_ $abc$8517$new_n1000_ bus_address[31] $abc$8517$new_n1012_ $abc$8517$new_n1199_ -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010101 1 -010110 1 -010111 1 -011001 1 -011011 1 -011100 1 -011101 1 -011111 1 -.names $abc$8517$new_n1201_ $abc$8517$new_n1189_ singlecycle_datapath.program_counter.value[29] singlecycle_datapath.program_counter.value[30] $abc$8517$new_n1196_ $abc$8517$new_n1188_ $abc$8517$new_n1200_ -000001 1 -000010 1 -000011 1 -000111 1 -001010 1 -001011 1 -010000 1 -010001 1 -010010 1 -010011 1 -010110 1 -010111 1 -011001 1 -011010 1 -011011 1 -011111 1 -100000 1 -100100 1 -100101 1 -100110 1 -101000 1 -101001 1 -101100 1 -101101 1 -101110 1 -101111 1 -110100 1 -110101 1 -111000 1 -111100 1 -111101 1 -111110 1 -.names singlecycle_datapath.program_counter.value[31] $abc$8517$new_n494_ $abc$8517$new_n1201_ -00 1 -11 1 -.names $abc$8517$new_n1016_ bus_address[31] $abc$8517$new_n1202_ -11 1 -.names singlecycle_datapath.program_counter.value[30] singlecycle_datapath.program_counter.value[29] $abc$8517$new_n1192_ $abc$8517$new_n1203_ -111 1 -.names $abc$8517$new_n1005_ inst[13] inst[12] bus_byte_enable[0] -100 1 -101 1 -110 1 -.names inst[12] bus_address[1] inst[13] bus_address[0] bus_byte_enable[1] -0001 1 -0010 1 -0011 1 -1000 1 -1001 1 -.names inst[13] inst[12] bus_address[0] bus_address[1] bus_byte_enable[2] -0001 1 -0101 1 -0110 1 -1000 1 -1001 1 -1010 1 -.names inst[12] bus_address[1] inst[13] bus_address[0] bus_byte_enable[3] -0010 1 -0011 1 -0101 1 -0110 1 -0111 1 -1100 1 -1101 1 -.names $abc$8517$new_n299_ $abc$8517$new_n1209_ bus_address[0] $abc$8517$new_n1210_ singlecycle_datapath.program_counter.value[0] $abc$8517$new_n302_ rd_data[0] -000011 1 -000111 1 -001000 1 -001001 1 -001010 1 -001011 1 -001111 1 -010011 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011111 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110011 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111111 1 -.names bus_address[1] bus_address[0] bus_read_data[24] bus_read_data[8] bus_read_data[16] bus_read_data[0] $abc$8517$new_n1209_ -000000 1 -000010 1 -000100 1 -000110 1 -001000 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -011000 1 -011001 1 -011010 1 -011011 1 -100000 1 -100001 1 -100100 1 -100101 1 -101000 1 -101001 1 -101100 1 -101101 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -.names $abc$8517$new_n301_ $abc$8517$new_n300_ inst[5] $abc$8517$new_n1210_ -000 1 -001 1 -011 1 -.names $abc$8517$new_n299_ $abc$8517$new_n1212_ bus_address[1] $abc$8517$new_n1210_ singlecycle_datapath.program_counter.value[1] $abc$8517$new_n302_ rd_data[1] -000011 1 -000111 1 -001000 1 -001001 1 -001010 1 -001011 1 -001111 1 -010011 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011111 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110011 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111111 1 -.names bus_address[1] bus_address[0] bus_read_data[25] bus_read_data[9] bus_read_data[17] bus_read_data[1] $abc$8517$new_n1212_ -000000 1 -000010 1 -000100 1 -000110 1 -001000 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -011000 1 -011001 1 -011010 1 -011011 1 -100000 1 -100001 1 -100100 1 -100101 1 -101000 1 -101001 1 -101100 1 -101101 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -.names $abc$8517$new_n299_ $abc$8517$new_n1214_ bus_address[2] $abc$8517$new_n1210_ $abc$8517$new_n302_ singlecycle_datapath.program_counter.value[2] rd_data[2] -000010 1 -000110 1 -001000 1 -001001 1 -001010 1 -001011 1 -001110 1 -010010 1 -010110 1 -011000 1 -011001 1 -011010 1 -011011 1 -011110 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110010 1 -110110 1 -111000 1 -111001 1 -111010 1 -111011 1 -111110 1 -.names bus_address[1] bus_address[0] bus_read_data[26] bus_read_data[10] bus_read_data[18] bus_read_data[2] $abc$8517$new_n1214_ -000000 1 -000010 1 -000100 1 -000110 1 -001000 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -011000 1 -011001 1 -011010 1 -011011 1 -100000 1 -100001 1 -100100 1 -100101 1 -101000 1 -101001 1 -101100 1 -101101 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -.names $abc$8517$new_n299_ $abc$8517$new_n1216_ bus_address[3] $abc$8517$new_n1210_ $abc$8517$new_n1028_ $abc$8517$new_n302_ rd_data[3] -000011 1 -000111 1 -001000 1 -001001 1 -001010 1 -001011 1 -001111 1 -010011 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011111 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110011 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111111 1 -.names bus_address[1] bus_address[0] bus_read_data[27] bus_read_data[11] bus_read_data[19] bus_read_data[3] $abc$8517$new_n1216_ -000000 1 -000010 1 -000100 1 -000110 1 -001000 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -011000 1 -011001 1 -011010 1 -011011 1 -100000 1 -100001 1 -100100 1 -100101 1 -101000 1 -101001 1 -101100 1 -101101 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -.names $abc$8517$new_n299_ $abc$8517$new_n1218_ bus_address[4] $abc$8517$new_n1210_ $abc$8517$new_n1033_ $abc$8517$new_n302_ rd_data[4] -000011 1 -000111 1 -001000 1 -001001 1 -001010 1 -001011 1 -001111 1 -010011 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011111 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110011 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111111 1 -.names bus_address[1] bus_address[0] bus_read_data[28] bus_read_data[12] bus_read_data[20] bus_read_data[4] $abc$8517$new_n1218_ -000000 1 -000010 1 -000100 1 -000110 1 -001000 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -011000 1 -011001 1 -011010 1 -011011 1 -100000 1 -100001 1 -100100 1 -100101 1 -101000 1 -101001 1 -101100 1 -101101 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -.names $abc$8517$new_n299_ $abc$8517$new_n1220_ bus_address[5] $abc$8517$new_n1210_ $abc$8517$new_n1042_ $abc$8517$new_n302_ rd_data[5] -000011 1 -000111 1 -001000 1 -001001 1 -001010 1 -001011 1 -001111 1 -010011 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011111 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110011 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111111 1 -.names bus_address[1] bus_address[0] bus_read_data[29] bus_read_data[13] bus_read_data[21] bus_read_data[5] $abc$8517$new_n1220_ -000000 1 -000010 1 -000100 1 -000110 1 -001000 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -011000 1 -011001 1 -011010 1 -011011 1 -100000 1 -100001 1 -100100 1 -100101 1 -101000 1 -101001 1 -101100 1 -101101 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -.names $abc$8517$new_n299_ $abc$8517$new_n1222_ bus_address[6] $abc$8517$new_n1210_ $abc$8517$new_n1048_ $abc$8517$new_n302_ rd_data[6] -000011 1 -000111 1 -001000 1 -001001 1 -001010 1 -001011 1 -001111 1 -010011 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011111 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110011 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111111 1 -.names bus_address[1] bus_address[0] bus_read_data[30] bus_read_data[14] bus_read_data[22] bus_read_data[6] $abc$8517$new_n1222_ -000000 1 -000010 1 -000100 1 -000110 1 -001000 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -011000 1 -011001 1 -011010 1 -011011 1 -100000 1 -100001 1 -100100 1 -100101 1 -101000 1 -101001 1 -101100 1 -101101 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -.names $abc$8517$new_n299_ $abc$8517$new_n1224_ bus_address[7] $abc$8517$new_n1210_ $abc$8517$new_n1054_ $abc$8517$new_n302_ rd_data[7] -000011 1 -000111 1 -001000 1 -001001 1 -001010 1 -001011 1 -001111 1 -010011 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011111 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110011 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111111 1 -.names bus_address[1] bus_address[0] bus_read_data[31] bus_read_data[15] bus_read_data[23] bus_read_data[7] $abc$8517$new_n1224_ -000000 1 -000010 1 -000100 1 -000110 1 -001000 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -011000 1 -011001 1 -011010 1 -011011 1 -100000 1 -100001 1 -100100 1 -100101 1 -101000 1 -101001 1 -101100 1 -101101 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -.names $abc$8517$new_n1226_ $abc$8517$new_n1210_ bus_address[8] $abc$8517$new_n1060_ $abc$8517$new_n302_ rd_data[8] -00011 1 -00100 1 -00101 1 -00110 1 -00111 1 -01011 1 -01111 1 -10000 1 -10001 1 -10010 1 -10011 1 -10100 1 -10101 1 -10110 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n299_ inst[12] inst[13] $abc$8517$new_n1228_ $abc$8517$new_n1227_ $abc$8517$new_n1226_ -10001 1 -10011 1 -10100 1 -10101 1 -10111 1 -11000 1 -11001 1 -11011 1 -11101 1 -11111 1 -.names inst[13] inst[12] inst[14] $abc$8517$new_n1224_ $abc$8517$new_n1227_ -0000 1 -1100 1 -.names bus_address[1] bus_address[0] bus_read_data[16] bus_read_data[24] bus_read_data[8] $abc$8517$new_n1228_ -00000 1 -00010 1 -00100 1 -00110 1 -01000 1 -01001 1 -01010 1 -01011 1 -10000 1 -10001 1 -10100 1 -10101 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n1230_ $abc$8517$new_n1210_ bus_address[9] $abc$8517$new_n1067_ $abc$8517$new_n302_ rd_data[9] -00011 1 -00100 1 -00101 1 -00110 1 -00111 1 -01011 1 -01111 1 -10000 1 -10001 1 -10010 1 -10011 1 -10100 1 -10101 1 -10110 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n299_ inst[12] inst[13] $abc$8517$new_n1231_ $abc$8517$new_n1227_ $abc$8517$new_n1230_ -10001 1 -10011 1 -10100 1 -10101 1 -10111 1 -11000 1 -11001 1 -11011 1 -11101 1 -11111 1 -.names bus_address[1] bus_address[0] bus_read_data[17] bus_read_data[25] bus_read_data[9] $abc$8517$new_n1231_ -00000 1 -00010 1 -00100 1 -00110 1 -01000 1 -01001 1 -01010 1 -01011 1 -10000 1 -10001 1 -10100 1 -10101 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n1233_ $abc$8517$new_n1210_ bus_address[10] $abc$8517$new_n1073_ $abc$8517$new_n302_ rd_data[10] -00011 1 -00100 1 -00101 1 -00110 1 -00111 1 -01011 1 -01111 1 -10000 1 -10001 1 -10010 1 -10011 1 -10100 1 -10101 1 -10110 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n299_ inst[12] inst[13] $abc$8517$new_n1234_ $abc$8517$new_n1227_ $abc$8517$new_n1233_ -10001 1 -10011 1 -10100 1 -10101 1 -10111 1 -11000 1 -11001 1 -11011 1 -11101 1 -11111 1 -.names bus_address[1] bus_address[0] bus_read_data[18] bus_read_data[26] bus_read_data[10] $abc$8517$new_n1234_ -00000 1 -00010 1 -00100 1 -00110 1 -01000 1 -01001 1 -01010 1 -01011 1 -10000 1 -10001 1 -10100 1 -10101 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n1236_ $abc$8517$new_n1210_ bus_address[11] $abc$8517$new_n1079_ $abc$8517$new_n302_ rd_data[11] -00011 1 -00100 1 -00101 1 -00110 1 -00111 1 -01011 1 -01111 1 -10000 1 -10001 1 -10010 1 -10011 1 -10100 1 -10101 1 -10110 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n299_ inst[12] inst[13] $abc$8517$new_n1237_ $abc$8517$new_n1227_ $abc$8517$new_n1236_ -10001 1 -10011 1 -10100 1 -10101 1 -10111 1 -11000 1 -11001 1 -11011 1 -11101 1 -11111 1 -.names bus_address[1] bus_address[0] bus_read_data[19] bus_read_data[27] bus_read_data[11] $abc$8517$new_n1237_ -00000 1 -00010 1 -00100 1 -00110 1 -01000 1 -01001 1 -01010 1 -01011 1 -10000 1 -10001 1 -10100 1 -10101 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n1239_ $abc$8517$new_n302_ $abc$8517$new_n1086_ rd_data[12] -000 1 -001 1 -010 1 -011 1 -111 1 -.names $abc$8517$new_n1241_ $abc$8517$new_n1227_ inst[12] inst[13] $abc$8517$new_n299_ $abc$8517$new_n1240_ $abc$8517$new_n1239_ -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100111 1 -101000 1 -101001 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110001 1 -110100 1 -110101 1 -111000 1 -111001 1 -111100 1 -111101 1 -.names bus_address[1] bus_address[0] bus_read_data[20] bus_read_data[28] bus_read_data[12] $abc$8517$new_n1240_ -00000 1 -00010 1 -00100 1 -00110 1 -01000 1 -01001 1 -01010 1 -01011 1 -10000 1 -10001 1 -10100 1 -10101 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n1210_ bus_address[12] $abc$8517$new_n300_ $abc$8517$new_n1084_ $abc$8517$new_n1241_ -0000 1 -0001 1 -0010 1 -0011 1 -1000 1 -1001 1 -1011 1 -1100 1 -1101 1 -1111 1 -.names $abc$8517$new_n1243_ bus_address[13] $abc$8517$new_n1210_ rd_data[13] -000 1 -001 1 -010 1 -011 1 -110 1 -.names $abc$8517$new_n1245_ $abc$8517$new_n1227_ inst[12] inst[13] $abc$8517$new_n299_ $abc$8517$new_n1244_ $abc$8517$new_n1243_ -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100111 1 -101000 1 -101001 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110001 1 -110100 1 -110101 1 -111000 1 -111001 1 -111100 1 -111101 1 -.names bus_address[1] bus_address[0] bus_read_data[21] bus_read_data[29] bus_read_data[13] $abc$8517$new_n1244_ -00000 1 -00010 1 -00100 1 -00110 1 -01000 1 -01001 1 -01010 1 -01011 1 -10000 1 -10001 1 -10100 1 -10101 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n1092_ $abc$8517$new_n302_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n1090_ $abc$8517$new_n1245_ -00000 1 -00001 1 -00010 1 -00011 1 -00100 1 -00101 1 -00111 1 -01000 1 -01001 1 -01010 1 -01011 1 -01100 1 -01101 1 -01111 1 -10000 1 -10001 1 -10010 1 -10011 1 -10100 1 -10101 1 -10111 1 -.names $abc$8517$new_n1247_ bus_address[14] $abc$8517$new_n1210_ rd_data[14] -000 1 -001 1 -010 1 -011 1 -110 1 -.names $abc$8517$new_n1249_ $abc$8517$new_n1227_ inst[12] inst[13] $abc$8517$new_n299_ $abc$8517$new_n1248_ $abc$8517$new_n1247_ -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100111 1 -101000 1 -101001 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110001 1 -110100 1 -110101 1 -111000 1 -111001 1 -111100 1 -111101 1 -.names bus_address[1] bus_address[0] bus_read_data[22] bus_read_data[30] bus_read_data[14] $abc$8517$new_n1248_ -00000 1 -00010 1 -00100 1 -00110 1 -01000 1 -01001 1 -01010 1 -01011 1 -10000 1 -10001 1 -10100 1 -10101 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n1098_ $abc$8517$new_n302_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n1097_ $abc$8517$new_n1249_ -00000 1 -00001 1 -00010 1 -00011 1 -00100 1 -00101 1 -00111 1 -01000 1 -01001 1 -01010 1 -01011 1 -01100 1 -01101 1 -01111 1 -10000 1 -10001 1 -10010 1 -10011 1 -10100 1 -10101 1 -10111 1 -.names $abc$8517$new_n1251_ bus_address[15] $abc$8517$new_n1210_ rd_data[15] -000 1 -001 1 -010 1 -011 1 -110 1 -.names $abc$8517$new_n1253_ $abc$8517$new_n1227_ inst[12] inst[13] $abc$8517$new_n299_ $abc$8517$new_n1252_ $abc$8517$new_n1251_ -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100111 1 -101000 1 -101001 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110001 1 -110100 1 -110101 1 -111000 1 -111001 1 -111100 1 -111101 1 -.names bus_address[1] bus_address[0] bus_read_data[23] bus_read_data[31] bus_read_data[15] $abc$8517$new_n1252_ -00000 1 -00010 1 -00100 1 -00110 1 -01000 1 -01001 1 -01010 1 -01011 1 -10000 1 -10001 1 -10100 1 -10101 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n1107_ $abc$8517$new_n302_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n1105_ $abc$8517$new_n1253_ -00000 1 -00001 1 -00010 1 -00011 1 -00100 1 -00101 1 -00111 1 -01000 1 -01001 1 -01010 1 -01011 1 -01100 1 -01101 1 -01111 1 -10000 1 -10001 1 -10010 1 -10011 1 -10100 1 -10101 1 -10111 1 -.names $abc$8517$new_n1255_ bus_address[16] $abc$8517$new_n1210_ rd_data[16] -000 1 -001 1 -010 1 -011 1 -110 1 -.names $abc$8517$new_n1256_ $abc$8517$new_n302_ $abc$8517$new_n1112_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n1111_ $abc$8517$new_n1255_ -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -000111 1 -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010111 1 -.names $abc$8517$new_n299_ $abc$8517$new_n1258_ bus_address[0] $abc$8517$new_n1257_ bus_read_data[24] bus_read_data[16] $abc$8517$new_n1256_ -100000 1 -100001 1 -100010 1 -100011 1 -101000 1 -101001 1 -101010 1 -101011 1 -110000 1 -110001 1 -110010 1 -110011 1 -110101 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111110 1 -111111 1 -.names inst[12] inst[13] inst[14] $abc$8517$new_n1224_ $abc$8517$new_n1252_ $abc$8517$new_n1257_ -00010 1 -00011 1 -00100 1 -00101 1 -00110 1 -00111 1 -01000 1 -01001 1 -01010 1 -01011 1 -01100 1 -01101 1 -01110 1 -01111 1 -10001 1 -10011 1 -10100 1 -10101 1 -10110 1 -10111 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names inst[13] inst[12] bus_address[1] $abc$8517$new_n1258_ -100 1 -.names $abc$8517$new_n1260_ $abc$8517$new_n302_ $abc$8517$new_n1118_ rd_data[17] -000 1 -001 1 -010 1 -011 1 -111 1 -.names $abc$8517$new_n1261_ bus_address[17] $abc$8517$new_n1210_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n1117_ $abc$8517$new_n1260_ -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -000111 1 -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011111 1 -.names $abc$8517$new_n299_ $abc$8517$new_n1258_ bus_address[0] $abc$8517$new_n1257_ bus_read_data[25] bus_read_data[17] $abc$8517$new_n1261_ -100000 1 -100001 1 -100010 1 -100011 1 -101000 1 -101001 1 -101010 1 -101011 1 -110000 1 -110001 1 -110010 1 -110011 1 -110101 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111110 1 -111111 1 -.names $abc$8517$new_n1263_ bus_address[18] $abc$8517$new_n1210_ rd_data[18] -000 1 -001 1 -010 1 -011 1 -110 1 -.names $abc$8517$new_n1264_ $abc$8517$new_n302_ $abc$8517$new_n1124_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n1122_ $abc$8517$new_n1263_ -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -000111 1 -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010111 1 -.names $abc$8517$new_n299_ $abc$8517$new_n1258_ bus_address[0] $abc$8517$new_n1257_ bus_read_data[26] bus_read_data[18] $abc$8517$new_n1264_ -100000 1 -100001 1 -100010 1 -100011 1 -101000 1 -101001 1 -101010 1 -101011 1 -110000 1 -110001 1 -110010 1 -110011 1 -110101 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111110 1 -111111 1 -.names $abc$8517$new_n1266_ bus_address[19] $abc$8517$new_n1210_ rd_data[19] -000 1 -001 1 -010 1 -011 1 -110 1 -.names $abc$8517$new_n1267_ $abc$8517$new_n302_ $abc$8517$new_n1130_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n1128_ $abc$8517$new_n1266_ -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -000111 1 -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010111 1 -.names $abc$8517$new_n299_ $abc$8517$new_n1258_ bus_address[0] $abc$8517$new_n1257_ bus_read_data[27] bus_read_data[19] $abc$8517$new_n1267_ -100000 1 -100001 1 -100010 1 -100011 1 -101000 1 -101001 1 -101010 1 -101011 1 -110000 1 -110001 1 -110010 1 -110011 1 -110101 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111110 1 -111111 1 -.names $abc$8517$new_n1269_ bus_address[20] $abc$8517$new_n1210_ rd_data[20] -000 1 -001 1 -010 1 -011 1 -110 1 -.names $abc$8517$new_n1270_ $abc$8517$new_n302_ $abc$8517$new_n1138_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n1137_ $abc$8517$new_n1269_ -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -000111 1 -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010111 1 -.names $abc$8517$new_n299_ $abc$8517$new_n1258_ bus_address[0] $abc$8517$new_n1257_ bus_read_data[28] bus_read_data[20] $abc$8517$new_n1270_ -100000 1 -100001 1 -100010 1 -100011 1 -101000 1 -101001 1 -101010 1 -101011 1 -110000 1 -110001 1 -110010 1 -110011 1 -110101 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111110 1 -111111 1 -.names $abc$8517$new_n1272_ bus_address[21] $abc$8517$new_n1210_ rd_data[21] -000 1 -001 1 -010 1 -011 1 -110 1 -.names $abc$8517$new_n1273_ $abc$8517$new_n302_ $abc$8517$new_n1143_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n1142_ $abc$8517$new_n1272_ -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -000111 1 -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010111 1 -.names $abc$8517$new_n299_ $abc$8517$new_n1258_ bus_address[0] $abc$8517$new_n1257_ bus_read_data[29] bus_read_data[21] $abc$8517$new_n1273_ -100000 1 -100001 1 -100010 1 -100011 1 -101000 1 -101001 1 -101010 1 -101011 1 -110000 1 -110001 1 -110010 1 -110011 1 -110101 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111110 1 -111111 1 -.names $abc$8517$new_n1276_ $abc$8517$new_n1275_ bus_address[22] $abc$8517$new_n1210_ rd_data[22] -0000 1 -0001 1 -0010 1 -0011 1 -0100 1 -0101 1 -0110 1 -0111 1 -1010 1 -1100 1 -1101 1 -1110 1 -1111 1 -.names $abc$8517$new_n299_ $abc$8517$new_n1258_ bus_address[0] $abc$8517$new_n1257_ bus_read_data[30] bus_read_data[22] $abc$8517$new_n1275_ -100000 1 -100001 1 -100010 1 -100011 1 -101000 1 -101001 1 -101010 1 -101011 1 -110000 1 -110001 1 -110010 1 -110011 1 -110101 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111110 1 -111111 1 -.names $abc$8517$new_n302_ singlecycle_datapath.program_counter.value[22] $abc$8517$new_n1148_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n414_ $abc$8517$new_n1276_ -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -000111 1 -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011111 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111111 1 -.names $abc$8517$new_n1278_ bus_address[23] $abc$8517$new_n1210_ rd_data[23] -000 1 -001 1 -010 1 -011 1 -110 1 -.names $abc$8517$new_n1279_ $abc$8517$new_n302_ $abc$8517$new_n1153_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n427_ $abc$8517$new_n1278_ -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -000111 1 -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010111 1 -.names $abc$8517$new_n299_ $abc$8517$new_n1258_ bus_address[0] $abc$8517$new_n1257_ bus_read_data[31] bus_read_data[23] $abc$8517$new_n1279_ -100000 1 -100001 1 -100010 1 -100011 1 -101000 1 -101001 1 -101010 1 -101011 1 -110000 1 -110001 1 -110010 1 -110011 1 -110101 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111110 1 -111111 1 -.names $abc$8517$new_n1281_ bus_address[24] $abc$8517$new_n1210_ rd_data[24] -000 1 -001 1 -010 1 -011 1 -110 1 -.names $abc$8517$new_n1283_ $abc$8517$new_n1257_ $abc$8517$new_n299_ $abc$8517$new_n1282_ bus_read_data[24] $abc$8517$new_n1281_ -10000 1 -10001 1 -10010 1 -10011 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -.names $abc$8517$new_n1258_ bus_address[0] $abc$8517$new_n1282_ -10 1 -.names $abc$8517$new_n1159_ $abc$8517$new_n302_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n1157_ $abc$8517$new_n1283_ -00000 1 -00001 1 -00010 1 -00011 1 -00100 1 -00101 1 -00111 1 -01000 1 -01001 1 -01010 1 -01011 1 -01100 1 -01101 1 -01111 1 -10000 1 -10001 1 -10010 1 -10011 1 -10100 1 -10101 1 -10111 1 -.names $abc$8517$new_n1285_ bus_address[25] $abc$8517$new_n1210_ rd_data[25] -000 1 -001 1 -010 1 -011 1 -110 1 -.names $abc$8517$new_n1286_ $abc$8517$new_n1257_ $abc$8517$new_n299_ $abc$8517$new_n1282_ bus_read_data[25] $abc$8517$new_n1285_ -10000 1 -10001 1 -10010 1 -10011 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -.names $abc$8517$new_n1169_ $abc$8517$new_n302_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n1167_ $abc$8517$new_n1286_ -00000 1 -00001 1 -00010 1 -00011 1 -00100 1 -00101 1 -00111 1 -01000 1 -01001 1 -01010 1 -01011 1 -01100 1 -01101 1 -01111 1 -10000 1 -10001 1 -10010 1 -10011 1 -10100 1 -10101 1 -10111 1 -.names $abc$8517$new_n1288_ bus_address[26] $abc$8517$new_n1210_ rd_data[26] -000 1 -001 1 -010 1 -011 1 -110 1 -.names $abc$8517$new_n1289_ $abc$8517$new_n1257_ $abc$8517$new_n299_ $abc$8517$new_n1282_ bus_read_data[26] $abc$8517$new_n1288_ -10000 1 -10001 1 -10010 1 -10011 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -.names $abc$8517$new_n1174_ $abc$8517$new_n302_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n1172_ $abc$8517$new_n1289_ -00000 1 -00001 1 -00010 1 -00011 1 -00100 1 -00101 1 -00111 1 -01000 1 -01001 1 -01010 1 -01011 1 -01100 1 -01101 1 -01111 1 -10000 1 -10001 1 -10010 1 -10011 1 -10100 1 -10101 1 -10111 1 -.names $abc$8517$new_n1291_ bus_address[27] $abc$8517$new_n1210_ rd_data[27] -000 1 -001 1 -010 1 -011 1 -110 1 -.names $abc$8517$new_n1292_ $abc$8517$new_n1257_ $abc$8517$new_n299_ $abc$8517$new_n1282_ bus_read_data[27] $abc$8517$new_n1291_ -10000 1 -10001 1 -10010 1 -10011 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -.names $abc$8517$new_n1180_ $abc$8517$new_n302_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n1179_ $abc$8517$new_n1292_ -00000 1 -00001 1 -00010 1 -00011 1 -00100 1 -00101 1 -00111 1 -01000 1 -01001 1 -01010 1 -01011 1 -01100 1 -01101 1 -01111 1 -10000 1 -10001 1 -10010 1 -10011 1 -10100 1 -10101 1 -10111 1 -.names $abc$8517$new_n1294_ bus_address[28] $abc$8517$new_n1210_ rd_data[28] -000 1 -001 1 -010 1 -011 1 -110 1 -.names $abc$8517$new_n1295_ $abc$8517$new_n1257_ $abc$8517$new_n299_ $abc$8517$new_n1282_ bus_read_data[28] $abc$8517$new_n1294_ -10000 1 -10001 1 -10010 1 -10011 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -.names $abc$8517$new_n1185_ $abc$8517$new_n302_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n1184_ $abc$8517$new_n1295_ -00000 1 -00001 1 -00010 1 -00011 1 -00100 1 -00101 1 -00111 1 -01000 1 -01001 1 -01010 1 -01011 1 -01100 1 -01101 1 -01111 1 -10000 1 -10001 1 -10010 1 -10011 1 -10100 1 -10101 1 -10111 1 -.names $abc$8517$new_n1297_ bus_address[29] $abc$8517$new_n1210_ rd_data[29] -000 1 -001 1 -010 1 -011 1 -110 1 -.names $abc$8517$new_n1298_ $abc$8517$new_n1257_ $abc$8517$new_n299_ $abc$8517$new_n1282_ bus_read_data[29] $abc$8517$new_n1297_ -10000 1 -10001 1 -10010 1 -10011 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -.names $abc$8517$new_n1191_ $abc$8517$new_n302_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n1189_ $abc$8517$new_n1298_ -00000 1 -00001 1 -00010 1 -00011 1 -00100 1 -00101 1 -00111 1 -01000 1 -01001 1 -01010 1 -01011 1 -01100 1 -01101 1 -01111 1 -10000 1 -10001 1 -10010 1 -10011 1 -10100 1 -10101 1 -10111 1 -.names $abc$8517$new_n1300_ bus_address[30] $abc$8517$new_n1210_ rd_data[30] -000 1 -001 1 -010 1 -011 1 -110 1 -.names $abc$8517$new_n1301_ $abc$8517$new_n1257_ $abc$8517$new_n299_ $abc$8517$new_n1282_ bus_read_data[30] $abc$8517$new_n1300_ -10000 1 -10001 1 -10010 1 -10011 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -.names $abc$8517$new_n1197_ $abc$8517$new_n302_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n1196_ $abc$8517$new_n1301_ -00000 1 -00001 1 -00010 1 -00011 1 -00100 1 -00101 1 -00111 1 -01000 1 -01001 1 -01010 1 -01011 1 -01100 1 -01101 1 -01111 1 -10000 1 -10001 1 -10010 1 -10011 1 -10100 1 -10101 1 -10111 1 -.names $abc$8517$new_n1304_ $abc$8517$new_n1303_ bus_address[31] $abc$8517$new_n1210_ rd_data[31] -0000 1 -0001 1 -0010 1 -0011 1 -0100 1 -0101 1 -0110 1 -0111 1 -1010 1 -1100 1 -1101 1 -1110 1 -1111 1 -.names $abc$8517$new_n299_ $abc$8517$new_n1257_ bus_read_data[31] $abc$8517$new_n1282_ $abc$8517$new_n1303_ -1000 1 -1001 1 -1010 1 -1011 1 -1111 1 -.names $abc$8517$new_n302_ singlecycle_datapath.program_counter.value[31] $abc$8517$new_n1203_ inst[5] $abc$8517$new_n300_ $abc$8517$new_n494_ $abc$8517$new_n1304_ -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -000111 1 -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011111 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111111 1 -.names $abc$8517$new_n299_ $abc$8517$new_n1009_ $abc$8517$new_n348_ $abc$8517$new_n1307_ $abc$8517$new_n1306_ inst[3] bus_write_enable -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -101000 1 -101001 1 -101010 1 -101011 1 -.names inst[2] $abc$8517$new_n313_ $abc$8517$new_n1306_ -11 1 -.names inst[6] inst[5] inst[4] $abc$8517$new_n1307_ -000 1 -.names $abc$8517$new_n299_ $abc$8517$new_n1306_ $abc$8517$new_n346_ $abc$8517$new_n1310_ bus_read_enable -1010 1 -1011 1 -1100 1 -1110 1 -.names inst[3] $abc$8517$new_n1307_ $abc$8517$new_n1310_ -11 1 -.names rs2_data[0] $abc$8517$new_n1005_ bus_write_data[0] -11 1 -.names rs2_data[1] $abc$8517$new_n1005_ bus_write_data[1] -11 1 -.names rs2_data[2] $abc$8517$new_n1005_ bus_write_data[2] -11 1 -.names rs2_data[3] $abc$8517$new_n1005_ bus_write_data[3] -11 1 -.names rs2_data[4] $abc$8517$new_n1005_ bus_write_data[4] -11 1 -.names rs2_data[5] $abc$8517$new_n1005_ bus_write_data[5] -11 1 -.names rs2_data[6] $abc$8517$new_n1005_ bus_write_data[6] -11 1 -.names rs2_data[7] $abc$8517$new_n1005_ bus_write_data[7] -11 1 -.names bus_address[0] bus_address[1] rs2_data[0] rs2_data[8] bus_write_data[8] -0001 1 -0011 1 -1010 1 -1011 1 -.names bus_address[0] bus_address[1] rs2_data[1] rs2_data[9] bus_write_data[9] -0001 1 -0011 1 -1010 1 -1011 1 -.names bus_address[0] bus_address[1] rs2_data[2] rs2_data[10] bus_write_data[10] -0001 1 -0011 1 -1010 1 -1011 1 -.names bus_address[0] bus_address[1] rs2_data[3] rs2_data[11] bus_write_data[11] -0001 1 -0011 1 -1010 1 -1011 1 -.names bus_address[0] bus_address[1] rs2_data[4] rs2_data[12] bus_write_data[12] -0001 1 -0011 1 -1010 1 -1011 1 -.names bus_address[0] bus_address[1] rs2_data[5] rs2_data[13] bus_write_data[13] -0001 1 -0011 1 -1010 1 -1011 1 -.names bus_address[0] bus_address[1] rs2_data[6] rs2_data[14] bus_write_data[14] -0001 1 -0011 1 -1010 1 -1011 1 -.names bus_address[0] bus_address[1] rs2_data[7] rs2_data[15] bus_write_data[15] -0001 1 -0011 1 -1010 1 -1011 1 -.names bus_address[1] bus_address[0] rs2_data[8] rs2_data[0] rs2_data[16] bus_write_data[16] -00001 1 -00011 1 -00101 1 -00111 1 -01100 1 -01101 1 -01110 1 -01111 1 -10010 1 -10011 1 -10110 1 -10111 1 -.names bus_address[1] bus_address[0] rs2_data[9] rs2_data[1] rs2_data[17] bus_write_data[17] -00001 1 -00011 1 -00101 1 -00111 1 -01100 1 -01101 1 -01110 1 -01111 1 -10010 1 -10011 1 -10110 1 -10111 1 -.names bus_address[1] bus_address[0] rs2_data[10] rs2_data[2] rs2_data[18] bus_write_data[18] -00001 1 -00011 1 -00101 1 -00111 1 -01100 1 -01101 1 -01110 1 -01111 1 -10010 1 -10011 1 -10110 1 -10111 1 -.names bus_address[1] bus_address[0] rs2_data[11] rs2_data[3] rs2_data[19] bus_write_data[19] -00001 1 -00011 1 -00101 1 -00111 1 -01100 1 -01101 1 -01110 1 -01111 1 -10010 1 -10011 1 -10110 1 -10111 1 -.names bus_address[1] bus_address[0] rs2_data[12] rs2_data[4] rs2_data[20] bus_write_data[20] -00001 1 -00011 1 -00101 1 -00111 1 -01100 1 -01101 1 -01110 1 -01111 1 -10010 1 -10011 1 -10110 1 -10111 1 -.names bus_address[1] bus_address[0] rs2_data[13] rs2_data[5] rs2_data[21] bus_write_data[21] -00001 1 -00011 1 -00101 1 -00111 1 -01100 1 -01101 1 -01110 1 -01111 1 -10010 1 -10011 1 -10110 1 -10111 1 -.names bus_address[1] bus_address[0] rs2_data[14] rs2_data[6] rs2_data[22] bus_write_data[22] -00001 1 -00011 1 -00101 1 -00111 1 -01100 1 -01101 1 -01110 1 -01111 1 -10010 1 -10011 1 -10110 1 -10111 1 -.names bus_address[1] bus_address[0] rs2_data[15] rs2_data[7] rs2_data[23] bus_write_data[23] -00001 1 -00011 1 -00101 1 -00111 1 -01100 1 -01101 1 -01110 1 -01111 1 -10010 1 -10011 1 -10110 1 -10111 1 -.names bus_address[1] bus_address[0] rs2_data[0] rs2_data[16] rs2_data[8] rs2_data[24] bus_write_data[24] -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names bus_address[1] bus_address[0] rs2_data[1] rs2_data[17] rs2_data[9] rs2_data[25] bus_write_data[25] -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names bus_address[1] bus_address[0] rs2_data[2] rs2_data[18] rs2_data[10] rs2_data[26] bus_write_data[26] -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names bus_address[1] bus_address[0] rs2_data[3] rs2_data[19] rs2_data[11] rs2_data[27] bus_write_data[27] -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names bus_address[1] bus_address[0] rs2_data[4] rs2_data[20] rs2_data[12] rs2_data[28] bus_write_data[28] -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names bus_address[1] bus_address[0] rs2_data[5] rs2_data[21] rs2_data[13] rs2_data[29] bus_write_data[29] -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names bus_address[1] bus_address[0] rs2_data[6] rs2_data[22] rs2_data[14] rs2_data[30] bus_write_data[30] -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names bus_address[1] bus_address[0] rs2_data[7] rs2_data[23] rs2_data[15] rs2_data[31] bus_write_data[31] -000001 1 -000011 1 -000101 1 -000111 1 -001001 1 -001011 1 -001101 1 -001111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100011 1 -100110 1 -100111 1 -101010 1 -101011 1 -101110 1 -101111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n462_ $abc$8517$new_n444_ $abc$8517$new_n306_ $abc$8517$new_n411_ $abc$8517$new_n454_ $abc$8517$new_n451_ $abc$8517$new_n1343_ -000011 1 -000111 1 -001011 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -100000 1 -100001 1 -100010 1 -100011 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n433_ $abc$8517$new_n436_ $abc$8517$new_n432_ $abc$8517$new_n431_ $abc$8517$new_n442_ $abc$8517$new_n1343_ $abc$8517$new_n1344_ -000000 1 -000001 1 -000010 1 -000100 1 -000101 1 -000110 1 -000111 1 -001100 1 -001101 1 -001110 1 -010100 1 -010101 1 -010110 1 -010111 1 -.names $abc$8517$new_n310_ rs2_data[19] $abc$8517$new_n314_ inst[31] $abc$8517$new_n312_ $abc$8517$new_n1345_ -00010 1 -00100 1 -00101 1 -00110 1 -00111 1 -01010 1 -01100 1 -01101 1 -01110 1 -01111 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n318_ $abc$8517$new_n315_ $abc$8517$new_n423_ $abc$8517$new_n1345_ $abc$8517$new_n335_ $abc$8517$new_n418_ $abc$8517$new_n1346_ -000000 1 -000001 1 -000011 1 -000100 1 -000101 1 -000111 1 -001000 1 -001001 1 -001011 1 -001100 1 -001101 1 -001111 1 -010000 1 -010001 1 -010011 1 -011000 1 -011001 1 -011011 1 -100000 1 -100001 1 -100011 1 -101000 1 -101001 1 -101011 1 -101100 1 -101101 1 -101111 1 -111000 1 -111001 1 -111011 1 -.names $abc$8517$new_n1346_ $abc$8517$new_n413_ $abc$8517$new_n419_ $abc$8517$new_n330_ $abc$8517$new_n329_ $abc$8517$new_n415_ $abc$8517$new_n1347_ -000000 1 -000100 1 -001000 1 -001100 1 -010000 1 -010010 1 -010100 1 -010110 1 -011000 1 -011010 1 -011100 1 -011110 1 -100000 1 -100001 1 -100100 1 -101000 1 -101100 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110110 1 -111000 1 -111010 1 -111100 1 -111110 1 -.names $abc$8517$new_n433_ $abc$8517$new_n461_ $abc$8517$new_n432_ $abc$8517$new_n431_ $abc$8517$new_n411_ $abc$8517$new_n306_ $abc$8517$new_n1348_ -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -000110 1 -000111 1 -001100 1 -001101 1 -001110 1 -001111 1 -010010 1 -010100 1 -010101 1 -010110 1 -010111 1 -011110 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n495_ $abc$8517$new_n1348_ $abc$8517$new_n493_ $abc$8517$new_n492_ $abc$8517$new_n1349_ -1000 1 -1001 1 -1010 1 -1011 1 -1101 1 -1110 1 -1111 1 -.names $abc$8517$new_n479_ $abc$8517$new_n497_ $abc$8517$new_n519_ $abc$8517$new_n353_ $abc$8517$new_n467_ $abc$8517$new_n489_ $abc$8517$new_n1350_ -000000 1 -000001 1 -000010 1 -000011 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101110 1 -110010 1 -110110 1 -111010 1 -111110 1 -.names $abc$8517$new_n551_ $abc$8517$new_n520_ $abc$8517$new_n368_ $abc$8517$new_n543_ $abc$8517$new_n542_ $abc$8517$new_n362_ $abc$8517$new_n1351_ -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -000110 1 -000111 1 -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -.names $abc$8517$new_n1351_ $abc$8517$new_n545_ $abc$8517$new_n486_ $abc$8517$new_n549_ $abc$8517$new_n502_ $abc$8517$new_n1350_ $abc$8517$new_n1352_ -100000 1 -100010 1 -100110 1 -101000 1 -101001 1 -101010 1 -101011 1 -101110 1 -101111 1 -.names $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n573_ $abc$8517$new_n571_ $abc$8517$new_n574_ $abc$8517$new_n572_ $abc$8517$new_n1353_ -000000 1 -000010 1 -000100 1 -000110 1 -001000 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -011000 1 -011001 1 -011010 1 -011011 1 -100000 1 -100001 1 -100100 1 -100101 1 -101000 1 -101001 1 -101100 1 -101101 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -.names $abc$8517$new_n575_ $abc$8517$new_n578_ $abc$8517$new_n345_ $abc$8517$new_n554_ $abc$8517$new_n1353_ $abc$8517$new_n549_ bus_address[1] -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -000110 1 -000111 1 -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110001 1 -110010 1 -110011 1 -110111 1 -111011 1 -111111 1 -.names $abc$8517$new_n359_ $abc$8517$new_n548_ $abc$8517$new_n618_ $abc$8517$new_n546_ $abc$8517$new_n616_ $abc$8517$new_n1358_ $abc$8517$new_n1355_ -000001 1 -000011 1 -000111 1 -001001 1 -001011 1 -001111 1 -010001 1 -010011 1 -010111 1 -100001 1 -100011 1 -100101 1 -101001 1 -101011 1 -101101 1 -111001 1 -111011 1 -111101 1 -.names $abc$8517$new_n486_ $abc$8517$new_n497_ $abc$8517$new_n368_ $abc$8517$new_n361_ $abc$8517$new_n479_ $abc$8517$new_n1357_ -00010 1 -00100 1 -00110 1 -00111 1 -01000 1 -01001 1 -01010 1 -01011 1 -01100 1 -01101 1 -01110 1 -01111 1 -10000 1 -10001 1 -10011 1 -10101 1 -10110 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -11111 1 -.names $abc$8517$new_n1357_ $abc$8517$new_n612_ $abc$8517$new_n543_ $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n1358_ -10000 1 -10001 1 -10010 1 -10011 1 -10100 1 -10101 1 -10110 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -.names $abc$8517$new_n659_ $abc$8517$new_n547_ $abc$8517$new_n344_ $abc$8517$new_n342_ $abc$8517$new_n576_ $abc$8517$new_n579_ $abc$8517$new_n1359_ -000000 1 -000100 1 -000101 1 -001000 1 -001001 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010010 1 -011100 1 -011101 1 -011110 1 -011111 1 -.names $abc$8517$new_n1359_ $abc$8517$new_n653_ $abc$8517$new_n647_ $abc$8517$new_n646_ $abc$8517$new_n549_ $abc$8517$new_n657_ bus_address[5] -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -000110 1 -000111 1 -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -100010 1 -100100 1 -100101 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n543_ $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n682_ $abc$8517$new_n681_ $abc$8517$new_n680_ $abc$8517$new_n1361_ -000100 1 -000101 1 -000110 1 -000111 1 -001100 1 -001101 1 -001110 1 -001111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011100 1 -011101 1 -011110 1 -011111 1 -100100 1 -100101 1 -100110 1 -100111 1 -101100 1 -101101 1 -101110 1 -101111 1 -110100 1 -110110 1 -111110 1 -111111 1 -.names $abc$8517$new_n1361_ $abc$8517$new_n665_ $abc$8517$new_n368_ $abc$8517$new_n549_ $abc$8517$new_n1365_ $abc$8517$new_n1367_ $abc$8517$new_n1362_ -100000 1 -100001 1 -100010 1 -100011 1 -100101 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101110 1 -101111 1 -.names $abc$8517$new_n663_ $abc$8517$new_n548_ $abc$8517$new_n664_ $abc$8517$new_n546_ $abc$8517$new_n662_ $abc$8517$new_n1362_ bus_address[6] -000000 1 -000010 1 -000100 1 -000101 1 -000110 1 -001000 1 -001010 1 -001100 1 -001101 1 -001110 1 -010000 1 -010010 1 -010100 1 -010101 1 -010110 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -100000 1 -100010 1 -100100 1 -100110 1 -100111 1 -101000 1 -101010 1 -101100 1 -101110 1 -101111 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111010 1 -111100 1 -111110 1 -111111 1 -.names $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n517_ $abc$8517$new_n516_ $abc$8517$new_n507_ $abc$8517$new_n506_ $abc$8517$new_n1364_ -010000 1 -010100 1 -011000 1 -011100 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110001 1 -110010 1 -110011 1 -.names $abc$8517$new_n1364_ $abc$8517$new_n356_ $abc$8517$new_n505_ $abc$8517$new_n504_ $abc$8517$new_n510_ $abc$8517$new_n509_ $abc$8517$new_n1365_ -000000 1 -000100 1 -001000 1 -001100 1 -100000 1 -100001 1 -100010 1 -100011 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n362_ $abc$8517$new_n356_ $abc$8517$new_n512_ $abc$8517$new_n511_ $abc$8517$new_n525_ $abc$8517$new_n524_ $abc$8517$new_n1366_ -010000 1 -010100 1 -011000 1 -011100 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110001 1 -110010 1 -110011 1 -.names $abc$8517$new_n1366_ $abc$8517$new_n356_ $abc$8517$new_n523_ $abc$8517$new_n522_ $abc$8517$new_n528_ $abc$8517$new_n527_ $abc$8517$new_n1367_ -000000 1 -000100 1 -001000 1 -001100 1 -100000 1 -100001 1 -100010 1 -100011 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n497_ $abc$8517$new_n486_ $abc$8517$new_n399_ $abc$8517$new_n479_ $abc$8517$new_n397_ $abc$8517$new_n489_ $abc$8517$new_n1368_ -000010 1 -000011 1 -001000 1 -001001 1 -001010 1 -001011 1 -001110 1 -001111 1 -010000 1 -010001 1 -010100 1 -010101 1 -010110 1 -010111 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -110001 1 -110011 1 -110101 1 -110111 1 -111001 1 -111011 1 -111101 1 -111111 1 -.names $abc$8517$new_n712_ $abc$8517$new_n1368_ $abc$8517$new_n497_ $abc$8517$new_n701_ $abc$8517$new_n548_ $abc$8517$new_n711_ $abc$8517$new_n1369_ -001000 1 -001001 1 -001010 1 -001100 1 -001101 1 -001110 1 -010000 1 -010001 1 -010010 1 -010100 1 -010101 1 -010110 1 -011100 1 -011101 1 -011110 1 -101000 1 -101001 1 -101011 1 -101100 1 -101101 1 -101111 1 -110000 1 -110001 1 -110011 1 -110100 1 -110101 1 -110111 1 -111000 1 -111001 1 -111011 1 -.names $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n571_ $abc$8517$new_n556_ $abc$8517$new_n572_ $abc$8517$new_n557_ $abc$8517$new_n1371_ -000000 1 -000010 1 -000100 1 -000110 1 -001000 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -011000 1 -011001 1 -011010 1 -011011 1 -100000 1 -100001 1 -100100 1 -100101 1 -101000 1 -101001 1 -101100 1 -101101 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -.names $abc$8517$new_n724_ $abc$8517$new_n720_ $abc$8517$new_n717_ $abc$8517$new_n549_ $abc$8517$new_n1371_ $abc$8517$new_n345_ $abc$8517$new_n1372_ -100001 1 -100011 1 -100101 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -.names $abc$8517$new_n1372_ $abc$8517$new_n715_ $abc$8517$new_n406_ $abc$8517$new_n714_ $abc$8517$new_n709_ $abc$8517$new_n546_ bus_address[9] -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -000110 1 -000111 1 -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -100011 1 -100101 1 -100111 1 -101001 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n620_ $abc$8517$new_n605_ $abc$8517$new_n621_ $abc$8517$new_n606_ $abc$8517$new_n1374_ -000000 1 -000010 1 -000100 1 -000110 1 -001000 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -011000 1 -011001 1 -011010 1 -011011 1 -100000 1 -100001 1 -100100 1 -100101 1 -101000 1 -101001 1 -101100 1 -101101 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -.names $abc$8517$new_n754_ $abc$8517$new_n752_ $abc$8517$new_n755_ $abc$8517$new_n756_ $abc$8517$new_n745_ $abc$8517$new_n548_ $abc$8517$new_n1375_ -100000 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101100 1 -101110 1 -.names $abc$8517$new_n1375_ $abc$8517$new_n746_ $abc$8517$new_n1374_ $abc$8517$new_n549_ $abc$8517$new_n1376_ -1000 1 -1001 1 -1010 1 -.names $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n508_ $abc$8517$new_n526_ $abc$8517$new_n521_ $abc$8517$new_n531_ $abc$8517$new_n1377_ -000000 1 -000010 1 -000100 1 -000110 1 -001000 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -011000 1 -011001 1 -011010 1 -011011 1 -100000 1 -100001 1 -100100 1 -100101 1 -101000 1 -101001 1 -101100 1 -101101 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -.names $abc$8517$new_n768_ $abc$8517$new_n772_ $abc$8517$new_n770_ $abc$8517$new_n769_ $abc$8517$new_n711_ $abc$8517$new_n548_ $abc$8517$new_n1378_ -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -100111 1 -101000 1 -101010 1 -101100 1 -101101 1 -101110 1 -110000 1 -110010 1 -110100 1 -110110 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111110 1 -111111 1 -.names $abc$8517$new_n1378_ $abc$8517$new_n763_ $abc$8517$new_n766_ $abc$8517$new_n549_ $abc$8517$new_n543_ $abc$8517$new_n1377_ $abc$8517$new_n1379_ -100000 1 -100001 1 -100100 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101110 1 -.names $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n572_ $abc$8517$new_n557_ $abc$8517$new_n556_ $abc$8517$new_n649_ $abc$8517$new_n1380_ -000000 1 -000010 1 -000100 1 -000110 1 -001000 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -011000 1 -011001 1 -011010 1 -011011 1 -100000 1 -100001 1 -100100 1 -100101 1 -101000 1 -101001 1 -101100 1 -101101 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -.names $abc$8517$new_n782_ $abc$8517$new_n781_ $abc$8517$new_n777_ $abc$8517$new_n549_ $abc$8517$new_n543_ $abc$8517$new_n1380_ $abc$8517$new_n1381_ -100000 1 -100001 1 -100100 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101110 1 -.names $abc$8517$new_n1381_ $abc$8517$new_n382_ $abc$8517$new_n783_ $abc$8517$new_n775_ $abc$8517$new_n548_ $abc$8517$new_n1382_ -10000 1 -10010 1 -10011 1 -10100 1 -10101 1 -10110 1 -10111 1 -11000 1 -11001 1 -11010 1 -11100 1 -11110 1 -.names $abc$8517$new_n368_ $abc$8517$new_n345_ $abc$8517$new_n362_ $abc$8517$new_n837_ $abc$8517$new_n838_ $abc$8517$new_n778_ $abc$8517$new_n1383_ -010000 1 -010001 1 -010010 1 -010011 1 -011000 1 -011001 1 -011010 1 -011011 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110010 1 -110100 1 -110110 1 -111000 1 -111001 1 -111100 1 -111101 1 -.names $abc$8517$new_n1383_ $abc$8517$new_n820_ $abc$8517$new_n345_ $abc$8517$new_n840_ $abc$8517$new_n554_ $abc$8517$new_n581_ $abc$8517$new_n1384_ -000100 1 -000101 1 -000110 1 -000111 1 -001110 1 -001111 1 -010100 1 -010101 1 -010110 1 -010111 1 -011110 1 -011111 1 -100100 1 -100101 1 -100110 1 -100111 1 -101110 1 -101111 1 -110100 1 -110110 1 -.names $abc$8517$new_n368_ $abc$8517$new_n820_ $abc$8517$new_n588_ $abc$8517$new_n345_ $abc$8517$new_n734_ $abc$8517$new_n850_ $abc$8517$new_n1385_ -000100 1 -000101 1 -000110 1 -000111 1 -010110 1 -010111 1 -100100 1 -100101 1 -100110 1 -100111 1 -110000 1 -110001 1 -110010 1 -110011 1 -110101 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -.names $abc$8517$new_n853_ $abc$8517$new_n345_ $abc$8517$new_n845_ $abc$8517$new_n1385_ $abc$8517$new_n600_ $abc$8517$new_n583_ $abc$8517$new_n1386_ -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -110100 1 -110110 1 -.names $abc$8517$new_n546_ $abc$8517$new_n843_ $abc$8517$new_n316_ $abc$8517$new_n1386_ bus_address[18] -0000 1 -0010 1 -0100 1 -0110 1 -1000 1 -1010 1 -1011 1 -1100 1 -1101 1 -1110 1 -.names $abc$8517$new_n368_ $abc$8517$new_n543_ $abc$8517$new_n874_ $abc$8517$new_n819_ $abc$8517$new_n634_ $abc$8517$new_n877_ $abc$8517$new_n1388_ -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -000110 1 -000111 1 -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001110 1 -001111 1 -010001 1 -010011 1 -010101 1 -010111 1 -011001 1 -011011 1 -011101 1 -011111 1 -100000 1 -100001 1 -100010 1 -100011 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101110 1 -101111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111110 1 -111111 1 -.names $abc$8517$new_n1388_ $abc$8517$new_n878_ $abc$8517$new_n879_ $abc$8517$new_n869_ $abc$8517$new_n333_ $abc$8517$new_n548_ $abc$8517$new_n1389_ -110000 1 -110001 1 -110010 1 -110100 1 -110110 1 -110111 1 -.names $abc$8517$new_n413_ $abc$8517$new_n329_ $abc$8517$new_n497_ $abc$8517$new_n552_ $abc$8517$new_n547_ $abc$8517$new_n478_ $abc$8517$new_n1390_ -000000 1 -000010 1 -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -011000 1 -011001 1 -011100 1 -011101 1 -100000 1 -100001 1 -101000 1 -101001 1 -101100 1 -101101 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n1390_ $abc$8517$new_n901_ $abc$8517$new_n897_ $abc$8517$new_n896_ $abc$8517$new_n894_ $abc$8517$new_n811_ bus_address[22] -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -000110 1 -000111 1 -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n345_ $abc$8517$new_n717_ $abc$8517$new_n933_ $abc$8517$new_n543_ $abc$8517$new_n568_ $abc$8517$new_n434_ $abc$8517$new_n1392_ -000000 1 -000001 1 -000011 1 -001000 1 -001001 1 -001011 1 -001100 1 -001101 1 -001111 1 -010000 1 -010001 1 -010011 1 -011000 1 -011001 1 -011011 1 -011100 1 -011101 1 -011111 1 -110000 1 -110001 1 -110010 1 -110011 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n936_ $abc$8517$new_n1392_ $abc$8517$new_n932_ $abc$8517$new_n929_ $abc$8517$new_n546_ $abc$8517$new_n927_ bus_address[25] -000000 1 -000001 1 -000010 1 -000011 1 -000100 1 -000101 1 -000110 1 -000111 1 -001000 1 -001001 1 -001010 1 -001011 1 -001100 1 -001101 1 -001110 1 -001111 1 -010000 1 -010001 1 -010010 1 -010011 1 -010100 1 -010101 1 -010110 1 -010111 1 -011000 1 -011001 1 -011010 1 -011011 1 -011100 1 -011101 1 -011110 1 -011111 1 -100000 1 -100001 1 -100010 1 -100011 1 -100100 1 -100101 1 -100110 1 -100111 1 -101000 1 -101001 1 -101010 1 -101011 1 -101100 1 -101101 1 -101110 1 -101111 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n820_ $abc$8517$new_n368_ $abc$8517$new_n863_ $abc$8517$new_n362_ $abc$8517$new_n612_ $abc$8517$new_n1394_ -10011 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -.names $abc$8517$new_n345_ $abc$8517$new_n748_ $abc$8517$new_n692_ $abc$8517$new_n1394_ $abc$8517$new_n719_ $abc$8517$new_n747_ $abc$8517$new_n1395_ -000000 1 -000001 1 -000010 1 -000011 1 -001000 1 -001001 1 -001010 1 -001011 1 -010000 1 -010001 1 -010010 1 -010011 1 -011000 1 -011001 1 -011010 1 -011011 1 -100000 1 -100001 1 -100011 1 -100100 1 -100101 1 -100111 1 -.names $abc$8517$new_n959_ $abc$8517$new_n1395_ $abc$8517$new_n956_ $abc$8517$new_n811_ $abc$8517$new_n548_ $abc$8517$new_n952_ $abc$8517$new_n1396_ -110000 1 -110001 1 -110010 1 -.names $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n623_ $abc$8517$new_n620_ $abc$8517$new_n624_ $abc$8517$new_n621_ $abc$8517$new_n1397_ -000000 1 -000010 1 -000100 1 -000110 1 -001000 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -011000 1 -011001 1 -011010 1 -011011 1 -100000 1 -100001 1 -100100 1 -100101 1 -101000 1 -101001 1 -101100 1 -101101 1 -111000 1 -111001 1 -111010 1 -111011 1 -111100 1 -111101 1 -111110 1 -111111 1 -.names $abc$8517$new_n1355_ $abc$8517$new_n603_ $abc$8517$new_n1397_ $abc$8517$new_n549_ bus_address[3] -0000 1 -0001 1 -0010 1 -0011 1 -0100 1 -0101 1 -0110 1 -0111 1 -1011 1 -1100 1 -1101 1 -1110 1 -1111 1 -.names $abc$8517$new_n368_ $abc$8517$new_n362_ $abc$8517$new_n503_ $abc$8517$new_n521_ $abc$8517$new_n508_ $abc$8517$new_n526_ $abc$8517$new_n1399_ -000000 1 -000010 1 -000100 1 -000110 1 -001000 1 -001010 1 -001100 1 -001110 1 -010000 1 -010001 1 -010010 1 -010011 1 -011000 1 -011001 1 -011010 1 -011011 1 -100000 1 -100001 1 -100100 1 -100101 1 -101000 1 -101001 1 -101100 1 -101101 1 -110000 1 -110001 1 -110010 1 -110011 1 -110100 1 -110101 1 -110110 1 -110111 1 -.names $abc$8517$new_n1369_ $abc$8517$new_n707_ $abc$8517$new_n703_ $abc$8517$new_n1399_ $abc$8517$new_n549_ bus_address[8] -00000 1 -00001 1 -00010 1 -00011 1 -00100 1 -00101 1 -00110 1 -00111 1 -01000 1 -01001 1 -01010 1 -01011 1 -01100 1 -01101 1 -01110 1 -01111 1 -10011 1 -10100 1 -10101 1 -10110 1 -10111 1 -11000 1 -11001 1 -11010 1 -11011 1 -11100 1 -11101 1 -11110 1 -11111 1 -.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][0] singlecycle_datapath.program_counter.value[0] re clock 2 -.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][1] singlecycle_datapath.program_counter.value[1] re clock 2 -.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][2] singlecycle_datapath.program_counter.value[2] re clock 2 -.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][3] singlecycle_datapath.program_counter.value[3] re clock 2 -.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][4] singlecycle_datapath.program_counter.value[4] re clock 2 -.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][5] singlecycle_datapath.program_counter.value[5] re clock 2 -.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][6] singlecycle_datapath.program_counter.value[6] re clock 2 -.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][7] singlecycle_datapath.program_counter.value[7] re clock 2 -.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][8] singlecycle_datapath.program_counter.value[8] re clock 2 -.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][9] singlecycle_datapath.program_counter.value[9] re clock 2 -.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][10] singlecycle_datapath.program_counter.value[10] re clock 2 -.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][11] singlecycle_datapath.program_counter.value[11] re clock 2 -.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][12] singlecycle_datapath.program_counter.value[12] re clock 2 -.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][13] singlecycle_datapath.program_counter.value[13] re clock 2 -.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][14] singlecycle_datapath.program_counter.value[14] re clock 2 -.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][15] singlecycle_datapath.program_counter.value[15] re clock 2 -.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][16] singlecycle_datapath.program_counter.value[16] re clock 2 -.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][17] singlecycle_datapath.program_counter.value[17] re clock 2 -.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][18] singlecycle_datapath.program_counter.value[18] re clock 2 -.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][19] singlecycle_datapath.program_counter.value[19] re clock 2 -.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][20] singlecycle_datapath.program_counter.value[20] re clock 2 -.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][21] singlecycle_datapath.program_counter.value[21] re clock 2 -.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][22] singlecycle_datapath.program_counter.value[22] re clock 2 -.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][23] singlecycle_datapath.program_counter.value[23] re clock 2 -.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][24] singlecycle_datapath.program_counter.value[24] re clock 2 -.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][25] singlecycle_datapath.program_counter.value[25] re clock 2 -.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][26] singlecycle_datapath.program_counter.value[26] re clock 2 -.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][27] singlecycle_datapath.program_counter.value[27] re clock 2 -.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][28] singlecycle_datapath.program_counter.value[28] re clock 2 -.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][29] singlecycle_datapath.program_counter.value[29] re clock 2 -.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][30] singlecycle_datapath.program_counter.value[30] re clock 2 -.latch $abc$8517$techmap\singlecycle_datapath.program_counter.$0\value[31:0][31] singlecycle_datapath.program_counter.value[31] re clock 2 -.names bus_address[0] address[0] -1 1 -.names bus_address[1] address[1] -1 1 -.names bus_address[2] address[2] -1 1 -.names bus_address[3] address[3] -1 1 -.names bus_address[4] address[4] -1 1 -.names bus_address[5] address[5] -1 1 -.names bus_address[6] address[6] -1 1 -.names bus_address[7] address[7] -1 1 -.names bus_address[8] address[8] -1 1 -.names bus_address[9] address[9] -1 1 -.names bus_address[10] address[10] -1 1 -.names bus_address[11] address[11] -1 1 -.names bus_address[12] address[12] -1 1 -.names bus_address[13] address[13] -1 1 -.names bus_address[14] address[14] -1 1 -.names bus_address[15] address[15] -1 1 -.names bus_address[16] address[16] -1 1 -.names bus_address[17] address[17] -1 1 -.names bus_address[18] address[18] -1 1 -.names bus_address[19] address[19] -1 1 -.names bus_address[20] address[20] -1 1 -.names bus_address[21] address[21] -1 1 -.names bus_address[22] address[22] -1 1 -.names bus_address[23] address[23] -1 1 -.names bus_address[24] address[24] -1 1 -.names bus_address[25] address[25] -1 1 -.names bus_address[26] address[26] -1 1 -.names bus_address[27] address[27] -1 1 -.names bus_address[28] address[28] -1 1 -.names bus_address[29] address[29] -1 1 -.names bus_address[30] address[30] -1 1 -.names bus_address[31] address[31] -1 1 -.names $false alu_function[4] -1 1 -.names bus_address[0] data_memory_interface.address[0] -1 1 -.names bus_address[1] data_memory_interface.address[1] -1 1 -.names bus_address[2] data_memory_interface.address[2] -1 1 -.names bus_address[3] data_memory_interface.address[3] -1 1 -.names bus_address[4] data_memory_interface.address[4] -1 1 -.names bus_address[5] data_memory_interface.address[5] -1 1 -.names bus_address[6] data_memory_interface.address[6] -1 1 -.names bus_address[7] data_memory_interface.address[7] -1 1 -.names bus_address[8] data_memory_interface.address[8] -1 1 -.names bus_address[9] data_memory_interface.address[9] -1 1 -.names bus_address[10] data_memory_interface.address[10] -1 1 -.names bus_address[11] data_memory_interface.address[11] -1 1 -.names bus_address[12] data_memory_interface.address[12] -1 1 -.names bus_address[13] data_memory_interface.address[13] -1 1 -.names bus_address[14] data_memory_interface.address[14] -1 1 -.names bus_address[15] data_memory_interface.address[15] -1 1 -.names bus_address[16] data_memory_interface.address[16] -1 1 -.names bus_address[17] data_memory_interface.address[17] -1 1 -.names bus_address[18] data_memory_interface.address[18] -1 1 -.names bus_address[19] data_memory_interface.address[19] -1 1 -.names bus_address[20] data_memory_interface.address[20] -1 1 -.names bus_address[21] data_memory_interface.address[21] -1 1 -.names bus_address[22] data_memory_interface.address[22] -1 1 -.names bus_address[23] data_memory_interface.address[23] -1 1 -.names bus_address[24] data_memory_interface.address[24] -1 1 -.names bus_address[25] data_memory_interface.address[25] -1 1 -.names bus_address[26] data_memory_interface.address[26] -1 1 -.names bus_address[27] data_memory_interface.address[27] -1 1 -.names bus_address[28] data_memory_interface.address[28] -1 1 -.names bus_address[29] data_memory_interface.address[29] -1 1 -.names bus_address[30] data_memory_interface.address[30] -1 1 -.names bus_address[31] data_memory_interface.address[31] -1 1 -.names bus_address[0] data_memory_interface.bus_address[0] -1 1 -.names bus_address[1] data_memory_interface.bus_address[1] -1 1 -.names bus_address[2] data_memory_interface.bus_address[2] -1 1 -.names bus_address[3] data_memory_interface.bus_address[3] -1 1 -.names bus_address[4] data_memory_interface.bus_address[4] -1 1 -.names bus_address[5] data_memory_interface.bus_address[5] -1 1 -.names bus_address[6] data_memory_interface.bus_address[6] -1 1 -.names bus_address[7] data_memory_interface.bus_address[7] -1 1 -.names bus_address[8] data_memory_interface.bus_address[8] -1 1 -.names bus_address[9] data_memory_interface.bus_address[9] -1 1 -.names bus_address[10] data_memory_interface.bus_address[10] -1 1 -.names bus_address[11] data_memory_interface.bus_address[11] -1 1 -.names bus_address[12] data_memory_interface.bus_address[12] -1 1 -.names bus_address[13] data_memory_interface.bus_address[13] -1 1 -.names bus_address[14] data_memory_interface.bus_address[14] -1 1 -.names bus_address[15] data_memory_interface.bus_address[15] -1 1 -.names bus_address[16] data_memory_interface.bus_address[16] -1 1 -.names bus_address[17] data_memory_interface.bus_address[17] -1 1 -.names bus_address[18] data_memory_interface.bus_address[18] -1 1 -.names bus_address[19] data_memory_interface.bus_address[19] -1 1 -.names bus_address[20] data_memory_interface.bus_address[20] -1 1 -.names bus_address[21] data_memory_interface.bus_address[21] -1 1 -.names bus_address[22] data_memory_interface.bus_address[22] -1 1 -.names bus_address[23] data_memory_interface.bus_address[23] -1 1 -.names bus_address[24] data_memory_interface.bus_address[24] -1 1 -.names bus_address[25] data_memory_interface.bus_address[25] -1 1 -.names bus_address[26] data_memory_interface.bus_address[26] -1 1 -.names bus_address[27] data_memory_interface.bus_address[27] -1 1 -.names bus_address[28] data_memory_interface.bus_address[28] -1 1 -.names bus_address[29] data_memory_interface.bus_address[29] -1 1 -.names bus_address[30] data_memory_interface.bus_address[30] -1 1 -.names bus_address[31] data_memory_interface.bus_address[31] -1 1 -.names bus_byte_enable[0] data_memory_interface.bus_byte_enable[0] -1 1 -.names bus_byte_enable[1] data_memory_interface.bus_byte_enable[1] -1 1 -.names bus_byte_enable[2] data_memory_interface.bus_byte_enable[2] -1 1 -.names bus_byte_enable[3] data_memory_interface.bus_byte_enable[3] -1 1 -.names bus_read_data[0] data_memory_interface.bus_read_data[0] -1 1 -.names bus_read_data[1] data_memory_interface.bus_read_data[1] -1 1 -.names bus_read_data[2] data_memory_interface.bus_read_data[2] -1 1 -.names bus_read_data[3] data_memory_interface.bus_read_data[3] -1 1 -.names bus_read_data[4] data_memory_interface.bus_read_data[4] -1 1 -.names bus_read_data[5] data_memory_interface.bus_read_data[5] -1 1 -.names bus_read_data[6] data_memory_interface.bus_read_data[6] -1 1 -.names bus_read_data[7] data_memory_interface.bus_read_data[7] -1 1 -.names bus_read_data[8] data_memory_interface.bus_read_data[8] -1 1 -.names bus_read_data[9] data_memory_interface.bus_read_data[9] -1 1 -.names bus_read_data[10] data_memory_interface.bus_read_data[10] -1 1 -.names bus_read_data[11] data_memory_interface.bus_read_data[11] -1 1 -.names bus_read_data[12] data_memory_interface.bus_read_data[12] -1 1 -.names bus_read_data[13] data_memory_interface.bus_read_data[13] -1 1 -.names bus_read_data[14] data_memory_interface.bus_read_data[14] -1 1 -.names bus_read_data[15] data_memory_interface.bus_read_data[15] -1 1 -.names bus_read_data[16] data_memory_interface.bus_read_data[16] -1 1 -.names bus_read_data[17] data_memory_interface.bus_read_data[17] -1 1 -.names bus_read_data[18] data_memory_interface.bus_read_data[18] -1 1 -.names bus_read_data[19] data_memory_interface.bus_read_data[19] -1 1 -.names bus_read_data[20] data_memory_interface.bus_read_data[20] -1 1 -.names bus_read_data[21] data_memory_interface.bus_read_data[21] -1 1 -.names bus_read_data[22] data_memory_interface.bus_read_data[22] -1 1 -.names bus_read_data[23] data_memory_interface.bus_read_data[23] -1 1 -.names bus_read_data[24] data_memory_interface.bus_read_data[24] -1 1 -.names bus_read_data[25] data_memory_interface.bus_read_data[25] -1 1 -.names bus_read_data[26] data_memory_interface.bus_read_data[26] -1 1 -.names bus_read_data[27] data_memory_interface.bus_read_data[27] -1 1 -.names bus_read_data[28] data_memory_interface.bus_read_data[28] -1 1 -.names bus_read_data[29] data_memory_interface.bus_read_data[29] -1 1 -.names bus_read_data[30] data_memory_interface.bus_read_data[30] -1 1 -.names bus_read_data[31] data_memory_interface.bus_read_data[31] -1 1 -.names bus_read_enable data_memory_interface.bus_read_enable -1 1 -.names bus_write_data[0] data_memory_interface.bus_write_data[0] -1 1 -.names bus_write_data[1] data_memory_interface.bus_write_data[1] -1 1 -.names bus_write_data[2] data_memory_interface.bus_write_data[2] -1 1 -.names bus_write_data[3] data_memory_interface.bus_write_data[3] -1 1 -.names bus_write_data[4] data_memory_interface.bus_write_data[4] -1 1 -.names bus_write_data[5] data_memory_interface.bus_write_data[5] -1 1 -.names bus_write_data[6] data_memory_interface.bus_write_data[6] -1 1 -.names bus_write_data[7] data_memory_interface.bus_write_data[7] -1 1 -.names bus_write_data[8] data_memory_interface.bus_write_data[8] -1 1 -.names bus_write_data[9] data_memory_interface.bus_write_data[9] -1 1 -.names bus_write_data[10] data_memory_interface.bus_write_data[10] -1 1 -.names bus_write_data[11] data_memory_interface.bus_write_data[11] -1 1 -.names bus_write_data[12] data_memory_interface.bus_write_data[12] -1 1 -.names bus_write_data[13] data_memory_interface.bus_write_data[13] -1 1 -.names bus_write_data[14] data_memory_interface.bus_write_data[14] -1 1 -.names bus_write_data[15] data_memory_interface.bus_write_data[15] -1 1 -.names bus_write_data[16] data_memory_interface.bus_write_data[16] -1 1 -.names bus_write_data[17] data_memory_interface.bus_write_data[17] -1 1 -.names bus_write_data[18] data_memory_interface.bus_write_data[18] -1 1 -.names bus_write_data[19] data_memory_interface.bus_write_data[19] -1 1 -.names bus_write_data[20] data_memory_interface.bus_write_data[20] -1 1 -.names bus_write_data[21] data_memory_interface.bus_write_data[21] -1 1 -.names bus_write_data[22] data_memory_interface.bus_write_data[22] -1 1 -.names bus_write_data[23] data_memory_interface.bus_write_data[23] -1 1 -.names bus_write_data[24] data_memory_interface.bus_write_data[24] -1 1 -.names bus_write_data[25] data_memory_interface.bus_write_data[25] -1 1 -.names bus_write_data[26] data_memory_interface.bus_write_data[26] -1 1 -.names bus_write_data[27] data_memory_interface.bus_write_data[27] -1 1 -.names bus_write_data[28] data_memory_interface.bus_write_data[28] -1 1 -.names bus_write_data[29] data_memory_interface.bus_write_data[29] -1 1 -.names bus_write_data[30] data_memory_interface.bus_write_data[30] -1 1 -.names bus_write_data[31] data_memory_interface.bus_write_data[31] -1 1 -.names bus_write_enable data_memory_interface.bus_write_enable -1 1 -.names clock data_memory_interface.clock -1 1 -.names inst[12] data_memory_interface.data_format[0] -1 1 -.names inst[13] data_memory_interface.data_format[1] -1 1 -.names inst[14] data_memory_interface.data_format[2] -1 1 -.names bus_read_enable data_memory_interface.read_enable -1 1 -.names rs2_data[0] data_memory_interface.write_data[0] -1 1 -.names rs2_data[1] data_memory_interface.write_data[1] -1 1 -.names rs2_data[2] data_memory_interface.write_data[2] -1 1 -.names rs2_data[3] data_memory_interface.write_data[3] -1 1 -.names rs2_data[4] data_memory_interface.write_data[4] -1 1 -.names rs2_data[5] data_memory_interface.write_data[5] -1 1 -.names rs2_data[6] data_memory_interface.write_data[6] -1 1 -.names rs2_data[7] data_memory_interface.write_data[7] -1 1 -.names rs2_data[8] data_memory_interface.write_data[8] -1 1 -.names rs2_data[9] data_memory_interface.write_data[9] -1 1 -.names rs2_data[10] data_memory_interface.write_data[10] -1 1 -.names rs2_data[11] data_memory_interface.write_data[11] -1 1 -.names rs2_data[12] data_memory_interface.write_data[12] -1 1 -.names rs2_data[13] data_memory_interface.write_data[13] -1 1 -.names rs2_data[14] data_memory_interface.write_data[14] -1 1 -.names rs2_data[15] data_memory_interface.write_data[15] -1 1 -.names rs2_data[16] data_memory_interface.write_data[16] -1 1 -.names rs2_data[17] data_memory_interface.write_data[17] -1 1 -.names rs2_data[18] data_memory_interface.write_data[18] -1 1 -.names rs2_data[19] data_memory_interface.write_data[19] -1 1 -.names rs2_data[20] data_memory_interface.write_data[20] -1 1 -.names rs2_data[21] data_memory_interface.write_data[21] -1 1 -.names rs2_data[22] data_memory_interface.write_data[22] -1 1 -.names rs2_data[23] data_memory_interface.write_data[23] -1 1 -.names rs2_data[24] data_memory_interface.write_data[24] -1 1 -.names rs2_data[25] data_memory_interface.write_data[25] -1 1 -.names rs2_data[26] data_memory_interface.write_data[26] -1 1 -.names rs2_data[27] data_memory_interface.write_data[27] -1 1 -.names rs2_data[28] data_memory_interface.write_data[28] -1 1 -.names rs2_data[29] data_memory_interface.write_data[29] -1 1 -.names rs2_data[30] data_memory_interface.write_data[30] -1 1 -.names rs2_data[31] data_memory_interface.write_data[31] -1 1 -.names bus_write_enable data_memory_interface.write_enable -1 1 -.names inst[12] inst_funct3[0] -1 1 -.names inst[13] inst_funct3[1] -1 1 -.names inst[14] inst_funct3[2] -1 1 -.names inst[25] inst_funct7[0] -1 1 -.names inst[26] inst_funct7[1] -1 1 -.names inst[27] inst_funct7[2] -1 1 -.names inst[28] inst_funct7[3] -1 1 -.names inst[29] inst_funct7[4] -1 1 -.names inst[30] inst_funct7[5] -1 1 -.names inst[31] inst_funct7[6] -1 1 -.names inst[0] inst_opcode[0] -1 1 -.names inst[1] inst_opcode[1] -1 1 -.names inst[2] inst_opcode[2] -1 1 -.names inst[3] inst_opcode[3] -1 1 -.names inst[4] inst_opcode[4] -1 1 -.names inst[5] inst_opcode[5] -1 1 -.names inst[6] inst_opcode[6] -1 1 -.names singlecycle_datapath.program_counter.value[0] pc[0] -1 1 -.names singlecycle_datapath.program_counter.value[1] pc[1] -1 1 -.names singlecycle_datapath.program_counter.value[2] pc[2] -1 1 -.names singlecycle_datapath.program_counter.value[3] pc[3] -1 1 -.names singlecycle_datapath.program_counter.value[4] pc[4] -1 1 -.names singlecycle_datapath.program_counter.value[5] pc[5] -1 1 -.names singlecycle_datapath.program_counter.value[6] pc[6] -1 1 -.names singlecycle_datapath.program_counter.value[7] pc[7] -1 1 -.names singlecycle_datapath.program_counter.value[8] pc[8] -1 1 -.names singlecycle_datapath.program_counter.value[9] pc[9] -1 1 -.names singlecycle_datapath.program_counter.value[10] pc[10] -1 1 -.names singlecycle_datapath.program_counter.value[11] pc[11] -1 1 -.names singlecycle_datapath.program_counter.value[12] pc[12] -1 1 -.names singlecycle_datapath.program_counter.value[13] pc[13] -1 1 -.names singlecycle_datapath.program_counter.value[14] pc[14] -1 1 -.names singlecycle_datapath.program_counter.value[15] pc[15] -1 1 -.names singlecycle_datapath.program_counter.value[16] pc[16] -1 1 -.names singlecycle_datapath.program_counter.value[17] pc[17] -1 1 -.names singlecycle_datapath.program_counter.value[18] pc[18] -1 1 -.names singlecycle_datapath.program_counter.value[19] pc[19] -1 1 -.names singlecycle_datapath.program_counter.value[20] pc[20] -1 1 -.names singlecycle_datapath.program_counter.value[21] pc[21] -1 1 -.names singlecycle_datapath.program_counter.value[22] pc[22] -1 1 -.names singlecycle_datapath.program_counter.value[23] pc[23] -1 1 -.names singlecycle_datapath.program_counter.value[24] pc[24] -1 1 -.names singlecycle_datapath.program_counter.value[25] pc[25] -1 1 -.names singlecycle_datapath.program_counter.value[26] pc[26] -1 1 -.names singlecycle_datapath.program_counter.value[27] pc[27] -1 1 -.names singlecycle_datapath.program_counter.value[28] pc[28] -1 1 -.names singlecycle_datapath.program_counter.value[29] pc[29] -1 1 -.names singlecycle_datapath.program_counter.value[30] pc[30] -1 1 -.names singlecycle_datapath.program_counter.value[31] pc[31] -1 1 -.names $true pc_write_enable -1 1 -.names inst[7] rd_address[0] -1 1 -.names $false rd_address[1] -1 1 -.names $false rd_address[2] -1 1 -.names $false rd_address[3] -1 1 -.names $false rd_address[4] -1 1 -.names bus_read_enable read_enable -1 1 -.names inst[15] rs1_address[0] -1 1 -.names $false rs1_address[1] -1 1 -.names $false rs1_address[2] -1 1 -.names $false rs1_address[3] -1 1 -.names $false rs1_address[4] -1 1 -.names inst[20] rs2_address[0] -1 1 -.names $false rs2_address[1] -1 1 -.names $false rs2_address[2] -1 1 -.names $false rs2_address[3] -1 1 -.names $false rs2_address[4] -1 1 -.names alu_function[0] singlecycle_ctlpath.alu_control.alu_function[0] -1 1 -.names alu_function[1] singlecycle_ctlpath.alu_control.alu_function[1] -1 1 -.names alu_function[2] singlecycle_ctlpath.alu_control.alu_function[2] -1 1 -.names alu_function[3] singlecycle_ctlpath.alu_control.alu_function[3] -1 1 -.names $false singlecycle_ctlpath.alu_control.alu_function[4] -1 1 -.names singlecycle_ctlpath.alu_control.branch_funct[2] singlecycle_ctlpath.alu_control.branch_funct[1] -1 1 -.names inst[12] singlecycle_ctlpath.alu_control.inst_funct3[0] -1 1 -.names inst[13] singlecycle_ctlpath.alu_control.inst_funct3[1] -1 1 -.names inst[14] singlecycle_ctlpath.alu_control.inst_funct3[2] -1 1 -.names inst[25] singlecycle_ctlpath.alu_control.inst_funct7[0] -1 1 -.names inst[26] singlecycle_ctlpath.alu_control.inst_funct7[1] -1 1 -.names inst[27] singlecycle_ctlpath.alu_control.inst_funct7[2] -1 1 -.names inst[28] singlecycle_ctlpath.alu_control.inst_funct7[3] -1 1 -.names inst[29] singlecycle_ctlpath.alu_control.inst_funct7[4] -1 1 -.names inst[30] singlecycle_ctlpath.alu_control.inst_funct7[5] -1 1 -.names inst[31] singlecycle_ctlpath.alu_control.inst_funct7[6] -1 1 -.names $false singlecycle_ctlpath.alu_control.op_funct[4] -1 1 -.names $false singlecycle_ctlpath.alu_control.op_imm_funct[4] -1 1 -.names alu_function[0] singlecycle_ctlpath.alu_function[0] -1 1 -.names alu_function[1] singlecycle_ctlpath.alu_function[1] -1 1 -.names alu_function[2] singlecycle_ctlpath.alu_function[2] -1 1 -.names alu_function[3] singlecycle_ctlpath.alu_function[3] -1 1 -.names $false singlecycle_ctlpath.alu_function[4] -1 1 -.names inst[12] singlecycle_ctlpath.control_transfer.inst_funct3[0] -1 1 -.names inst[13] singlecycle_ctlpath.control_transfer.inst_funct3[1] -1 1 -.names inst[14] singlecycle_ctlpath.control_transfer.inst_funct3[2] -1 1 -.names bus_read_enable singlecycle_ctlpath.data_mem_read_enable -1 1 -.names bus_write_enable singlecycle_ctlpath.data_mem_write_enable -1 1 -.names inst[12] singlecycle_ctlpath.inst_funct3[0] -1 1 -.names inst[13] singlecycle_ctlpath.inst_funct3[1] -1 1 -.names inst[14] singlecycle_ctlpath.inst_funct3[2] -1 1 -.names inst[25] singlecycle_ctlpath.inst_funct7[0] -1 1 -.names inst[26] singlecycle_ctlpath.inst_funct7[1] -1 1 -.names inst[27] singlecycle_ctlpath.inst_funct7[2] -1 1 -.names inst[28] singlecycle_ctlpath.inst_funct7[3] -1 1 -.names inst[29] singlecycle_ctlpath.inst_funct7[4] -1 1 -.names inst[30] singlecycle_ctlpath.inst_funct7[5] -1 1 -.names inst[31] singlecycle_ctlpath.inst_funct7[6] -1 1 -.names inst[0] singlecycle_ctlpath.inst_opcode[0] -1 1 -.names inst[1] singlecycle_ctlpath.inst_opcode[1] -1 1 -.names inst[2] singlecycle_ctlpath.inst_opcode[2] -1 1 -.names inst[3] singlecycle_ctlpath.inst_opcode[3] -1 1 -.names inst[4] singlecycle_ctlpath.inst_opcode[4] -1 1 -.names inst[5] singlecycle_ctlpath.inst_opcode[5] -1 1 -.names inst[6] singlecycle_ctlpath.inst_opcode[6] -1 1 -.names $true singlecycle_ctlpath.pc_write_enable -1 1 -.names $false singlecycle_ctlpath.reg_writeback_select[2] -1 1 -.names regfile_write_enable singlecycle_ctlpath.regfile_write_enable -1 1 -.names bus_read_enable singlecycle_ctlpath.singlecycle_control.data_mem_read_enable -1 1 -.names bus_write_enable singlecycle_ctlpath.singlecycle_control.data_mem_write_enable -1 1 -.names inst[0] singlecycle_ctlpath.singlecycle_control.inst_opcode[0] -1 1 -.names inst[1] singlecycle_ctlpath.singlecycle_control.inst_opcode[1] -1 1 -.names inst[2] singlecycle_ctlpath.singlecycle_control.inst_opcode[2] -1 1 -.names inst[3] singlecycle_ctlpath.singlecycle_control.inst_opcode[3] -1 1 -.names inst[4] singlecycle_ctlpath.singlecycle_control.inst_opcode[4] -1 1 -.names inst[5] singlecycle_ctlpath.singlecycle_control.inst_opcode[5] -1 1 -.names inst[6] singlecycle_ctlpath.singlecycle_control.inst_opcode[6] -1 1 -.names $true singlecycle_ctlpath.singlecycle_control.pc_write_enable -1 1 -.names $false singlecycle_ctlpath.singlecycle_control.reg_writeback_select[2] -1 1 -.names regfile_write_enable singlecycle_ctlpath.singlecycle_control.regfile_write_enable -1 1 -.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[0] -1 1 -.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[1] -1 1 -.names $true singlecycle_datapath.adder_pc_plus_4.operand_a[2] -1 1 -.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[3] -1 1 -.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[4] -1 1 -.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[5] -1 1 -.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[6] -1 1 -.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[7] -1 1 -.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[8] -1 1 -.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[9] -1 1 -.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[10] -1 1 -.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[11] -1 1 -.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[12] -1 1 -.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[13] -1 1 -.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[14] -1 1 -.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[15] -1 1 -.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[16] -1 1 -.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[17] -1 1 -.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[18] -1 1 -.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[19] -1 1 -.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[20] -1 1 -.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[21] -1 1 -.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[22] -1 1 -.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[23] -1 1 -.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[24] -1 1 -.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[25] -1 1 -.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[26] -1 1 -.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[27] -1 1 -.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[28] -1 1 -.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[29] -1 1 -.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[30] -1 1 -.names $false singlecycle_datapath.adder_pc_plus_4.operand_a[31] -1 1 -.names singlecycle_datapath.program_counter.value[0] singlecycle_datapath.adder_pc_plus_4.operand_b[0] -1 1 -.names singlecycle_datapath.program_counter.value[1] singlecycle_datapath.adder_pc_plus_4.operand_b[1] -1 1 -.names singlecycle_datapath.program_counter.value[2] singlecycle_datapath.adder_pc_plus_4.operand_b[2] -1 1 -.names singlecycle_datapath.program_counter.value[3] singlecycle_datapath.adder_pc_plus_4.operand_b[3] -1 1 -.names singlecycle_datapath.program_counter.value[4] singlecycle_datapath.adder_pc_plus_4.operand_b[4] -1 1 -.names singlecycle_datapath.program_counter.value[5] singlecycle_datapath.adder_pc_plus_4.operand_b[5] -1 1 -.names singlecycle_datapath.program_counter.value[6] singlecycle_datapath.adder_pc_plus_4.operand_b[6] -1 1 -.names singlecycle_datapath.program_counter.value[7] singlecycle_datapath.adder_pc_plus_4.operand_b[7] -1 1 -.names singlecycle_datapath.program_counter.value[8] singlecycle_datapath.adder_pc_plus_4.operand_b[8] -1 1 -.names singlecycle_datapath.program_counter.value[9] singlecycle_datapath.adder_pc_plus_4.operand_b[9] -1 1 -.names singlecycle_datapath.program_counter.value[10] singlecycle_datapath.adder_pc_plus_4.operand_b[10] -1 1 -.names singlecycle_datapath.program_counter.value[11] singlecycle_datapath.adder_pc_plus_4.operand_b[11] -1 1 -.names singlecycle_datapath.program_counter.value[12] singlecycle_datapath.adder_pc_plus_4.operand_b[12] -1 1 -.names singlecycle_datapath.program_counter.value[13] singlecycle_datapath.adder_pc_plus_4.operand_b[13] -1 1 -.names singlecycle_datapath.program_counter.value[14] singlecycle_datapath.adder_pc_plus_4.operand_b[14] -1 1 -.names singlecycle_datapath.program_counter.value[15] singlecycle_datapath.adder_pc_plus_4.operand_b[15] -1 1 -.names singlecycle_datapath.program_counter.value[16] singlecycle_datapath.adder_pc_plus_4.operand_b[16] -1 1 -.names singlecycle_datapath.program_counter.value[17] singlecycle_datapath.adder_pc_plus_4.operand_b[17] -1 1 -.names singlecycle_datapath.program_counter.value[18] singlecycle_datapath.adder_pc_plus_4.operand_b[18] -1 1 -.names singlecycle_datapath.program_counter.value[19] singlecycle_datapath.adder_pc_plus_4.operand_b[19] -1 1 -.names singlecycle_datapath.program_counter.value[20] singlecycle_datapath.adder_pc_plus_4.operand_b[20] -1 1 -.names singlecycle_datapath.program_counter.value[21] singlecycle_datapath.adder_pc_plus_4.operand_b[21] -1 1 -.names singlecycle_datapath.program_counter.value[22] singlecycle_datapath.adder_pc_plus_4.operand_b[22] -1 1 -.names singlecycle_datapath.program_counter.value[23] singlecycle_datapath.adder_pc_plus_4.operand_b[23] -1 1 -.names singlecycle_datapath.program_counter.value[24] singlecycle_datapath.adder_pc_plus_4.operand_b[24] -1 1 -.names singlecycle_datapath.program_counter.value[25] singlecycle_datapath.adder_pc_plus_4.operand_b[25] -1 1 -.names singlecycle_datapath.program_counter.value[26] singlecycle_datapath.adder_pc_plus_4.operand_b[26] -1 1 -.names singlecycle_datapath.program_counter.value[27] singlecycle_datapath.adder_pc_plus_4.operand_b[27] -1 1 -.names singlecycle_datapath.program_counter.value[28] singlecycle_datapath.adder_pc_plus_4.operand_b[28] -1 1 -.names singlecycle_datapath.program_counter.value[29] singlecycle_datapath.adder_pc_plus_4.operand_b[29] -1 1 -.names singlecycle_datapath.program_counter.value[30] singlecycle_datapath.adder_pc_plus_4.operand_b[30] -1 1 -.names singlecycle_datapath.program_counter.value[31] singlecycle_datapath.adder_pc_plus_4.operand_b[31] -1 1 -.names singlecycle_datapath.program_counter.value[0] singlecycle_datapath.adder_pc_plus_4.result[0] -1 1 -.names singlecycle_datapath.program_counter.value[1] singlecycle_datapath.adder_pc_plus_4.result[1] -1 1 -.names singlecycle_datapath.program_counter.value[0] singlecycle_datapath.adder_pc_plus_immediate.operand_a[0] -1 1 -.names singlecycle_datapath.program_counter.value[1] singlecycle_datapath.adder_pc_plus_immediate.operand_a[1] -1 1 -.names singlecycle_datapath.program_counter.value[2] singlecycle_datapath.adder_pc_plus_immediate.operand_a[2] -1 1 -.names singlecycle_datapath.program_counter.value[3] singlecycle_datapath.adder_pc_plus_immediate.operand_a[3] -1 1 -.names singlecycle_datapath.program_counter.value[4] singlecycle_datapath.adder_pc_plus_immediate.operand_a[4] -1 1 -.names singlecycle_datapath.program_counter.value[5] singlecycle_datapath.adder_pc_plus_immediate.operand_a[5] -1 1 -.names singlecycle_datapath.program_counter.value[6] singlecycle_datapath.adder_pc_plus_immediate.operand_a[6] -1 1 -.names singlecycle_datapath.program_counter.value[7] singlecycle_datapath.adder_pc_plus_immediate.operand_a[7] -1 1 -.names singlecycle_datapath.program_counter.value[8] singlecycle_datapath.adder_pc_plus_immediate.operand_a[8] -1 1 -.names singlecycle_datapath.program_counter.value[9] singlecycle_datapath.adder_pc_plus_immediate.operand_a[9] -1 1 -.names singlecycle_datapath.program_counter.value[10] singlecycle_datapath.adder_pc_plus_immediate.operand_a[10] -1 1 -.names singlecycle_datapath.program_counter.value[11] singlecycle_datapath.adder_pc_plus_immediate.operand_a[11] -1 1 -.names singlecycle_datapath.program_counter.value[12] singlecycle_datapath.adder_pc_plus_immediate.operand_a[12] -1 1 -.names singlecycle_datapath.program_counter.value[13] singlecycle_datapath.adder_pc_plus_immediate.operand_a[13] -1 1 -.names singlecycle_datapath.program_counter.value[14] singlecycle_datapath.adder_pc_plus_immediate.operand_a[14] -1 1 -.names singlecycle_datapath.program_counter.value[15] singlecycle_datapath.adder_pc_plus_immediate.operand_a[15] -1 1 -.names singlecycle_datapath.program_counter.value[16] singlecycle_datapath.adder_pc_plus_immediate.operand_a[16] -1 1 -.names singlecycle_datapath.program_counter.value[17] singlecycle_datapath.adder_pc_plus_immediate.operand_a[17] -1 1 -.names singlecycle_datapath.program_counter.value[18] singlecycle_datapath.adder_pc_plus_immediate.operand_a[18] -1 1 -.names singlecycle_datapath.program_counter.value[19] singlecycle_datapath.adder_pc_plus_immediate.operand_a[19] -1 1 -.names singlecycle_datapath.program_counter.value[20] singlecycle_datapath.adder_pc_plus_immediate.operand_a[20] -1 1 -.names singlecycle_datapath.program_counter.value[21] singlecycle_datapath.adder_pc_plus_immediate.operand_a[21] -1 1 -.names singlecycle_datapath.program_counter.value[22] singlecycle_datapath.adder_pc_plus_immediate.operand_a[22] -1 1 -.names singlecycle_datapath.program_counter.value[23] singlecycle_datapath.adder_pc_plus_immediate.operand_a[23] -1 1 -.names singlecycle_datapath.program_counter.value[24] singlecycle_datapath.adder_pc_plus_immediate.operand_a[24] -1 1 -.names singlecycle_datapath.program_counter.value[25] singlecycle_datapath.adder_pc_plus_immediate.operand_a[25] -1 1 -.names singlecycle_datapath.program_counter.value[26] singlecycle_datapath.adder_pc_plus_immediate.operand_a[26] -1 1 -.names singlecycle_datapath.program_counter.value[27] singlecycle_datapath.adder_pc_plus_immediate.operand_a[27] -1 1 -.names singlecycle_datapath.program_counter.value[28] singlecycle_datapath.adder_pc_plus_immediate.operand_a[28] -1 1 -.names singlecycle_datapath.program_counter.value[29] singlecycle_datapath.adder_pc_plus_immediate.operand_a[29] -1 1 -.names singlecycle_datapath.program_counter.value[30] singlecycle_datapath.adder_pc_plus_immediate.operand_a[30] -1 1 -.names singlecycle_datapath.program_counter.value[31] singlecycle_datapath.adder_pc_plus_immediate.operand_a[31] -1 1 -.names alu_function[0] singlecycle_datapath.alu.alu_function[0] -1 1 -.names alu_function[1] singlecycle_datapath.alu.alu_function[1] -1 1 -.names alu_function[2] singlecycle_datapath.alu.alu_function[2] -1 1 -.names alu_function[3] singlecycle_datapath.alu.alu_function[3] -1 1 -.names $false singlecycle_datapath.alu.alu_function[4] -1 1 -.names bus_address[0] singlecycle_datapath.alu.result[0] -1 1 -.names bus_address[1] singlecycle_datapath.alu.result[1] -1 1 -.names bus_address[2] singlecycle_datapath.alu.result[2] -1 1 -.names bus_address[3] singlecycle_datapath.alu.result[3] -1 1 -.names bus_address[4] singlecycle_datapath.alu.result[4] -1 1 -.names bus_address[5] singlecycle_datapath.alu.result[5] -1 1 -.names bus_address[6] singlecycle_datapath.alu.result[6] -1 1 -.names bus_address[7] singlecycle_datapath.alu.result[7] -1 1 -.names bus_address[8] singlecycle_datapath.alu.result[8] -1 1 -.names bus_address[9] singlecycle_datapath.alu.result[9] -1 1 -.names bus_address[10] singlecycle_datapath.alu.result[10] -1 1 -.names bus_address[11] singlecycle_datapath.alu.result[11] -1 1 -.names bus_address[12] singlecycle_datapath.alu.result[12] -1 1 -.names bus_address[13] singlecycle_datapath.alu.result[13] -1 1 -.names bus_address[14] singlecycle_datapath.alu.result[14] -1 1 -.names bus_address[15] singlecycle_datapath.alu.result[15] -1 1 -.names bus_address[16] singlecycle_datapath.alu.result[16] -1 1 -.names bus_address[17] singlecycle_datapath.alu.result[17] -1 1 -.names bus_address[18] singlecycle_datapath.alu.result[18] -1 1 -.names bus_address[19] singlecycle_datapath.alu.result[19] -1 1 -.names bus_address[20] singlecycle_datapath.alu.result[20] -1 1 -.names bus_address[21] singlecycle_datapath.alu.result[21] -1 1 -.names bus_address[22] singlecycle_datapath.alu.result[22] -1 1 -.names bus_address[23] singlecycle_datapath.alu.result[23] -1 1 -.names bus_address[24] singlecycle_datapath.alu.result[24] -1 1 -.names bus_address[25] singlecycle_datapath.alu.result[25] -1 1 -.names bus_address[26] singlecycle_datapath.alu.result[26] -1 1 -.names bus_address[27] singlecycle_datapath.alu.result[27] -1 1 -.names bus_address[28] singlecycle_datapath.alu.result[28] -1 1 -.names bus_address[29] singlecycle_datapath.alu.result[29] -1 1 -.names bus_address[30] singlecycle_datapath.alu.result[30] -1 1 -.names bus_address[31] singlecycle_datapath.alu.result[31] -1 1 -.names alu_function[0] singlecycle_datapath.alu_function[0] -1 1 -.names alu_function[1] singlecycle_datapath.alu_function[1] -1 1 -.names alu_function[2] singlecycle_datapath.alu_function[2] -1 1 -.names alu_function[3] singlecycle_datapath.alu_function[3] -1 1 -.names $false singlecycle_datapath.alu_function[4] -1 1 -.names bus_address[0] singlecycle_datapath.alu_result[0] -1 1 -.names bus_address[1] singlecycle_datapath.alu_result[1] -1 1 -.names bus_address[2] singlecycle_datapath.alu_result[2] -1 1 -.names bus_address[3] singlecycle_datapath.alu_result[3] -1 1 -.names bus_address[4] singlecycle_datapath.alu_result[4] -1 1 -.names bus_address[5] singlecycle_datapath.alu_result[5] -1 1 -.names bus_address[6] singlecycle_datapath.alu_result[6] -1 1 -.names bus_address[7] singlecycle_datapath.alu_result[7] -1 1 -.names bus_address[8] singlecycle_datapath.alu_result[8] -1 1 -.names bus_address[9] singlecycle_datapath.alu_result[9] -1 1 -.names bus_address[10] singlecycle_datapath.alu_result[10] -1 1 -.names bus_address[11] singlecycle_datapath.alu_result[11] -1 1 -.names bus_address[12] singlecycle_datapath.alu_result[12] -1 1 -.names bus_address[13] singlecycle_datapath.alu_result[13] -1 1 -.names bus_address[14] singlecycle_datapath.alu_result[14] -1 1 -.names bus_address[15] singlecycle_datapath.alu_result[15] -1 1 -.names bus_address[16] singlecycle_datapath.alu_result[16] -1 1 -.names bus_address[17] singlecycle_datapath.alu_result[17] -1 1 -.names bus_address[18] singlecycle_datapath.alu_result[18] -1 1 -.names bus_address[19] singlecycle_datapath.alu_result[19] -1 1 -.names bus_address[20] singlecycle_datapath.alu_result[20] -1 1 -.names bus_address[21] singlecycle_datapath.alu_result[21] -1 1 -.names bus_address[22] singlecycle_datapath.alu_result[22] -1 1 -.names bus_address[23] singlecycle_datapath.alu_result[23] -1 1 -.names bus_address[24] singlecycle_datapath.alu_result[24] -1 1 -.names bus_address[25] singlecycle_datapath.alu_result[25] -1 1 -.names bus_address[26] singlecycle_datapath.alu_result[26] -1 1 -.names bus_address[27] singlecycle_datapath.alu_result[27] -1 1 -.names bus_address[28] singlecycle_datapath.alu_result[28] -1 1 -.names bus_address[29] singlecycle_datapath.alu_result[29] -1 1 -.names bus_address[30] singlecycle_datapath.alu_result[30] -1 1 -.names bus_address[31] singlecycle_datapath.alu_result[31] -1 1 -.names clock singlecycle_datapath.clock -1 1 -.names bus_address[0] singlecycle_datapath.data_mem_address[0] -1 1 -.names bus_address[1] singlecycle_datapath.data_mem_address[1] -1 1 -.names bus_address[2] singlecycle_datapath.data_mem_address[2] -1 1 -.names bus_address[3] singlecycle_datapath.data_mem_address[3] -1 1 -.names bus_address[4] singlecycle_datapath.data_mem_address[4] -1 1 -.names bus_address[5] singlecycle_datapath.data_mem_address[5] -1 1 -.names bus_address[6] singlecycle_datapath.data_mem_address[6] -1 1 -.names bus_address[7] singlecycle_datapath.data_mem_address[7] -1 1 -.names bus_address[8] singlecycle_datapath.data_mem_address[8] -1 1 -.names bus_address[9] singlecycle_datapath.data_mem_address[9] -1 1 -.names bus_address[10] singlecycle_datapath.data_mem_address[10] -1 1 -.names bus_address[11] singlecycle_datapath.data_mem_address[11] -1 1 -.names bus_address[12] singlecycle_datapath.data_mem_address[12] -1 1 -.names bus_address[13] singlecycle_datapath.data_mem_address[13] -1 1 -.names bus_address[14] singlecycle_datapath.data_mem_address[14] -1 1 -.names bus_address[15] singlecycle_datapath.data_mem_address[15] -1 1 -.names bus_address[16] singlecycle_datapath.data_mem_address[16] -1 1 -.names bus_address[17] singlecycle_datapath.data_mem_address[17] -1 1 -.names bus_address[18] singlecycle_datapath.data_mem_address[18] -1 1 -.names bus_address[19] singlecycle_datapath.data_mem_address[19] -1 1 -.names bus_address[20] singlecycle_datapath.data_mem_address[20] -1 1 -.names bus_address[21] singlecycle_datapath.data_mem_address[21] -1 1 -.names bus_address[22] singlecycle_datapath.data_mem_address[22] -1 1 -.names bus_address[23] singlecycle_datapath.data_mem_address[23] -1 1 -.names bus_address[24] singlecycle_datapath.data_mem_address[24] -1 1 -.names bus_address[25] singlecycle_datapath.data_mem_address[25] -1 1 -.names bus_address[26] singlecycle_datapath.data_mem_address[26] -1 1 -.names bus_address[27] singlecycle_datapath.data_mem_address[27] -1 1 -.names bus_address[28] singlecycle_datapath.data_mem_address[28] -1 1 -.names bus_address[29] singlecycle_datapath.data_mem_address[29] -1 1 -.names bus_address[30] singlecycle_datapath.data_mem_address[30] -1 1 -.names bus_address[31] singlecycle_datapath.data_mem_address[31] -1 1 -.names rs2_data[0] singlecycle_datapath.data_mem_write_data[0] -1 1 -.names rs2_data[1] singlecycle_datapath.data_mem_write_data[1] -1 1 -.names rs2_data[2] singlecycle_datapath.data_mem_write_data[2] -1 1 -.names rs2_data[3] singlecycle_datapath.data_mem_write_data[3] -1 1 -.names rs2_data[4] singlecycle_datapath.data_mem_write_data[4] -1 1 -.names rs2_data[5] singlecycle_datapath.data_mem_write_data[5] -1 1 -.names rs2_data[6] singlecycle_datapath.data_mem_write_data[6] -1 1 -.names rs2_data[7] singlecycle_datapath.data_mem_write_data[7] -1 1 -.names rs2_data[8] singlecycle_datapath.data_mem_write_data[8] -1 1 -.names rs2_data[9] singlecycle_datapath.data_mem_write_data[9] -1 1 -.names rs2_data[10] singlecycle_datapath.data_mem_write_data[10] -1 1 -.names rs2_data[11] singlecycle_datapath.data_mem_write_data[11] -1 1 -.names rs2_data[12] singlecycle_datapath.data_mem_write_data[12] -1 1 -.names rs2_data[13] singlecycle_datapath.data_mem_write_data[13] -1 1 -.names rs2_data[14] singlecycle_datapath.data_mem_write_data[14] -1 1 -.names rs2_data[15] singlecycle_datapath.data_mem_write_data[15] -1 1 -.names rs2_data[16] singlecycle_datapath.data_mem_write_data[16] -1 1 -.names rs2_data[17] singlecycle_datapath.data_mem_write_data[17] -1 1 -.names rs2_data[18] singlecycle_datapath.data_mem_write_data[18] -1 1 -.names rs2_data[19] singlecycle_datapath.data_mem_write_data[19] -1 1 -.names rs2_data[20] singlecycle_datapath.data_mem_write_data[20] -1 1 -.names rs2_data[21] singlecycle_datapath.data_mem_write_data[21] -1 1 -.names rs2_data[22] singlecycle_datapath.data_mem_write_data[22] -1 1 -.names rs2_data[23] singlecycle_datapath.data_mem_write_data[23] -1 1 -.names rs2_data[24] singlecycle_datapath.data_mem_write_data[24] -1 1 -.names rs2_data[25] singlecycle_datapath.data_mem_write_data[25] -1 1 -.names rs2_data[26] singlecycle_datapath.data_mem_write_data[26] -1 1 -.names rs2_data[27] singlecycle_datapath.data_mem_write_data[27] -1 1 -.names rs2_data[28] singlecycle_datapath.data_mem_write_data[28] -1 1 -.names rs2_data[29] singlecycle_datapath.data_mem_write_data[29] -1 1 -.names rs2_data[30] singlecycle_datapath.data_mem_write_data[30] -1 1 -.names rs2_data[31] singlecycle_datapath.data_mem_write_data[31] -1 1 -.names inst[0] singlecycle_datapath.immediate_generator.inst[0] -1 1 -.names inst[1] singlecycle_datapath.immediate_generator.inst[1] -1 1 -.names inst[2] singlecycle_datapath.immediate_generator.inst[2] -1 1 -.names inst[3] singlecycle_datapath.immediate_generator.inst[3] -1 1 -.names inst[4] singlecycle_datapath.immediate_generator.inst[4] -1 1 -.names inst[5] singlecycle_datapath.immediate_generator.inst[5] -1 1 -.names inst[6] singlecycle_datapath.immediate_generator.inst[6] -1 1 -.names inst[7] singlecycle_datapath.immediate_generator.inst[7] -1 1 -.names inst[8] singlecycle_datapath.immediate_generator.inst[8] -1 1 -.names inst[9] singlecycle_datapath.immediate_generator.inst[9] -1 1 -.names inst[10] singlecycle_datapath.immediate_generator.inst[10] -1 1 -.names inst[11] singlecycle_datapath.immediate_generator.inst[11] -1 1 -.names inst[12] singlecycle_datapath.immediate_generator.inst[12] -1 1 -.names inst[13] singlecycle_datapath.immediate_generator.inst[13] -1 1 -.names inst[14] singlecycle_datapath.immediate_generator.inst[14] -1 1 -.names inst[15] singlecycle_datapath.immediate_generator.inst[15] -1 1 -.names inst[16] singlecycle_datapath.immediate_generator.inst[16] -1 1 -.names inst[17] singlecycle_datapath.immediate_generator.inst[17] -1 1 -.names inst[18] singlecycle_datapath.immediate_generator.inst[18] -1 1 -.names inst[19] singlecycle_datapath.immediate_generator.inst[19] -1 1 -.names inst[20] singlecycle_datapath.immediate_generator.inst[20] -1 1 -.names inst[21] singlecycle_datapath.immediate_generator.inst[21] -1 1 -.names inst[22] singlecycle_datapath.immediate_generator.inst[22] -1 1 -.names inst[23] singlecycle_datapath.immediate_generator.inst[23] -1 1 -.names inst[24] singlecycle_datapath.immediate_generator.inst[24] -1 1 -.names inst[25] singlecycle_datapath.immediate_generator.inst[25] -1 1 -.names inst[26] singlecycle_datapath.immediate_generator.inst[26] -1 1 -.names inst[27] singlecycle_datapath.immediate_generator.inst[27] -1 1 -.names inst[28] singlecycle_datapath.immediate_generator.inst[28] -1 1 -.names inst[29] singlecycle_datapath.immediate_generator.inst[29] -1 1 -.names inst[30] singlecycle_datapath.immediate_generator.inst[30] -1 1 -.names inst[31] singlecycle_datapath.immediate_generator.inst[31] -1 1 -.names inst[0] singlecycle_datapath.inst[0] -1 1 -.names inst[1] singlecycle_datapath.inst[1] -1 1 -.names inst[2] singlecycle_datapath.inst[2] -1 1 -.names inst[3] singlecycle_datapath.inst[3] -1 1 -.names inst[4] singlecycle_datapath.inst[4] -1 1 -.names inst[5] singlecycle_datapath.inst[5] -1 1 -.names inst[6] singlecycle_datapath.inst[6] -1 1 -.names inst[7] singlecycle_datapath.inst[7] -1 1 -.names inst[8] singlecycle_datapath.inst[8] -1 1 -.names inst[9] singlecycle_datapath.inst[9] -1 1 -.names inst[10] singlecycle_datapath.inst[10] -1 1 -.names inst[11] singlecycle_datapath.inst[11] -1 1 -.names inst[12] singlecycle_datapath.inst[12] -1 1 -.names inst[13] singlecycle_datapath.inst[13] -1 1 -.names inst[14] singlecycle_datapath.inst[14] -1 1 -.names inst[15] singlecycle_datapath.inst[15] -1 1 -.names inst[16] singlecycle_datapath.inst[16] -1 1 -.names inst[17] singlecycle_datapath.inst[17] -1 1 -.names inst[18] singlecycle_datapath.inst[18] -1 1 -.names inst[19] singlecycle_datapath.inst[19] -1 1 -.names inst[20] singlecycle_datapath.inst[20] -1 1 -.names inst[21] singlecycle_datapath.inst[21] -1 1 -.names inst[22] singlecycle_datapath.inst[22] -1 1 -.names inst[23] singlecycle_datapath.inst[23] -1 1 -.names inst[24] singlecycle_datapath.inst[24] -1 1 -.names inst[25] singlecycle_datapath.inst[25] -1 1 -.names inst[26] singlecycle_datapath.inst[26] -1 1 -.names inst[27] singlecycle_datapath.inst[27] -1 1 -.names inst[28] singlecycle_datapath.inst[28] -1 1 -.names inst[29] singlecycle_datapath.inst[29] -1 1 -.names inst[30] singlecycle_datapath.inst[30] -1 1 -.names inst[31] singlecycle_datapath.inst[31] -1 1 -.names inst[12] singlecycle_datapath.inst_funct3[0] -1 1 -.names inst[13] singlecycle_datapath.inst_funct3[1] -1 1 -.names inst[14] singlecycle_datapath.inst_funct3[2] -1 1 -.names inst[25] singlecycle_datapath.inst_funct7[0] -1 1 -.names inst[26] singlecycle_datapath.inst_funct7[1] -1 1 -.names inst[27] singlecycle_datapath.inst_funct7[2] -1 1 -.names inst[28] singlecycle_datapath.inst_funct7[3] -1 1 -.names inst[29] singlecycle_datapath.inst_funct7[4] -1 1 -.names inst[30] singlecycle_datapath.inst_funct7[5] -1 1 -.names inst[31] singlecycle_datapath.inst_funct7[6] -1 1 -.names inst[0] singlecycle_datapath.inst_opcode[0] -1 1 -.names inst[1] singlecycle_datapath.inst_opcode[1] -1 1 -.names inst[2] singlecycle_datapath.inst_opcode[2] -1 1 -.names inst[3] singlecycle_datapath.inst_opcode[3] -1 1 -.names inst[4] singlecycle_datapath.inst_opcode[4] -1 1 -.names inst[5] singlecycle_datapath.inst_opcode[5] -1 1 -.names inst[6] singlecycle_datapath.inst_opcode[6] -1 1 -.names inst[7] singlecycle_datapath.inst_rd -1 1 -.names inst[15] singlecycle_datapath.inst_rs1 -1 1 -.names inst[20] singlecycle_datapath.inst_rs2 -1 1 -.names inst[0] singlecycle_datapath.instruction_decoder.inst[0] -1 1 -.names inst[1] singlecycle_datapath.instruction_decoder.inst[1] -1 1 -.names inst[2] singlecycle_datapath.instruction_decoder.inst[2] -1 1 -.names inst[3] singlecycle_datapath.instruction_decoder.inst[3] -1 1 -.names inst[4] singlecycle_datapath.instruction_decoder.inst[4] -1 1 -.names inst[5] singlecycle_datapath.instruction_decoder.inst[5] -1 1 -.names inst[6] singlecycle_datapath.instruction_decoder.inst[6] -1 1 -.names inst[7] singlecycle_datapath.instruction_decoder.inst[7] -1 1 -.names inst[8] singlecycle_datapath.instruction_decoder.inst[8] -1 1 -.names inst[9] singlecycle_datapath.instruction_decoder.inst[9] -1 1 -.names inst[10] singlecycle_datapath.instruction_decoder.inst[10] -1 1 -.names inst[11] singlecycle_datapath.instruction_decoder.inst[11] -1 1 -.names inst[12] singlecycle_datapath.instruction_decoder.inst[12] -1 1 -.names inst[13] singlecycle_datapath.instruction_decoder.inst[13] -1 1 -.names inst[14] singlecycle_datapath.instruction_decoder.inst[14] -1 1 -.names inst[15] singlecycle_datapath.instruction_decoder.inst[15] -1 1 -.names inst[16] singlecycle_datapath.instruction_decoder.inst[16] -1 1 -.names inst[17] singlecycle_datapath.instruction_decoder.inst[17] -1 1 -.names inst[18] singlecycle_datapath.instruction_decoder.inst[18] -1 1 -.names inst[19] singlecycle_datapath.instruction_decoder.inst[19] -1 1 -.names inst[20] singlecycle_datapath.instruction_decoder.inst[20] -1 1 -.names inst[21] singlecycle_datapath.instruction_decoder.inst[21] -1 1 -.names inst[22] singlecycle_datapath.instruction_decoder.inst[22] -1 1 -.names inst[23] singlecycle_datapath.instruction_decoder.inst[23] -1 1 -.names inst[24] singlecycle_datapath.instruction_decoder.inst[24] -1 1 -.names inst[25] singlecycle_datapath.instruction_decoder.inst[25] -1 1 -.names inst[26] singlecycle_datapath.instruction_decoder.inst[26] -1 1 -.names inst[27] singlecycle_datapath.instruction_decoder.inst[27] -1 1 -.names inst[28] singlecycle_datapath.instruction_decoder.inst[28] -1 1 -.names inst[29] singlecycle_datapath.instruction_decoder.inst[29] -1 1 -.names inst[30] singlecycle_datapath.instruction_decoder.inst[30] -1 1 -.names inst[31] singlecycle_datapath.instruction_decoder.inst[31] -1 1 -.names inst[12] singlecycle_datapath.instruction_decoder.inst_funct3[0] -1 1 -.names inst[13] singlecycle_datapath.instruction_decoder.inst_funct3[1] -1 1 -.names inst[14] singlecycle_datapath.instruction_decoder.inst_funct3[2] -1 1 -.names inst[25] singlecycle_datapath.instruction_decoder.inst_funct7[0] -1 1 -.names inst[26] singlecycle_datapath.instruction_decoder.inst_funct7[1] -1 1 -.names inst[27] singlecycle_datapath.instruction_decoder.inst_funct7[2] -1 1 -.names inst[28] singlecycle_datapath.instruction_decoder.inst_funct7[3] -1 1 -.names inst[29] singlecycle_datapath.instruction_decoder.inst_funct7[4] -1 1 -.names inst[30] singlecycle_datapath.instruction_decoder.inst_funct7[5] -1 1 -.names inst[31] singlecycle_datapath.instruction_decoder.inst_funct7[6] -1 1 -.names inst[0] singlecycle_datapath.instruction_decoder.inst_opcode[0] -1 1 -.names inst[1] singlecycle_datapath.instruction_decoder.inst_opcode[1] -1 1 -.names inst[2] singlecycle_datapath.instruction_decoder.inst_opcode[2] -1 1 -.names inst[3] singlecycle_datapath.instruction_decoder.inst_opcode[3] -1 1 -.names inst[4] singlecycle_datapath.instruction_decoder.inst_opcode[4] -1 1 -.names inst[5] singlecycle_datapath.instruction_decoder.inst_opcode[5] -1 1 -.names inst[6] singlecycle_datapath.instruction_decoder.inst_opcode[6] -1 1 -.names inst[7] singlecycle_datapath.instruction_decoder.inst_rd[0] -1 1 -.names inst[8] singlecycle_datapath.instruction_decoder.inst_rd[1] -1 1 -.names inst[9] singlecycle_datapath.instruction_decoder.inst_rd[2] -1 1 -.names inst[10] singlecycle_datapath.instruction_decoder.inst_rd[3] -1 1 -.names inst[11] singlecycle_datapath.instruction_decoder.inst_rd[4] -1 1 -.names inst[15] singlecycle_datapath.instruction_decoder.inst_rs1[0] -1 1 -.names inst[16] singlecycle_datapath.instruction_decoder.inst_rs1[1] -1 1 -.names inst[17] singlecycle_datapath.instruction_decoder.inst_rs1[2] -1 1 -.names inst[18] singlecycle_datapath.instruction_decoder.inst_rs1[3] -1 1 -.names inst[19] singlecycle_datapath.instruction_decoder.inst_rs1[4] -1 1 -.names inst[20] singlecycle_datapath.instruction_decoder.inst_rs2[0] -1 1 -.names inst[21] singlecycle_datapath.instruction_decoder.inst_rs2[1] -1 1 -.names inst[22] singlecycle_datapath.instruction_decoder.inst_rs2[2] -1 1 -.names inst[23] singlecycle_datapath.instruction_decoder.inst_rs2[3] -1 1 -.names inst[24] singlecycle_datapath.instruction_decoder.inst_rs2[4] -1 1 -.names singlecycle_datapath.program_counter.value[0] singlecycle_datapath.mux_next_pc_select.in0[0] -1 1 -.names singlecycle_datapath.program_counter.value[1] singlecycle_datapath.mux_next_pc_select.in0[1] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[2] singlecycle_datapath.mux_next_pc_select.in0[2] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[3] singlecycle_datapath.mux_next_pc_select.in0[3] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[4] singlecycle_datapath.mux_next_pc_select.in0[4] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[5] singlecycle_datapath.mux_next_pc_select.in0[5] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[6] singlecycle_datapath.mux_next_pc_select.in0[6] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[7] singlecycle_datapath.mux_next_pc_select.in0[7] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[8] singlecycle_datapath.mux_next_pc_select.in0[8] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[9] singlecycle_datapath.mux_next_pc_select.in0[9] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[10] singlecycle_datapath.mux_next_pc_select.in0[10] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[11] singlecycle_datapath.mux_next_pc_select.in0[11] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[12] singlecycle_datapath.mux_next_pc_select.in0[12] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[13] singlecycle_datapath.mux_next_pc_select.in0[13] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[14] singlecycle_datapath.mux_next_pc_select.in0[14] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[15] singlecycle_datapath.mux_next_pc_select.in0[15] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[16] singlecycle_datapath.mux_next_pc_select.in0[16] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[17] singlecycle_datapath.mux_next_pc_select.in0[17] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[18] singlecycle_datapath.mux_next_pc_select.in0[18] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[19] singlecycle_datapath.mux_next_pc_select.in0[19] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[20] singlecycle_datapath.mux_next_pc_select.in0[20] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[21] singlecycle_datapath.mux_next_pc_select.in0[21] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[22] singlecycle_datapath.mux_next_pc_select.in0[22] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[23] singlecycle_datapath.mux_next_pc_select.in0[23] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[24] singlecycle_datapath.mux_next_pc_select.in0[24] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[25] singlecycle_datapath.mux_next_pc_select.in0[25] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[26] singlecycle_datapath.mux_next_pc_select.in0[26] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[27] singlecycle_datapath.mux_next_pc_select.in0[27] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[28] singlecycle_datapath.mux_next_pc_select.in0[28] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[29] singlecycle_datapath.mux_next_pc_select.in0[29] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[30] singlecycle_datapath.mux_next_pc_select.in0[30] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[31] singlecycle_datapath.mux_next_pc_select.in0[31] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.in2[0] -1 1 -.names bus_address[1] singlecycle_datapath.mux_next_pc_select.in2[1] -1 1 -.names bus_address[2] singlecycle_datapath.mux_next_pc_select.in2[2] -1 1 -.names bus_address[3] singlecycle_datapath.mux_next_pc_select.in2[3] -1 1 -.names bus_address[4] singlecycle_datapath.mux_next_pc_select.in2[4] -1 1 -.names bus_address[5] singlecycle_datapath.mux_next_pc_select.in2[5] -1 1 -.names bus_address[6] singlecycle_datapath.mux_next_pc_select.in2[6] -1 1 -.names bus_address[7] singlecycle_datapath.mux_next_pc_select.in2[7] -1 1 -.names bus_address[8] singlecycle_datapath.mux_next_pc_select.in2[8] -1 1 -.names bus_address[9] singlecycle_datapath.mux_next_pc_select.in2[9] -1 1 -.names bus_address[10] singlecycle_datapath.mux_next_pc_select.in2[10] -1 1 -.names bus_address[11] singlecycle_datapath.mux_next_pc_select.in2[11] -1 1 -.names bus_address[12] singlecycle_datapath.mux_next_pc_select.in2[12] -1 1 -.names bus_address[13] singlecycle_datapath.mux_next_pc_select.in2[13] -1 1 -.names bus_address[14] singlecycle_datapath.mux_next_pc_select.in2[14] -1 1 -.names bus_address[15] singlecycle_datapath.mux_next_pc_select.in2[15] -1 1 -.names bus_address[16] singlecycle_datapath.mux_next_pc_select.in2[16] -1 1 -.names bus_address[17] singlecycle_datapath.mux_next_pc_select.in2[17] -1 1 -.names bus_address[18] singlecycle_datapath.mux_next_pc_select.in2[18] -1 1 -.names bus_address[19] singlecycle_datapath.mux_next_pc_select.in2[19] -1 1 -.names bus_address[20] singlecycle_datapath.mux_next_pc_select.in2[20] -1 1 -.names bus_address[21] singlecycle_datapath.mux_next_pc_select.in2[21] -1 1 -.names bus_address[22] singlecycle_datapath.mux_next_pc_select.in2[22] -1 1 -.names bus_address[23] singlecycle_datapath.mux_next_pc_select.in2[23] -1 1 -.names bus_address[24] singlecycle_datapath.mux_next_pc_select.in2[24] -1 1 -.names bus_address[25] singlecycle_datapath.mux_next_pc_select.in2[25] -1 1 -.names bus_address[26] singlecycle_datapath.mux_next_pc_select.in2[26] -1 1 -.names bus_address[27] singlecycle_datapath.mux_next_pc_select.in2[27] -1 1 -.names bus_address[28] singlecycle_datapath.mux_next_pc_select.in2[28] -1 1 -.names bus_address[29] singlecycle_datapath.mux_next_pc_select.in2[29] -1 1 -.names bus_address[30] singlecycle_datapath.mux_next_pc_select.in2[30] -1 1 -.names bus_address[31] singlecycle_datapath.mux_next_pc_select.in2[31] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.in3[0] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.in3[1] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.in3[2] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.in3[3] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.in3[4] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.in3[5] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.in3[6] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.in3[7] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.in3[8] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.in3[9] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.in3[10] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.in3[11] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.in3[12] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.in3[13] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.in3[14] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.in3[15] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.in3[16] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.in3[17] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.in3[18] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.in3[19] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.in3[20] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.in3[21] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.in3[22] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.in3[23] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.in3[24] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.in3[25] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.in3[26] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.in3[27] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.in3[28] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.in3[29] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.in3[30] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.in3[31] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[0] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[1] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[2] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[3] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[4] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[5] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[6] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[7] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[8] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[9] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[10] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[11] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[12] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[13] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[14] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[15] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[16] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[17] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[18] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[19] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[20] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[21] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[22] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[23] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[24] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[25] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[26] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[27] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[28] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[29] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[30] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[31] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[32] -1 1 -.names bus_address[1] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[33] -1 1 -.names bus_address[2] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[34] -1 1 -.names bus_address[3] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[35] -1 1 -.names bus_address[4] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[36] -1 1 -.names bus_address[5] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[37] -1 1 -.names bus_address[6] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[38] -1 1 -.names bus_address[7] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[39] -1 1 -.names bus_address[8] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[40] -1 1 -.names bus_address[9] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[41] -1 1 -.names bus_address[10] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[42] -1 1 -.names bus_address[11] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[43] -1 1 -.names bus_address[12] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[44] -1 1 -.names bus_address[13] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[45] -1 1 -.names bus_address[14] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[46] -1 1 -.names bus_address[15] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[47] -1 1 -.names bus_address[16] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[48] -1 1 -.names bus_address[17] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[49] -1 1 -.names bus_address[18] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[50] -1 1 -.names bus_address[19] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[51] -1 1 -.names bus_address[20] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[52] -1 1 -.names bus_address[21] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[53] -1 1 -.names bus_address[22] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[54] -1 1 -.names bus_address[23] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[55] -1 1 -.names bus_address[24] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[56] -1 1 -.names bus_address[25] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[57] -1 1 -.names bus_address[26] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[58] -1 1 -.names bus_address[27] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[59] -1 1 -.names bus_address[28] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[60] -1 1 -.names bus_address[29] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[61] -1 1 -.names bus_address[30] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[62] -1 1 -.names bus_address[31] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[63] -1 1 -.names singlecycle_datapath.program_counter.value[0] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[96] -1 1 -.names singlecycle_datapath.program_counter.value[1] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[97] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[2] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[98] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[3] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[99] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[4] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[100] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[5] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[101] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[6] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[102] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[7] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[103] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[8] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[104] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[9] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[105] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[10] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[106] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[11] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[107] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[12] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[108] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[13] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[109] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[14] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[110] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[15] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[111] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[16] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[112] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[17] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[113] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[18] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[114] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[19] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[115] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[20] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[116] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[21] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[117] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[22] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[118] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[23] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[119] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[24] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[120] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[25] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[121] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[26] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[122] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[27] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[123] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[28] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[124] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[29] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[125] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[30] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[126] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[31] singlecycle_datapath.mux_next_pc_select.multiplexer.in_bus[127] -1 1 -.names singlecycle_datapath.program_counter.value[0] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][0] -1 1 -.names singlecycle_datapath.program_counter.value[1] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][1] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[2] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][2] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[3] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][3] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[4] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][4] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[5] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][5] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[6] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][6] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[7] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][7] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[8] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][8] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[9] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][9] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[10] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][10] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[11] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][11] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[12] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][12] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[13] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][13] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[14] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][14] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[15] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][15] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[16] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][16] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[17] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][17] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[18] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][18] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[19] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][19] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[20] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][20] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[21] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][21] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[22] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][22] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[23] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][23] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[24] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][24] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[25] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][25] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[26] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][26] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[27] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][27] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[28] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][28] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[29] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][29] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[30] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][30] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[31] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[0][31] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][0] -1 1 -.names bus_address[1] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][1] -1 1 -.names bus_address[2] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][2] -1 1 -.names bus_address[3] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][3] -1 1 -.names bus_address[4] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][4] -1 1 -.names bus_address[5] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][5] -1 1 -.names bus_address[6] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][6] -1 1 -.names bus_address[7] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][7] -1 1 -.names bus_address[8] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][8] -1 1 -.names bus_address[9] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][9] -1 1 -.names bus_address[10] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][10] -1 1 -.names bus_address[11] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][11] -1 1 -.names bus_address[12] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][12] -1 1 -.names bus_address[13] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][13] -1 1 -.names bus_address[14] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][14] -1 1 -.names bus_address[15] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][15] -1 1 -.names bus_address[16] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][16] -1 1 -.names bus_address[17] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][17] -1 1 -.names bus_address[18] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][18] -1 1 -.names bus_address[19] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][19] -1 1 -.names bus_address[20] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][20] -1 1 -.names bus_address[21] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][21] -1 1 -.names bus_address[22] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][22] -1 1 -.names bus_address[23] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][23] -1 1 -.names bus_address[24] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][24] -1 1 -.names bus_address[25] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][25] -1 1 -.names bus_address[26] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][26] -1 1 -.names bus_address[27] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][27] -1 1 -.names bus_address[28] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][28] -1 1 -.names bus_address[29] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][29] -1 1 -.names bus_address[30] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][30] -1 1 -.names bus_address[31] singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[2][31] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][0] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][1] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][2] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][3] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][4] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][5] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][6] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][7] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][8] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][9] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][10] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][11] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][12] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][13] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][14] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][15] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][16] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][17] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][18] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][19] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][20] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][21] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][22] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][23] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][24] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][25] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][26] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][27] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][28] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][29] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][30] -1 1 -.names $false singlecycle_datapath.mux_next_pc_select.multiplexer.input_array[3][31] -1 1 -.names rs1_data[0] singlecycle_datapath.mux_operand_a.in0[0] -1 1 -.names rs1_data[1] singlecycle_datapath.mux_operand_a.in0[1] -1 1 -.names rs1_data[2] singlecycle_datapath.mux_operand_a.in0[2] -1 1 -.names rs1_data[3] singlecycle_datapath.mux_operand_a.in0[3] -1 1 -.names rs1_data[4] singlecycle_datapath.mux_operand_a.in0[4] -1 1 -.names rs1_data[5] singlecycle_datapath.mux_operand_a.in0[5] -1 1 -.names rs1_data[6] singlecycle_datapath.mux_operand_a.in0[6] -1 1 -.names rs1_data[7] singlecycle_datapath.mux_operand_a.in0[7] -1 1 -.names rs1_data[8] singlecycle_datapath.mux_operand_a.in0[8] -1 1 -.names rs1_data[9] singlecycle_datapath.mux_operand_a.in0[9] -1 1 -.names rs1_data[10] singlecycle_datapath.mux_operand_a.in0[10] -1 1 -.names rs1_data[11] singlecycle_datapath.mux_operand_a.in0[11] -1 1 -.names rs1_data[12] singlecycle_datapath.mux_operand_a.in0[12] -1 1 -.names rs1_data[13] singlecycle_datapath.mux_operand_a.in0[13] -1 1 -.names rs1_data[14] singlecycle_datapath.mux_operand_a.in0[14] -1 1 -.names rs1_data[15] singlecycle_datapath.mux_operand_a.in0[15] -1 1 -.names rs1_data[16] singlecycle_datapath.mux_operand_a.in0[16] -1 1 -.names rs1_data[17] singlecycle_datapath.mux_operand_a.in0[17] -1 1 -.names rs1_data[18] singlecycle_datapath.mux_operand_a.in0[18] -1 1 -.names rs1_data[19] singlecycle_datapath.mux_operand_a.in0[19] -1 1 -.names rs1_data[20] singlecycle_datapath.mux_operand_a.in0[20] -1 1 -.names rs1_data[21] singlecycle_datapath.mux_operand_a.in0[21] -1 1 -.names rs1_data[22] singlecycle_datapath.mux_operand_a.in0[22] -1 1 -.names rs1_data[23] singlecycle_datapath.mux_operand_a.in0[23] -1 1 -.names rs1_data[24] singlecycle_datapath.mux_operand_a.in0[24] -1 1 -.names rs1_data[25] singlecycle_datapath.mux_operand_a.in0[25] -1 1 -.names rs1_data[26] singlecycle_datapath.mux_operand_a.in0[26] -1 1 -.names rs1_data[27] singlecycle_datapath.mux_operand_a.in0[27] -1 1 -.names rs1_data[28] singlecycle_datapath.mux_operand_a.in0[28] -1 1 -.names rs1_data[29] singlecycle_datapath.mux_operand_a.in0[29] -1 1 -.names rs1_data[30] singlecycle_datapath.mux_operand_a.in0[30] -1 1 -.names rs1_data[31] singlecycle_datapath.mux_operand_a.in0[31] -1 1 -.names singlecycle_datapath.program_counter.value[0] singlecycle_datapath.mux_operand_a.in1[0] -1 1 -.names singlecycle_datapath.program_counter.value[1] singlecycle_datapath.mux_operand_a.in1[1] -1 1 -.names singlecycle_datapath.program_counter.value[2] singlecycle_datapath.mux_operand_a.in1[2] -1 1 -.names singlecycle_datapath.program_counter.value[3] singlecycle_datapath.mux_operand_a.in1[3] -1 1 -.names singlecycle_datapath.program_counter.value[4] singlecycle_datapath.mux_operand_a.in1[4] -1 1 -.names singlecycle_datapath.program_counter.value[5] singlecycle_datapath.mux_operand_a.in1[5] -1 1 -.names singlecycle_datapath.program_counter.value[6] singlecycle_datapath.mux_operand_a.in1[6] -1 1 -.names singlecycle_datapath.program_counter.value[7] singlecycle_datapath.mux_operand_a.in1[7] -1 1 -.names singlecycle_datapath.program_counter.value[8] singlecycle_datapath.mux_operand_a.in1[8] -1 1 -.names singlecycle_datapath.program_counter.value[9] singlecycle_datapath.mux_operand_a.in1[9] -1 1 -.names singlecycle_datapath.program_counter.value[10] singlecycle_datapath.mux_operand_a.in1[10] -1 1 -.names singlecycle_datapath.program_counter.value[11] singlecycle_datapath.mux_operand_a.in1[11] -1 1 -.names singlecycle_datapath.program_counter.value[12] singlecycle_datapath.mux_operand_a.in1[12] -1 1 -.names singlecycle_datapath.program_counter.value[13] singlecycle_datapath.mux_operand_a.in1[13] -1 1 -.names singlecycle_datapath.program_counter.value[14] singlecycle_datapath.mux_operand_a.in1[14] -1 1 -.names singlecycle_datapath.program_counter.value[15] singlecycle_datapath.mux_operand_a.in1[15] -1 1 -.names singlecycle_datapath.program_counter.value[16] singlecycle_datapath.mux_operand_a.in1[16] -1 1 -.names singlecycle_datapath.program_counter.value[17] singlecycle_datapath.mux_operand_a.in1[17] -1 1 -.names singlecycle_datapath.program_counter.value[18] singlecycle_datapath.mux_operand_a.in1[18] -1 1 -.names singlecycle_datapath.program_counter.value[19] singlecycle_datapath.mux_operand_a.in1[19] -1 1 -.names singlecycle_datapath.program_counter.value[20] singlecycle_datapath.mux_operand_a.in1[20] -1 1 -.names singlecycle_datapath.program_counter.value[21] singlecycle_datapath.mux_operand_a.in1[21] -1 1 -.names singlecycle_datapath.program_counter.value[22] singlecycle_datapath.mux_operand_a.in1[22] -1 1 -.names singlecycle_datapath.program_counter.value[23] singlecycle_datapath.mux_operand_a.in1[23] -1 1 -.names singlecycle_datapath.program_counter.value[24] singlecycle_datapath.mux_operand_a.in1[24] -1 1 -.names singlecycle_datapath.program_counter.value[25] singlecycle_datapath.mux_operand_a.in1[25] -1 1 -.names singlecycle_datapath.program_counter.value[26] singlecycle_datapath.mux_operand_a.in1[26] -1 1 -.names singlecycle_datapath.program_counter.value[27] singlecycle_datapath.mux_operand_a.in1[27] -1 1 -.names singlecycle_datapath.program_counter.value[28] singlecycle_datapath.mux_operand_a.in1[28] -1 1 -.names singlecycle_datapath.program_counter.value[29] singlecycle_datapath.mux_operand_a.in1[29] -1 1 -.names singlecycle_datapath.program_counter.value[30] singlecycle_datapath.mux_operand_a.in1[30] -1 1 -.names singlecycle_datapath.program_counter.value[31] singlecycle_datapath.mux_operand_a.in1[31] -1 1 -.names singlecycle_datapath.program_counter.value[0] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[0] -1 1 -.names singlecycle_datapath.program_counter.value[1] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[1] -1 1 -.names singlecycle_datapath.program_counter.value[2] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[2] -1 1 -.names singlecycle_datapath.program_counter.value[3] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[3] -1 1 -.names singlecycle_datapath.program_counter.value[4] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[4] -1 1 -.names singlecycle_datapath.program_counter.value[5] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[5] -1 1 -.names singlecycle_datapath.program_counter.value[6] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[6] -1 1 -.names singlecycle_datapath.program_counter.value[7] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[7] -1 1 -.names singlecycle_datapath.program_counter.value[8] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[8] -1 1 -.names singlecycle_datapath.program_counter.value[9] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[9] -1 1 -.names singlecycle_datapath.program_counter.value[10] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[10] -1 1 -.names singlecycle_datapath.program_counter.value[11] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[11] -1 1 -.names singlecycle_datapath.program_counter.value[12] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[12] -1 1 -.names singlecycle_datapath.program_counter.value[13] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[13] -1 1 -.names singlecycle_datapath.program_counter.value[14] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[14] -1 1 -.names singlecycle_datapath.program_counter.value[15] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[15] -1 1 -.names singlecycle_datapath.program_counter.value[16] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[16] -1 1 -.names singlecycle_datapath.program_counter.value[17] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[17] -1 1 -.names singlecycle_datapath.program_counter.value[18] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[18] -1 1 -.names singlecycle_datapath.program_counter.value[19] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[19] -1 1 -.names singlecycle_datapath.program_counter.value[20] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[20] -1 1 -.names singlecycle_datapath.program_counter.value[21] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[21] -1 1 -.names singlecycle_datapath.program_counter.value[22] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[22] -1 1 -.names singlecycle_datapath.program_counter.value[23] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[23] -1 1 -.names singlecycle_datapath.program_counter.value[24] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[24] -1 1 -.names singlecycle_datapath.program_counter.value[25] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[25] -1 1 -.names singlecycle_datapath.program_counter.value[26] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[26] -1 1 -.names singlecycle_datapath.program_counter.value[27] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[27] -1 1 -.names singlecycle_datapath.program_counter.value[28] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[28] -1 1 -.names singlecycle_datapath.program_counter.value[29] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[29] -1 1 -.names singlecycle_datapath.program_counter.value[30] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[30] -1 1 -.names singlecycle_datapath.program_counter.value[31] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[31] -1 1 -.names rs1_data[0] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[32] -1 1 -.names rs1_data[1] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[33] -1 1 -.names rs1_data[2] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[34] -1 1 -.names rs1_data[3] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[35] -1 1 -.names rs1_data[4] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[36] -1 1 -.names rs1_data[5] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[37] -1 1 -.names rs1_data[6] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[38] -1 1 -.names rs1_data[7] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[39] -1 1 -.names rs1_data[8] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[40] -1 1 -.names rs1_data[9] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[41] -1 1 -.names rs1_data[10] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[42] -1 1 -.names rs1_data[11] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[43] -1 1 -.names rs1_data[12] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[44] -1 1 -.names rs1_data[13] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[45] -1 1 -.names rs1_data[14] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[46] -1 1 -.names rs1_data[15] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[47] -1 1 -.names rs1_data[16] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[48] -1 1 -.names rs1_data[17] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[49] -1 1 -.names rs1_data[18] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[50] -1 1 -.names rs1_data[19] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[51] -1 1 -.names rs1_data[20] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[52] -1 1 -.names rs1_data[21] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[53] -1 1 -.names rs1_data[22] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[54] -1 1 -.names rs1_data[23] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[55] -1 1 -.names rs1_data[24] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[56] -1 1 -.names rs1_data[25] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[57] -1 1 -.names rs1_data[26] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[58] -1 1 -.names rs1_data[27] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[59] -1 1 -.names rs1_data[28] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[60] -1 1 -.names rs1_data[29] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[61] -1 1 -.names rs1_data[30] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[62] -1 1 -.names rs1_data[31] singlecycle_datapath.mux_operand_a.multiplexer.in_bus[63] -1 1 -.names rs1_data[0] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][0] -1 1 -.names rs1_data[1] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][1] -1 1 -.names rs1_data[2] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][2] -1 1 -.names rs1_data[3] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][3] -1 1 -.names rs1_data[4] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][4] -1 1 -.names rs1_data[5] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][5] -1 1 -.names rs1_data[6] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][6] -1 1 -.names rs1_data[7] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][7] -1 1 -.names rs1_data[8] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][8] -1 1 -.names rs1_data[9] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][9] -1 1 -.names rs1_data[10] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][10] -1 1 -.names rs1_data[11] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][11] -1 1 -.names rs1_data[12] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][12] -1 1 -.names rs1_data[13] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][13] -1 1 -.names rs1_data[14] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][14] -1 1 -.names rs1_data[15] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][15] -1 1 -.names rs1_data[16] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][16] -1 1 -.names rs1_data[17] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][17] -1 1 -.names rs1_data[18] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][18] -1 1 -.names rs1_data[19] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][19] -1 1 -.names rs1_data[20] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][20] -1 1 -.names rs1_data[21] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][21] -1 1 -.names rs1_data[22] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][22] -1 1 -.names rs1_data[23] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][23] -1 1 -.names rs1_data[24] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][24] -1 1 -.names rs1_data[25] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][25] -1 1 -.names rs1_data[26] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][26] -1 1 -.names rs1_data[27] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][27] -1 1 -.names rs1_data[28] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][28] -1 1 -.names rs1_data[29] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][29] -1 1 -.names rs1_data[30] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][30] -1 1 -.names rs1_data[31] singlecycle_datapath.mux_operand_a.multiplexer.input_array[0][31] -1 1 -.names singlecycle_datapath.program_counter.value[0] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][0] -1 1 -.names singlecycle_datapath.program_counter.value[1] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][1] -1 1 -.names singlecycle_datapath.program_counter.value[2] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][2] -1 1 -.names singlecycle_datapath.program_counter.value[3] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][3] -1 1 -.names singlecycle_datapath.program_counter.value[4] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][4] -1 1 -.names singlecycle_datapath.program_counter.value[5] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][5] -1 1 -.names singlecycle_datapath.program_counter.value[6] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][6] -1 1 -.names singlecycle_datapath.program_counter.value[7] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][7] -1 1 -.names singlecycle_datapath.program_counter.value[8] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][8] -1 1 -.names singlecycle_datapath.program_counter.value[9] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][9] -1 1 -.names singlecycle_datapath.program_counter.value[10] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][10] -1 1 -.names singlecycle_datapath.program_counter.value[11] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][11] -1 1 -.names singlecycle_datapath.program_counter.value[12] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][12] -1 1 -.names singlecycle_datapath.program_counter.value[13] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][13] -1 1 -.names singlecycle_datapath.program_counter.value[14] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][14] -1 1 -.names singlecycle_datapath.program_counter.value[15] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][15] -1 1 -.names singlecycle_datapath.program_counter.value[16] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][16] -1 1 -.names singlecycle_datapath.program_counter.value[17] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][17] -1 1 -.names singlecycle_datapath.program_counter.value[18] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][18] -1 1 -.names singlecycle_datapath.program_counter.value[19] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][19] -1 1 -.names singlecycle_datapath.program_counter.value[20] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][20] -1 1 -.names singlecycle_datapath.program_counter.value[21] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][21] -1 1 -.names singlecycle_datapath.program_counter.value[22] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][22] -1 1 -.names singlecycle_datapath.program_counter.value[23] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][23] -1 1 -.names singlecycle_datapath.program_counter.value[24] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][24] -1 1 -.names singlecycle_datapath.program_counter.value[25] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][25] -1 1 -.names singlecycle_datapath.program_counter.value[26] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][26] -1 1 -.names singlecycle_datapath.program_counter.value[27] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][27] -1 1 -.names singlecycle_datapath.program_counter.value[28] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][28] -1 1 -.names singlecycle_datapath.program_counter.value[29] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][29] -1 1 -.names singlecycle_datapath.program_counter.value[30] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][30] -1 1 -.names singlecycle_datapath.program_counter.value[31] singlecycle_datapath.mux_operand_a.multiplexer.input_array[1][31] -1 1 -.names rs2_data[0] singlecycle_datapath.mux_operand_b.in0[0] -1 1 -.names rs2_data[1] singlecycle_datapath.mux_operand_b.in0[1] -1 1 -.names rs2_data[2] singlecycle_datapath.mux_operand_b.in0[2] -1 1 -.names rs2_data[3] singlecycle_datapath.mux_operand_b.in0[3] -1 1 -.names rs2_data[4] singlecycle_datapath.mux_operand_b.in0[4] -1 1 -.names rs2_data[5] singlecycle_datapath.mux_operand_b.in0[5] -1 1 -.names rs2_data[6] singlecycle_datapath.mux_operand_b.in0[6] -1 1 -.names rs2_data[7] singlecycle_datapath.mux_operand_b.in0[7] -1 1 -.names rs2_data[8] singlecycle_datapath.mux_operand_b.in0[8] -1 1 -.names rs2_data[9] singlecycle_datapath.mux_operand_b.in0[9] -1 1 -.names rs2_data[10] singlecycle_datapath.mux_operand_b.in0[10] -1 1 -.names rs2_data[11] singlecycle_datapath.mux_operand_b.in0[11] -1 1 -.names rs2_data[12] singlecycle_datapath.mux_operand_b.in0[12] -1 1 -.names rs2_data[13] singlecycle_datapath.mux_operand_b.in0[13] -1 1 -.names rs2_data[14] singlecycle_datapath.mux_operand_b.in0[14] -1 1 -.names rs2_data[15] singlecycle_datapath.mux_operand_b.in0[15] -1 1 -.names rs2_data[16] singlecycle_datapath.mux_operand_b.in0[16] -1 1 -.names rs2_data[17] singlecycle_datapath.mux_operand_b.in0[17] -1 1 -.names rs2_data[18] singlecycle_datapath.mux_operand_b.in0[18] -1 1 -.names rs2_data[19] singlecycle_datapath.mux_operand_b.in0[19] -1 1 -.names rs2_data[20] singlecycle_datapath.mux_operand_b.in0[20] -1 1 -.names rs2_data[21] singlecycle_datapath.mux_operand_b.in0[21] -1 1 -.names rs2_data[22] singlecycle_datapath.mux_operand_b.in0[22] -1 1 -.names rs2_data[23] singlecycle_datapath.mux_operand_b.in0[23] -1 1 -.names rs2_data[24] singlecycle_datapath.mux_operand_b.in0[24] -1 1 -.names rs2_data[25] singlecycle_datapath.mux_operand_b.in0[25] -1 1 -.names rs2_data[26] singlecycle_datapath.mux_operand_b.in0[26] -1 1 -.names rs2_data[27] singlecycle_datapath.mux_operand_b.in0[27] -1 1 -.names rs2_data[28] singlecycle_datapath.mux_operand_b.in0[28] -1 1 -.names rs2_data[29] singlecycle_datapath.mux_operand_b.in0[29] -1 1 -.names rs2_data[30] singlecycle_datapath.mux_operand_b.in0[30] -1 1 -.names rs2_data[31] singlecycle_datapath.mux_operand_b.in0[31] -1 1 -.names rs2_data[0] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[32] -1 1 -.names rs2_data[1] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[33] -1 1 -.names rs2_data[2] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[34] -1 1 -.names rs2_data[3] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[35] -1 1 -.names rs2_data[4] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[36] -1 1 -.names rs2_data[5] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[37] -1 1 -.names rs2_data[6] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[38] -1 1 -.names rs2_data[7] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[39] -1 1 -.names rs2_data[8] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[40] -1 1 -.names rs2_data[9] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[41] -1 1 -.names rs2_data[10] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[42] -1 1 -.names rs2_data[11] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[43] -1 1 -.names rs2_data[12] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[44] -1 1 -.names rs2_data[13] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[45] -1 1 -.names rs2_data[14] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[46] -1 1 -.names rs2_data[15] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[47] -1 1 -.names rs2_data[16] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[48] -1 1 -.names rs2_data[17] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[49] -1 1 -.names rs2_data[18] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[50] -1 1 -.names rs2_data[19] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[51] -1 1 -.names rs2_data[20] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[52] -1 1 -.names rs2_data[21] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[53] -1 1 -.names rs2_data[22] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[54] -1 1 -.names rs2_data[23] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[55] -1 1 -.names rs2_data[24] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[56] -1 1 -.names rs2_data[25] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[57] -1 1 -.names rs2_data[26] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[58] -1 1 -.names rs2_data[27] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[59] -1 1 -.names rs2_data[28] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[60] -1 1 -.names rs2_data[29] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[61] -1 1 -.names rs2_data[30] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[62] -1 1 -.names rs2_data[31] singlecycle_datapath.mux_operand_b.multiplexer.in_bus[63] -1 1 -.names rs2_data[0] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][0] -1 1 -.names rs2_data[1] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][1] -1 1 -.names rs2_data[2] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][2] -1 1 -.names rs2_data[3] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][3] -1 1 -.names rs2_data[4] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][4] -1 1 -.names rs2_data[5] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][5] -1 1 -.names rs2_data[6] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][6] -1 1 -.names rs2_data[7] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][7] -1 1 -.names rs2_data[8] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][8] -1 1 -.names rs2_data[9] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][9] -1 1 -.names rs2_data[10] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][10] -1 1 -.names rs2_data[11] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][11] -1 1 -.names rs2_data[12] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][12] -1 1 -.names rs2_data[13] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][13] -1 1 -.names rs2_data[14] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][14] -1 1 -.names rs2_data[15] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][15] -1 1 -.names rs2_data[16] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][16] -1 1 -.names rs2_data[17] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][17] -1 1 -.names rs2_data[18] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][18] -1 1 -.names rs2_data[19] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][19] -1 1 -.names rs2_data[20] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][20] -1 1 -.names rs2_data[21] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][21] -1 1 -.names rs2_data[22] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][22] -1 1 -.names rs2_data[23] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][23] -1 1 -.names rs2_data[24] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][24] -1 1 -.names rs2_data[25] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][25] -1 1 -.names rs2_data[26] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][26] -1 1 -.names rs2_data[27] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][27] -1 1 -.names rs2_data[28] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][28] -1 1 -.names rs2_data[29] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][29] -1 1 -.names rs2_data[30] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][30] -1 1 -.names rs2_data[31] singlecycle_datapath.mux_operand_b.multiplexer.input_array[0][31] -1 1 -.names bus_address[0] singlecycle_datapath.mux_reg_writeback.in0[0] -1 1 -.names bus_address[1] singlecycle_datapath.mux_reg_writeback.in0[1] -1 1 -.names bus_address[2] singlecycle_datapath.mux_reg_writeback.in0[2] -1 1 -.names bus_address[3] singlecycle_datapath.mux_reg_writeback.in0[3] -1 1 -.names bus_address[4] singlecycle_datapath.mux_reg_writeback.in0[4] -1 1 -.names bus_address[5] singlecycle_datapath.mux_reg_writeback.in0[5] -1 1 -.names bus_address[6] singlecycle_datapath.mux_reg_writeback.in0[6] -1 1 -.names bus_address[7] singlecycle_datapath.mux_reg_writeback.in0[7] -1 1 -.names bus_address[8] singlecycle_datapath.mux_reg_writeback.in0[8] -1 1 -.names bus_address[9] singlecycle_datapath.mux_reg_writeback.in0[9] -1 1 -.names bus_address[10] singlecycle_datapath.mux_reg_writeback.in0[10] -1 1 -.names bus_address[11] singlecycle_datapath.mux_reg_writeback.in0[11] -1 1 -.names bus_address[12] singlecycle_datapath.mux_reg_writeback.in0[12] -1 1 -.names bus_address[13] singlecycle_datapath.mux_reg_writeback.in0[13] -1 1 -.names bus_address[14] singlecycle_datapath.mux_reg_writeback.in0[14] -1 1 -.names bus_address[15] singlecycle_datapath.mux_reg_writeback.in0[15] -1 1 -.names bus_address[16] singlecycle_datapath.mux_reg_writeback.in0[16] -1 1 -.names bus_address[17] singlecycle_datapath.mux_reg_writeback.in0[17] -1 1 -.names bus_address[18] singlecycle_datapath.mux_reg_writeback.in0[18] -1 1 -.names bus_address[19] singlecycle_datapath.mux_reg_writeback.in0[19] -1 1 -.names bus_address[20] singlecycle_datapath.mux_reg_writeback.in0[20] -1 1 -.names bus_address[21] singlecycle_datapath.mux_reg_writeback.in0[21] -1 1 -.names bus_address[22] singlecycle_datapath.mux_reg_writeback.in0[22] -1 1 -.names bus_address[23] singlecycle_datapath.mux_reg_writeback.in0[23] -1 1 -.names bus_address[24] singlecycle_datapath.mux_reg_writeback.in0[24] -1 1 -.names bus_address[25] singlecycle_datapath.mux_reg_writeback.in0[25] -1 1 -.names bus_address[26] singlecycle_datapath.mux_reg_writeback.in0[26] -1 1 -.names bus_address[27] singlecycle_datapath.mux_reg_writeback.in0[27] -1 1 -.names bus_address[28] singlecycle_datapath.mux_reg_writeback.in0[28] -1 1 -.names bus_address[29] singlecycle_datapath.mux_reg_writeback.in0[29] -1 1 -.names bus_address[30] singlecycle_datapath.mux_reg_writeback.in0[30] -1 1 -.names bus_address[31] singlecycle_datapath.mux_reg_writeback.in0[31] -1 1 -.names singlecycle_datapath.program_counter.value[0] singlecycle_datapath.mux_reg_writeback.in2[0] -1 1 -.names singlecycle_datapath.program_counter.value[1] singlecycle_datapath.mux_reg_writeback.in2[1] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[2] singlecycle_datapath.mux_reg_writeback.in2[2] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[3] singlecycle_datapath.mux_reg_writeback.in2[3] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[4] singlecycle_datapath.mux_reg_writeback.in2[4] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[5] singlecycle_datapath.mux_reg_writeback.in2[5] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[6] singlecycle_datapath.mux_reg_writeback.in2[6] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[7] singlecycle_datapath.mux_reg_writeback.in2[7] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[8] singlecycle_datapath.mux_reg_writeback.in2[8] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[9] singlecycle_datapath.mux_reg_writeback.in2[9] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[10] singlecycle_datapath.mux_reg_writeback.in2[10] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[11] singlecycle_datapath.mux_reg_writeback.in2[11] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[12] singlecycle_datapath.mux_reg_writeback.in2[12] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[13] singlecycle_datapath.mux_reg_writeback.in2[13] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[14] singlecycle_datapath.mux_reg_writeback.in2[14] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[15] singlecycle_datapath.mux_reg_writeback.in2[15] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[16] singlecycle_datapath.mux_reg_writeback.in2[16] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[17] singlecycle_datapath.mux_reg_writeback.in2[17] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[18] singlecycle_datapath.mux_reg_writeback.in2[18] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[19] singlecycle_datapath.mux_reg_writeback.in2[19] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[20] singlecycle_datapath.mux_reg_writeback.in2[20] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[21] singlecycle_datapath.mux_reg_writeback.in2[21] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[22] singlecycle_datapath.mux_reg_writeback.in2[22] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[23] singlecycle_datapath.mux_reg_writeback.in2[23] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[24] singlecycle_datapath.mux_reg_writeback.in2[24] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[25] singlecycle_datapath.mux_reg_writeback.in2[25] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[26] singlecycle_datapath.mux_reg_writeback.in2[26] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[27] singlecycle_datapath.mux_reg_writeback.in2[27] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[28] singlecycle_datapath.mux_reg_writeback.in2[28] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[29] singlecycle_datapath.mux_reg_writeback.in2[29] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[30] singlecycle_datapath.mux_reg_writeback.in2[30] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[31] singlecycle_datapath.mux_reg_writeback.in2[31] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in4[0] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in4[1] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in4[2] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in4[3] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in4[4] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in4[5] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in4[6] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in4[7] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in4[8] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in4[9] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in4[10] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in4[11] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in4[12] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in4[13] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in4[14] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in4[15] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in4[16] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in4[17] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in4[18] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in4[19] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in4[20] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in4[21] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in4[22] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in4[23] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in4[24] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in4[25] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in4[26] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in4[27] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in4[28] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in4[29] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in4[30] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in4[31] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in5[0] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in5[1] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in5[2] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in5[3] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in5[4] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in5[5] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in5[6] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in5[7] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in5[8] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in5[9] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in5[10] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in5[11] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in5[12] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in5[13] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in5[14] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in5[15] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in5[16] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in5[17] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in5[18] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in5[19] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in5[20] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in5[21] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in5[22] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in5[23] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in5[24] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in5[25] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in5[26] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in5[27] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in5[28] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in5[29] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in5[30] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in5[31] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in6[0] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in6[1] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in6[2] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in6[3] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in6[4] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in6[5] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in6[6] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in6[7] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in6[8] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in6[9] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in6[10] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in6[11] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in6[12] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in6[13] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in6[14] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in6[15] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in6[16] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in6[17] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in6[18] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in6[19] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in6[20] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in6[21] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in6[22] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in6[23] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in6[24] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in6[25] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in6[26] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in6[27] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in6[28] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in6[29] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in6[30] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in6[31] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in7[0] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in7[1] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in7[2] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in7[3] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in7[4] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in7[5] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in7[6] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in7[7] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in7[8] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in7[9] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in7[10] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in7[11] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in7[12] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in7[13] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in7[14] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in7[15] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in7[16] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in7[17] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in7[18] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in7[19] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in7[20] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in7[21] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in7[22] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in7[23] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in7[24] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in7[25] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in7[26] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in7[27] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in7[28] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in7[29] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in7[30] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.in7[31] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[0] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[1] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[2] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[3] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[4] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[5] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[6] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[7] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[8] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[9] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[10] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[11] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[12] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[13] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[14] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[15] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[16] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[17] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[18] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[19] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[20] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[21] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[22] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[23] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[24] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[25] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[26] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[27] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[28] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[29] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[30] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[31] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[32] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[33] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[34] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[35] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[36] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[37] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[38] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[39] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[40] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[41] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[42] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[43] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[44] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[45] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[46] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[47] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[48] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[49] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[50] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[51] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[52] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[53] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[54] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[55] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[56] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[57] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[58] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[59] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[60] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[61] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[62] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[63] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[64] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[65] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[66] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[67] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[68] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[69] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[70] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[71] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[72] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[73] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[74] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[75] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[76] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[77] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[78] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[79] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[80] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[81] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[82] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[83] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[84] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[85] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[86] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[87] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[88] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[89] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[90] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[91] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[92] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[93] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[94] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[95] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[96] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[97] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[98] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[99] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[100] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[101] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[102] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[103] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[104] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[105] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[106] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[107] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[108] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[109] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[110] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[111] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[112] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[113] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[114] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[115] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[116] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[117] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[118] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[119] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[120] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[121] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[122] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[123] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[124] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[125] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[126] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[127] -1 1 -.names singlecycle_datapath.program_counter.value[0] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[160] -1 1 -.names singlecycle_datapath.program_counter.value[1] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[161] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[2] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[162] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[3] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[163] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[4] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[164] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[5] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[165] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[6] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[166] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[7] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[167] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[8] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[168] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[9] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[169] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[10] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[170] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[11] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[171] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[12] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[172] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[13] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[173] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[14] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[174] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[15] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[175] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[16] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[176] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[17] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[177] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[18] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[178] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[19] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[179] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[20] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[180] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[21] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[181] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[22] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[182] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[23] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[183] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[24] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[184] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[25] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[185] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[26] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[186] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[27] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[187] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[28] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[188] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[29] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[189] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[30] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[190] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[31] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[191] -1 1 -.names bus_address[0] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[224] -1 1 -.names bus_address[1] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[225] -1 1 -.names bus_address[2] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[226] -1 1 -.names bus_address[3] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[227] -1 1 -.names bus_address[4] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[228] -1 1 -.names bus_address[5] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[229] -1 1 -.names bus_address[6] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[230] -1 1 -.names bus_address[7] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[231] -1 1 -.names bus_address[8] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[232] -1 1 -.names bus_address[9] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[233] -1 1 -.names bus_address[10] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[234] -1 1 -.names bus_address[11] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[235] -1 1 -.names bus_address[12] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[236] -1 1 -.names bus_address[13] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[237] -1 1 -.names bus_address[14] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[238] -1 1 -.names bus_address[15] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[239] -1 1 -.names bus_address[16] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[240] -1 1 -.names bus_address[17] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[241] -1 1 -.names bus_address[18] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[242] -1 1 -.names bus_address[19] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[243] -1 1 -.names bus_address[20] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[244] -1 1 -.names bus_address[21] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[245] -1 1 -.names bus_address[22] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[246] -1 1 -.names bus_address[23] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[247] -1 1 -.names bus_address[24] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[248] -1 1 -.names bus_address[25] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[249] -1 1 -.names bus_address[26] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[250] -1 1 -.names bus_address[27] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[251] -1 1 -.names bus_address[28] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[252] -1 1 -.names bus_address[29] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[253] -1 1 -.names bus_address[30] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[254] -1 1 -.names bus_address[31] singlecycle_datapath.mux_reg_writeback.multiplexer.in_bus[255] -1 1 -.names bus_address[0] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][0] -1 1 -.names bus_address[1] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][1] -1 1 -.names bus_address[2] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][2] -1 1 -.names bus_address[3] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][3] -1 1 -.names bus_address[4] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][4] -1 1 -.names bus_address[5] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][5] -1 1 -.names bus_address[6] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][6] -1 1 -.names bus_address[7] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][7] -1 1 -.names bus_address[8] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][8] -1 1 -.names bus_address[9] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][9] -1 1 -.names bus_address[10] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][10] -1 1 -.names bus_address[11] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][11] -1 1 -.names bus_address[12] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][12] -1 1 -.names bus_address[13] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][13] -1 1 -.names bus_address[14] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][14] -1 1 -.names bus_address[15] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][15] -1 1 -.names bus_address[16] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][16] -1 1 -.names bus_address[17] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][17] -1 1 -.names bus_address[18] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][18] -1 1 -.names bus_address[19] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][19] -1 1 -.names bus_address[20] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][20] -1 1 -.names bus_address[21] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][21] -1 1 -.names bus_address[22] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][22] -1 1 -.names bus_address[23] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][23] -1 1 -.names bus_address[24] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][24] -1 1 -.names bus_address[25] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][25] -1 1 -.names bus_address[26] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][26] -1 1 -.names bus_address[27] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][27] -1 1 -.names bus_address[28] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][28] -1 1 -.names bus_address[29] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][29] -1 1 -.names bus_address[30] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][30] -1 1 -.names bus_address[31] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[0][31] -1 1 -.names singlecycle_datapath.program_counter.value[0] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][0] -1 1 -.names singlecycle_datapath.program_counter.value[1] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][1] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[2] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][2] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[3] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][3] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[4] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][4] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[5] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][5] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[6] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][6] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[7] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][7] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[8] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][8] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[9] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][9] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[10] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][10] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[11] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][11] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[12] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][12] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[13] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][13] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[14] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][14] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[15] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][15] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[16] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][16] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[17] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][17] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[18] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][18] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[19] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][19] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[20] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][20] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[21] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][21] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[22] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][22] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[23] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][23] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[24] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][24] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[25] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][25] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[26] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][26] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[27] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][27] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[28] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][28] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[29] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][29] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[30] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][30] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[31] singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[2][31] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][0] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][1] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][2] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][3] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][4] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][5] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][6] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][7] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][8] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][9] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][10] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][11] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][12] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][13] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][14] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][15] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][16] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][17] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][18] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][19] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][20] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][21] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][22] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][23] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][24] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][25] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][26] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][27] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][28] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][29] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][30] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[4][31] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][0] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][1] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][2] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][3] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][4] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][5] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][6] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][7] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][8] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][9] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][10] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][11] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][12] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][13] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][14] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][15] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][16] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][17] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][18] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][19] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][20] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][21] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][22] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][23] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][24] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][25] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][26] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][27] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][28] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][29] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][30] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[5][31] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][0] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][1] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][2] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][3] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][4] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][5] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][6] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][7] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][8] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][9] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][10] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][11] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][12] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][13] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][14] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][15] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][16] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][17] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][18] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][19] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][20] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][21] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][22] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][23] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][24] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][25] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][26] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][27] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][28] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][29] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][30] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[6][31] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][0] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][1] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][2] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][3] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][4] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][5] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][6] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][7] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][8] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][9] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][10] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][11] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][12] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][13] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][14] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][15] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][16] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][17] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][18] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][19] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][20] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][21] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][22] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][23] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][24] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][25] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][26] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][27] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][28] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][29] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][30] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.input_array[7][31] -1 1 -.names rd_data[0] singlecycle_datapath.mux_reg_writeback.multiplexer.out[0] -1 1 -.names rd_data[1] singlecycle_datapath.mux_reg_writeback.multiplexer.out[1] -1 1 -.names rd_data[2] singlecycle_datapath.mux_reg_writeback.multiplexer.out[2] -1 1 -.names rd_data[3] singlecycle_datapath.mux_reg_writeback.multiplexer.out[3] -1 1 -.names rd_data[4] singlecycle_datapath.mux_reg_writeback.multiplexer.out[4] -1 1 -.names rd_data[5] singlecycle_datapath.mux_reg_writeback.multiplexer.out[5] -1 1 -.names rd_data[6] singlecycle_datapath.mux_reg_writeback.multiplexer.out[6] -1 1 -.names rd_data[7] singlecycle_datapath.mux_reg_writeback.multiplexer.out[7] -1 1 -.names rd_data[8] singlecycle_datapath.mux_reg_writeback.multiplexer.out[8] -1 1 -.names rd_data[9] singlecycle_datapath.mux_reg_writeback.multiplexer.out[9] -1 1 -.names rd_data[10] singlecycle_datapath.mux_reg_writeback.multiplexer.out[10] -1 1 -.names rd_data[11] singlecycle_datapath.mux_reg_writeback.multiplexer.out[11] -1 1 -.names rd_data[12] singlecycle_datapath.mux_reg_writeback.multiplexer.out[12] -1 1 -.names rd_data[13] singlecycle_datapath.mux_reg_writeback.multiplexer.out[13] -1 1 -.names rd_data[14] singlecycle_datapath.mux_reg_writeback.multiplexer.out[14] -1 1 -.names rd_data[15] singlecycle_datapath.mux_reg_writeback.multiplexer.out[15] -1 1 -.names rd_data[16] singlecycle_datapath.mux_reg_writeback.multiplexer.out[16] -1 1 -.names rd_data[17] singlecycle_datapath.mux_reg_writeback.multiplexer.out[17] -1 1 -.names rd_data[18] singlecycle_datapath.mux_reg_writeback.multiplexer.out[18] -1 1 -.names rd_data[19] singlecycle_datapath.mux_reg_writeback.multiplexer.out[19] -1 1 -.names rd_data[20] singlecycle_datapath.mux_reg_writeback.multiplexer.out[20] -1 1 -.names rd_data[21] singlecycle_datapath.mux_reg_writeback.multiplexer.out[21] -1 1 -.names rd_data[22] singlecycle_datapath.mux_reg_writeback.multiplexer.out[22] -1 1 -.names rd_data[23] singlecycle_datapath.mux_reg_writeback.multiplexer.out[23] -1 1 -.names rd_data[24] singlecycle_datapath.mux_reg_writeback.multiplexer.out[24] -1 1 -.names rd_data[25] singlecycle_datapath.mux_reg_writeback.multiplexer.out[25] -1 1 -.names rd_data[26] singlecycle_datapath.mux_reg_writeback.multiplexer.out[26] -1 1 -.names rd_data[27] singlecycle_datapath.mux_reg_writeback.multiplexer.out[27] -1 1 -.names rd_data[28] singlecycle_datapath.mux_reg_writeback.multiplexer.out[28] -1 1 -.names rd_data[29] singlecycle_datapath.mux_reg_writeback.multiplexer.out[29] -1 1 -.names rd_data[30] singlecycle_datapath.mux_reg_writeback.multiplexer.out[30] -1 1 -.names rd_data[31] singlecycle_datapath.mux_reg_writeback.multiplexer.out[31] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.multiplexer.sel[2] -1 1 -.names rd_data[0] singlecycle_datapath.mux_reg_writeback.out[0] -1 1 -.names rd_data[1] singlecycle_datapath.mux_reg_writeback.out[1] -1 1 -.names rd_data[2] singlecycle_datapath.mux_reg_writeback.out[2] -1 1 -.names rd_data[3] singlecycle_datapath.mux_reg_writeback.out[3] -1 1 -.names rd_data[4] singlecycle_datapath.mux_reg_writeback.out[4] -1 1 -.names rd_data[5] singlecycle_datapath.mux_reg_writeback.out[5] -1 1 -.names rd_data[6] singlecycle_datapath.mux_reg_writeback.out[6] -1 1 -.names rd_data[7] singlecycle_datapath.mux_reg_writeback.out[7] -1 1 -.names rd_data[8] singlecycle_datapath.mux_reg_writeback.out[8] -1 1 -.names rd_data[9] singlecycle_datapath.mux_reg_writeback.out[9] -1 1 -.names rd_data[10] singlecycle_datapath.mux_reg_writeback.out[10] -1 1 -.names rd_data[11] singlecycle_datapath.mux_reg_writeback.out[11] -1 1 -.names rd_data[12] singlecycle_datapath.mux_reg_writeback.out[12] -1 1 -.names rd_data[13] singlecycle_datapath.mux_reg_writeback.out[13] -1 1 -.names rd_data[14] singlecycle_datapath.mux_reg_writeback.out[14] -1 1 -.names rd_data[15] singlecycle_datapath.mux_reg_writeback.out[15] -1 1 -.names rd_data[16] singlecycle_datapath.mux_reg_writeback.out[16] -1 1 -.names rd_data[17] singlecycle_datapath.mux_reg_writeback.out[17] -1 1 -.names rd_data[18] singlecycle_datapath.mux_reg_writeback.out[18] -1 1 -.names rd_data[19] singlecycle_datapath.mux_reg_writeback.out[19] -1 1 -.names rd_data[20] singlecycle_datapath.mux_reg_writeback.out[20] -1 1 -.names rd_data[21] singlecycle_datapath.mux_reg_writeback.out[21] -1 1 -.names rd_data[22] singlecycle_datapath.mux_reg_writeback.out[22] -1 1 -.names rd_data[23] singlecycle_datapath.mux_reg_writeback.out[23] -1 1 -.names rd_data[24] singlecycle_datapath.mux_reg_writeback.out[24] -1 1 -.names rd_data[25] singlecycle_datapath.mux_reg_writeback.out[25] -1 1 -.names rd_data[26] singlecycle_datapath.mux_reg_writeback.out[26] -1 1 -.names rd_data[27] singlecycle_datapath.mux_reg_writeback.out[27] -1 1 -.names rd_data[28] singlecycle_datapath.mux_reg_writeback.out[28] -1 1 -.names rd_data[29] singlecycle_datapath.mux_reg_writeback.out[29] -1 1 -.names rd_data[30] singlecycle_datapath.mux_reg_writeback.out[30] -1 1 -.names rd_data[31] singlecycle_datapath.mux_reg_writeback.out[31] -1 1 -.names $false singlecycle_datapath.mux_reg_writeback.sel[2] -1 1 -.names singlecycle_datapath.program_counter.value[0] singlecycle_datapath.pc[0] -1 1 -.names singlecycle_datapath.program_counter.value[1] singlecycle_datapath.pc[1] -1 1 -.names singlecycle_datapath.program_counter.value[2] singlecycle_datapath.pc[2] -1 1 -.names singlecycle_datapath.program_counter.value[3] singlecycle_datapath.pc[3] -1 1 -.names singlecycle_datapath.program_counter.value[4] singlecycle_datapath.pc[4] -1 1 -.names singlecycle_datapath.program_counter.value[5] singlecycle_datapath.pc[5] -1 1 -.names singlecycle_datapath.program_counter.value[6] singlecycle_datapath.pc[6] -1 1 -.names singlecycle_datapath.program_counter.value[7] singlecycle_datapath.pc[7] -1 1 -.names singlecycle_datapath.program_counter.value[8] singlecycle_datapath.pc[8] -1 1 -.names singlecycle_datapath.program_counter.value[9] singlecycle_datapath.pc[9] -1 1 -.names singlecycle_datapath.program_counter.value[10] singlecycle_datapath.pc[10] -1 1 -.names singlecycle_datapath.program_counter.value[11] singlecycle_datapath.pc[11] -1 1 -.names singlecycle_datapath.program_counter.value[12] singlecycle_datapath.pc[12] -1 1 -.names singlecycle_datapath.program_counter.value[13] singlecycle_datapath.pc[13] -1 1 -.names singlecycle_datapath.program_counter.value[14] singlecycle_datapath.pc[14] -1 1 -.names singlecycle_datapath.program_counter.value[15] singlecycle_datapath.pc[15] -1 1 -.names singlecycle_datapath.program_counter.value[16] singlecycle_datapath.pc[16] -1 1 -.names singlecycle_datapath.program_counter.value[17] singlecycle_datapath.pc[17] -1 1 -.names singlecycle_datapath.program_counter.value[18] singlecycle_datapath.pc[18] -1 1 -.names singlecycle_datapath.program_counter.value[19] singlecycle_datapath.pc[19] -1 1 -.names singlecycle_datapath.program_counter.value[20] singlecycle_datapath.pc[20] -1 1 -.names singlecycle_datapath.program_counter.value[21] singlecycle_datapath.pc[21] -1 1 -.names singlecycle_datapath.program_counter.value[22] singlecycle_datapath.pc[22] -1 1 -.names singlecycle_datapath.program_counter.value[23] singlecycle_datapath.pc[23] -1 1 -.names singlecycle_datapath.program_counter.value[24] singlecycle_datapath.pc[24] -1 1 -.names singlecycle_datapath.program_counter.value[25] singlecycle_datapath.pc[25] -1 1 -.names singlecycle_datapath.program_counter.value[26] singlecycle_datapath.pc[26] -1 1 -.names singlecycle_datapath.program_counter.value[27] singlecycle_datapath.pc[27] -1 1 -.names singlecycle_datapath.program_counter.value[28] singlecycle_datapath.pc[28] -1 1 -.names singlecycle_datapath.program_counter.value[29] singlecycle_datapath.pc[29] -1 1 -.names singlecycle_datapath.program_counter.value[30] singlecycle_datapath.pc[30] -1 1 -.names singlecycle_datapath.program_counter.value[31] singlecycle_datapath.pc[31] -1 1 -.names singlecycle_datapath.program_counter.value[0] singlecycle_datapath.pc_plus_4[0] -1 1 -.names singlecycle_datapath.program_counter.value[1] singlecycle_datapath.pc_plus_4[1] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[2] singlecycle_datapath.pc_plus_4[2] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[3] singlecycle_datapath.pc_plus_4[3] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[4] singlecycle_datapath.pc_plus_4[4] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[5] singlecycle_datapath.pc_plus_4[5] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[6] singlecycle_datapath.pc_plus_4[6] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[7] singlecycle_datapath.pc_plus_4[7] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[8] singlecycle_datapath.pc_plus_4[8] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[9] singlecycle_datapath.pc_plus_4[9] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[10] singlecycle_datapath.pc_plus_4[10] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[11] singlecycle_datapath.pc_plus_4[11] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[12] singlecycle_datapath.pc_plus_4[12] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[13] singlecycle_datapath.pc_plus_4[13] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[14] singlecycle_datapath.pc_plus_4[14] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[15] singlecycle_datapath.pc_plus_4[15] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[16] singlecycle_datapath.pc_plus_4[16] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[17] singlecycle_datapath.pc_plus_4[17] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[18] singlecycle_datapath.pc_plus_4[18] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[19] singlecycle_datapath.pc_plus_4[19] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[20] singlecycle_datapath.pc_plus_4[20] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[21] singlecycle_datapath.pc_plus_4[21] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[22] singlecycle_datapath.pc_plus_4[22] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[23] singlecycle_datapath.pc_plus_4[23] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[24] singlecycle_datapath.pc_plus_4[24] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[25] singlecycle_datapath.pc_plus_4[25] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[26] singlecycle_datapath.pc_plus_4[26] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[27] singlecycle_datapath.pc_plus_4[27] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[28] singlecycle_datapath.pc_plus_4[28] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[29] singlecycle_datapath.pc_plus_4[29] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[30] singlecycle_datapath.pc_plus_4[30] -1 1 -.names singlecycle_datapath.adder_pc_plus_4.result[31] singlecycle_datapath.pc_plus_4[31] -1 1 -.names $true singlecycle_datapath.pc_write_enable -1 1 -.names clock singlecycle_datapath.program_counter.clock -1 1 -.names reset singlecycle_datapath.program_counter.reset -1 1 -.names $true singlecycle_datapath.program_counter.write_enable -1 1 -.names inst[7] singlecycle_datapath.rd_address[0] -1 1 -.names $false singlecycle_datapath.rd_address[1] -1 1 -.names $false singlecycle_datapath.rd_address[2] -1 1 -.names $false singlecycle_datapath.rd_address[3] -1 1 -.names $false singlecycle_datapath.rd_address[4] -1 1 -.names rd_data[0] singlecycle_datapath.rd_data[0] -1 1 -.names rd_data[1] singlecycle_datapath.rd_data[1] -1 1 -.names rd_data[2] singlecycle_datapath.rd_data[2] -1 1 -.names rd_data[3] singlecycle_datapath.rd_data[3] -1 1 -.names rd_data[4] singlecycle_datapath.rd_data[4] -1 1 -.names rd_data[5] singlecycle_datapath.rd_data[5] -1 1 -.names rd_data[6] singlecycle_datapath.rd_data[6] -1 1 -.names rd_data[7] singlecycle_datapath.rd_data[7] -1 1 -.names rd_data[8] singlecycle_datapath.rd_data[8] -1 1 -.names rd_data[9] singlecycle_datapath.rd_data[9] -1 1 -.names rd_data[10] singlecycle_datapath.rd_data[10] -1 1 -.names rd_data[11] singlecycle_datapath.rd_data[11] -1 1 -.names rd_data[12] singlecycle_datapath.rd_data[12] -1 1 -.names rd_data[13] singlecycle_datapath.rd_data[13] -1 1 -.names rd_data[14] singlecycle_datapath.rd_data[14] -1 1 -.names rd_data[15] singlecycle_datapath.rd_data[15] -1 1 -.names rd_data[16] singlecycle_datapath.rd_data[16] -1 1 -.names rd_data[17] singlecycle_datapath.rd_data[17] -1 1 -.names rd_data[18] singlecycle_datapath.rd_data[18] -1 1 -.names rd_data[19] singlecycle_datapath.rd_data[19] -1 1 -.names rd_data[20] singlecycle_datapath.rd_data[20] -1 1 -.names rd_data[21] singlecycle_datapath.rd_data[21] -1 1 -.names rd_data[22] singlecycle_datapath.rd_data[22] -1 1 -.names rd_data[23] singlecycle_datapath.rd_data[23] -1 1 -.names rd_data[24] singlecycle_datapath.rd_data[24] -1 1 -.names rd_data[25] singlecycle_datapath.rd_data[25] -1 1 -.names rd_data[26] singlecycle_datapath.rd_data[26] -1 1 -.names rd_data[27] singlecycle_datapath.rd_data[27] -1 1 -.names rd_data[28] singlecycle_datapath.rd_data[28] -1 1 -.names rd_data[29] singlecycle_datapath.rd_data[29] -1 1 -.names rd_data[30] singlecycle_datapath.rd_data[30] -1 1 -.names rd_data[31] singlecycle_datapath.rd_data[31] -1 1 -.names $false singlecycle_datapath.reg_writeback_select[2] -1 1 -.names reset singlecycle_datapath.reset -1 1 -.names inst[15] singlecycle_datapath.rs1_address[0] -1 1 -.names $false singlecycle_datapath.rs1_address[1] -1 1 -.names $false singlecycle_datapath.rs1_address[2] -1 1 -.names $false singlecycle_datapath.rs1_address[3] -1 1 -.names $false singlecycle_datapath.rs1_address[4] -1 1 -.names rs1_data[0] singlecycle_datapath.rs1_data[0] -1 1 -.names rs1_data[1] singlecycle_datapath.rs1_data[1] -1 1 -.names rs1_data[2] singlecycle_datapath.rs1_data[2] -1 1 -.names rs1_data[3] singlecycle_datapath.rs1_data[3] -1 1 -.names rs1_data[4] singlecycle_datapath.rs1_data[4] -1 1 -.names rs1_data[5] singlecycle_datapath.rs1_data[5] -1 1 -.names rs1_data[6] singlecycle_datapath.rs1_data[6] -1 1 -.names rs1_data[7] singlecycle_datapath.rs1_data[7] -1 1 -.names rs1_data[8] singlecycle_datapath.rs1_data[8] -1 1 -.names rs1_data[9] singlecycle_datapath.rs1_data[9] -1 1 -.names rs1_data[10] singlecycle_datapath.rs1_data[10] -1 1 -.names rs1_data[11] singlecycle_datapath.rs1_data[11] -1 1 -.names rs1_data[12] singlecycle_datapath.rs1_data[12] -1 1 -.names rs1_data[13] singlecycle_datapath.rs1_data[13] -1 1 -.names rs1_data[14] singlecycle_datapath.rs1_data[14] -1 1 -.names rs1_data[15] singlecycle_datapath.rs1_data[15] -1 1 -.names rs1_data[16] singlecycle_datapath.rs1_data[16] -1 1 -.names rs1_data[17] singlecycle_datapath.rs1_data[17] -1 1 -.names rs1_data[18] singlecycle_datapath.rs1_data[18] -1 1 -.names rs1_data[19] singlecycle_datapath.rs1_data[19] -1 1 -.names rs1_data[20] singlecycle_datapath.rs1_data[20] -1 1 -.names rs1_data[21] singlecycle_datapath.rs1_data[21] -1 1 -.names rs1_data[22] singlecycle_datapath.rs1_data[22] -1 1 -.names rs1_data[23] singlecycle_datapath.rs1_data[23] -1 1 -.names rs1_data[24] singlecycle_datapath.rs1_data[24] -1 1 -.names rs1_data[25] singlecycle_datapath.rs1_data[25] -1 1 -.names rs1_data[26] singlecycle_datapath.rs1_data[26] -1 1 -.names rs1_data[27] singlecycle_datapath.rs1_data[27] -1 1 -.names rs1_data[28] singlecycle_datapath.rs1_data[28] -1 1 -.names rs1_data[29] singlecycle_datapath.rs1_data[29] -1 1 -.names rs1_data[30] singlecycle_datapath.rs1_data[30] -1 1 -.names rs1_data[31] singlecycle_datapath.rs1_data[31] -1 1 -.names inst[20] singlecycle_datapath.rs2_address[0] -1 1 -.names $false singlecycle_datapath.rs2_address[1] -1 1 -.names $false singlecycle_datapath.rs2_address[2] -1 1 -.names $false singlecycle_datapath.rs2_address[3] -1 1 -.names $false singlecycle_datapath.rs2_address[4] -1 1 -.names rs2_data[0] singlecycle_datapath.rs2_data[0] -1 1 -.names rs2_data[1] singlecycle_datapath.rs2_data[1] -1 1 -.names rs2_data[2] singlecycle_datapath.rs2_data[2] -1 1 -.names rs2_data[3] singlecycle_datapath.rs2_data[3] -1 1 -.names rs2_data[4] singlecycle_datapath.rs2_data[4] -1 1 -.names rs2_data[5] singlecycle_datapath.rs2_data[5] -1 1 -.names rs2_data[6] singlecycle_datapath.rs2_data[6] -1 1 -.names rs2_data[7] singlecycle_datapath.rs2_data[7] -1 1 -.names rs2_data[8] singlecycle_datapath.rs2_data[8] -1 1 -.names rs2_data[9] singlecycle_datapath.rs2_data[9] -1 1 -.names rs2_data[10] singlecycle_datapath.rs2_data[10] -1 1 -.names rs2_data[11] singlecycle_datapath.rs2_data[11] -1 1 -.names rs2_data[12] singlecycle_datapath.rs2_data[12] -1 1 -.names rs2_data[13] singlecycle_datapath.rs2_data[13] -1 1 -.names rs2_data[14] singlecycle_datapath.rs2_data[14] -1 1 -.names rs2_data[15] singlecycle_datapath.rs2_data[15] -1 1 -.names rs2_data[16] singlecycle_datapath.rs2_data[16] -1 1 -.names rs2_data[17] singlecycle_datapath.rs2_data[17] -1 1 -.names rs2_data[18] singlecycle_datapath.rs2_data[18] -1 1 -.names rs2_data[19] singlecycle_datapath.rs2_data[19] -1 1 -.names rs2_data[20] singlecycle_datapath.rs2_data[20] -1 1 -.names rs2_data[21] singlecycle_datapath.rs2_data[21] -1 1 -.names rs2_data[22] singlecycle_datapath.rs2_data[22] -1 1 -.names rs2_data[23] singlecycle_datapath.rs2_data[23] -1 1 -.names rs2_data[24] singlecycle_datapath.rs2_data[24] -1 1 -.names rs2_data[25] singlecycle_datapath.rs2_data[25] -1 1 -.names rs2_data[26] singlecycle_datapath.rs2_data[26] -1 1 -.names rs2_data[27] singlecycle_datapath.rs2_data[27] -1 1 -.names rs2_data[28] singlecycle_datapath.rs2_data[28] -1 1 -.names rs2_data[29] singlecycle_datapath.rs2_data[29] -1 1 -.names rs2_data[30] singlecycle_datapath.rs2_data[30] -1 1 -.names rs2_data[31] singlecycle_datapath.rs2_data[31] -1 1 -.names rs2_data[0] write_data[0] -1 1 -.names rs2_data[1] write_data[1] -1 1 -.names rs2_data[2] write_data[2] -1 1 -.names rs2_data[3] write_data[3] -1 1 -.names rs2_data[4] write_data[4] -1 1 -.names rs2_data[5] write_data[5] -1 1 -.names rs2_data[6] write_data[6] -1 1 -.names rs2_data[7] write_data[7] -1 1 -.names rs2_data[8] write_data[8] -1 1 -.names rs2_data[9] write_data[9] -1 1 -.names rs2_data[10] write_data[10] -1 1 -.names rs2_data[11] write_data[11] -1 1 -.names rs2_data[12] write_data[12] -1 1 -.names rs2_data[13] write_data[13] -1 1 -.names rs2_data[14] write_data[14] -1 1 -.names rs2_data[15] write_data[15] -1 1 -.names rs2_data[16] write_data[16] -1 1 -.names rs2_data[17] write_data[17] -1 1 -.names rs2_data[18] write_data[18] -1 1 -.names rs2_data[19] write_data[19] -1 1 -.names rs2_data[20] write_data[20] -1 1 -.names rs2_data[21] write_data[21] -1 1 -.names rs2_data[22] write_data[22] -1 1 -.names rs2_data[23] write_data[23] -1 1 -.names rs2_data[24] write_data[24] -1 1 -.names rs2_data[25] write_data[25] -1 1 -.names rs2_data[26] write_data[26] -1 1 -.names rs2_data[27] write_data[27] -1 1 -.names rs2_data[28] write_data[28] -1 1 -.names rs2_data[29] write_data[29] -1 1 -.names rs2_data[30] write_data[30] -1 1 -.names rs2_data[31] write_data[31] -1 1 -.names bus_write_enable write_enable -1 1 -.end diff --git a/openfpga/test_blif/s298.blif b/openfpga/test_blif/s298.blif new file mode 100644 index 000000000..c588a6e0a --- /dev/null +++ b/openfpga/test_blif/s298.blif @@ -0,0 +1,90 @@ +# Benchmark "s298" written by ABC on Tue Mar 12 09:40:31 2019 +.model s298 +.inputs clock G0 G1 G2 +.outputs G117 G132 G66 G118 G133 G67 + +.latch n21 G10 re clock 0 +.latch n26 G11 re clock 0 +.latch n31 G12 re clock 0 +.latch n36 G13 re clock 0 +.latch n41 G14 re clock 0 +.latch n46 G15 re clock 0 +.latch n51 G66 re clock 0 +.latch n55 G67 re clock 0 +.latch n59 G117 re clock 0 +.latch n63 G118 re clock 0 +.latch n67 G132 re clock 0 +.latch n71 G133 re clock 0 +.latch n75 G22 re clock 0 +.latch n80 G23 re clock 0 + +.names n56 n57 G10 n63 +0-0 1 +11- 1 +.names G15 G11 G13 G22 G14 G12 n56 +01---- 1 +0-0--- 1 +0--0-- 1 +0---1- 1 +0----1 1 +-11000 1 +.names G14 G13 G12 G118 G11 n57 +01--- 1 +100-0 1 +1-11- 1 +-1-1- 1 +.names n56 n59_1 G10 n67 +0-0 1 +11- 1 +.names G14 G13 G12 G132 G11 n59_1 +100-0 1 +11-1- 1 +1-11- 1 +.names G0 G10 n21 +00 1 +.names G10 G11 G0 G12 G13 n26 +010-- 1 +1001- 1 +100-0 1 +.names G12 G0 G11 G10 n31 +0011 1 +100- 1 +10-0 1 +.names G13 G0 G11 G12 G10 n36 +00111 1 +1001- 1 +1010- 1 +10--0 1 +.names n65 G14 G0 n41 +000 1 +110 1 +.names G23 G10 G13 G11 G12 n65 +1---- 0 +-1100 0 +.names G0 n56 n46 +00 1 +.names n56 G66 G14 G13 G12 n51 +111-1 1 +11-1- 1 +1-01- 1 +.names n56 G13 G14 G11 G67 G12 n55 +1000-- 1 +10-1-0 1 +111-1- 1 +1-1-11 1 +.names n56 G13 G117 G14 G12 G11 n59 +10-0-- 1 +10--01 1 +1111-- 1 +1-111- 1 +.names n56 G14 G12 G13 G133 G11 n71 +1010-1 1 +111-1- 1 +11-11- 1 +.names G2 G22 G0 n75 +010 1 +100 1 +.names G1 G23 G0 n80 +010 1 +100 1 +.end diff --git a/openfpga/test_script/riscv_core.openfpga b/openfpga/test_script/s298.openfpga similarity index 74% rename from openfpga/test_script/riscv_core.openfpga rename to openfpga/test_script/s298.openfpga index d2614df44..d9d1e15ca 100644 --- a/openfpga/test_script/riscv_core.openfpga +++ b/openfpga/test_script/s298.openfpga @@ -1,2 +1,2 @@ # Run VPR for the RISC-V core -vpr ./test_arch/k6_frac_N10_frac_chain_depop50_mem32K_40nm.xml ./test_blif/riscv_core_lut6.blif +vpr ./test_arch/k6_frac_N10_frac_chain_depop50_mem32K_40nm.xml ./test_blif/s298.blif From 01c80b9126686504884b3960557ae33f638b0555 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 27 Jan 2020 13:39:13 -0700 Subject: [PATCH 024/645] add sample architecture to be used for Openfpga --- .../k6_N10_40nm_openfpga.xml | 234 ++++++++++++ openfpga/test_vpr_arch/k6_N10_40nm.xml | 308 +++++++++++++++ openfpga/test_vpr_arch/k6_frac_N10_40nm.xml | 351 ++++++++++++++++++ ...rac_N10_frac_chain_depop50_mem32K_40nm.xml | 0 4 files changed, 893 insertions(+) create mode 100644 openfpga/test_openfpga_arch/k6_N10_40nm_openfpga.xml create mode 100644 openfpga/test_vpr_arch/k6_N10_40nm.xml create mode 100644 openfpga/test_vpr_arch/k6_frac_N10_40nm.xml rename openfpga/{test_arch => test_vpr_arch}/k6_frac_N10_frac_chain_depop50_mem32K_40nm.xml (100%) diff --git a/openfpga/test_openfpga_arch/k6_N10_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_N10_40nm_openfpga.xml new file mode 100644 index 000000000..23607e234 --- /dev/null +++ b/openfpga/test_openfpga_arch/k6_N10_40nm_openfpga.xml @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + + + + + 10e-12 5e-12 5e-12 + + + 10e-12 5e-12 5e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga/test_vpr_arch/k6_N10_40nm.xml b/openfpga/test_vpr_arch/k6_N10_40nm.xml new file mode 100644 index 000000000..d2501969d --- /dev/null +++ b/openfpga/test_vpr_arch/k6_N10_40nm.xml @@ -0,0 +1,308 @@ + + + + + + + + + + + + + + + + + + + + + + + + + io.outpad io.inpad io.clock + io.outpad io.inpad io.clock + io.outpad io.inpad io.clock + io.outpad io.inpad io.clock + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml new file mode 100644 index 000000000..4da00d67a --- /dev/null +++ b/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml @@ -0,0 +1,351 @@ + + + + + + + + + + + + + + + + io.outpad io.inpad io.clock + io.outpad io.inpad io.clock + io.outpad io.inpad io.clock + io.outpad io.inpad io.clock + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 235e-12 + 235e-12 + 235e-12 + 235e-12 + 235e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga/test_arch/k6_frac_N10_frac_chain_depop50_mem32K_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_frac_chain_depop50_mem32K_40nm.xml similarity index 100% rename from openfpga/test_arch/k6_frac_N10_frac_chain_depop50_mem32K_40nm.xml rename to openfpga/test_vpr_arch/k6_frac_N10_frac_chain_depop50_mem32K_40nm.xml From 46b53f20ba8d440e2d523296fe6662d7b3344713 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 27 Jan 2020 13:44:28 -0700 Subject: [PATCH 025/645] add ezgl support in the CMakelist for VPR8 --- CMakeLists.txt | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index e1c967e43..f46955c8b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,6 +46,10 @@ project("OPENFPGA" C CXX) option(ENABLE_VPR_GRAPHICS "Enables VPR graphics" ON) option(ENABLE_VPR_GRAPHICS "Enables build with librtlnumber" OFF) +#Allow the user to decide weather to compile the graphics library +set(VPR_USE_EZGL "auto" CACHE STRING "Specify whether vpr uses the graphics library") +set_property(CACHE VPR_USE_EZGL PROPERTY STRINGS auto off on) + # Version number set(OPENFPGA_VERSION_MAJOR 1) set(OPENFPGA_VERSION_MINOR 0) @@ -154,6 +158,27 @@ message(STATUS "CMAKE_CXX_FLAGS: ${CMAKE_CXX_FLAGS}") # enable_testing() +# set VPR_USE_EZGL in the root of VTR to decide whether to add +# subdirectory of graphics library, which prevents users +# without gtk/x11 libraries installed to build. VPR_USE_EZGL is +# being used in both the vpr CMakeLists and libs/EXTERNAL CMakeLists. +# +# check if GTK and X11 are installed and turn on/off graphics +if (VPR_USE_EZGL STREQUAL "auto") + find_package(PkgConfig REQUIRED) + pkg_check_modules(GTK3 QUIET gtk+-3.0) + pkg_check_modules(X11 QUIET x11) + + if(GTK3_FOUND AND X11_FOUND) + set(VPR_USE_EZGL "on") + message(STATUS "VPR Graphics: Enabled") + else() + set(VPR_USE_EZGL "off") + message(STATUS "VPR Graphics: Disabled (required libraries missing, on debia/ubuntu try: sudo apt install libgtk-3 libx11-dev") + endif() +endif() + + # # Sub-projects # From a6fbbce33e705b990672aef9f7bf3a554f980243 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 27 Jan 2020 15:31:12 -0700 Subject: [PATCH 026/645] start developing the openfpga arch binding to vpr --- libs/libarchfpga/src/physical_types.h | 16 ++++ openfpga/src/base/openfpga_context.h | 5 ++ openfpga/src/base/openfpga_link_arch.cpp | 86 ++++++++++++++++++++ openfpga/src/base/openfpga_link_arch.h | 22 +++++ openfpga/src/base/openfpga_setup_command.cpp | 18 +++- openfpga/src/base/vpr_pb_type_annotation.cpp | 18 ++++ openfpga/src/base/vpr_pb_type_annotation.h | 39 +++++++++ 7 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 openfpga/src/base/openfpga_link_arch.cpp create mode 100644 openfpga/src/base/openfpga_link_arch.h create mode 100644 openfpga/src/base/vpr_pb_type_annotation.cpp create mode 100644 openfpga/src/base/vpr_pb_type_annotation.h diff --git a/libs/libarchfpga/src/physical_types.h b/libs/libarchfpga/src/physical_types.h index 36901e6f0..a1e750d69 100644 --- a/libs/libarchfpga/src/physical_types.h +++ b/libs/libarchfpga/src/physical_types.h @@ -789,6 +789,22 @@ struct t_pb_type { t_pin_to_pin_annotation* annotations = nullptr; /* [0..num_annotations-1] */ int num_annotations = 0; + /************************************************************************ + * Xifan Tang: Add useful methods for pb_type + * - A pb_type is considered to be primitive when it has zero modes + * However, this not always true. An exception is the LUT_CLASS + * VPR added two modes by default to a LUT pb_type. Therefore, + * for LUT_CLASS, it is a primitive when it is binded to a blif model + * - A pb_type is the root pb_type when it has no parent mode + ************************************************************************/ + bool is_primitive() const { + if (LUT_CLASS == class_type) { + return nullptr != blif_model; + } + return 0 == num_modes; + } + bool is_root() const { return parent_mode == nullptr; } + /* Power related members */ t_pb_type_power* pb_type_power = nullptr; diff --git a/openfpga/src/base/openfpga_context.h b/openfpga/src/base/openfpga_context.h index c66012615..185a08c5c 100644 --- a/openfpga/src/base/openfpga_context.h +++ b/openfpga/src/base/openfpga_context.h @@ -3,6 +3,7 @@ #include "vpr_context.h" #include "openfpga_arch.h" +#include "vpr_pb_type_annotation.h" /******************************************************************** * This file includes the declaration of the date structure @@ -34,11 +35,15 @@ class OpenfpgaContext : public Context { public: /* Public accessors */ const openfpga::Arch& arch() const { return arch_; } + const openfpga::VprPbTypeAnnotation& vpr_pb_type_annotation() const { return vpr_pb_type_annotation_; } public: /* Public mutators */ openfpga::Arch& mutable_arch() { return arch_; } + openfpga::VprPbTypeAnnotation& mutable_vpr_pb_type_annotation() { return vpr_pb_type_annotation_; } private: /* Internal data */ /* Data structure to store information from read_openfpga_arch library */ openfpga::Arch arch_; + /* Annotation to pb_type of VPR */ + openfpga::VprPbTypeAnnotation vpr_pb_type_annotation_; }; #endif diff --git a/openfpga/src/base/openfpga_link_arch.cpp b/openfpga/src/base/openfpga_link_arch.cpp new file mode 100644 index 000000000..4af1f00ac --- /dev/null +++ b/openfpga/src/base/openfpga_link_arch.cpp @@ -0,0 +1,86 @@ +/******************************************************************** + * This file includes functions to read an OpenFPGA architecture file + * which are built on the libarchopenfpga library + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_log.h" + +#include "vpr_pb_type_annotation.h" +#include "openfpga_link_arch.h" + +/* Include global variables of VPR */ +#include "globals.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * This function will recursively traverse pb_type graph to ensure + * 1. there is only a physical mode under each pb_type + * 2. physical mode appears only when its parent is a physical mode. + *******************************************************************/ +static +void rec_check_pb_type_physical_mode(const t_pb_type& cur_pb_type) { + /* We do not check any primitive pb_type */ + if (true == cur_pb_type.is_primitive()) { + return; + } + + /* For non-primitive pb_type: we should iterate over each mode + * Ensure there is only one physical mode + */ + + /* Traverse all the modes for identifying idle mode */ + for (int imode = 0; cur_pb_type.num_modes; ++imode) { + /* Check each pb_type_child */ + for (int ichild = 0; ichild < cur_pb_type.modes[imode].num_pb_type_children; ++ichild) { + rec_check_pb_type_physical_mode(cur_pb_type.modes[imode].pb_type_children[ichild]); + } + } +} + +/******************************************************************** + * This function will + * - identify the physical pb_type for each multi-mode pb_type in + * VPR pb_type graph + * - identify the physical pb_type for operating pb_types in VPR + *******************************************************************/ +static +void build_physical_pb_type_vpr_annotation(const DeviceContext& vpr_device_ctx, + const Arch& openfpga_arch, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + /* Walk through the pb_type annotation stored in the openfpga arch */ + for (const PbTypeAnnotation& pb_type_annotation : openfpga_arch.pb_type_annotations) { + /* Pb type information are located at the logic_block_types in the device context of VPR + * We iterate over the vectors and annotate the pb_type + */ + for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { + /* By pass nullptr for pb_type head */ + if (nullptr == lb_type.pb_type) { + continue; + } + } + } +} + +/******************************************************************** + * Top-level function to link openfpga architecture to VPR, including: + * - physical pb_type + * - idle pb_type + * - circuit models for pb_type, pb interconnect and routing architecture + *******************************************************************/ +void link_arch(OpenfpgaContext& openfpga_context) { + + /* Annotate physical pb_type in the VPR pb_type graph */ + build_physical_pb_type_vpr_annotation(g_vpr_ctx.device(), openfpga_context.arch(), + openfpga_context.mutable_vpr_pb_type_annotation()); + + /* Annotate idle pb_type in the VPR pb_type graph */ + + /* Link physical pb_type to circuit model */ + + /* Link routing architecture to circuit model */ +} + +} /* end namespace openfpga */ + diff --git a/openfpga/src/base/openfpga_link_arch.h b/openfpga/src/base/openfpga_link_arch.h new file mode 100644 index 000000000..be957c5b0 --- /dev/null +++ b/openfpga/src/base/openfpga_link_arch.h @@ -0,0 +1,22 @@ +#ifndef OPENFPGA_LINK_ARCH_COMMAND_H +#define OPENFPGA_LINK_ARCH_COMMAND_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "command.h" +#include "command_context.h" +#include "openfpga_context.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void link_arch(OpenfpgaContext& openfpga_context); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/base/openfpga_setup_command.cpp b/openfpga/src/base/openfpga_setup_command.cpp index 322378b07..fdc7a4e98 100644 --- a/openfpga/src/base/openfpga_setup_command.cpp +++ b/openfpga/src/base/openfpga_setup_command.cpp @@ -4,6 +4,7 @@ * - read_openfpga_arch : read OpenFPGA architecture file *******************************************************************/ #include "openfpga_read_arch.h" +#include "openfpga_link_arch.h" #include "openfpga_setup_command.h" /* begin namespace openfpga */ @@ -36,8 +37,23 @@ void add_openfpga_setup_commands(openfpga::Shell& shell) { ShellCommandId shell_cmd_write_arch_id = shell.add_command(shell_cmd_write_arch, "write OpenFPGA architecture file"); shell.set_command_class(shell_cmd_write_arch_id, openfpga_setup_cmd_class); shell.set_command_const_execute_function(shell_cmd_write_arch_id, write_arch); - /* The 'write_openfpga_arch' command should be executed before 'read_openfpga_arch' */ + /* The 'write_openfpga_arch' command should NOT be executed before 'read_openfpga_arch' */ shell.set_command_dependency(shell_cmd_write_arch_id, std::vector(1, shell_cmd_read_arch_id)); + + /* Command 'link_openfpga_arch' */ + Command shell_cmd_link_openfpga_arch("link_openfpga_arch"); + + /* Add command 'link_openfpga_arch' to the Shell */ + ShellCommandId shell_cmd_link_openfpga_arch_id = shell.add_command(shell_cmd_link_openfpga_arch, "Bind OpenFPGA architecture to VPR"); + shell.set_command_class(shell_cmd_link_openfpga_arch_id, openfpga_setup_cmd_class); + shell.set_command_execute_function(shell_cmd_link_openfpga_arch_id, link_arch); + /* The 'link_openfpga_arch' command should NOT be executed before 'read_openfpga_arch' and 'vpr' */ + const ShellCommandId& shell_cmd_vpr_id = shell.command("vpr"); + std::vector cmd_dependency_link_openfpga_arch; + cmd_dependency_link_openfpga_arch.push_back(shell_cmd_read_arch_id); + cmd_dependency_link_openfpga_arch.push_back(shell_cmd_vpr_id); + shell.set_command_dependency(shell_cmd_link_openfpga_arch_id, cmd_dependency_link_openfpga_arch); + } } /* end namespace openfpga */ diff --git a/openfpga/src/base/vpr_pb_type_annotation.cpp b/openfpga/src/base/vpr_pb_type_annotation.cpp new file mode 100644 index 000000000..70532cae4 --- /dev/null +++ b/openfpga/src/base/vpr_pb_type_annotation.cpp @@ -0,0 +1,18 @@ +/************************************************************************ + * Member functions for class VprPbTypeAnnotation + ***********************************************************************/ +#include "vtr_log.h" +#include "vtr_assert.h" +#include "vpr_pb_type_annotation.h" + +/* namespace openfpga begins */ +namespace openfpga { + +/************************************************************************ + * Constructors + ***********************************************************************/ +VprPbTypeAnnotation::VprPbTypeAnnotation() { + return; +} + +} /* End namespace openfpga*/ diff --git a/openfpga/src/base/vpr_pb_type_annotation.h b/openfpga/src/base/vpr_pb_type_annotation.h new file mode 100644 index 000000000..8fb6318b8 --- /dev/null +++ b/openfpga/src/base/vpr_pb_type_annotation.h @@ -0,0 +1,39 @@ +#ifndef VPR_PB_TYPE_ANNOTATION_H +#define VPR_PB_TYPE_ANNOTATION_H + +/******************************************************************** + * Include header files required by the data structure definition + *******************************************************************/ +#include + +/* Header from archfpga library */ +#include "physical_types.h" + +/* Header from openfpgautil library */ +#include "circuit_library.h" + +/* Begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * This is the critical data structure to link the pb_type in VPR + * to openfpga annotations + * With a given pb_type pointer, it aims to identify: + * 1. if the pb_type is a physical pb_type or a operating pb_type + * 2. what is the circuit model id linked to a physical pb_type + * 3. what is the physical pb_type for an operating pb_type + * 4. what is the mode pointer that represents the physical mode for a pb_type + *******************************************************************/ +class VprPbTypeAnnotation { + public: /* Constructor */ + VprPbTypeAnnotation(); + private: /* Internal data */ + std::map is_physical_pb_types_; + std::map physical_pb_types_; + std::map physical_pb_modes_; + std::map pb_type_circuit_models_; +}; + +} /* End namespace openfpga*/ + +#endif From 5ecb7716736278f322c5156f6b5ec8014c448d2c Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 27 Jan 2020 17:43:22 -0700 Subject: [PATCH 027/645] debugging the annotation to physical mode of pb_types --- libopenfpga/libopenfpgashell/src/shell.tpp | 4 + libs/libarchfpga/src/physical_types.h | 16 --- openfpga/src/base/openfpga_link_arch.cpp | 110 +++++++++++++++++-- openfpga/src/base/openfpga_link_arch.h | 2 - openfpga/src/base/openfpga_setup_command.cpp | 2 +- openfpga/src/base/pb_type_utils.cpp | 59 ++++++++++ openfpga/src/base/pb_type_utils.h | 26 +++++ openfpga/src/base/vpr_pb_type_annotation.cpp | 26 +++++ openfpga/src/base/vpr_pb_type_annotation.h | 4 + openfpga/src/vpr_wrapper/vpr_command.cpp | 4 +- openfpga/test_script/s298.openfpga | 10 +- 11 files changed, 231 insertions(+), 32 deletions(-) create mode 100644 openfpga/src/base/pb_type_utils.cpp create mode 100644 openfpga/src/base/pb_type_utils.h diff --git a/libopenfpga/libopenfpgashell/src/shell.tpp b/libopenfpga/libopenfpgashell/src/shell.tpp index 0a52fda0b..9d5f8833e 100644 --- a/libopenfpga/libopenfpgashell/src/shell.tpp +++ b/libopenfpga/libopenfpgashell/src/shell.tpp @@ -390,6 +390,10 @@ void Shell::execute_command(const char* cmd_line, free(argv[itok]); } free(argv); + + /* Change the status of the command */ + command_status_[cmd_id] = true; + /* Finish for macro command, return */ return; } diff --git a/libs/libarchfpga/src/physical_types.h b/libs/libarchfpga/src/physical_types.h index a1e750d69..36901e6f0 100644 --- a/libs/libarchfpga/src/physical_types.h +++ b/libs/libarchfpga/src/physical_types.h @@ -789,22 +789,6 @@ struct t_pb_type { t_pin_to_pin_annotation* annotations = nullptr; /* [0..num_annotations-1] */ int num_annotations = 0; - /************************************************************************ - * Xifan Tang: Add useful methods for pb_type - * - A pb_type is considered to be primitive when it has zero modes - * However, this not always true. An exception is the LUT_CLASS - * VPR added two modes by default to a LUT pb_type. Therefore, - * for LUT_CLASS, it is a primitive when it is binded to a blif model - * - A pb_type is the root pb_type when it has no parent mode - ************************************************************************/ - bool is_primitive() const { - if (LUT_CLASS == class_type) { - return nullptr != blif_model; - } - return 0 == num_modes; - } - bool is_root() const { return parent_mode == nullptr; } - /* Power related members */ t_pb_type_power* pb_type_power = nullptr; diff --git a/openfpga/src/base/openfpga_link_arch.cpp b/openfpga/src/base/openfpga_link_arch.cpp index 4af1f00ac..239829e8e 100644 --- a/openfpga/src/base/openfpga_link_arch.cpp +++ b/openfpga/src/base/openfpga_link_arch.cpp @@ -3,9 +3,11 @@ * which are built on the libarchopenfpga library *******************************************************************/ /* Headers from vtrutil library */ +#include "vtr_assert.h" #include "vtr_log.h" #include "vpr_pb_type_annotation.h" +#include "pb_type_utils.h" #include "openfpga_link_arch.h" /* Include global variables of VPR */ @@ -14,15 +16,57 @@ /* begin namespace openfpga */ namespace openfpga { +/******************************************************************** + * This function will traverse pb_type graph from its top to find + * a pb_type with a given name as well as its hierarchy + *******************************************************************/ +static +t_pb_type* try_find_pb_type_with_given_path(t_pb_type* top_pb_type, + const std::vector& target_pb_type_names, + const std::vector& target_pb_mode_names) { + /* Ensure that number of parent names and modes matches */ + VTR_ASSERT_SAFE(target_pb_type_names.size() == target_pb_mode_names.size() + 1); + + t_pb_type* cur_pb_type = top_pb_type; + + /* We start from the first element of the parent names and parent modes. + * If the pb_type does not match in name, we fail + * If we cannot find a mode match the name, we fail + */ + for (size_t i = 0; i < target_pb_type_names.size() - 1; ++i) { + /* If this level does not match, search fail */ + if (target_pb_type_names[i] != std::string(cur_pb_type->name)) { + return nullptr; + } + /* Find if the mode matches */ + t_mode* cur_mode = find_pb_type_mode(cur_pb_type, target_pb_mode_names[i].c_str()); + if (nullptr == cur_mode) { + return nullptr; + } + /* Go to the next level of pb_type */ + cur_pb_type = find_mode_child_pb_type(cur_mode, target_pb_type_names[i + 1].c_str()); + if (nullptr == cur_pb_type) { + return nullptr; + } + /* If this is already the last pb_type in the list, this is what we want */ + if (i == target_pb_type_names.size() - 1) { + return cur_pb_type; + } + } + + /* Reach here, it means we find nothing */ + return nullptr; +} + /******************************************************************** * This function will recursively traverse pb_type graph to ensure * 1. there is only a physical mode under each pb_type * 2. physical mode appears only when its parent is a physical mode. *******************************************************************/ static -void rec_check_pb_type_physical_mode(const t_pb_type& cur_pb_type) { +void rec_check_pb_type_physical_mode(t_pb_type* cur_pb_type) { /* We do not check any primitive pb_type */ - if (true == cur_pb_type.is_primitive()) { + if (true == is_primitive_pb_type(cur_pb_type)) { return; } @@ -31,10 +75,10 @@ void rec_check_pb_type_physical_mode(const t_pb_type& cur_pb_type) { */ /* Traverse all the modes for identifying idle mode */ - for (int imode = 0; cur_pb_type.num_modes; ++imode) { + for (int imode = 0; cur_pb_type->num_modes; ++imode) { /* Check each pb_type_child */ - for (int ichild = 0; ichild < cur_pb_type.modes[imode].num_pb_type_children; ++ichild) { - rec_check_pb_type_physical_mode(cur_pb_type.modes[imode].pb_type_children[ichild]); + for (int ichild = 0; ichild < cur_pb_type->modes[imode].num_pb_type_children; ++ichild) { + rec_check_pb_type_physical_mode(&(cur_pb_type->modes[imode].pb_type_children[ichild])); } } } @@ -46,19 +90,68 @@ void rec_check_pb_type_physical_mode(const t_pb_type& cur_pb_type) { * - identify the physical pb_type for operating pb_types in VPR *******************************************************************/ static -void build_physical_pb_type_vpr_annotation(const DeviceContext& vpr_device_ctx, +void build_vpr_physical_pb_type_annotation(const DeviceContext& vpr_device_ctx, const Arch& openfpga_arch, VprPbTypeAnnotation& vpr_pb_type_annotation) { /* Walk through the pb_type annotation stored in the openfpga arch */ for (const PbTypeAnnotation& pb_type_annotation : openfpga_arch.pb_type_annotations) { + /* Since our target is to annotate the physical mode name, + * we can skip those has not physical mode defined + */ + if (true == pb_type_annotation.physical_mode_name().empty()) { + continue; + } + + /* Identify if the pb_type is operating or physical, + * For operating pb_type, get the full name of operating pb_type + * For physical pb_type, get the full name of physical pb_type + */ + std::vector target_pb_type_names; + std::vector target_pb_mode_names; + + if (true == pb_type_annotation.is_operating_pb_type()) { + target_pb_type_names = pb_type_annotation.operating_parent_pb_type_names(); + target_pb_type_names.push_back(pb_type_annotation.operating_pb_type_name()); + target_pb_mode_names = pb_type_annotation.operating_parent_mode_names(); + } + + if (true == pb_type_annotation.is_physical_pb_type()) { + target_pb_type_names = pb_type_annotation.physical_parent_pb_type_names(); + target_pb_type_names.push_back(pb_type_annotation.physical_pb_type_name()); + target_pb_mode_names = pb_type_annotation.physical_parent_mode_names(); + } + + /* We must have at least one pb_type in the list */ + VTR_ASSERT_SAFE(0 < target_pb_type_names.size()); + /* Pb type information are located at the logic_block_types in the device context of VPR - * We iterate over the vectors and annotate the pb_type + * We iterate over the vectors and find the pb_type matches the parent_pb_type_name */ for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { /* By pass nullptr for pb_type head */ if (nullptr == lb_type.pb_type) { continue; } + /* Check the name of the top-level pb_type, if it does not match, we can bypass */ + if (target_pb_type_names[0] != std::string(lb_type.pb_type->name)) { + continue; + } + /* Match the name in the top-level, we go further to search the pb_type in the graph */ + t_pb_type* target_pb_type = try_find_pb_type_with_given_path(lb_type.pb_type, target_pb_type_names, + target_pb_mode_names); + if (nullptr == target_pb_type) { + /* Not found, error out! */ + VTR_LOG_ERROR("Unable to find the pb_type '%s' in VPR architecture definition!\n", + target_pb_type_names.back().c_str()); + } + + /* Found, we update the annotation by assigning the physical mode */ + t_mode* physical_mode = find_pb_type_mode(target_pb_type, pb_type_annotation.physical_mode_name().c_str()); + vpr_pb_type_annotation.add_pb_type_physical_mode(target_pb_type, physical_mode); + + /* Give a message */ + VTR_LOG("Annotate pb_type '%s' with physical mode '%s'\n", + target_pb_type->name, physical_mode->name); } } } @@ -72,7 +165,7 @@ void build_physical_pb_type_vpr_annotation(const DeviceContext& vpr_device_ctx, void link_arch(OpenfpgaContext& openfpga_context) { /* Annotate physical pb_type in the VPR pb_type graph */ - build_physical_pb_type_vpr_annotation(g_vpr_ctx.device(), openfpga_context.arch(), + build_vpr_physical_pb_type_annotation(g_vpr_ctx.device(), openfpga_context.arch(), openfpga_context.mutable_vpr_pb_type_annotation()); /* Annotate idle pb_type in the VPR pb_type graph */ @@ -83,4 +176,3 @@ void link_arch(OpenfpgaContext& openfpga_context) { } } /* end namespace openfpga */ - diff --git a/openfpga/src/base/openfpga_link_arch.h b/openfpga/src/base/openfpga_link_arch.h index be957c5b0..c36c57711 100644 --- a/openfpga/src/base/openfpga_link_arch.h +++ b/openfpga/src/base/openfpga_link_arch.h @@ -4,8 +4,6 @@ /******************************************************************** * Include header files that are required by function declaration *******************************************************************/ -#include "command.h" -#include "command_context.h" #include "openfpga_context.h" /******************************************************************** diff --git a/openfpga/src/base/openfpga_setup_command.cpp b/openfpga/src/base/openfpga_setup_command.cpp index fdc7a4e98..af078446a 100644 --- a/openfpga/src/base/openfpga_setup_command.cpp +++ b/openfpga/src/base/openfpga_setup_command.cpp @@ -48,7 +48,7 @@ void add_openfpga_setup_commands(openfpga::Shell& shell) { shell.set_command_class(shell_cmd_link_openfpga_arch_id, openfpga_setup_cmd_class); shell.set_command_execute_function(shell_cmd_link_openfpga_arch_id, link_arch); /* The 'link_openfpga_arch' command should NOT be executed before 'read_openfpga_arch' and 'vpr' */ - const ShellCommandId& shell_cmd_vpr_id = shell.command("vpr"); + const ShellCommandId& shell_cmd_vpr_id = shell.command(std::string("vpr")); std::vector cmd_dependency_link_openfpga_arch; cmd_dependency_link_openfpga_arch.push_back(shell_cmd_read_arch_id); cmd_dependency_link_openfpga_arch.push_back(shell_cmd_vpr_id); diff --git a/openfpga/src/base/pb_type_utils.cpp b/openfpga/src/base/pb_type_utils.cpp new file mode 100644 index 000000000..8eb3c42a1 --- /dev/null +++ b/openfpga/src/base/pb_type_utils.cpp @@ -0,0 +1,59 @@ +/******************************************************************** + * This file includes most utilized functions for the pb_type + * and pb_graph_node data structure in the OpenFPGA context + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_log.h" + +#include "pb_type_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/************************************************************************ + * A pb_type is considered to be primitive when it has zero modes + * However, this not always true. An exception is the LUT_CLASS + * VPR added two modes by default to a LUT pb_type. Therefore, + * for LUT_CLASS, it is a primitive when it is binded to a blif model + ************************************************************************/ +bool is_primitive_pb_type(t_pb_type* pb_type) { + if (LUT_CLASS == pb_type->class_type) { + return nullptr != pb_type->blif_model; + } + return 0 == pb_type->num_modes; +} + +/************************************************************************ + * A pb_type is the root pb_type when it has no parent mode + ************************************************************************/ +bool is_root_pb_type(t_pb_type* pb_type) { + return pb_type->parent_mode == nullptr; +} + +/************************************************************************ + * With a given mode name, find the mode pointer + ************************************************************************/ +t_mode* find_pb_type_mode(t_pb_type* pb_type, const char* mode_name) { + for (int i = 0; i < pb_type->num_modes; ++i) { + if (std::string(mode_name) == std::string(pb_type->modes[i].name)) { + return &(pb_type->modes[i]); + } + } + /* Note found, return a nullptr */ + return nullptr; +} + +/************************************************************************ + * With a given pb_type name, find the pb_type pointer + ************************************************************************/ +t_pb_type* find_mode_child_pb_type(t_mode* mode, const char* child_name) { + for (int i = 0; i < mode->num_pb_type_children; ++i) { + if (std::string(child_name) == std::string(mode->pb_type_children[i].name)) { + return &(mode->pb_type_children[i]); + } + } + /* Note found, return a nullptr */ + return nullptr; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/base/pb_type_utils.h b/openfpga/src/base/pb_type_utils.h new file mode 100644 index 000000000..c0c1881d5 --- /dev/null +++ b/openfpga/src/base/pb_type_utils.h @@ -0,0 +1,26 @@ +#ifndef PB_TYPE_UTILS_H +#define PB_TYPE_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "physical_types.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +bool is_primitive_pb_type(t_pb_type* pb_type); + +bool is_root_pb_type(t_pb_type* pb_type); + +t_mode* find_pb_type_mode(t_pb_type* pb_type, const char* mode_name); + +t_pb_type* find_mode_child_pb_type(t_mode* mode, const char* child_name); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/base/vpr_pb_type_annotation.cpp b/openfpga/src/base/vpr_pb_type_annotation.cpp index 70532cae4..1d4beb1ff 100644 --- a/openfpga/src/base/vpr_pb_type_annotation.cpp +++ b/openfpga/src/base/vpr_pb_type_annotation.cpp @@ -15,4 +15,30 @@ VprPbTypeAnnotation::VprPbTypeAnnotation() { return; } +/************************************************************************ + * Public mutators + ***********************************************************************/ +t_mode* VprPbTypeAnnotation::physical_mode(t_pb_type* pb_type) const { + /* Ensure that the pb_type is in the list */ + std::map::const_iterator it = physical_pb_modes_.find(pb_type); + if (it == physical_pb_modes_.end()) { + return nullptr; + } + return physical_pb_modes_.at(pb_type); +} + +/************************************************************************ + * Public mutators + ***********************************************************************/ +void VprPbTypeAnnotation::add_pb_type_physical_mode(t_pb_type* pb_type, t_mode* physical_mode) { + /* Warn any override attempt */ + std::map::const_iterator it = physical_pb_modes_.find(pb_type); + if (it != physical_pb_modes_.end()) { + VTR_LOG_WARN("Override the annotation between pb_type '%s' and it physical mode '%s'!\n", + pb_type->name, physical_mode->name); + } + + physical_pb_modes_[pb_type] = physical_mode; +} + } /* End namespace openfpga*/ diff --git a/openfpga/src/base/vpr_pb_type_annotation.h b/openfpga/src/base/vpr_pb_type_annotation.h index 8fb6318b8..5410a7ca9 100644 --- a/openfpga/src/base/vpr_pb_type_annotation.h +++ b/openfpga/src/base/vpr_pb_type_annotation.h @@ -27,6 +27,10 @@ namespace openfpga { class VprPbTypeAnnotation { public: /* Constructor */ VprPbTypeAnnotation(); + public: /* Public accessors */ + t_mode* physical_mode(t_pb_type* pb_type) const; + public: /* Public mutators */ + void add_pb_type_physical_mode(t_pb_type* pb_type, t_mode* physical_mode); private: /* Internal data */ std::map is_physical_pb_types_; std::map physical_pb_types_; diff --git a/openfpga/src/vpr_wrapper/vpr_command.cpp b/openfpga/src/vpr_wrapper/vpr_command.cpp index 5baa1ec84..d87ffa32b 100644 --- a/openfpga/src/vpr_wrapper/vpr_command.cpp +++ b/openfpga/src/vpr_wrapper/vpr_command.cpp @@ -10,13 +10,13 @@ namespace openfpga { void add_vpr_commands(openfpga::Shell& shell) { /* Add a new class of commands */ - ShellCommandClassId arith_cmd_class = shell.add_command_class("VPR"); + ShellCommandClassId vpr_cmd_class = shell.add_command_class("VPR"); /* Create a macro command of 'vpr' which will call the main engine of vpr */ Command shell_cmd_vpr("vpr"); ShellCommandId shell_cmd_vpr_id = shell.add_command(shell_cmd_vpr, "Start VPR core engine to pack, place and route a BLIF design on a FPGA architecture"); - shell.set_command_class(shell_cmd_vpr_id, arith_cmd_class); + shell.set_command_class(shell_cmd_vpr_id, vpr_cmd_class); shell.set_command_execute_function(shell_cmd_vpr_id, vpr::vpr); } diff --git a/openfpga/test_script/s298.openfpga b/openfpga/test_script/s298.openfpga index d9d1e15ca..ad12f4807 100644 --- a/openfpga/test_script/s298.openfpga +++ b/openfpga/test_script/s298.openfpga @@ -1,2 +1,8 @@ -# Run VPR for the RISC-V core -vpr ./test_arch/k6_frac_N10_frac_chain_depop50_mem32K_40nm.xml ./test_blif/s298.blif +# Run VPR for the s298 design +vpr ./test_vpr_arch/k6_N10_40nm.xml ./test_blif/s298.blif + +# Read OpenFPGA architecture definition +read_openfpga_arch -f ./test_openfpga_arch/k6_N10_40nm_openfpga.xml + +# Annotate the OpenFPGA architecture to VPR data base +link_openfpga_arch From df056f5d70c9cfe67b71efb2ca2077f978664006 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 27 Jan 2020 17:56:24 -0700 Subject: [PATCH 028/645] openfpga shell will stay in interactive mode after executing a script --- libopenfpga/libopenfpgashell/src/shell.h | 2 +- libopenfpga/libopenfpgashell/src/shell.tpp | 17 +++++++++++------ openfpga/src/base/openfpga_link_arch.cpp | 1 + 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/libopenfpga/libopenfpgashell/src/shell.h b/libopenfpga/libopenfpgashell/src/shell.h index 415fc1b1c..427580010 100644 --- a/libopenfpga/libopenfpgashell/src/shell.h +++ b/libopenfpga/libopenfpgashell/src/shell.h @@ -115,7 +115,7 @@ class Shell { bool valid_command_class_id(const ShellCommandClassId& cmd_class_id) const; public: /* Public executors */ /* Start the interactive mode, where users will type-in command by command */ - void run_interactive_mode(T& context); + void run_interactive_mode(T& context, const bool& quiet_mode = false); /* Start the script mode, where users provide a file which includes all the commands to run */ void run_script_mode(const char* script_file_name, T& context); /* Print all the commands by their classes. This is actually the help desk */ diff --git a/libopenfpga/libopenfpgashell/src/shell.tpp b/libopenfpga/libopenfpgashell/src/shell.tpp index 9d5f8833e..72ebe5168 100644 --- a/libopenfpga/libopenfpgashell/src/shell.tpp +++ b/libopenfpga/libopenfpgashell/src/shell.tpp @@ -239,13 +239,15 @@ ShellCommandClassId Shell::add_command_class(const char* name) { * Public executors ***********************************************************************/ template -void Shell::run_interactive_mode(T& context) { - VTR_LOG("Start interactive mode of %s...\n", - name().c_str()); +void Shell::run_interactive_mode(T& context, const bool& quiet_mode) { + if (false == quiet_mode) { + VTR_LOG("Start interactive mode of %s...\n", + name().c_str()); - /* Print the title of the shell */ - if (!title().empty()) { - VTR_LOG("%s\n", title().c_str()); + /* Print the title of the shell */ + if (!title().empty()) { + VTR_LOG("%s\n", title().c_str()); + } } /* Wait for users input and execute the command */ @@ -306,6 +308,9 @@ void Shell::run_script_mode(const char* script_file_name, T& context) { } } fp.close(); + + /* Return to interactive mode, stay tuned */ + run_interactive_mode(context, true); } template diff --git a/openfpga/src/base/openfpga_link_arch.cpp b/openfpga/src/base/openfpga_link_arch.cpp index 239829e8e..e4bf6bbed 100644 --- a/openfpga/src/base/openfpga_link_arch.cpp +++ b/openfpga/src/base/openfpga_link_arch.cpp @@ -143,6 +143,7 @@ void build_vpr_physical_pb_type_annotation(const DeviceContext& vpr_device_ctx, /* Not found, error out! */ VTR_LOG_ERROR("Unable to find the pb_type '%s' in VPR architecture definition!\n", target_pb_type_names.back().c_str()); + break; } /* Found, we update the annotation by assigning the physical mode */ From b8c504f57434dfcbfd46289f92c230d95400843f Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 27 Jan 2020 19:49:05 -0700 Subject: [PATCH 029/645] Do not allow vpr to free everything when it is done. So that we can have access to their device data --- openfpga/src/base/openfpga_link_arch.cpp | 29 ++++++++++++++++++++---- openfpga/src/vpr_wrapper/vpr_main.cpp | 8 ++++--- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/openfpga/src/base/openfpga_link_arch.cpp b/openfpga/src/base/openfpga_link_arch.cpp index e4bf6bbed..d0da76280 100644 --- a/openfpga/src/base/openfpga_link_arch.cpp +++ b/openfpga/src/base/openfpga_link_arch.cpp @@ -29,6 +29,15 @@ t_pb_type* try_find_pb_type_with_given_path(t_pb_type* top_pb_type, t_pb_type* cur_pb_type = top_pb_type; + /* If the top pb_type is what we want, we can return here */ + if (1 == target_pb_type_names.size()) { + if (target_pb_type_names[0] == std::string(top_pb_type->name)) { + return top_pb_type; + } + /* Not match, return null pointer */ + return nullptr; + } + /* We start from the first element of the parent names and parent modes. * If the pb_type does not match in name, we fail * If we cannot find a mode match the name, we fail @@ -124,9 +133,14 @@ void build_vpr_physical_pb_type_annotation(const DeviceContext& vpr_device_ctx, /* We must have at least one pb_type in the list */ VTR_ASSERT_SAFE(0 < target_pb_type_names.size()); + VTR_LOG("Trying to link pb_type '%s' to vpr architecture\n", + target_pb_type_names.back().c_str()); + /* Pb type information are located at the logic_block_types in the device context of VPR * We iterate over the vectors and find the pb_type matches the parent_pb_type_name */ + bool link_success = false; + for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { /* By pass nullptr for pb_type head */ if (nullptr == lb_type.pb_type) { @@ -140,10 +154,7 @@ void build_vpr_physical_pb_type_annotation(const DeviceContext& vpr_device_ctx, t_pb_type* target_pb_type = try_find_pb_type_with_given_path(lb_type.pb_type, target_pb_type_names, target_pb_mode_names); if (nullptr == target_pb_type) { - /* Not found, error out! */ - VTR_LOG_ERROR("Unable to find the pb_type '%s' in VPR architecture definition!\n", - target_pb_type_names.back().c_str()); - break; + continue; } /* Found, we update the annotation by assigning the physical mode */ @@ -153,6 +164,16 @@ void build_vpr_physical_pb_type_annotation(const DeviceContext& vpr_device_ctx, /* Give a message */ VTR_LOG("Annotate pb_type '%s' with physical mode '%s'\n", target_pb_type->name, physical_mode->name); + + link_success = true; + break; + } + + if (false == link_success) { + /* Not found, error out! */ + VTR_LOG_ERROR("Unable to find the pb_type '%s' in VPR architecture definition!\n", + target_pb_type_names.back().c_str()); + return; } } } diff --git a/openfpga/src/vpr_wrapper/vpr_main.cpp b/openfpga/src/vpr_wrapper/vpr_main.cpp index 2af952584..bf0f6de91 100644 --- a/openfpga/src/vpr_wrapper/vpr_main.cpp +++ b/openfpga/src/vpr_wrapper/vpr_main.cpp @@ -48,7 +48,7 @@ int vpr(int argc, char** argv) { vpr_install_signal_handler(); /* Read options, architecture, and circuit netlist */ - vpr_init(argc, (const char**)argv, &Options, &vpr_setup, &Arch); + vpr_init(argc, const_cast(argv), &Options, &vpr_setup, &Arch); if (Options.show_version) { return SUCCESS_EXIT_CODE; @@ -70,8 +70,10 @@ int vpr(int argc, char** argv) { timing_ctx.stats.num_full_hold_updates, timing_ctx.stats.num_full_setup_hold_updates); - /* free data structures */ - vpr_free_all(Arch, vpr_setup); + /* TODO: move this to the end of flow + * free data structures + */ + /* vpr_free_all(Arch, vpr_setup); */ VTR_LOG("VPR suceeded\n"); From f99dd4c2614c4e421c940c9bcb0ff15dd831f459 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 27 Jan 2020 20:40:18 -0700 Subject: [PATCH 030/645] debugged pb_type physical mode annotation --- openfpga/src/base/openfpga_link_arch.cpp | 156 +++++++++++++++++------ openfpga/src/base/pb_type_utils.cpp | 10 +- 2 files changed, 128 insertions(+), 38 deletions(-) diff --git a/openfpga/src/base/openfpga_link_arch.cpp b/openfpga/src/base/openfpga_link_arch.cpp index d0da76280..a3e695345 100644 --- a/openfpga/src/base/openfpga_link_arch.cpp +++ b/openfpga/src/base/openfpga_link_arch.cpp @@ -3,6 +3,7 @@ * which are built on the libarchopenfpga library *******************************************************************/ /* Headers from vtrutil library */ +#include "vtr_time.h" #include "vtr_assert.h" #include "vtr_log.h" @@ -68,40 +69,14 @@ t_pb_type* try_find_pb_type_with_given_path(t_pb_type* top_pb_type, } /******************************************************************** - * This function will recursively traverse pb_type graph to ensure - * 1. there is only a physical mode under each pb_type - * 2. physical mode appears only when its parent is a physical mode. + * This function will identify the physical pb_type for each multi-mode + * pb_type in VPR pb_type graph by following the explicit definition + * in OpenFPGA architecture XML *******************************************************************/ static -void rec_check_pb_type_physical_mode(t_pb_type* cur_pb_type) { - /* We do not check any primitive pb_type */ - if (true == is_primitive_pb_type(cur_pb_type)) { - return; - } - - /* For non-primitive pb_type: we should iterate over each mode - * Ensure there is only one physical mode - */ - - /* Traverse all the modes for identifying idle mode */ - for (int imode = 0; cur_pb_type->num_modes; ++imode) { - /* Check each pb_type_child */ - for (int ichild = 0; ichild < cur_pb_type->modes[imode].num_pb_type_children; ++ichild) { - rec_check_pb_type_physical_mode(&(cur_pb_type->modes[imode].pb_type_children[ichild])); - } - } -} - -/******************************************************************** - * This function will - * - identify the physical pb_type for each multi-mode pb_type in - * VPR pb_type graph - * - identify the physical pb_type for operating pb_types in VPR - *******************************************************************/ -static -void build_vpr_physical_pb_type_annotation(const DeviceContext& vpr_device_ctx, - const Arch& openfpga_arch, - VprPbTypeAnnotation& vpr_pb_type_annotation) { +void build_vpr_physical_pb_mode_explicit_annotation(const DeviceContext& vpr_device_ctx, + const Arch& openfpga_arch, + VprPbTypeAnnotation& vpr_pb_type_annotation) { /* Walk through the pb_type annotation stored in the openfpga arch */ for (const PbTypeAnnotation& pb_type_annotation : openfpga_arch.pb_type_annotations) { /* Since our target is to annotate the physical mode name, @@ -133,9 +108,6 @@ void build_vpr_physical_pb_type_annotation(const DeviceContext& vpr_device_ctx, /* We must have at least one pb_type in the list */ VTR_ASSERT_SAFE(0 < target_pb_type_names.size()); - VTR_LOG("Trying to link pb_type '%s' to vpr architecture\n", - target_pb_type_names.back().c_str()); - /* Pb type information are located at the logic_block_types in the device context of VPR * We iterate over the vectors and find the pb_type matches the parent_pb_type_name */ @@ -178,6 +150,112 @@ void build_vpr_physical_pb_type_annotation(const DeviceContext& vpr_device_ctx, } } +/******************************************************************** + * This function will recursively visit all the pb_type from the top + * pb_type in the graph and + * infer the physical pb_type for each multi-mode + * pb_type in VPR pb_type graph without OpenFPGA architecture XML + * + * The following rule is applied: + * if there is only 1 mode under a pb_type, it will be the default + * physical mode for this pb_type + *******************************************************************/ +static +void rec_infer_vpr_physical_pb_mode_annotation(t_pb_type* cur_pb_type, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + /* We do not check any primitive pb_type */ + if (true == is_primitive_pb_type(cur_pb_type)) { + return; + } + + /* For non-primitive pb_type: + * - if there is only one mode, it will be the physical mode + * we just need to make sure that we do not repeatedly annotate this + * - if there are multiple modes, we should be able to find a physical mode + * and then go recursively + */ + t_mode* physical_mode = nullptr; + + if (1 == cur_pb_type->num_modes) { + if (nullptr == vpr_pb_type_annotation.physical_mode(cur_pb_type)) { + /* Not assigned by explicit annotation, we should infer here */ + vpr_pb_type_annotation.add_pb_type_physical_mode(cur_pb_type, &(cur_pb_type->modes[0])); + VTR_LOG("Implicitly infer physical mode '%s' for pb_type '%s'\n", + cur_pb_type->modes[0].name, cur_pb_type->name); + } + } else { + VTR_ASSERT(1 < cur_pb_type->num_modes); + if (nullptr == vpr_pb_type_annotation.physical_mode(cur_pb_type)) { + /* Not assigned by explicit annotation, we should infer here */ + vpr_pb_type_annotation.add_pb_type_physical_mode(cur_pb_type, &(cur_pb_type->modes[0])); + VTR_LOG_ERROR("Unable to find a physical mode for a multi-mode pb_type '%s'!\n", + cur_pb_type->name); + VTR_LOG_ERROR("Please specify in the OpenFPGA architecture\n"); + return; + } + } + + /* Get the physical mode from annotation */ + physical_mode = vpr_pb_type_annotation.physical_mode(cur_pb_type); + + VTR_ASSERT(nullptr != physical_mode); + + /* Traverse the pb_type children under the physical mode */ + for (int ichild = 0; ichild < physical_mode->num_pb_type_children; ++ichild) { + rec_infer_vpr_physical_pb_mode_annotation(&(physical_mode->pb_type_children[ichild]), + vpr_pb_type_annotation); + } +} + +/******************************************************************** + * This function will infer the physical pb_type for each multi-mode + * pb_type in VPR pb_type graph without OpenFPGA architecture XML + * + * The following rule is applied: + * if there is only 1 mode under a pb_type, it will be the default + * physical mode for this pb_type + * + * Note: + * This function must be executed AFTER the function + * build_vpr_physical_pb_mode_explicit_annotation() + *******************************************************************/ +static +void build_vpr_physical_pb_mode_implicit_annotation(const DeviceContext& vpr_device_ctx, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { + /* By pass nullptr for pb_type head */ + if (nullptr == lb_type.pb_type) { + continue; + } + rec_infer_vpr_physical_pb_mode_annotation(lb_type.pb_type, vpr_pb_type_annotation); + } +} + +/******************************************************************** + * This function will recursively traverse pb_type graph to ensure + * 1. there is only a physical mode under each pb_type + * 2. physical mode appears only when its parent is a physical mode. + *******************************************************************/ +static +void rec_check_pb_type_physical_mode(t_pb_type* cur_pb_type) { + /* We do not check any primitive pb_type */ + if (true == is_primitive_pb_type(cur_pb_type)) { + return; + } + + /* For non-primitive pb_type: we should iterate over each mode + * Ensure there is only one physical mode + */ + + /* Traverse all the modes for identifying idle mode */ + for (int imode = 0; cur_pb_type->num_modes; ++imode) { + /* Check each pb_type_child */ + for (int ichild = 0; ichild < cur_pb_type->modes[imode].num_pb_type_children; ++ichild) { + rec_check_pb_type_physical_mode(&(cur_pb_type->modes[imode].pb_type_children[ichild])); + } + } +} + /******************************************************************** * Top-level function to link openfpga architecture to VPR, including: * - physical pb_type @@ -186,9 +264,13 @@ void build_vpr_physical_pb_type_annotation(const DeviceContext& vpr_device_ctx, *******************************************************************/ void link_arch(OpenfpgaContext& openfpga_context) { + vtr::ScopedStartFinishTimer timer("Link OpenFPGA architecture to VPR architecture"); + /* Annotate physical pb_type in the VPR pb_type graph */ - build_vpr_physical_pb_type_annotation(g_vpr_ctx.device(), openfpga_context.arch(), - openfpga_context.mutable_vpr_pb_type_annotation()); + build_vpr_physical_pb_mode_explicit_annotation(g_vpr_ctx.device(), openfpga_context.arch(), + openfpga_context.mutable_vpr_pb_type_annotation()); + build_vpr_physical_pb_mode_implicit_annotation(g_vpr_ctx.device(), + openfpga_context.mutable_vpr_pb_type_annotation()); /* Annotate idle pb_type in the VPR pb_type graph */ diff --git a/openfpga/src/base/pb_type_utils.cpp b/openfpga/src/base/pb_type_utils.cpp index 8eb3c42a1..7a69ad37c 100644 --- a/openfpga/src/base/pb_type_utils.cpp +++ b/openfpga/src/base/pb_type_utils.cpp @@ -3,6 +3,7 @@ * and pb_graph_node data structure in the OpenFPGA context *******************************************************************/ /* Headers from vtrutil library */ +#include "vtr_assert.h" #include "vtr_log.h" #include "pb_type_utils.h" @@ -15,10 +16,17 @@ namespace openfpga { * However, this not always true. An exception is the LUT_CLASS * VPR added two modes by default to a LUT pb_type. Therefore, * for LUT_CLASS, it is a primitive when it is binded to a blif model + * + * Note: + * - if VPR changes its mode organization for LUT pb_type + * this code should be adapted as well! ************************************************************************/ bool is_primitive_pb_type(t_pb_type* pb_type) { if (LUT_CLASS == pb_type->class_type) { - return nullptr != pb_type->blif_model; + /* The first mode of LUT is wire, the second is the regular LUT */ + VTR_ASSERT(std::string("wire") == std::string(pb_type->modes[0].name)); + VTR_ASSERT(std::string(pb_type->name) == std::string(pb_type->modes[1].name)); + return true; } return 0 == pb_type->num_modes; } From 82f71e82e84e06f644ea3d19f29175d394f31807 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 27 Jan 2020 21:15:32 -0700 Subject: [PATCH 031/645] add check codes for physical mode annotation for pb_types --- openfpga/src/base/openfpga_link_arch.cpp | 78 +++++++++++++++++++++--- 1 file changed, 69 insertions(+), 9 deletions(-) diff --git a/openfpga/src/base/openfpga_link_arch.cpp b/openfpga/src/base/openfpga_link_arch.cpp index a3e695345..d01841074 100644 --- a/openfpga/src/base/openfpga_link_arch.cpp +++ b/openfpga/src/base/openfpga_link_arch.cpp @@ -237,22 +237,79 @@ void build_vpr_physical_pb_mode_implicit_annotation(const DeviceContext& vpr_dev * 2. physical mode appears only when its parent is a physical mode. *******************************************************************/ static -void rec_check_pb_type_physical_mode(t_pb_type* cur_pb_type) { +void rec_check_vpr_physical_pb_mode_annotation(t_pb_type* cur_pb_type, + const bool& expect_physical_mode, + const VprPbTypeAnnotation& vpr_pb_type_annotation, + size_t& num_err) { /* We do not check any primitive pb_type */ if (true == is_primitive_pb_type(cur_pb_type)) { return; } - /* For non-primitive pb_type: we should iterate over each mode - * Ensure there is only one physical mode + /* For non-primitive pb_type: + * - If we expect a physical mode to exist under this pb_type + * we should be able to find one in the annoation + * - If we do NOT expect a physical mode, make sure we find + * nothing in the annotation */ - - /* Traverse all the modes for identifying idle mode */ - for (int imode = 0; cur_pb_type->num_modes; ++imode) { - /* Check each pb_type_child */ - for (int ichild = 0; ichild < cur_pb_type->modes[imode].num_pb_type_children; ++ichild) { - rec_check_pb_type_physical_mode(&(cur_pb_type->modes[imode].pb_type_children[ichild])); + if (true == expect_physical_mode) { + if (nullptr == vpr_pb_type_annotation.physical_mode(cur_pb_type)) { + VTR_LOG_ERROR("Unable to find a physical mode for a multi-mode pb_type '%s'!\n", + cur_pb_type->name); + VTR_LOG_ERROR("Please specify in the OpenFPGA architecture\n"); + num_err++; + return; } + } else { + VTR_ASSERT_SAFE(false == expect_physical_mode); + if (nullptr != vpr_pb_type_annotation.physical_mode(cur_pb_type)) { + VTR_LOG_ERROR("Find a physical mode '%s' for pb_type '%s' which is not under any physical mode!\n", + vpr_pb_type_annotation.physical_mode(cur_pb_type)->name, + cur_pb_type->name); + num_err++; + return; + } + } + + /* Traverse all the modes + * - for pb_type children under a physical mode, we expect an physical mode + * - for pb_type children under non-physical mode, we expect no physical mode + */ + for (int imode = 0; imode < cur_pb_type->num_modes; ++imode) { + bool expect_child_physical_mode = false; + if (&(cur_pb_type->modes[imode]) == vpr_pb_type_annotation.physical_mode(cur_pb_type)) { + expect_child_physical_mode = true && expect_physical_mode; + } + for (int ichild = 0; ichild < cur_pb_type->modes[imode].num_pb_type_children; ++ichild) { + rec_check_vpr_physical_pb_mode_annotation(&(cur_pb_type->modes[imode].pb_type_children[ichild]), + expect_child_physical_mode, vpr_pb_type_annotation, + num_err); + } + } +} + +/******************************************************************** + * This function will check the physical mode annotation for + * each pb_type in the device + *******************************************************************/ +static +void check_vpr_physical_pb_mode_annotation(const DeviceContext& vpr_device_ctx, + const VprPbTypeAnnotation& vpr_pb_type_annotation) { + size_t num_err = 0; + + for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { + /* By pass nullptr for pb_type head */ + if (nullptr == lb_type.pb_type) { + continue; + } + /* Top pb_type should always has a physical mode! */ + rec_check_vpr_physical_pb_mode_annotation(lb_type.pb_type, true, vpr_pb_type_annotation, num_err); + } + if (0 == num_err) { + VTR_LOG("Check physical mode annotation for pb_types passed.\n"); + } else { + VTR_LOG("Check physical mode annotation for pb_types failed with %ld errors!\n", + num_err); } } @@ -272,6 +329,9 @@ void link_arch(OpenfpgaContext& openfpga_context) { build_vpr_physical_pb_mode_implicit_annotation(g_vpr_ctx.device(), openfpga_context.mutable_vpr_pb_type_annotation()); + check_vpr_physical_pb_mode_annotation(g_vpr_ctx.device(), + openfpga_context.vpr_pb_type_annotation()); + /* Annotate idle pb_type in the VPR pb_type graph */ /* Link physical pb_type to circuit model */ From caeb0bfff89a4b1c09f4dd89be7699618f77aa31 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 28 Jan 2020 14:27:35 -0700 Subject: [PATCH 032/645] add physical pb_type binding for explicit annotations --- openfpga/src/base/openfpga_link_arch.cpp | 167 ++++++++++++++++++- openfpga/src/base/pb_type_utils.cpp | 24 +++ openfpga/src/base/pb_type_utils.h | 5 + openfpga/src/base/vpr_pb_type_annotation.cpp | 64 +++++++ openfpga/src/base/vpr_pb_type_annotation.h | 47 ++++++ 5 files changed, 301 insertions(+), 6 deletions(-) diff --git a/openfpga/src/base/openfpga_link_arch.cpp b/openfpga/src/base/openfpga_link_arch.cpp index d01841074..443373d20 100644 --- a/openfpga/src/base/openfpga_link_arch.cpp +++ b/openfpga/src/base/openfpga_link_arch.cpp @@ -59,7 +59,7 @@ t_pb_type* try_find_pb_type_with_given_path(t_pb_type* top_pb_type, return nullptr; } /* If this is already the last pb_type in the list, this is what we want */ - if (i == target_pb_type_names.size() - 1) { + if (i + 1 == target_pb_type_names.size() - 1) { return cur_pb_type; } } @@ -69,7 +69,7 @@ t_pb_type* try_find_pb_type_with_given_path(t_pb_type* top_pb_type, } /******************************************************************** - * This function will identify the physical pb_type for each multi-mode + * This function will identify the physical mode for each multi-mode * pb_type in VPR pb_type graph by following the explicit definition * in OpenFPGA architecture XML *******************************************************************/ @@ -153,7 +153,7 @@ void build_vpr_physical_pb_mode_explicit_annotation(const DeviceContext& vpr_dev /******************************************************************** * This function will recursively visit all the pb_type from the top * pb_type in the graph and - * infer the physical pb_type for each multi-mode + * infer the physical mode for each multi-mode * pb_type in VPR pb_type graph without OpenFPGA architecture XML * * The following rule is applied: @@ -208,7 +208,7 @@ void rec_infer_vpr_physical_pb_mode_annotation(t_pb_type* cur_pb_type, } /******************************************************************** - * This function will infer the physical pb_type for each multi-mode + * This function will infer the physical mode for each multi-mode * pb_type in VPR pb_type graph without OpenFPGA architecture XML * * The following rule is applied: @@ -313,6 +313,159 @@ void check_vpr_physical_pb_mode_annotation(const DeviceContext& vpr_device_ctx, } } +/******************************************************************** + * This function aims to make a pair of operating and physical + * pb_types: + * - In addition to pairing the pb_types, it will pair the ports of the pb_types + * - For the ports which are explicited annotated as physical pin mapping + * in the pb_type annotation. + * We will check the port range and create a pair + * - For the ports which are not specified in the pb_type annotation + * we assume their physical ports share the same as the operating ports + * We will try to find a port in the physical pb_type and check the port range + * If found, we will create a pair + * - All the pairs will be updated in vpr_pb_type_annotation + *******************************************************************/ +static +bool pair_operating_and_physical_pb_types(t_pb_type* operating_pb_type, + t_pb_type* physical_pb_type, + const PbTypeAnnotation& pb_type_annotation, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + /* Reach here, we should have valid operating and physical pb_types */ + VTR_ASSERT((nullptr != operating_pb_type) && (nullptr != physical_pb_type)); + + /* Iterate over the ports under the operating pb_type + * For each pin, we will try to find its physical port in the pb_type_annotation + * if not found, we assume that the physical port is the same as the operating pb_port + */ + for (t_port* operating_pb_port : pb_type_ports(operating_pb_type)) { + /* Try to find the port in the pb_type_annotation */ + BasicPort expected_physical_pb_port = pb_type_annotation.physical_pb_type_port(std::string(operating_pb_port->name)); + if (true == expected_physical_pb_port.get_name().empty()) { + /* Not found, we reset the port information to be consistent as the operating pb_port */ + expected_physical_pb_port.set_name(std::string(operating_pb_port->name)); + expected_physical_pb_port.set_width(operating_pb_port->num_pins); + } + + /* Try to find the expected port in the physical pb_type */ + t_port* physical_pb_port = find_pb_type_port(physical_pb_type, expected_physical_pb_port.get_name()); + /* Not found, mapping fails */ + if (nullptr == physical_pb_port) { + return false; + } + /* If the port range does not match, mapping fails */ + if (false == expected_physical_pb_port.contained(BasicPort(physical_pb_port->name, physical_pb_port->num_pins))) { + return false; + } + /* Now, port mapping should succeed, we update the vpr_pb_type_annotation */ + vpr_pb_type_annotation.add_physical_pb_port(operating_pb_port, physical_pb_port); + vpr_pb_type_annotation.add_physical_pb_port_range(operating_pb_port, expected_physical_pb_port); + } + + /* Now, pb_type mapping should succeed, we update the vpr_pb_type_annotation */ + vpr_pb_type_annotation.add_physical_pb_type(operating_pb_type, physical_pb_type); + + return true; +} + +/******************************************************************** + * This function will identify the physical pb_type for each operating + * pb_type in VPR pb_type graph by following the explicit definition + * in OpenFPGA architecture XML + * + * Note: + * - This function should be executed only AFTER the physical mode + * annotation is completed + *******************************************************************/ +static +void build_vpr_physical_pb_type_annotation(const DeviceContext& vpr_device_ctx, + const Arch& openfpga_arch, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + /* Walk through the pb_type annotation stored in the openfpga arch */ + for (const PbTypeAnnotation& pb_type_annotation : openfpga_arch.pb_type_annotations) { + /* Since our target is to annotate the operating pb_type tp physical pb_type + * we can skip those annotation only for physical pb_type + */ + if (true == pb_type_annotation.is_physical_pb_type()) { + continue; + } + + VTR_ASSERT(true == pb_type_annotation.is_operating_pb_type()); + + /* Collect the information about the full hierarchy of operating pb_type to be annotated */ + std::vector target_op_pb_type_names; + std::vector target_op_pb_mode_names; + + target_op_pb_type_names = pb_type_annotation.operating_parent_pb_type_names(); + target_op_pb_type_names.push_back(pb_type_annotation.operating_pb_type_name()); + target_op_pb_mode_names = pb_type_annotation.operating_parent_mode_names(); + + /* Collect the information about the full hierarchy of physical pb_type to be annotated */ + std::vector target_phy_pb_type_names; + std::vector target_phy_pb_mode_names; + + target_phy_pb_type_names = pb_type_annotation.physical_parent_pb_type_names(); + target_phy_pb_type_names.push_back(pb_type_annotation.physical_pb_type_name()); + target_phy_pb_mode_names = pb_type_annotation.physical_parent_mode_names(); + + /* We must have at least one pb_type in the list */ + VTR_ASSERT_SAFE(0 < target_op_pb_type_names.size()); + VTR_ASSERT_SAFE(0 < target_phy_pb_type_names.size()); + + /* Pb type information are located at the logic_block_types in the device context of VPR + * We iterate over the vectors and find the pb_type matches the parent_pb_type_name + */ + bool link_success = false; + + for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { + /* By pass nullptr for pb_type head */ + if (nullptr == lb_type.pb_type) { + continue; + } + /* Check the name of the top-level pb_type, if it does not match, we can bypass */ + if (target_op_pb_type_names[0] != std::string(lb_type.pb_type->name)) { + continue; + } + /* Match the name in the top-level, we go further to search the operating as well as + * physical pb_types in the graph */ + t_pb_type* target_op_pb_type = try_find_pb_type_with_given_path(lb_type.pb_type, target_op_pb_type_names, + target_op_pb_mode_names); + if (nullptr == target_op_pb_type) { + continue; + } + + t_pb_type* target_phy_pb_type = try_find_pb_type_with_given_path(lb_type.pb_type, target_phy_pb_type_names, + target_phy_pb_mode_names); + if (nullptr == target_phy_pb_type) { + continue; + } + + /* Both operating and physical pb_type have been found, + * we update the annotation by assigning the physical mode + */ + if (true == pair_operating_and_physical_pb_types(target_op_pb_type, target_phy_pb_type, + pb_type_annotation, vpr_pb_type_annotation)) { + + /* Give a message */ + VTR_LOG("Annotate operating pb_type '%s' to its physical pb_type '%s'\n", + target_op_pb_type->name, target_phy_pb_type->name); + + link_success = true; + break; + } + } + + if (false == link_success) { + /* Not found, error out! */ + VTR_LOG_ERROR("Unable to pair the operating pb_type '%s' to its physical pb_type '%s'!\n", + target_op_pb_type_names.back().c_str(), + target_phy_pb_type_names.back().c_str()); + return; + } + } +} + + /******************************************************************** * Top-level function to link openfpga architecture to VPR, including: * - physical pb_type @@ -323,7 +476,7 @@ void link_arch(OpenfpgaContext& openfpga_context) { vtr::ScopedStartFinishTimer timer("Link OpenFPGA architecture to VPR architecture"); - /* Annotate physical pb_type in the VPR pb_type graph */ + /* Annotate physical mode to pb_type in the VPR pb_type graph */ build_vpr_physical_pb_mode_explicit_annotation(g_vpr_ctx.device(), openfpga_context.arch(), openfpga_context.mutable_vpr_pb_type_annotation()); build_vpr_physical_pb_mode_implicit_annotation(g_vpr_ctx.device(), @@ -332,7 +485,9 @@ void link_arch(OpenfpgaContext& openfpga_context) { check_vpr_physical_pb_mode_annotation(g_vpr_ctx.device(), openfpga_context.vpr_pb_type_annotation()); - /* Annotate idle pb_type in the VPR pb_type graph */ + /* Annotate physical pb_types to operating pb_type in the VPR pb_type graph */ + build_vpr_physical_pb_type_annotation(g_vpr_ctx.device(), openfpga_context.arch(), + openfpga_context.mutable_vpr_pb_type_annotation()); /* Link physical pb_type to circuit model */ diff --git a/openfpga/src/base/pb_type_utils.cpp b/openfpga/src/base/pb_type_utils.cpp index 7a69ad37c..1d0c31b6c 100644 --- a/openfpga/src/base/pb_type_utils.cpp +++ b/openfpga/src/base/pb_type_utils.cpp @@ -64,4 +64,28 @@ t_pb_type* find_mode_child_pb_type(t_mode* mode, const char* child_name) { return nullptr; } +/************************************************************************ + * With a given pb_type, provide a list of its ports + ************************************************************************/ +std::vector pb_type_ports(t_pb_type* pb_type) { + std::vector ports; + for (int i = 0; i < pb_type->num_ports; ++i) { + ports.push_back(&(pb_type->ports[i])); + } + return ports; +} + +/************************************************************************ + * Find a port for a pb_type with a given name + * If not found, return null pointer + ************************************************************************/ +t_port* find_pb_type_port(t_pb_type* pb_type, const std::string& port_name) { + for (int i = 0; i < pb_type->num_ports; ++i) { + if (port_name == std::string(pb_type->ports[i].name)) { + return &(pb_type->ports[i]); + } + } + return nullptr; +} + } /* end namespace openfpga */ diff --git a/openfpga/src/base/pb_type_utils.h b/openfpga/src/base/pb_type_utils.h index c0c1881d5..902f203e3 100644 --- a/openfpga/src/base/pb_type_utils.h +++ b/openfpga/src/base/pb_type_utils.h @@ -4,6 +4,7 @@ /******************************************************************** * Include header files that are required by function declaration *******************************************************************/ +#include #include "physical_types.h" /******************************************************************** @@ -21,6 +22,10 @@ t_mode* find_pb_type_mode(t_pb_type* pb_type, const char* mode_name); t_pb_type* find_mode_child_pb_type(t_mode* mode, const char* child_name); +std::vector pb_type_ports(t_pb_type* pb_type); + +t_port* find_pb_type_port(t_pb_type* pb_type, const std::string& port_name); + } /* end namespace openfpga */ #endif diff --git a/openfpga/src/base/vpr_pb_type_annotation.cpp b/openfpga/src/base/vpr_pb_type_annotation.cpp index 1d4beb1ff..e89045307 100644 --- a/openfpga/src/base/vpr_pb_type_annotation.cpp +++ b/openfpga/src/base/vpr_pb_type_annotation.cpp @@ -27,6 +27,34 @@ t_mode* VprPbTypeAnnotation::physical_mode(t_pb_type* pb_type) const { return physical_pb_modes_.at(pb_type); } +t_pb_type* VprPbTypeAnnotation::physical_pb_type(t_pb_type* pb_type) const { + /* Ensure that the pb_type is in the list */ + std::map::const_iterator it = physical_pb_types_.find(pb_type); + if (it == physical_pb_types_.end()) { + return nullptr; + } + return physical_pb_types_.at(pb_type); +} + +t_port* VprPbTypeAnnotation::physical_pb_port(t_port* pb_port) const { + /* Ensure that the pb_type is in the list */ + std::map::const_iterator it = physical_pb_ports_.find(pb_port); + if (it == physical_pb_ports_.end()) { + return nullptr; + } + return physical_pb_ports_.at(pb_port); +} + +BasicPort VprPbTypeAnnotation::physical_pb_port_range(t_port* pb_port) const { + /* Ensure that the pb_type is in the list */ + std::map::const_iterator it = physical_pb_port_ranges_.find(pb_port); + if (it == physical_pb_port_ranges_.end()) { + /* Return an invalid port. As such the port width will be 0, which is an invalid value */ + return BasicPort(); + } + return physical_pb_port_ranges_.at(pb_port); +} + /************************************************************************ * Public mutators ***********************************************************************/ @@ -41,4 +69,40 @@ void VprPbTypeAnnotation::add_pb_type_physical_mode(t_pb_type* pb_type, t_mode* physical_pb_modes_[pb_type] = physical_mode; } +void VprPbTypeAnnotation::add_physical_pb_type(t_pb_type* operating_pb_type, t_pb_type* physical_pb_type) { + /* Warn any override attempt */ + std::map::const_iterator it = physical_pb_types_.find(operating_pb_type); + if (it != physical_pb_types_.end()) { + VTR_LOG_WARN("Override the annotation between operating pb_type '%s' and it physical pb_type '%s'!\n", + operating_pb_type->name, physical_pb_type->name); + } + + physical_pb_types_[operating_pb_type] = physical_pb_type; +} + +void VprPbTypeAnnotation::add_physical_pb_port(t_port* operating_pb_port, t_port* physical_pb_port) { + /* Warn any override attempt */ + std::map::const_iterator it = physical_pb_ports_.find(operating_pb_port); + if (it != physical_pb_ports_.end()) { + VTR_LOG_WARN("Override the annotation between operating pb_port '%s' and it physical pb_port '%s'!\n", + operating_pb_port->name, physical_pb_port->name); + } + + physical_pb_ports_[operating_pb_port] = physical_pb_port; +} + +void VprPbTypeAnnotation::add_physical_pb_port_range(t_port* operating_pb_port, const BasicPort& port_range) { + /* The port range must satify the port width*/ + VTR_ASSERT((size_t)operating_pb_port->num_pins == port_range.get_width()); + + /* Warn any override attempt */ + std::map::const_iterator it = physical_pb_port_ranges_.find(operating_pb_port); + if (it != physical_pb_port_ranges_.end()) { + VTR_LOG_WARN("Override the annotation between operating pb_port '%s' and it physical pb_port range '[%ld:%ld]'!\n", + operating_pb_port->name, port_range.get_lsb(), port_range.get_msb()); + } + + physical_pb_port_ranges_[operating_pb_port] = port_range; +} + } /* End namespace openfpga*/ diff --git a/openfpga/src/base/vpr_pb_type_annotation.h b/openfpga/src/base/vpr_pb_type_annotation.h index 5410a7ca9..90b29598f 100644 --- a/openfpga/src/base/vpr_pb_type_annotation.h +++ b/openfpga/src/base/vpr_pb_type_annotation.h @@ -10,6 +10,7 @@ #include "physical_types.h" /* Header from openfpgautil library */ +#include "openfpga_port.h" #include "circuit_library.h" /* Begin namespace openfpga */ @@ -29,13 +30,59 @@ class VprPbTypeAnnotation { VprPbTypeAnnotation(); public: /* Public accessors */ t_mode* physical_mode(t_pb_type* pb_type) const; + t_pb_type* physical_pb_type(t_pb_type* pb_type) const; + t_port* physical_pb_port(t_port* pb_port) const; + BasicPort physical_pb_port_range(t_port* pb_port) const; public: /* Public mutators */ void add_pb_type_physical_mode(t_pb_type* pb_type, t_mode* physical_mode); + void add_physical_pb_type(t_pb_type* operating_pb_type, t_pb_type* physical_pb_type); + void add_physical_pb_port(t_port* operating_pb_port, t_port* physical_pb_port); + void add_physical_pb_port_range(t_port* operating_pb_port, const BasicPort& port_range); private: /* Internal data */ + /* Flag about if a pb_type is a physical pb_type */ std::map is_physical_pb_types_; + + /* Pair a regular pb_type to its physical pb_type */ std::map physical_pb_types_; + + /* Pair a physical mode for a pb_type + * Note: + * - the physical mode MUST be a child mode of the pb_type + * - the pb_type MUST be a physical pb_type itself + */ std::map physical_pb_modes_; + + /* Pair a physical pb_type to its circuit model + * Note: + * - the pb_type MUST be a physical pb_type itself + */ std::map pb_type_circuit_models_; + + /* Pair a pb_type to its mode selection bits + * - if the pb_type is a physical pb_type, the mode bits are the default mode + * where the physical pb_type will operate when used + * - if the pb_type is an operating pb_type, the mode bits will be applied + * when the operating pb_type is used by packer + */ + std::map> pb_type_mode_bits_; + + /* Pair a pb_port to its physical pb_port + * Note: + * - the parent of physical pb_port MUST be a physical pb_type + */ + std::map physical_pb_ports_; + + /* Pair a pb_port to its LSB and MSB of a physical pb_port + * Note: + * - the LSB and MSB MUST be in range of the physical pb_port + */ + std::map physical_pb_port_ranges_; + + /* Pair a pb_graph_node to a physical pb_graph_node + * Note: + * - the pb_type of physical pb_graph_node must be a physical pb_type + */ + std::map physical_pb_graph_nodes_; }; } /* End namespace openfpga*/ From fdcc04cca8f3658a61e55d9ef3c4e839e5ff3312 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 28 Jan 2020 14:55:47 -0700 Subject: [PATCH 033/645] add physical pb_type inference --- openfpga/src/base/openfpga_link_arch.cpp | 99 ++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 5 deletions(-) diff --git a/openfpga/src/base/openfpga_link_arch.cpp b/openfpga/src/base/openfpga_link_arch.cpp index 443373d20..d1b09df85 100644 --- a/openfpga/src/base/openfpga_link_arch.cpp +++ b/openfpga/src/base/openfpga_link_arch.cpp @@ -378,9 +378,9 @@ bool pair_operating_and_physical_pb_types(t_pb_type* operating_pb_type, * annotation is completed *******************************************************************/ static -void build_vpr_physical_pb_type_annotation(const DeviceContext& vpr_device_ctx, - const Arch& openfpga_arch, - VprPbTypeAnnotation& vpr_pb_type_annotation) { +void build_vpr_physical_pb_type_explicit_annotation(const DeviceContext& vpr_device_ctx, + const Arch& openfpga_arch, + VprPbTypeAnnotation& vpr_pb_type_annotation) { /* Walk through the pb_type annotation stored in the openfpga arch */ for (const PbTypeAnnotation& pb_type_annotation : openfpga_arch.pb_type_annotations) { /* Since our target is to annotate the operating pb_type tp physical pb_type @@ -465,6 +465,92 @@ void build_vpr_physical_pb_type_annotation(const DeviceContext& vpr_device_ctx, } } +/******************************************************************** + * This function aims to pair a physical pb_type to itself + *******************************************************************/ +static +bool self_pair_physical_pb_types(t_pb_type* physical_pb_type, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + /* Reach here, we should have valid physical pb_types */ + VTR_ASSERT(nullptr != physical_pb_type); + + /* Iterate over the ports under the operating pb_type + * For each pin, we will try to find its physical port in the pb_type_annotation + * if not found, we assume that the physical port is the same as the operating pb_port + */ + for (t_port* physical_pb_port : pb_type_ports(physical_pb_type)) { + BasicPort physical_port_range(physical_pb_port->name, physical_pb_port->num_pins); + vpr_pb_type_annotation.add_physical_pb_port(physical_pb_port, physical_pb_port); + vpr_pb_type_annotation.add_physical_pb_port_range(physical_pb_port, physical_port_range); + } + + /* Now, pb_type mapping should succeed, we update the vpr_pb_type_annotation */ + vpr_pb_type_annotation.add_physical_pb_type(physical_pb_type, physical_pb_type); + + return true; +} + +/******************************************************************** + * This function will recursively visit all the pb_type from the top + * pb_type in the graph (only in the physical mode) and infer the + * physical pb_type + * This is mainly applied to single-mode pb_type graphs, where the + * physical pb_type should be pb_type itself + * We can infer this and save the explicit annotation required by users + *******************************************************************/ +static +void rec_infer_vpr_physical_pb_type_annotation(t_pb_type* cur_pb_type, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + /* Physical pb_type is mainly for the primitive pb_type */ + if (true == is_primitive_pb_type(cur_pb_type)) { + /* If the physical pb_type has been mapped, we can skip it */ + if (nullptr != vpr_pb_type_annotation.physical_pb_type(cur_pb_type)) { + return; + } + /* Create the pair here */ + if (true == self_pair_physical_pb_types(cur_pb_type, vpr_pb_type_annotation)) { + /* Give a message */ + VTR_LOG("Implicitly infer the physical pb_type for pb_type '%s' itself\n", + cur_pb_type->name); + } else { + VTR_LOG_ERROR("Unable to infer the physical pb_type for pb_type '%s' itself!\n", + cur_pb_type->name); + } + return; + } + + /* Get the physical mode from annotation */ + t_mode* physical_mode = vpr_pb_type_annotation.physical_mode(cur_pb_type); + + VTR_ASSERT(nullptr != physical_mode); + + /* Traverse the pb_type children under the physical mode */ + for (int ichild = 0; ichild < physical_mode->num_pb_type_children; ++ichild) { + rec_infer_vpr_physical_pb_type_annotation(&(physical_mode->pb_type_children[ichild]), + vpr_pb_type_annotation); + } +} + +/******************************************************************** + * This function will infer the physical pb_type for each operating + * pb_type in VPR pb_type graph which have not been explicitedly defined + * in OpenFPGA architecture XML + * + * Note: + * - This function should be executed only AFTER the physical mode + * annotation is completed + *******************************************************************/ +static +void build_vpr_physical_pb_type_implicit_annotation(const DeviceContext& vpr_device_ctx, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { + /* By pass nullptr for pb_type head */ + if (nullptr == lb_type.pb_type) { + continue; + } + rec_infer_vpr_physical_pb_type_annotation(lb_type.pb_type, vpr_pb_type_annotation); + } +} /******************************************************************** * Top-level function to link openfpga architecture to VPR, including: @@ -486,8 +572,11 @@ void link_arch(OpenfpgaContext& openfpga_context) { openfpga_context.vpr_pb_type_annotation()); /* Annotate physical pb_types to operating pb_type in the VPR pb_type graph */ - build_vpr_physical_pb_type_annotation(g_vpr_ctx.device(), openfpga_context.arch(), - openfpga_context.mutable_vpr_pb_type_annotation()); + build_vpr_physical_pb_type_explicit_annotation(g_vpr_ctx.device(), openfpga_context.arch(), + openfpga_context.mutable_vpr_pb_type_annotation()); + + build_vpr_physical_pb_type_implicit_annotation(g_vpr_ctx.device(), + openfpga_context.mutable_vpr_pb_type_annotation()); /* Link physical pb_type to circuit model */ From 5d9850c2eb84a8dba46df33e0695d535c0dea1ff Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 28 Jan 2020 15:13:14 -0700 Subject: [PATCH 034/645] move pb_type annotation to independent source files as they are getting large --- openfpga/src/base/annotate_pb_types.cpp | 531 +++++++++++++++++++++ openfpga/src/base/annotate_pb_types.h | 24 + openfpga/src/base/openfpga_link_arch.cpp | 566 +---------------------- openfpga/src/base/openfpga_link_arch.h | 4 +- openfpga/src/base/pb_type_utils.cpp | 50 ++ openfpga/src/base/pb_type_utils.h | 5 + 6 files changed, 624 insertions(+), 556 deletions(-) create mode 100644 openfpga/src/base/annotate_pb_types.cpp create mode 100644 openfpga/src/base/annotate_pb_types.h diff --git a/openfpga/src/base/annotate_pb_types.cpp b/openfpga/src/base/annotate_pb_types.cpp new file mode 100644 index 000000000..fe7629281 --- /dev/null +++ b/openfpga/src/base/annotate_pb_types.cpp @@ -0,0 +1,531 @@ +/******************************************************************** + * This file includes functions to build links between pb_types + * in particular to annotate the physical mode and physical pb_type + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_time.h" +#include "vtr_assert.h" +#include "vtr_log.h" + +#include "vpr_pb_type_annotation.h" +#include "pb_type_utils.h" +#include "annotate_pb_types.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * This function will identify the physical mode for each multi-mode + * pb_type in VPR pb_type graph by following the explicit definition + * in OpenFPGA architecture XML + *******************************************************************/ +static +void build_vpr_physical_pb_mode_explicit_annotation(const DeviceContext& vpr_device_ctx, + const Arch& openfpga_arch, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + /* Walk through the pb_type annotation stored in the openfpga arch */ + for (const PbTypeAnnotation& pb_type_annotation : openfpga_arch.pb_type_annotations) { + /* Since our target is to annotate the physical mode name, + * we can skip those has not physical mode defined + */ + if (true == pb_type_annotation.physical_mode_name().empty()) { + continue; + } + + /* Identify if the pb_type is operating or physical, + * For operating pb_type, get the full name of operating pb_type + * For physical pb_type, get the full name of physical pb_type + */ + std::vector target_pb_type_names; + std::vector target_pb_mode_names; + + if (true == pb_type_annotation.is_operating_pb_type()) { + target_pb_type_names = pb_type_annotation.operating_parent_pb_type_names(); + target_pb_type_names.push_back(pb_type_annotation.operating_pb_type_name()); + target_pb_mode_names = pb_type_annotation.operating_parent_mode_names(); + } + + if (true == pb_type_annotation.is_physical_pb_type()) { + target_pb_type_names = pb_type_annotation.physical_parent_pb_type_names(); + target_pb_type_names.push_back(pb_type_annotation.physical_pb_type_name()); + target_pb_mode_names = pb_type_annotation.physical_parent_mode_names(); + } + + /* We must have at least one pb_type in the list */ + VTR_ASSERT_SAFE(0 < target_pb_type_names.size()); + + /* Pb type information are located at the logic_block_types in the device context of VPR + * We iterate over the vectors and find the pb_type matches the parent_pb_type_name + */ + bool link_success = false; + + for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { + /* By pass nullptr for pb_type head */ + if (nullptr == lb_type.pb_type) { + continue; + } + /* Check the name of the top-level pb_type, if it does not match, we can bypass */ + if (target_pb_type_names[0] != std::string(lb_type.pb_type->name)) { + continue; + } + /* Match the name in the top-level, we go further to search the pb_type in the graph */ + t_pb_type* target_pb_type = try_find_pb_type_with_given_path(lb_type.pb_type, target_pb_type_names, + target_pb_mode_names); + if (nullptr == target_pb_type) { + continue; + } + + /* Found, we update the annotation by assigning the physical mode */ + t_mode* physical_mode = find_pb_type_mode(target_pb_type, pb_type_annotation.physical_mode_name().c_str()); + vpr_pb_type_annotation.add_pb_type_physical_mode(target_pb_type, physical_mode); + + /* Give a message */ + VTR_LOG("Annotate pb_type '%s' with physical mode '%s'\n", + target_pb_type->name, physical_mode->name); + + link_success = true; + break; + } + + if (false == link_success) { + /* Not found, error out! */ + VTR_LOG_ERROR("Unable to find the pb_type '%s' in VPR architecture definition!\n", + target_pb_type_names.back().c_str()); + return; + } + } +} + +/******************************************************************** + * This function will recursively visit all the pb_type from the top + * pb_type in the graph and + * infer the physical mode for each multi-mode + * pb_type in VPR pb_type graph without OpenFPGA architecture XML + * + * The following rule is applied: + * if there is only 1 mode under a pb_type, it will be the default + * physical mode for this pb_type + *******************************************************************/ +static +void rec_infer_vpr_physical_pb_mode_annotation(t_pb_type* cur_pb_type, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + /* We do not check any primitive pb_type */ + if (true == is_primitive_pb_type(cur_pb_type)) { + return; + } + + /* For non-primitive pb_type: + * - if there is only one mode, it will be the physical mode + * we just need to make sure that we do not repeatedly annotate this + * - if there are multiple modes, we should be able to find a physical mode + * and then go recursively + */ + t_mode* physical_mode = nullptr; + + if (1 == cur_pb_type->num_modes) { + if (nullptr == vpr_pb_type_annotation.physical_mode(cur_pb_type)) { + /* Not assigned by explicit annotation, we should infer here */ + vpr_pb_type_annotation.add_pb_type_physical_mode(cur_pb_type, &(cur_pb_type->modes[0])); + VTR_LOG("Implicitly infer physical mode '%s' for pb_type '%s'\n", + cur_pb_type->modes[0].name, cur_pb_type->name); + } + } else { + VTR_ASSERT(1 < cur_pb_type->num_modes); + if (nullptr == vpr_pb_type_annotation.physical_mode(cur_pb_type)) { + /* Not assigned by explicit annotation, we should infer here */ + vpr_pb_type_annotation.add_pb_type_physical_mode(cur_pb_type, &(cur_pb_type->modes[0])); + VTR_LOG_ERROR("Unable to find a physical mode for a multi-mode pb_type '%s'!\n", + cur_pb_type->name); + VTR_LOG_ERROR("Please specify in the OpenFPGA architecture\n"); + return; + } + } + + /* Get the physical mode from annotation */ + physical_mode = vpr_pb_type_annotation.physical_mode(cur_pb_type); + + VTR_ASSERT(nullptr != physical_mode); + + /* Traverse the pb_type children under the physical mode */ + for (int ichild = 0; ichild < physical_mode->num_pb_type_children; ++ichild) { + rec_infer_vpr_physical_pb_mode_annotation(&(physical_mode->pb_type_children[ichild]), + vpr_pb_type_annotation); + } +} + +/******************************************************************** + * This function will infer the physical mode for each multi-mode + * pb_type in VPR pb_type graph without OpenFPGA architecture XML + * + * The following rule is applied: + * if there is only 1 mode under a pb_type, it will be the default + * physical mode for this pb_type + * + * Note: + * This function must be executed AFTER the function + * build_vpr_physical_pb_mode_explicit_annotation() + *******************************************************************/ +static +void build_vpr_physical_pb_mode_implicit_annotation(const DeviceContext& vpr_device_ctx, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { + /* By pass nullptr for pb_type head */ + if (nullptr == lb_type.pb_type) { + continue; + } + rec_infer_vpr_physical_pb_mode_annotation(lb_type.pb_type, vpr_pb_type_annotation); + } +} + +/******************************************************************** + * This function will recursively traverse pb_type graph to ensure + * 1. there is only a physical mode under each pb_type + * 2. physical mode appears only when its parent is a physical mode. + *******************************************************************/ +static +void rec_check_vpr_physical_pb_mode_annotation(t_pb_type* cur_pb_type, + const bool& expect_physical_mode, + const VprPbTypeAnnotation& vpr_pb_type_annotation, + size_t& num_err) { + /* We do not check any primitive pb_type */ + if (true == is_primitive_pb_type(cur_pb_type)) { + return; + } + + /* For non-primitive pb_type: + * - If we expect a physical mode to exist under this pb_type + * we should be able to find one in the annoation + * - If we do NOT expect a physical mode, make sure we find + * nothing in the annotation + */ + if (true == expect_physical_mode) { + if (nullptr == vpr_pb_type_annotation.physical_mode(cur_pb_type)) { + VTR_LOG_ERROR("Unable to find a physical mode for a multi-mode pb_type '%s'!\n", + cur_pb_type->name); + VTR_LOG_ERROR("Please specify in the OpenFPGA architecture\n"); + num_err++; + return; + } + } else { + VTR_ASSERT_SAFE(false == expect_physical_mode); + if (nullptr != vpr_pb_type_annotation.physical_mode(cur_pb_type)) { + VTR_LOG_ERROR("Find a physical mode '%s' for pb_type '%s' which is not under any physical mode!\n", + vpr_pb_type_annotation.physical_mode(cur_pb_type)->name, + cur_pb_type->name); + num_err++; + return; + } + } + + /* Traverse all the modes + * - for pb_type children under a physical mode, we expect an physical mode + * - for pb_type children under non-physical mode, we expect no physical mode + */ + for (int imode = 0; imode < cur_pb_type->num_modes; ++imode) { + bool expect_child_physical_mode = false; + if (&(cur_pb_type->modes[imode]) == vpr_pb_type_annotation.physical_mode(cur_pb_type)) { + expect_child_physical_mode = true && expect_physical_mode; + } + for (int ichild = 0; ichild < cur_pb_type->modes[imode].num_pb_type_children; ++ichild) { + rec_check_vpr_physical_pb_mode_annotation(&(cur_pb_type->modes[imode].pb_type_children[ichild]), + expect_child_physical_mode, vpr_pb_type_annotation, + num_err); + } + } +} + +/******************************************************************** + * This function will check the physical mode annotation for + * each pb_type in the device + *******************************************************************/ +static +void check_vpr_physical_pb_mode_annotation(const DeviceContext& vpr_device_ctx, + const VprPbTypeAnnotation& vpr_pb_type_annotation) { + size_t num_err = 0; + + for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { + /* By pass nullptr for pb_type head */ + if (nullptr == lb_type.pb_type) { + continue; + } + /* Top pb_type should always has a physical mode! */ + rec_check_vpr_physical_pb_mode_annotation(lb_type.pb_type, true, vpr_pb_type_annotation, num_err); + } + if (0 == num_err) { + VTR_LOG("Check physical mode annotation for pb_types passed.\n"); + } else { + VTR_LOG("Check physical mode annotation for pb_types failed with %ld errors!\n", + num_err); + } +} + +/******************************************************************** + * This function aims to make a pair of operating and physical + * pb_types: + * - In addition to pairing the pb_types, it will pair the ports of the pb_types + * - For the ports which are explicited annotated as physical pin mapping + * in the pb_type annotation. + * We will check the port range and create a pair + * - For the ports which are not specified in the pb_type annotation + * we assume their physical ports share the same as the operating ports + * We will try to find a port in the physical pb_type and check the port range + * If found, we will create a pair + * - All the pairs will be updated in vpr_pb_type_annotation + *******************************************************************/ +static +bool pair_operating_and_physical_pb_types(t_pb_type* operating_pb_type, + t_pb_type* physical_pb_type, + const PbTypeAnnotation& pb_type_annotation, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + /* Reach here, we should have valid operating and physical pb_types */ + VTR_ASSERT((nullptr != operating_pb_type) && (nullptr != physical_pb_type)); + + /* Iterate over the ports under the operating pb_type + * For each pin, we will try to find its physical port in the pb_type_annotation + * if not found, we assume that the physical port is the same as the operating pb_port + */ + for (t_port* operating_pb_port : pb_type_ports(operating_pb_type)) { + /* Try to find the port in the pb_type_annotation */ + BasicPort expected_physical_pb_port = pb_type_annotation.physical_pb_type_port(std::string(operating_pb_port->name)); + if (true == expected_physical_pb_port.get_name().empty()) { + /* Not found, we reset the port information to be consistent as the operating pb_port */ + expected_physical_pb_port.set_name(std::string(operating_pb_port->name)); + expected_physical_pb_port.set_width(operating_pb_port->num_pins); + } + + /* Try to find the expected port in the physical pb_type */ + t_port* physical_pb_port = find_pb_type_port(physical_pb_type, expected_physical_pb_port.get_name()); + /* Not found, mapping fails */ + if (nullptr == physical_pb_port) { + return false; + } + /* If the port range does not match, mapping fails */ + if (false == expected_physical_pb_port.contained(BasicPort(physical_pb_port->name, physical_pb_port->num_pins))) { + return false; + } + /* Now, port mapping should succeed, we update the vpr_pb_type_annotation */ + vpr_pb_type_annotation.add_physical_pb_port(operating_pb_port, physical_pb_port); + vpr_pb_type_annotation.add_physical_pb_port_range(operating_pb_port, expected_physical_pb_port); + } + + /* Now, pb_type mapping should succeed, we update the vpr_pb_type_annotation */ + vpr_pb_type_annotation.add_physical_pb_type(operating_pb_type, physical_pb_type); + + return true; +} + +/******************************************************************** + * This function will identify the physical pb_type for each operating + * pb_type in VPR pb_type graph by following the explicit definition + * in OpenFPGA architecture XML + * + * Note: + * - This function should be executed only AFTER the physical mode + * annotation is completed + *******************************************************************/ +static +void build_vpr_physical_pb_type_explicit_annotation(const DeviceContext& vpr_device_ctx, + const Arch& openfpga_arch, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + /* Walk through the pb_type annotation stored in the openfpga arch */ + for (const PbTypeAnnotation& pb_type_annotation : openfpga_arch.pb_type_annotations) { + /* Since our target is to annotate the operating pb_type tp physical pb_type + * we can skip those annotation only for physical pb_type + */ + if (true == pb_type_annotation.is_physical_pb_type()) { + continue; + } + + VTR_ASSERT(true == pb_type_annotation.is_operating_pb_type()); + + /* Collect the information about the full hierarchy of operating pb_type to be annotated */ + std::vector target_op_pb_type_names; + std::vector target_op_pb_mode_names; + + target_op_pb_type_names = pb_type_annotation.operating_parent_pb_type_names(); + target_op_pb_type_names.push_back(pb_type_annotation.operating_pb_type_name()); + target_op_pb_mode_names = pb_type_annotation.operating_parent_mode_names(); + + /* Collect the information about the full hierarchy of physical pb_type to be annotated */ + std::vector target_phy_pb_type_names; + std::vector target_phy_pb_mode_names; + + target_phy_pb_type_names = pb_type_annotation.physical_parent_pb_type_names(); + target_phy_pb_type_names.push_back(pb_type_annotation.physical_pb_type_name()); + target_phy_pb_mode_names = pb_type_annotation.physical_parent_mode_names(); + + /* We must have at least one pb_type in the list */ + VTR_ASSERT_SAFE(0 < target_op_pb_type_names.size()); + VTR_ASSERT_SAFE(0 < target_phy_pb_type_names.size()); + + /* Pb type information are located at the logic_block_types in the device context of VPR + * We iterate over the vectors and find the pb_type matches the parent_pb_type_name + */ + bool link_success = false; + + for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { + /* By pass nullptr for pb_type head */ + if (nullptr == lb_type.pb_type) { + continue; + } + /* Check the name of the top-level pb_type, if it does not match, we can bypass */ + if (target_op_pb_type_names[0] != std::string(lb_type.pb_type->name)) { + continue; + } + /* Match the name in the top-level, we go further to search the operating as well as + * physical pb_types in the graph */ + t_pb_type* target_op_pb_type = try_find_pb_type_with_given_path(lb_type.pb_type, target_op_pb_type_names, + target_op_pb_mode_names); + if (nullptr == target_op_pb_type) { + continue; + } + + t_pb_type* target_phy_pb_type = try_find_pb_type_with_given_path(lb_type.pb_type, target_phy_pb_type_names, + target_phy_pb_mode_names); + if (nullptr == target_phy_pb_type) { + continue; + } + + /* Both operating and physical pb_type have been found, + * we update the annotation by assigning the physical mode + */ + if (true == pair_operating_and_physical_pb_types(target_op_pb_type, target_phy_pb_type, + pb_type_annotation, vpr_pb_type_annotation)) { + + /* Give a message */ + VTR_LOG("Annotate operating pb_type '%s' to its physical pb_type '%s'\n", + target_op_pb_type->name, target_phy_pb_type->name); + + link_success = true; + break; + } + } + + if (false == link_success) { + /* Not found, error out! */ + VTR_LOG_ERROR("Unable to pair the operating pb_type '%s' to its physical pb_type '%s'!\n", + target_op_pb_type_names.back().c_str(), + target_phy_pb_type_names.back().c_str()); + return; + } + } +} + +/******************************************************************** + * This function aims to pair a physical pb_type to itself + *******************************************************************/ +static +bool self_pair_physical_pb_types(t_pb_type* physical_pb_type, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + /* Reach here, we should have valid physical pb_types */ + VTR_ASSERT(nullptr != physical_pb_type); + + /* Iterate over the ports under the operating pb_type + * For each pin, we will try to find its physical port in the pb_type_annotation + * if not found, we assume that the physical port is the same as the operating pb_port + */ + for (t_port* physical_pb_port : pb_type_ports(physical_pb_type)) { + BasicPort physical_port_range(physical_pb_port->name, physical_pb_port->num_pins); + vpr_pb_type_annotation.add_physical_pb_port(physical_pb_port, physical_pb_port); + vpr_pb_type_annotation.add_physical_pb_port_range(physical_pb_port, physical_port_range); + } + + /* Now, pb_type mapping should succeed, we update the vpr_pb_type_annotation */ + vpr_pb_type_annotation.add_physical_pb_type(physical_pb_type, physical_pb_type); + + return true; +} + +/******************************************************************** + * This function will recursively visit all the pb_type from the top + * pb_type in the graph (only in the physical mode) and infer the + * physical pb_type + * This is mainly applied to single-mode pb_type graphs, where the + * physical pb_type should be pb_type itself + * We can infer this and save the explicit annotation required by users + *******************************************************************/ +static +void rec_infer_vpr_physical_pb_type_annotation(t_pb_type* cur_pb_type, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + /* Physical pb_type is mainly for the primitive pb_type */ + if (true == is_primitive_pb_type(cur_pb_type)) { + /* If the physical pb_type has been mapped, we can skip it */ + if (nullptr != vpr_pb_type_annotation.physical_pb_type(cur_pb_type)) { + return; + } + /* Create the pair here */ + if (true == self_pair_physical_pb_types(cur_pb_type, vpr_pb_type_annotation)) { + /* Give a message */ + VTR_LOG("Implicitly infer the physical pb_type for pb_type '%s' itself\n", + cur_pb_type->name); + } else { + VTR_LOG_ERROR("Unable to infer the physical pb_type for pb_type '%s' itself!\n", + cur_pb_type->name); + } + return; + } + + /* Get the physical mode from annotation */ + t_mode* physical_mode = vpr_pb_type_annotation.physical_mode(cur_pb_type); + + VTR_ASSERT(nullptr != physical_mode); + + /* Traverse the pb_type children under the physical mode */ + for (int ichild = 0; ichild < physical_mode->num_pb_type_children; ++ichild) { + rec_infer_vpr_physical_pb_type_annotation(&(physical_mode->pb_type_children[ichild]), + vpr_pb_type_annotation); + } +} + +/******************************************************************** + * This function will infer the physical pb_type for each operating + * pb_type in VPR pb_type graph which have not been explicitedly defined + * in OpenFPGA architecture XML + * + * Note: + * - This function should be executed only AFTER the physical mode + * annotation is completed + *******************************************************************/ +static +void build_vpr_physical_pb_type_implicit_annotation(const DeviceContext& vpr_device_ctx, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { + /* By pass nullptr for pb_type head */ + if (nullptr == lb_type.pb_type) { + continue; + } + rec_infer_vpr_physical_pb_type_annotation(lb_type.pb_type, vpr_pb_type_annotation); + } +} + +/******************************************************************** + * Top-level function to link openfpga architecture to VPR, including: + * - physical pb_type + * - circuit models for pb_type, pb interconnect + *******************************************************************/ +void annotate_pb_types(const DeviceContext& vpr_device_ctx, + const Arch& openfpga_arch, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + + /* Annotate physical mode to pb_type in the VPR pb_type graph */ + build_vpr_physical_pb_mode_explicit_annotation(vpr_device_ctx, openfpga_arch, + vpr_pb_type_annotation); + build_vpr_physical_pb_mode_implicit_annotation(vpr_device_ctx, + vpr_pb_type_annotation); + + check_vpr_physical_pb_mode_annotation(vpr_device_ctx, + const_cast(vpr_pb_type_annotation)); + + /* Annotate physical pb_types to operating pb_type in the VPR pb_type graph */ + build_vpr_physical_pb_type_explicit_annotation(vpr_device_ctx, openfpga_arch, + vpr_pb_type_annotation); + + build_vpr_physical_pb_type_implicit_annotation(vpr_device_ctx, + vpr_pb_type_annotation); + + /* Link physical pb_type to circuit model */ + + /* Link routing architecture to circuit model */ +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/base/annotate_pb_types.h b/openfpga/src/base/annotate_pb_types.h new file mode 100644 index 000000000..494b2c771 --- /dev/null +++ b/openfpga/src/base/annotate_pb_types.h @@ -0,0 +1,24 @@ +#ifndef ANNOTATE_PB_TYPES_H +#define ANNOTATE_PB_TYPES_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "vpr_context.h" +#include "openfpga_context.h" +#include "vpr_pb_type_annotation.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void annotate_pb_types(const DeviceContext& vpr_device_ctx, + const Arch& openfpga_arch, + VprPbTypeAnnotation& vpr_pb_type_annotation); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/base/openfpga_link_arch.cpp b/openfpga/src/base/openfpga_link_arch.cpp index d1b09df85..d10c5d931 100644 --- a/openfpga/src/base/openfpga_link_arch.cpp +++ b/openfpga/src/base/openfpga_link_arch.cpp @@ -9,6 +9,7 @@ #include "vpr_pb_type_annotation.h" #include "pb_type_utils.h" +#include "annotate_pb_types.h" #include "openfpga_link_arch.h" /* Include global variables of VPR */ @@ -17,568 +18,25 @@ /* begin namespace openfpga */ namespace openfpga { -/******************************************************************** - * This function will traverse pb_type graph from its top to find - * a pb_type with a given name as well as its hierarchy - *******************************************************************/ -static -t_pb_type* try_find_pb_type_with_given_path(t_pb_type* top_pb_type, - const std::vector& target_pb_type_names, - const std::vector& target_pb_mode_names) { - /* Ensure that number of parent names and modes matches */ - VTR_ASSERT_SAFE(target_pb_type_names.size() == target_pb_mode_names.size() + 1); - - t_pb_type* cur_pb_type = top_pb_type; - - /* If the top pb_type is what we want, we can return here */ - if (1 == target_pb_type_names.size()) { - if (target_pb_type_names[0] == std::string(top_pb_type->name)) { - return top_pb_type; - } - /* Not match, return null pointer */ - return nullptr; - } - - /* We start from the first element of the parent names and parent modes. - * If the pb_type does not match in name, we fail - * If we cannot find a mode match the name, we fail - */ - for (size_t i = 0; i < target_pb_type_names.size() - 1; ++i) { - /* If this level does not match, search fail */ - if (target_pb_type_names[i] != std::string(cur_pb_type->name)) { - return nullptr; - } - /* Find if the mode matches */ - t_mode* cur_mode = find_pb_type_mode(cur_pb_type, target_pb_mode_names[i].c_str()); - if (nullptr == cur_mode) { - return nullptr; - } - /* Go to the next level of pb_type */ - cur_pb_type = find_mode_child_pb_type(cur_mode, target_pb_type_names[i + 1].c_str()); - if (nullptr == cur_pb_type) { - return nullptr; - } - /* If this is already the last pb_type in the list, this is what we want */ - if (i + 1 == target_pb_type_names.size() - 1) { - return cur_pb_type; - } - } - - /* Reach here, it means we find nothing */ - return nullptr; -} - -/******************************************************************** - * This function will identify the physical mode for each multi-mode - * pb_type in VPR pb_type graph by following the explicit definition - * in OpenFPGA architecture XML - *******************************************************************/ -static -void build_vpr_physical_pb_mode_explicit_annotation(const DeviceContext& vpr_device_ctx, - const Arch& openfpga_arch, - VprPbTypeAnnotation& vpr_pb_type_annotation) { - /* Walk through the pb_type annotation stored in the openfpga arch */ - for (const PbTypeAnnotation& pb_type_annotation : openfpga_arch.pb_type_annotations) { - /* Since our target is to annotate the physical mode name, - * we can skip those has not physical mode defined - */ - if (true == pb_type_annotation.physical_mode_name().empty()) { - continue; - } - - /* Identify if the pb_type is operating or physical, - * For operating pb_type, get the full name of operating pb_type - * For physical pb_type, get the full name of physical pb_type - */ - std::vector target_pb_type_names; - std::vector target_pb_mode_names; - - if (true == pb_type_annotation.is_operating_pb_type()) { - target_pb_type_names = pb_type_annotation.operating_parent_pb_type_names(); - target_pb_type_names.push_back(pb_type_annotation.operating_pb_type_name()); - target_pb_mode_names = pb_type_annotation.operating_parent_mode_names(); - } - - if (true == pb_type_annotation.is_physical_pb_type()) { - target_pb_type_names = pb_type_annotation.physical_parent_pb_type_names(); - target_pb_type_names.push_back(pb_type_annotation.physical_pb_type_name()); - target_pb_mode_names = pb_type_annotation.physical_parent_mode_names(); - } - - /* We must have at least one pb_type in the list */ - VTR_ASSERT_SAFE(0 < target_pb_type_names.size()); - - /* Pb type information are located at the logic_block_types in the device context of VPR - * We iterate over the vectors and find the pb_type matches the parent_pb_type_name - */ - bool link_success = false; - - for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { - /* By pass nullptr for pb_type head */ - if (nullptr == lb_type.pb_type) { - continue; - } - /* Check the name of the top-level pb_type, if it does not match, we can bypass */ - if (target_pb_type_names[0] != std::string(lb_type.pb_type->name)) { - continue; - } - /* Match the name in the top-level, we go further to search the pb_type in the graph */ - t_pb_type* target_pb_type = try_find_pb_type_with_given_path(lb_type.pb_type, target_pb_type_names, - target_pb_mode_names); - if (nullptr == target_pb_type) { - continue; - } - - /* Found, we update the annotation by assigning the physical mode */ - t_mode* physical_mode = find_pb_type_mode(target_pb_type, pb_type_annotation.physical_mode_name().c_str()); - vpr_pb_type_annotation.add_pb_type_physical_mode(target_pb_type, physical_mode); - - /* Give a message */ - VTR_LOG("Annotate pb_type '%s' with physical mode '%s'\n", - target_pb_type->name, physical_mode->name); - - link_success = true; - break; - } - - if (false == link_success) { - /* Not found, error out! */ - VTR_LOG_ERROR("Unable to find the pb_type '%s' in VPR architecture definition!\n", - target_pb_type_names.back().c_str()); - return; - } - } -} - -/******************************************************************** - * This function will recursively visit all the pb_type from the top - * pb_type in the graph and - * infer the physical mode for each multi-mode - * pb_type in VPR pb_type graph without OpenFPGA architecture XML - * - * The following rule is applied: - * if there is only 1 mode under a pb_type, it will be the default - * physical mode for this pb_type - *******************************************************************/ -static -void rec_infer_vpr_physical_pb_mode_annotation(t_pb_type* cur_pb_type, - VprPbTypeAnnotation& vpr_pb_type_annotation) { - /* We do not check any primitive pb_type */ - if (true == is_primitive_pb_type(cur_pb_type)) { - return; - } - - /* For non-primitive pb_type: - * - if there is only one mode, it will be the physical mode - * we just need to make sure that we do not repeatedly annotate this - * - if there are multiple modes, we should be able to find a physical mode - * and then go recursively - */ - t_mode* physical_mode = nullptr; - - if (1 == cur_pb_type->num_modes) { - if (nullptr == vpr_pb_type_annotation.physical_mode(cur_pb_type)) { - /* Not assigned by explicit annotation, we should infer here */ - vpr_pb_type_annotation.add_pb_type_physical_mode(cur_pb_type, &(cur_pb_type->modes[0])); - VTR_LOG("Implicitly infer physical mode '%s' for pb_type '%s'\n", - cur_pb_type->modes[0].name, cur_pb_type->name); - } - } else { - VTR_ASSERT(1 < cur_pb_type->num_modes); - if (nullptr == vpr_pb_type_annotation.physical_mode(cur_pb_type)) { - /* Not assigned by explicit annotation, we should infer here */ - vpr_pb_type_annotation.add_pb_type_physical_mode(cur_pb_type, &(cur_pb_type->modes[0])); - VTR_LOG_ERROR("Unable to find a physical mode for a multi-mode pb_type '%s'!\n", - cur_pb_type->name); - VTR_LOG_ERROR("Please specify in the OpenFPGA architecture\n"); - return; - } - } - - /* Get the physical mode from annotation */ - physical_mode = vpr_pb_type_annotation.physical_mode(cur_pb_type); - - VTR_ASSERT(nullptr != physical_mode); - - /* Traverse the pb_type children under the physical mode */ - for (int ichild = 0; ichild < physical_mode->num_pb_type_children; ++ichild) { - rec_infer_vpr_physical_pb_mode_annotation(&(physical_mode->pb_type_children[ichild]), - vpr_pb_type_annotation); - } -} - -/******************************************************************** - * This function will infer the physical mode for each multi-mode - * pb_type in VPR pb_type graph without OpenFPGA architecture XML - * - * The following rule is applied: - * if there is only 1 mode under a pb_type, it will be the default - * physical mode for this pb_type - * - * Note: - * This function must be executed AFTER the function - * build_vpr_physical_pb_mode_explicit_annotation() - *******************************************************************/ -static -void build_vpr_physical_pb_mode_implicit_annotation(const DeviceContext& vpr_device_ctx, - VprPbTypeAnnotation& vpr_pb_type_annotation) { - for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { - /* By pass nullptr for pb_type head */ - if (nullptr == lb_type.pb_type) { - continue; - } - rec_infer_vpr_physical_pb_mode_annotation(lb_type.pb_type, vpr_pb_type_annotation); - } -} - -/******************************************************************** - * This function will recursively traverse pb_type graph to ensure - * 1. there is only a physical mode under each pb_type - * 2. physical mode appears only when its parent is a physical mode. - *******************************************************************/ -static -void rec_check_vpr_physical_pb_mode_annotation(t_pb_type* cur_pb_type, - const bool& expect_physical_mode, - const VprPbTypeAnnotation& vpr_pb_type_annotation, - size_t& num_err) { - /* We do not check any primitive pb_type */ - if (true == is_primitive_pb_type(cur_pb_type)) { - return; - } - - /* For non-primitive pb_type: - * - If we expect a physical mode to exist under this pb_type - * we should be able to find one in the annoation - * - If we do NOT expect a physical mode, make sure we find - * nothing in the annotation - */ - if (true == expect_physical_mode) { - if (nullptr == vpr_pb_type_annotation.physical_mode(cur_pb_type)) { - VTR_LOG_ERROR("Unable to find a physical mode for a multi-mode pb_type '%s'!\n", - cur_pb_type->name); - VTR_LOG_ERROR("Please specify in the OpenFPGA architecture\n"); - num_err++; - return; - } - } else { - VTR_ASSERT_SAFE(false == expect_physical_mode); - if (nullptr != vpr_pb_type_annotation.physical_mode(cur_pb_type)) { - VTR_LOG_ERROR("Find a physical mode '%s' for pb_type '%s' which is not under any physical mode!\n", - vpr_pb_type_annotation.physical_mode(cur_pb_type)->name, - cur_pb_type->name); - num_err++; - return; - } - } - - /* Traverse all the modes - * - for pb_type children under a physical mode, we expect an physical mode - * - for pb_type children under non-physical mode, we expect no physical mode - */ - for (int imode = 0; imode < cur_pb_type->num_modes; ++imode) { - bool expect_child_physical_mode = false; - if (&(cur_pb_type->modes[imode]) == vpr_pb_type_annotation.physical_mode(cur_pb_type)) { - expect_child_physical_mode = true && expect_physical_mode; - } - for (int ichild = 0; ichild < cur_pb_type->modes[imode].num_pb_type_children; ++ichild) { - rec_check_vpr_physical_pb_mode_annotation(&(cur_pb_type->modes[imode].pb_type_children[ichild]), - expect_child_physical_mode, vpr_pb_type_annotation, - num_err); - } - } -} - -/******************************************************************** - * This function will check the physical mode annotation for - * each pb_type in the device - *******************************************************************/ -static -void check_vpr_physical_pb_mode_annotation(const DeviceContext& vpr_device_ctx, - const VprPbTypeAnnotation& vpr_pb_type_annotation) { - size_t num_err = 0; - - for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { - /* By pass nullptr for pb_type head */ - if (nullptr == lb_type.pb_type) { - continue; - } - /* Top pb_type should always has a physical mode! */ - rec_check_vpr_physical_pb_mode_annotation(lb_type.pb_type, true, vpr_pb_type_annotation, num_err); - } - if (0 == num_err) { - VTR_LOG("Check physical mode annotation for pb_types passed.\n"); - } else { - VTR_LOG("Check physical mode annotation for pb_types failed with %ld errors!\n", - num_err); - } -} - -/******************************************************************** - * This function aims to make a pair of operating and physical - * pb_types: - * - In addition to pairing the pb_types, it will pair the ports of the pb_types - * - For the ports which are explicited annotated as physical pin mapping - * in the pb_type annotation. - * We will check the port range and create a pair - * - For the ports which are not specified in the pb_type annotation - * we assume their physical ports share the same as the operating ports - * We will try to find a port in the physical pb_type and check the port range - * If found, we will create a pair - * - All the pairs will be updated in vpr_pb_type_annotation - *******************************************************************/ -static -bool pair_operating_and_physical_pb_types(t_pb_type* operating_pb_type, - t_pb_type* physical_pb_type, - const PbTypeAnnotation& pb_type_annotation, - VprPbTypeAnnotation& vpr_pb_type_annotation) { - /* Reach here, we should have valid operating and physical pb_types */ - VTR_ASSERT((nullptr != operating_pb_type) && (nullptr != physical_pb_type)); - - /* Iterate over the ports under the operating pb_type - * For each pin, we will try to find its physical port in the pb_type_annotation - * if not found, we assume that the physical port is the same as the operating pb_port - */ - for (t_port* operating_pb_port : pb_type_ports(operating_pb_type)) { - /* Try to find the port in the pb_type_annotation */ - BasicPort expected_physical_pb_port = pb_type_annotation.physical_pb_type_port(std::string(operating_pb_port->name)); - if (true == expected_physical_pb_port.get_name().empty()) { - /* Not found, we reset the port information to be consistent as the operating pb_port */ - expected_physical_pb_port.set_name(std::string(operating_pb_port->name)); - expected_physical_pb_port.set_width(operating_pb_port->num_pins); - } - - /* Try to find the expected port in the physical pb_type */ - t_port* physical_pb_port = find_pb_type_port(physical_pb_type, expected_physical_pb_port.get_name()); - /* Not found, mapping fails */ - if (nullptr == physical_pb_port) { - return false; - } - /* If the port range does not match, mapping fails */ - if (false == expected_physical_pb_port.contained(BasicPort(physical_pb_port->name, physical_pb_port->num_pins))) { - return false; - } - /* Now, port mapping should succeed, we update the vpr_pb_type_annotation */ - vpr_pb_type_annotation.add_physical_pb_port(operating_pb_port, physical_pb_port); - vpr_pb_type_annotation.add_physical_pb_port_range(operating_pb_port, expected_physical_pb_port); - } - - /* Now, pb_type mapping should succeed, we update the vpr_pb_type_annotation */ - vpr_pb_type_annotation.add_physical_pb_type(operating_pb_type, physical_pb_type); - - return true; -} - -/******************************************************************** - * This function will identify the physical pb_type for each operating - * pb_type in VPR pb_type graph by following the explicit definition - * in OpenFPGA architecture XML - * - * Note: - * - This function should be executed only AFTER the physical mode - * annotation is completed - *******************************************************************/ -static -void build_vpr_physical_pb_type_explicit_annotation(const DeviceContext& vpr_device_ctx, - const Arch& openfpga_arch, - VprPbTypeAnnotation& vpr_pb_type_annotation) { - /* Walk through the pb_type annotation stored in the openfpga arch */ - for (const PbTypeAnnotation& pb_type_annotation : openfpga_arch.pb_type_annotations) { - /* Since our target is to annotate the operating pb_type tp physical pb_type - * we can skip those annotation only for physical pb_type - */ - if (true == pb_type_annotation.is_physical_pb_type()) { - continue; - } - - VTR_ASSERT(true == pb_type_annotation.is_operating_pb_type()); - - /* Collect the information about the full hierarchy of operating pb_type to be annotated */ - std::vector target_op_pb_type_names; - std::vector target_op_pb_mode_names; - - target_op_pb_type_names = pb_type_annotation.operating_parent_pb_type_names(); - target_op_pb_type_names.push_back(pb_type_annotation.operating_pb_type_name()); - target_op_pb_mode_names = pb_type_annotation.operating_parent_mode_names(); - - /* Collect the information about the full hierarchy of physical pb_type to be annotated */ - std::vector target_phy_pb_type_names; - std::vector target_phy_pb_mode_names; - - target_phy_pb_type_names = pb_type_annotation.physical_parent_pb_type_names(); - target_phy_pb_type_names.push_back(pb_type_annotation.physical_pb_type_name()); - target_phy_pb_mode_names = pb_type_annotation.physical_parent_mode_names(); - - /* We must have at least one pb_type in the list */ - VTR_ASSERT_SAFE(0 < target_op_pb_type_names.size()); - VTR_ASSERT_SAFE(0 < target_phy_pb_type_names.size()); - - /* Pb type information are located at the logic_block_types in the device context of VPR - * We iterate over the vectors and find the pb_type matches the parent_pb_type_name - */ - bool link_success = false; - - for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { - /* By pass nullptr for pb_type head */ - if (nullptr == lb_type.pb_type) { - continue; - } - /* Check the name of the top-level pb_type, if it does not match, we can bypass */ - if (target_op_pb_type_names[0] != std::string(lb_type.pb_type->name)) { - continue; - } - /* Match the name in the top-level, we go further to search the operating as well as - * physical pb_types in the graph */ - t_pb_type* target_op_pb_type = try_find_pb_type_with_given_path(lb_type.pb_type, target_op_pb_type_names, - target_op_pb_mode_names); - if (nullptr == target_op_pb_type) { - continue; - } - - t_pb_type* target_phy_pb_type = try_find_pb_type_with_given_path(lb_type.pb_type, target_phy_pb_type_names, - target_phy_pb_mode_names); - if (nullptr == target_phy_pb_type) { - continue; - } - - /* Both operating and physical pb_type have been found, - * we update the annotation by assigning the physical mode - */ - if (true == pair_operating_and_physical_pb_types(target_op_pb_type, target_phy_pb_type, - pb_type_annotation, vpr_pb_type_annotation)) { - - /* Give a message */ - VTR_LOG("Annotate operating pb_type '%s' to its physical pb_type '%s'\n", - target_op_pb_type->name, target_phy_pb_type->name); - - link_success = true; - break; - } - } - - if (false == link_success) { - /* Not found, error out! */ - VTR_LOG_ERROR("Unable to pair the operating pb_type '%s' to its physical pb_type '%s'!\n", - target_op_pb_type_names.back().c_str(), - target_phy_pb_type_names.back().c_str()); - return; - } - } -} - -/******************************************************************** - * This function aims to pair a physical pb_type to itself - *******************************************************************/ -static -bool self_pair_physical_pb_types(t_pb_type* physical_pb_type, - VprPbTypeAnnotation& vpr_pb_type_annotation) { - /* Reach here, we should have valid physical pb_types */ - VTR_ASSERT(nullptr != physical_pb_type); - - /* Iterate over the ports under the operating pb_type - * For each pin, we will try to find its physical port in the pb_type_annotation - * if not found, we assume that the physical port is the same as the operating pb_port - */ - for (t_port* physical_pb_port : pb_type_ports(physical_pb_type)) { - BasicPort physical_port_range(physical_pb_port->name, physical_pb_port->num_pins); - vpr_pb_type_annotation.add_physical_pb_port(physical_pb_port, physical_pb_port); - vpr_pb_type_annotation.add_physical_pb_port_range(physical_pb_port, physical_port_range); - } - - /* Now, pb_type mapping should succeed, we update the vpr_pb_type_annotation */ - vpr_pb_type_annotation.add_physical_pb_type(physical_pb_type, physical_pb_type); - - return true; -} - -/******************************************************************** - * This function will recursively visit all the pb_type from the top - * pb_type in the graph (only in the physical mode) and infer the - * physical pb_type - * This is mainly applied to single-mode pb_type graphs, where the - * physical pb_type should be pb_type itself - * We can infer this and save the explicit annotation required by users - *******************************************************************/ -static -void rec_infer_vpr_physical_pb_type_annotation(t_pb_type* cur_pb_type, - VprPbTypeAnnotation& vpr_pb_type_annotation) { - /* Physical pb_type is mainly for the primitive pb_type */ - if (true == is_primitive_pb_type(cur_pb_type)) { - /* If the physical pb_type has been mapped, we can skip it */ - if (nullptr != vpr_pb_type_annotation.physical_pb_type(cur_pb_type)) { - return; - } - /* Create the pair here */ - if (true == self_pair_physical_pb_types(cur_pb_type, vpr_pb_type_annotation)) { - /* Give a message */ - VTR_LOG("Implicitly infer the physical pb_type for pb_type '%s' itself\n", - cur_pb_type->name); - } else { - VTR_LOG_ERROR("Unable to infer the physical pb_type for pb_type '%s' itself!\n", - cur_pb_type->name); - } - return; - } - - /* Get the physical mode from annotation */ - t_mode* physical_mode = vpr_pb_type_annotation.physical_mode(cur_pb_type); - - VTR_ASSERT(nullptr != physical_mode); - - /* Traverse the pb_type children under the physical mode */ - for (int ichild = 0; ichild < physical_mode->num_pb_type_children; ++ichild) { - rec_infer_vpr_physical_pb_type_annotation(&(physical_mode->pb_type_children[ichild]), - vpr_pb_type_annotation); - } -} - -/******************************************************************** - * This function will infer the physical pb_type for each operating - * pb_type in VPR pb_type graph which have not been explicitedly defined - * in OpenFPGA architecture XML - * - * Note: - * - This function should be executed only AFTER the physical mode - * annotation is completed - *******************************************************************/ -static -void build_vpr_physical_pb_type_implicit_annotation(const DeviceContext& vpr_device_ctx, - VprPbTypeAnnotation& vpr_pb_type_annotation) { - for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { - /* By pass nullptr for pb_type head */ - if (nullptr == lb_type.pb_type) { - continue; - } - rec_infer_vpr_physical_pb_type_annotation(lb_type.pb_type, vpr_pb_type_annotation); - } -} - /******************************************************************** * Top-level function to link openfpga architecture to VPR, including: * - physical pb_type - * - idle pb_type - * - circuit models for pb_type, pb interconnect and routing architecture + * - mode selection bits for pb_type and pb interconnect + * - circuit models for pb_type and pb interconnect + * - physical pb_graph nodes and pb_graph pins + * - circuit models for global routing architecture *******************************************************************/ void link_arch(OpenfpgaContext& openfpga_context) { vtr::ScopedStartFinishTimer timer("Link OpenFPGA architecture to VPR architecture"); - /* Annotate physical mode to pb_type in the VPR pb_type graph */ - build_vpr_physical_pb_mode_explicit_annotation(g_vpr_ctx.device(), openfpga_context.arch(), - openfpga_context.mutable_vpr_pb_type_annotation()); - build_vpr_physical_pb_mode_implicit_annotation(g_vpr_ctx.device(), - openfpga_context.mutable_vpr_pb_type_annotation()); - - check_vpr_physical_pb_mode_annotation(g_vpr_ctx.device(), - openfpga_context.vpr_pb_type_annotation()); - - /* Annotate physical pb_types to operating pb_type in the VPR pb_type graph */ - build_vpr_physical_pb_type_explicit_annotation(g_vpr_ctx.device(), openfpga_context.arch(), - openfpga_context.mutable_vpr_pb_type_annotation()); - - build_vpr_physical_pb_type_implicit_annotation(g_vpr_ctx.device(), - openfpga_context.mutable_vpr_pb_type_annotation()); - - /* Link physical pb_type to circuit model */ + /* Annotate pb_type graphs + * - physical pb_type + * - mode selection bits for pb_type and pb interconnect + * - circuit models for pb_type and pb interconnect + */ + annotate_pb_types(g_vpr_ctx.device(), openfpga_context.arch(), + openfpga_context.mutable_vpr_pb_type_annotation()); /* Link routing architecture to circuit model */ } diff --git a/openfpga/src/base/openfpga_link_arch.h b/openfpga/src/base/openfpga_link_arch.h index c36c57711..3775ee789 100644 --- a/openfpga/src/base/openfpga_link_arch.h +++ b/openfpga/src/base/openfpga_link_arch.h @@ -1,5 +1,5 @@ -#ifndef OPENFPGA_LINK_ARCH_COMMAND_H -#define OPENFPGA_LINK_ARCH_COMMAND_H +#ifndef OPENFPGA_LINK_ARCH_H +#define OPENFPGA_LINK_ARCH_H /******************************************************************** * Include header files that are required by function declaration diff --git a/openfpga/src/base/pb_type_utils.cpp b/openfpga/src/base/pb_type_utils.cpp index 1d0c31b6c..980614538 100644 --- a/openfpga/src/base/pb_type_utils.cpp +++ b/openfpga/src/base/pb_type_utils.cpp @@ -88,4 +88,54 @@ t_port* find_pb_type_port(t_pb_type* pb_type, const std::string& port_name) { return nullptr; } +/******************************************************************** + * This function will traverse pb_type graph from its top to find + * a pb_type with a given name as well as its hierarchy + *******************************************************************/ +t_pb_type* try_find_pb_type_with_given_path(t_pb_type* top_pb_type, + const std::vector& target_pb_type_names, + const std::vector& target_pb_mode_names) { + /* Ensure that number of parent names and modes matches */ + VTR_ASSERT_SAFE(target_pb_type_names.size() == target_pb_mode_names.size() + 1); + + t_pb_type* cur_pb_type = top_pb_type; + + /* If the top pb_type is what we want, we can return here */ + if (1 == target_pb_type_names.size()) { + if (target_pb_type_names[0] == std::string(top_pb_type->name)) { + return top_pb_type; + } + /* Not match, return null pointer */ + return nullptr; + } + + /* We start from the first element of the parent names and parent modes. + * If the pb_type does not match in name, we fail + * If we cannot find a mode match the name, we fail + */ + for (size_t i = 0; i < target_pb_type_names.size() - 1; ++i) { + /* If this level does not match, search fail */ + if (target_pb_type_names[i] != std::string(cur_pb_type->name)) { + return nullptr; + } + /* Find if the mode matches */ + t_mode* cur_mode = find_pb_type_mode(cur_pb_type, target_pb_mode_names[i].c_str()); + if (nullptr == cur_mode) { + return nullptr; + } + /* Go to the next level of pb_type */ + cur_pb_type = find_mode_child_pb_type(cur_mode, target_pb_type_names[i + 1].c_str()); + if (nullptr == cur_pb_type) { + return nullptr; + } + /* If this is already the last pb_type in the list, this is what we want */ + if (i + 1 == target_pb_type_names.size() - 1) { + return cur_pb_type; + } + } + + /* Reach here, it means we find nothing */ + return nullptr; +} + } /* end namespace openfpga */ diff --git a/openfpga/src/base/pb_type_utils.h b/openfpga/src/base/pb_type_utils.h index 902f203e3..aa658d9da 100644 --- a/openfpga/src/base/pb_type_utils.h +++ b/openfpga/src/base/pb_type_utils.h @@ -4,6 +4,7 @@ /******************************************************************** * Include header files that are required by function declaration *******************************************************************/ +#include #include #include "physical_types.h" @@ -26,6 +27,10 @@ std::vector pb_type_ports(t_pb_type* pb_type); t_port* find_pb_type_port(t_pb_type* pb_type, const std::string& port_name); +t_pb_type* try_find_pb_type_with_given_path(t_pb_type* top_pb_type, + const std::vector& target_pb_type_names, + const std::vector& target_pb_mode_names); + } /* end namespace openfpga */ #endif From a4a84ca35bdac12a1e7e3c14f49c969bbe88bb3b Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 28 Jan 2020 15:27:00 -0700 Subject: [PATCH 035/645] add check codes for physical pb_type and port annotation --- openfpga/src/base/annotate_pb_types.cpp | 89 +++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/openfpga/src/base/annotate_pb_types.cpp b/openfpga/src/base/annotate_pb_types.cpp index fe7629281..ae0106ca5 100644 --- a/openfpga/src/base/annotate_pb_types.cpp +++ b/openfpga/src/base/annotate_pb_types.cpp @@ -498,6 +498,92 @@ void build_vpr_physical_pb_type_implicit_annotation(const DeviceContext& vpr_dev } } +/******************************************************************** + * This function will check + * - if a primitive pb_type has been mapped to a physical pb_type + * - if every port of the pb_type have been linked a port of a physical pb_type + *******************************************************************/ +static +void check_vpr_physical_primitive_pb_type_annotation(t_pb_type* cur_pb_type, + const VprPbTypeAnnotation& vpr_pb_type_annotation, + size_t& num_err) { + if (nullptr == vpr_pb_type_annotation.physical_pb_type(cur_pb_type)) { + VTR_LOG_ERROR("Find a pb_type '%s' which has not been mapped to any physical pb_type!\n", + cur_pb_type->name); + VTR_LOG_ERROR("Please specify in the OpenFPGA architecture\n"); + num_err++; + return; + } + + /* Now we need to check each port of the pb_type */ + for (t_port* pb_port : pb_type_ports(cur_pb_type)) { + if (nullptr == vpr_pb_type_annotation.physical_pb_port(pb_port)) { + VTR_LOG_ERROR("Find a port '%s' of pb_type '%s' which has not been mapped to any physical port!\n", + pb_port->name, cur_pb_type->name); + VTR_LOG_ERROR("Please specify in the OpenFPGA architecture\n"); + num_err++; + } + } + + return; +} + +/******************************************************************** + * This function will recursively traverse pb_type graph to ensure + * 1. there is only a physical mode under each pb_type + * 2. physical mode appears only when its parent is a physical mode. + *******************************************************************/ +static +void rec_check_vpr_physical_pb_type_annotation(t_pb_type* cur_pb_type, + const VprPbTypeAnnotation& vpr_pb_type_annotation, + size_t& num_err) { + /* Primitive pb_type should always been binded to a physical pb_type */ + if (true == is_primitive_pb_type(cur_pb_type)) { + check_vpr_physical_primitive_pb_type_annotation(cur_pb_type, vpr_pb_type_annotation, num_err); + return; + } + + /* Traverse all the modes + * - for pb_type children under a physical mode, we expect an physical mode + * - for pb_type children under non-physical mode, we expect no physical mode + */ + for (int imode = 0; imode < cur_pb_type->num_modes; ++imode) { + for (int ichild = 0; ichild < cur_pb_type->modes[imode].num_pb_type_children; ++ichild) { + rec_check_vpr_physical_pb_type_annotation(&(cur_pb_type->modes[imode].pb_type_children[ichild]), + vpr_pb_type_annotation, + num_err); + } + } +} + +/******************************************************************** + * This function will check the physical pb_type annotation for + * each pb_type in the device + * Every pb_type should have been linked to a physical pb_type + * and every port of the pb_type have been linked a port of a physical pb_type + *******************************************************************/ +static +void check_vpr_physical_pb_type_annotation(const DeviceContext& vpr_device_ctx, + const VprPbTypeAnnotation& vpr_pb_type_annotation) { + size_t num_err = 0; + + for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { + /* By pass nullptr for pb_type head */ + if (nullptr == lb_type.pb_type) { + continue; + } + /* Top pb_type should always has a physical mode! */ + rec_check_vpr_physical_pb_type_annotation(lb_type.pb_type, vpr_pb_type_annotation, num_err); + } + if (0 == num_err) { + VTR_LOG("Check physical pb_type annotation for pb_types passed.\n"); + } else { + VTR_LOG("Check physical pb_type annotation for pb_types failed with %ld errors!\n", + num_err); + } +} + + /******************************************************************** * Top-level function to link openfpga architecture to VPR, including: * - physical pb_type @@ -523,6 +609,9 @@ void annotate_pb_types(const DeviceContext& vpr_device_ctx, build_vpr_physical_pb_type_implicit_annotation(vpr_device_ctx, vpr_pb_type_annotation); + check_vpr_physical_pb_type_annotation(vpr_device_ctx, + const_cast(vpr_pb_type_annotation)); + /* Link physical pb_type to circuit model */ /* Link routing architecture to circuit model */ From 1651c9ca188b64a940d4d2412c1108594f9cc28a Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 28 Jan 2020 16:03:02 -0700 Subject: [PATCH 036/645] add binding between physical pb_type and circuit models --- openfpga/src/base/annotate_pb_types.cpp | 130 +++++++++++++++++- openfpga/src/base/vpr_pb_type_annotation.cpp | 33 ++++- openfpga/src/base/vpr_pb_type_annotation.h | 6 +- .../k6_N10_40nm_openfpga.xml | 6 +- 4 files changed, 166 insertions(+), 9 deletions(-) diff --git a/openfpga/src/base/annotate_pb_types.cpp b/openfpga/src/base/annotate_pb_types.cpp index ae0106ca5..3186a896d 100644 --- a/openfpga/src/base/annotate_pb_types.cpp +++ b/openfpga/src/base/annotate_pb_types.cpp @@ -583,6 +583,125 @@ void check_vpr_physical_pb_type_annotation(const DeviceContext& vpr_device_ctx, } } +/******************************************************************** + * This function aims to link a physical pb_type to a valid circuit model + * in the circuit library + *******************************************************************/ +static +bool link_physical_pb_type_to_circuit_model(t_pb_type* physical_pb_type, + const CircuitLibrary& circuit_lib, + const PbTypeAnnotation& pb_type_annotation, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + /* Reach here, we should have valid operating and physical pb_types */ + VTR_ASSERT(nullptr != physical_pb_type); + + /* This must be a physical pb_type according to our annotation! */ + if (false == vpr_pb_type_annotation.is_physical_pb_type(physical_pb_type)) { + VTR_LOG_ERROR("An operating pb_type '%s' is not allowed to be linked to any circuit model!\n", + physical_pb_type->name); + return false; + } + + std::string pb_type_circuit_model_name = pb_type_annotation.circuit_model_name(); + CircuitModelId circuit_model_id = circuit_lib.model(pb_type_circuit_model_name); + + if (CircuitModelId::INVALID() == circuit_model_id) { + return false; + } + + /* Now the circuit model is valid, update the vpr_pb_type_annotation */ + vpr_pb_type_annotation.add_pb_type_circuit_model(physical_pb_type, circuit_model_id); + return true; +} + +/******************************************************************** + * This function will link + * - pb_type to circuit models in circuit library by following + * the explicit definition in OpenFPGA architecture XML + * - interconnect of pb_type to circuit models in circuit library by following + * the explicit definition in OpenFPGA architecture XML + * + * Note: + * - This function should be executed only AFTER the physical mode and + * physical pb_type annotation is completed + *******************************************************************/ +static +void link_vpr_pb_type_to_circuit_model_explicit_annotation(const DeviceContext& vpr_device_ctx, + const Arch& openfpga_arch, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + /* Walk through the pb_type annotation stored in the openfpga arch */ + for (const PbTypeAnnotation& pb_type_annotation : openfpga_arch.pb_type_annotations) { + /* Since our target is to annotate the circuti model for physical pb_type + * we can skip those annotation only for operating pb_type + */ + if (true == pb_type_annotation.is_operating_pb_type()) { + continue; + } + + /* Bypass those who have no circuit model defined */ + if (true == pb_type_annotation.circuit_model_name().empty()) { + continue; + } + + VTR_ASSERT(true == pb_type_annotation.is_physical_pb_type()); + + /* Collect the information about the full hierarchy of physical pb_type to be annotated */ + std::vector target_phy_pb_type_names; + std::vector target_phy_pb_mode_names; + + target_phy_pb_type_names = pb_type_annotation.physical_parent_pb_type_names(); + target_phy_pb_type_names.push_back(pb_type_annotation.physical_pb_type_name()); + target_phy_pb_mode_names = pb_type_annotation.physical_parent_mode_names(); + + /* We must have at least one pb_type in the list */ + VTR_ASSERT_SAFE(0 < target_phy_pb_type_names.size()); + + /* Pb type information are located at the logic_block_types in the device context of VPR + * We iterate over the vectors and find the pb_type matches the parent_pb_type_name + */ + bool link_success = false; + + for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { + /* By pass nullptr for pb_type head */ + if (nullptr == lb_type.pb_type) { + continue; + } + /* Check the name of the top-level pb_type, if it does not match, we can bypass */ + if (target_phy_pb_type_names[0] != std::string(lb_type.pb_type->name)) { + continue; + } + /* Match the name in the top-level, we go further to search the operating as well as + * physical pb_types in the graph */ + t_pb_type* target_phy_pb_type = try_find_pb_type_with_given_path(lb_type.pb_type, target_phy_pb_type_names, + target_phy_pb_mode_names); + if (nullptr == target_phy_pb_type) { + continue; + } + + /* Both operating and physical pb_type have been found, + * we update the annotation by assigning the physical mode + */ + if (true == link_physical_pb_type_to_circuit_model(target_phy_pb_type, openfpga_arch.circuit_lib, + pb_type_annotation, vpr_pb_type_annotation)) { + /* Give a message */ + VTR_LOG("Bind physical pb_type '%s' to its circuit model '%s'\n", + target_phy_pb_type->name, + openfpga_arch.circuit_lib.model_name(vpr_pb_type_annotation.pb_type_circuit_model(target_phy_pb_type)).c_str()); + + link_success = true; + break; + } + } + + if (false == link_success) { + /* Not found, error out! */ + VTR_LOG_ERROR("Unable to bind physical pb_type '%s' to circuit model '%s'!\n", + target_phy_pb_type_names.back().c_str(), + pb_type_annotation.circuit_model_name().c_str()); + return; + } + } +} /******************************************************************** * Top-level function to link openfpga architecture to VPR, including: @@ -596,6 +715,7 @@ void annotate_pb_types(const DeviceContext& vpr_device_ctx, /* Annotate physical mode to pb_type in the VPR pb_type graph */ build_vpr_physical_pb_mode_explicit_annotation(vpr_device_ctx, openfpga_arch, vpr_pb_type_annotation); + build_vpr_physical_pb_mode_implicit_annotation(vpr_device_ctx, vpr_pb_type_annotation); @@ -612,9 +732,15 @@ void annotate_pb_types(const DeviceContext& vpr_device_ctx, check_vpr_physical_pb_type_annotation(vpr_device_ctx, const_cast(vpr_pb_type_annotation)); - /* Link physical pb_type to circuit model */ + /* Link + * - physical pb_type to circuit model + * - interconnect of physical pb_type to circuit model + */ + link_vpr_pb_type_to_circuit_model_explicit_annotation(vpr_device_ctx, openfpga_arch, + vpr_pb_type_annotation); + + /* Link physical pb_type to mode_bits */ - /* Link routing architecture to circuit model */ } } /* end namespace openfpga */ diff --git a/openfpga/src/base/vpr_pb_type_annotation.cpp b/openfpga/src/base/vpr_pb_type_annotation.cpp index e89045307..2dedacb28 100644 --- a/openfpga/src/base/vpr_pb_type_annotation.cpp +++ b/openfpga/src/base/vpr_pb_type_annotation.cpp @@ -16,8 +16,18 @@ VprPbTypeAnnotation::VprPbTypeAnnotation() { } /************************************************************************ - * Public mutators + * Public accessors ***********************************************************************/ +bool VprPbTypeAnnotation::is_physical_pb_type(t_pb_type* pb_type) const { + /* Ensure that the pb_type is in the list */ + std::map::const_iterator it = physical_pb_types_.find(pb_type); + if (it == physical_pb_types_.end()) { + return false; + } + /* A physical pb_type should be mapped to itself! Otherwise, it is an operating pb_type */ + return pb_type == physical_pb_types_.at(pb_type); +} + t_mode* VprPbTypeAnnotation::physical_mode(t_pb_type* pb_type) const { /* Ensure that the pb_type is in the list */ std::map::const_iterator it = physical_pb_modes_.find(pb_type); @@ -55,6 +65,16 @@ BasicPort VprPbTypeAnnotation::physical_pb_port_range(t_port* pb_port) const { return physical_pb_port_ranges_.at(pb_port); } +CircuitModelId VprPbTypeAnnotation::pb_type_circuit_model(t_pb_type* physical_pb_type) const { + /* Ensure that the pb_type is in the list */ + std::map::const_iterator it = pb_type_circuit_models_.find(physical_pb_type); + if (it == pb_type_circuit_models_.end()) { + /* Return an invalid port. As such the port width will be 0, which is an invalid value */ + return CircuitModelId::INVALID(); + } + return pb_type_circuit_models_.at(physical_pb_type); +} + /************************************************************************ * Public mutators ***********************************************************************/ @@ -105,4 +125,15 @@ void VprPbTypeAnnotation::add_physical_pb_port_range(t_port* operating_pb_port, physical_pb_port_ranges_[operating_pb_port] = port_range; } +void VprPbTypeAnnotation::add_pb_type_circuit_model(t_pb_type* physical_pb_type, const CircuitModelId& circuit_model) { + /* Warn any override attempt */ + std::map::const_iterator it = pb_type_circuit_models_.find(physical_pb_type); + if (it != pb_type_circuit_models_.end()) { + VTR_LOG_WARN("Override the circuit model for physical pb_type '%s'!\n", + physical_pb_type->name); + } + + pb_type_circuit_models_[physical_pb_type] = circuit_model; +} + } /* End namespace openfpga*/ diff --git a/openfpga/src/base/vpr_pb_type_annotation.h b/openfpga/src/base/vpr_pb_type_annotation.h index 90b29598f..e235538e7 100644 --- a/openfpga/src/base/vpr_pb_type_annotation.h +++ b/openfpga/src/base/vpr_pb_type_annotation.h @@ -29,19 +29,19 @@ class VprPbTypeAnnotation { public: /* Constructor */ VprPbTypeAnnotation(); public: /* Public accessors */ + bool is_physical_pb_type(t_pb_type* pb_type) const; t_mode* physical_mode(t_pb_type* pb_type) const; t_pb_type* physical_pb_type(t_pb_type* pb_type) const; t_port* physical_pb_port(t_port* pb_port) const; BasicPort physical_pb_port_range(t_port* pb_port) const; + CircuitModelId pb_type_circuit_model(t_pb_type* physical_pb_type) const; public: /* Public mutators */ void add_pb_type_physical_mode(t_pb_type* pb_type, t_mode* physical_mode); void add_physical_pb_type(t_pb_type* operating_pb_type, t_pb_type* physical_pb_type); void add_physical_pb_port(t_port* operating_pb_port, t_port* physical_pb_port); void add_physical_pb_port_range(t_port* operating_pb_port, const BasicPort& port_range); + void add_pb_type_circuit_model(t_pb_type* physical_pb_type, const CircuitModelId& circuit_model); private: /* Internal data */ - /* Flag about if a pb_type is a physical pb_type */ - std::map is_physical_pb_types_; - /* Pair a regular pb_type to its physical pb_type */ std::map physical_pb_types_; diff --git a/openfpga/test_openfpga_arch/k6_N10_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_N10_40nm_openfpga.xml index 23607e234..d93495f6b 100644 --- a/openfpga/test_openfpga_arch/k6_N10_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_N10_40nm_openfpga.xml @@ -134,16 +134,16 @@ - + - + - + From bb7fa2af77b1df514e70b809133c6cd0f5a992f6 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 28 Jan 2020 17:04:10 -0700 Subject: [PATCH 037/645] add pb interconnect binding to circuit model --- openfpga/src/base/annotate_pb_types.cpp | 154 ++++++++++++++++++- openfpga/src/base/pb_type_utils.cpp | 16 ++ openfpga/src/base/pb_type_utils.h | 2 + openfpga/src/base/vpr_pb_type_annotation.cpp | 23 ++- openfpga/src/base/vpr_pb_type_annotation.h | 8 + openfpga/test_script/s298.openfpga | 3 + 6 files changed, 199 insertions(+), 7 deletions(-) diff --git a/openfpga/src/base/annotate_pb_types.cpp b/openfpga/src/base/annotate_pb_types.cpp index 3186a896d..e8e7ddab4 100644 --- a/openfpga/src/base/annotate_pb_types.cpp +++ b/openfpga/src/base/annotate_pb_types.cpp @@ -606,6 +606,9 @@ bool link_physical_pb_type_to_circuit_model(t_pb_type* physical_pb_type, CircuitModelId circuit_model_id = circuit_lib.model(pb_type_circuit_model_name); if (CircuitModelId::INVALID() == circuit_model_id) { + VTR_LOG_ERROR("Unable to find a circuit model '%s' for physical pb_type '%s'!\n", + pb_type_circuit_model_name.c_str(), + physical_pb_type->name); return false; } @@ -614,12 +617,68 @@ bool link_physical_pb_type_to_circuit_model(t_pb_type* physical_pb_type, return true; } +/******************************************************************** + * This function aims to link an interconnect of a physical mode of + * a pb_type to a valid circuit model in the circuit library + *******************************************************************/ +static +bool link_physical_pb_interconnect_to_circuit_model(t_pb_type* physical_pb_type, + const std::string& interconnect_name, + const CircuitLibrary& circuit_lib, + const PbTypeAnnotation& pb_type_annotation, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + /* The physical pb_type should NOT be a primitive, otherwise it should never contain any interconnect */ + if (true == is_primitive_pb_type(physical_pb_type)) { + VTR_LOG_ERROR("Link interconnect to circuit model is not allowed for a primitive pb_type '%s'!\n", + physical_pb_type->name); + return false; + } + + /* Get the physical mode from annotation */ + t_mode* physical_mode = vpr_pb_type_annotation.physical_mode(physical_pb_type); + + VTR_ASSERT(nullptr != physical_mode); + + /* Find the interconnect name under the physical mode of a physical pb_type */ + t_interconnect* pb_interc = find_pb_mode_interconnect(physical_mode, interconnect_name.c_str()); + + if (nullptr == pb_interc) { + VTR_LOG_ERROR("Unable to find interconnect '%s' under physical mode '%s' of pb_type '%s'!\n", + interconnect_name.c_str(), + physical_mode->name, + physical_pb_type->name); + return false; + } + + /* Try to find the circuit model name */ + std::string pb_type_circuit_model_name = pb_type_annotation.interconnect_circuit_model_name(interconnect_name); + CircuitModelId circuit_model_id = circuit_lib.model(pb_type_circuit_model_name); + + if (CircuitModelId::INVALID() == circuit_model_id) { + VTR_LOG_ERROR("Unable to find a circuit model '%s' for interconnect '%s' under physical mode '%s' of pb_type '%s'!\n", + pb_type_circuit_model_name.c_str(), + interconnect_name.c_str(), + physical_mode->name, + physical_pb_type->name); + return false; + } + + /* Now the circuit model is valid, update the vpr_pb_type_annotation */ + vpr_pb_type_annotation.add_interconnect_circuit_model(pb_interc, circuit_model_id); + + VTR_LOG("Bind pb_type '%s' physical mode '%s' interconnect '%s' to circuit model '%s'\n", + physical_pb_type->name, + physical_mode->name, + pb_interc->name, + circuit_lib.model_name(circuit_model_id).c_str()); + + return true; +} + /******************************************************************** * This function will link * - pb_type to circuit models in circuit library by following * the explicit definition in OpenFPGA architecture XML - * - interconnect of pb_type to circuit models in circuit library by following - * the explicit definition in OpenFPGA architecture XML * * Note: * - This function should be executed only AFTER the physical mode and @@ -638,7 +697,6 @@ void link_vpr_pb_type_to_circuit_model_explicit_annotation(const DeviceContext& continue; } - /* Bypass those who have no circuit model defined */ if (true == pb_type_annotation.circuit_model_name().empty()) { continue; } @@ -678,9 +736,7 @@ void link_vpr_pb_type_to_circuit_model_explicit_annotation(const DeviceContext& continue; } - /* Both operating and physical pb_type have been found, - * we update the annotation by assigning the physical mode - */ + /* Only try to bind pb_type to circuit model when it is defined by users */ if (true == link_physical_pb_type_to_circuit_model(target_phy_pb_type, openfpga_arch.circuit_lib, pb_type_annotation, vpr_pb_type_annotation)) { /* Give a message */ @@ -703,6 +759,90 @@ void link_vpr_pb_type_to_circuit_model_explicit_annotation(const DeviceContext& } } +/******************************************************************** + * This function will link + * - interconnect of pb_type to circuit models in circuit library by following + * the explicit definition in OpenFPGA architecture XML + * + * Note: + * - This function should be executed only AFTER the physical mode and + * physical pb_type annotation is completed + *******************************************************************/ +static +void link_vpr_pb_interconnect_to_circuit_model_explicit_annotation(const DeviceContext& vpr_device_ctx, + const Arch& openfpga_arch, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + /* Walk through the pb_type annotation stored in the openfpga arch */ + for (const PbTypeAnnotation& pb_type_annotation : openfpga_arch.pb_type_annotations) { + /* Since our target is to annotate the circuti model for physical pb_type + * we can skip those annotation only for operating pb_type + */ + if (true == pb_type_annotation.is_operating_pb_type()) { + continue; + } + + if (0 == pb_type_annotation.interconnect_names().size()) { + continue; + } + + VTR_ASSERT(true == pb_type_annotation.is_physical_pb_type()); + + /* Collect the information about the full hierarchy of physical pb_type to be annotated */ + std::vector target_phy_pb_type_names; + std::vector target_phy_pb_mode_names; + + target_phy_pb_type_names = pb_type_annotation.physical_parent_pb_type_names(); + target_phy_pb_type_names.push_back(pb_type_annotation.physical_pb_type_name()); + target_phy_pb_mode_names = pb_type_annotation.physical_parent_mode_names(); + + /* We must have at least one pb_type in the list */ + VTR_ASSERT_SAFE(0 < target_phy_pb_type_names.size()); + + /* Pb type information are located at the logic_block_types in the device context of VPR + * We iterate over the vectors and find the pb_type matches the parent_pb_type_name + */ + bool link_success = true; + + for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { + /* By pass nullptr for pb_type head */ + if (nullptr == lb_type.pb_type) { + continue; + } + /* Check the name of the top-level pb_type, if it does not match, we can bypass */ + if (target_phy_pb_type_names[0] != std::string(lb_type.pb_type->name)) { + continue; + } + /* Match the name in the top-level, we go further to search the operating as well as + * physical pb_types in the graph */ + t_pb_type* target_phy_pb_type = try_find_pb_type_with_given_path(lb_type.pb_type, target_phy_pb_type_names, + target_phy_pb_mode_names); + if (nullptr == target_phy_pb_type) { + continue; + } + + /* Only try to bind interconnect to circuit model when it is defined by users */ + for (const std::string& interc_name : pb_type_annotation.interconnect_names()) { + if (false == link_physical_pb_interconnect_to_circuit_model(target_phy_pb_type, interc_name, openfpga_arch.circuit_lib, + pb_type_annotation, vpr_pb_type_annotation)) { + VTR_LOG_ERROR("Unable to bind pb_type '%s' interconnect '%s' to circuit model '%s'!\n", + target_phy_pb_type_names.back().c_str(), + interc_name.c_str(), + pb_type_annotation.circuit_model_name().c_str()); + link_success = false; + } + } + } + + if (false == link_success) { + /* Not found, error out! */ + VTR_LOG_ERROR("Unable to bind interconnects of physical pb_type '%s' to circuit model '%s'!\n", + target_phy_pb_type_names.back().c_str(), + pb_type_annotation.circuit_model_name().c_str()); + return; + } + } +} + /******************************************************************** * Top-level function to link openfpga architecture to VPR, including: * - physical pb_type @@ -738,6 +878,8 @@ void annotate_pb_types(const DeviceContext& vpr_device_ctx, */ link_vpr_pb_type_to_circuit_model_explicit_annotation(vpr_device_ctx, openfpga_arch, vpr_pb_type_annotation); + link_vpr_pb_interconnect_to_circuit_model_explicit_annotation(vpr_device_ctx, openfpga_arch, + vpr_pb_type_annotation); /* Link physical pb_type to mode_bits */ diff --git a/openfpga/src/base/pb_type_utils.cpp b/openfpga/src/base/pb_type_utils.cpp index 980614538..93b644b8c 100644 --- a/openfpga/src/base/pb_type_utils.cpp +++ b/openfpga/src/base/pb_type_utils.cpp @@ -138,4 +138,20 @@ t_pb_type* try_find_pb_type_with_given_path(t_pb_type* top_pb_type, return nullptr; } +/******************************************************************** + * This function will try to find an interconnect defined under a mode + * of pb_type with a given name. + * If not found, return null pointer + *******************************************************************/ +t_interconnect* find_pb_mode_interconnect(t_mode* pb_mode, const char* interc_name) { + for (int i = 0; i < pb_mode->num_interconnect; ++i) { + if (std::string(interc_name) == std::string(pb_mode->interconnect[i].name)) { + return &(pb_mode->interconnect[i]); + } + } + + /* Reach here, it means we find nothing */ + return nullptr; +} + } /* end namespace openfpga */ diff --git a/openfpga/src/base/pb_type_utils.h b/openfpga/src/base/pb_type_utils.h index aa658d9da..3e9f9a07f 100644 --- a/openfpga/src/base/pb_type_utils.h +++ b/openfpga/src/base/pb_type_utils.h @@ -31,6 +31,8 @@ t_pb_type* try_find_pb_type_with_given_path(t_pb_type* top_pb_type, const std::vector& target_pb_type_names, const std::vector& target_pb_mode_names); +t_interconnect* find_pb_mode_interconnect(t_mode* pb_mode, const char* interc_name); + } /* end namespace openfpga */ #endif diff --git a/openfpga/src/base/vpr_pb_type_annotation.cpp b/openfpga/src/base/vpr_pb_type_annotation.cpp index 2dedacb28..8949d13c3 100644 --- a/openfpga/src/base/vpr_pb_type_annotation.cpp +++ b/openfpga/src/base/vpr_pb_type_annotation.cpp @@ -69,12 +69,22 @@ CircuitModelId VprPbTypeAnnotation::pb_type_circuit_model(t_pb_type* physical_pb /* Ensure that the pb_type is in the list */ std::map::const_iterator it = pb_type_circuit_models_.find(physical_pb_type); if (it == pb_type_circuit_models_.end()) { - /* Return an invalid port. As such the port width will be 0, which is an invalid value */ + /* Return an invalid circuit model id */ return CircuitModelId::INVALID(); } return pb_type_circuit_models_.at(physical_pb_type); } +CircuitModelId VprPbTypeAnnotation::interconnect_circuit_model(t_interconnect* pb_interconnect) const { + /* Ensure that the pb_type is in the list */ + std::map::const_iterator it = interconnect_circuit_models_.find(pb_interconnect); + if (it == interconnect_circuit_models_.end()) { + /* Return an invalid circuit model id */ + return CircuitModelId::INVALID(); + } + return interconnect_circuit_models_.at(pb_interconnect); +} + /************************************************************************ * Public mutators ***********************************************************************/ @@ -136,4 +146,15 @@ void VprPbTypeAnnotation::add_pb_type_circuit_model(t_pb_type* physical_pb_type, pb_type_circuit_models_[physical_pb_type] = circuit_model; } +void VprPbTypeAnnotation::add_interconnect_circuit_model(t_interconnect* pb_interconnect, const CircuitModelId& circuit_model) { + /* Warn any override attempt */ + std::map::const_iterator it = interconnect_circuit_models_.find(pb_interconnect); + if (it != interconnect_circuit_models_.end()) { + VTR_LOG_WARN("Override the circuit model for interconnect '%s'!\n", + pb_interconnect->name); + } + + interconnect_circuit_models_[pb_interconnect] = circuit_model; +} + } /* End namespace openfpga*/ diff --git a/openfpga/src/base/vpr_pb_type_annotation.h b/openfpga/src/base/vpr_pb_type_annotation.h index e235538e7..7b19c3a29 100644 --- a/openfpga/src/base/vpr_pb_type_annotation.h +++ b/openfpga/src/base/vpr_pb_type_annotation.h @@ -35,12 +35,14 @@ class VprPbTypeAnnotation { t_port* physical_pb_port(t_port* pb_port) const; BasicPort physical_pb_port_range(t_port* pb_port) const; CircuitModelId pb_type_circuit_model(t_pb_type* physical_pb_type) const; + CircuitModelId interconnect_circuit_model(t_interconnect* pb_interconnect) const; public: /* Public mutators */ void add_pb_type_physical_mode(t_pb_type* pb_type, t_mode* physical_mode); void add_physical_pb_type(t_pb_type* operating_pb_type, t_pb_type* physical_pb_type); void add_physical_pb_port(t_port* operating_pb_port, t_port* physical_pb_port); void add_physical_pb_port_range(t_port* operating_pb_port, const BasicPort& port_range); void add_pb_type_circuit_model(t_pb_type* physical_pb_type, const CircuitModelId& circuit_model); + void add_interconnect_circuit_model(t_interconnect* pb_interconnect, const CircuitModelId& circuit_model); private: /* Internal data */ /* Pair a regular pb_type to its physical pb_type */ std::map physical_pb_types_; @@ -58,6 +60,12 @@ class VprPbTypeAnnotation { */ std::map pb_type_circuit_models_; + /* Pair a interconnect of a physical pb_type to its circuit model + * Note: + * - the pb_type MUST be a physical pb_type itself + */ + std::map interconnect_circuit_models_; + /* Pair a pb_type to its mode selection bits * - if the pb_type is a physical pb_type, the mode bits are the default mode * where the physical pb_type will operate when used diff --git a/openfpga/test_script/s298.openfpga b/openfpga/test_script/s298.openfpga index ad12f4807..6d4eaa434 100644 --- a/openfpga/test_script/s298.openfpga +++ b/openfpga/test_script/s298.openfpga @@ -6,3 +6,6 @@ read_openfpga_arch -f ./test_openfpga_arch/k6_N10_40nm_openfpga.xml # Annotate the OpenFPGA architecture to VPR data base link_openfpga_arch + +# Finish and exit OpenFPGA +exit From 8a7a4dc48e20482afaa4052d3be45dab092a01fc Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 28 Jan 2020 21:59:10 -0700 Subject: [PATCH 038/645] add physical type annotation for interconnects and inference --- libs/libarchfpga/src/physical_types.h | 3 +- openfpga/src/base/annotate_pb_graph.cpp | 122 +++++++++++++++++++ openfpga/src/base/annotate_pb_graph.h | 23 ++++ openfpga/src/base/annotate_pb_types.cpp | 97 +++++++++++++++ openfpga/src/base/openfpga_link_arch.cpp | 1 + openfpga/src/base/pb_graph_utils.cpp | 41 +++++++ openfpga/src/base/pb_graph_utils.h | 23 ++++ openfpga/src/base/pb_type_utils.cpp | 78 +++++++++++- openfpga/src/base/pb_type_utils.h | 8 ++ openfpga/src/base/vpr_pb_type_annotation.cpp | 22 ++++ openfpga/src/base/vpr_pb_type_annotation.h | 8 ++ 11 files changed, 423 insertions(+), 3 deletions(-) create mode 100644 openfpga/src/base/annotate_pb_graph.cpp create mode 100644 openfpga/src/base/annotate_pb_graph.h create mode 100644 openfpga/src/base/pb_graph_utils.cpp create mode 100644 openfpga/src/base/pb_graph_utils.h diff --git a/libs/libarchfpga/src/physical_types.h b/libs/libarchfpga/src/physical_types.h index 36901e6f0..503427303 100644 --- a/libs/libarchfpga/src/physical_types.h +++ b/libs/libarchfpga/src/physical_types.h @@ -162,7 +162,8 @@ enum e_pin_type { enum e_interconnect { COMPLETE_INTERC = 1, DIRECT_INTERC = 2, - MUX_INTERC = 3 + MUX_INTERC = 3, + NUM_INTERC_TYPES }; /* Orientations. */ diff --git a/openfpga/src/base/annotate_pb_graph.cpp b/openfpga/src/base/annotate_pb_graph.cpp new file mode 100644 index 000000000..82e214067 --- /dev/null +++ b/openfpga/src/base/annotate_pb_graph.cpp @@ -0,0 +1,122 @@ +/******************************************************************** + * This file includes functions that are used to annotate pb_graph_node + * and pb_graph_pins from VPR to OpenFPGA + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" + +#include "pb_type_utils.h" +#include "pb_graph_utils.h" + +#include "annotate_pb_graph.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * This function will recursively walk through all the pb_graph nodes + * starting from a top node. + * It aims to annotate the physical type of each interconnect. + * This is due to that the type of interconnect 'complete' may diverge + * in physical implmentation. + * - When there is only one input driving a 'complete' interconnection, + * it will be implemented with wires + * - When there are multiple inputs driving a 'complete' interconnection, + * it will be implemented with routing multiplexers + *******************************************************************/ +static +void rec_build_vpr_pb_graph_interconnect_physical_type_annotation(t_pb_graph_node* pb_graph_node, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + /* Skip the root node because we start from the inputs of child pb_graph node + * + * pb_graph_node + * +---------------------------------- + * | child_pb_graph_node + * | +----------------- + * | | + * |-------------+-->input_pins + * | | + * |-------------+-->clock_pins + * | + * + */ + if (false == pb_graph_node->is_root()) { + /* We only care the physical modes! But we have to find it through the parent node */ + t_mode* child_physical_mode = vpr_pb_type_annotation.physical_mode(pb_graph_node->parent_pb_graph_node->pb_type); + VTR_ASSERT(nullptr != child_physical_mode); + + std::map interc_num_inputs; + /* Initialize the counter */ + for (t_interconnect* interc : pb_mode_interconnects(child_physical_mode)) { + interc_num_inputs[interc] = 0; + } + + /* We only care input and clock pins */ + for (int iport = 0; iport < pb_graph_node->num_input_ports; ++iport) { + for (int ipin = 0; ipin < pb_graph_node->num_input_pins[iport]; ++ipin) { + /* For each interconnect, we count the total number of inputs */ + for (t_interconnect* interc : pb_mode_interconnects(child_physical_mode)) { + interc_num_inputs[interc] += pb_graph_pin_inputs(&(pb_graph_node->input_pins[iport][ipin]), interc).size(); + } + } + } + + for (int iport = 0; iport < pb_graph_node->num_clock_ports; ++iport) { + for (int ipin = 0; ipin < pb_graph_node->num_clock_pins[iport]; ++ipin) { + /* For each interconnect, we count the total number of inputs */ + for (t_interconnect* interc : pb_mode_interconnects(child_physical_mode)) { + interc_num_inputs[interc] += pb_graph_pin_inputs(&(pb_graph_node->clock_pins[iport][ipin]), interc).size(); + } + } + } + + /* For each interconnect that has more than 1 input, we can infer the physical type */ + for (t_interconnect* interc : pb_mode_interconnects(child_physical_mode)) { + e_interconnect interc_physical_type = pb_interconnect_physical_type(interc, interc_num_inputs[interc]); + if (interc_physical_type == vpr_pb_type_annotation.interconnect_physical_type(interc)) { + /* Skip annotation if we have already done! */ + continue; + } + vpr_pb_type_annotation.add_interconnect_physical_type(interc, interc_physical_type); + } + } + + /* If we reach a primitive pb_graph node, we return */ + if (true == is_primitive_pb_type(pb_graph_node->pb_type)) { + return; + } + + /* Recursively visit all the child pb_graph_nodes */ + t_mode* physical_mode = vpr_pb_type_annotation.physical_mode(pb_graph_node->pb_type); + VTR_ASSERT(nullptr != physical_mode); + for (int ipb = 0; ipb < physical_mode->num_pb_type_children; ++ipb) { + /* Each child may exist multiple times in the hierarchy*/ + for (int jpb = 0; jpb < physical_mode->pb_type_children[ipb].num_pb; ++jpb) { + rec_build_vpr_pb_graph_interconnect_physical_type_annotation(&(pb_graph_node->child_pb_graph_nodes[physical_mode->index][ipb][jpb]), vpr_pb_type_annotation); + } + } +} + +/******************************************************************** + * This function aims to annotate the physical type for each interconnect + * inside the pb_graph + * + * Note: + * - This function should be executed AFTER functions + * build_vpr_physical_pb_mode_explicit_annotation() + * build_vpr_physical_pb_mode_implicit_annotation() + *******************************************************************/ +void annotate_pb_graph_interconnect_physical_type(const DeviceContext& vpr_device_ctx, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { + /* By pass nullptr for pb_graph head */ + if (nullptr == lb_type.pb_graph_head) { + continue; + } + rec_build_vpr_pb_graph_interconnect_physical_type_annotation(lb_type.pb_graph_head, vpr_pb_type_annotation); + } +} + +} /* end namespace openfpga */ + diff --git a/openfpga/src/base/annotate_pb_graph.h b/openfpga/src/base/annotate_pb_graph.h new file mode 100644 index 000000000..8749cd28f --- /dev/null +++ b/openfpga/src/base/annotate_pb_graph.h @@ -0,0 +1,23 @@ +#ifndef ANNOTATE_PB_GRAPH_H +#define ANNOTATE_PB_GRAPH_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "vpr_context.h" +#include "openfpga_context.h" +#include "vpr_pb_type_annotation.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void annotate_pb_graph_interconnect_physical_type(const DeviceContext& vpr_device_ctx, + VprPbTypeAnnotation& vpr_pb_type_annotation); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/base/annotate_pb_types.cpp b/openfpga/src/base/annotate_pb_types.cpp index e8e7ddab4..c4605325c 100644 --- a/openfpga/src/base/annotate_pb_types.cpp +++ b/openfpga/src/base/annotate_pb_types.cpp @@ -9,6 +9,7 @@ #include "vpr_pb_type_annotation.h" #include "pb_type_utils.h" +#include "annotate_pb_graph.h" #include "annotate_pb_types.h" /* begin namespace openfpga */ @@ -843,6 +844,92 @@ void link_vpr_pb_interconnect_to_circuit_model_explicit_annotation(const DeviceC } } +/******************************************************************** + * This function will recursively visit all the pb_type from the top + * pb_type in the graph and infer the circuit model for physical mode + * of pb_types in VPR pb_type graph without OpenFPGA architecture XML + * + * Because only the interconnect in physical modes need circuit model + * annotation, we will skip all the operating modes here + * + * Note: + * - This function will automatically infer the type of circuit model + * that is required and find the default circuit model in the type + * - Interconnect type to Circuit mode type assumption: + * - MUX_INTERC -> CIRCUIT_MODEL_MUX + * - DIRECT_INTERC -> CIRCUIT_MODEL_WIRE + * - COMPLETE_INTERC (single input) -> CIRCUIT_MODEL_WIRE + * - COMPLETE_INTERC (multiple input pins) -> CIRCUIT_MODEL_MUX + *******************************************************************/ +static +void rec_infer_vpr_pb_interconnect_circuit_model_annotation(t_pb_type* cur_pb_type, + const CircuitLibrary& circuit_lib, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + /* We do not check any primitive pb_type */ + if (true == is_primitive_pb_type(cur_pb_type)) { + return; + } + + /* Get the physical mode from annotation */ + t_mode* physical_mode = vpr_pb_type_annotation.physical_mode(cur_pb_type); + + VTR_ASSERT(nullptr != physical_mode); + + /* Annotate the circuit model for each interconnect under this physical mode */ + for (t_interconnect* pb_interc : pb_mode_interconnects(physical_mode)) { + /* If the interconnect has been annotated, we skip it */ + if (CircuitModelId::INVALID() != vpr_pb_type_annotation.interconnect_circuit_model(pb_interc)) { + continue; + } + /* Infer the circuit model type for a given interconnect */ + e_circuit_model_type circuit_model_type = pb_interconnect_require_circuit_model_type(vpr_pb_type_annotation.interconnect_physical_type(pb_interc)); + /* Try to find a default circuit model from the circuit library */ + CircuitModelId default_circuit_model = circuit_lib.default_model(circuit_model_type); + /* Update the annotation if the model id is valid */ + if (CircuitModelId::INVALID() == default_circuit_model) { + VTR_LOG_ERROR("Unable to infer a circuit model for interconnect '%s' under physical mode '%s' of pb_type '%s'!\n", + pb_interc->name, + physical_mode->name, + cur_pb_type->name); + } + vpr_pb_type_annotation.add_interconnect_circuit_model(pb_interc, default_circuit_model); + VTR_LOG("Implicitly infer a circuit model '%s' for interconnect '%s' under physical mode '%s' of pb_type '%s'\n", + circuit_lib.model_name(default_circuit_model).c_str(), + pb_interc->name, + physical_mode->name, + cur_pb_type->name); + } + + /* Traverse the pb_type children under the physical mode */ + for (int ichild = 0; ichild < physical_mode->num_pb_type_children; ++ichild) { + rec_infer_vpr_pb_interconnect_circuit_model_annotation(&(physical_mode->pb_type_children[ichild]), + circuit_lib, vpr_pb_type_annotation); + } +} + +/******************************************************************** + * This function will infer the circuit model for each interconnect + * under a physical mode of a pb_type in VPR pb_type graph without + * OpenFPGA architecture XML + * + * Note: + * This function must be executed AFTER the function + * build_vpr_physical_pb_mode_explicit_annotation() + * build_vpr_physical_pb_mode_implicit_annotation() + *******************************************************************/ +static +void link_vpr_pb_interconnect_to_circuit_model_implicit_annotation(const DeviceContext& vpr_device_ctx, + const CircuitLibrary& circuit_lib, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { + /* By pass nullptr for pb_type head */ + if (nullptr == lb_type.pb_type) { + continue; + } + rec_infer_vpr_pb_interconnect_circuit_model_annotation(lb_type.pb_type, circuit_lib, vpr_pb_type_annotation); + } +} + /******************************************************************** * Top-level function to link openfpga architecture to VPR, including: * - physical pb_type @@ -862,6 +949,13 @@ void annotate_pb_types(const DeviceContext& vpr_device_ctx, check_vpr_physical_pb_mode_annotation(vpr_device_ctx, const_cast(vpr_pb_type_annotation)); + /* Annotate the physical type for each interconnect under physical modes + * Must run AFTER physical mode annotation is done and + * BEFORE inferring the circuit model for interconnect + */ + annotate_pb_graph_interconnect_physical_type(vpr_device_ctx, + vpr_pb_type_annotation); + /* Annotate physical pb_types to operating pb_type in the VPR pb_type graph */ build_vpr_physical_pb_type_explicit_annotation(vpr_device_ctx, openfpga_arch, vpr_pb_type_annotation); @@ -876,10 +970,13 @@ void annotate_pb_types(const DeviceContext& vpr_device_ctx, * - physical pb_type to circuit model * - interconnect of physical pb_type to circuit model */ + /* TODO: link the pb_type port to circuit model port here! */ link_vpr_pb_type_to_circuit_model_explicit_annotation(vpr_device_ctx, openfpga_arch, vpr_pb_type_annotation); link_vpr_pb_interconnect_to_circuit_model_explicit_annotation(vpr_device_ctx, openfpga_arch, vpr_pb_type_annotation); + link_vpr_pb_interconnect_to_circuit_model_implicit_annotation(vpr_device_ctx, openfpga_arch.circuit_lib, + vpr_pb_type_annotation); /* Link physical pb_type to mode_bits */ diff --git a/openfpga/src/base/openfpga_link_arch.cpp b/openfpga/src/base/openfpga_link_arch.cpp index d10c5d931..a30453d1c 100644 --- a/openfpga/src/base/openfpga_link_arch.cpp +++ b/openfpga/src/base/openfpga_link_arch.cpp @@ -10,6 +10,7 @@ #include "vpr_pb_type_annotation.h" #include "pb_type_utils.h" #include "annotate_pb_types.h" +#include "annotate_pb_graph.h" #include "openfpga_link_arch.h" /* Include global variables of VPR */ diff --git a/openfpga/src/base/pb_graph_utils.cpp b/openfpga/src/base/pb_graph_utils.cpp new file mode 100644 index 000000000..84e642bde --- /dev/null +++ b/openfpga/src/base/pb_graph_utils.cpp @@ -0,0 +1,41 @@ +/******************************************************************** + * This file includes most utilized functions for the pb_graph_node + * and pb_graph_pin data structure in the OpenFPGA context + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" + +#include "pb_graph_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * This function aims to find out all the pb_graph_pins that drive + * a given pb_graph pin w.r.t. a given interconnect definition + *******************************************************************/ +std::vector pb_graph_pin_inputs(t_pb_graph_pin* pb_graph_pin, + t_interconnect* selected_interconnect) { + std::vector inputs; + + /* Search the input edges only, stats on the size of MUX we may need (fan-in) */ + for (int iedge = 0; iedge < pb_graph_pin->num_input_edges; ++iedge) { + /* We care the only edges in the selected mode */ + if (selected_interconnect != pb_graph_pin->input_edges[iedge]->interconnect) { + continue; + } + for (int ipin = 0; ipin < pb_graph_pin->input_edges[iedge]->num_input_pins; ++ipin) { + /* Ensure that the pin is unique in the list */ + if (inputs.end() != std::find(inputs.begin(), inputs.end(), pb_graph_pin->input_edges[iedge]->input_pins[ipin])) { + continue; + } + /* Unique pin, push to the vector */ + inputs.push_back(pb_graph_pin->input_edges[iedge]->input_pins[ipin]); + } + } + + return inputs; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/base/pb_graph_utils.h b/openfpga/src/base/pb_graph_utils.h new file mode 100644 index 000000000..d46457b7d --- /dev/null +++ b/openfpga/src/base/pb_graph_utils.h @@ -0,0 +1,23 @@ +#ifndef PB_GRAPH_UTILS_H +#define PB_GRAPH_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "physical_types.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +std::vector pb_graph_pin_inputs(t_pb_graph_pin* pb_graph_pin, + t_interconnect* selected_interconnect); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/base/pb_type_utils.cpp b/openfpga/src/base/pb_type_utils.cpp index 93b644b8c..090661b50 100644 --- a/openfpga/src/base/pb_type_utils.cpp +++ b/openfpga/src/base/pb_type_utils.cpp @@ -1,7 +1,9 @@ /******************************************************************** - * This file includes most utilized functions for the pb_type - * and pb_graph_node data structure in the OpenFPGA context + * This file includes most utilized functions for the t_pb_type, + * t_mode and t_port data structure in the OpenFPGA context *******************************************************************/ +#include + /* Headers from vtrutil library */ #include "vtr_assert.h" #include "vtr_log.h" @@ -138,6 +140,18 @@ t_pb_type* try_find_pb_type_with_given_path(t_pb_type* top_pb_type, return nullptr; } +/******************************************************************** + * This function will return all the interconnects defined under a mode + * of pb_type + *******************************************************************/ +std::vector pb_mode_interconnects(t_mode* pb_mode) { + std::vector interc; + for (int i = 0; i < pb_mode->num_interconnect; ++i) { + interc.push_back(&(pb_mode->interconnect[i])); + } + return interc; +} + /******************************************************************** * This function will try to find an interconnect defined under a mode * of pb_type with a given name. @@ -154,4 +168,64 @@ t_interconnect* find_pb_mode_interconnect(t_mode* pb_mode, const char* interc_na return nullptr; } +/******************************************************************** + * This function will automatically infer the actual type of an interconnect + * that will be used to implement the physical design: + * - MUX_INTERC -> MUX_INTERC + * - DIRECT_INTERC -> DIRECT_INTERC + * - COMPLETE_INTERC (single input) -> DIRECT_INTERC + * - COMPLETE_INTERC (multiple input pins) -> MUX_INTERC + *******************************************************************/ +e_interconnect pb_interconnect_physical_type(t_interconnect* pb_interc, + const size_t& num_inputs) { + /* Check */ + VTR_ASSERT(nullptr != pb_interc); + + /* Initialize the interconnection type that will be implemented in SPICE netlist*/ + switch (pb_interc->type) { + case DIRECT_INTERC: + return DIRECT_INTERC; + break; + case COMPLETE_INTERC: + if (1 == num_inputs) { + return DIRECT_INTERC; + } else { + VTR_ASSERT(1 < num_inputs); + return MUX_INTERC; + } + break; + case MUX_INTERC: + return MUX_INTERC; + break; + default: + VTR_LOG_ERROR("Invalid type for interconnection '%s'!\n", + pb_interc->name); + exit(1); + } + + return NUM_INTERC_TYPES; +} + +/******************************************************************** + * This function will automatically infer the actual type of an interconnect + * that will be used to implement the physical design: + * - MUX_INTERC -> CIRCUIT_MODEL_MUX + * - DIRECT_INTERC -> CIRCUIT_MODEL_WIRE + * + * Note: + * - COMPLETE_INTERC should not appear here! + * - We assume the interconnect type is the physical type + * after interconnect physical type annotation is done! + *******************************************************************/ +e_circuit_model_type pb_interconnect_require_circuit_model_type(const e_interconnect& pb_interc_type) { + /* A map from interconnect type to circuit model type */ + std::map type_mapping; + type_mapping[MUX_INTERC] = CIRCUIT_MODEL_MUX; + type_mapping[DIRECT_INTERC] = CIRCUIT_MODEL_WIRE; + + VTR_ASSERT((MUX_INTERC == pb_interc_type) || (DIRECT_INTERC == pb_interc_type)); + + return type_mapping.at(pb_interc_type); +} + } /* end namespace openfpga */ diff --git a/openfpga/src/base/pb_type_utils.h b/openfpga/src/base/pb_type_utils.h index 3e9f9a07f..ac3ade343 100644 --- a/openfpga/src/base/pb_type_utils.h +++ b/openfpga/src/base/pb_type_utils.h @@ -7,6 +7,7 @@ #include #include #include "physical_types.h" +#include "circuit_library.h" /******************************************************************** * Function declaration @@ -31,8 +32,15 @@ t_pb_type* try_find_pb_type_with_given_path(t_pb_type* top_pb_type, const std::vector& target_pb_type_names, const std::vector& target_pb_mode_names); +std::vector pb_mode_interconnects(t_mode* pb_mode); + t_interconnect* find_pb_mode_interconnect(t_mode* pb_mode, const char* interc_name); +e_interconnect pb_interconnect_physical_type(t_interconnect* pb_interc, + const size_t& num_inputs); + +e_circuit_model_type pb_interconnect_require_circuit_model_type(const e_interconnect& pb_interc_type); + } /* end namespace openfpga */ #endif diff --git a/openfpga/src/base/vpr_pb_type_annotation.cpp b/openfpga/src/base/vpr_pb_type_annotation.cpp index 8949d13c3..97dc7354b 100644 --- a/openfpga/src/base/vpr_pb_type_annotation.cpp +++ b/openfpga/src/base/vpr_pb_type_annotation.cpp @@ -85,6 +85,16 @@ CircuitModelId VprPbTypeAnnotation::interconnect_circuit_model(t_interconnect* p return interconnect_circuit_models_.at(pb_interconnect); } +e_interconnect VprPbTypeAnnotation::interconnect_physical_type(t_interconnect* pb_interconnect) const { + /* Ensure that the pb_type is in the list */ + std::map::const_iterator it = interconnect_physical_types_.find(pb_interconnect); + if (it == interconnect_physical_types_.end()) { + /* Return an invalid circuit model id */ + return NUM_INTERC_TYPES; + } + return interconnect_physical_types_.at(pb_interconnect); +} + /************************************************************************ * Public mutators ***********************************************************************/ @@ -157,4 +167,16 @@ void VprPbTypeAnnotation::add_interconnect_circuit_model(t_interconnect* pb_inte interconnect_circuit_models_[pb_interconnect] = circuit_model; } +void VprPbTypeAnnotation::add_interconnect_physical_type(t_interconnect* pb_interconnect, + const e_interconnect& physical_type) { + /* Warn any override attempt */ + std::map::const_iterator it = interconnect_physical_types_.find(pb_interconnect); + if (it != interconnect_physical_types_.end()) { + VTR_LOG_WARN("Override the physical interconnect for interconnect '%s'!\n", + pb_interconnect->name); + } + + interconnect_physical_types_[pb_interconnect] = physical_type; +} + } /* End namespace openfpga*/ diff --git a/openfpga/src/base/vpr_pb_type_annotation.h b/openfpga/src/base/vpr_pb_type_annotation.h index 7b19c3a29..824a81e87 100644 --- a/openfpga/src/base/vpr_pb_type_annotation.h +++ b/openfpga/src/base/vpr_pb_type_annotation.h @@ -36,6 +36,7 @@ class VprPbTypeAnnotation { BasicPort physical_pb_port_range(t_port* pb_port) const; CircuitModelId pb_type_circuit_model(t_pb_type* physical_pb_type) const; CircuitModelId interconnect_circuit_model(t_interconnect* pb_interconnect) const; + e_interconnect interconnect_physical_type(t_interconnect* pb_interconnect) const; public: /* Public mutators */ void add_pb_type_physical_mode(t_pb_type* pb_type, t_mode* physical_mode); void add_physical_pb_type(t_pb_type* operating_pb_type, t_pb_type* physical_pb_type); @@ -43,6 +44,7 @@ class VprPbTypeAnnotation { void add_physical_pb_port_range(t_port* operating_pb_port, const BasicPort& port_range); void add_pb_type_circuit_model(t_pb_type* physical_pb_type, const CircuitModelId& circuit_model); void add_interconnect_circuit_model(t_interconnect* pb_interconnect, const CircuitModelId& circuit_model); + void add_interconnect_physical_type(t_interconnect* pb_interconnect, const e_interconnect& physical_type); private: /* Internal data */ /* Pair a regular pb_type to its physical pb_type */ std::map physical_pb_types_; @@ -66,6 +68,12 @@ class VprPbTypeAnnotation { */ std::map interconnect_circuit_models_; + /* Physical type of interconnect + * Note: + * - only applicable to an interconnect belongs to physical mode + */ + std::map interconnect_physical_types_; + /* Pair a pb_type to its mode selection bits * - if the pb_type is a physical pb_type, the mode bits are the default mode * where the physical pb_type will operate when used From cf3c5b5c42e9abbff21eeb4c0850a69b7f2f56c9 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 29 Jan 2020 10:41:02 -0700 Subject: [PATCH 039/645] add circuit model type checking for physical pb_type annotation --- openfpga/src/base/annotate_pb_types.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/openfpga/src/base/annotate_pb_types.cpp b/openfpga/src/base/annotate_pb_types.cpp index c4605325c..dc1618d6e 100644 --- a/openfpga/src/base/annotate_pb_types.cpp +++ b/openfpga/src/base/annotate_pb_types.cpp @@ -613,6 +613,8 @@ bool link_physical_pb_type_to_circuit_model(t_pb_type* physical_pb_type, return false; } + /* TODO: Ensure that the pb_type ports can be matched in the circuit model ports */ + /* Now the circuit model is valid, update the vpr_pb_type_annotation */ vpr_pb_type_annotation.add_pb_type_circuit_model(physical_pb_type, circuit_model_id); return true; @@ -663,6 +665,19 @@ bool link_physical_pb_interconnect_to_circuit_model(t_pb_type* physical_pb_type, physical_pb_type->name); return false; } + + /* Double check the type of circuit model, it should be the same as required physical type */ + e_circuit_model_type required_circuit_model_type = pb_interconnect_require_circuit_model_type(vpr_pb_type_annotation.interconnect_physical_type(pb_interc)); + if (circuit_lib.model_type(circuit_model_id) != required_circuit_model_type) { + VTR_LOG_ERROR("Circuit model '%s' type '%s' does not match required type '%s' for interconnect '%s' under physical mode '%s' of pb_type '%s'!\n", + circuit_lib.model_name(circuit_model_id).c_str(), + CIRCUIT_MODEL_TYPE_STRING[circuit_lib.model_type(circuit_model_id)], + CIRCUIT_MODEL_TYPE_STRING[required_circuit_model_type], + pb_interc->name, + physical_mode->name, + physical_pb_type->name); + return false; + } /* Now the circuit model is valid, update the vpr_pb_type_annotation */ vpr_pb_type_annotation.add_interconnect_circuit_model(pb_interc, circuit_model_id); @@ -977,6 +992,7 @@ void annotate_pb_types(const DeviceContext& vpr_device_ctx, vpr_pb_type_annotation); link_vpr_pb_interconnect_to_circuit_model_implicit_annotation(vpr_device_ctx, openfpga_arch.circuit_lib, vpr_pb_type_annotation); + /* TODO: check the circuit model annotation */ /* Link physical pb_type to mode_bits */ From 399ba8d6481f11a9ad6b4441545e1a6fc2633024 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 29 Jan 2020 11:24:14 -0700 Subject: [PATCH 040/645] add pb type port mapping to circuit model ports --- openfpga/src/base/annotate_pb_types.cpp | 68 +++++++++++++++++++- openfpga/src/base/pb_type_utils.cpp | 26 ++++++++ openfpga/src/base/pb_type_utils.h | 2 + openfpga/src/base/vpr_pb_type_annotation.cpp | 24 ++++++- openfpga/src/base/vpr_pb_type_annotation.h | 8 +++ 5 files changed, 126 insertions(+), 2 deletions(-) diff --git a/openfpga/src/base/annotate_pb_types.cpp b/openfpga/src/base/annotate_pb_types.cpp index dc1618d6e..783447cfb 100644 --- a/openfpga/src/base/annotate_pb_types.cpp +++ b/openfpga/src/base/annotate_pb_types.cpp @@ -584,6 +584,69 @@ void check_vpr_physical_pb_type_annotation(const DeviceContext& vpr_device_ctx, } } +/******************************************************************** + * This function aims to link all the ports defined under a physical + * pb_type to the ports of circuit model in the circuit library + * The binding assumes that pb_type port should be defined with the same name + * as the circuit port + *******************************************************************/ +static +bool link_physical_pb_port_to_circuit_port(t_pb_type* physical_pb_type, + const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + bool link_success = true; + /* Iterate over the pb_ports + * Note: + * - Not every port defined in the circuit model will appear under the pb_type + * Some circuit ports are just for physical implementation + */ + for (t_port* pb_port : pb_type_ports(physical_pb_type)) { + CircuitPortId circuit_port = circuit_lib.model_port(circuit_model, std::string(pb_port->name)); + + /* If we cannot find a port with the same name, error out */ + if (CircuitPortId::INVALID() == circuit_port) { + VTR_LOG_ERROR("Pb type port '%s' is not found in any port of circuit model '%s'!\n", + pb_port->name, circuit_lib.model_name(circuit_model).c_str()); + link_success = false; + continue; + } + + /* If the port width does not match, error out */ + if ((size_t)(pb_port->num_pins) != circuit_lib.port_size(circuit_port)) { + VTR_LOG_ERROR("Pb type port '%s[%d:%d]' does not match the port '%s[%d:%d]' of circuit model '%s' in size!\n", + pb_port->name, + 0, pb_port->num_pins - 1, + circuit_lib.port_prefix(circuit_port).c_str(), + 0, circuit_lib.port_size(circuit_port) - 1, + circuit_lib.model_name(circuit_model).c_str()); + link_success = false; + continue; + } + + /* If the port type does not match, error out */ + if (ERR_PORT == circuit_port_require_pb_port_type(circuit_lib.port_type(circuit_port))) { + VTR_LOG_ERROR("Pb type port '%s' type does not match the port type '%s' of circuit model '%s'!\n", + pb_port->name, + circuit_lib.port_prefix(circuit_port).c_str(), + circuit_lib.model_name(circuit_model).c_str()); + + link_success = false; + continue; + } + + /* Reach here, it means that mapping should be ok, update the vpr_pb_type_annotation */ + vpr_pb_type_annotation.add_pb_circuit_port(pb_port, circuit_port); + VTR_LOG("Bind pb type '%s' port '%s' to circuit model '%s' port '%s'\n", + physical_pb_type->name, + pb_port->name, + circuit_lib.model_name(circuit_model).c_str(), + circuit_lib.port_prefix(circuit_port).c_str()); + } + + return link_success; +} + /******************************************************************** * This function aims to link a physical pb_type to a valid circuit model * in the circuit library @@ -613,7 +676,10 @@ bool link_physical_pb_type_to_circuit_model(t_pb_type* physical_pb_type, return false; } - /* TODO: Ensure that the pb_type ports can be matched in the circuit model ports */ + /* Ensure that the pb_type ports can be matched in the circuit model ports */ + if (false == link_physical_pb_port_to_circuit_port(physical_pb_type, circuit_lib, circuit_model_id, vpr_pb_type_annotation)) { + return false; + } /* Now the circuit model is valid, update the vpr_pb_type_annotation */ vpr_pb_type_annotation.add_pb_type_circuit_model(physical_pb_type, circuit_model_id); diff --git a/openfpga/src/base/pb_type_utils.cpp b/openfpga/src/base/pb_type_utils.cpp index 090661b50..b791485a2 100644 --- a/openfpga/src/base/pb_type_utils.cpp +++ b/openfpga/src/base/pb_type_utils.cpp @@ -228,4 +228,30 @@ e_circuit_model_type pb_interconnect_require_circuit_model_type(const e_intercon return type_mapping.at(pb_interc_type); } +/******************************************************************** + * This function aims to find the required type for a pb_port + * when it is linked to a port of a circuit model + * We start from the view of a circuit port here. + * This is due to circuit port has more types than a pb_type port + * as it is designed for physical implementation + * As such, a few types of circuit port may be directed the same type of pb_type port + * This is done to make the mapping much simpler than doing in the opposite direction + *******************************************************************/ +enum PORTS circuit_port_require_pb_port_type(const e_circuit_model_port_type& circuit_port_type) { + std::map type_mapping; + + /* These circuit model ports may be founed in the pb_type ports */ + type_mapping[CIRCUIT_MODEL_PORT_INPUT] = IN_PORT; + type_mapping[CIRCUIT_MODEL_PORT_OUTPUT] = OUT_PORT; + type_mapping[CIRCUIT_MODEL_PORT_INOUT] = INOUT_PORT; + type_mapping[CIRCUIT_MODEL_PORT_CLOCK] = IN_PORT; + + /* Other circuit model ports should not be found when mapping to the pb_type ports */ + if (type_mapping.end() == type_mapping.find(circuit_port_type)) { + return ERR_PORT; + } + + return type_mapping.at(circuit_port_type); +} + } /* end namespace openfpga */ diff --git a/openfpga/src/base/pb_type_utils.h b/openfpga/src/base/pb_type_utils.h index ac3ade343..17ec7f78e 100644 --- a/openfpga/src/base/pb_type_utils.h +++ b/openfpga/src/base/pb_type_utils.h @@ -41,6 +41,8 @@ e_interconnect pb_interconnect_physical_type(t_interconnect* pb_interc, e_circuit_model_type pb_interconnect_require_circuit_model_type(const e_interconnect& pb_interc_type); +enum PORTS circuit_port_require_pb_port_type(const e_circuit_model_port_type& circuit_port_type); + } /* end namespace openfpga */ #endif diff --git a/openfpga/src/base/vpr_pb_type_annotation.cpp b/openfpga/src/base/vpr_pb_type_annotation.cpp index 97dc7354b..867f40794 100644 --- a/openfpga/src/base/vpr_pb_type_annotation.cpp +++ b/openfpga/src/base/vpr_pb_type_annotation.cpp @@ -89,12 +89,23 @@ e_interconnect VprPbTypeAnnotation::interconnect_physical_type(t_interconnect* p /* Ensure that the pb_type is in the list */ std::map::const_iterator it = interconnect_physical_types_.find(pb_interconnect); if (it == interconnect_physical_types_.end()) { - /* Return an invalid circuit model id */ + /* Return an invalid interconnect type */ return NUM_INTERC_TYPES; } return interconnect_physical_types_.at(pb_interconnect); } +CircuitPortId VprPbTypeAnnotation::pb_circuit_port(t_port* pb_port) const { + /* Ensure that the pb_type is in the list */ + std::map::const_iterator it = pb_circuit_ports_.find(pb_port); + if (it == pb_circuit_ports_.end()) { + /* Return an invalid circuit port id */ + return CircuitPortId::INVALID(); + } + return pb_circuit_ports_.at(pb_port); +} + + /************************************************************************ * Public mutators ***********************************************************************/ @@ -179,4 +190,15 @@ void VprPbTypeAnnotation::add_interconnect_physical_type(t_interconnect* pb_inte interconnect_physical_types_[pb_interconnect] = physical_type; } +void VprPbTypeAnnotation::add_pb_circuit_port(t_port* pb_port, const CircuitPortId& circuit_port) { + /* Warn any override attempt */ + std::map::const_iterator it = pb_circuit_ports_.find(pb_port); + if (it != pb_circuit_ports_.end()) { + VTR_LOG_WARN("Override the circuit port mapping for pb_type port '%s'!\n", + pb_port->name); + } + + pb_circuit_ports_[pb_port] = circuit_port; +} + } /* End namespace openfpga*/ diff --git a/openfpga/src/base/vpr_pb_type_annotation.h b/openfpga/src/base/vpr_pb_type_annotation.h index 824a81e87..fb0fec934 100644 --- a/openfpga/src/base/vpr_pb_type_annotation.h +++ b/openfpga/src/base/vpr_pb_type_annotation.h @@ -37,6 +37,7 @@ class VprPbTypeAnnotation { CircuitModelId pb_type_circuit_model(t_pb_type* physical_pb_type) const; CircuitModelId interconnect_circuit_model(t_interconnect* pb_interconnect) const; e_interconnect interconnect_physical_type(t_interconnect* pb_interconnect) const; + CircuitPortId pb_circuit_port(t_port* pb_port) const; public: /* Public mutators */ void add_pb_type_physical_mode(t_pb_type* pb_type, t_mode* physical_mode); void add_physical_pb_type(t_pb_type* operating_pb_type, t_pb_type* physical_pb_type); @@ -45,6 +46,7 @@ class VprPbTypeAnnotation { void add_pb_type_circuit_model(t_pb_type* physical_pb_type, const CircuitModelId& circuit_model); void add_interconnect_circuit_model(t_interconnect* pb_interconnect, const CircuitModelId& circuit_model); void add_interconnect_physical_type(t_interconnect* pb_interconnect, const e_interconnect& physical_type); + void add_pb_circuit_port(t_port* pb_port, const CircuitPortId& circuit_port); private: /* Internal data */ /* Pair a regular pb_type to its physical pb_type */ std::map physical_pb_types_; @@ -94,6 +96,12 @@ class VprPbTypeAnnotation { */ std::map physical_pb_port_ranges_; + /* Pair a pb_port to a circuit port in circuit model + * Note: + * - the parent of physical pb_port MUST be a physical pb_type + */ + std::map pb_circuit_ports_; + /* Pair a pb_graph_node to a physical pb_graph_node * Note: * - the pb_type of physical pb_graph_node must be a physical pb_type From 61b487eb75be91e5af9c7d8543e98e9198959d4c Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 29 Jan 2020 11:29:41 -0700 Subject: [PATCH 041/645] show more information in the log file --- openfpga/src/base/annotate_pb_types.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/openfpga/src/base/annotate_pb_types.cpp b/openfpga/src/base/annotate_pb_types.cpp index 783447cfb..064fc2cc0 100644 --- a/openfpga/src/base/annotate_pb_types.cpp +++ b/openfpga/src/base/annotate_pb_types.cpp @@ -1021,6 +1021,8 @@ void annotate_pb_types(const DeviceContext& vpr_device_ctx, VprPbTypeAnnotation& vpr_pb_type_annotation) { /* Annotate physical mode to pb_type in the VPR pb_type graph */ + VTR_LOG("\n"); + VTR_LOG("Building annotation for physical modes in pb_type...\n"); build_vpr_physical_pb_mode_explicit_annotation(vpr_device_ctx, openfpga_arch, vpr_pb_type_annotation); @@ -1034,10 +1036,15 @@ void annotate_pb_types(const DeviceContext& vpr_device_ctx, * Must run AFTER physical mode annotation is done and * BEFORE inferring the circuit model for interconnect */ + VTR_LOG("\n"); + VTR_LOG("Building annotation about physical types for pb_type interconnection..."); annotate_pb_graph_interconnect_physical_type(vpr_device_ctx, vpr_pb_type_annotation); + VTR_LOG("Done\n"); /* Annotate physical pb_types to operating pb_type in the VPR pb_type graph */ + VTR_LOG("\n"); + VTR_LOG("Building annotation between operating and physical pb_types...\n"); build_vpr_physical_pb_type_explicit_annotation(vpr_device_ctx, openfpga_arch, vpr_pb_type_annotation); @@ -1051,7 +1058,8 @@ void annotate_pb_types(const DeviceContext& vpr_device_ctx, * - physical pb_type to circuit model * - interconnect of physical pb_type to circuit model */ - /* TODO: link the pb_type port to circuit model port here! */ + VTR_LOG("\n"); + VTR_LOG("Building annotation between physical pb_types and circuit models...\n"); link_vpr_pb_type_to_circuit_model_explicit_annotation(vpr_device_ctx, openfpga_arch, vpr_pb_type_annotation); link_vpr_pb_interconnect_to_circuit_model_explicit_annotation(vpr_device_ctx, openfpga_arch, From 24b180b2983cd85a20d79b62a17e93a5658786a6 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 29 Jan 2020 11:59:20 -0700 Subject: [PATCH 042/645] change the mode bit storage in annotation data structure from string to vector of integers --- .../src/pb_type_annotation.cpp | 4 +-- .../libarchopenfpga/src/pb_type_annotation.h | 6 ++-- .../src/read_xml_pb_type_annotation.cpp | 28 ++++++++++++++++++- .../src/write_xml_pb_type_annotation.cpp | 7 ++++- 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/libopenfpga/libarchopenfpga/src/pb_type_annotation.cpp b/libopenfpga/libarchopenfpga/src/pb_type_annotation.cpp index da494f77f..d04e39b72 100644 --- a/libopenfpga/libarchopenfpga/src/pb_type_annotation.cpp +++ b/libopenfpga/libarchopenfpga/src/pb_type_annotation.cpp @@ -61,7 +61,7 @@ std::string PbTypeAnnotation::idle_mode_name() const { return idle_mode_name_; } -std::string PbTypeAnnotation::mode_bits() const { +std::vector PbTypeAnnotation::mode_bits() const { return mode_bits_; } @@ -155,7 +155,7 @@ void PbTypeAnnotation::set_idle_mode_name(const std::string& name) { idle_mode_name_ = name; } -void PbTypeAnnotation::set_mode_bits(const std::string& mode_bits) { +void PbTypeAnnotation::set_mode_bits(const std::vector& mode_bits) { mode_bits_ = mode_bits; } diff --git a/libopenfpga/libarchopenfpga/src/pb_type_annotation.h b/libopenfpga/libarchopenfpga/src/pb_type_annotation.h index e03201cde..a969ee93d 100644 --- a/libopenfpga/libarchopenfpga/src/pb_type_annotation.h +++ b/libopenfpga/libarchopenfpga/src/pb_type_annotation.h @@ -43,7 +43,7 @@ class PbTypeAnnotation { bool is_physical_pb_type() const; std::string physical_mode_name() const; std::string idle_mode_name() const; - std::string mode_bits() const; + std::vector mode_bits() const; std::string circuit_model_name() const; int physical_pb_type_index_factor() const; int physical_pb_type_index_offset() const; @@ -61,7 +61,7 @@ class PbTypeAnnotation { void set_physical_parent_mode_names(const std::vector& names); void set_physical_mode_name(const std::string& name); void set_idle_mode_name(const std::string& name); - void set_mode_bits(const std::string& mode_bits); + void set_mode_bits(const std::vector& mode_bits); void set_circuit_model_name(const std::string& name); void set_physical_pb_type_index_factor(const int& value); void set_physical_pb_type_index_offset(const int& value); @@ -102,7 +102,7 @@ class PbTypeAnnotation { std::string idle_mode_name_; /* Configuration bits to select an operting mode for the circuit mode name */ - std::string mode_bits_; + std::vector mode_bits_; /* Circuit mode name linked to a physical pb_type. * This is only applicable to the physical pb_type diff --git a/libopenfpga/libarchopenfpga/src/read_xml_pb_type_annotation.cpp b/libopenfpga/libarchopenfpga/src/read_xml_pb_type_annotation.cpp index 253053bf5..a17e970b7 100644 --- a/libopenfpga/libarchopenfpga/src/read_xml_pb_type_annotation.cpp +++ b/libopenfpga/libarchopenfpga/src/read_xml_pb_type_annotation.cpp @@ -63,6 +63,31 @@ void read_xml_pb_port_annotation(pugi::xml_node& xml_port, pb_type_annotation.set_physical_pin_rotate_offset(name_attr, get_attribute(xml_port, "physical_mode_pin_rotate_offset", loc_data, pugiutil::ReqOpt::OPTIONAL).as_int(0)); } +/******************************************************************** + * Parse mode_bits: convert from string to array of digits + * We only allow the bit to either '0' or '1' + *******************************************************************/ +static +std::vector parse_mode_bits(pugi::xml_node& xml_mode_bits, + const pugiutil::loc_data& loc_data, + const std::string& mode_bit_str) { + std::vector mode_bits; + + for (const char& bit_char : mode_bit_str) { + if ('0' == bit_char) { + mode_bits.push_back(0); + } else if ('1' == bit_char) { + mode_bits.push_back(1); + } else { + archfpga_throw(loc_data.filename_c_str(), loc_data.line(xml_mode_bits), + "Unexpected '%c' character found in the mode bit '%s'! Only allow either '0' or '1'\n", + bit_char, mode_bit_str.c_str()); + } + } + + return mode_bits; +} + /******************************************************************** * Parse XML description for a pb_type annotation under a XML node *******************************************************************/ @@ -119,7 +144,8 @@ void read_xml_pb_type_annotation(pugi::xml_node& xml_pb_type, pb_type_annotation.set_idle_mode_name(get_attribute(xml_pb_type, "idle_mode_name", loc_data, pugiutil::ReqOpt::OPTIONAL).as_string()); /* Parse mode bits which are applied to both pb_types */ - pb_type_annotation.set_mode_bits(get_attribute(xml_pb_type, "mode_bits", loc_data, pugiutil::ReqOpt::OPTIONAL).as_string()); + std::vector mode_bit_data = parse_mode_bits(xml_pb_type, loc_data, get_attribute(xml_pb_type, "mode_bits", loc_data, pugiutil::ReqOpt::OPTIONAL).as_string()); + pb_type_annotation.set_mode_bits(mode_bit_data); /* If this is a physical pb_type, circuit model name is an optional attribute, * which is applicable to leaf pb_type in the hierarchy diff --git a/libopenfpga/libarchopenfpga/src/write_xml_pb_type_annotation.cpp b/libopenfpga/libarchopenfpga/src/write_xml_pb_type_annotation.cpp index a771c782d..531e117f1 100644 --- a/libopenfpga/libarchopenfpga/src/write_xml_pb_type_annotation.cpp +++ b/libopenfpga/libarchopenfpga/src/write_xml_pb_type_annotation.cpp @@ -159,7 +159,12 @@ void write_xml_pb_type_annotation(std::fstream& fp, /* Output mode_bits */ if (!pb_type_annotation.mode_bits().empty()) { - write_xml_attribute(fp, "mode_bits", pb_type_annotation.mode_bits().c_str()); + /* Convert the vector of integer to string */ + std::string mode_bits_str; + for (const size_t& bit : pb_type_annotation.mode_bits()) { + mode_bits_str += std::to_string(bit); + } + write_xml_attribute(fp, "mode_bits", mode_bits_str.c_str()); } /* Output circuit model name */ From a722438fa3ab89dc1fe360483905c9d66e9cbec2 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 29 Jan 2020 12:27:55 -0700 Subject: [PATCH 043/645] add mode bits binding to pb_type annotation --- openfpga/src/base/annotate_pb_types.cpp | 116 +++++++++++++++++++ openfpga/src/base/vpr_pb_type_annotation.cpp | 20 ++++ openfpga/src/base/vpr_pb_type_annotation.h | 4 +- 3 files changed, 139 insertions(+), 1 deletion(-) diff --git a/openfpga/src/base/annotate_pb_types.cpp b/openfpga/src/base/annotate_pb_types.cpp index 064fc2cc0..7095855e0 100644 --- a/openfpga/src/base/annotate_pb_types.cpp +++ b/openfpga/src/base/annotate_pb_types.cpp @@ -1011,6 +1011,118 @@ void link_vpr_pb_interconnect_to_circuit_model_implicit_annotation(const DeviceC } } +/******************************************************************** + * This function will bind mode selection bits to a primitive pb_type + * in the vpr_pb_type_annotation + *******************************************************************/ +static +bool link_primitive_pb_type_to_mode_bits(t_pb_type* primitive_pb_type, + const PbTypeAnnotation& pb_type_annotation, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + /* Error out if this is not a primitive pb_type */ + if (false == is_primitive_pb_type(primitive_pb_type)) { + VTR_LOG_ERROR("Mode selection is only applicable to primitive pb_type while pb_type '%s' is not primitve !\n", + primitive_pb_type->name); + return false; + } + + /* Update the annotation */ + vpr_pb_type_annotation.add_pb_type_mode_bits(primitive_pb_type, pb_type_annotation.mode_bits()); + + return true; +} + +/******************************************************************** + * This function will link + * - pb_type to mode bits by following + * the explicit definition in OpenFPGA architecture XML + * + * Note: + * - This function should be executed only AFTER + * the physical mode and physical pb_type annotation is completed + * the physical pb_type circuit model annotation is completed + *******************************************************************/ +static +void link_vpr_pb_type_to_mode_bits_explicit_annotation(const DeviceContext& vpr_device_ctx, + const Arch& openfpga_arch, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + /* Walk through the pb_type annotation stored in the openfpga arch */ + for (const PbTypeAnnotation& pb_type_annotation : openfpga_arch.pb_type_annotations) { + if (true == pb_type_annotation.mode_bits().empty()) { + continue; + } + + /* Convert the vector of integer to string */ + std::string mode_bits_str; + for (const size_t& bit : pb_type_annotation.mode_bits()) { + mode_bits_str += std::to_string(bit); + } + + /* Collect the information about the full hierarchy of physical pb_type to be annotated */ + std::vector target_pb_type_names; + std::vector target_pb_mode_names; + + if (true == pb_type_annotation.is_operating_pb_type()) { + target_pb_type_names = pb_type_annotation.operating_parent_pb_type_names(); + target_pb_type_names.push_back(pb_type_annotation.operating_pb_type_name()); + target_pb_mode_names = pb_type_annotation.operating_parent_mode_names(); + } + + if (true == pb_type_annotation.is_physical_pb_type()) { + target_pb_type_names = pb_type_annotation.physical_parent_pb_type_names(); + target_pb_type_names.push_back(pb_type_annotation.physical_pb_type_name()); + target_pb_mode_names = pb_type_annotation.physical_parent_mode_names(); + } + + /* We must have at least one pb_type in the list */ + VTR_ASSERT_SAFE(0 < target_pb_type_names.size()); + + /* Pb type information are located at the logic_block_types in the device context of VPR + * We iterate over the vectors and find the pb_type matches the parent_pb_type_name + */ + bool link_success = false; + + for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { + /* By pass nullptr for pb_type head */ + if (nullptr == lb_type.pb_type) { + continue; + } + /* Check the name of the top-level pb_type, if it does not match, we can bypass */ + if (target_pb_type_names[0] != std::string(lb_type.pb_type->name)) { + continue; + } + /* Match the name in the top-level, we go further to search the operating as well as + * physical pb_types in the graph */ + t_pb_type* target_pb_type = try_find_pb_type_with_given_path(lb_type.pb_type, target_pb_type_names, + target_pb_mode_names); + if (nullptr == target_pb_type) { + continue; + } + + /* Only try to bind pb_type to circuit model when it is defined by users */ + if (true == link_primitive_pb_type_to_mode_bits(target_pb_type, + pb_type_annotation, vpr_pb_type_annotation)) { + /* Give a message */ + VTR_LOG("Bind physical pb_type '%s' to mode selection bits '%s'\n", + target_pb_type->name, + mode_bits_str.c_str()); + + link_success = true; + break; + } + } + + if (false == link_success) { + /* Not found, error out! */ + VTR_LOG_ERROR("Unable to bind pb_type '%s' to mode_selection bits '%s'!\n", + target_pb_type_names.back().c_str(), + mode_bits_str.c_str()); + return; + } + } +} + + /******************************************************************** * Top-level function to link openfpga architecture to VPR, including: * - physical pb_type @@ -1069,6 +1181,10 @@ void annotate_pb_types(const DeviceContext& vpr_device_ctx, /* TODO: check the circuit model annotation */ /* Link physical pb_type to mode_bits */ + VTR_LOG("\n"); + VTR_LOG("Building annotation between physical pb_types and mode selection bits...\n"); + link_vpr_pb_type_to_mode_bits_explicit_annotation(vpr_device_ctx, openfpga_arch, + vpr_pb_type_annotation); } diff --git a/openfpga/src/base/vpr_pb_type_annotation.cpp b/openfpga/src/base/vpr_pb_type_annotation.cpp index 867f40794..435f46be7 100644 --- a/openfpga/src/base/vpr_pb_type_annotation.cpp +++ b/openfpga/src/base/vpr_pb_type_annotation.cpp @@ -105,6 +105,15 @@ CircuitPortId VprPbTypeAnnotation::pb_circuit_port(t_port* pb_port) const { return pb_circuit_ports_.at(pb_port); } +std::vector VprPbTypeAnnotation::pb_type_mode_bits(t_pb_type* pb_type) const { + /* Ensure that the pb_type is in the list */ + std::map>::const_iterator it = pb_type_mode_bits_.find(pb_type); + if (it == pb_type_mode_bits_.end()) { + /* Return an empty vector */ + return std::vector(); + } + return pb_type_mode_bits_.at(pb_type); +} /************************************************************************ * Public mutators @@ -201,4 +210,15 @@ void VprPbTypeAnnotation::add_pb_circuit_port(t_port* pb_port, const CircuitPort pb_circuit_ports_[pb_port] = circuit_port; } +void VprPbTypeAnnotation::add_pb_type_mode_bits(t_pb_type* pb_type, const std::vector& mode_bits) { + /* Warn any override attempt */ + std::map>::const_iterator it = pb_type_mode_bits_.find(pb_type); + if (it != pb_type_mode_bits_.end()) { + VTR_LOG_WARN("Override the mode bits mapping for pb_type '%s'!\n", + pb_type->name); + } + + pb_type_mode_bits_[pb_type] = mode_bits; +} + } /* End namespace openfpga*/ diff --git a/openfpga/src/base/vpr_pb_type_annotation.h b/openfpga/src/base/vpr_pb_type_annotation.h index fb0fec934..24535ee4a 100644 --- a/openfpga/src/base/vpr_pb_type_annotation.h +++ b/openfpga/src/base/vpr_pb_type_annotation.h @@ -38,6 +38,7 @@ class VprPbTypeAnnotation { CircuitModelId interconnect_circuit_model(t_interconnect* pb_interconnect) const; e_interconnect interconnect_physical_type(t_interconnect* pb_interconnect) const; CircuitPortId pb_circuit_port(t_port* pb_port) const; + std::vector pb_type_mode_bits(t_pb_type* pb_type) const; public: /* Public mutators */ void add_pb_type_physical_mode(t_pb_type* pb_type, t_mode* physical_mode); void add_physical_pb_type(t_pb_type* operating_pb_type, t_pb_type* physical_pb_type); @@ -47,6 +48,7 @@ class VprPbTypeAnnotation { void add_interconnect_circuit_model(t_interconnect* pb_interconnect, const CircuitModelId& circuit_model); void add_interconnect_physical_type(t_interconnect* pb_interconnect, const e_interconnect& physical_type); void add_pb_circuit_port(t_port* pb_port, const CircuitPortId& circuit_port); + void add_pb_type_mode_bits(t_pb_type* pb_type, const std::vector& mode_bits); private: /* Internal data */ /* Pair a regular pb_type to its physical pb_type */ std::map physical_pb_types_; @@ -82,7 +84,7 @@ class VprPbTypeAnnotation { * - if the pb_type is an operating pb_type, the mode bits will be applied * when the operating pb_type is used by packer */ - std::map> pb_type_mode_bits_; + std::map> pb_type_mode_bits_; /* Pair a pb_port to its physical pb_port * Note: From b67358d2c5e8fe4ebed16c01cf25dfd8f65c8f0d Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 29 Jan 2020 12:56:49 -0700 Subject: [PATCH 044/645] add check codes for physical pb_type circuit model annotation --- openfpga/src/base/annotate_pb_types.cpp | 100 +++++++++++++++++++++++- 1 file changed, 99 insertions(+), 1 deletion(-) diff --git a/openfpga/src/base/annotate_pb_types.cpp b/openfpga/src/base/annotate_pb_types.cpp index 7095855e0..e326cc772 100644 --- a/openfpga/src/base/annotate_pb_types.cpp +++ b/openfpga/src/base/annotate_pb_types.cpp @@ -1011,6 +1011,103 @@ void link_vpr_pb_interconnect_to_circuit_model_implicit_annotation(const DeviceC } } +/******************************************************************** + * This function will recursively traverse only the physical mode + * and physical pb_types in the graph to ensure + * - Every physical pb_type should be linked to a valid circuit model + * - Every port of the pb_type have been linked to a valid port of a circuit model + * - Every interconnect has been linked to a valid circuit model + * in a correct type + *******************************************************************/ +static +void rec_check_vpr_pb_type_circuit_model_annotation(t_pb_type* cur_pb_type, + const CircuitLibrary& circuit_lib, + const VprPbTypeAnnotation& vpr_pb_type_annotation, + size_t& num_err) { + /* Primitive pb_type should always been binded to a physical pb_type */ + if (true == is_primitive_pb_type(cur_pb_type)) { + /* Every physical pb_type should be linked to a valid circuit model */ + if (CircuitModelId::INVALID() == vpr_pb_type_annotation.pb_type_circuit_model(cur_pb_type)) { + VTR_LOG_ERROR("Found a physical pb_type '%s' missing circuit model binding!\n", + cur_pb_type->name); + num_err++; + return; /* Invalid id already, further check is not applicable */ + } + /* Every port of the pb_type have been linked to a valid port of a circuit model */ + for (t_port* port : pb_type_ports(cur_pb_type)) { + if (CircuitPortId::INVALID() == vpr_pb_type_annotation.pb_circuit_port(port)) { + VTR_LOG_ERROR("Found a port '%s' of physical pb_type '%s' missing circuit port binding!\n", + port->name, cur_pb_type->name); + num_err++; + } + } + return; + } + + /* Every interconnect in the physical mode has been linked to a valid circuit model in a correct type */ + t_mode* physical_mode = vpr_pb_type_annotation.physical_mode(cur_pb_type); + for (t_interconnect* interc : pb_mode_interconnects(physical_mode)) { + CircuitModelId interc_circuit_model = vpr_pb_type_annotation.interconnect_circuit_model(interc); + if (CircuitModelId::INVALID() == interc_circuit_model) { + VTR_LOG_ERROR("Found an interconnect '%s' under physical mode '%s' of pb_type '%s' missing circuit model binding!\n", + interc->name, + physical_mode->name, + cur_pb_type->name); + num_err++; + continue; + } + e_circuit_model_type required_circuit_model_type = pb_interconnect_require_circuit_model_type(vpr_pb_type_annotation.interconnect_physical_type(interc)); + if (circuit_lib.model_type(interc_circuit_model) != required_circuit_model_type) { + VTR_LOG_ERROR("Found an interconnect '%s' under physical mode '%s' of pb_type '%s' linked to a circuit model '%s' with a wrong type!\nExpect: '%s' Linked: '%s'\n", + interc->name, + physical_mode->name, + cur_pb_type->name, + circuit_lib.model_name(interc_circuit_model).c_str(), + CIRCUIT_MODEL_TYPE_STRING[circuit_lib.model_type(interc_circuit_model)], + CIRCUIT_MODEL_TYPE_STRING[required_circuit_model_type]); + num_err++; + } + } + + /* Traverse only the physical mode */ + for (int ichild = 0; ichild < physical_mode->num_pb_type_children; ++ichild) { + rec_check_vpr_pb_type_circuit_model_annotation(&(physical_mode->pb_type_children[ichild]), + circuit_lib, + vpr_pb_type_annotation, + num_err); + } +} + +/******************************************************************** + * This function will check the circuit model annotation for + * each physical pb_type in the device + * - Every physical pb_type should be linked to a valid circuit model + * - Every port of the pb_type have been linked a valid port of a circuit model + * - Every interconnect has been linked to a valid circuit model + * in a correct type + *******************************************************************/ +static +void check_vpr_pb_type_circuit_model_annotation(const DeviceContext& vpr_device_ctx, + const CircuitLibrary& circuit_lib, + const VprPbTypeAnnotation& vpr_pb_type_annotation) { + size_t num_err = 0; + + for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { + /* By pass nullptr for pb_type head */ + if (nullptr == lb_type.pb_type) { + continue; + } + /* Top pb_type should always has a physical mode! */ + rec_check_vpr_pb_type_circuit_model_annotation(lb_type.pb_type, circuit_lib, vpr_pb_type_annotation, num_err); + } + if (0 == num_err) { + VTR_LOG("Check physical pb_type annotation for circuit model passed.\n"); + } else { + VTR_LOG_ERROR("Check physical pb_type annotation for circuit model failed with %ld errors!\n", + num_err); + } +} + /******************************************************************** * This function will bind mode selection bits to a primitive pb_type * in the vpr_pb_type_annotation @@ -1178,7 +1275,8 @@ void annotate_pb_types(const DeviceContext& vpr_device_ctx, vpr_pb_type_annotation); link_vpr_pb_interconnect_to_circuit_model_implicit_annotation(vpr_device_ctx, openfpga_arch.circuit_lib, vpr_pb_type_annotation); - /* TODO: check the circuit model annotation */ + check_vpr_pb_type_circuit_model_annotation(vpr_device_ctx, openfpga_arch.circuit_lib, + const_cast(vpr_pb_type_annotation)); /* Link physical pb_type to mode_bits */ VTR_LOG("\n"); From a4381563bcfaec676cb6209b8fb9c2a80961e266 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 29 Jan 2020 13:47:59 -0700 Subject: [PATCH 045/645] move check codes to separated source files --- openfpga/src/base/annotate_pb_types.cpp | 266 +---------------- .../src/base/check_pb_type_annotation.cpp | 277 ++++++++++++++++++ openfpga/src/base/check_pb_type_annotation.h | 30 ++ 3 files changed, 308 insertions(+), 265 deletions(-) create mode 100644 openfpga/src/base/check_pb_type_annotation.cpp create mode 100644 openfpga/src/base/check_pb_type_annotation.h diff --git a/openfpga/src/base/annotate_pb_types.cpp b/openfpga/src/base/annotate_pb_types.cpp index e326cc772..3163813b8 100644 --- a/openfpga/src/base/annotate_pb_types.cpp +++ b/openfpga/src/base/annotate_pb_types.cpp @@ -10,6 +10,7 @@ #include "vpr_pb_type_annotation.h" #include "pb_type_utils.h" #include "annotate_pb_graph.h" +#include "check_pb_type_annotation.h" #include "annotate_pb_types.h" /* begin namespace openfpga */ @@ -178,88 +179,6 @@ void build_vpr_physical_pb_mode_implicit_annotation(const DeviceContext& vpr_dev } } -/******************************************************************** - * This function will recursively traverse pb_type graph to ensure - * 1. there is only a physical mode under each pb_type - * 2. physical mode appears only when its parent is a physical mode. - *******************************************************************/ -static -void rec_check_vpr_physical_pb_mode_annotation(t_pb_type* cur_pb_type, - const bool& expect_physical_mode, - const VprPbTypeAnnotation& vpr_pb_type_annotation, - size_t& num_err) { - /* We do not check any primitive pb_type */ - if (true == is_primitive_pb_type(cur_pb_type)) { - return; - } - - /* For non-primitive pb_type: - * - If we expect a physical mode to exist under this pb_type - * we should be able to find one in the annoation - * - If we do NOT expect a physical mode, make sure we find - * nothing in the annotation - */ - if (true == expect_physical_mode) { - if (nullptr == vpr_pb_type_annotation.physical_mode(cur_pb_type)) { - VTR_LOG_ERROR("Unable to find a physical mode for a multi-mode pb_type '%s'!\n", - cur_pb_type->name); - VTR_LOG_ERROR("Please specify in the OpenFPGA architecture\n"); - num_err++; - return; - } - } else { - VTR_ASSERT_SAFE(false == expect_physical_mode); - if (nullptr != vpr_pb_type_annotation.physical_mode(cur_pb_type)) { - VTR_LOG_ERROR("Find a physical mode '%s' for pb_type '%s' which is not under any physical mode!\n", - vpr_pb_type_annotation.physical_mode(cur_pb_type)->name, - cur_pb_type->name); - num_err++; - return; - } - } - - /* Traverse all the modes - * - for pb_type children under a physical mode, we expect an physical mode - * - for pb_type children under non-physical mode, we expect no physical mode - */ - for (int imode = 0; imode < cur_pb_type->num_modes; ++imode) { - bool expect_child_physical_mode = false; - if (&(cur_pb_type->modes[imode]) == vpr_pb_type_annotation.physical_mode(cur_pb_type)) { - expect_child_physical_mode = true && expect_physical_mode; - } - for (int ichild = 0; ichild < cur_pb_type->modes[imode].num_pb_type_children; ++ichild) { - rec_check_vpr_physical_pb_mode_annotation(&(cur_pb_type->modes[imode].pb_type_children[ichild]), - expect_child_physical_mode, vpr_pb_type_annotation, - num_err); - } - } -} - -/******************************************************************** - * This function will check the physical mode annotation for - * each pb_type in the device - *******************************************************************/ -static -void check_vpr_physical_pb_mode_annotation(const DeviceContext& vpr_device_ctx, - const VprPbTypeAnnotation& vpr_pb_type_annotation) { - size_t num_err = 0; - - for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { - /* By pass nullptr for pb_type head */ - if (nullptr == lb_type.pb_type) { - continue; - } - /* Top pb_type should always has a physical mode! */ - rec_check_vpr_physical_pb_mode_annotation(lb_type.pb_type, true, vpr_pb_type_annotation, num_err); - } - if (0 == num_err) { - VTR_LOG("Check physical mode annotation for pb_types passed.\n"); - } else { - VTR_LOG("Check physical mode annotation for pb_types failed with %ld errors!\n", - num_err); - } -} - /******************************************************************** * This function aims to make a pair of operating and physical * pb_types: @@ -499,91 +418,6 @@ void build_vpr_physical_pb_type_implicit_annotation(const DeviceContext& vpr_dev } } -/******************************************************************** - * This function will check - * - if a primitive pb_type has been mapped to a physical pb_type - * - if every port of the pb_type have been linked a port of a physical pb_type - *******************************************************************/ -static -void check_vpr_physical_primitive_pb_type_annotation(t_pb_type* cur_pb_type, - const VprPbTypeAnnotation& vpr_pb_type_annotation, - size_t& num_err) { - if (nullptr == vpr_pb_type_annotation.physical_pb_type(cur_pb_type)) { - VTR_LOG_ERROR("Find a pb_type '%s' which has not been mapped to any physical pb_type!\n", - cur_pb_type->name); - VTR_LOG_ERROR("Please specify in the OpenFPGA architecture\n"); - num_err++; - return; - } - - /* Now we need to check each port of the pb_type */ - for (t_port* pb_port : pb_type_ports(cur_pb_type)) { - if (nullptr == vpr_pb_type_annotation.physical_pb_port(pb_port)) { - VTR_LOG_ERROR("Find a port '%s' of pb_type '%s' which has not been mapped to any physical port!\n", - pb_port->name, cur_pb_type->name); - VTR_LOG_ERROR("Please specify in the OpenFPGA architecture\n"); - num_err++; - } - } - - return; -} - -/******************************************************************** - * This function will recursively traverse pb_type graph to ensure - * 1. there is only a physical mode under each pb_type - * 2. physical mode appears only when its parent is a physical mode. - *******************************************************************/ -static -void rec_check_vpr_physical_pb_type_annotation(t_pb_type* cur_pb_type, - const VprPbTypeAnnotation& vpr_pb_type_annotation, - size_t& num_err) { - /* Primitive pb_type should always been binded to a physical pb_type */ - if (true == is_primitive_pb_type(cur_pb_type)) { - check_vpr_physical_primitive_pb_type_annotation(cur_pb_type, vpr_pb_type_annotation, num_err); - return; - } - - /* Traverse all the modes - * - for pb_type children under a physical mode, we expect an physical mode - * - for pb_type children under non-physical mode, we expect no physical mode - */ - for (int imode = 0; imode < cur_pb_type->num_modes; ++imode) { - for (int ichild = 0; ichild < cur_pb_type->modes[imode].num_pb_type_children; ++ichild) { - rec_check_vpr_physical_pb_type_annotation(&(cur_pb_type->modes[imode].pb_type_children[ichild]), - vpr_pb_type_annotation, - num_err); - } - } -} - -/******************************************************************** - * This function will check the physical pb_type annotation for - * each pb_type in the device - * Every pb_type should have been linked to a physical pb_type - * and every port of the pb_type have been linked a port of a physical pb_type - *******************************************************************/ -static -void check_vpr_physical_pb_type_annotation(const DeviceContext& vpr_device_ctx, - const VprPbTypeAnnotation& vpr_pb_type_annotation) { - size_t num_err = 0; - - for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { - /* By pass nullptr for pb_type head */ - if (nullptr == lb_type.pb_type) { - continue; - } - /* Top pb_type should always has a physical mode! */ - rec_check_vpr_physical_pb_type_annotation(lb_type.pb_type, vpr_pb_type_annotation, num_err); - } - if (0 == num_err) { - VTR_LOG("Check physical pb_type annotation for pb_types passed.\n"); - } else { - VTR_LOG("Check physical pb_type annotation for pb_types failed with %ld errors!\n", - num_err); - } -} - /******************************************************************** * This function aims to link all the ports defined under a physical * pb_type to the ports of circuit model in the circuit library @@ -1011,103 +845,6 @@ void link_vpr_pb_interconnect_to_circuit_model_implicit_annotation(const DeviceC } } -/******************************************************************** - * This function will recursively traverse only the physical mode - * and physical pb_types in the graph to ensure - * - Every physical pb_type should be linked to a valid circuit model - * - Every port of the pb_type have been linked to a valid port of a circuit model - * - Every interconnect has been linked to a valid circuit model - * in a correct type - *******************************************************************/ -static -void rec_check_vpr_pb_type_circuit_model_annotation(t_pb_type* cur_pb_type, - const CircuitLibrary& circuit_lib, - const VprPbTypeAnnotation& vpr_pb_type_annotation, - size_t& num_err) { - /* Primitive pb_type should always been binded to a physical pb_type */ - if (true == is_primitive_pb_type(cur_pb_type)) { - /* Every physical pb_type should be linked to a valid circuit model */ - if (CircuitModelId::INVALID() == vpr_pb_type_annotation.pb_type_circuit_model(cur_pb_type)) { - VTR_LOG_ERROR("Found a physical pb_type '%s' missing circuit model binding!\n", - cur_pb_type->name); - num_err++; - return; /* Invalid id already, further check is not applicable */ - } - /* Every port of the pb_type have been linked to a valid port of a circuit model */ - for (t_port* port : pb_type_ports(cur_pb_type)) { - if (CircuitPortId::INVALID() == vpr_pb_type_annotation.pb_circuit_port(port)) { - VTR_LOG_ERROR("Found a port '%s' of physical pb_type '%s' missing circuit port binding!\n", - port->name, cur_pb_type->name); - num_err++; - } - } - return; - } - - /* Every interconnect in the physical mode has been linked to a valid circuit model in a correct type */ - t_mode* physical_mode = vpr_pb_type_annotation.physical_mode(cur_pb_type); - for (t_interconnect* interc : pb_mode_interconnects(physical_mode)) { - CircuitModelId interc_circuit_model = vpr_pb_type_annotation.interconnect_circuit_model(interc); - if (CircuitModelId::INVALID() == interc_circuit_model) { - VTR_LOG_ERROR("Found an interconnect '%s' under physical mode '%s' of pb_type '%s' missing circuit model binding!\n", - interc->name, - physical_mode->name, - cur_pb_type->name); - num_err++; - continue; - } - e_circuit_model_type required_circuit_model_type = pb_interconnect_require_circuit_model_type(vpr_pb_type_annotation.interconnect_physical_type(interc)); - if (circuit_lib.model_type(interc_circuit_model) != required_circuit_model_type) { - VTR_LOG_ERROR("Found an interconnect '%s' under physical mode '%s' of pb_type '%s' linked to a circuit model '%s' with a wrong type!\nExpect: '%s' Linked: '%s'\n", - interc->name, - physical_mode->name, - cur_pb_type->name, - circuit_lib.model_name(interc_circuit_model).c_str(), - CIRCUIT_MODEL_TYPE_STRING[circuit_lib.model_type(interc_circuit_model)], - CIRCUIT_MODEL_TYPE_STRING[required_circuit_model_type]); - num_err++; - } - } - - /* Traverse only the physical mode */ - for (int ichild = 0; ichild < physical_mode->num_pb_type_children; ++ichild) { - rec_check_vpr_pb_type_circuit_model_annotation(&(physical_mode->pb_type_children[ichild]), - circuit_lib, - vpr_pb_type_annotation, - num_err); - } -} - -/******************************************************************** - * This function will check the circuit model annotation for - * each physical pb_type in the device - * - Every physical pb_type should be linked to a valid circuit model - * - Every port of the pb_type have been linked a valid port of a circuit model - * - Every interconnect has been linked to a valid circuit model - * in a correct type - *******************************************************************/ -static -void check_vpr_pb_type_circuit_model_annotation(const DeviceContext& vpr_device_ctx, - const CircuitLibrary& circuit_lib, - const VprPbTypeAnnotation& vpr_pb_type_annotation) { - size_t num_err = 0; - - for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { - /* By pass nullptr for pb_type head */ - if (nullptr == lb_type.pb_type) { - continue; - } - /* Top pb_type should always has a physical mode! */ - rec_check_vpr_pb_type_circuit_model_annotation(lb_type.pb_type, circuit_lib, vpr_pb_type_annotation, num_err); - } - if (0 == num_err) { - VTR_LOG("Check physical pb_type annotation for circuit model passed.\n"); - } else { - VTR_LOG_ERROR("Check physical pb_type annotation for circuit model failed with %ld errors!\n", - num_err); - } -} - /******************************************************************** * This function will bind mode selection bits to a primitive pb_type * in the vpr_pb_type_annotation @@ -1219,7 +956,6 @@ void link_vpr_pb_type_to_mode_bits_explicit_annotation(const DeviceContext& vpr_ } } - /******************************************************************** * Top-level function to link openfpga architecture to VPR, including: * - physical pb_type diff --git a/openfpga/src/base/check_pb_type_annotation.cpp b/openfpga/src/base/check_pb_type_annotation.cpp new file mode 100644 index 000000000..b77e48162 --- /dev/null +++ b/openfpga/src/base/check_pb_type_annotation.cpp @@ -0,0 +1,277 @@ +/******************************************************************** + * This file includes functions to build links between pb_types + * in particular to annotate the physical mode and physical pb_type + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_time.h" +#include "vtr_assert.h" +#include "vtr_log.h" + +#include "pb_type_utils.h" +#include "check_pb_type_annotation.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * This function will recursively traverse pb_type graph to ensure + * 1. there is only a physical mode under each pb_type + * 2. physical mode appears only when its parent is a physical mode. + *******************************************************************/ +static +void rec_check_vpr_physical_pb_mode_annotation(t_pb_type* cur_pb_type, + const bool& expect_physical_mode, + const VprPbTypeAnnotation& vpr_pb_type_annotation, + size_t& num_err) { + /* We do not check any primitive pb_type */ + if (true == is_primitive_pb_type(cur_pb_type)) { + return; + } + + /* For non-primitive pb_type: + * - If we expect a physical mode to exist under this pb_type + * we should be able to find one in the annoation + * - If we do NOT expect a physical mode, make sure we find + * nothing in the annotation + */ + if (true == expect_physical_mode) { + if (nullptr == vpr_pb_type_annotation.physical_mode(cur_pb_type)) { + VTR_LOG_ERROR("Unable to find a physical mode for a multi-mode pb_type '%s'!\n", + cur_pb_type->name); + VTR_LOG_ERROR("Please specify in the OpenFPGA architecture\n"); + num_err++; + return; + } + } else { + VTR_ASSERT_SAFE(false == expect_physical_mode); + if (nullptr != vpr_pb_type_annotation.physical_mode(cur_pb_type)) { + VTR_LOG_ERROR("Find a physical mode '%s' for pb_type '%s' which is not under any physical mode!\n", + vpr_pb_type_annotation.physical_mode(cur_pb_type)->name, + cur_pb_type->name); + num_err++; + return; + } + } + + /* Traverse all the modes + * - for pb_type children under a physical mode, we expect an physical mode + * - for pb_type children under non-physical mode, we expect no physical mode + */ + for (int imode = 0; imode < cur_pb_type->num_modes; ++imode) { + bool expect_child_physical_mode = false; + if (&(cur_pb_type->modes[imode]) == vpr_pb_type_annotation.physical_mode(cur_pb_type)) { + expect_child_physical_mode = true && expect_physical_mode; + } + for (int ichild = 0; ichild < cur_pb_type->modes[imode].num_pb_type_children; ++ichild) { + rec_check_vpr_physical_pb_mode_annotation(&(cur_pb_type->modes[imode].pb_type_children[ichild]), + expect_child_physical_mode, vpr_pb_type_annotation, + num_err); + } + } +} + +/******************************************************************** + * This function will check the physical mode annotation for + * each pb_type in the device + *******************************************************************/ +void check_vpr_physical_pb_mode_annotation(const DeviceContext& vpr_device_ctx, + const VprPbTypeAnnotation& vpr_pb_type_annotation) { + size_t num_err = 0; + + for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { + /* By pass nullptr for pb_type head */ + if (nullptr == lb_type.pb_type) { + continue; + } + /* Top pb_type should always has a physical mode! */ + rec_check_vpr_physical_pb_mode_annotation(lb_type.pb_type, true, vpr_pb_type_annotation, num_err); + } + if (0 == num_err) { + VTR_LOG("Check physical mode annotation for pb_types passed.\n"); + } else { + VTR_LOG("Check physical mode annotation for pb_types failed with %ld errors!\n", + num_err); + } +} + +/******************************************************************** + * This function will check + * - if a primitive pb_type has been mapped to a physical pb_type + * - if every port of the pb_type have been linked a port of a physical pb_type + *******************************************************************/ +static +void check_vpr_physical_primitive_pb_type_annotation(t_pb_type* cur_pb_type, + const VprPbTypeAnnotation& vpr_pb_type_annotation, + size_t& num_err) { + if (nullptr == vpr_pb_type_annotation.physical_pb_type(cur_pb_type)) { + VTR_LOG_ERROR("Find a pb_type '%s' which has not been mapped to any physical pb_type!\n", + cur_pb_type->name); + VTR_LOG_ERROR("Please specify in the OpenFPGA architecture\n"); + num_err++; + return; + } + + /* Now we need to check each port of the pb_type */ + for (t_port* pb_port : pb_type_ports(cur_pb_type)) { + if (nullptr == vpr_pb_type_annotation.physical_pb_port(pb_port)) { + VTR_LOG_ERROR("Find a port '%s' of pb_type '%s' which has not been mapped to any physical port!\n", + pb_port->name, cur_pb_type->name); + VTR_LOG_ERROR("Please specify in the OpenFPGA architecture\n"); + num_err++; + } + } + + return; +} + +/******************************************************************** + * This function will recursively traverse pb_type graph to ensure + * 1. there is only a physical mode under each pb_type + * 2. physical mode appears only when its parent is a physical mode. + *******************************************************************/ +static +void rec_check_vpr_physical_pb_type_annotation(t_pb_type* cur_pb_type, + const VprPbTypeAnnotation& vpr_pb_type_annotation, + size_t& num_err) { + /* Primitive pb_type should always been binded to a physical pb_type */ + if (true == is_primitive_pb_type(cur_pb_type)) { + check_vpr_physical_primitive_pb_type_annotation(cur_pb_type, vpr_pb_type_annotation, num_err); + return; + } + + /* Traverse all the modes + * - for pb_type children under a physical mode, we expect an physical mode + * - for pb_type children under non-physical mode, we expect no physical mode + */ + for (int imode = 0; imode < cur_pb_type->num_modes; ++imode) { + for (int ichild = 0; ichild < cur_pb_type->modes[imode].num_pb_type_children; ++ichild) { + rec_check_vpr_physical_pb_type_annotation(&(cur_pb_type->modes[imode].pb_type_children[ichild]), + vpr_pb_type_annotation, + num_err); + } + } +} + +/******************************************************************** + * This function will check the physical pb_type annotation for + * each pb_type in the device + * Every pb_type should have been linked to a physical pb_type + * and every port of the pb_type have been linked a port of a physical pb_type + *******************************************************************/ +void check_vpr_physical_pb_type_annotation(const DeviceContext& vpr_device_ctx, + const VprPbTypeAnnotation& vpr_pb_type_annotation) { + size_t num_err = 0; + + for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { + /* By pass nullptr for pb_type head */ + if (nullptr == lb_type.pb_type) { + continue; + } + /* Top pb_type should always has a physical mode! */ + rec_check_vpr_physical_pb_type_annotation(lb_type.pb_type, vpr_pb_type_annotation, num_err); + } + if (0 == num_err) { + VTR_LOG("Check physical pb_type annotation for pb_types passed.\n"); + } else { + VTR_LOG("Check physical pb_type annotation for pb_types failed with %ld errors!\n", + num_err); + } +} + +/******************************************************************** + * This function will recursively traverse only the physical mode + * and physical pb_types in the graph to ensure + * - Every physical pb_type should be linked to a valid circuit model + * - Every port of the pb_type have been linked to a valid port of a circuit model + * - Every interconnect has been linked to a valid circuit model + * in a correct type + *******************************************************************/ +static +void rec_check_vpr_pb_type_circuit_model_annotation(t_pb_type* cur_pb_type, + const CircuitLibrary& circuit_lib, + const VprPbTypeAnnotation& vpr_pb_type_annotation, + size_t& num_err) { + /* Primitive pb_type should always been binded to a physical pb_type */ + if (true == is_primitive_pb_type(cur_pb_type)) { + /* Every physical pb_type should be linked to a valid circuit model */ + if (CircuitModelId::INVALID() == vpr_pb_type_annotation.pb_type_circuit_model(cur_pb_type)) { + VTR_LOG_ERROR("Found a physical pb_type '%s' missing circuit model binding!\n", + cur_pb_type->name); + num_err++; + return; /* Invalid id already, further check is not applicable */ + } + /* Every port of the pb_type have been linked to a valid port of a circuit model */ + for (t_port* port : pb_type_ports(cur_pb_type)) { + if (CircuitPortId::INVALID() == vpr_pb_type_annotation.pb_circuit_port(port)) { + VTR_LOG_ERROR("Found a port '%s' of physical pb_type '%s' missing circuit port binding!\n", + port->name, cur_pb_type->name); + num_err++; + } + } + return; + } + + /* Every interconnect in the physical mode has been linked to a valid circuit model in a correct type */ + t_mode* physical_mode = vpr_pb_type_annotation.physical_mode(cur_pb_type); + for (t_interconnect* interc : pb_mode_interconnects(physical_mode)) { + CircuitModelId interc_circuit_model = vpr_pb_type_annotation.interconnect_circuit_model(interc); + if (CircuitModelId::INVALID() == interc_circuit_model) { + VTR_LOG_ERROR("Found an interconnect '%s' under physical mode '%s' of pb_type '%s' missing circuit model binding!\n", + interc->name, + physical_mode->name, + cur_pb_type->name); + num_err++; + continue; + } + e_circuit_model_type required_circuit_model_type = pb_interconnect_require_circuit_model_type(vpr_pb_type_annotation.interconnect_physical_type(interc)); + if (circuit_lib.model_type(interc_circuit_model) != required_circuit_model_type) { + VTR_LOG_ERROR("Found an interconnect '%s' under physical mode '%s' of pb_type '%s' linked to a circuit model '%s' with a wrong type!\nExpect: '%s' Linked: '%s'\n", + interc->name, + physical_mode->name, + cur_pb_type->name, + circuit_lib.model_name(interc_circuit_model).c_str(), + CIRCUIT_MODEL_TYPE_STRING[circuit_lib.model_type(interc_circuit_model)], + CIRCUIT_MODEL_TYPE_STRING[required_circuit_model_type]); + num_err++; + } + } + + /* Traverse only the physical mode */ + for (int ichild = 0; ichild < physical_mode->num_pb_type_children; ++ichild) { + rec_check_vpr_pb_type_circuit_model_annotation(&(physical_mode->pb_type_children[ichild]), + circuit_lib, + vpr_pb_type_annotation, + num_err); + } +} + +/******************************************************************** + * This function will check the circuit model annotation for + * each physical pb_type in the device + * - Every physical pb_type should be linked to a valid circuit model + * - Every port of the pb_type have been linked a valid port of a circuit model + * - Every interconnect has been linked to a valid circuit model + * in a correct type + *******************************************************************/ +void check_vpr_pb_type_circuit_model_annotation(const DeviceContext& vpr_device_ctx, + const CircuitLibrary& circuit_lib, + const VprPbTypeAnnotation& vpr_pb_type_annotation) { + size_t num_err = 0; + + for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { + /* By pass nullptr for pb_type head */ + if (nullptr == lb_type.pb_type) { + continue; + } + /* Top pb_type should always has a physical mode! */ + rec_check_vpr_pb_type_circuit_model_annotation(lb_type.pb_type, circuit_lib, vpr_pb_type_annotation, num_err); + } + if (0 == num_err) { + VTR_LOG("Check physical pb_type annotation for circuit model passed.\n"); + } else { + VTR_LOG_ERROR("Check physical pb_type annotation for circuit model failed with %ld errors!\n", + num_err); + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/base/check_pb_type_annotation.h b/openfpga/src/base/check_pb_type_annotation.h new file mode 100644 index 000000000..25e13fc16 --- /dev/null +++ b/openfpga/src/base/check_pb_type_annotation.h @@ -0,0 +1,30 @@ +#ifndef CHECK_PB_TYPE_ANNOTATION_H +#define CHECK_PB_TYPE_ANNOTATION_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "vpr_context.h" +#include "openfpga_context.h" +#include "vpr_pb_type_annotation.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void check_vpr_physical_pb_mode_annotation(const DeviceContext& vpr_device_ctx, + const VprPbTypeAnnotation& vpr_pb_type_annotation); + +void check_vpr_physical_pb_type_annotation(const DeviceContext& vpr_device_ctx, + const VprPbTypeAnnotation& vpr_pb_type_annotation); + +void check_vpr_pb_type_circuit_model_annotation(const DeviceContext& vpr_device_ctx, + const CircuitLibrary& circuit_lib, + const VprPbTypeAnnotation& vpr_pb_type_annotation); + +} /* end namespace openfpga */ + +#endif From d2c47693f61d2c66306741bb496be6a9d036d72f Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 29 Jan 2020 14:29:00 -0700 Subject: [PATCH 046/645] add check codes for mode bits annotation to pb_types and clean up utils source files --- openfpga/src/base/annotate_pb_types.cpp | 2 + .../src/base/check_pb_type_annotation.cpp | 120 ++++++++- openfpga/src/base/check_pb_type_annotation.h | 4 + openfpga/src/utils/circuit_library_utils.cpp | 229 ++++++++++++++++++ openfpga/src/utils/circuit_library_utils.h | 44 ++++ .../src/{base => utils}/pb_graph_utils.cpp | 0 openfpga/src/{base => utils}/pb_graph_utils.h | 0 .../src/{base => utils}/pb_type_utils.cpp | 0 openfpga/src/{base => utils}/pb_type_utils.h | 0 9 files changed, 391 insertions(+), 8 deletions(-) create mode 100644 openfpga/src/utils/circuit_library_utils.cpp create mode 100644 openfpga/src/utils/circuit_library_utils.h rename openfpga/src/{base => utils}/pb_graph_utils.cpp (100%) rename openfpga/src/{base => utils}/pb_graph_utils.h (100%) rename openfpga/src/{base => utils}/pb_type_utils.cpp (100%) rename openfpga/src/{base => utils}/pb_type_utils.h (100%) diff --git a/openfpga/src/base/annotate_pb_types.cpp b/openfpga/src/base/annotate_pb_types.cpp index 3163813b8..023154c9b 100644 --- a/openfpga/src/base/annotate_pb_types.cpp +++ b/openfpga/src/base/annotate_pb_types.cpp @@ -1019,6 +1019,8 @@ void annotate_pb_types(const DeviceContext& vpr_device_ctx, VTR_LOG("Building annotation between physical pb_types and mode selection bits...\n"); link_vpr_pb_type_to_mode_bits_explicit_annotation(vpr_device_ctx, openfpga_arch, vpr_pb_type_annotation); + check_vpr_pb_type_mode_bits_annotation(vpr_device_ctx, openfpga_arch.circuit_lib, + const_cast(vpr_pb_type_annotation)); } diff --git a/openfpga/src/base/check_pb_type_annotation.cpp b/openfpga/src/base/check_pb_type_annotation.cpp index b77e48162..fdebba00c 100644 --- a/openfpga/src/base/check_pb_type_annotation.cpp +++ b/openfpga/src/base/check_pb_type_annotation.cpp @@ -8,6 +8,7 @@ #include "vtr_log.h" #include "pb_type_utils.h" +#include "circuit_library_utils.h" #include "check_pb_type_annotation.h" /* begin namespace openfpga */ @@ -89,8 +90,8 @@ void check_vpr_physical_pb_mode_annotation(const DeviceContext& vpr_device_ctx, if (0 == num_err) { VTR_LOG("Check physical mode annotation for pb_types passed.\n"); } else { - VTR_LOG("Check physical mode annotation for pb_types failed with %ld errors!\n", - num_err); + VTR_LOG_ERROR("Check physical mode annotation for pb_types failed with %ld errors!\n", + num_err); } } @@ -139,10 +140,7 @@ void rec_check_vpr_physical_pb_type_annotation(t_pb_type* cur_pb_type, return; } - /* Traverse all the modes - * - for pb_type children under a physical mode, we expect an physical mode - * - for pb_type children under non-physical mode, we expect no physical mode - */ + /* Traverse all the modes */ for (int imode = 0; imode < cur_pb_type->num_modes; ++imode) { for (int ichild = 0; ichild < cur_pb_type->modes[imode].num_pb_type_children; ++ichild) { rec_check_vpr_physical_pb_type_annotation(&(cur_pb_type->modes[imode].pb_type_children[ichild]), @@ -173,8 +171,8 @@ void check_vpr_physical_pb_type_annotation(const DeviceContext& vpr_device_ctx, if (0 == num_err) { VTR_LOG("Check physical pb_type annotation for pb_types passed.\n"); } else { - VTR_LOG("Check physical pb_type annotation for pb_types failed with %ld errors!\n", - num_err); + VTR_LOG_ERROR("Check physical pb_type annotation for pb_types failed with %ld errors!\n", + num_err); } } @@ -274,4 +272,110 @@ void check_vpr_pb_type_circuit_model_annotation(const DeviceContext& vpr_device_ } } +/******************************************************************** + * This function will recursively traverse all the primitive pb_types + * in the graph to ensure + * - If a primitive pb_type has mode bits, it must have been linked to a physical pb_type + * and the circuit model must have a port for mode selection. + * And the port size must match the length of mode bits + *******************************************************************/ +static +void rec_check_vpr_pb_type_mode_bits_annotation(t_pb_type* cur_pb_type, + const CircuitLibrary& circuit_lib, + const VprPbTypeAnnotation& vpr_pb_type_annotation, + size_t& num_err) { + /* Primitive pb_type should always been binded to a physical pb_type */ + if (true == is_primitive_pb_type(cur_pb_type)) { + /* Find the physical pb_type + * If the physical pb_type has mode selection bits, this pb_type must have as well! + */ + t_pb_type* physical_pb_type = vpr_pb_type_annotation.physical_pb_type(cur_pb_type); + + if (nullptr == physical_pb_type) { + VTR_LOG_ERROR("Find a pb_type '%s' which has not been mapped to any physical pb_type!\n", + cur_pb_type->name); + VTR_LOG_ERROR("Please specify in the OpenFPGA architecture\n"); + num_err++; + return; + } + + if (vpr_pb_type_annotation.pb_type_mode_bits(cur_pb_type).size() != vpr_pb_type_annotation.pb_type_mode_bits(physical_pb_type).size()) { + VTR_LOG_ERROR("Found different sizes of mode_bits for pb_type '%s' and its physical pb_type '%s'\n", + cur_pb_type->name, + physical_pb_type->name); + num_err++; + return; + } + + /* Try to find a mode selection port for the circuit model linked to the circuit model */ + CircuitModelId circuit_model = vpr_pb_type_annotation.pb_type_circuit_model(physical_pb_type); + if (CircuitModelId::INVALID() == vpr_pb_type_annotation.pb_type_circuit_model(physical_pb_type)) { + VTR_LOG_ERROR("Found a physical pb_type '%s' missing circuit model binding!\n", + physical_pb_type->name); + num_err++; + return; /* Invalid id already, further check is not applicable */ + } + + if (0 == vpr_pb_type_annotation.pb_type_mode_bits(cur_pb_type).size()) { + /* No mode bits to be checked! */ + return; + } + /* Search the ports of this circuit model and we must have a mode selection port */ + std::vector mode_select_ports = find_circuit_mode_select_sram_ports(circuit_lib, circuit_model); + size_t port_num_mode_bits = 0; + for (const CircuitPortId& mode_select_port : mode_select_ports) { + port_num_mode_bits += circuit_lib.port_size(mode_select_port); + } + if (port_num_mode_bits != vpr_pb_type_annotation.pb_type_mode_bits(cur_pb_type).size()) { + VTR_LOG_ERROR("Length of mode bits of pb_type '%s' does not match the size(%ld) of mode selection ports of circuit model '%s'!\n", + cur_pb_type->name, + port_num_mode_bits, + circuit_lib.model_name(circuit_model).c_str()); + num_err++; + } + + return; + } + + /* Traverse all the modes */ + for (int imode = 0; imode < cur_pb_type->num_modes; ++imode) { + for (int ichild = 0; ichild < cur_pb_type->modes[imode].num_pb_type_children; ++ichild) { + rec_check_vpr_pb_type_mode_bits_annotation(&(cur_pb_type->modes[imode].pb_type_children[ichild]), + circuit_lib, vpr_pb_type_annotation, + num_err); + } + } +} + +/******************************************************************** + * This function will check the mode_bits annotation for each pb_type + * - If a primitive pb_type has mode bits, it must have been linked to a physical pb_type + * - If a primitive pb_type has mode bits, the circuit model must have + * a port for mode selection. And the port size must match the length of mode bits + * + * Note: + * - This function should be run after circuit mode and mode bits annotation + * is completed + *******************************************************************/ +void check_vpr_pb_type_mode_bits_annotation(const DeviceContext& vpr_device_ctx, + const CircuitLibrary& circuit_lib, + const VprPbTypeAnnotation& vpr_pb_type_annotation) { + size_t num_err = 0; + + for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { + /* By pass nullptr for pb_type head */ + if (nullptr == lb_type.pb_type) { + continue; + } + /* Top pb_type should always has a physical mode! */ + rec_check_vpr_pb_type_mode_bits_annotation(lb_type.pb_type, circuit_lib, vpr_pb_type_annotation, num_err); + } + if (0 == num_err) { + VTR_LOG("Check pb_type annotation for mode selection bits passed.\n"); + } else { + VTR_LOG_ERROR("Check physical pb_type annotation for mode selection bits failed with %ld errors!\n", + num_err); + } +} + } /* end namespace openfpga */ diff --git a/openfpga/src/base/check_pb_type_annotation.h b/openfpga/src/base/check_pb_type_annotation.h index 25e13fc16..d3d2799aa 100644 --- a/openfpga/src/base/check_pb_type_annotation.h +++ b/openfpga/src/base/check_pb_type_annotation.h @@ -25,6 +25,10 @@ void check_vpr_pb_type_circuit_model_annotation(const DeviceContext& vpr_device_ const CircuitLibrary& circuit_lib, const VprPbTypeAnnotation& vpr_pb_type_annotation); +void check_vpr_pb_type_mode_bits_annotation(const DeviceContext& vpr_device_ctx, + const CircuitLibrary& circuit_lib, + const VprPbTypeAnnotation& vpr_pb_type_annotation); + } /* end namespace openfpga */ #endif diff --git a/openfpga/src/utils/circuit_library_utils.cpp b/openfpga/src/utils/circuit_library_utils.cpp new file mode 100644 index 000000000..720a0e7c1 --- /dev/null +++ b/openfpga/src/utils/circuit_library_utils.cpp @@ -0,0 +1,229 @@ +/************************************************************************ + * Function to perform fundamental operation for the circuit library + * These functions are not universal methods for the CircuitLibrary class + * They are made to ease the development in some specific purposes + * Please classify such functions in this file + ***********************************************************************/ +#include + +/* Headers from vtr util library */ +#include "vtr_assert.h" +#include "vtr_log.h" + +#include "circuit_library_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Get the model id of a SRAM model that is used to configure + * a circuit model + *******************************************************************/ +std::vector find_circuit_sram_models(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model) { + /* SRAM model id is stored in the sram ports of a circuit model */ + std::vector sram_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_SRAM); + std::vector sram_models; + + /* Create a list of sram models, but avoid duplicated model ids */ + for (const auto& sram_port : sram_ports) { + CircuitModelId sram_model = circuit_lib.port_tri_state_model(sram_port); + VTR_ASSERT( true == circuit_lib.valid_model_id(sram_model) ); + if (sram_models.end() != std::find(sram_models.begin(), sram_models.end(), sram_model)) { + continue; /* Already in the list, skip the addition */ + } + /* Not in the list, add it */ + sram_models.push_back(sram_model); + } + + return sram_models; +} + +/******************************************************************** + * Find regular (not mode select) sram ports of a circuit model + *******************************************************************/ +std::vector find_circuit_regular_sram_ports(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model) { + std::vector sram_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_SRAM, true); + std::vector regular_sram_ports; + + for (const auto& port : sram_ports) { + if (true == circuit_lib.port_is_mode_select(port)) { + continue; + } + regular_sram_ports.push_back(port); + } + + return regular_sram_ports; +} + +/******************************************************************** + * Find mode select sram ports of a circuit model + *******************************************************************/ +std::vector find_circuit_mode_select_sram_ports(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model) { + std::vector sram_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_SRAM, true); + std::vector mode_select_sram_ports; + + for (const auto& port : sram_ports) { + if (false == circuit_lib.port_is_mode_select(port)) { + continue; + } + mode_select_sram_ports.push_back(port); + } + + return mode_select_sram_ports; +} + + +/******************************************************************** + * Find the number of shared configuration bits for a ReRAM circuit + * TODO: this function is subjected to be changed due to ReRAM-based SRAM cell design!!! + *******************************************************************/ +static +size_t find_rram_circuit_num_shared_config_bits(const CircuitLibrary& circuit_lib, + const CircuitModelId& rram_model, + const e_config_protocol_type& config_protocol_type) { + size_t num_shared_config_bits = 0; + + /* Branch on the organization of configuration protocol */ + switch (config_protocol_type) { + case CONFIG_MEM_STANDALONE: + case CONFIG_MEM_SCAN_CHAIN: + break; + case CONFIG_MEM_MEMORY_BANK: { + /* Find BL/WL ports */ + std::vector blb_ports = circuit_lib.model_ports_by_type(rram_model, CIRCUIT_MODEL_PORT_BLB); + for (auto blb_port : blb_ports) { + num_shared_config_bits = std::max((int)num_shared_config_bits, (int)circuit_lib.port_size(blb_port) - 1); + } + break; + } + default: + VTR_LOG_ERROR("Invalid type of configuration protocol!\n"); + exit(1); + } + + return num_shared_config_bits; +} + +/******************************************************************** + * A generic function to find the number of shared configuration bits + * for circuit model + * It will return 0 for CMOS circuits + * It will return the maximum shared configuration bits across ReRAM models + * + * Note: This function may give WRONG results when all the SRAM ports + * are not properly linked to its circuit models! + * So, it should be called after the SRAM linking is done!!! + * + * IMPORTANT: This function should NOT be used to find the number of shared configuration bits + * for a multiplexer, because the multiplexer size is determined during + * the FPGA architecture generation (NOT during the XML parsing). + *******************************************************************/ +size_t find_circuit_num_shared_config_bits(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const e_config_protocol_type& config_protocol_type) { + size_t num_shared_config_bits = 0; + + std::vector sram_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_SRAM); + for (auto sram_port : sram_ports) { + CircuitModelId sram_model = circuit_lib.port_tri_state_model(sram_port); + VTR_ASSERT( true == circuit_lib.valid_model_id(sram_model) ); + + /* Depend on the design technolgy of SRAM model, the number of configuration bits will be different */ + switch (circuit_lib.design_tech_type(sram_model)) { + case CIRCUIT_MODEL_DESIGN_CMOS: + /* CMOS circuit do not need shared configuration bits */ + break; + case CIRCUIT_MODEL_DESIGN_RRAM: + /* RRAM circuit do need shared configuration bits, but it is subjected to the largest one among different SRAM models */ + num_shared_config_bits = std::max((int)num_shared_config_bits, (int)find_rram_circuit_num_shared_config_bits(circuit_lib, sram_model, config_protocol_type)); + break; + default: + VTR_LOG_ERROR("Invalid design technology for SRAM circuit model!\n", + circuit_lib.model_name(circuit_model).c_str()); + exit(1); + } + } + + return num_shared_config_bits; +} + +/******************************************************************** + * A generic function to find the number of configuration bits + * for circuit model + * It will sum up the sizes of all the sram ports + * + * IMPORTANT: This function should NOT be used to find the number of configuration bits + * for a multiplexer, because the multiplexer size is determined during + * the FPGA architecture generation (NOT during the XML parsing). + *******************************************************************/ +size_t find_circuit_num_config_bits(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model) { + size_t num_config_bits = 0; + + std::vector sram_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_SRAM); + for (auto sram_port : sram_ports) { + num_config_bits += circuit_lib.port_size(sram_port); + } + + return num_config_bits; +} + +/******************************************************************** + * A generic function to find all the global ports in a circuit library + * + * IMPORTANT: This function will uniquify the global ports whose share + * share the same name !!! + *******************************************************************/ +std::vector find_circuit_library_global_ports(const CircuitLibrary& circuit_lib) { + std::vector global_ports; + + for (auto port : circuit_lib.ports()) { + /* By pass non-global ports*/ + if (false == circuit_lib.port_is_global(port)) { + continue; + } + /* Check if a same port with the same name has already been in the list */ + bool add_to_list = true; + for (const auto& global_port : global_ports) { + if (0 == circuit_lib.port_prefix(port).compare(circuit_lib.port_prefix(global_port))) { + /* Same name, skip list update */ + add_to_list = false; + break; + } + } + if (true == add_to_list) { + /* Add the global_port to the list */ + global_ports.push_back(port); + } + } + + return global_ports; +} + +/******************************************************************** + * A generic function to find all the unique user-defined + * Verilog netlists in a circuit library + * Netlists with same names will be considered as one + *******************************************************************/ +std::vector find_circuit_library_unique_verilog_netlists(const CircuitLibrary& circuit_lib) { + std::vector netlists; + + for (const CircuitModelId& model : circuit_lib.models()) { + /* Skip empty netlist names */ + if (true == circuit_lib.model_verilog_netlist(model).empty()) { + continue; + } + /* See if the netlist name is already in the list */ + std::vector::iterator it = std::find(netlists.begin(), netlists.end(), circuit_lib.model_verilog_netlist(model)); + if (it == netlists.end()) { + netlists.push_back(circuit_lib.model_verilog_netlist(model)); + } + } + + return netlists; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/utils/circuit_library_utils.h b/openfpga/src/utils/circuit_library_utils.h new file mode 100644 index 000000000..aad080b72 --- /dev/null +++ b/openfpga/src/utils/circuit_library_utils.h @@ -0,0 +1,44 @@ +/******************************************************************** + * Header file for circuit_library_utils.cpp + *******************************************************************/ +#ifndef CIRCUIT_LIBRARY_UTILS_H +#define CIRCUIT_LIBRARY_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ + +#include +#include "circuit_types.h" +#include "circuit_library.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +std::vector find_circuit_sram_models(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model); + +std::vector find_circuit_regular_sram_ports(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model); + +std::vector find_circuit_mode_select_sram_ports(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model); + +size_t find_circuit_num_shared_config_bits(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const e_config_protocol_type& sram_orgz_type); + +size_t find_circuit_num_config_bits(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model); + +std::vector find_circuit_library_global_ports(const CircuitLibrary& circuit_lib); + +std::vector find_circuit_library_unique_verilog_netlists(const CircuitLibrary& circuit_lib); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/base/pb_graph_utils.cpp b/openfpga/src/utils/pb_graph_utils.cpp similarity index 100% rename from openfpga/src/base/pb_graph_utils.cpp rename to openfpga/src/utils/pb_graph_utils.cpp diff --git a/openfpga/src/base/pb_graph_utils.h b/openfpga/src/utils/pb_graph_utils.h similarity index 100% rename from openfpga/src/base/pb_graph_utils.h rename to openfpga/src/utils/pb_graph_utils.h diff --git a/openfpga/src/base/pb_type_utils.cpp b/openfpga/src/utils/pb_type_utils.cpp similarity index 100% rename from openfpga/src/base/pb_type_utils.cpp rename to openfpga/src/utils/pb_type_utils.cpp diff --git a/openfpga/src/base/pb_type_utils.h b/openfpga/src/utils/pb_type_utils.h similarity index 100% rename from openfpga/src/base/pb_type_utils.h rename to openfpga/src/utils/pb_type_utils.h From 8c86c0af042ac0ea52a799c77e837d060b970df3 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 29 Jan 2020 16:23:41 -0700 Subject: [PATCH 047/645] add check netlist naming conflict command and functions --- .../base/check_netlist_naming_conflict.cpp | 92 +++++++++++++++++++ .../src/base/check_netlist_naming_conflict.h | 23 +++++ openfpga/src/base/openfpga_setup_command.cpp | 34 ++++++- openfpga/test_script/s298.openfpga | 3 + 4 files changed, 148 insertions(+), 4 deletions(-) create mode 100644 openfpga/src/base/check_netlist_naming_conflict.cpp create mode 100644 openfpga/src/base/check_netlist_naming_conflict.h diff --git a/openfpga/src/base/check_netlist_naming_conflict.cpp b/openfpga/src/base/check_netlist_naming_conflict.cpp new file mode 100644 index 000000000..58980687f --- /dev/null +++ b/openfpga/src/base/check_netlist_naming_conflict.cpp @@ -0,0 +1,92 @@ +/******************************************************************** + * This file includes functions to detect and correct any naming + * in the users' BLIF netlist that violates the syntax of OpenFPGA + * fabric generator, i.e., Verilog generator and SPICE generator + *******************************************************************/ +#include + +/* Headers from vtrutil library */ +#include "vtr_time.h" +#include "vtr_assert.h" +#include "vtr_log.h" + +#include "check_netlist_naming_conflict.h" + +/* Include global variables of VPR */ +#include "globals.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * This function aims to check if the name contains any of the + * sensitive characters in the list + *******************************************************************/ +static +bool name_contain_sensitive_chars(const std::string& name, + const std::string& sensitive_chars) { + for (const char& sensitive_char : sensitive_chars) { + /* Return true since we find a characters */ + if (std::string::npos != name.find(sensitive_char)) { + return true; + } + } + + return false; +} + +/******************************************************************** + * Detect and report any naming conflict by checking a list of + * sensitive characters + * - Iterate over all the blocks and see if any block name contain + * any sensitive character + * - Iterate over all the nets and see if any net name contain + * any sensitive character + *******************************************************************/ +static +void detect_netlist_naming_conflict(const AtomNetlist& atom_netlist, + const std::string& sensitive_chars) { + size_t num_conflicts = 0; + + /* Walk through blocks in the netlist */ + for (const auto& block : atom_netlist.blocks()) { + const std::string& block_name = atom_netlist.block_name(block); + if (true == name_contain_sensitive_chars(block_name, sensitive_chars)) { + VTR_LOG("Block '%s' violates the syntax requirement by OpenFPGA!\n", + block_name.c_str()); + num_conflicts++; + } + } + + /* Walk through nets in the netlist */ + for (const auto& net : atom_netlist.nets()) { + const std::string& net_name = atom_netlist.net_name(net); + if (true == name_contain_sensitive_chars(net_name, sensitive_chars)) { + VTR_LOG("Net '%s' violates the syntax requirement by OpenFPGA!\n", + net_name.c_str()); + num_conflicts++; + } + } + + if (0 < num_conflicts) { + VTR_LOG("Found %ld naming conflicts in the netlist. Please correct so as to use any fabric generators.\n", + num_conflicts); + } +} + +/******************************************************************** + * Top-level function to detect and correct any naming + * in the users' BLIF netlist that violates the syntax of OpenFPGA + * fabric generator, i.e., Verilog generator and SPICE generator + *******************************************************************/ +void check_netlist_naming_conflict(OpenfpgaContext& openfpga_context, + const Command& cmd, const CommandContext& cmd_context) { + const std::string& sensitive_chars(".,:;\'\"+-<>()[]{}!@#$%^&*~`?/"); + + /* Do the main job first: detect any naming in the BLIF netlist that violates the syntax */ + detect_netlist_naming_conflict(g_vpr_ctx.atom().nlist, sensitive_chars); + +} + +} /* end namespace openfpga */ + diff --git a/openfpga/src/base/check_netlist_naming_conflict.h b/openfpga/src/base/check_netlist_naming_conflict.h new file mode 100644 index 000000000..54d86bca4 --- /dev/null +++ b/openfpga/src/base/check_netlist_naming_conflict.h @@ -0,0 +1,23 @@ +#ifndef CHECK_NETLIST_NAMING_CONFLICT_H +#define CHECK_NETLIST_NAMING_CONFLICT_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "command.h" +#include "command_context.h" +#include "openfpga_context.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void check_netlist_naming_conflict(OpenfpgaContext& openfpga_context, + const Command& cmd, const CommandContext& cmd_context); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/base/openfpga_setup_command.cpp b/openfpga/src/base/openfpga_setup_command.cpp index af078446a..ba7ad45b6 100644 --- a/openfpga/src/base/openfpga_setup_command.cpp +++ b/openfpga/src/base/openfpga_setup_command.cpp @@ -5,16 +5,22 @@ *******************************************************************/ #include "openfpga_read_arch.h" #include "openfpga_link_arch.h" +#include "check_netlist_naming_conflict.h" #include "openfpga_setup_command.h" /* begin namespace openfpga */ namespace openfpga { void add_openfpga_setup_commands(openfpga::Shell& shell) { + /* Get the unique id of 'vpr' command which is to be used in creating the dependency graph */ + const ShellCommandId& shell_cmd_vpr_id = shell.command(std::string("vpr")); + /* Add a new class of commands */ ShellCommandClassId openfpga_setup_cmd_class = shell.add_command_class("OpenFPGA setup"); - /* Command 'read_openfpga_arch' */ + /******************************** + * Command 'read_openfpga_arch' + */ Command shell_cmd_read_arch("read_openfpga_arch"); /* Add an option '--file' in short '-f'*/ CommandOptionId read_arch_opt_file = shell_cmd_read_arch.add_option("file", true, "file path to the architecture XML"); @@ -26,7 +32,9 @@ void add_openfpga_setup_commands(openfpga::Shell& shell) { shell.set_command_class(shell_cmd_read_arch_id, openfpga_setup_cmd_class); shell.set_command_execute_function(shell_cmd_read_arch_id, read_arch); - /* Command 'write_openfpga_arch' */ + /******************************** + * Command 'write_openfpga_arch' + */ Command shell_cmd_write_arch("write_openfpga_arch"); /* Add an option '--file' in short '-f'*/ CommandOptionId write_arch_opt_file = shell_cmd_write_arch.add_option("file", true, "file path to the architecture XML"); @@ -40,7 +48,9 @@ void add_openfpga_setup_commands(openfpga::Shell& shell) { /* The 'write_openfpga_arch' command should NOT be executed before 'read_openfpga_arch' */ shell.set_command_dependency(shell_cmd_write_arch_id, std::vector(1, shell_cmd_read_arch_id)); - /* Command 'link_openfpga_arch' */ + /******************************** + * Command 'link_openfpga_arch' + */ Command shell_cmd_link_openfpga_arch("link_openfpga_arch"); /* Add command 'link_openfpga_arch' to the Shell */ @@ -48,12 +58,28 @@ void add_openfpga_setup_commands(openfpga::Shell& shell) { shell.set_command_class(shell_cmd_link_openfpga_arch_id, openfpga_setup_cmd_class); shell.set_command_execute_function(shell_cmd_link_openfpga_arch_id, link_arch); /* The 'link_openfpga_arch' command should NOT be executed before 'read_openfpga_arch' and 'vpr' */ - const ShellCommandId& shell_cmd_vpr_id = shell.command(std::string("vpr")); std::vector cmd_dependency_link_openfpga_arch; cmd_dependency_link_openfpga_arch.push_back(shell_cmd_read_arch_id); cmd_dependency_link_openfpga_arch.push_back(shell_cmd_vpr_id); shell.set_command_dependency(shell_cmd_link_openfpga_arch_id, cmd_dependency_link_openfpga_arch); + /******************************************* + * Command 'check_netlist_naming_conflict' + */ + Command shell_cmd_check_netlist_naming_conflict("check_netlist_naming_conflict"); + /* Add an option '--correction' */ + shell_cmd_check_netlist_naming_conflict.add_option("correction", false, "Apply correction to any conflicts found"); + /* Add an option '--report' */ + CommandOptionId check_netlist_opt_rpt = shell_cmd_check_netlist_naming_conflict.add_option("report", false, "Output a report file about what any correction applied"); + shell_cmd_check_netlist_naming_conflict.set_option_require_value(check_netlist_opt_rpt, openfpga::OPT_STRING); + + /* Add command 'check_netlist_naming_conflict' to the Shell */ + ShellCommandId shell_cmd_check_netlist_naming_conflict_id = shell.add_command(shell_cmd_check_netlist_naming_conflict, "Check any block/net naming in users' BLIF netlist violates the syntax of fabric generator"); + shell.set_command_class(shell_cmd_check_netlist_naming_conflict_id, openfpga_setup_cmd_class); + shell.set_command_execute_function(shell_cmd_check_netlist_naming_conflict_id, check_netlist_naming_conflict); + /* The 'link_openfpga_arch' command should NOT be executed before 'read_openfpga_arch' and 'vpr' */ + std::vector cmd_dependency_check_netlist_naming_conflict(1, shell_cmd_vpr_id); + shell.set_command_dependency(shell_cmd_link_openfpga_arch_id, cmd_dependency_check_netlist_naming_conflict); } } /* end namespace openfpga */ diff --git a/openfpga/test_script/s298.openfpga b/openfpga/test_script/s298.openfpga index 6d4eaa434..08e4d9ce3 100644 --- a/openfpga/test_script/s298.openfpga +++ b/openfpga/test_script/s298.openfpga @@ -7,5 +7,8 @@ read_openfpga_arch -f ./test_openfpga_arch/k6_N10_40nm_openfpga.xml # Annotate the OpenFPGA architecture to VPR data base link_openfpga_arch +# Check and correct any naming conflicts in the BLIF netlist +check_netlist_naming_conflict --correction --report ./netlist_renaming.rpt + # Finish and exit OpenFPGA exit From 2dc4c26257cdec0c822c564af4a98c42fa533733 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 29 Jan 2020 17:49:33 -0700 Subject: [PATCH 048/645] add naming fix-up --- .../base/check_netlist_naming_conflict.cpp | 122 +++++++++++++++--- openfpga/src/base/openfpga_context.h | 5 + openfpga/src/base/openfpga_setup_command.cpp | 2 +- openfpga/src/base/vpr_netlist_annotation.cpp | 68 ++++++++++ openfpga/src/base/vpr_netlist_annotation.h | 43 ++++++ openfpga/test_script/s298.openfpga | 2 +- 6 files changed, 223 insertions(+), 19 deletions(-) create mode 100644 openfpga/src/base/vpr_netlist_annotation.cpp create mode 100644 openfpga/src/base/vpr_netlist_annotation.h diff --git a/openfpga/src/base/check_netlist_naming_conflict.cpp b/openfpga/src/base/check_netlist_naming_conflict.cpp index 58980687f..57056712a 100644 --- a/openfpga/src/base/check_netlist_naming_conflict.cpp +++ b/openfpga/src/base/check_netlist_naming_conflict.cpp @@ -21,18 +21,47 @@ namespace openfpga { /******************************************************************** * This function aims to check if the name contains any of the * sensitive characters in the list + * Return a string of sensitive characters which are contained + * in the name *******************************************************************/ static -bool name_contain_sensitive_chars(const std::string& name, - const std::string& sensitive_chars) { +std::string name_contain_sensitive_chars(const std::string& name, + const std::string& sensitive_chars) { + std::string violation; + for (const char& sensitive_char : sensitive_chars) { /* Return true since we find a characters */ if (std::string::npos != name.find(sensitive_char)) { - return true; + violation.push_back(sensitive_char); } } - return false; + return violation; +} + +/******************************************************************** + * This function aims to fix up a name that contains any of the + * sensitive characters in the list + * Return a string the fixed name + *******************************************************************/ +static +std::string fix_name_contain_sensitive_chars(const std::string& name, + const std::string& sensitive_chars, + const std::string& fix_chars) { + std::string fixed_name = name; + + VTR_ASSERT(sensitive_chars.length() == fix_chars.length()); + + for (size_t ichar = 0; ichar < sensitive_chars.length(); ++ichar) { + /* Keep fixing the characters until we cannot find anymore */ + std::string::size_type pos = 0u; + while (std::string::npos != (pos = fixed_name.find(sensitive_chars[ichar], pos))) { + fixed_name.replace(pos, 1, std::string(1, fix_chars[ichar])); + pos += 1; + } + } + + return fixed_name; } /******************************************************************** @@ -44,16 +73,17 @@ bool name_contain_sensitive_chars(const std::string& name, * any sensitive character *******************************************************************/ static -void detect_netlist_naming_conflict(const AtomNetlist& atom_netlist, +size_t detect_netlist_naming_conflict(const AtomNetlist& atom_netlist, const std::string& sensitive_chars) { size_t num_conflicts = 0; /* Walk through blocks in the netlist */ for (const auto& block : atom_netlist.blocks()) { const std::string& block_name = atom_netlist.block_name(block); - if (true == name_contain_sensitive_chars(block_name, sensitive_chars)) { - VTR_LOG("Block '%s' violates the syntax requirement by OpenFPGA!\n", - block_name.c_str()); + const std::string& violation = name_contain_sensitive_chars(block_name, sensitive_chars); + if (false == violation.empty()) { + VTR_LOG("Block '%s' contains illegal characters '%s'\n", + block_name.c_str(), violation.c_str()); num_conflicts++; } } @@ -61,19 +91,62 @@ void detect_netlist_naming_conflict(const AtomNetlist& atom_netlist, /* Walk through nets in the netlist */ for (const auto& net : atom_netlist.nets()) { const std::string& net_name = atom_netlist.net_name(net); - if (true == name_contain_sensitive_chars(net_name, sensitive_chars)) { - VTR_LOG("Net '%s' violates the syntax requirement by OpenFPGA!\n", - net_name.c_str()); + const std::string& violation = name_contain_sensitive_chars(net_name, sensitive_chars); + if (false == violation.empty()) { + VTR_LOG("Net '%s' contains illegal characters '%s'\n", + net_name.c_str(), violation.c_str()); num_conflicts++; } } - if (0 < num_conflicts) { - VTR_LOG("Found %ld naming conflicts in the netlist. Please correct so as to use any fabric generators.\n", - num_conflicts); - } + return num_conflicts; } +/******************************************************************** + * Correct and report any naming conflict by checking a list of + * sensitive characters + * - Iterate over all the blocks and correct any block name that contains + * any sensitive character + * - Iterate over all the nets and correct any net name that contains + * any sensitive character + *******************************************************************/ +static +void correct_netlist_naming_conflict(const AtomNetlist& atom_netlist, + const std::string& sensitive_chars, + const std::string& fix_chars, + VprNetlistAnnotation& vpr_netlist_annotation) { + size_t num_fixes = 0; + + /* Walk through blocks in the netlist */ + for (const auto& block : atom_netlist.blocks()) { + const std::string& block_name = atom_netlist.block_name(block); + const std::string& violation = name_contain_sensitive_chars(block_name, sensitive_chars); + + if (false == violation.empty()) { + /* Apply fix-up here */ + vpr_netlist_annotation.rename_block(block, fix_name_contain_sensitive_chars(block_name, sensitive_chars, fix_chars)); + num_fixes++; + } + } + + /* Walk through nets in the netlist */ + for (const auto& net : atom_netlist.nets()) { + const std::string& net_name = atom_netlist.net_name(net); + const std::string& violation = name_contain_sensitive_chars(net_name, sensitive_chars); + if (false == violation.empty()) { + /* Apply fix-up here */ + vpr_netlist_annotation.rename_net(net, fix_name_contain_sensitive_chars(net_name, sensitive_chars, fix_chars)); + num_fixes++; + } + } + + if (0 < num_fixes) { + VTR_LOG("Fixed %ld naming conflicts in the netlist.\n", + num_fixes); + } + +} + /******************************************************************** * Top-level function to detect and correct any naming * in the users' BLIF netlist that violates the syntax of OpenFPGA @@ -81,11 +154,26 @@ void detect_netlist_naming_conflict(const AtomNetlist& atom_netlist, *******************************************************************/ void check_netlist_naming_conflict(OpenfpgaContext& openfpga_context, const Command& cmd, const CommandContext& cmd_context) { + /* By default, we replace all the illegal characters with '_' */ const std::string& sensitive_chars(".,:;\'\"+-<>()[]{}!@#$%^&*~`?/"); + const std::string& fix_chars("____________________________"); + + CommandOptionId opt_fix = cmd.option("fix"); /* Do the main job first: detect any naming in the BLIF netlist that violates the syntax */ - detect_netlist_naming_conflict(g_vpr_ctx.atom().nlist, sensitive_chars); - + size_t num_conflicts = detect_netlist_naming_conflict(g_vpr_ctx.atom().nlist, sensitive_chars); + VTR_LOGV_ERROR((0 < num_conflicts && (false == cmd_context.option_enable(cmd, opt_fix))), + "Found %ld naming conflicts in the netlist. Please correct so as to use any fabric generators.\n", + num_conflicts); + VTR_LOGV(0 == num_conflicts, + "Check naming conflicts in the netlist passed.\n"); + + + /* If the auto correction is enabled, we apply a fix */ + if (true == cmd_context.option_enable(cmd, opt_fix)) { + correct_netlist_naming_conflict(g_vpr_ctx.atom().nlist, sensitive_chars, + fix_chars, openfpga_context.mutable_vpr_netlist_annotation()); + } } } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_context.h b/openfpga/src/base/openfpga_context.h index 185a08c5c..cac5877d5 100644 --- a/openfpga/src/base/openfpga_context.h +++ b/openfpga/src/base/openfpga_context.h @@ -3,6 +3,7 @@ #include "vpr_context.h" #include "openfpga_arch.h" +#include "vpr_netlist_annotation.h" #include "vpr_pb_type_annotation.h" /******************************************************************** @@ -36,14 +37,18 @@ class OpenfpgaContext : public Context { public: /* Public accessors */ const openfpga::Arch& arch() const { return arch_; } const openfpga::VprPbTypeAnnotation& vpr_pb_type_annotation() const { return vpr_pb_type_annotation_; } + const openfpga::VprNetlistAnnotation& vpr_netlist_annotation() const { return vpr_netlist_annotation_; } public: /* Public mutators */ openfpga::Arch& mutable_arch() { return arch_; } openfpga::VprPbTypeAnnotation& mutable_vpr_pb_type_annotation() { return vpr_pb_type_annotation_; } + openfpga::VprNetlistAnnotation& mutable_vpr_netlist_annotation() { return vpr_netlist_annotation_; } private: /* Internal data */ /* Data structure to store information from read_openfpga_arch library */ openfpga::Arch arch_; /* Annotation to pb_type of VPR */ openfpga::VprPbTypeAnnotation vpr_pb_type_annotation_; + /* Naming fix to netlist */ + openfpga::VprNetlistAnnotation vpr_netlist_annotation_; }; #endif diff --git a/openfpga/src/base/openfpga_setup_command.cpp b/openfpga/src/base/openfpga_setup_command.cpp index ba7ad45b6..9a3eee970 100644 --- a/openfpga/src/base/openfpga_setup_command.cpp +++ b/openfpga/src/base/openfpga_setup_command.cpp @@ -68,7 +68,7 @@ void add_openfpga_setup_commands(openfpga::Shell& shell) { */ Command shell_cmd_check_netlist_naming_conflict("check_netlist_naming_conflict"); /* Add an option '--correction' */ - shell_cmd_check_netlist_naming_conflict.add_option("correction", false, "Apply correction to any conflicts found"); + shell_cmd_check_netlist_naming_conflict.add_option("fix", false, "Apply correction to any conflicts found"); /* Add an option '--report' */ CommandOptionId check_netlist_opt_rpt = shell_cmd_check_netlist_naming_conflict.add_option("report", false, "Output a report file about what any correction applied"); shell_cmd_check_netlist_naming_conflict.set_option_require_value(check_netlist_opt_rpt, openfpga::OPT_STRING); diff --git a/openfpga/src/base/vpr_netlist_annotation.cpp b/openfpga/src/base/vpr_netlist_annotation.cpp new file mode 100644 index 000000000..0d9d5443d --- /dev/null +++ b/openfpga/src/base/vpr_netlist_annotation.cpp @@ -0,0 +1,68 @@ +/************************************************************************ + * Member functions for class VprNetlistAnnotation + ***********************************************************************/ +#include "vtr_log.h" +#include "vtr_assert.h" +#include "vpr_netlist_annotation.h" + +/* namespace openfpga begins */ +namespace openfpga { + +/************************************************************************ + * Constructors + ***********************************************************************/ +VprNetlistAnnotation::VprNetlistAnnotation() { + return; +} + +/************************************************************************ + * Public accessors + ***********************************************************************/ +bool VprNetlistAnnotation::is_block_renamed(const AtomBlockId& block) const { + /* Ensure that the pb_type is in the list */ + std::map::const_iterator it = block_names_.find(block); + return it != block_names_.end(); +} + +std::string VprNetlistAnnotation::block_name(const AtomBlockId& block) const { + VTR_ASSERT(true == is_block_renamed(block)); + return block_names_.at(block); +} + +bool VprNetlistAnnotation::is_net_renamed(const AtomNetId& net) const { + /* Ensure that the pb_type is in the list */ + std::map::const_iterator it = net_names_.find(net); + return it != net_names_.end(); +} + +std::string VprNetlistAnnotation::net_name(const AtomNetId& net) const { + VTR_ASSERT(true == is_net_renamed(net)); + return net_names_.at(net); +} + +/************************************************************************ + * Public mutators + ***********************************************************************/ +void VprNetlistAnnotation::rename_block(const AtomBlockId& block, const std::string& name) { + /* Warn any override attempt */ + std::map::const_iterator it = block_names_.find(block); + if (it != block_names_.end()) { + VTR_LOG_WARN("Override the block with name '%s' in netlist annotation!\n", + name.c_str()); + } + + block_names_[block] = name; +} + +void VprNetlistAnnotation::rename_net(const AtomNetId& net, const std::string& name) { + /* Warn any override attempt */ + std::map::const_iterator it = net_names_.find(net); + if (it != net_names_.end()) { + VTR_LOG_WARN("Override the net with name '%s' in netlist annotation!\n", + name.c_str()); + } + + net_names_[net] = name; +} + +} /* End namespace openfpga*/ diff --git a/openfpga/src/base/vpr_netlist_annotation.h b/openfpga/src/base/vpr_netlist_annotation.h new file mode 100644 index 000000000..3b8bbd16f --- /dev/null +++ b/openfpga/src/base/vpr_netlist_annotation.h @@ -0,0 +1,43 @@ +#ifndef VPR_NETLIST_ANNOTATION_H +#define VPR_NETLIST_ANNOTATION_H + +/******************************************************************** + * Include header files required by the data structure definition + *******************************************************************/ +#include + +/* Header from vpr library */ +#include "atom_netlist.h" + +/* Begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * This is the critical data structure to link the pb_type in VPR + * to openfpga annotations + * With a given pb_type pointer, it aims to identify: + * 1. if the pb_type is a physical pb_type or a operating pb_type + * 2. what is the circuit model id linked to a physical pb_type + * 3. what is the physical pb_type for an operating pb_type + * 4. what is the mode pointer that represents the physical mode for a pb_type + *******************************************************************/ +class VprNetlistAnnotation { + public: /* Constructor */ + VprNetlistAnnotation(); + public: /* Public accessors */ + bool is_block_renamed(const AtomBlockId& block) const; + std::string block_name(const AtomBlockId& block) const; + bool is_net_renamed(const AtomNetId& net) const; + std::string net_name(const AtomNetId& net) const; + public: /* Public mutators */ + void rename_block(const AtomBlockId& block, const std::string& name); + void rename_net(const AtomNetId& net, const std::string& name); + private: /* Internal data */ + /* Pair a regular pb_type to its physical pb_type */ + std::map block_names_; + std::map net_names_; +}; + +} /* End namespace openfpga*/ + +#endif diff --git a/openfpga/test_script/s298.openfpga b/openfpga/test_script/s298.openfpga index 08e4d9ce3..cafc16074 100644 --- a/openfpga/test_script/s298.openfpga +++ b/openfpga/test_script/s298.openfpga @@ -8,7 +8,7 @@ read_openfpga_arch -f ./test_openfpga_arch/k6_N10_40nm_openfpga.xml link_openfpga_arch # Check and correct any naming conflicts in the BLIF netlist -check_netlist_naming_conflict --correction --report ./netlist_renaming.rpt +check_netlist_naming_conflict --fix --report ./netlist_renaming.rpt # Finish and exit OpenFPGA exit From 87f1ca1151ebbb1216567f761f09eafd8b11acb1 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 29 Jan 2020 18:56:47 -0700 Subject: [PATCH 049/645] add naming fix-up report generation --- .../src/write_xml_openfpga_arch.cpp | 3 + .../base/check_netlist_naming_conflict.cpp | 99 ++++++++++++++++--- 2 files changed, 89 insertions(+), 13 deletions(-) diff --git a/libopenfpga/libarchopenfpga/src/write_xml_openfpga_arch.cpp b/libopenfpga/libarchopenfpga/src/write_xml_openfpga_arch.cpp index b71853156..eb999dcd1 100644 --- a/libopenfpga/libarchopenfpga/src/write_xml_openfpga_arch.cpp +++ b/libopenfpga/libarchopenfpga/src/write_xml_openfpga_arch.cpp @@ -66,4 +66,7 @@ void write_xml_openfpga_arch(const char* fname, /* Write the simulation */ write_xml_simulation_setting(fp, fname, openfpga_arch.sim_setting); + + /* Close the file stream */ + fp.close(); } diff --git a/openfpga/src/base/check_netlist_naming_conflict.cpp b/openfpga/src/base/check_netlist_naming_conflict.cpp index 57056712a..c3fc50d0b 100644 --- a/openfpga/src/base/check_netlist_naming_conflict.cpp +++ b/openfpga/src/base/check_netlist_naming_conflict.cpp @@ -4,12 +4,19 @@ * fabric generator, i.e., Verilog generator and SPICE generator *******************************************************************/ #include +#include /* Headers from vtrutil library */ #include "vtr_time.h" #include "vtr_assert.h" #include "vtr_log.h" +/* Headers from archopenfpga library */ +#include "write_xml_utils.h" + +/* Headers from openfpgautil library */ +#include "openfpga_digest.h" + #include "check_netlist_naming_conflict.h" /* Include global variables of VPR */ @@ -111,10 +118,10 @@ size_t detect_netlist_naming_conflict(const AtomNetlist& atom_netlist, * any sensitive character *******************************************************************/ static -void correct_netlist_naming_conflict(const AtomNetlist& atom_netlist, - const std::string& sensitive_chars, - const std::string& fix_chars, - VprNetlistAnnotation& vpr_netlist_annotation) { +void fix_netlist_naming_conflict(const AtomNetlist& atom_netlist, + const std::string& sensitive_chars, + const std::string& fix_chars, + VprNetlistAnnotation& vpr_netlist_annotation) { size_t num_fixes = 0; /* Walk through blocks in the netlist */ @@ -144,7 +151,62 @@ void correct_netlist_naming_conflict(const AtomNetlist& atom_netlist, VTR_LOG("Fixed %ld naming conflicts in the netlist.\n", num_fixes); } +} +/******************************************************************** + * Report all the fix-up in the naming of netlist components, + * i.e., blocks, nets + *******************************************************************/ +static +void print_netlist_naming_fix_report(const std::string& fname, + const AtomNetlist& atom_netlist, + const VprNetlistAnnotation& vpr_netlist_annotation) { + /* Create a file handler */ + std::fstream fp; + /* Open the file stream */ + fp.open(fname, std::fstream::out | std::fstream::trunc); + + /* Validate the file stream */ + openfpga::check_file_stream(fname.c_str(), fp); + + fp << " " << "\n"; + fp << "" << "\n"; + + fp << "\t" << "" << "\n"; + + for (const auto& block : atom_netlist.blocks()) { + const std::string& block_name = atom_netlist.block_name(block); + if (false == vpr_netlist_annotation.is_block_renamed(block)) { + continue; + } + fp << "\t\t" << "" << "\n"; + } + + fp << "\t" << "" << "\n"; + + fp << "\t" << "" << "\n"; + + for (const auto& net : atom_netlist.nets()) { + const std::string& net_name = atom_netlist.net_name(net); + if (false == vpr_netlist_annotation.is_net_renamed(net)) { + continue; + } + fp << "\t\t" << "" << "\n"; + } + + fp << "\t" << "" << "\n"; + + + fp << "" << "\n"; + + /* Close the file stream */ + fp.close(); } /******************************************************************** @@ -154,6 +216,8 @@ void correct_netlist_naming_conflict(const AtomNetlist& atom_netlist, *******************************************************************/ void check_netlist_naming_conflict(OpenfpgaContext& openfpga_context, const Command& cmd, const CommandContext& cmd_context) { + vtr::ScopedStartFinishTimer timer("Check naming violations of netlist blocks and nets"); + /* By default, we replace all the illegal characters with '_' */ const std::string& sensitive_chars(".,:;\'\"+-<>()[]{}!@#$%^&*~`?/"); const std::string& fix_chars("____________________________"); @@ -161,18 +225,27 @@ void check_netlist_naming_conflict(OpenfpgaContext& openfpga_context, CommandOptionId opt_fix = cmd.option("fix"); /* Do the main job first: detect any naming in the BLIF netlist that violates the syntax */ - size_t num_conflicts = detect_netlist_naming_conflict(g_vpr_ctx.atom().nlist, sensitive_chars); - VTR_LOGV_ERROR((0 < num_conflicts && (false == cmd_context.option_enable(cmd, opt_fix))), - "Found %ld naming conflicts in the netlist. Please correct so as to use any fabric generators.\n", - num_conflicts); - VTR_LOGV(0 == num_conflicts, - "Check naming conflicts in the netlist passed.\n"); - + if (false == cmd_context.option_enable(cmd, opt_fix)) { + size_t num_conflicts = detect_netlist_naming_conflict(g_vpr_ctx.atom().nlist, sensitive_chars); + VTR_LOGV_ERROR((0 < num_conflicts && (false == cmd_context.option_enable(cmd, opt_fix))), + "Found %ld naming conflicts in the netlist. Please correct so as to use any fabric generators.\n", + num_conflicts); + VTR_LOGV(0 == num_conflicts, + "Check naming conflicts in the netlist passed.\n"); + return; + } /* If the auto correction is enabled, we apply a fix */ if (true == cmd_context.option_enable(cmd, opt_fix)) { - correct_netlist_naming_conflict(g_vpr_ctx.atom().nlist, sensitive_chars, - fix_chars, openfpga_context.mutable_vpr_netlist_annotation()); + fix_netlist_naming_conflict(g_vpr_ctx.atom().nlist, sensitive_chars, + fix_chars, openfpga_context.mutable_vpr_netlist_annotation()); + + CommandOptionId opt_report = cmd.option("report"); + if (true == cmd_context.option_enable(cmd, opt_report)) { + print_netlist_naming_fix_report(cmd_context.option_value(cmd, opt_report), + g_vpr_ctx.atom().nlist, + openfpga_context.vpr_netlist_annotation()); + } } } From f28ca3ffd0c327f2fa92a08b28d443cbb285e47d Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 29 Jan 2020 18:58:57 -0700 Subject: [PATCH 050/645] add more echo to log --- openfpga/src/base/check_netlist_naming_conflict.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openfpga/src/base/check_netlist_naming_conflict.cpp b/openfpga/src/base/check_netlist_naming_conflict.cpp index c3fc50d0b..3470c9f0a 100644 --- a/openfpga/src/base/check_netlist_naming_conflict.cpp +++ b/openfpga/src/base/check_netlist_naming_conflict.cpp @@ -202,7 +202,6 @@ void print_netlist_naming_fix_report(const std::string& fname, fp << "\t" << "" << "\n"; - fp << "" << "\n"; /* Close the file stream */ @@ -245,6 +244,8 @@ void check_netlist_naming_conflict(OpenfpgaContext& openfpga_context, print_netlist_naming_fix_report(cmd_context.option_value(cmd, opt_report), g_vpr_ctx.atom().nlist, openfpga_context.vpr_netlist_annotation()); + VTR_LOG("Naming fix-up report is generated to file '%s'\n", + cmd_context.option_value(cmd, opt_report).c_str()); } } } From 568ed120c2a6784f4429eec40b30491ab50ad4af Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 29 Jan 2020 21:53:56 -0700 Subject: [PATCH 051/645] change report naming fix-up to be XML format --- openfpga/test_script/s298.openfpga | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openfpga/test_script/s298.openfpga b/openfpga/test_script/s298.openfpga index cafc16074..216f6d8c4 100644 --- a/openfpga/test_script/s298.openfpga +++ b/openfpga/test_script/s298.openfpga @@ -8,7 +8,7 @@ read_openfpga_arch -f ./test_openfpga_arch/k6_N10_40nm_openfpga.xml link_openfpga_arch # Check and correct any naming conflicts in the BLIF netlist -check_netlist_naming_conflict --fix --report ./netlist_renaming.rpt +check_netlist_naming_conflict --fix --report ./netlist_renaming.xml # Finish and exit OpenFPGA exit From e48ab8cb44a8964acde6866b8e44788db8bdc212 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 30 Jan 2020 13:37:41 -0700 Subject: [PATCH 052/645] move annotation source files to a separated folder --- openfpga/src/{base => annotation}/annotate_pb_graph.cpp | 0 openfpga/src/{base => annotation}/annotate_pb_graph.h | 0 openfpga/src/{base => annotation}/annotate_pb_types.cpp | 0 openfpga/src/{base => annotation}/annotate_pb_types.h | 0 .../src/{base => annotation}/check_netlist_naming_conflict.cpp | 0 openfpga/src/{base => annotation}/check_netlist_naming_conflict.h | 0 openfpga/src/{base => annotation}/check_pb_type_annotation.cpp | 0 openfpga/src/{base => annotation}/check_pb_type_annotation.h | 0 openfpga/src/{base => annotation}/vpr_netlist_annotation.cpp | 0 openfpga/src/{base => annotation}/vpr_netlist_annotation.h | 0 openfpga/src/{base => annotation}/vpr_pb_type_annotation.cpp | 0 openfpga/src/{base => annotation}/vpr_pb_type_annotation.h | 0 12 files changed, 0 insertions(+), 0 deletions(-) rename openfpga/src/{base => annotation}/annotate_pb_graph.cpp (100%) rename openfpga/src/{base => annotation}/annotate_pb_graph.h (100%) rename openfpga/src/{base => annotation}/annotate_pb_types.cpp (100%) rename openfpga/src/{base => annotation}/annotate_pb_types.h (100%) rename openfpga/src/{base => annotation}/check_netlist_naming_conflict.cpp (100%) rename openfpga/src/{base => annotation}/check_netlist_naming_conflict.h (100%) rename openfpga/src/{base => annotation}/check_pb_type_annotation.cpp (100%) rename openfpga/src/{base => annotation}/check_pb_type_annotation.h (100%) rename openfpga/src/{base => annotation}/vpr_netlist_annotation.cpp (100%) rename openfpga/src/{base => annotation}/vpr_netlist_annotation.h (100%) rename openfpga/src/{base => annotation}/vpr_pb_type_annotation.cpp (100%) rename openfpga/src/{base => annotation}/vpr_pb_type_annotation.h (100%) diff --git a/openfpga/src/base/annotate_pb_graph.cpp b/openfpga/src/annotation/annotate_pb_graph.cpp similarity index 100% rename from openfpga/src/base/annotate_pb_graph.cpp rename to openfpga/src/annotation/annotate_pb_graph.cpp diff --git a/openfpga/src/base/annotate_pb_graph.h b/openfpga/src/annotation/annotate_pb_graph.h similarity index 100% rename from openfpga/src/base/annotate_pb_graph.h rename to openfpga/src/annotation/annotate_pb_graph.h diff --git a/openfpga/src/base/annotate_pb_types.cpp b/openfpga/src/annotation/annotate_pb_types.cpp similarity index 100% rename from openfpga/src/base/annotate_pb_types.cpp rename to openfpga/src/annotation/annotate_pb_types.cpp diff --git a/openfpga/src/base/annotate_pb_types.h b/openfpga/src/annotation/annotate_pb_types.h similarity index 100% rename from openfpga/src/base/annotate_pb_types.h rename to openfpga/src/annotation/annotate_pb_types.h diff --git a/openfpga/src/base/check_netlist_naming_conflict.cpp b/openfpga/src/annotation/check_netlist_naming_conflict.cpp similarity index 100% rename from openfpga/src/base/check_netlist_naming_conflict.cpp rename to openfpga/src/annotation/check_netlist_naming_conflict.cpp diff --git a/openfpga/src/base/check_netlist_naming_conflict.h b/openfpga/src/annotation/check_netlist_naming_conflict.h similarity index 100% rename from openfpga/src/base/check_netlist_naming_conflict.h rename to openfpga/src/annotation/check_netlist_naming_conflict.h diff --git a/openfpga/src/base/check_pb_type_annotation.cpp b/openfpga/src/annotation/check_pb_type_annotation.cpp similarity index 100% rename from openfpga/src/base/check_pb_type_annotation.cpp rename to openfpga/src/annotation/check_pb_type_annotation.cpp diff --git a/openfpga/src/base/check_pb_type_annotation.h b/openfpga/src/annotation/check_pb_type_annotation.h similarity index 100% rename from openfpga/src/base/check_pb_type_annotation.h rename to openfpga/src/annotation/check_pb_type_annotation.h diff --git a/openfpga/src/base/vpr_netlist_annotation.cpp b/openfpga/src/annotation/vpr_netlist_annotation.cpp similarity index 100% rename from openfpga/src/base/vpr_netlist_annotation.cpp rename to openfpga/src/annotation/vpr_netlist_annotation.cpp diff --git a/openfpga/src/base/vpr_netlist_annotation.h b/openfpga/src/annotation/vpr_netlist_annotation.h similarity index 100% rename from openfpga/src/base/vpr_netlist_annotation.h rename to openfpga/src/annotation/vpr_netlist_annotation.h diff --git a/openfpga/src/base/vpr_pb_type_annotation.cpp b/openfpga/src/annotation/vpr_pb_type_annotation.cpp similarity index 100% rename from openfpga/src/base/vpr_pb_type_annotation.cpp rename to openfpga/src/annotation/vpr_pb_type_annotation.cpp diff --git a/openfpga/src/base/vpr_pb_type_annotation.h b/openfpga/src/annotation/vpr_pb_type_annotation.h similarity index 100% rename from openfpga/src/base/vpr_pb_type_annotation.h rename to openfpga/src/annotation/vpr_pb_type_annotation.h From d62c9fe86f85c307ed70576b357300de9c0600a2 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 30 Jan 2020 16:40:13 -0700 Subject: [PATCH 053/645] adding pb_graph_node annotation --- openfpga/src/annotation/annotate_pb_graph.cpp | 145 ++++++++++++++++++ openfpga/src/annotation/annotate_pb_graph.h | 3 + openfpga/src/annotation/annotate_pb_types.cpp | 22 ++- .../src/annotation/vpr_pb_type_annotation.cpp | 132 ++++++++++++++++ .../src/annotation/vpr_pb_type_annotation.h | 33 ++++ openfpga/src/base/openfpga_link_arch.cpp | 8 +- 6 files changed, 339 insertions(+), 4 deletions(-) diff --git a/openfpga/src/annotation/annotate_pb_graph.cpp b/openfpga/src/annotation/annotate_pb_graph.cpp index 82e214067..bb9ab08c8 100644 --- a/openfpga/src/annotation/annotate_pb_graph.cpp +++ b/openfpga/src/annotation/annotate_pb_graph.cpp @@ -118,5 +118,150 @@ void annotate_pb_graph_interconnect_physical_type(const DeviceContext& vpr_devic } } +/******************************************************************** + * This function will recursively walk through all the pb_graph nodes + * starting from a top node. + * It aims to give an unique index to each pb_graph node + * + * Therefore, the sequence in visiting the nodes is critical + * Here, we will follow the strategy where primitive nodes are visited first + *******************************************************************/ +static +void rec_build_vpr_primitive_pb_graph_node_unique_index(t_pb_graph_node* pb_graph_node, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + /* Go recursive first until we touch the primitive node */ + if (false == is_primitive_pb_type(pb_graph_node->pb_type)) { + for (int imode = 0; imode < pb_graph_node->pb_type->num_modes; ++imode) { + for (int ipb = 0; ipb < pb_graph_node->pb_type->modes[imode].num_pb_type_children; ++ipb) { + /* Each child may exist multiple times in the hierarchy*/ + for (int jpb = 0; jpb < pb_graph_node->pb_type->modes[imode].pb_type_children[ipb].num_pb; ++jpb) { + rec_build_vpr_primitive_pb_graph_node_unique_index(&(pb_graph_node->child_pb_graph_nodes[imode][ipb][jpb]), + vpr_pb_type_annotation); + } + } + } + return; + } + + /* Give a unique index to the pb_graph_node */ + vpr_pb_type_annotation.add_pb_graph_node_unique_index(pb_graph_node); +} + +/******************************************************************** + * This function aims to assign an unique index to each + * primitive pb_graph_node by following a recursive way in walking + * through the pb_graph + * + * Note: + * - The unique index is different from the placement_index in VPR's + * pb_graph_node data structure. The placement index is only unique + * for a node under its parent node. If the parent node is duplicated + * across the graph, the placement index is not unique. + * For example, a CLB contains 10 LEs and each of LE contains 2 LUTs + * Inside each LE, the placement index of the LUTs are 0 and 1 respectively. + * But these indices are not unique in the graph, as there are 20 LUTs in total + *******************************************************************/ +static +void annotate_primitive_pb_graph_node_unique_index(const DeviceContext& vpr_device_ctx, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { + /* By pass nullptr for pb_graph head */ + if (nullptr == lb_type.pb_graph_head) { + continue; + } + rec_build_vpr_primitive_pb_graph_node_unique_index(lb_type.pb_graph_head, vpr_pb_type_annotation); + } +} + +/******************************************************************** + * This function will recursively walk through all the pb_graph nodes + * starting from a top node. + * It aims to give an unique index to each pb_graph node + * + * Therefore, the sequence in visiting the nodes is critical + * Here, we will follow the strategy where primitive nodes are visited first + *******************************************************************/ +static +void rec_build_vpr_physical_pb_graph_node_annotation(t_pb_graph_node* pb_graph_node, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + /* Go recursive first until we touch the primitive node */ + if (false == is_primitive_pb_type(pb_graph_node->pb_type)) { + for (int imode = 0; imode < pb_graph_node->pb_type->num_modes; ++imode) { + for (int ipb = 0; ipb < pb_graph_node->pb_type->modes[imode].num_pb_type_children; ++ipb) { + /* Each child may exist multiple times in the hierarchy*/ + for (int jpb = 0; jpb < pb_graph_node->pb_type->modes[imode].pb_type_children[ipb].num_pb; ++jpb) { + rec_build_vpr_physical_pb_graph_node_annotation(&(pb_graph_node->child_pb_graph_nodes[imode][ipb][jpb]), + vpr_pb_type_annotation); + } + } + } + return; + } + + /* To bind operating pb_graph_node to their physical pb_graph_node: + * - Get the physical pb_type that this type of pb_graph_node should be mapped to + * - Calculate the unique index of physical pb_graph_node to which + * this pb_graph_node should be binded + * - Find the physical pb_graph_node with the given index + * To bind pins from operating pb_graph_node to their physical pb_graph_node pins + */ + t_pb_type* physical_pb_type = vpr_pb_type_annotation.physical_pb_type(pb_graph_node->pb_type); + VTR_ASSERT(nullptr != physical_pb_type); + + /* Index inference: + * physical_pb_graph_node_unique_index = operating_pb_graph_node_unique_index * factor + offset + * where factor and offset are provided by users + */ + PbGraphNodeId physical_pb_graph_node_id = PbGraphNodeId( + vpr_pb_type_annotation.physical_pb_type_index_factor(pb_graph_node->pb_type) + * (size_t)vpr_pb_type_annotation.pb_graph_node_unique_index(pb_graph_node) + + vpr_pb_type_annotation.physical_pb_type_index_offset(pb_graph_node->pb_type) + ); + t_pb_graph_node* physical_pb_graph_node = vpr_pb_type_annotation.pb_graph_node(pb_graph_node->pb_type, physical_pb_graph_node_id); + VTR_ASSERT(nullptr != physical_pb_graph_node); + vpr_pb_type_annotation.add_physical_pb_graph_node(pb_graph_node, physical_pb_graph_node); + + VTR_LOG("Bind operating pb_graph_node '%s' to physical pb_graph_node '%s'\n", + pb_graph_node->hierarchical_type_name().c_str(), + physical_pb_graph_node->hierarchical_type_name().c_str()); + + /* Try to bind each pins under this pb_graph_node to physical_pb_graph_node */ +} + +/******************************************************************** + * Find the physical pb_graph_node for each primitive pb_graph_node + * - Bind operating pb_graph_node to their physical pb_graph_node + * - Bind pins from operating pb_graph_node to their physical pb_graph_node pins + *******************************************************************/ +static +void annotate_physical_pb_graph_node(const DeviceContext& vpr_device_ctx, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { + /* By pass nullptr for pb_graph head */ + if (nullptr == lb_type.pb_graph_head) { + continue; + } + rec_build_vpr_physical_pb_graph_node_annotation(lb_type.pb_graph_head, vpr_pb_type_annotation); + } +} + +/******************************************************************** + * Top-level function to annotate all the pb_graph nodes and pins + * - Give unique index to each primitive node in the same type + * - Bind operating pb_graph_node to their physical pb_graph_node + * - Bind pins from operating pb_graph_node to their physical pb_graph_node pins + *******************************************************************/ +void annotate_pb_graph(const DeviceContext& vpr_device_ctx, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + + VTR_LOG("Assigning unique indices for primitive pb_graph nodes..."); + annotate_primitive_pb_graph_node_unique_index(vpr_device_ctx, vpr_pb_type_annotation); + VTR_LOG("Done\n"); + + VTR_LOG("Binding operating pb_graph nodes/pins to physical pb_graph nodes/pins...\n"); + annotate_physical_pb_graph_node(vpr_device_ctx, vpr_pb_type_annotation); + VTR_LOG("Done\n"); +} + } /* end namespace openfpga */ diff --git a/openfpga/src/annotation/annotate_pb_graph.h b/openfpga/src/annotation/annotate_pb_graph.h index 8749cd28f..e809d3cfa 100644 --- a/openfpga/src/annotation/annotate_pb_graph.h +++ b/openfpga/src/annotation/annotate_pb_graph.h @@ -18,6 +18,9 @@ namespace openfpga { void annotate_pb_graph_interconnect_physical_type(const DeviceContext& vpr_device_ctx, VprPbTypeAnnotation& vpr_pb_type_annotation); +void annotate_pb_graph(const DeviceContext& vpr_device_ctx, + VprPbTypeAnnotation& vpr_pb_type_annotation); + } /* end namespace openfpga */ #endif diff --git a/openfpga/src/annotation/annotate_pb_types.cpp b/openfpga/src/annotation/annotate_pb_types.cpp index 023154c9b..fb2905637 100644 --- a/openfpga/src/annotation/annotate_pb_types.cpp +++ b/openfpga/src/annotation/annotate_pb_types.cpp @@ -223,13 +223,24 @@ bool pair_operating_and_physical_pb_types(t_pb_type* operating_pb_type, if (false == expected_physical_pb_port.contained(BasicPort(physical_pb_port->name, physical_pb_port->num_pins))) { return false; } - /* Now, port mapping should succeed, we update the vpr_pb_type_annotation */ + /* Now, port mapping should succeed, we update the vpr_pb_type_annotation + * - port binding + * - port range + * - port pin rotate offset + */ vpr_pb_type_annotation.add_physical_pb_port(operating_pb_port, physical_pb_port); vpr_pb_type_annotation.add_physical_pb_port_range(operating_pb_port, expected_physical_pb_port); + vpr_pb_type_annotation.add_physical_pb_pin_rotate_offset(operating_pb_port, pb_type_annotation.physical_pin_rotate_offset(std::string(operating_pb_port->name))); } - /* Now, pb_type mapping should succeed, we update the vpr_pb_type_annotation */ + /* Now, pb_type mapping should succeed, we update the vpr_pb_type_annotation + * - pb_type binding + * - physical_pb_type_index_factor + * - physical_pb_type_index_offset + */ vpr_pb_type_annotation.add_physical_pb_type(operating_pb_type, physical_pb_type); + vpr_pb_type_annotation.add_physical_pb_type_index_factor(operating_pb_type, pb_type_annotation.physical_pb_type_index_factor()); + vpr_pb_type_annotation.add_physical_pb_type_index_offset(operating_pb_type, pb_type_annotation.physical_pb_type_index_offset()); return true; } @@ -987,7 +998,12 @@ void annotate_pb_types(const DeviceContext& vpr_device_ctx, vpr_pb_type_annotation); VTR_LOG("Done\n"); - /* Annotate physical pb_types to operating pb_type in the VPR pb_type graph */ + /* Annotate physical pb_types to operating pb_type in the VPR pb_type graph + * This function will also annotate + * - pb_type_index_factor + * - pb_type_index_offset + * - physical_pin_rotate_offset + */ VTR_LOG("\n"); VTR_LOG("Building annotation between operating and physical pb_types...\n"); build_vpr_physical_pb_type_explicit_annotation(vpr_device_ctx, openfpga_arch, diff --git a/openfpga/src/annotation/vpr_pb_type_annotation.cpp b/openfpga/src/annotation/vpr_pb_type_annotation.cpp index 435f46be7..407907e59 100644 --- a/openfpga/src/annotation/vpr_pb_type_annotation.cpp +++ b/openfpga/src/annotation/vpr_pb_type_annotation.cpp @@ -1,6 +1,8 @@ /************************************************************************ * Member functions for class VprPbTypeAnnotation ***********************************************************************/ +#include + #include "vtr_log.h" #include "vtr_assert.h" #include "vpr_pb_type_annotation.h" @@ -115,6 +117,84 @@ std::vector VprPbTypeAnnotation::pb_type_mode_bits(t_pb_type* pb_type) c return pb_type_mode_bits_.at(pb_type); } +PbGraphNodeId VprPbTypeAnnotation::pb_graph_node_unique_index(t_pb_graph_node* pb_graph_node) const { + /* Ensure that the pb_type is in the list */ + std::map>::const_iterator it = pb_graph_node_unique_index_.find(pb_graph_node->pb_type); + if (it == pb_graph_node_unique_index_.end()) { + /* Invalid pb_type, return a null pointer */ + return PbGraphNodeId::INVALID(); + } + + /* Try to find the pb_graph_node in the vector */ + std::vector::const_iterator it_node = std::find(pb_graph_node_unique_index_.at(pb_graph_node->pb_type).begin(), + pb_graph_node_unique_index_.at(pb_graph_node->pb_type).end(), + pb_graph_node); + /* If it exists, return the index + * Otherwise, return an invalid id + */ + if (it_node == pb_graph_node_unique_index_.at(pb_graph_node->pb_type).end()) { + return PbGraphNodeId::INVALID(); + } + return PbGraphNodeId(it_node - pb_graph_node_unique_index_.at(pb_graph_node->pb_type).begin()); +} + +t_pb_graph_node* VprPbTypeAnnotation::pb_graph_node(t_pb_type* pb_type, const PbGraphNodeId& unique_index) const { + /* Ensure that the pb_type is in the list */ + std::map>::const_iterator it = pb_graph_node_unique_index_.find(pb_type); + if (it == pb_graph_node_unique_index_.end()) { + /* Invalid pb_type, return a null pointer */ + return nullptr; + } + /* Check if the unique index is in the range: + * - Out of range: return a null pointer + * - In range: return the pointer + */ + if ((size_t)unique_index > pb_graph_node_unique_index_.at(pb_type).size() - 1) { + return nullptr; + } + + return pb_graph_node_unique_index_.at(pb_type)[size_t(unique_index)]; +} + +t_pb_graph_node* VprPbTypeAnnotation::physical_pb_graph_node(t_pb_graph_node* pb_graph_node) const { + /* Ensure that the pb_graph_node is in the list */ + std::map::const_iterator it = physical_pb_graph_nodes_.find(pb_graph_node); + if (it == physical_pb_graph_nodes_.end()) { + return nullptr; + } + return physical_pb_graph_nodes_.at(pb_graph_node); +} + +int VprPbTypeAnnotation::physical_pb_type_index_factor(t_pb_type* pb_type) const { + /* Ensure that the pb_type is in the list */ + std::map::const_iterator it = physical_pb_type_index_factors_.find(pb_type); + if (it == physical_pb_type_index_factors_.end()) { + /* Default value is 1 */ + return 1; + } + return physical_pb_type_index_factors_.at(pb_type); +} + +int VprPbTypeAnnotation::physical_pb_type_index_offset(t_pb_type* pb_type) const { + /* Ensure that the pb_type is in the list */ + std::map::const_iterator it = physical_pb_type_index_offsets_.find(pb_type); + if (it == physical_pb_type_index_offsets_.end()) { + /* Default value is 0 */ + return 0; + } + return physical_pb_type_index_offsets_.at(pb_type); +} + +int VprPbTypeAnnotation::physical_pb_pin_rotate_offset(t_port* pb_port) const { + /* Ensure that the pb_type is in the list */ + std::map::const_iterator it = physical_pb_pin_rotate_offsets_.find(pb_port); + if (it == physical_pb_pin_rotate_offsets_.end()) { + /* Default value is 0 */ + return 0; + } + return physical_pb_pin_rotate_offsets_.at(pb_port); +} + /************************************************************************ * Public mutators ***********************************************************************/ @@ -221,4 +301,56 @@ void VprPbTypeAnnotation::add_pb_type_mode_bits(t_pb_type* pb_type, const std::v pb_type_mode_bits_[pb_type] = mode_bits; } +void VprPbTypeAnnotation::add_pb_graph_node_unique_index(t_pb_graph_node* pb_graph_node) { + pb_graph_node_unique_index_[pb_graph_node->pb_type].push_back(pb_graph_node); +} + +void VprPbTypeAnnotation::add_physical_pb_graph_node(t_pb_graph_node* operating_pb_graph_node, + t_pb_graph_node* physical_pb_graph_node) { + /* Warn any override attempt */ + std::map::const_iterator it = physical_pb_graph_nodes_.find(operating_pb_graph_node); + if (it != physical_pb_graph_nodes_.end()) { + VTR_LOG_WARN("Override the annotation between operating pb_graph_node '%s[%d]' and it physical pb_graph_node '%s[%d]'!\n", + operating_pb_graph_node->pb_type->name, + operating_pb_graph_node->placement_index, + physical_pb_graph_node->pb_type->name, + physical_pb_graph_node->placement_index); + } + + physical_pb_graph_nodes_[operating_pb_graph_node] = physical_pb_graph_node; +} + +void VprPbTypeAnnotation::add_physical_pb_type_index_factor(t_pb_type* pb_type, const int& factor) { + /* Warn any override attempt */ + std::map::const_iterator it = physical_pb_type_index_factors_.find(pb_type); + if (it != physical_pb_type_index_factors_.end()) { + VTR_LOG_WARN("Override the annotation between operating pb_type '%s' and it physical pb_type index factor '%d'!\n", + pb_type->name, factor); + } + + physical_pb_type_index_factors_[pb_type] = factor; +} + +void VprPbTypeAnnotation::add_physical_pb_type_index_offset(t_pb_type* pb_type, const int& offset) { + /* Warn any override attempt */ + std::map::const_iterator it = physical_pb_type_index_offsets_.find(pb_type); + if (it != physical_pb_type_index_offsets_.end()) { + VTR_LOG_WARN("Override the annotation between operating pb_type '%s' and it physical pb_type index offset '%d'!\n", + pb_type->name, offset); + } + + physical_pb_type_index_offsets_[pb_type] = offset; +} + +void VprPbTypeAnnotation::add_physical_pb_pin_rotate_offset(t_port* pb_port, const int& offset) { + /* Warn any override attempt */ + std::map::const_iterator it = physical_pb_pin_rotate_offsets_.find(pb_port); + if (it != physical_pb_pin_rotate_offsets_.end()) { + VTR_LOG_WARN("Override the annotation between operating pb_port '%s' and it physical pb_port pin rotate offset '%d'!\n", + pb_port->name, offset); + } + + physical_pb_pin_rotate_offsets_[pb_port] = offset; +} + } /* End namespace openfpga*/ diff --git a/openfpga/src/annotation/vpr_pb_type_annotation.h b/openfpga/src/annotation/vpr_pb_type_annotation.h index 24535ee4a..bcdff9e3e 100644 --- a/openfpga/src/annotation/vpr_pb_type_annotation.h +++ b/openfpga/src/annotation/vpr_pb_type_annotation.h @@ -6,6 +6,9 @@ *******************************************************************/ #include +/* Header from vtrutil library */ +#include "vtr_strong_id.h" + /* Header from archfpga library */ #include "physical_types.h" @@ -16,6 +19,11 @@ /* Begin namespace openfpga */ namespace openfpga { +/* Unique index for pb_graph node */ +struct pb_graph_node_id_tag; + +typedef vtr::StrongId PbGraphNodeId; + /******************************************************************** * This is the critical data structure to link the pb_type in VPR * to openfpga annotations @@ -39,6 +47,14 @@ class VprPbTypeAnnotation { e_interconnect interconnect_physical_type(t_interconnect* pb_interconnect) const; CircuitPortId pb_circuit_port(t_port* pb_port) const; std::vector pb_type_mode_bits(t_pb_type* pb_type) const; + /* Get the unique index of a pb_graph_node */ + PbGraphNodeId pb_graph_node_unique_index(t_pb_graph_node* pb_graph_node) const; + /* Get the pointer to a pb_graph node using an unique index */ + t_pb_graph_node* pb_graph_node(t_pb_type* pb_type, const PbGraphNodeId& unique_index) const; + t_pb_graph_node* physical_pb_graph_node(t_pb_graph_node* pb_graph_node) const; + int physical_pb_type_index_factor(t_pb_type* pb_type) const; + int physical_pb_type_index_offset(t_pb_type* pb_type) const; + int physical_pb_pin_rotate_offset(t_port* pb_port) const; public: /* Public mutators */ void add_pb_type_physical_mode(t_pb_type* pb_type, t_mode* physical_mode); void add_physical_pb_type(t_pb_type* operating_pb_type, t_pb_type* physical_pb_type); @@ -49,9 +65,17 @@ class VprPbTypeAnnotation { void add_interconnect_physical_type(t_interconnect* pb_interconnect, const e_interconnect& physical_type); void add_pb_circuit_port(t_port* pb_port, const CircuitPortId& circuit_port); void add_pb_type_mode_bits(t_pb_type* pb_type, const std::vector& mode_bits); + void add_pb_graph_node_unique_index(t_pb_graph_node* pb_graph_node); + void add_physical_pb_graph_node(t_pb_graph_node* operating_pb_graph_node, + t_pb_graph_node* physical_pb_graph_node); + void add_physical_pb_type_index_factor(t_pb_type* pb_type, const int& factor); + void add_physical_pb_type_index_offset(t_pb_type* pb_type, const int& offset); + void add_physical_pb_pin_rotate_offset(t_port* pb_port, const int& offset); private: /* Internal data */ /* Pair a regular pb_type to its physical pb_type */ std::map physical_pb_types_; + std::map physical_pb_type_index_factors_; + std::map physical_pb_type_index_offsets_; /* Pair a physical mode for a pb_type * Note: @@ -91,6 +115,7 @@ class VprPbTypeAnnotation { * - the parent of physical pb_port MUST be a physical pb_type */ std::map physical_pb_ports_; + std::map physical_pb_pin_rotate_offsets_; /* Pair a pb_port to its LSB and MSB of a physical pb_port * Note: @@ -104,11 +129,19 @@ class VprPbTypeAnnotation { */ std::map pb_circuit_ports_; + /* Pair each pb_graph_node to an unique index in the graph + * The unique index if the index in the array of t_pb_graph_node* + */ + std::map> pb_graph_node_unique_index_; + /* Pair a pb_graph_node to a physical pb_graph_node * Note: * - the pb_type of physical pb_graph_node must be a physical pb_type */ std::map physical_pb_graph_nodes_; + + /* Pair a pb_graph_pin to a physical pb_graph_pin */ + std::map physical_pb_graph_pins_; }; } /* End namespace openfpga*/ diff --git a/openfpga/src/base/openfpga_link_arch.cpp b/openfpga/src/base/openfpga_link_arch.cpp index a30453d1c..e6762485e 100644 --- a/openfpga/src/base/openfpga_link_arch.cpp +++ b/openfpga/src/base/openfpga_link_arch.cpp @@ -39,7 +39,13 @@ void link_arch(OpenfpgaContext& openfpga_context) { annotate_pb_types(g_vpr_ctx.device(), openfpga_context.arch(), openfpga_context.mutable_vpr_pb_type_annotation()); - /* Link routing architecture to circuit model */ + /* Annotate pb_graph_nodes + * - Give unique index to each node in the same type + * - Bind operating pb_graph_node to their physical pb_graph_node + * - Bind pins from operating pb_graph_node to their physical pb_graph_node pins + */ + annotate_pb_graph(g_vpr_ctx.device(), + openfpga_context.mutable_vpr_pb_type_annotation()); } } /* end namespace openfpga */ From 007e1997e63e2a6b0b028a009e1f64f4262dcb15 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 30 Jan 2020 19:40:40 -0700 Subject: [PATCH 054/645] add pb_graph pin annotation --- openfpga/src/annotation/annotate_pb_graph.cpp | 167 ++++++++++++++++++ .../src/annotation/vpr_pb_type_annotation.cpp | 59 +++++++ .../src/annotation/vpr_pb_type_annotation.h | 15 ++ 3 files changed, 241 insertions(+) diff --git a/openfpga/src/annotation/annotate_pb_graph.cpp b/openfpga/src/annotation/annotate_pb_graph.cpp index bb9ab08c8..fb94bc238 100644 --- a/openfpga/src/annotation/annotate_pb_graph.cpp +++ b/openfpga/src/annotation/annotate_pb_graph.cpp @@ -173,6 +173,172 @@ void annotate_primitive_pb_graph_node_unique_index(const DeviceContext& vpr_devi } } +/******************************************************************** + * Evaluate if the two pb_graph pins are matched by + * - pb_type port annotation + * - LSB/MSB and pin offset + *******************************************************************/ +static +bool try_match_pb_graph_pin(t_pb_graph_pin* operating_pb_graph_pin, + t_pb_graph_pin* physical_pb_graph_pin, + const VprPbTypeAnnotation& vpr_pb_type_annotation) { + /* If the parent ports of the two pins are not paired, fail */ + if (physical_pb_graph_pin->port != vpr_pb_type_annotation.physical_pb_port(operating_pb_graph_pin->port)) { + return false; + } + /* Check the pin number of physical pb_graph_pin matches the pin number of + * operating pb_graph_pin plus a rotation offset + * operating port physical port + * LSB port_range.lsb() pin_number pin_number MSB + * | | | + * Operating port | | +------ | + * | |<----offset--->| + * Physical port | + + + + * + * Note: + * - accumulated offset is NOT the pin rotate offset specified by users + * It is an aggregation of the offset during pin pairing + * Each time, we manage to pair two pins, the accumulated offset will be incremented + * by the pin rotate offset value + * The accumulated offset will be reset to 0 when it exceeds the msb() of the physical port + */ + int acc_offset = vpr_pb_type_annotation.physical_pb_pin_offset(operating_pb_graph_pin->port); + const BasicPort& physical_port_range = vpr_pb_type_annotation.physical_pb_port_range(operating_pb_graph_pin->port); + if (physical_pb_graph_pin->pin_number != operating_pb_graph_pin->pin_number + + (int)physical_port_range.get_lsb() + + acc_offset) { + return false; + } + + /* Reach here, it means all the requirements have been met */ + return true; +} + +/******************************************************************** + * Bind a pb_graph_pin from an operating pb_graph_node to + * a pb_graph_pin from a physical pb_graph_node + * - the name matching rules are already defined in the vpr_pb_type_annotation + *******************************************************************/ +static +void annotate_physical_pb_graph_pin(t_pb_graph_pin* operating_pb_graph_pin, + t_pb_graph_node* physical_pb_graph_node, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + /* Iterate over every port and pin of the operating pb_graph_node + * and find the physical pins + */ + for (int iport = 0; iport < physical_pb_graph_node->num_input_ports; ++iport) { + for (int ipin = 0; ipin < physical_pb_graph_node->num_input_pins[iport]; ++ipin) { + if (false == try_match_pb_graph_pin(operating_pb_graph_pin, + &(physical_pb_graph_node->input_pins[iport][ipin]), + vpr_pb_type_annotation)) { + continue; + } + /* Reach here, it means the pins are matched by the annotation requirements + * We can pair the pin and return + */ + vpr_pb_type_annotation.add_physical_pb_graph_pin(operating_pb_graph_pin, &(physical_pb_graph_node->input_pins[iport][ipin])); + VTR_LOG("Bind a pb_graph_node '%s[%d]' pin '%s[%d]' to a pb_graph_node '%s[%d]' pin '%s[%d]'!\n", + operating_pb_graph_pin->parent_node->pb_type->name, + operating_pb_graph_pin->parent_node->placement_index, + operating_pb_graph_pin->port->name, + operating_pb_graph_pin->pin_number, + physical_pb_graph_node->pb_type->name, + physical_pb_graph_node->placement_index, + physical_pb_graph_node->input_pins[iport][ipin].port->name, + physical_pb_graph_node->input_pins[iport][ipin].pin_number); + return; + } + } + + for (int iport = 0; iport < physical_pb_graph_node->num_output_ports; ++iport) { + for (int ipin = 0; ipin < physical_pb_graph_node->num_output_pins[iport]; ++ipin) { + if (false == try_match_pb_graph_pin(operating_pb_graph_pin, + &(physical_pb_graph_node->output_pins[iport][ipin]), + vpr_pb_type_annotation)) { + continue; + } + /* Reach here, it means the pins are matched by the annotation requirements + * We can pair the pin and return + */ + vpr_pb_type_annotation.add_physical_pb_graph_pin(operating_pb_graph_pin, &(physical_pb_graph_node->output_pins[iport][ipin])); + VTR_LOG("Bind a pb_graph_node '%s[%d]' pin '%s[%d]' to a pb_graph_node '%s[%d]' pin '%s[%d]'!\n", + operating_pb_graph_pin->parent_node->pb_type->name, + operating_pb_graph_pin->parent_node->placement_index, + operating_pb_graph_pin->port->name, + operating_pb_graph_pin->pin_number, + physical_pb_graph_node->pb_type->name, + physical_pb_graph_node->placement_index, + physical_pb_graph_node->output_pins[iport][ipin].port->name, + physical_pb_graph_node->output_pins[iport][ipin].pin_number); + return; + } + } + + for (int iport = 0; iport < physical_pb_graph_node->num_clock_ports; ++iport) { + for (int ipin = 0; ipin < physical_pb_graph_node->num_clock_pins[iport]; ++ipin) { + if (false == try_match_pb_graph_pin(operating_pb_graph_pin, + &(physical_pb_graph_node->clock_pins[iport][ipin]), + vpr_pb_type_annotation)) { + continue; + } + /* Reach here, it means the pins are matched by the annotation requirements + * We can pair the pin and return + */ + vpr_pb_type_annotation.add_physical_pb_graph_pin(operating_pb_graph_pin, &(physical_pb_graph_node->clock_pins[iport][ipin])); + VTR_LOG("Bind a pb_graph_node '%s[%d]' pin '%s[%d]' to a pb_graph_node '%s[%d]' pin '%s[%d]'!\n", + operating_pb_graph_pin->parent_node->pb_type->name, + operating_pb_graph_pin->parent_node->placement_index, + operating_pb_graph_pin->port->name, + operating_pb_graph_pin->pin_number, + physical_pb_graph_node->pb_type->name, + physical_pb_graph_node->placement_index, + physical_pb_graph_node->clock_pins[iport][ipin].port->name, + physical_pb_graph_node->clock_pins[iport][ipin].pin_number); + return; + } + } + + /* If we reach here, it means that pin pairing fails, error out! */ + VTR_LOG_ERROR("Fail to match a physical pin for '%s' from pb_graph_node '%s'!\n", + operating_pb_graph_pin->port->name, + physical_pb_graph_node->hierarchical_type_name().c_str()); +} + +/******************************************************************** + * This function will try bind each pin of the operating pb_graph_node + * to a pin of the physical pb_graph_node by following the annotation + * available in vpr_pb_type_annotation + * It will add the pin bindings to the vpr_pb_type_annotation + *******************************************************************/ +static +void annotate_physical_pb_graph_node_pins(t_pb_graph_node* operating_pb_graph_node, + t_pb_graph_node* physical_pb_graph_node, + VprPbTypeAnnotation& vpr_pb_type_annotation) { + /* Iterate over every port and pin of the operating pb_graph_node + * and find the physical pins + */ + for (int iport = 0; iport < operating_pb_graph_node->num_input_ports; ++iport) { + for (int ipin = 0; ipin < operating_pb_graph_node->num_input_pins[iport]; ++ipin) { + annotate_physical_pb_graph_pin(&(operating_pb_graph_node->input_pins[iport][ipin]), + physical_pb_graph_node, vpr_pb_type_annotation); + } + } + + for (int iport = 0; iport < operating_pb_graph_node->num_output_ports; ++iport) { + for (int ipin = 0; ipin < operating_pb_graph_node->num_output_pins[iport]; ++ipin) { + annotate_physical_pb_graph_pin(&(operating_pb_graph_node->output_pins[iport][ipin]), + physical_pb_graph_node, vpr_pb_type_annotation); + } + } + + for (int iport = 0; iport < operating_pb_graph_node->num_clock_ports; ++iport) { + for (int ipin = 0; ipin < operating_pb_graph_node->num_clock_pins[iport]; ++ipin) { + annotate_physical_pb_graph_pin(&(operating_pb_graph_node->clock_pins[iport][ipin]), + physical_pb_graph_node, vpr_pb_type_annotation); + } + } +} + /******************************************************************** * This function will recursively walk through all the pb_graph nodes * starting from a top node. @@ -226,6 +392,7 @@ void rec_build_vpr_physical_pb_graph_node_annotation(t_pb_graph_node* pb_graph_n physical_pb_graph_node->hierarchical_type_name().c_str()); /* Try to bind each pins under this pb_graph_node to physical_pb_graph_node */ + annotate_physical_pb_graph_node_pins(pb_graph_node, physical_pb_graph_node, vpr_pb_type_annotation); } /******************************************************************** diff --git a/openfpga/src/annotation/vpr_pb_type_annotation.cpp b/openfpga/src/annotation/vpr_pb_type_annotation.cpp index 407907e59..3204dd597 100644 --- a/openfpga/src/annotation/vpr_pb_type_annotation.cpp +++ b/openfpga/src/annotation/vpr_pb_type_annotation.cpp @@ -195,6 +195,26 @@ int VprPbTypeAnnotation::physical_pb_pin_rotate_offset(t_port* pb_port) const { return physical_pb_pin_rotate_offsets_.at(pb_port); } +int VprPbTypeAnnotation::physical_pb_pin_offset(t_port* pb_port) const { + /* Ensure that the pb_type is in the list */ + std::map::const_iterator it = physical_pb_pin_offsets_.find(pb_port); + if (it == physical_pb_pin_offsets_.end()) { + /* Default value is 0 */ + return 0; + } + return physical_pb_pin_offsets_.at(pb_port); +} + + +t_pb_graph_pin* VprPbTypeAnnotation::physical_pb_graph_pin(t_pb_graph_pin* pb_graph_pin) const { + /* Ensure that the pb_type is in the list */ + std::map::const_iterator it = physical_pb_graph_pins_.find(pb_graph_pin); + if (it == physical_pb_graph_pins_.end()) { + return nullptr; + } + return physical_pb_graph_pins_.at(pb_graph_pin); +} + /************************************************************************ * Public mutators ***********************************************************************/ @@ -351,6 +371,45 @@ void VprPbTypeAnnotation::add_physical_pb_pin_rotate_offset(t_port* pb_port, con } physical_pb_pin_rotate_offsets_[pb_port] = offset; + /* We initialize the accumulated offset to 0 */ + physical_pb_pin_offsets_[pb_port] = 0; +} + +void VprPbTypeAnnotation::add_physical_pb_graph_pin(t_pb_graph_pin* operating_pb_graph_pin, + t_pb_graph_pin* physical_pb_graph_pin) { + /* Warn any override attempt */ + std::map::const_iterator it = physical_pb_graph_pins_.find(operating_pb_graph_pin); + if (it != physical_pb_graph_pins_.end()) { + VTR_LOG_WARN("Override the annotation between operating pb_graph_pin '%s' and it physical pb_graph_pin '%s'!\n", + operating_pb_graph_pin->port->name, physical_pb_graph_pin->port->name); + } + + physical_pb_graph_pins_[operating_pb_graph_pin] = physical_pb_graph_pin; + + /* Update the accumulated offsets for the operating port + * Each time we pair two pins, we update the offset by the pin rotate offset + * When the accumulated offset exceeds the MSB of the port range of physical port + * we reset it to 0 + * operating port physical port + * LSB port_range.lsb() pin_number pin_number MSB + * | | | + * Operating port | | +------ | + * | |<----offset--->| + * Physical port | + + + + * + */ + if (0 == physical_pb_pin_rotate_offset(operating_pb_graph_pin->port)) { + return; + } + + physical_pb_pin_offsets_[operating_pb_graph_pin->port] += physical_pb_pin_rotate_offset(operating_pb_graph_pin->port); + + if (physical_pb_port_range(operating_pb_graph_pin->port).get_msb() + < operating_pb_graph_pin->pin_number + + physical_pb_port_range(operating_pb_graph_pin->port).get_lsb() + + physical_pb_pin_offset(operating_pb_graph_pin->port)) { + physical_pb_pin_offsets_[operating_pb_graph_pin->port] = 0; + } } } /* End namespace openfpga*/ diff --git a/openfpga/src/annotation/vpr_pb_type_annotation.h b/openfpga/src/annotation/vpr_pb_type_annotation.h index bcdff9e3e..add45e42d 100644 --- a/openfpga/src/annotation/vpr_pb_type_annotation.h +++ b/openfpga/src/annotation/vpr_pb_type_annotation.h @@ -54,7 +54,18 @@ class VprPbTypeAnnotation { t_pb_graph_node* physical_pb_graph_node(t_pb_graph_node* pb_graph_node) const; int physical_pb_type_index_factor(t_pb_type* pb_type) const; int physical_pb_type_index_offset(t_pb_type* pb_type) const; + int physical_pb_pin_rotate_offset(t_port* pb_port) const; + + /**This function returns an accumulated offset. Note that the + * accumulated offset is NOT the pin rotate offset specified by users + * It is an aggregation of the offset during pin pairing + * Each time, we manage to pair two pins, the accumulated offset will be incremented + * by the pin rotate offset value + * The accumulated offset will be reset to 0 when it exceeds the msb() of the physical port + */ + int physical_pb_pin_offset(t_port* pb_port) const; + t_pb_graph_pin* physical_pb_graph_pin(t_pb_graph_pin* pb_graph_pin) const; public: /* Public mutators */ void add_pb_type_physical_mode(t_pb_type* pb_type, t_mode* physical_mode); void add_physical_pb_type(t_pb_type* operating_pb_type, t_pb_type* physical_pb_type); @@ -71,6 +82,7 @@ class VprPbTypeAnnotation { void add_physical_pb_type_index_factor(t_pb_type* pb_type, const int& factor); void add_physical_pb_type_index_offset(t_pb_type* pb_type, const int& offset); void add_physical_pb_pin_rotate_offset(t_port* pb_port, const int& offset); + void add_physical_pb_graph_pin(t_pb_graph_pin* operating_pb_graph_pin, t_pb_graph_pin* physical_pb_graph_pin); private: /* Internal data */ /* Pair a regular pb_type to its physical pb_type */ std::map physical_pb_types_; @@ -117,6 +129,9 @@ class VprPbTypeAnnotation { std::map physical_pb_ports_; std::map physical_pb_pin_rotate_offsets_; + /* Accumulated offsets for a physical pb_type port, just for internal usage */ + std::map physical_pb_pin_offsets_; + /* Pair a pb_port to its LSB and MSB of a physical pb_port * Note: * - the LSB and MSB MUST be in range of the physical pb_port From 02d6256e9559bc8b4af403b8d6b63a797419c9e0 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 30 Jan 2020 21:39:44 -0700 Subject: [PATCH 055/645] pass simple test on pb_type annotation for frac_lut5 architecture --- openfpga/src/annotation/annotate_pb_graph.cpp | 9 +- openfpga/src/annotation/annotate_pb_types.cpp | 3 +- .../k6_frac_N10_40nm_openfpga.xml | 265 ++++++++++++++++++ openfpga/test_script/s298_k6_frac.openfpga | 14 + openfpga/test_vpr_arch/k6_frac_N10_40nm.xml | 87 ++++++ 5 files changed, 375 insertions(+), 3 deletions(-) create mode 100644 openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml create mode 100644 openfpga/test_script/s298_k6_frac.openfpga diff --git a/openfpga/src/annotation/annotate_pb_graph.cpp b/openfpga/src/annotation/annotate_pb_graph.cpp index fb94bc238..92f1b90b2 100644 --- a/openfpga/src/annotation/annotate_pb_graph.cpp +++ b/openfpga/src/annotation/annotate_pb_graph.cpp @@ -73,6 +73,13 @@ void rec_build_vpr_pb_graph_interconnect_physical_type_annotation(t_pb_graph_nod /* For each interconnect that has more than 1 input, we can infer the physical type */ for (t_interconnect* interc : pb_mode_interconnects(child_physical_mode)) { + /* If the number inputs for an interconnect is zero, this is a 0-driver pin + * we just set 1 to use direct wires + */ + if (0 == interc_num_inputs[interc]) { + interc_num_inputs[interc] = 1; + } + e_interconnect interc_physical_type = pb_interconnect_physical_type(interc, interc_num_inputs[interc]); if (interc_physical_type == vpr_pb_type_annotation.interconnect_physical_type(interc)) { /* Skip annotation if we have already done! */ @@ -383,7 +390,7 @@ void rec_build_vpr_physical_pb_graph_node_annotation(t_pb_graph_node* pb_graph_n * (size_t)vpr_pb_type_annotation.pb_graph_node_unique_index(pb_graph_node) + vpr_pb_type_annotation.physical_pb_type_index_offset(pb_graph_node->pb_type) ); - t_pb_graph_node* physical_pb_graph_node = vpr_pb_type_annotation.pb_graph_node(pb_graph_node->pb_type, physical_pb_graph_node_id); + t_pb_graph_node* physical_pb_graph_node = vpr_pb_type_annotation.pb_graph_node(physical_pb_type, physical_pb_graph_node_id); VTR_ASSERT(nullptr != physical_pb_graph_node); vpr_pb_type_annotation.add_physical_pb_graph_node(pb_graph_node, physical_pb_graph_node); diff --git a/openfpga/src/annotation/annotate_pb_types.cpp b/openfpga/src/annotation/annotate_pb_types.cpp index fb2905637..8dc89251a 100644 --- a/openfpga/src/annotation/annotate_pb_types.cpp +++ b/openfpga/src/annotation/annotate_pb_types.cpp @@ -220,7 +220,7 @@ bool pair_operating_and_physical_pb_types(t_pb_type* operating_pb_type, return false; } /* If the port range does not match, mapping fails */ - if (false == expected_physical_pb_port.contained(BasicPort(physical_pb_port->name, physical_pb_port->num_pins))) { + if (false == BasicPort(physical_pb_port->name, physical_pb_port->num_pins).contained(expected_physical_pb_port)) { return false; } /* Now, port mapping should succeed, we update the vpr_pb_type_annotation @@ -337,7 +337,6 @@ void build_vpr_physical_pb_type_explicit_annotation(const DeviceContext& vpr_dev VTR_LOG_ERROR("Unable to pair the operating pb_type '%s' to its physical pb_type '%s'!\n", target_op_pb_type_names.back().c_str(), target_phy_pb_type_names.back().c_str()); - return; } } } diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml new file mode 100644 index 000000000..b517fa24b --- /dev/null +++ b/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml @@ -0,0 +1,265 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + + + + 10e-12 5e-12 + + + 10e-12 5e-12 + + + + + + + + + + + + 10e-12 5e-12 5e-12 + + + 10e-12 5e-12 5e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga/test_script/s298_k6_frac.openfpga b/openfpga/test_script/s298_k6_frac.openfpga new file mode 100644 index 000000000..22e74cdb1 --- /dev/null +++ b/openfpga/test_script/s298_k6_frac.openfpga @@ -0,0 +1,14 @@ +# Run VPR for the s298 design +vpr ./test_vpr_arch/k6_frac_N10_40nm.xml ./test_blif/s298.blif + +# Read OpenFPGA architecture definition +read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml + +# Annotate the OpenFPGA architecture to VPR data base +link_openfpga_arch + +# Check and correct any naming conflicts in the BLIF netlist +check_netlist_naming_conflict --fix --report ./netlist_renaming.xml + +# Finish and exit OpenFPGA +exit diff --git a/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml index 4da00d67a..269384cbc 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml @@ -24,6 +24,25 @@ that describe them. --> + + + + + + + + + + + + + + + + + + + @@ -127,6 +146,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From fdc304a0fbc17be1195039b3313ec92ff5d8d916 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 30 Jan 2020 22:00:53 -0700 Subject: [PATCH 056/645] fixed a bug in mapping pb_graph pins using rotation offset --- openfpga/src/annotation/vpr_pb_type_annotation.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openfpga/src/annotation/vpr_pb_type_annotation.cpp b/openfpga/src/annotation/vpr_pb_type_annotation.cpp index 3204dd597..f75a91059 100644 --- a/openfpga/src/annotation/vpr_pb_type_annotation.cpp +++ b/openfpga/src/annotation/vpr_pb_type_annotation.cpp @@ -404,10 +404,10 @@ void VprPbTypeAnnotation::add_physical_pb_graph_pin(t_pb_graph_pin* operating_pb physical_pb_pin_offsets_[operating_pb_graph_pin->port] += physical_pb_pin_rotate_offset(operating_pb_graph_pin->port); - if (physical_pb_port_range(operating_pb_graph_pin->port).get_msb() + if ((size_t)physical_pb_port(operating_pb_graph_pin->port)->num_pins - 1 < operating_pb_graph_pin->pin_number + physical_pb_port_range(operating_pb_graph_pin->port).get_lsb() - + physical_pb_pin_offset(operating_pb_graph_pin->port)) { + + physical_pb_pin_offsets_[operating_pb_graph_pin->port]) { physical_pb_pin_offsets_[operating_pb_graph_pin->port] = 0; } } From afde9808da3d86f7ef78a7d50825cabcfce4dc4d Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 31 Jan 2020 10:47:05 -0700 Subject: [PATCH 057/645] add check codes for physical pb_graph_node and pb_graph_pin annotation --- openfpga/src/annotation/annotate_pb_graph.cpp | 4 + .../annotation/check_pb_graph_annotation.cpp | 124 ++++++++++++++++++ .../annotation/check_pb_graph_annotation.h | 23 ++++ 3 files changed, 151 insertions(+) create mode 100644 openfpga/src/annotation/check_pb_graph_annotation.cpp create mode 100644 openfpga/src/annotation/check_pb_graph_annotation.h diff --git a/openfpga/src/annotation/annotate_pb_graph.cpp b/openfpga/src/annotation/annotate_pb_graph.cpp index 92f1b90b2..d720426cc 100644 --- a/openfpga/src/annotation/annotate_pb_graph.cpp +++ b/openfpga/src/annotation/annotate_pb_graph.cpp @@ -10,6 +10,7 @@ #include "pb_graph_utils.h" #include "annotate_pb_graph.h" +#include "check_pb_graph_annotation.h" /* begin namespace openfpga */ namespace openfpga { @@ -435,6 +436,9 @@ void annotate_pb_graph(const DeviceContext& vpr_device_ctx, VTR_LOG("Binding operating pb_graph nodes/pins to physical pb_graph nodes/pins...\n"); annotate_physical_pb_graph_node(vpr_device_ctx, vpr_pb_type_annotation); VTR_LOG("Done\n"); + + /* Check each primitive pb_graph_node and pin has been binded to a physical node and pin */ + check_physical_pb_graph_node_annotation(vpr_device_ctx, const_cast(vpr_pb_type_annotation)); } } /* end namespace openfpga */ diff --git a/openfpga/src/annotation/check_pb_graph_annotation.cpp b/openfpga/src/annotation/check_pb_graph_annotation.cpp new file mode 100644 index 000000000..92cabefb4 --- /dev/null +++ b/openfpga/src/annotation/check_pb_graph_annotation.cpp @@ -0,0 +1,124 @@ +/******************************************************************** + * This file includes functions to check the annotation on + * the physical pb_graph_node and pb_graph_pin + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_time.h" +#include "vtr_assert.h" +#include "vtr_log.h" + +#include "pb_type_utils.h" +#include "check_pb_graph_annotation.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * A function to check if a physical pb_graph_pin for a pb_graph_pin + * Print error to log file/screen when checking fails + *******************************************************************/ +static +bool check_physical_pb_graph_pin(t_pb_graph_pin* pb_graph_pin, + const VprPbTypeAnnotation& vpr_pb_type_annotation) { + if (nullptr == vpr_pb_type_annotation.physical_pb_graph_pin(pb_graph_pin)) { + VTR_LOG_ERROR("Found a pb_graph_pin '%s' missing physical pb_graph_pin binding!\n", + pb_graph_pin->port->name); + return false; + } + + return true; +} + +/******************************************************************** + * This function will recursively walk through all the pb_graph nodes + * starting from a top node. + * It aims to check + * - if each primitive pb_graph node has been binded to a physical + * pb_graph_node + * - if each pin of the primitive pb_graph node has been binded to + * a physical graph_pin + *******************************************************************/ +static +void rec_check_vpr_physical_pb_graph_node_annotation(t_pb_graph_node* pb_graph_node, + const VprPbTypeAnnotation& vpr_pb_type_annotation, + size_t& num_err) { + /* Go recursive first until we touch the primitive node */ + if (false == is_primitive_pb_type(pb_graph_node->pb_type)) { + for (int imode = 0; imode < pb_graph_node->pb_type->num_modes; ++imode) { + for (int ipb = 0; ipb < pb_graph_node->pb_type->modes[imode].num_pb_type_children; ++ipb) { + /* Each child may exist multiple times in the hierarchy*/ + for (int jpb = 0; jpb < pb_graph_node->pb_type->modes[imode].pb_type_children[ipb].num_pb; ++jpb) { + rec_check_vpr_physical_pb_graph_node_annotation(&(pb_graph_node->child_pb_graph_nodes[imode][ipb][jpb]), + vpr_pb_type_annotation, num_err); + } + } + } + return; + } + + /* Ensure that the pb_graph_node has been mapped to a physical node */ + t_pb_graph_node* physical_pb_graph_node = vpr_pb_type_annotation.physical_pb_graph_node(pb_graph_node); + if (nullptr == physical_pb_graph_node) { + VTR_LOG_ERROR("Found a pb_graph_node '%s' missing physical pb_graph_node binding!\n", + physical_pb_graph_node->pb_type->name); + num_err++; + return; /* Invalid pointer already, further check is not applicable */ + } + + /* Reach here, we should have a valid pointer to the physical pb_graph_node, + * Check the physical pb_graph_pin for each pin + */ + for (int iport = 0; iport < physical_pb_graph_node->num_input_ports; ++iport) { + for (int ipin = 0; ipin < physical_pb_graph_node->num_input_pins[iport]; ++ipin) { + if (false == check_physical_pb_graph_pin(&(physical_pb_graph_node->input_pins[iport][ipin]), + vpr_pb_type_annotation)) { + num_err++; + } + } + } + + for (int iport = 0; iport < physical_pb_graph_node->num_output_ports; ++iport) { + for (int ipin = 0; ipin < physical_pb_graph_node->num_output_pins[iport]; ++ipin) { + if (false == check_physical_pb_graph_pin(&(physical_pb_graph_node->output_pins[iport][ipin]), + vpr_pb_type_annotation)) { + num_err++; + } + } + } + + for (int iport = 0; iport < physical_pb_graph_node->num_clock_ports; ++iport) { + for (int ipin = 0; ipin < physical_pb_graph_node->num_clock_pins[iport]; ++ipin) { + if (false == check_physical_pb_graph_pin(&(physical_pb_graph_node->clock_pins[iport][ipin]), + vpr_pb_type_annotation)) { + num_err++; + } + } + } +} + +/******************************************************************** + * Check each primitive pb_graph_node + * - It has been binded to an physical pb_graph_node + * - Each pin has been binded to a physical pb_graph_node pin + *******************************************************************/ +void check_physical_pb_graph_node_annotation(const DeviceContext& vpr_device_ctx, + const VprPbTypeAnnotation& vpr_pb_type_annotation) { + size_t num_err = 0; + + for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { + /* By pass nullptr for pb_graph head */ + if (nullptr == lb_type.pb_graph_head) { + continue; + } + rec_check_vpr_physical_pb_graph_node_annotation(lb_type.pb_graph_head, vpr_pb_type_annotation, num_err); + } + + if (0 == num_err) { + VTR_LOG("Check pb_graph annotation for physical nodes and pins passed.\n"); + } else { + VTR_LOG_ERROR("Check pb_graph annotation for physical nodes and pins failed with %ld errors!\n", + num_err); + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/annotation/check_pb_graph_annotation.h b/openfpga/src/annotation/check_pb_graph_annotation.h new file mode 100644 index 000000000..c482943c5 --- /dev/null +++ b/openfpga/src/annotation/check_pb_graph_annotation.h @@ -0,0 +1,23 @@ +#ifndef CHECK_PB_GRAPH_ANNOTATION_H +#define CHECK_PB_GRAPH_ANNOTATION_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "vpr_context.h" +#include "openfpga_context.h" +#include "vpr_pb_type_annotation.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void check_physical_pb_graph_node_annotation(const DeviceContext& vpr_device_ctx, + const VprPbTypeAnnotation& vpr_pb_type_annotation); + +} /* end namespace openfpga */ + +#endif From 392ab0f027470e0a199f8ecdba704fb7d5646f10 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 31 Jan 2020 10:53:41 -0700 Subject: [PATCH 058/645] move duplicated codes on message printing to functions --- openfpga/src/annotation/annotate_pb_graph.cpp | 48 ++++++++----------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/openfpga/src/annotation/annotate_pb_graph.cpp b/openfpga/src/annotation/annotate_pb_graph.cpp index d720426cc..3a1b17365 100644 --- a/openfpga/src/annotation/annotate_pb_graph.cpp +++ b/openfpga/src/annotation/annotate_pb_graph.cpp @@ -222,6 +222,24 @@ bool try_match_pb_graph_pin(t_pb_graph_pin* operating_pb_graph_pin, return true; } +/******************************************************************** + * A function to print message to log file/screen when physical pb_graph_pin + * binding succeed + *******************************************************************/ +static +void print_success_bind_pb_graph_pin(t_pb_graph_pin* operating_pb_graph_pin, + t_pb_graph_pin* physical_pb_graph_pin) { + VTR_LOG("Bind a pb_graph_node '%s[%d]' pin '%s[%d]' to a pb_graph_node '%s[%d]' pin '%s[%d]'!\n", + operating_pb_graph_pin->parent_node->pb_type->name, + operating_pb_graph_pin->parent_node->placement_index, + operating_pb_graph_pin->port->name, + operating_pb_graph_pin->pin_number, + physical_pb_graph_pin->parent_node->pb_type->name, + physical_pb_graph_pin->parent_node->placement_index, + physical_pb_graph_pin->port->name, + physical_pb_graph_pin->pin_number); +} + /******************************************************************** * Bind a pb_graph_pin from an operating pb_graph_node to * a pb_graph_pin from a physical pb_graph_node @@ -245,15 +263,7 @@ void annotate_physical_pb_graph_pin(t_pb_graph_pin* operating_pb_graph_pin, * We can pair the pin and return */ vpr_pb_type_annotation.add_physical_pb_graph_pin(operating_pb_graph_pin, &(physical_pb_graph_node->input_pins[iport][ipin])); - VTR_LOG("Bind a pb_graph_node '%s[%d]' pin '%s[%d]' to a pb_graph_node '%s[%d]' pin '%s[%d]'!\n", - operating_pb_graph_pin->parent_node->pb_type->name, - operating_pb_graph_pin->parent_node->placement_index, - operating_pb_graph_pin->port->name, - operating_pb_graph_pin->pin_number, - physical_pb_graph_node->pb_type->name, - physical_pb_graph_node->placement_index, - physical_pb_graph_node->input_pins[iport][ipin].port->name, - physical_pb_graph_node->input_pins[iport][ipin].pin_number); + print_success_bind_pb_graph_pin(operating_pb_graph_pin, vpr_pb_type_annotation.physical_pb_graph_pin(operating_pb_graph_pin)); return; } } @@ -269,15 +279,7 @@ void annotate_physical_pb_graph_pin(t_pb_graph_pin* operating_pb_graph_pin, * We can pair the pin and return */ vpr_pb_type_annotation.add_physical_pb_graph_pin(operating_pb_graph_pin, &(physical_pb_graph_node->output_pins[iport][ipin])); - VTR_LOG("Bind a pb_graph_node '%s[%d]' pin '%s[%d]' to a pb_graph_node '%s[%d]' pin '%s[%d]'!\n", - operating_pb_graph_pin->parent_node->pb_type->name, - operating_pb_graph_pin->parent_node->placement_index, - operating_pb_graph_pin->port->name, - operating_pb_graph_pin->pin_number, - physical_pb_graph_node->pb_type->name, - physical_pb_graph_node->placement_index, - physical_pb_graph_node->output_pins[iport][ipin].port->name, - physical_pb_graph_node->output_pins[iport][ipin].pin_number); + print_success_bind_pb_graph_pin(operating_pb_graph_pin, vpr_pb_type_annotation.physical_pb_graph_pin(operating_pb_graph_pin)); return; } } @@ -293,15 +295,7 @@ void annotate_physical_pb_graph_pin(t_pb_graph_pin* operating_pb_graph_pin, * We can pair the pin and return */ vpr_pb_type_annotation.add_physical_pb_graph_pin(operating_pb_graph_pin, &(physical_pb_graph_node->clock_pins[iport][ipin])); - VTR_LOG("Bind a pb_graph_node '%s[%d]' pin '%s[%d]' to a pb_graph_node '%s[%d]' pin '%s[%d]'!\n", - operating_pb_graph_pin->parent_node->pb_type->name, - operating_pb_graph_pin->parent_node->placement_index, - operating_pb_graph_pin->port->name, - operating_pb_graph_pin->pin_number, - physical_pb_graph_node->pb_type->name, - physical_pb_graph_node->placement_index, - physical_pb_graph_node->clock_pins[iport][ipin].port->name, - physical_pb_graph_node->clock_pins[iport][ipin].pin_number); + print_success_bind_pb_graph_pin(operating_pb_graph_pin, vpr_pb_type_annotation.physical_pb_graph_pin(operating_pb_graph_pin)); return; } } From 75c3507acf926eb34504574182736838bc652fca Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 31 Jan 2020 11:36:58 -0700 Subject: [PATCH 059/645] add verbose output option for openfpga linking architecture --- libs/libarchfpga/src/physical_types.h | 1 + openfpga/src/annotation/annotate_pb_graph.cpp | 71 ++++--- openfpga/src/annotation/annotate_pb_graph.h | 6 +- openfpga/src/annotation/annotate_pb_types.cpp | 176 ++++++++++++------ openfpga/src/annotation/annotate_pb_types.h | 3 +- openfpga/src/base/openfpga_link_arch.cpp | 11 +- openfpga/src/base/openfpga_link_arch.h | 5 +- openfpga/src/base/openfpga_setup_command.cpp | 4 +- 8 files changed, 184 insertions(+), 93 deletions(-) diff --git a/libs/libarchfpga/src/physical_types.h b/libs/libarchfpga/src/physical_types.h index 503427303..153fd981e 100644 --- a/libs/libarchfpga/src/physical_types.h +++ b/libs/libarchfpga/src/physical_types.h @@ -165,6 +165,7 @@ enum e_interconnect { MUX_INTERC = 3, NUM_INTERC_TYPES }; +constexpr std::array INTERCONNECT_TYPE_STRING = {{"complete", "direct", "mux"}}; //String versions of interconnection type /* Orientations. */ enum e_side : unsigned char { diff --git a/openfpga/src/annotation/annotate_pb_graph.cpp b/openfpga/src/annotation/annotate_pb_graph.cpp index 3a1b17365..2043c2098 100644 --- a/openfpga/src/annotation/annotate_pb_graph.cpp +++ b/openfpga/src/annotation/annotate_pb_graph.cpp @@ -28,7 +28,8 @@ namespace openfpga { *******************************************************************/ static void rec_build_vpr_pb_graph_interconnect_physical_type_annotation(t_pb_graph_node* pb_graph_node, - VprPbTypeAnnotation& vpr_pb_type_annotation) { + VprPbTypeAnnotation& vpr_pb_type_annotation, + const bool& verbose_output) { /* Skip the root node because we start from the inputs of child pb_graph node * * pb_graph_node @@ -86,6 +87,9 @@ void rec_build_vpr_pb_graph_interconnect_physical_type_annotation(t_pb_graph_nod /* Skip annotation if we have already done! */ continue; } + VTR_LOGV(verbose_output, + "Infer physical type '%s' of interconnect '%s' (was '%s')\n", + INTERCONNECT_TYPE_STRING[interc_physical_type], interc->name, INTERCONNECT_TYPE_STRING[interc->type]); vpr_pb_type_annotation.add_interconnect_physical_type(interc, interc_physical_type); } } @@ -101,7 +105,9 @@ void rec_build_vpr_pb_graph_interconnect_physical_type_annotation(t_pb_graph_nod for (int ipb = 0; ipb < physical_mode->num_pb_type_children; ++ipb) { /* Each child may exist multiple times in the hierarchy*/ for (int jpb = 0; jpb < physical_mode->pb_type_children[ipb].num_pb; ++jpb) { - rec_build_vpr_pb_graph_interconnect_physical_type_annotation(&(pb_graph_node->child_pb_graph_nodes[physical_mode->index][ipb][jpb]), vpr_pb_type_annotation); + rec_build_vpr_pb_graph_interconnect_physical_type_annotation(&(pb_graph_node->child_pb_graph_nodes[physical_mode->index][ipb][jpb]), + vpr_pb_type_annotation, + verbose_output); } } } @@ -116,13 +122,14 @@ void rec_build_vpr_pb_graph_interconnect_physical_type_annotation(t_pb_graph_nod * build_vpr_physical_pb_mode_implicit_annotation() *******************************************************************/ void annotate_pb_graph_interconnect_physical_type(const DeviceContext& vpr_device_ctx, - VprPbTypeAnnotation& vpr_pb_type_annotation) { + VprPbTypeAnnotation& vpr_pb_type_annotation, + const bool& verbose_output) { for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { /* By pass nullptr for pb_graph head */ if (nullptr == lb_type.pb_graph_head) { continue; } - rec_build_vpr_pb_graph_interconnect_physical_type_annotation(lb_type.pb_graph_head, vpr_pb_type_annotation); + rec_build_vpr_pb_graph_interconnect_physical_type_annotation(lb_type.pb_graph_head, vpr_pb_type_annotation, verbose_output); } } @@ -248,7 +255,8 @@ void print_success_bind_pb_graph_pin(t_pb_graph_pin* operating_pb_graph_pin, static void annotate_physical_pb_graph_pin(t_pb_graph_pin* operating_pb_graph_pin, t_pb_graph_node* physical_pb_graph_node, - VprPbTypeAnnotation& vpr_pb_type_annotation) { + VprPbTypeAnnotation& vpr_pb_type_annotation, + const bool& verbose_output) { /* Iterate over every port and pin of the operating pb_graph_node * and find the physical pins */ @@ -263,7 +271,9 @@ void annotate_physical_pb_graph_pin(t_pb_graph_pin* operating_pb_graph_pin, * We can pair the pin and return */ vpr_pb_type_annotation.add_physical_pb_graph_pin(operating_pb_graph_pin, &(physical_pb_graph_node->input_pins[iport][ipin])); - print_success_bind_pb_graph_pin(operating_pb_graph_pin, vpr_pb_type_annotation.physical_pb_graph_pin(operating_pb_graph_pin)); + if (true == verbose_output) { + print_success_bind_pb_graph_pin(operating_pb_graph_pin, vpr_pb_type_annotation.physical_pb_graph_pin(operating_pb_graph_pin)); + } return; } } @@ -279,7 +289,9 @@ void annotate_physical_pb_graph_pin(t_pb_graph_pin* operating_pb_graph_pin, * We can pair the pin and return */ vpr_pb_type_annotation.add_physical_pb_graph_pin(operating_pb_graph_pin, &(physical_pb_graph_node->output_pins[iport][ipin])); - print_success_bind_pb_graph_pin(operating_pb_graph_pin, vpr_pb_type_annotation.physical_pb_graph_pin(operating_pb_graph_pin)); + if (true == verbose_output) { + print_success_bind_pb_graph_pin(operating_pb_graph_pin, vpr_pb_type_annotation.physical_pb_graph_pin(operating_pb_graph_pin)); + } return; } } @@ -295,7 +307,9 @@ void annotate_physical_pb_graph_pin(t_pb_graph_pin* operating_pb_graph_pin, * We can pair the pin and return */ vpr_pb_type_annotation.add_physical_pb_graph_pin(operating_pb_graph_pin, &(physical_pb_graph_node->clock_pins[iport][ipin])); - print_success_bind_pb_graph_pin(operating_pb_graph_pin, vpr_pb_type_annotation.physical_pb_graph_pin(operating_pb_graph_pin)); + if (true == verbose_output) { + print_success_bind_pb_graph_pin(operating_pb_graph_pin, vpr_pb_type_annotation.physical_pb_graph_pin(operating_pb_graph_pin)); + } return; } } @@ -315,28 +329,32 @@ void annotate_physical_pb_graph_pin(t_pb_graph_pin* operating_pb_graph_pin, static void annotate_physical_pb_graph_node_pins(t_pb_graph_node* operating_pb_graph_node, t_pb_graph_node* physical_pb_graph_node, - VprPbTypeAnnotation& vpr_pb_type_annotation) { + VprPbTypeAnnotation& vpr_pb_type_annotation, + const bool& verbose_output) { /* Iterate over every port and pin of the operating pb_graph_node * and find the physical pins */ for (int iport = 0; iport < operating_pb_graph_node->num_input_ports; ++iport) { for (int ipin = 0; ipin < operating_pb_graph_node->num_input_pins[iport]; ++ipin) { annotate_physical_pb_graph_pin(&(operating_pb_graph_node->input_pins[iport][ipin]), - physical_pb_graph_node, vpr_pb_type_annotation); + physical_pb_graph_node, vpr_pb_type_annotation, + verbose_output); } } for (int iport = 0; iport < operating_pb_graph_node->num_output_ports; ++iport) { for (int ipin = 0; ipin < operating_pb_graph_node->num_output_pins[iport]; ++ipin) { annotate_physical_pb_graph_pin(&(operating_pb_graph_node->output_pins[iport][ipin]), - physical_pb_graph_node, vpr_pb_type_annotation); + physical_pb_graph_node, vpr_pb_type_annotation, + verbose_output); } } for (int iport = 0; iport < operating_pb_graph_node->num_clock_ports; ++iport) { for (int ipin = 0; ipin < operating_pb_graph_node->num_clock_pins[iport]; ++ipin) { annotate_physical_pb_graph_pin(&(operating_pb_graph_node->clock_pins[iport][ipin]), - physical_pb_graph_node, vpr_pb_type_annotation); + physical_pb_graph_node, vpr_pb_type_annotation, + verbose_output); } } } @@ -351,7 +369,8 @@ void annotate_physical_pb_graph_node_pins(t_pb_graph_node* operating_pb_graph_no *******************************************************************/ static void rec_build_vpr_physical_pb_graph_node_annotation(t_pb_graph_node* pb_graph_node, - VprPbTypeAnnotation& vpr_pb_type_annotation) { + VprPbTypeAnnotation& vpr_pb_type_annotation, + const bool& verbose_output) { /* Go recursive first until we touch the primitive node */ if (false == is_primitive_pb_type(pb_graph_node->pb_type)) { for (int imode = 0; imode < pb_graph_node->pb_type->num_modes; ++imode) { @@ -359,7 +378,8 @@ void rec_build_vpr_physical_pb_graph_node_annotation(t_pb_graph_node* pb_graph_n /* Each child may exist multiple times in the hierarchy*/ for (int jpb = 0; jpb < pb_graph_node->pb_type->modes[imode].pb_type_children[ipb].num_pb; ++jpb) { rec_build_vpr_physical_pb_graph_node_annotation(&(pb_graph_node->child_pb_graph_nodes[imode][ipb][jpb]), - vpr_pb_type_annotation); + vpr_pb_type_annotation, + verbose_output); } } } @@ -389,12 +409,13 @@ void rec_build_vpr_physical_pb_graph_node_annotation(t_pb_graph_node* pb_graph_n VTR_ASSERT(nullptr != physical_pb_graph_node); vpr_pb_type_annotation.add_physical_pb_graph_node(pb_graph_node, physical_pb_graph_node); - VTR_LOG("Bind operating pb_graph_node '%s' to physical pb_graph_node '%s'\n", - pb_graph_node->hierarchical_type_name().c_str(), - physical_pb_graph_node->hierarchical_type_name().c_str()); + VTR_LOGV(verbose_output, + "Bind operating pb_graph_node '%s' to physical pb_graph_node '%s'\n", + pb_graph_node->hierarchical_type_name().c_str(), + physical_pb_graph_node->hierarchical_type_name().c_str()); /* Try to bind each pins under this pb_graph_node to physical_pb_graph_node */ - annotate_physical_pb_graph_node_pins(pb_graph_node, physical_pb_graph_node, vpr_pb_type_annotation); + annotate_physical_pb_graph_node_pins(pb_graph_node, physical_pb_graph_node, vpr_pb_type_annotation, verbose_output); } /******************************************************************** @@ -404,13 +425,14 @@ void rec_build_vpr_physical_pb_graph_node_annotation(t_pb_graph_node* pb_graph_n *******************************************************************/ static void annotate_physical_pb_graph_node(const DeviceContext& vpr_device_ctx, - VprPbTypeAnnotation& vpr_pb_type_annotation) { + VprPbTypeAnnotation& vpr_pb_type_annotation, + const bool& verbose_output) { for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { /* By pass nullptr for pb_graph head */ if (nullptr == lb_type.pb_graph_head) { continue; } - rec_build_vpr_physical_pb_graph_node_annotation(lb_type.pb_graph_head, vpr_pb_type_annotation); + rec_build_vpr_physical_pb_graph_node_annotation(lb_type.pb_graph_head, vpr_pb_type_annotation, verbose_output); } } @@ -421,14 +443,17 @@ void annotate_physical_pb_graph_node(const DeviceContext& vpr_device_ctx, * - Bind pins from operating pb_graph_node to their physical pb_graph_node pins *******************************************************************/ void annotate_pb_graph(const DeviceContext& vpr_device_ctx, - VprPbTypeAnnotation& vpr_pb_type_annotation) { + VprPbTypeAnnotation& vpr_pb_type_annotation, + const bool& verbose_output) { VTR_LOG("Assigning unique indices for primitive pb_graph nodes..."); + VTR_LOGV(verbose_output, "\n"); annotate_primitive_pb_graph_node_unique_index(vpr_device_ctx, vpr_pb_type_annotation); VTR_LOG("Done\n"); - VTR_LOG("Binding operating pb_graph nodes/pins to physical pb_graph nodes/pins...\n"); - annotate_physical_pb_graph_node(vpr_device_ctx, vpr_pb_type_annotation); + VTR_LOG("Binding operating pb_graph nodes/pins to physical pb_graph nodes/pins..."); + VTR_LOGV(verbose_output, "\n"); + annotate_physical_pb_graph_node(vpr_device_ctx, vpr_pb_type_annotation, verbose_output); VTR_LOG("Done\n"); /* Check each primitive pb_graph_node and pin has been binded to a physical node and pin */ diff --git a/openfpga/src/annotation/annotate_pb_graph.h b/openfpga/src/annotation/annotate_pb_graph.h index e809d3cfa..64eced256 100644 --- a/openfpga/src/annotation/annotate_pb_graph.h +++ b/openfpga/src/annotation/annotate_pb_graph.h @@ -16,10 +16,12 @@ namespace openfpga { void annotate_pb_graph_interconnect_physical_type(const DeviceContext& vpr_device_ctx, - VprPbTypeAnnotation& vpr_pb_type_annotation); + VprPbTypeAnnotation& vpr_pb_type_annotation, + const bool& verbose_output); void annotate_pb_graph(const DeviceContext& vpr_device_ctx, - VprPbTypeAnnotation& vpr_pb_type_annotation); + VprPbTypeAnnotation& vpr_pb_type_annotation, + const bool& verbose_output); } /* end namespace openfpga */ diff --git a/openfpga/src/annotation/annotate_pb_types.cpp b/openfpga/src/annotation/annotate_pb_types.cpp index 8dc89251a..d70ab7427 100644 --- a/openfpga/src/annotation/annotate_pb_types.cpp +++ b/openfpga/src/annotation/annotate_pb_types.cpp @@ -24,7 +24,8 @@ namespace openfpga { static void build_vpr_physical_pb_mode_explicit_annotation(const DeviceContext& vpr_device_ctx, const Arch& openfpga_arch, - VprPbTypeAnnotation& vpr_pb_type_annotation) { + VprPbTypeAnnotation& vpr_pb_type_annotation, + const bool& verbose_output) { /* Walk through the pb_type annotation stored in the openfpga arch */ for (const PbTypeAnnotation& pb_type_annotation : openfpga_arch.pb_type_annotations) { /* Since our target is to annotate the physical mode name, @@ -82,8 +83,9 @@ void build_vpr_physical_pb_mode_explicit_annotation(const DeviceContext& vpr_dev vpr_pb_type_annotation.add_pb_type_physical_mode(target_pb_type, physical_mode); /* Give a message */ - VTR_LOG("Annotate pb_type '%s' with physical mode '%s'\n", - target_pb_type->name, physical_mode->name); + VTR_LOGV(verbose_output, + "Annotate pb_type '%s' with physical mode '%s'\n", + target_pb_type->name, physical_mode->name); link_success = true; break; @@ -110,7 +112,8 @@ void build_vpr_physical_pb_mode_explicit_annotation(const DeviceContext& vpr_dev *******************************************************************/ static void rec_infer_vpr_physical_pb_mode_annotation(t_pb_type* cur_pb_type, - VprPbTypeAnnotation& vpr_pb_type_annotation) { + VprPbTypeAnnotation& vpr_pb_type_annotation, + const bool& verbose_output) { /* We do not check any primitive pb_type */ if (true == is_primitive_pb_type(cur_pb_type)) { return; @@ -128,8 +131,9 @@ void rec_infer_vpr_physical_pb_mode_annotation(t_pb_type* cur_pb_type, if (nullptr == vpr_pb_type_annotation.physical_mode(cur_pb_type)) { /* Not assigned by explicit annotation, we should infer here */ vpr_pb_type_annotation.add_pb_type_physical_mode(cur_pb_type, &(cur_pb_type->modes[0])); - VTR_LOG("Implicitly infer physical mode '%s' for pb_type '%s'\n", - cur_pb_type->modes[0].name, cur_pb_type->name); + VTR_LOGV(verbose_output, + "Implicitly infer physical mode '%s' for pb_type '%s'\n", + cur_pb_type->modes[0].name, cur_pb_type->name); } } else { VTR_ASSERT(1 < cur_pb_type->num_modes); @@ -151,7 +155,8 @@ void rec_infer_vpr_physical_pb_mode_annotation(t_pb_type* cur_pb_type, /* Traverse the pb_type children under the physical mode */ for (int ichild = 0; ichild < physical_mode->num_pb_type_children; ++ichild) { rec_infer_vpr_physical_pb_mode_annotation(&(physical_mode->pb_type_children[ichild]), - vpr_pb_type_annotation); + vpr_pb_type_annotation, + verbose_output); } } @@ -169,13 +174,14 @@ void rec_infer_vpr_physical_pb_mode_annotation(t_pb_type* cur_pb_type, *******************************************************************/ static void build_vpr_physical_pb_mode_implicit_annotation(const DeviceContext& vpr_device_ctx, - VprPbTypeAnnotation& vpr_pb_type_annotation) { + VprPbTypeAnnotation& vpr_pb_type_annotation, + const bool& verbose_output) { for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { /* By pass nullptr for pb_type head */ if (nullptr == lb_type.pb_type) { continue; } - rec_infer_vpr_physical_pb_mode_annotation(lb_type.pb_type, vpr_pb_type_annotation); + rec_infer_vpr_physical_pb_mode_annotation(lb_type.pb_type, vpr_pb_type_annotation, verbose_output); } } @@ -257,7 +263,8 @@ bool pair_operating_and_physical_pb_types(t_pb_type* operating_pb_type, static void build_vpr_physical_pb_type_explicit_annotation(const DeviceContext& vpr_device_ctx, const Arch& openfpga_arch, - VprPbTypeAnnotation& vpr_pb_type_annotation) { + VprPbTypeAnnotation& vpr_pb_type_annotation, + const bool& verbose_output) { /* Walk through the pb_type annotation stored in the openfpga arch */ for (const PbTypeAnnotation& pb_type_annotation : openfpga_arch.pb_type_annotations) { /* Since our target is to annotate the operating pb_type tp physical pb_type @@ -324,8 +331,9 @@ void build_vpr_physical_pb_type_explicit_annotation(const DeviceContext& vpr_dev pb_type_annotation, vpr_pb_type_annotation)) { /* Give a message */ - VTR_LOG("Annotate operating pb_type '%s' to its physical pb_type '%s'\n", - target_op_pb_type->name, target_phy_pb_type->name); + VTR_LOGV(verbose_output, + "Annotate operating pb_type '%s' to its physical pb_type '%s'\n", + target_op_pb_type->name, target_phy_pb_type->name); link_success = true; break; @@ -376,7 +384,8 @@ bool self_pair_physical_pb_types(t_pb_type* physical_pb_type, *******************************************************************/ static void rec_infer_vpr_physical_pb_type_annotation(t_pb_type* cur_pb_type, - VprPbTypeAnnotation& vpr_pb_type_annotation) { + VprPbTypeAnnotation& vpr_pb_type_annotation, + const bool& verbose_output) { /* Physical pb_type is mainly for the primitive pb_type */ if (true == is_primitive_pb_type(cur_pb_type)) { /* If the physical pb_type has been mapped, we can skip it */ @@ -386,8 +395,9 @@ void rec_infer_vpr_physical_pb_type_annotation(t_pb_type* cur_pb_type, /* Create the pair here */ if (true == self_pair_physical_pb_types(cur_pb_type, vpr_pb_type_annotation)) { /* Give a message */ - VTR_LOG("Implicitly infer the physical pb_type for pb_type '%s' itself\n", - cur_pb_type->name); + VTR_LOGV(verbose_output, + "Implicitly infer the physical pb_type for pb_type '%s' itself\n", + cur_pb_type->name); } else { VTR_LOG_ERROR("Unable to infer the physical pb_type for pb_type '%s' itself!\n", cur_pb_type->name); @@ -403,7 +413,8 @@ void rec_infer_vpr_physical_pb_type_annotation(t_pb_type* cur_pb_type, /* Traverse the pb_type children under the physical mode */ for (int ichild = 0; ichild < physical_mode->num_pb_type_children; ++ichild) { rec_infer_vpr_physical_pb_type_annotation(&(physical_mode->pb_type_children[ichild]), - vpr_pb_type_annotation); + vpr_pb_type_annotation, + verbose_output); } } @@ -418,13 +429,14 @@ void rec_infer_vpr_physical_pb_type_annotation(t_pb_type* cur_pb_type, *******************************************************************/ static void build_vpr_physical_pb_type_implicit_annotation(const DeviceContext& vpr_device_ctx, - VprPbTypeAnnotation& vpr_pb_type_annotation) { + VprPbTypeAnnotation& vpr_pb_type_annotation, + const bool& verbose_output) { for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { /* By pass nullptr for pb_type head */ if (nullptr == lb_type.pb_type) { continue; } - rec_infer_vpr_physical_pb_type_annotation(lb_type.pb_type, vpr_pb_type_annotation); + rec_infer_vpr_physical_pb_type_annotation(lb_type.pb_type, vpr_pb_type_annotation, verbose_output); } } @@ -438,7 +450,8 @@ static bool link_physical_pb_port_to_circuit_port(t_pb_type* physical_pb_type, const CircuitLibrary& circuit_lib, const CircuitModelId& circuit_model, - VprPbTypeAnnotation& vpr_pb_type_annotation) { + VprPbTypeAnnotation& vpr_pb_type_annotation, + const bool& verbose_output) { bool link_success = true; /* Iterate over the pb_ports * Note: @@ -481,11 +494,12 @@ bool link_physical_pb_port_to_circuit_port(t_pb_type* physical_pb_type, /* Reach here, it means that mapping should be ok, update the vpr_pb_type_annotation */ vpr_pb_type_annotation.add_pb_circuit_port(pb_port, circuit_port); - VTR_LOG("Bind pb type '%s' port '%s' to circuit model '%s' port '%s'\n", - physical_pb_type->name, - pb_port->name, - circuit_lib.model_name(circuit_model).c_str(), - circuit_lib.port_prefix(circuit_port).c_str()); + VTR_LOGV(verbose_output, + "Bind pb type '%s' port '%s' to circuit model '%s' port '%s'\n", + physical_pb_type->name, + pb_port->name, + circuit_lib.model_name(circuit_model).c_str(), + circuit_lib.port_prefix(circuit_port).c_str()); } return link_success; @@ -499,7 +513,8 @@ static bool link_physical_pb_type_to_circuit_model(t_pb_type* physical_pb_type, const CircuitLibrary& circuit_lib, const PbTypeAnnotation& pb_type_annotation, - VprPbTypeAnnotation& vpr_pb_type_annotation) { + VprPbTypeAnnotation& vpr_pb_type_annotation, + const bool& verbose_output) { /* Reach here, we should have valid operating and physical pb_types */ VTR_ASSERT(nullptr != physical_pb_type); @@ -521,7 +536,8 @@ bool link_physical_pb_type_to_circuit_model(t_pb_type* physical_pb_type, } /* Ensure that the pb_type ports can be matched in the circuit model ports */ - if (false == link_physical_pb_port_to_circuit_port(physical_pb_type, circuit_lib, circuit_model_id, vpr_pb_type_annotation)) { + if (false == link_physical_pb_port_to_circuit_port(physical_pb_type, circuit_lib, circuit_model_id, + vpr_pb_type_annotation, verbose_output)) { return false; } @@ -539,7 +555,8 @@ bool link_physical_pb_interconnect_to_circuit_model(t_pb_type* physical_pb_type, const std::string& interconnect_name, const CircuitLibrary& circuit_lib, const PbTypeAnnotation& pb_type_annotation, - VprPbTypeAnnotation& vpr_pb_type_annotation) { + VprPbTypeAnnotation& vpr_pb_type_annotation, + const bool& verbose_output) { /* The physical pb_type should NOT be a primitive, otherwise it should never contain any interconnect */ if (true == is_primitive_pb_type(physical_pb_type)) { VTR_LOG_ERROR("Link interconnect to circuit model is not allowed for a primitive pb_type '%s'!\n", @@ -592,7 +609,8 @@ bool link_physical_pb_interconnect_to_circuit_model(t_pb_type* physical_pb_type, /* Now the circuit model is valid, update the vpr_pb_type_annotation */ vpr_pb_type_annotation.add_interconnect_circuit_model(pb_interc, circuit_model_id); - VTR_LOG("Bind pb_type '%s' physical mode '%s' interconnect '%s' to circuit model '%s'\n", + VTR_LOGV(verbose_output, + "Bind pb_type '%s' physical mode '%s' interconnect '%s' to circuit model '%s'\n", physical_pb_type->name, physical_mode->name, pb_interc->name, @@ -613,7 +631,8 @@ bool link_physical_pb_interconnect_to_circuit_model(t_pb_type* physical_pb_type, static void link_vpr_pb_type_to_circuit_model_explicit_annotation(const DeviceContext& vpr_device_ctx, const Arch& openfpga_arch, - VprPbTypeAnnotation& vpr_pb_type_annotation) { + VprPbTypeAnnotation& vpr_pb_type_annotation, + const bool& verbose_output) { /* Walk through the pb_type annotation stored in the openfpga arch */ for (const PbTypeAnnotation& pb_type_annotation : openfpga_arch.pb_type_annotations) { /* Since our target is to annotate the circuti model for physical pb_type @@ -664,11 +683,13 @@ void link_vpr_pb_type_to_circuit_model_explicit_annotation(const DeviceContext& /* Only try to bind pb_type to circuit model when it is defined by users */ if (true == link_physical_pb_type_to_circuit_model(target_phy_pb_type, openfpga_arch.circuit_lib, - pb_type_annotation, vpr_pb_type_annotation)) { + pb_type_annotation, vpr_pb_type_annotation, + verbose_output)) { /* Give a message */ - VTR_LOG("Bind physical pb_type '%s' to its circuit model '%s'\n", - target_phy_pb_type->name, - openfpga_arch.circuit_lib.model_name(vpr_pb_type_annotation.pb_type_circuit_model(target_phy_pb_type)).c_str()); + VTR_LOGV(verbose_output, + "Bind physical pb_type '%s' to its circuit model '%s'\n", + target_phy_pb_type->name, + openfpga_arch.circuit_lib.model_name(vpr_pb_type_annotation.pb_type_circuit_model(target_phy_pb_type)).c_str()); link_success = true; break; @@ -697,7 +718,8 @@ void link_vpr_pb_type_to_circuit_model_explicit_annotation(const DeviceContext& static void link_vpr_pb_interconnect_to_circuit_model_explicit_annotation(const DeviceContext& vpr_device_ctx, const Arch& openfpga_arch, - VprPbTypeAnnotation& vpr_pb_type_annotation) { + VprPbTypeAnnotation& vpr_pb_type_annotation, + const bool& verbose_output) { /* Walk through the pb_type annotation stored in the openfpga arch */ for (const PbTypeAnnotation& pb_type_annotation : openfpga_arch.pb_type_annotations) { /* Since our target is to annotate the circuti model for physical pb_type @@ -749,7 +771,8 @@ void link_vpr_pb_interconnect_to_circuit_model_explicit_annotation(const DeviceC /* Only try to bind interconnect to circuit model when it is defined by users */ for (const std::string& interc_name : pb_type_annotation.interconnect_names()) { if (false == link_physical_pb_interconnect_to_circuit_model(target_phy_pb_type, interc_name, openfpga_arch.circuit_lib, - pb_type_annotation, vpr_pb_type_annotation)) { + pb_type_annotation, vpr_pb_type_annotation, + verbose_output)) { VTR_LOG_ERROR("Unable to bind pb_type '%s' interconnect '%s' to circuit model '%s'!\n", target_phy_pb_type_names.back().c_str(), interc_name.c_str(), @@ -789,7 +812,8 @@ void link_vpr_pb_interconnect_to_circuit_model_explicit_annotation(const DeviceC static void rec_infer_vpr_pb_interconnect_circuit_model_annotation(t_pb_type* cur_pb_type, const CircuitLibrary& circuit_lib, - VprPbTypeAnnotation& vpr_pb_type_annotation) { + VprPbTypeAnnotation& vpr_pb_type_annotation, + const bool& verbose_output) { /* We do not check any primitive pb_type */ if (true == is_primitive_pb_type(cur_pb_type)) { return; @@ -818,17 +842,19 @@ void rec_infer_vpr_pb_interconnect_circuit_model_annotation(t_pb_type* cur_pb_ty cur_pb_type->name); } vpr_pb_type_annotation.add_interconnect_circuit_model(pb_interc, default_circuit_model); - VTR_LOG("Implicitly infer a circuit model '%s' for interconnect '%s' under physical mode '%s' of pb_type '%s'\n", - circuit_lib.model_name(default_circuit_model).c_str(), - pb_interc->name, - physical_mode->name, - cur_pb_type->name); + VTR_LOGV(verbose_output, + "Implicitly infer a circuit model '%s' for interconnect '%s' under physical mode '%s' of pb_type '%s'\n", + circuit_lib.model_name(default_circuit_model).c_str(), + pb_interc->name, + physical_mode->name, + cur_pb_type->name); } /* Traverse the pb_type children under the physical mode */ for (int ichild = 0; ichild < physical_mode->num_pb_type_children; ++ichild) { rec_infer_vpr_pb_interconnect_circuit_model_annotation(&(physical_mode->pb_type_children[ichild]), - circuit_lib, vpr_pb_type_annotation); + circuit_lib, vpr_pb_type_annotation, + verbose_output); } } @@ -845,13 +871,14 @@ void rec_infer_vpr_pb_interconnect_circuit_model_annotation(t_pb_type* cur_pb_ty static void link_vpr_pb_interconnect_to_circuit_model_implicit_annotation(const DeviceContext& vpr_device_ctx, const CircuitLibrary& circuit_lib, - VprPbTypeAnnotation& vpr_pb_type_annotation) { + VprPbTypeAnnotation& vpr_pb_type_annotation, + const bool& verbose_output) { for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { /* By pass nullptr for pb_type head */ if (nullptr == lb_type.pb_type) { continue; } - rec_infer_vpr_pb_interconnect_circuit_model_annotation(lb_type.pb_type, circuit_lib, vpr_pb_type_annotation); + rec_infer_vpr_pb_interconnect_circuit_model_annotation(lb_type.pb_type, circuit_lib, vpr_pb_type_annotation, verbose_output); } } @@ -889,7 +916,8 @@ bool link_primitive_pb_type_to_mode_bits(t_pb_type* primitive_pb_type, static void link_vpr_pb_type_to_mode_bits_explicit_annotation(const DeviceContext& vpr_device_ctx, const Arch& openfpga_arch, - VprPbTypeAnnotation& vpr_pb_type_annotation) { + VprPbTypeAnnotation& vpr_pb_type_annotation, + const bool& verbose_output) { /* Walk through the pb_type annotation stored in the openfpga arch */ for (const PbTypeAnnotation& pb_type_annotation : openfpga_arch.pb_type_annotations) { if (true == pb_type_annotation.mode_bits().empty()) { @@ -947,9 +975,10 @@ void link_vpr_pb_type_to_mode_bits_explicit_annotation(const DeviceContext& vpr_ if (true == link_primitive_pb_type_to_mode_bits(target_pb_type, pb_type_annotation, vpr_pb_type_annotation)) { /* Give a message */ - VTR_LOG("Bind physical pb_type '%s' to mode selection bits '%s'\n", - target_pb_type->name, - mode_bits_str.c_str()); + VTR_LOGV(verbose_output, + "Bind physical pb_type '%s' to mode selection bits '%s'\n", + target_pb_type->name, + mode_bits_str.c_str()); link_success = true; break; @@ -973,16 +1002,21 @@ void link_vpr_pb_type_to_mode_bits_explicit_annotation(const DeviceContext& vpr_ *******************************************************************/ void annotate_pb_types(const DeviceContext& vpr_device_ctx, const Arch& openfpga_arch, - VprPbTypeAnnotation& vpr_pb_type_annotation) { + VprPbTypeAnnotation& vpr_pb_type_annotation, + const bool& verbose_output) { /* Annotate physical mode to pb_type in the VPR pb_type graph */ VTR_LOG("\n"); - VTR_LOG("Building annotation for physical modes in pb_type...\n"); + VTR_LOG("Building annotation for physical modes in pb_type..."); + VTR_LOGV(verbose_output, "\n"); build_vpr_physical_pb_mode_explicit_annotation(vpr_device_ctx, openfpga_arch, - vpr_pb_type_annotation); + vpr_pb_type_annotation, + verbose_output); build_vpr_physical_pb_mode_implicit_annotation(vpr_device_ctx, - vpr_pb_type_annotation); + vpr_pb_type_annotation, + verbose_output); + VTR_LOG("Done\n"); check_vpr_physical_pb_mode_annotation(vpr_device_ctx, const_cast(vpr_pb_type_annotation)); @@ -993,8 +1027,10 @@ void annotate_pb_types(const DeviceContext& vpr_device_ctx, */ VTR_LOG("\n"); VTR_LOG("Building annotation about physical types for pb_type interconnection..."); + VTR_LOGV(verbose_output, "\n"); annotate_pb_graph_interconnect_physical_type(vpr_device_ctx, - vpr_pb_type_annotation); + vpr_pb_type_annotation, + verbose_output); VTR_LOG("Done\n"); /* Annotate physical pb_types to operating pb_type in the VPR pb_type graph @@ -1004,12 +1040,16 @@ void annotate_pb_types(const DeviceContext& vpr_device_ctx, * - physical_pin_rotate_offset */ VTR_LOG("\n"); - VTR_LOG("Building annotation between operating and physical pb_types...\n"); + VTR_LOG("Building annotation between operating and physical pb_types..."); + VTR_LOGV(verbose_output, "\n"); build_vpr_physical_pb_type_explicit_annotation(vpr_device_ctx, openfpga_arch, - vpr_pb_type_annotation); + vpr_pb_type_annotation, + verbose_output); build_vpr_physical_pb_type_implicit_annotation(vpr_device_ctx, - vpr_pb_type_annotation); + vpr_pb_type_annotation, + verbose_output); + VTR_LOG("Done\n"); check_vpr_physical_pb_type_annotation(vpr_device_ctx, const_cast(vpr_pb_type_annotation)); @@ -1019,21 +1059,33 @@ void annotate_pb_types(const DeviceContext& vpr_device_ctx, * - interconnect of physical pb_type to circuit model */ VTR_LOG("\n"); - VTR_LOG("Building annotation between physical pb_types and circuit models...\n"); + VTR_LOG("Building annotation between physical pb_types and circuit models..."); + VTR_LOGV(verbose_output, "\n"); link_vpr_pb_type_to_circuit_model_explicit_annotation(vpr_device_ctx, openfpga_arch, - vpr_pb_type_annotation); + vpr_pb_type_annotation, + verbose_output); + link_vpr_pb_interconnect_to_circuit_model_explicit_annotation(vpr_device_ctx, openfpga_arch, - vpr_pb_type_annotation); + vpr_pb_type_annotation, + verbose_output); + link_vpr_pb_interconnect_to_circuit_model_implicit_annotation(vpr_device_ctx, openfpga_arch.circuit_lib, - vpr_pb_type_annotation); + vpr_pb_type_annotation, + verbose_output); + VTR_LOG("Done\n"); + check_vpr_pb_type_circuit_model_annotation(vpr_device_ctx, openfpga_arch.circuit_lib, const_cast(vpr_pb_type_annotation)); /* Link physical pb_type to mode_bits */ VTR_LOG("\n"); - VTR_LOG("Building annotation between physical pb_types and mode selection bits...\n"); + VTR_LOG("Building annotation between physical pb_types and mode selection bits..."); + VTR_LOGV(verbose_output, "\n"); link_vpr_pb_type_to_mode_bits_explicit_annotation(vpr_device_ctx, openfpga_arch, - vpr_pb_type_annotation); + vpr_pb_type_annotation, + verbose_output); + VTR_LOG("Done\n"); + check_vpr_pb_type_mode_bits_annotation(vpr_device_ctx, openfpga_arch.circuit_lib, const_cast(vpr_pb_type_annotation)); diff --git a/openfpga/src/annotation/annotate_pb_types.h b/openfpga/src/annotation/annotate_pb_types.h index 494b2c771..4be50aed6 100644 --- a/openfpga/src/annotation/annotate_pb_types.h +++ b/openfpga/src/annotation/annotate_pb_types.h @@ -17,7 +17,8 @@ namespace openfpga { void annotate_pb_types(const DeviceContext& vpr_device_ctx, const Arch& openfpga_arch, - VprPbTypeAnnotation& vpr_pb_type_annotation); + VprPbTypeAnnotation& vpr_pb_type_annotation, + const bool& verbose_output); } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_link_arch.cpp b/openfpga/src/base/openfpga_link_arch.cpp index e6762485e..8f7b5b54d 100644 --- a/openfpga/src/base/openfpga_link_arch.cpp +++ b/openfpga/src/base/openfpga_link_arch.cpp @@ -27,17 +27,21 @@ namespace openfpga { * - physical pb_graph nodes and pb_graph pins * - circuit models for global routing architecture *******************************************************************/ -void link_arch(OpenfpgaContext& openfpga_context) { +void link_arch(OpenfpgaContext& openfpga_context, + const Command& cmd, const CommandContext& cmd_context) { vtr::ScopedStartFinishTimer timer("Link OpenFPGA architecture to VPR architecture"); + CommandOptionId opt_verbose = cmd.option("verbose"); + /* Annotate pb_type graphs * - physical pb_type * - mode selection bits for pb_type and pb interconnect * - circuit models for pb_type and pb interconnect */ annotate_pb_types(g_vpr_ctx.device(), openfpga_context.arch(), - openfpga_context.mutable_vpr_pb_type_annotation()); + openfpga_context.mutable_vpr_pb_type_annotation(), + cmd_context.option_enable(cmd, opt_verbose)); /* Annotate pb_graph_nodes * - Give unique index to each node in the same type @@ -45,7 +49,8 @@ void link_arch(OpenfpgaContext& openfpga_context) { * - Bind pins from operating pb_graph_node to their physical pb_graph_node pins */ annotate_pb_graph(g_vpr_ctx.device(), - openfpga_context.mutable_vpr_pb_type_annotation()); + openfpga_context.mutable_vpr_pb_type_annotation(), + cmd_context.option_enable(cmd, opt_verbose)); } } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_link_arch.h b/openfpga/src/base/openfpga_link_arch.h index 3775ee789..742c29541 100644 --- a/openfpga/src/base/openfpga_link_arch.h +++ b/openfpga/src/base/openfpga_link_arch.h @@ -4,6 +4,8 @@ /******************************************************************** * Include header files that are required by function declaration *******************************************************************/ +#include "command.h" +#include "command_context.h" #include "openfpga_context.h" /******************************************************************** @@ -13,7 +15,8 @@ /* begin namespace openfpga */ namespace openfpga { -void link_arch(OpenfpgaContext& openfpga_context); +void link_arch(OpenfpgaContext& openfpga_context, + const Command& cmd, const CommandContext& cmd_context); } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_setup_command.cpp b/openfpga/src/base/openfpga_setup_command.cpp index 9a3eee970..ef392a4c4 100644 --- a/openfpga/src/base/openfpga_setup_command.cpp +++ b/openfpga/src/base/openfpga_setup_command.cpp @@ -52,6 +52,8 @@ void add_openfpga_setup_commands(openfpga::Shell& shell) { * Command 'link_openfpga_arch' */ Command shell_cmd_link_openfpga_arch("link_openfpga_arch"); + /* Add an option '--verbose' */ + shell_cmd_link_openfpga_arch.add_option("verbose", false, "Show verbose outputs"); /* Add command 'link_openfpga_arch' to the Shell */ ShellCommandId shell_cmd_link_openfpga_arch_id = shell.add_command(shell_cmd_link_openfpga_arch, "Bind OpenFPGA architecture to VPR"); @@ -67,7 +69,7 @@ void add_openfpga_setup_commands(openfpga::Shell& shell) { * Command 'check_netlist_naming_conflict' */ Command shell_cmd_check_netlist_naming_conflict("check_netlist_naming_conflict"); - /* Add an option '--correction' */ + /* Add an option '--fix' */ shell_cmd_check_netlist_naming_conflict.add_option("fix", false, "Apply correction to any conflicts found"); /* Add an option '--report' */ CommandOptionId check_netlist_opt_rpt = shell_cmd_check_netlist_naming_conflict.add_option("report", false, "Output a report file about what any correction applied"); From fb0bcd7a48dd1f70d1ad47c99edf13a8509cdead Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 31 Jan 2020 12:29:50 -0700 Subject: [PATCH 060/645] create rr_graph library to enforce unit test on the new data structures as well as compare to legacy rr_node --- libs/CMakeLists.txt | 1 + libs/librrgraph/CMakeLists.txt | 35 + .../librrgraph/src}/check_rr_graph_obj.cpp | 19 +- .../librrgraph/src}/check_rr_graph_obj.h | 0 .../librrgraph/src}/rr_graph_fwd.h | 0 .../librrgraph/src}/rr_graph_obj.cpp | 631 ++++++++++-------- .../librrgraph/src}/rr_graph_obj.h | 171 +++-- .../librrgraph/src}/rr_graph_obj_utils.h | 26 + libs/librrgraph/src/rr_graph_types.h | 43 ++ .../librrgraph/src}/rr_graph_util.cpp | 0 .../librrgraph/src}/rr_graph_util.h | 0 libs/librrgraph/test/test_rr_graph.cpp | 8 + libs/libvtrutil/src/vtr_geometry.tpp | 6 + vpr/CMakeLists.txt | 1 + vpr/src/base/vpr_types.h | 32 +- 15 files changed, 612 insertions(+), 361 deletions(-) create mode 100644 libs/librrgraph/CMakeLists.txt rename {vpr/src/device => libs/librrgraph/src}/check_rr_graph_obj.cpp (94%) rename {vpr/src/device => libs/librrgraph/src}/check_rr_graph_obj.h (100%) rename {vpr/src/device => libs/librrgraph/src}/rr_graph_fwd.h (100%) rename {vpr/src/device => libs/librrgraph/src}/rr_graph_obj.cpp (65%) rename {vpr/src/device => libs/librrgraph/src}/rr_graph_obj.h (85%) rename {vpr/src/device => libs/librrgraph/src}/rr_graph_obj_utils.h (87%) create mode 100644 libs/librrgraph/src/rr_graph_types.h rename {vpr/src/device => libs/librrgraph/src}/rr_graph_util.cpp (100%) rename {vpr/src/device => libs/librrgraph/src}/rr_graph_util.h (100%) create mode 100644 libs/librrgraph/test/test_rr_graph.cpp diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index 8d09c541d..a85d179f8 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -5,6 +5,7 @@ add_subdirectory(EXTERNAL) # Only add warn flags for VPR internal libraries. #add_compile_options(${WARN_FLAGS}) add_subdirectory(libarchfpga) +add_subdirectory(librrgraph) add_subdirectory(libvtrutil) add_subdirectory(liblog) add_subdirectory(libpugiutil) diff --git a/libs/librrgraph/CMakeLists.txt b/libs/librrgraph/CMakeLists.txt new file mode 100644 index 000000000..52f46a170 --- /dev/null +++ b/libs/librrgraph/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.9) + +project("librrgraph") + +file(GLOB_RECURSE EXEC_SOURCE test/test_rr_graph.cpp) +file(GLOB_RECURSE LIB_SOURCES src/*.cpp) +file(GLOB_RECURSE LIB_HEADERS src/*.h) +files_to_dirs(LIB_HEADERS LIB_INCLUDE_DIRS) + +#Remove test executable from library +list(REMOVE_ITEM LIB_SOURCES ${EXEC_SOURCE}) + +#Create the library +add_library(librrgraph STATIC + ${LIB_HEADERS} + ${LIB_SOURCES}) +target_include_directories(librrgraph PUBLIC ${LIB_INCLUDE_DIRS}) +set_target_properties(librrgraph PROPERTIES PREFIX "") #Avoid extra 'lib' prefix + +#Specify link-time dependancies +target_link_libraries(librrgraph + libarchfpga + libvtrutil) + +#Create the test executable +add_executable(test_rr_graph ${EXEC_SOURCE}) +target_link_libraries(test_rr_graph librrgraph) + +#Supress IPO link warnings if IPO is enabled +#get_target_property(READ_ARCH_USES_IPO read_arch_openfpga INTERPROCEDURAL_OPTIMIZATION) +#if (READ_ARCH_USES_IPO) +# set_target_properties(read_arch_openfpga PROPERTIES LINK_FLAGS ${IPO_LINK_WARN_SUPRESS_FLAGS}) +#endif() + +#install(TARGETS libarchopenfpga read_arch_openfpga DESTINATION bin) diff --git a/vpr/src/device/check_rr_graph_obj.cpp b/libs/librrgraph/src/check_rr_graph_obj.cpp similarity index 94% rename from vpr/src/device/check_rr_graph_obj.cpp rename to libs/librrgraph/src/check_rr_graph_obj.cpp index 253092fb7..69c772fcb 100644 --- a/vpr/src/device/check_rr_graph_obj.cpp +++ b/libs/librrgraph/src/check_rr_graph_obj.cpp @@ -107,7 +107,7 @@ static bool check_rr_graph_source_nodes(const RRGraph& rr_graph) { } } - return invalid_sources; + return !invalid_sources; } /*********************************************************************** @@ -136,7 +136,7 @@ static bool check_rr_graph_sink_nodes(const RRGraph& rr_graph) { } } - return invalid_sinks; + return !invalid_sinks; } /*********************************************************************** @@ -153,48 +153,43 @@ static bool check_rr_graph_sink_nodes(const RRGraph& rr_graph) { * will work properly. **********************************************************************/ bool check_rr_graph(const RRGraph& rr_graph) { - bool check_flag = true; size_t num_err = 0; if (false == check_rr_graph_duplicated_edges(rr_graph)) { VTR_LOG_WARN("Fail in checking duplicated edges !\n"); - check_flag = false; num_err++; } if (false == check_rr_graph_dangling_nodes(rr_graph)) { VTR_LOG_WARN("Fail in checking dangling nodes !\n"); - check_flag = false; num_err++; } if (false == check_rr_graph_source_nodes(rr_graph)) { VTR_LOG_WARN("Fail in checking source nodes!\n"); - check_flag = false; num_err++; } if (false == check_rr_graph_sink_nodes(rr_graph)) { VTR_LOG_WARN("Fail in checking sink nodes!\n"); - check_flag = false; num_err++; } if (false == check_rr_graph_source_nodes(rr_graph)) { VTR_LOG_WARN("Fail in checking source nodes!\n"); - check_flag = false; num_err++; } if (false == check_rr_graph_sink_nodes(rr_graph)) { VTR_LOG_WARN("Fail in checking sink nodes!\n"); - check_flag = false; num_err++; } /* Error out if there is any fatal errors found */ - VTR_LOG_WARN("Checked Routing Resource graph with %d errors !\n", - num_err); + if (0 < num_err) { + VTR_LOG_WARN("Checked Routing Resource graph with %d errors !\n", + num_err); + } - return check_flag; + return (0 == num_err); } diff --git a/vpr/src/device/check_rr_graph_obj.h b/libs/librrgraph/src/check_rr_graph_obj.h similarity index 100% rename from vpr/src/device/check_rr_graph_obj.h rename to libs/librrgraph/src/check_rr_graph_obj.h diff --git a/vpr/src/device/rr_graph_fwd.h b/libs/librrgraph/src/rr_graph_fwd.h similarity index 100% rename from vpr/src/device/rr_graph_fwd.h rename to libs/librrgraph/src/rr_graph_fwd.h diff --git a/vpr/src/device/rr_graph_obj.cpp b/libs/librrgraph/src/rr_graph_obj.cpp similarity index 65% rename from vpr/src/device/rr_graph_obj.cpp rename to libs/librrgraph/src/rr_graph_obj.cpp index 0cd9c49e8..2df4a77bf 100644 --- a/vpr/src/device/rr_graph_obj.cpp +++ b/libs/librrgraph/src/rr_graph_obj.cpp @@ -7,6 +7,7 @@ #include #include +#include "vtr_geometry.h" #include "vtr_vector_map.h" #include "vtr_log.h" #include "vtr_util.h" @@ -14,13 +15,26 @@ #include "rr_graph_obj.h" #include "rr_graph_obj_utils.h" -//Accessors -RRGraph::node_range RRGraph::nodes() const { - return vtr::make_range(node_ids_.begin(), node_ids_.end()); +/******************************************************************** + * Constructors + *******************************************************************/ +RRGraph::RRGraph() + : num_nodes_(0) + , num_edges_(0) { + //Pass } -RRGraph::edge_range RRGraph::edges() const { - return vtr::make_range(edge_ids_.begin(), edge_ids_.end()); +/******************************************************************** + * Accessors + *******************************************************************/ +RRGraph::lazy_node_range RRGraph::nodes() const { + return vtr::make_range(lazy_node_iterator(RRNodeId(0), invalid_node_ids_), + lazy_node_iterator(RRNodeId(num_nodes_), invalid_node_ids_)); +} + +RRGraph::lazy_edge_range RRGraph::edges() const { + return vtr::make_range(lazy_edge_iterator(RREdgeId(0), invalid_edge_ids_), + lazy_edge_iterator(RREdgeId(num_edges_), invalid_edge_ids_)); } RRGraph::switch_range RRGraph::switches() const { @@ -117,11 +131,11 @@ vtr::Point RRGraph::node_end_coordinate(const RRNodeId& node) const { } short RRGraph::node_fan_in(const RRNodeId& node) const { - return node_in_edges(node).size(); + return node_num_in_edges_[node]; } short RRGraph::node_fan_out(const RRNodeId& node) const { - return node_out_edges(node).size(); + return node_num_out_edges_[node]; } short RRGraph::node_capacity(const RRNodeId& node) const { @@ -189,118 +203,65 @@ RRSegmentId RRGraph::node_segment(const RRNodeId& node) const { return node_segments_[node]; } -/* - * Get the number of configurable input edges of a node - * TODO: we would use the node_num_configurable_in_edges() - * when the rr_graph edges have been partitioned - * This can avoid unneccessary walkthrough - */ -short RRGraph::node_num_configurable_in_edges(const RRNodeId& node) const { - return node_configurable_in_edges(node).size(); +RRGraph::edge_range RRGraph::node_edges(const RRNodeId& node) const { + VTR_ASSERT_SAFE(valid_node_id(node)); + + return vtr::make_range(node_edges_[node].get(), + node_edges_[node].get() + node_num_in_edges_[node] + node_num_out_edges_[node]); } -/* - * Get the number of configurable output edges of a node - * TODO: we would use the node_num_configurable_out_edges() - * when the rr_graph edges have been partitioned - * This can avoid unneccessary walkthrough - */ -short RRGraph::node_num_configurable_out_edges(const RRNodeId& node) const { - return node_configurable_out_edges(node).size(); +RRGraph::edge_range RRGraph::node_in_edges(const RRNodeId& node) const { + VTR_ASSERT_SAFE(valid_node_id(node)); + + return vtr::make_range(node_edges_[node].get(), + node_edges_[node].get() + node_num_in_edges_[node]); } -/* - * Get the number of non-configurable input edges of a node - * TODO: we would use the node_num_configurable_in_edges() - * when the rr_graph edges have been partitioned - * This can avoid unneccessary walkthrough - */ -short RRGraph::node_num_non_configurable_in_edges(const RRNodeId& node) const { - return node_non_configurable_in_edges(node).size(); -} +RRGraph::edge_range RRGraph::node_out_edges(const RRNodeId& node) const { + VTR_ASSERT_SAFE(valid_node_id(node)); -/* - * Get the number of non-configurable output edges of a node - * TODO: we would use the node_num_configurable_out_edges() - * when the rr_graph edges have been partitioned - * This can avoid unneccessary walkthrough - */ -short RRGraph::node_num_non_configurable_out_edges(const RRNodeId& node) const { - return node_non_configurable_out_edges(node).size(); + return vtr::make_range((node_edges_[node].get() + node_num_in_edges_[node]), + (node_edges_[node].get() + node_num_in_edges_[node]) + node_num_out_edges_[node]); } /* Get the list of configurable edges from the input edges of a given node * And return the range(iterators) of the list */ RRGraph::edge_range RRGraph::node_configurable_in_edges(const RRNodeId& node) const { - /* Make sure we will access a valid node */ VTR_ASSERT_SAFE(valid_node_id(node)); - /* By default the configurable edges will be stored at the first part of the edge list (0 to XX) */ - auto begin = node_in_edges(node).begin(); - - /* By default the non-configurable edges will be stored at second part of the edge list (XX to end) */ - auto end = node_in_edges(node).end() - node_num_non_configurable_in_edges_[node]; - - return vtr::make_range(begin, end); + return vtr::make_range(node_edges_[node].get(), + node_edges_[node].get() + node_num_in_edges_[node] - node_num_non_configurable_in_edges_[node]); } -/* Get the list of configurable edges from the input edges of a given node +/* Get the list of non configurable edges from the input edges of a given node * And return the range(iterators) of the list */ RRGraph::edge_range RRGraph::node_non_configurable_in_edges(const RRNodeId& node) const { - /* Make sure we will access a valid node */ VTR_ASSERT_SAFE(valid_node_id(node)); - /* By default the configurable edges will be stored at the first part of the edge list (0 to XX) */ - auto begin = node_in_edges(node).end() - node_num_non_configurable_in_edges_[node]; - - /* By default the non-configurable edges will be stored at second part of the edge list (XX to end) */ - auto end = node_in_edges(node).end(); - - return vtr::make_range(begin, end); + return vtr::make_range(node_edges_[node].get() + node_num_in_edges_[node] - node_num_non_configurable_in_edges_[node], + node_edges_[node].get() + node_num_in_edges_[node]); } -/* Get the list of configurable edges from the input edges of a given node +/* Get the list of configurable edges from the output edges of a given node * And return the range(iterators) of the list */ RRGraph::edge_range RRGraph::node_configurable_out_edges(const RRNodeId& node) const { - /* Make sure we will access a valid node */ VTR_ASSERT_SAFE(valid_node_id(node)); - /* By default the configurable edges will be stored at the first part of the edge list (0 to XX) */ - auto begin = node_out_edges(node).begin(); - - /* By default the non-configurable edges will be stored at second part of the edge list (XX to end) */ - auto end = node_out_edges(node).end() - node_num_non_configurable_out_edges_[node]; - - return vtr::make_range(begin, end); + return vtr::make_range((node_edges_[node].get() + node_num_in_edges_[node]), + (node_edges_[node].get() + node_num_in_edges_[node]) + node_num_out_edges_[node] - node_num_non_configurable_out_edges_[node]); } -/* Get the list of configurable edges from the input edges of a given node +/* Get the list of non configurable edges from the output edges of a given node * And return the range(iterators) of the list */ RRGraph::edge_range RRGraph::node_non_configurable_out_edges(const RRNodeId& node) const { - /* Make sure we will access a valid node */ VTR_ASSERT_SAFE(valid_node_id(node)); - /* By default the configurable edges will be stored at the first part of the edge list (0 to XX) */ - auto begin = node_out_edges(node).end() - node_num_non_configurable_out_edges_[node]; - - /* By default the non-configurable edges will be stored at second part of the edge list (XX to end) */ - auto end = node_out_edges(node).end(); - - return vtr::make_range(begin, end); -} - -RRGraph::edge_range RRGraph::node_out_edges(const RRNodeId& node) const { - VTR_ASSERT_SAFE(valid_node_id(node)); - return vtr::make_range(node_out_edges_[node].begin(), node_out_edges_[node].end()); -} - -RRGraph::edge_range RRGraph::node_in_edges(const RRNodeId& node) const { - VTR_ASSERT_SAFE(valid_node_id(node)); - return vtr::make_range(node_in_edges_[node].begin(), node_in_edges_[node].end()); + return vtr::make_range((node_edges_[node].get() + node_num_in_edges_[node]) + node_num_out_edges_[node] - node_num_non_configurable_out_edges_[node], + (node_edges_[node].get() + node_num_in_edges_[node]) + node_num_out_edges_[node]); } //Edge attributes @@ -391,22 +352,22 @@ RRNodeId RRGraph::find_node(const short& x, const short& y, const t_rr_type& typ size_t iside = side; /* Check if x, y, type and ptc, side is valid */ - if ((x < 0) /* See if x is smaller than the index of first element */ - || (size_t(x) > node_lookup_.size() - 1)) { /* See if x is large than the index of last element */ + if ((x < 0) /* See if x is smaller than the index of first element */ + || (size_t(x) > node_lookup_.dim_size(0) - 1)) { /* See if x is large than the index of last element */ /* Return a zero range! */ return RRNodeId::INVALID(); } /* Check if x, y, type and ptc, side is valid */ - if ((y < 0) /* See if y is smaller than the index of first element */ - || (size_t(y) > node_lookup_[x].size() - 1)) { /* See if y is large than the index of last element */ + if ((y < 0) /* See if y is smaller than the index of first element */ + || (size_t(y) > node_lookup_.dim_size(1) - 1)) { /* See if y is large than the index of last element */ /* Return a zero range! */ return RRNodeId::INVALID(); } /* Check if x, y, type and ptc, side is valid */ /* itype is always larger than -1, we can skip checking */ - if (itype > node_lookup_[x][y].size() - 1) { /* See if type is large than the index of last element */ + if (itype > node_lookup_.dim_size(2) - 1) { /* See if type is large than the index of last element */ /* Return a zero range! */ return RRNodeId::INVALID(); } @@ -436,21 +397,21 @@ short RRGraph::chan_num_tracks(const short& x, const short& y, const t_rr_type& initialize_fast_node_lookup(); /* Check if x, y, type and ptc is valid */ - if ((x < 0) /* See if x is smaller than the index of first element */ - || (size_t(x) > node_lookup_.size() - 1)) { /* See if x is large than the index of last element */ + if ((x < 0) /* See if x is smaller than the index of first element */ + || (size_t(x) > node_lookup_.dim_size(0) - 1)) { /* See if x is large than the index of last element */ /* Return a zero range! */ return 0; } /* Check if x, y, type and ptc is valid */ - if ((y < 0) /* See if y is smaller than the index of first element */ - || (size_t(y) > node_lookup_[x].size() - 1)) { /* See if y is large than the index of last element */ + if ((y < 0) /* See if y is smaller than the index of first element */ + || (size_t(y) > node_lookup_.dim_size(1) - 1)) { /* See if y is large than the index of last element */ /* Return a zero range! */ return 0; } /* Check if x, y, type and ptc is valid */ - if ((size_t(type) > node_lookup_[x][y].size() - 1)) { /* See if type is large than the index of last element */ + if ((size_t(type) > node_lookup_.dim_size(2) - 1)) { /* See if type is large than the index of last element */ /* Return a zero range! */ return 0; } @@ -488,15 +449,20 @@ bool RRGraph::validate_node_segment(const RRNodeId& node) const { /* Check if the segment id of every node is in range */ bool RRGraph::validate_node_segments() const { bool all_valid = true; - for (auto node : nodes()) { - if (true == validate_node_segment(node)) { + for (size_t id = 0; id < num_nodes_; ++id) { + /* Try to find if this is an invalid id or not */ + if (!valid_node_id(RRNodeId(id))) { + /* Skip this id */ + continue; + } + if (true == validate_node_segment(RRNodeId(id))) { continue; } /* Reach here it means we find an invalid segment id */ all_valid = false; /* Print a warning! */ VTR_LOG_WARN("Node %d has an invalid segment id (%d)!\n", - size_t(node), size_t(node_segment(node))); + id, size_t(node_segment(RRNodeId(id)))); } return all_valid; } @@ -510,15 +476,20 @@ bool RRGraph::validate_edge_switch(const RREdgeId& edge) const { /* Check if the switch id of every edge is in range */ bool RRGraph::validate_edge_switches() const { bool all_valid = true; - for (auto edge : edges()) { - if (true == validate_edge_switch(edge)) { + for (size_t id = 0; id < num_edges_; ++id) { + /* Try to find if this is an invalid id or not */ + if (!valid_edge_id(RREdgeId(id))) { + /* Skip this id */ + continue; + } + if (true == validate_edge_switch(RREdgeId(id))) { continue; } /* Reach here it means we find an invalid segment id */ all_valid = false; /* Print a warning! */ VTR_LOG_WARN("Edge %d has an invalid switch id (%d)!\n", - size_t(edge), size_t(edge_switch(edge))); + id, size_t(edge_switch(RREdgeId(id)))); } return all_valid; } @@ -622,8 +593,13 @@ bool RRGraph::validate_node_edges(const RRNodeId& node) const { /* check if all the nodes' input edges are valid */ bool RRGraph::validate_nodes_in_edges() const { bool all_valid = true; - for (auto node : nodes()) { - if (true == validate_node_in_edges(node)) { + for (size_t id = 0; id < num_nodes_; ++id) { + /* Try to find if this is an invalid id or not */ + if (!valid_node_id(RRNodeId(id))) { + /* Skip this id */ + continue; + } + if (true == validate_node_in_edges(RRNodeId(id))) { continue; } /* Reach here, it means there is something wrong! @@ -637,8 +613,13 @@ bool RRGraph::validate_nodes_in_edges() const { /* check if all the nodes' output edges are valid */ bool RRGraph::validate_nodes_out_edges() const { bool all_valid = true; - for (auto node : nodes()) { - if (true == validate_node_out_edges(node)) { + for (size_t id = 0; id < num_nodes_; ++id) { + /* Try to find if this is an invalid id or not */ + if (!valid_node_id(RRNodeId(id))) { + /* Skip this id */ + continue; + } + if (true == validate_node_out_edges(RRNodeId(id))) { continue; } /* Reach here, it means there is something wrong! @@ -676,15 +657,20 @@ bool RRGraph::validate_edge_sink_node(const RREdgeId& edge) const { /* Check if source nodes of a edge are all valid */ bool RRGraph::validate_edge_src_nodes() const { bool all_valid = true; - for (auto edge : edges()) { - if (true == validate_edge_src_node(edge)) { + for (size_t id = 0; id < num_edges_; ++id) { + /* Try to find if this is an invalid id or not */ + if (!valid_edge_id(RREdgeId(id))) { + /* Skip this id */ + continue; + } + if (true == validate_edge_src_node(RREdgeId(id))) { continue; } /* Reach here, it means there is something wrong! * Print a warning */ VTR_LOG_WARN("Edge %d has a invalid source node %d!\n", - size_t(edge), size_t(edge_src_node(edge))); + id, size_t(edge_src_node(RREdgeId(id)))); all_valid = false; } return all_valid; @@ -693,15 +679,20 @@ bool RRGraph::validate_edge_src_nodes() const { /* Check if source nodes of a edge are all valid */ bool RRGraph::validate_edge_sink_nodes() const { bool all_valid = true; - for (auto edge : edges()) { - if (true == validate_edge_sink_node(edge)) { + for (size_t id = 0; id < num_edges_; ++id) { + /* Try to find if this is an invalid id or not */ + if (!valid_edge_id(RREdgeId(id))) { + /* Skip this id */ + continue; + } + if (true == validate_edge_sink_node(RREdgeId(id))) { continue; } /* Reach here, it means there is something wrong! * Print a warning */ VTR_LOG_WARN("Edge %d has a invalid sink node %d!\n", - size_t(edge), size_t(edge_sink_node(edge))); + id, size_t(edge_sink_node(RREdgeId(id)))); all_valid = false; } return all_valid; @@ -714,7 +705,6 @@ bool RRGraph::validate_edge_sink_nodes() const { * Warnings are thrown if optional checking fails */ bool RRGraph::validate() const { - bool check_flag = true; size_t num_err = 0; initialize_fast_node_lookup(); @@ -724,34 +714,32 @@ bool RRGraph::validate() const { */ if (false == validate_sizes()) { VTR_LOG_WARN("Fail in validating node- and edge-related vector sizes!\n"); - check_flag = false; num_err++; } /* Fundamental check */ if (false == validate_nodes_edges()) { VTR_LOG_WARN("Fail in validating edges connected to each node!\n"); - check_flag = false; num_err++; } if (false == validate_node_segments()) { VTR_LOG_WARN("Fail in validating segment IDs of nodes !\n"); - check_flag = false; num_err++; } if (false == validate_edge_switches()) { VTR_LOG_WARN("Fail in validating switch IDs of edges !\n"); - check_flag = false; num_err++; } /* Error out if there is any fatal errors found */ - VTR_LOG_ERROR("Routing Resource graph is not valid due to %d fatal errors !\n", - num_err); + if (0 < num_err) { + VTR_LOG_ERROR("Routing Resource graph is not valid due to %d fatal errors !\n", + num_err); + } - return check_flag; + return (0 == num_err); } bool RRGraph::is_dirty() const { @@ -767,55 +755,58 @@ void RRGraph::clear_dirty() { } /* Reserve a list of nodes */ -void RRGraph::reserve_nodes(const int& num_nodes) { +void RRGraph::reserve_nodes(const unsigned long& num_nodes) { /* Reserve the full set of vectors related to nodes */ /* Basic information */ - this->node_ids_.reserve(num_nodes); this->node_types_.reserve(num_nodes); + this->node_bounding_boxes_.reserve(num_nodes); + this->node_capacities_.reserve(num_nodes); this->node_ptc_nums_.reserve(num_nodes); + this->node_cost_indices_.reserve(num_nodes); this->node_directions_.reserve(num_nodes); this->node_sides_.reserve(num_nodes); this->node_Rs_.reserve(num_nodes); this->node_Cs_.reserve(num_nodes); this->node_segments_.reserve(num_nodes); + + /* Edge-related vectors */ + this->node_num_in_edges_.reserve(num_nodes); + this->node_num_out_edges_.reserve(num_nodes); this->node_num_non_configurable_in_edges_.reserve(num_nodes); this->node_num_non_configurable_out_edges_.reserve(num_nodes); - - /* Edge-relate vectors */ - this->node_in_edges_.reserve(num_nodes); - this->node_out_edges_.reserve(num_nodes); + this->node_edges_.reserve(num_nodes); } /* Reserve a list of edges */ -void RRGraph::reserve_edges(const int& num_edges) { +void RRGraph::reserve_edges(const unsigned long& num_edges) { /* Reserve the full set of vectors related to edges */ - this->edge_ids_.reserve(num_edges); this->edge_src_nodes_.reserve(num_edges); this->edge_sink_nodes_.reserve(num_edges); this->edge_switches_.reserve(num_edges); } /* Reserve a list of switches */ -void RRGraph::reserve_switches(const int& num_switches) { +void RRGraph::reserve_switches(const size_t& num_switches) { this->switch_ids_.reserve(num_switches); this->switches_.reserve(num_switches); } /* Reserve a list of segments */ -void RRGraph::reserve_segments(const int& num_segments) { +void RRGraph::reserve_segments(const size_t& num_segments) { this->segment_ids_.reserve(num_segments); this->segments_.reserve(num_segments); } /* Mutators */ RRNodeId RRGraph::create_node(const t_rr_type& type) { - //Allocate an ID - RRNodeId node_id = RRNodeId(node_ids_.size()); + /* Allocate an ID */ + RRNodeId node_id = RRNodeId(num_nodes_); + /* Expand range of node ids */ + num_nodes_++; - //Initialize the attributes - node_ids_.push_back(node_id); + /* Initialize the attributes */ node_types_.push_back(type); node_bounding_boxes_.emplace_back(-1, -1, -1, -1); @@ -827,12 +818,14 @@ RRNodeId RRGraph::create_node(const t_rr_type& type) { node_sides_.push_back(NUM_SIDES); node_Rs_.push_back(0.); node_Cs_.push_back(0.); + node_segments_.push_back(RRSegmentId::INVALID()); - node_in_edges_.emplace_back(); //Initially empty - node_out_edges_.emplace_back(); //Initially empty + node_edges_.emplace_back(); //Initially empty - node_num_non_configurable_in_edges_.emplace_back(); //Initially empty - node_num_non_configurable_out_edges_.emplace_back(); //Initially empty + node_num_in_edges_.emplace_back(0); + node_num_out_edges_.emplace_back(0); + node_num_non_configurable_in_edges_.emplace_back(0); + node_num_non_configurable_out_edges_.emplace_back(0); invalidate_fast_node_lookup(); @@ -846,19 +839,20 @@ RREdgeId RRGraph::create_edge(const RRNodeId& source, const RRNodeId& sink, cons VTR_ASSERT(valid_node_id(sink)); VTR_ASSERT(valid_switch_id(switch_id)); - //Allocate an ID - RREdgeId edge_id = RREdgeId(edge_ids_.size()); - - //Initialize the attributes - edge_ids_.push_back(edge_id); + /* Allocate an ID */ + RREdgeId edge_id = RREdgeId(num_edges_); + /* Expand range of edge ids */ + num_edges_++; + /* Initialize the attributes */ edge_src_nodes_.push_back(source); edge_sink_nodes_.push_back(sink); edge_switches_.push_back(switch_id); - //Add the edge to the nodes - node_out_edges_[source].push_back(edge_id); - node_in_edges_[sink].push_back(edge_id); + //We do not create the entry in node_edges_ here! + //For memory efficiency this is done when + //rebuild_node_edges() is called (i.e. after all + //edges have been created). VTR_ASSERT(validate_sizes()); @@ -902,7 +896,7 @@ void RRGraph::remove_node(const RRNodeId& node) { } //Mark node invalid - node_ids_[node] = RRNodeId::INVALID(); + invalid_node_ids_.insert(node); //Invalidate the node look-up invalidate_fast_node_lookup(); @@ -918,23 +912,24 @@ void RRGraph::remove_edge(const RREdgeId& edge) { RRNodeId src_node = edge_src_node(edge); RRNodeId sink_node = edge_sink_node(edge); - //Invalidate node to edge references - // TODO: consider making this optional (e.g. if called from remove_node) - for (size_t i = 0; i < node_out_edges_[src_node].size(); ++i) { - if (node_out_edges_[src_node][i] == edge) { - node_out_edges_[src_node][i] = RREdgeId::INVALID(); + /* Invalidate node to edge references + * TODO: consider making this optional (e.g. if called from remove_node) + */ + for (size_t i = 0; i < node_num_in_edges_[src_node]; ++i) { + if (node_edges_[src_node][i] == edge) { + node_edges_[src_node][i] = RREdgeId::INVALID(); break; } } - for (size_t i = 0; i < node_in_edges_[sink_node].size(); ++i) { - if (node_in_edges_[sink_node][i] == edge) { - node_in_edges_[sink_node][i] = RREdgeId::INVALID(); + for (size_t i = node_num_in_edges_[sink_node]; i < node_num_in_edges_[sink_node] + node_num_out_edges_[sink_node]; ++i) { + if (node_edges_[sink_node][i] == edge) { + node_edges_[sink_node][i] = RREdgeId::INVALID(); break; } } - //Mark edge invalid - edge_ids_[edge] = RREdgeId::INVALID(); + /* Mark edge invalid */ + invalid_edge_ids_.insert(edge); set_dirty(); } @@ -1047,94 +1042,150 @@ void RRGraph::set_node_segment(const RRNodeId& node, const RRSegmentId& segment_ node_segments_[node] = segment_id; } +void RRGraph::rebuild_node_edges() { + node_edges_.resize(nodes().size()); + node_num_in_edges_.resize(nodes().size(), 0); + node_num_out_edges_.resize(nodes().size(), 0); + node_num_non_configurable_in_edges_.resize(nodes().size(), 0); + node_num_non_configurable_out_edges_.resize(nodes().size(), 0); -/* For a given node in a rr_graph - * classify the edges of each node to be configurable (1st part) and non-configurable (2nd part) - */ -void RRGraph::partition_node_in_edges(const RRNodeId& node) { - //Partition the edges so the first set of edges are all configurable, and the later are not - auto first_non_config_edge = std::partition(node_in_edges_[node].begin(), node_in_edges_[node].end(), - [&](const RREdgeId edge) { return edge_is_configurable(edge); }); /* Condition to partition edges */ + //Count the number of edges of each type + for (RREdgeId edge : edges()) { + if (!edge) continue; - size_t num_conf_edges = std::distance(node_in_edges_[node].begin(), first_non_config_edge); - size_t num_non_conf_edges = node_in_edges_[node].size() - num_conf_edges; //Note we calculate using the size_t to get full range + RRNodeId src_node = edge_src_node(edge); + RRNodeId sink_node = edge_sink_node(edge); + bool config = edge_is_configurable(edge); - /* Check that within allowable range (no overflow when stored as num_non_configurable_edges_ - */ - VTR_ASSERT_MSG(num_non_conf_edges <= node_in_edges_[node].size(), - "Exceeded RR node maximum number of non-configurable input edges"); - - node_num_non_configurable_in_edges_[node] = num_non_conf_edges; //Narrowing -} - -/* For a given node in a rr_graph - * classify the edges of each node to be configurable (1st part) and non-configurable (2nd part) - */ -void RRGraph::partition_node_out_edges(const RRNodeId& node) { - //Partition the edges so the first set of edges are all configurable, and the later are not - auto first_non_config_edge = std::partition(node_out_edges_[node].begin(), node_out_edges_[node].end(), - [&](const RREdgeId edge) { return edge_is_configurable(edge); }); /* Condition to partition edges */ - - size_t num_conf_edges = std::distance(node_out_edges_[node].begin(), first_non_config_edge); - size_t num_non_conf_edges = node_out_edges_[node].size() - num_conf_edges; //Note we calculate using the size_t to get full range - - /* Check that within allowable range (no overflow when stored as num_non_configurable_edges_ - */ - VTR_ASSERT_MSG(num_non_conf_edges <= node_out_edges_[node].size(), - "Exceeded RR node maximum number of non-configurable output edges"); - - node_num_non_configurable_out_edges_[node] = num_non_conf_edges; //Narrowing -} - -/* For all nodes in a rr_graph - * classify the input edges of each node to be configurable (1st part) and non-configurable (2nd part) - */ -void RRGraph::partition_in_edges() { - /* For each node */ - for (auto node : nodes()) { - this->partition_node_in_edges(node); + ++node_num_out_edges_[src_node]; + ++node_num_in_edges_[sink_node]; + if (!config) { + ++node_num_non_configurable_out_edges_[src_node]; + ++node_num_non_configurable_in_edges_[sink_node]; + } } -} -/* For all nodes in a rr_graph - * classify the output edges of each node to be configurable (1st part) and non-configurable (2nd part) - */ -void RRGraph::partition_out_edges() { - /* For each node */ - for (auto node : nodes()) { - this->partition_node_out_edges(node); + //Allocate precisely the correct space for each nodes edge list + for (RRNodeId node : nodes()) { + if (!node) continue; + + node_edges_[node] = std::make_unique(node_num_in_edges_[node] + node_num_out_edges_[node]); } -} -/* For all nodes in a rr_graph - * classify both input and output edges of each node - * to be configurable (1st part) and non-configurable (2nd part) - */ -void RRGraph::partition_edges() { - /* Partition input edges */ - this->partition_in_edges(); - /* Partition output edges */ - this->partition_out_edges(); + //Insert the edges into the node lists + { + vtr::vector inserted_edge_cnt(nodes().size(), 0); + for (RREdgeId edge : edges()) { + RRNodeId src_node = edge_src_node(edge); + RRNodeId sink_node = edge_sink_node(edge); + + node_edges_[src_node][inserted_edge_cnt[src_node]++] = edge; + node_edges_[sink_node][inserted_edge_cnt[sink_node]++] = edge; + } + } + +#if 0 + //TODO: Sanity Check remove! + for (RRNodeId node : nodes()) { + for (size_t iedge = 0; iedge < node_num_in_edges_[node] + node_num_out_edges_[node]; ++iedge) { + RREdgeId edge = node_edges_[node][iedge]; + VTR_ASSERT(edge_src_node(edge) == node || edge_sink_node(edge) == node); + } + } +#endif + + //Partition each node's edge lists according to their type to line up with the counts + + auto is_configurable_edge = [&](const RREdgeId edge) { + return edge_is_configurable(edge); + }; + + for (RRNodeId node : nodes()) { + if (!node) continue; + + //We partition the nodes first by incoming/outgoing: + // + // +---------------------------+-----------------------------+ + // | in | out | + // +---------------------------+-----------------------------+ + // + //and then the two subsets by configurability. So the final ordering is: + // + // +-----------+---------------+------------+----------------+ + // | in_config | in_non_config | out_config | out_non_config | + // +-----------+---------------+------------+----------------+ + // + + //Partition first into incoming/outgoing + auto is_incoming_edge = [&](const RREdgeId edge) { + return edge_sink_node(edge) == node; + }; + std::partition(node_edges_[node].get(), + node_edges_[node].get() + node_num_in_edges_[node] + node_num_out_edges_[node], + is_incoming_edge); + + //Partition incoming by configurable/non-configurable + std::partition(node_edges_[node].get(), + node_edges_[node].get() + node_num_in_edges_[node], + is_configurable_edge); + + //Partition outgoing by configurable/non-configurable + std::partition(node_edges_[node].get() + node_num_in_edges_[node], + node_edges_[node].get() + node_num_in_edges_[node] + node_num_out_edges_[node], + is_configurable_edge); + +#if 0 + //TODO: Sanity check remove! + size_t nedges = node_num_in_edges_[node] + node_num_out_edges_[node]; + for (size_t iedge = 0; iedge < nedges; ++iedge) { + RREdgeId edge = node_edges_[node][iedge]; + if (iedge < node_num_in_edges_[node]) { //Incoming + VTR_ASSERT(edge_sink_node(edge) == node); + if (iedge < node_num_in_edges_[node] - node_num_non_configurable_in_edges_[node]) { + VTR_ASSERT(edge_is_configurable(edge)); + } else { + VTR_ASSERT(!edge_is_configurable(edge)); + } + } else { //Outgoing + VTR_ASSERT(edge_src_node(edge) == node); + if (iedge < node_num_in_edges_[node] + node_num_out_edges_[node] - node_num_non_configurable_out_edges_[node]) { + VTR_ASSERT(edge_is_configurable(edge)); + } else { + VTR_ASSERT(!edge_is_configurable(edge)); + } + } + } +#endif + } } void RRGraph::build_fast_node_lookup() const { + /* Free the current fast node look-up, we will rebuild a new one here */ invalidate_fast_node_lookup(); - for (auto node : nodes()) { + /* Get the max (x,y) and then we can resize the ndmatrix */ + vtr::Point max_coord(0, 0); + for (size_t id = 0; id < num_nodes_; ++id) { + /* Try to find if this is an invalid id or not */ + if (!valid_node_id(RRNodeId(id))) { + /* Skip this id */ + continue; + } + max_coord.set_x(std::max(max_coord.x(), node_xlow(RRNodeId(id)))); + max_coord.set_y(std::max(max_coord.y(), node_ylow(RRNodeId(id)))); + } + node_lookup_.resize({(size_t)max_coord.x() + 1, (size_t)max_coord.y() + 1, NUM_RR_TYPES + 1}); + + for (size_t id = 0; id < num_nodes_; ++id) { + /* Try to find if this is an invalid id or not */ + if (!valid_node_id(RRNodeId(id))) { + /* Skip this id */ + continue; + } + RRNodeId node = RRNodeId(id); size_t x = node_xlow(node); - if (x >= node_lookup_.size()) { - node_lookup_.resize(x + 1); - } - size_t y = node_ylow(node); - if (y >= node_lookup_[x].size()) { - node_lookup_[x].resize(y + 1); - } - size_t itype = node_type(node); - if (itype >= node_lookup_[x][y].size()) { - node_lookup_[x][y].resize(itype + 1); - } size_t ptc = node_ptc_num(node); if (ptc >= node_lookup_[x][y][itype].size()) { @@ -1172,11 +1223,13 @@ void RRGraph::initialize_fast_node_lookup() const { } bool RRGraph::valid_node_id(const RRNodeId& node) const { - return size_t(node) < node_ids_.size() && node_ids_[node] == node; + return (size_t(node) < num_nodes_) + && (!invalid_node_ids_.count(node)); } bool RRGraph::valid_edge_id(const RREdgeId& edge) const { - return size_t(edge) < edge_ids_.size() && edge_ids_[edge] == edge; + return (size_t(edge) < num_edges_) + && (!invalid_edge_ids_.count(edge)); } /* check if a given switch id is valid or not */ @@ -1205,26 +1258,25 @@ bool RRGraph::validate_sizes() const { } bool RRGraph::validate_node_sizes() const { - return node_types_.size() == node_ids_.size() - && node_bounding_boxes_.size() == node_ids_.size() - && node_capacities_.size() == node_ids_.size() - && node_ptc_nums_.size() == node_ids_.size() - && node_cost_indices_.size() == node_ids_.size() - && node_directions_.size() == node_ids_.size() - && node_sides_.size() == node_ids_.size() - && node_Rs_.size() == node_ids_.size() - && node_Cs_.size() == node_ids_.size() - && node_segments_.size() == node_ids_.size() - && node_num_non_configurable_in_edges_.size() == node_ids_.size() - && node_num_non_configurable_out_edges_.size() == node_ids_.size() - && node_in_edges_.size() == node_ids_.size() - && node_out_edges_.size() == node_ids_.size(); + return node_types_.size() == num_nodes_ + && node_bounding_boxes_.size() == num_nodes_ + && node_capacities_.size() == num_nodes_ + && node_ptc_nums_.size() == num_nodes_ + && node_cost_indices_.size() == num_nodes_ + && node_directions_.size() == num_nodes_ + && node_sides_.size() == num_nodes_ + && node_Rs_.size() == num_nodes_ + && node_Cs_.size() == num_nodes_ + && node_segments_.size() == num_nodes_ + && node_num_non_configurable_in_edges_.size() == num_nodes_ + && node_num_non_configurable_out_edges_.size() == num_nodes_ + && node_edges_.size() == num_nodes_; } bool RRGraph::validate_edge_sizes() const { - return edge_src_nodes_.size() == edge_ids_.size() - && edge_sink_nodes_.size() == edge_ids_.size() - && edge_switches_.size() == edge_ids_.size(); + return edge_src_nodes_.size() == num_edges_ + && edge_sink_nodes_.size() == num_edges_ + && edge_switches_.size() == num_edges_; } bool RRGraph::validate_switch_sizes() const { @@ -1236,8 +1288,8 @@ bool RRGraph::validate_segment_sizes() const { } void RRGraph::compress() { - vtr::vector node_id_map(node_ids_.size()); - vtr::vector edge_id_map(edge_ids_.size()); + vtr::vector node_id_map(num_nodes_); + vtr::vector edge_id_map(num_edges_); build_id_maps(node_id_map, edge_id_map); @@ -1248,17 +1300,48 @@ void RRGraph::compress() { invalidate_fast_node_lookup(); + /* Clear invalid node list */ + invalid_node_ids_.clear(); + + /* Clear invalid edge list */ + invalid_edge_ids_.clear(); + clear_dirty(); } void RRGraph::build_id_maps(vtr::vector& node_id_map, vtr::vector& edge_id_map) { - node_id_map = compress_ids(node_ids_); - edge_id_map = compress_ids(edge_ids_); + /* Build node ids including invalid ids and compress */ + vtr::vector node_ids; + for (size_t id = 0; id < num_nodes_; ++id) { + /* Try to find if this is an invalid id or not */ + if (!valid_node_id(RRNodeId(id))) { + /* Give and invalid id */ + node_ids.push_back(RRNodeId::INVALID()); + continue; + } + /* Reach here, this is a valid id, push to the edge list */ + node_ids.push_back(RRNodeId(id)); + } + node_id_map = compress_ids(node_ids); + + /* Build edge ids including invalid ids and compress */ + vtr::vector edge_ids; + for (size_t id = 0; id < num_edges_; ++id) { + /* Try to find if this is an invalid id or not */ + if (!valid_edge_id(RREdgeId(id))) { + /* Give and invalid id */ + edge_ids.push_back(RREdgeId::INVALID()); + continue; + } + /* Reach here, this is a valid id, push to the edge list */ + edge_ids.push_back(RREdgeId(id)); + } + edge_id_map = compress_ids(edge_ids); } void RRGraph::clean_nodes(const vtr::vector& node_id_map) { - node_ids_ = clean_and_reorder_ids(node_id_map); + num_nodes_ = node_id_map.size(); node_types_ = clean_and_reorder_values(node_types_, node_id_map); @@ -1272,39 +1355,46 @@ void RRGraph::clean_nodes(const vtr::vector& node_id_map) { node_Rs_ = clean_and_reorder_values(node_Rs_, node_id_map); node_Cs_ = clean_and_reorder_values(node_Cs_, node_id_map); - VTR_ASSERT(validate_node_sizes()); + node_segments_ = clean_and_reorder_values(node_segments_, node_id_map); + node_num_non_configurable_in_edges_ = clean_and_reorder_values(node_num_non_configurable_in_edges_, node_id_map); + node_num_non_configurable_out_edges_ = clean_and_reorder_values(node_num_non_configurable_out_edges_, node_id_map); + node_num_in_edges_ = clean_and_reorder_values(node_num_in_edges_, node_id_map); + node_num_out_edges_ = clean_and_reorder_values(node_num_out_edges_, node_id_map); + //node_edges_ = clean_and_reorder_values(node_edges_, node_id_map); - VTR_ASSERT_MSG(are_contiguous(node_ids_), "Ids should be contiguous"); - VTR_ASSERT_MSG(all_valid(node_ids_), "All Ids should be valid"); + VTR_ASSERT(validate_node_sizes()); } void RRGraph::clean_edges(const vtr::vector& edge_id_map) { - edge_ids_ = clean_and_reorder_ids(edge_id_map); + num_edges_ = edge_id_map.size(); edge_src_nodes_ = clean_and_reorder_values(edge_src_nodes_, edge_id_map); edge_sink_nodes_ = clean_and_reorder_values(edge_sink_nodes_, edge_id_map); edge_switches_ = clean_and_reorder_values(edge_switches_, edge_id_map); VTR_ASSERT(validate_edge_sizes()); - - VTR_ASSERT_MSG(are_contiguous(edge_ids_), "Ids should be contiguous"); - VTR_ASSERT_MSG(all_valid(edge_ids_), "All Ids should be valid"); } void RRGraph::rebuild_node_refs(const vtr::vector& edge_id_map) { - for (const auto& node : nodes()) { - node_in_edges_[node] = update_valid_refs(node_in_edges_[node], edge_id_map); - node_out_edges_[node] = update_valid_refs(node_out_edges_[node], edge_id_map); + for (size_t id = 0; id < num_nodes_; ++id) { + /* Try to find if this is an invalid id or not */ + if (!valid_node_id(RRNodeId(id))) { + /* Skip this id */ + continue; + } + RRNodeId node = RRNodeId(id); - VTR_ASSERT_MSG(all_valid(node_in_edges_[node]), "All Ids should be valid"); - VTR_ASSERT_MSG(all_valid(node_out_edges_[node]), "All Ids should be valid"); + auto begin = node_edges_[node].get(); + auto end = begin + node_num_in_edges_[node] + node_num_out_edges_[node]; + update_valid_refs(begin, end, edge_id_map); + + VTR_ASSERT_MSG(all_valid(begin, end), "All Ids should be valid"); } } /* Empty all the vectors related to nodes */ void RRGraph::clear_nodes() { - node_ids_.clear(); - + num_nodes_ = 0; node_types_.clear(); node_bounding_boxes_.clear(); @@ -1317,11 +1407,12 @@ void RRGraph::clear_nodes() { node_Cs_.clear(); node_segments_.clear(); + node_num_in_edges_.clear(); + node_num_out_edges_.clear(); node_num_non_configurable_in_edges_.clear(); node_num_non_configurable_out_edges_.clear(); - node_in_edges_.clear(); - node_out_edges_.clear(); + node_edges_.clear(); /* clean node_look_up */ node_lookup_.clear(); @@ -1329,7 +1420,7 @@ void RRGraph::clear_nodes() { /* Empty all the vectors related to edges */ void RRGraph::clear_edges() { - edge_ids_.clear(); + num_edges_ = 0; edge_src_nodes_.clear(); edge_sink_nodes_.clear(); edge_switches_.clear(); diff --git a/vpr/src/device/rr_graph_obj.h b/libs/librrgraph/src/rr_graph_obj.h similarity index 85% rename from vpr/src/device/rr_graph_obj.h rename to libs/librrgraph/src/rr_graph_obj.h index f34b11162..85bf7de4b 100644 --- a/vpr/src/device/rr_graph_obj.h +++ b/libs/librrgraph/src/rr_graph_obj.h @@ -198,30 +198,44 @@ /* Standard header files required go first */ #include #include +#include +#include /* EXTERNAL library header files go second*/ +#include "vtr_ndmatrix.h" #include "vtr_vector.h" #include "vtr_range.h" #include "vtr_geometry.h" #include "arch_types.h" /* VPR header files go third */ -#include "vpr_types.h" +#include "rr_graph_types.h" #include "rr_graph_fwd.h" class RRGraph { public: /* Types */ + //Lazy iterator utility forward declaration + template + class lazy_id_iterator; + /* Iterators used to create iterator-based loop for nodes/edges/switches/segments */ typedef vtr::vector::const_iterator node_iterator; typedef vtr::vector::const_iterator edge_iterator; typedef vtr::vector::const_iterator switch_iterator; typedef vtr::vector::const_iterator segment_iterator; + typedef lazy_id_iterator lazy_node_iterator; + typedef lazy_id_iterator lazy_edge_iterator; /* Ranges used to create range-based loop for nodes/edges/switches/segments */ typedef vtr::Range node_range; - typedef vtr::Range edge_range; + typedef vtr::Range edge_range; typedef vtr::Range switch_range; typedef vtr::Range segment_range; + typedef vtr::Range lazy_node_range; + typedef vtr::Range lazy_edge_range; + + public: /* Constructors */ + RRGraph(); public: /* Accessors */ /* Aggregates: create range-based loops for nodes/edges/switches/segments @@ -247,8 +261,8 @@ class RRGraph { * // Do something with each segment * } */ - node_range nodes() const; - edge_range edges() const; + lazy_node_range nodes() const; + lazy_edge_range edges() const; switch_range switches() const; segment_range segments() const; @@ -445,23 +459,9 @@ class RRGraph { */ RRSegmentId node_segment(const RRNodeId& node) const; - /* Get the number of non-configurable incoming edges to a node */ - short node_num_configurable_in_edges(const RRNodeId& node) const; - - /* Get the number of non-configurable outgoing edges from a node */ - short node_num_non_configurable_in_edges(const RRNodeId& node) const; - - /* Get the number of configurable output edges of a node */ - short node_num_configurable_out_edges(const RRNodeId& node) const; - - /* Get the number of non-configurable output edges of a node */ - short node_num_non_configurable_out_edges(const RRNodeId& node) const; - - /* Get the range (list) of edges related to a given node */ - edge_range node_configurable_in_edges(const RRNodeId& node) const; - edge_range node_non_configurable_in_edges(const RRNodeId& node) const; - edge_range node_configurable_out_edges(const RRNodeId& node) const; - edge_range node_non_configurable_out_edges(const RRNodeId& node) const; + //Returns an iterable range of all edges (incoming & outgoing) for + //the specified node + edge_range node_edges(const RRNodeId& node) const; /* Get a list of edge ids, which are incoming edges to a node */ edge_range node_in_edges(const RRNodeId& node) const; @@ -469,6 +469,12 @@ class RRGraph { /* Get a list of edge ids, which are outgoing edges from a node */ edge_range node_out_edges(const RRNodeId& node) const; + /* Get the range (list) of edges related to a given node */ + edge_range node_configurable_in_edges(const RRNodeId& node) const; + edge_range node_non_configurable_in_edges(const RRNodeId& node) const; + edge_range node_configurable_out_edges(const RRNodeId& node) const; + edge_range node_non_configurable_out_edges(const RRNodeId& node) const; + /* Edge-related attributes * An example to explain the terminology used in RRGraph * edgeA @@ -583,10 +589,10 @@ class RRGraph { * rr_graph.create_node(CHANX); * } */ - void reserve_nodes(const int& num_nodes); - void reserve_edges(const int& num_edges); - void reserve_switches(const int& num_switches); - void reserve_segments(const int& num_segments); + void reserve_nodes(const unsigned long& num_nodes); + void reserve_edges(const unsigned long& num_edges); + void reserve_switches(const size_t& num_switches); + void reserve_segments(const size_t& num_segments); /* Add new elements (node, edge, switch, etc.) to RRGraph */ /* Add a node to the RRGraph with a deposited type @@ -596,6 +602,7 @@ class RRGraph { * set_node_xlow(node, 0); */ RRNodeId create_node(const t_rr_type& type); + /* Add a edge to the RRGraph, by providing the source and sink node * This function will automatically create a node and * configure the nodes and edges in connection @@ -690,15 +697,14 @@ class RRGraph { /* Set the routing segment linked to a node, only applicable to CHANX and CHANY */ void set_node_segment(const RRNodeId& node, const RRSegmentId& segment_index); - /* Edge partitioning is performed for efficiency, - * so we can store configurable and non-configurable edge lists for a node in one vector, - * and efficiently iterate over all edges, or only the configurable or non-configurable subsets. - * This function will re-organize the incoming and outgoing edges of each node, - * i.e., node_in_edges() and node_out_edges(): - * 1. configurable edges (1st part of the vectors) - * 2. non-configurable edges (2nd part of the vectors) + /* + * Build the node to edge references to allow iteration through + * a node's in/out edges. + * + * Must be called before any node_*_in_edges() or node_*_out_edges() member + * functions can be called. */ - void partition_edges(); + void rebuild_node_edges(); /* Graph-level Clean-up, remove invalid nodes/edges etc. * This will clear the dirty flag (query by is_dirty()) of RRGraph object, if it was set @@ -708,18 +714,52 @@ class RRGraph { /* top-level function to free, should be called when to delete a RRGraph */ void clear(); - private: /* Internal Mutators to perform edge partitioning */ - /* classify the input edges of each node to be configurable (1st part) and non-configurable (2nd part) */ - void partition_node_in_edges(const RRNodeId& node); + public: /* Type implementations */ + /* + * This class (forward delcared above) is a template used to represent a lazily calculated + * iterator of the specified ID type. The key assumption made is that the ID space is + * contiguous and can be walked by incrementing the underlying ID value. To account for + * invalid IDs, it keeps a reference to the invalid ID set and returns ID::INVALID() for + * ID values in the set. + * + * It is used to lazily create an iteration range (e.g. as returned by RRGraph::edges() RRGraph::nodes()) + * just based on the count of allocated elements (i.e. RRGraph::num_nodes_ or RRGraph::num_edges_), + * and the set of any invalid IDs (i.e. RRGraph::invalid_node_ids_, RRGraph::invalid_edge_ids_). + */ + template + class lazy_id_iterator : public std::iterator { + public: + //Since we pass ID as a template to std::iterator we need to use an explicit 'typename' + //to bring the value_type and iterator names into scope + typedef typename std::iterator::value_type value_type; + typedef typename std::iterator::iterator iterator; - /* classify the output edges of each node to be configurable (1st part) and non-configurable (2nd part) */ - void partition_node_out_edges(const RRNodeId& node); + lazy_id_iterator(value_type init, const std::unordered_set& invalid_ids) + : value_(init) + , invalid_ids_(invalid_ids) {} - /* classify the input edges of each node to be configurable (1st part) and non-configurable (2nd part) */ - void partition_in_edges(); + //Advance to the next ID value + iterator operator++() { + value_ = ID(size_t(value_) + 1); + return *this; + } - /* classify the output edges of each node to be configurable (1st part) and non-configurable (2nd part) */ - void partition_out_edges(); + //Advance to the previous ID value + iterator operator--() { + value_ = ID(size_t(value_) - 1); + return *this; + } + + //Dereference the iterator + value_type operator*() const { return (invalid_ids_.count(value_)) ? ID::INVALID() : value_; } + + friend bool operator==(const lazy_id_iterator lhs, const lazy_id_iterator rhs) { return lhs.value_ == rhs.value_; } + friend bool operator!=(const lazy_id_iterator lhs, const lazy_id_iterator rhs) { return !(lhs == rhs); } + + private: + value_type value_; + const std::unordered_set& invalid_ids_; + }; private: /* Internal free functions */ void clear_nodes(); @@ -787,7 +827,9 @@ class RRGraph { private: /* Internal Data */ /* Node related data */ - vtr::vector node_ids_; /* Unique identifiers for the nodes */ + size_t num_nodes_; /* Range of node ids */ + std::unordered_set invalid_node_ids_; /* Invalid edge ids */ + vtr::vector node_types_; vtr::vector> node_bounding_boxes_; @@ -800,15 +842,44 @@ class RRGraph { vtr::vector node_Rs_; vtr::vector node_Cs_; vtr::vector node_segments_; /* Segment ids for each node */ - /* Record the dividing point between configurable and non-configurable edges for each node */ - vtr::vector node_num_non_configurable_in_edges_; - vtr::vector node_num_non_configurable_out_edges_; - vtr::vector> node_in_edges_; - vtr::vector> node_out_edges_; + /* + * We store the edges assoicated with each node in a single array per node (for memory efficiency). + * + * The array of edges is sorted into sub-ranges to allow for easy iteration (with node_in_edges(), + * node_non_configurable_out_edges() etc.). + * + * For a particular 'node' we store the sizes of the various sub-ranges in the node_num_* members, + * from which the delimiters for each sub-range can be calculated. + * + * + * node_edges_[node]: + * + * + * node_num_non_configurable_in_edges_[node] node_num_non_configurable_out_edges_[node] + * <-------------------> <--------------------> + * + * +------------------+---------------------+------------------+----------------------+ + * | in_edges_config | in_edges_non_config | out_edges_config | out_edges_non_config | + * +------------------+---------------------+------------------+----------------------+ + * + * <--------------------------------------> <---------------------------------------> + * node_num_in_edges_[node] node_num_out_edges_[node] + * + * All elements of node_edges_ should be initialized after all edges have been created (with create_edge()), + * by calling rebuild_node_edges(), which will allocate node_edges_, add the relevant edges and partition + * each node's arrays into the appropriate. rebuild_node_edges() also initializes all the node_num_* members + * based on the edges (created with create_edge()) in the edge_* members. + */ + vtr::vector node_num_in_edges_; + vtr::vector node_num_out_edges_; + vtr::vector node_num_non_configurable_in_edges_; + vtr::vector node_num_non_configurable_out_edges_; + vtr::vector> node_edges_; /* Edge related data */ - vtr::vector edge_ids_; /* unique identifiers for edges */ + size_t num_edges_; /* Range of edge ids */ + std::unordered_set invalid_edge_ids_; /* Invalid edge ids */ vtr::vector edge_src_nodes_; vtr::vector edge_sink_nodes_; vtr::vector edge_switches_; @@ -836,7 +907,7 @@ class RRGraph { /* Fast look-up to search a node by its type, coordinator and ptc_num * Indexing of fast look-up: [0..xmax][0..ymax][0..NUM_TYPES-1][0..ptc_max][0..NUM_SIDES-1] */ - typedef std::vector>>>> NodeLookup; + typedef vtr::NdMatrix>, 3> NodeLookup; mutable NodeLookup node_lookup_; }; diff --git a/vpr/src/device/rr_graph_obj_utils.h b/libs/librrgraph/src/rr_graph_obj_utils.h similarity index 87% rename from vpr/src/device/rr_graph_obj_utils.h rename to libs/librrgraph/src/rr_graph_obj_utils.h index 910b0ce3a..5c3a8f6d6 100644 --- a/vpr/src/device/rr_graph_obj_utils.h +++ b/libs/librrgraph/src/rr_graph_obj_utils.h @@ -38,6 +38,16 @@ bool all_valid(const Container& values) { return true; } +template +bool all_valid(Iterator begin, Iterator end) { + for (auto itr = begin; itr != end; ++itr) { + if (!*itr) { + return false; + } + } + return true; +} + //Builds a mapping from old to new ids by skipping values marked invalid template Container compress_ids(const Container& ids) { @@ -162,4 +172,20 @@ ValueContainer update_valid_refs(const ValueContainer& values, const IdContainer return updated; } +template +void update_valid_refs(Iterator begin, Iterator end, const IdContainer& id_map) { + for (auto itr = begin; itr != end; ++itr) { + auto orig_val = *itr; + if (orig_val) { + //Original item valid + + auto new_val = id_map[orig_val]; + if (new_val) { + //The original item exists in the new mapping + *itr = new_val; + } + } + } +} + #endif diff --git a/libs/librrgraph/src/rr_graph_types.h b/libs/librrgraph/src/rr_graph_types.h new file mode 100644 index 000000000..51cf3a0dd --- /dev/null +++ b/libs/librrgraph/src/rr_graph_types.h @@ -0,0 +1,43 @@ +#ifndef RR_GRAPH_TYPES +#define RR_GRAPH_TYPES + +/******************************************************************** + * Data types required by routing resource graph (RRGraph) definition + *******************************************************************/ + +/******************************************************************** + * Directionality of a routing track (node type CHANX and CHANY) in + * a routing resource graph + *******************************************************************/ +enum e_direction : unsigned char { + INC_DIRECTION = 0, + DEC_DIRECTION = 1, + BI_DIRECTION = 2, + NO_DIRECTION = 3, + NUM_DIRECTIONS +}; + +constexpr std::array DIRECTION_STRING = {{"INC_DIRECTION", "DEC_DIRECTION", "BI_DIRECTION", "NO_DIRECTION"}}; + +/* Type of a routing resource node. x-directed channel segment, * + * y-directed channel segment, input pin to a clb to pad, output * + * from a clb or pad (i.e. output pin of a net) and: * + * SOURCE: A dummy node that is a logical output within a block * + * -- i.e., the gate that generates a signal. * + * SINK: A dummy node that is a logical input within a block * + * -- i.e. the gate that needs a signal. */ +typedef enum e_rr_type : unsigned char { + SOURCE = 0, + SINK, + IPIN, + OPIN, + CHANX, + CHANY, + NUM_RR_TYPES +} t_rr_type; + +constexpr std::array RR_TYPES = {{SOURCE, SINK, IPIN, OPIN, CHANX, CHANY}}; +constexpr std::array rr_node_typename{{"SOURCE", "SINK", "IPIN", "OPIN", "CHANX", "CHANY"}}; + + +#endif diff --git a/vpr/src/device/rr_graph_util.cpp b/libs/librrgraph/src/rr_graph_util.cpp similarity index 100% rename from vpr/src/device/rr_graph_util.cpp rename to libs/librrgraph/src/rr_graph_util.cpp diff --git a/vpr/src/device/rr_graph_util.h b/libs/librrgraph/src/rr_graph_util.h similarity index 100% rename from vpr/src/device/rr_graph_util.h rename to libs/librrgraph/src/rr_graph_util.h diff --git a/libs/librrgraph/test/test_rr_graph.cpp b/libs/librrgraph/test/test_rr_graph.cpp new file mode 100644 index 000000000..2bd143b81 --- /dev/null +++ b/libs/librrgraph/test/test_rr_graph.cpp @@ -0,0 +1,8 @@ +#include "vtr_geometry.h" +#include "rr_graph_obj.h" + +int main(int argc, char** argv) { + RRGraph rr_graph_obj; + + return 0; +} diff --git a/libs/libvtrutil/src/vtr_geometry.tpp b/libs/libvtrutil/src/vtr_geometry.tpp index 608b09c4c..f0151df2a 100644 --- a/libs/libvtrutil/src/vtr_geometry.tpp +++ b/libs/libvtrutil/src/vtr_geometry.tpp @@ -79,6 +79,12 @@ Rect::Rect(Point bottom_left_val, Point top_right_val) //pass } +template +Rect::Rect() { + //pass +} + + template T Rect::xmin() const { return bottom_left_.x(); diff --git a/vpr/CMakeLists.txt b/vpr/CMakeLists.txt index 4d06b1e1a..217a7bb8d 100644 --- a/vpr/CMakeLists.txt +++ b/vpr/CMakeLists.txt @@ -58,6 +58,7 @@ set_target_properties(libvpr8 PROPERTIES PREFIX "") #Avoid extra 'lib' prefix target_link_libraries(libvpr8 libvtrutil libarchfpga + librrgraph libsdcparse libblifparse libtatum diff --git a/vpr/src/base/vpr_types.h b/vpr/src/base/vpr_types.h index e6c010f73..cb38f75ee 100644 --- a/vpr/src/base/vpr_types.h +++ b/vpr/src/base/vpr_types.h @@ -40,6 +40,9 @@ #include "vtr_flat_map.h" #include "vtr_cache.h" +/* Header for rr_graph related definition */ +#include "rr_graph_types.h" + /******************************************************************************* * Global data types and constants ******************************************************************************/ @@ -1011,15 +1014,6 @@ struct t_det_routing_arch { std::string write_rr_graph_filename; }; -enum e_direction : unsigned char { - INC_DIRECTION = 0, - DEC_DIRECTION = 1, - BI_DIRECTION = 2, - NO_DIRECTION = 3, - NUM_DIRECTIONS -}; - -constexpr std::array DIRECTION_STRING = {{"INC_DIRECTION", "DEC_DIRECTION", "BI_DIRECTION", "NO_DIRECTION"}}; /* Lists detailed information about segmentation. [0 .. W-1]. * * length: length of segment. * @@ -1132,26 +1126,6 @@ struct t_linked_f_pointer { typedef std::vector>>>> t_rr_node_indices; //[0..num_rr_types-1][0..grid_width-1][0..grid_height-1][0..NUM_SIDES-1][0..max_ptc-1] -/* Type of a routing resource node. x-directed channel segment, * - * y-directed channel segment, input pin to a clb to pad, output * - * from a clb or pad (i.e. output pin of a net) and: * - * SOURCE: A dummy node that is a logical output within a block * - * -- i.e., the gate that generates a signal. * - * SINK: A dummy node that is a logical input within a block * - * -- i.e. the gate that needs a signal. */ -typedef enum e_rr_type : unsigned char { - SOURCE = 0, - SINK, - IPIN, - OPIN, - CHANX, - CHANY, - NUM_RR_TYPES -} t_rr_type; - -constexpr std::array RR_TYPES = {{SOURCE, SINK, IPIN, OPIN, CHANX, CHANY}}; -constexpr std::array rr_node_typename{{"SOURCE", "SINK", "IPIN", "OPIN", "CHANX", "CHANY"}}; - /* Basic element used to store the traceback (routing) of each net. * * index: Array index (ID) of this routing resource node. * * iswitch: Index of the switch type used to go from this rr_node to * From 9269d7106d70ff1344c5522d8922e31e7eda1757 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 31 Jan 2020 15:42:44 -0700 Subject: [PATCH 061/645] move rr_graph back to vpr because the reader and writer requires too much dependency on the core engine --- libs/CMakeLists.txt | 1 - libs/librrgraph/CMakeLists.txt | 35 - libs/librrgraph/test/test_rr_graph.cpp | 8 - libs/libvtrutil/stWnhl1a | 0 vpr/CMakeLists.txt | 1 - .../src/device}/check_rr_graph_obj.cpp | 0 .../src/device}/check_rr_graph_obj.h | 0 .../src => vpr/src/device}/rr_graph_fwd.h | 0 .../src => vpr/src/device}/rr_graph_obj.cpp | 0 .../src => vpr/src/device}/rr_graph_obj.h | 0 .../src/device}/rr_graph_obj_utils.h | 0 .../src => vpr/src/device}/rr_graph_types.h | 0 .../src => vpr/src/device}/rr_graph_util.cpp | 0 .../src => vpr/src/device}/rr_graph_util.h | 0 vpr/src/device/write_xml_rr_graph_obj.cpp | 222 ++++++ vpr/src/device/write_xml_rr_graph_obj.h | 12 + vpr/valgrind.supp | 711 ------------------ 17 files changed, 234 insertions(+), 756 deletions(-) delete mode 100644 libs/librrgraph/CMakeLists.txt delete mode 100644 libs/librrgraph/test/test_rr_graph.cpp delete mode 100644 libs/libvtrutil/stWnhl1a rename {libs/librrgraph/src => vpr/src/device}/check_rr_graph_obj.cpp (100%) rename {libs/librrgraph/src => vpr/src/device}/check_rr_graph_obj.h (100%) rename {libs/librrgraph/src => vpr/src/device}/rr_graph_fwd.h (100%) rename {libs/librrgraph/src => vpr/src/device}/rr_graph_obj.cpp (100%) rename {libs/librrgraph/src => vpr/src/device}/rr_graph_obj.h (100%) rename {libs/librrgraph/src => vpr/src/device}/rr_graph_obj_utils.h (100%) rename {libs/librrgraph/src => vpr/src/device}/rr_graph_types.h (100%) rename {libs/librrgraph/src => vpr/src/device}/rr_graph_util.cpp (100%) rename {libs/librrgraph/src => vpr/src/device}/rr_graph_util.h (100%) create mode 100644 vpr/src/device/write_xml_rr_graph_obj.cpp create mode 100644 vpr/src/device/write_xml_rr_graph_obj.h delete mode 100644 vpr/valgrind.supp diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index a85d179f8..8d09c541d 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -5,7 +5,6 @@ add_subdirectory(EXTERNAL) # Only add warn flags for VPR internal libraries. #add_compile_options(${WARN_FLAGS}) add_subdirectory(libarchfpga) -add_subdirectory(librrgraph) add_subdirectory(libvtrutil) add_subdirectory(liblog) add_subdirectory(libpugiutil) diff --git a/libs/librrgraph/CMakeLists.txt b/libs/librrgraph/CMakeLists.txt deleted file mode 100644 index 52f46a170..000000000 --- a/libs/librrgraph/CMakeLists.txt +++ /dev/null @@ -1,35 +0,0 @@ -cmake_minimum_required(VERSION 3.9) - -project("librrgraph") - -file(GLOB_RECURSE EXEC_SOURCE test/test_rr_graph.cpp) -file(GLOB_RECURSE LIB_SOURCES src/*.cpp) -file(GLOB_RECURSE LIB_HEADERS src/*.h) -files_to_dirs(LIB_HEADERS LIB_INCLUDE_DIRS) - -#Remove test executable from library -list(REMOVE_ITEM LIB_SOURCES ${EXEC_SOURCE}) - -#Create the library -add_library(librrgraph STATIC - ${LIB_HEADERS} - ${LIB_SOURCES}) -target_include_directories(librrgraph PUBLIC ${LIB_INCLUDE_DIRS}) -set_target_properties(librrgraph PROPERTIES PREFIX "") #Avoid extra 'lib' prefix - -#Specify link-time dependancies -target_link_libraries(librrgraph - libarchfpga - libvtrutil) - -#Create the test executable -add_executable(test_rr_graph ${EXEC_SOURCE}) -target_link_libraries(test_rr_graph librrgraph) - -#Supress IPO link warnings if IPO is enabled -#get_target_property(READ_ARCH_USES_IPO read_arch_openfpga INTERPROCEDURAL_OPTIMIZATION) -#if (READ_ARCH_USES_IPO) -# set_target_properties(read_arch_openfpga PROPERTIES LINK_FLAGS ${IPO_LINK_WARN_SUPRESS_FLAGS}) -#endif() - -#install(TARGETS libarchopenfpga read_arch_openfpga DESTINATION bin) diff --git a/libs/librrgraph/test/test_rr_graph.cpp b/libs/librrgraph/test/test_rr_graph.cpp deleted file mode 100644 index 2bd143b81..000000000 --- a/libs/librrgraph/test/test_rr_graph.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "vtr_geometry.h" -#include "rr_graph_obj.h" - -int main(int argc, char** argv) { - RRGraph rr_graph_obj; - - return 0; -} diff --git a/libs/libvtrutil/stWnhl1a b/libs/libvtrutil/stWnhl1a deleted file mode 100644 index e69de29bb..000000000 diff --git a/vpr/CMakeLists.txt b/vpr/CMakeLists.txt index 217a7bb8d..4d06b1e1a 100644 --- a/vpr/CMakeLists.txt +++ b/vpr/CMakeLists.txt @@ -58,7 +58,6 @@ set_target_properties(libvpr8 PROPERTIES PREFIX "") #Avoid extra 'lib' prefix target_link_libraries(libvpr8 libvtrutil libarchfpga - librrgraph libsdcparse libblifparse libtatum diff --git a/libs/librrgraph/src/check_rr_graph_obj.cpp b/vpr/src/device/check_rr_graph_obj.cpp similarity index 100% rename from libs/librrgraph/src/check_rr_graph_obj.cpp rename to vpr/src/device/check_rr_graph_obj.cpp diff --git a/libs/librrgraph/src/check_rr_graph_obj.h b/vpr/src/device/check_rr_graph_obj.h similarity index 100% rename from libs/librrgraph/src/check_rr_graph_obj.h rename to vpr/src/device/check_rr_graph_obj.h diff --git a/libs/librrgraph/src/rr_graph_fwd.h b/vpr/src/device/rr_graph_fwd.h similarity index 100% rename from libs/librrgraph/src/rr_graph_fwd.h rename to vpr/src/device/rr_graph_fwd.h diff --git a/libs/librrgraph/src/rr_graph_obj.cpp b/vpr/src/device/rr_graph_obj.cpp similarity index 100% rename from libs/librrgraph/src/rr_graph_obj.cpp rename to vpr/src/device/rr_graph_obj.cpp diff --git a/libs/librrgraph/src/rr_graph_obj.h b/vpr/src/device/rr_graph_obj.h similarity index 100% rename from libs/librrgraph/src/rr_graph_obj.h rename to vpr/src/device/rr_graph_obj.h diff --git a/libs/librrgraph/src/rr_graph_obj_utils.h b/vpr/src/device/rr_graph_obj_utils.h similarity index 100% rename from libs/librrgraph/src/rr_graph_obj_utils.h rename to vpr/src/device/rr_graph_obj_utils.h diff --git a/libs/librrgraph/src/rr_graph_types.h b/vpr/src/device/rr_graph_types.h similarity index 100% rename from libs/librrgraph/src/rr_graph_types.h rename to vpr/src/device/rr_graph_types.h diff --git a/libs/librrgraph/src/rr_graph_util.cpp b/vpr/src/device/rr_graph_util.cpp similarity index 100% rename from libs/librrgraph/src/rr_graph_util.cpp rename to vpr/src/device/rr_graph_util.cpp diff --git a/libs/librrgraph/src/rr_graph_util.h b/vpr/src/device/rr_graph_util.h similarity index 100% rename from libs/librrgraph/src/rr_graph_util.h rename to vpr/src/device/rr_graph_util.h diff --git a/vpr/src/device/write_xml_rr_graph_obj.cpp b/vpr/src/device/write_xml_rr_graph_obj.cpp new file mode 100644 index 000000000..375d1ba52 --- /dev/null +++ b/vpr/src/device/write_xml_rr_graph_obj.cpp @@ -0,0 +1,222 @@ +/********************************************************************* + * This file defines the writing rr graph function in XML format. + * The rr graph is separated into channels, nodes, switches, + * grids, edges, block types, and segments. Each tag has several + * children tags such as timing, location, or some general + * details. Each tag has attributes to describe them + ********************************************************************/ + +#include +#include +#include +#include +#include +#include "vpr_error.h" +#include "globals.h" +#include "read_xml_arch_file.h" +#include "vtr_version.h" +#include "rr_graph_obj.h" +#include "write_xml_rr_graph_obj.h" + +using namespace std; + +/* All values are printed with this precision value. The higher the + * value, the more accurate the read in rr graph is. Using numeric_limits + * max_digits10 guarentees that no values change during a sequence of + * float -> string -> float conversions */ +constexpr int FLOAT_PRECISION = std::numeric_limits::max_digits10; + +/*********************** External Subroutines to this module *******************/ +void add_metadata_to_xml(fstream &fp, const char *tab_prefix, const t_metadata_dict & meta); +void write_rr_channel(fstream &fp); +void write_rr_grid(fstream &fp); +void write_rr_block_types(fstream &fp); + +/*********************** Subroutines local to this module *******************/ +void write_rr_graph_node(fstream &fp, const RRGraph& rr_graph); +void write_rr_graph_switches(fstream &fp, const RRGraph& rr_graph); +void write_rr_graph_edges(fstream &fp, const RRGraph& rr_graph); +void write_rr_graph_segments(fstream &fp, const RRGraph& rr_graph); + +/************************ Subroutine definitions ****************************/ + +/* This function is used to write the rr_graph into xml format into a a file with name: file_name */ +void write_xml_rr_graph_obj(const char *file_name, const RRGraph& rr_graph) { + fstream fp; + fp.open(file_name, fstream::out | fstream::trunc); + + /* Prints out general info for easy error checking*/ + if (!fp.is_open() || !fp.good()) { + vpr_throw(VPR_ERROR_OTHER, __FILE__, __LINE__, + "couldn't open file \"%s\" for generating RR graph file\n", file_name); + } + cout << "Writing RR graph" << endl; + fp << "" << endl; + + /* Write out each individual component + * Use existing write_rr_* functions as much as possible + * 1. For those using external data structures outside RRGraph, + * leave as it is. + * 2. For those using RRGraph internal data, + * write new functions + */ + write_rr_channel(fp); + write_rr_graph_switches(fp, rr_graph); + write_rr_graph_segments(fp, rr_graph); + write_rr_block_types(fp); + write_rr_grid(fp); + write_rr_graph_node(fp, rr_graph); + write_rr_graph_edges(fp, rr_graph); + fp << ""; + + fp.close(); + + cout << "Finished generating RR graph file named " << file_name << endl << endl; +} + +/* All relevant rr node info is written out to the graph. + * This includes location, timing, and segment info + */ +void write_rr_graph_node(fstream &fp, const RRGraph& rr_graph) { + /* TODO: we should make it function full independent from device_ctx !!! */ + auto& device_ctx = g_vpr_ctx.device(); + + fp << "\t" << endl; + + for (auto node : rr_graph.nodes()) { + fp << "\t\t" << endl; + fp << "\t\t\t" << endl; + fp << "\t\t\t" << endl; + + if (RRSegmentId::INVALID() != rr_graph.node_segment(node)) { + fp << "\t\t\t" << endl; + } + + const auto iter = device_ctx.rr_node_metadata.find(rr_graph.node_index(node)); + if(iter != device_ctx.rr_node_metadata.end()) { + const t_metadata_dict & meta = iter->second; + add_metadata_to_xml(fp, "\t\t\t", meta); + } + + fp << "\t\t" << endl; + } + + fp << "\t" << endl << endl; + + return; +} + +/* Segment information in the t_segment_inf data structure is written out. + * Information includes segment id, name, and optional timing parameters + */ +void write_rr_graph_segments(fstream &fp, const RRGraph& rr_graph) { + fp << "\t" << endl; + + for (auto seg : rr_graph.segments()) { + fp << "\t\t" << endl; + fp << "\t\t\t" << endl; + fp << "\t\t" << endl; + } + fp << "\t" << endl << endl; + + return; +} + +/* Switch info is written out into xml format. This includes + * general, sizing, and optional timing information + */ +void write_rr_graph_switches(fstream &fp, const RRGraph& rr_graph) { + fp << "\t" << endl; + + for (auto rr_switch : rr_graph.switches()) { + fp << "\t\t" << endl; + + fp << "\t\t\t" << endl; + fp << "\t\t\t" << endl; + fp << "\t\t" << endl; + } + fp << "\t" << endl << endl; + + return; +} + +/* Edges connecting to each rr node is printed out. The two nodes + * it connects to are also printed + */ +void write_rr_graph_edges(fstream &fp, const RRGraph& rr_graph) { + auto& device_ctx = g_vpr_ctx.device(); + fp << "\t" << endl; + + for (auto node : rr_graph.nodes()) { + for (auto edge: rr_graph.node_out_edges(node)) { + fp << "\t\t" << endl; + + const t_metadata_dict & meta = iter->second; + add_metadata_to_xml(fp, "\t\t\t", meta); + wrote_edge_metadata = true; + } + + if(wrote_edge_metadata == false) { + fp << "/>" << endl; + } else { + fp << "\t\t" << endl; + } + } + } + fp << "\t" << endl << endl; +} diff --git a/vpr/src/device/write_xml_rr_graph_obj.h b/vpr/src/device/write_xml_rr_graph_obj.h new file mode 100644 index 000000000..7337ff958 --- /dev/null +++ b/vpr/src/device/write_xml_rr_graph_obj.h @@ -0,0 +1,12 @@ +/********************************************************************* + * This function writes the RR_graph generated by VPR into a file in XML format + * Information included in the file includes rr nodes, rr switches, + * the grid, block info, node indices + ********************************************************************/ + +#ifndef WRITE_XML_RR_GRAPH_OBJ_H +#define WRITE_XML_RR_GRAPH_OBJ_H + +void write_xml_rr_graph_obj(const char *file_name, const RRGraph& rr_graph); + +#endif diff --git a/vpr/valgrind.supp b/vpr/valgrind.supp deleted file mode 100644 index 6bd7272c4..000000000 --- a/vpr/valgrind.supp +++ /dev/null @@ -1,711 +0,0 @@ -# -# Valgrind suppression file for EZGL -# - -#pango -{ - libpango - Memcheck:Leak - ... - obj:/usr/lib*/libpango* -} - -#GTK -{ - g_type_register - Memcheck:Leak - fun:*alloc - ... - fun:g_type_register_* - ... - fun:_dl_init - ... -} - -{ - g_quark_from_static_string - Memcheck:Leak - fun:*alloc - ... - fun:g_quark_from_static_string - ... - fun:_dl_init - ... -} - -{ - g_main_thread - Memcheck:Leak - fun:*alloc - ... - fun:g_main* - ... - fun:start_thread - fun:clone -} - -{ - g_closure - Memcheck:Leak - match-leak-kinds:possible - fun:*alloc - ... - fun:g_cclosure_new - fun:g_signal_connect_data - ... -} - -# -{ - g_object - Memcheck:Leak - match-leak-kinds:possible - ... - fun:g_object_new - ... -} - -{ - g_type_register_static - Memcheck:Leak - match-leak-kinds:possible - ... - fun:g_type_register_static - ... -} - -{ - g_signal_connect_closure - Memcheck:Leak - match-leak-kinds:possible - ... - fun:g_signal_connect_closure - fun:gtk_*group* - ... -} - -{ - gtk_module_init - Memcheck:Leak - - fun:*alloc - ... - fun:gtk_module_init - ... -} - -{ - g_closure_invoke - Memcheck:Leak - - fun:*alloc - ... - fun:g_closure_invoke - ... -} - -{ - gtk_style_context_set_state - Memcheck:Leak - - fun:*alloc - ... - fun:gtk_style_context_set_state - ... -} - -# -{ - call_init - Memcheck:Leak - - fun:*alloc - ... - fun:call_init - fun:_dl_init - ... -} - -{ - XML_ParseBuffer - Memcheck:Leak - fun:*alloc - ... - fun:XML_ParseBuffer - ... -} - -{ - FcConfigParseAndLoad - Memcheck:Leak - fun:*alloc - ... - fun:FcConfigParseAndLoad - ... -} - -# -{ - g_objectv - Memcheck:Leak - - ... - fun:g_object_newv - ... -} - -{ - g_type_add_interface_static - Memcheck:Leak - match-leak-kinds:possible - ... - fun:g_type_add_interface_static - ... -} - -{ - gtk_container_get_children - Memcheck:Leak - fun:*alloc - ... - fun:gtk_container_get_children - ... -} - -{ - cairo_select_font_face - Memcheck:Leak - fun:*alloc - ... - fun:cairo_select_font_face - ... -} - -{ - libfontconfig - Memcheck:Leak - fun:*alloc - ... - obj:*libfontconfig.so.* - ... -} - -{ - X11_XGetDefault - Memcheck:Leak - fun:realloc - obj:*libX11.so.6.3.0 - obj:*libX11.so.6.3.0 - obj:*libX11.so.6.3.0 - fun:_XlcCreateLC - fun:_XlcDefaultLoader - fun:_XOpenLC - fun:_XrmInitParseInfo - obj:*libX11.so.6.3.0 - fun:XrmGetStringDatabase - obj:*libX11.so.6.3.0 - fun:XGetDefault -} - -{ - XInternAtom_via_event_loop - Memcheck:Leak - fun:*alloc - fun:_XEnq - obj:*libX11.so.6.3.0 - fun:_XReply - fun:XInternAtom - ... -} - -{ - cairo_deep_*alloc - Memcheck:Leak - fun:*alloc - obj:*libcairo.* - ... -} - -#openmp -{ - GOMP_parallel - Memcheck:Leak - - fun:*alloc - fun:allocate_dtv - fun:_dl_allocate_tls - fun:allocate_stack - fun:pthread_create@@GLIBC_2.2.5 - ... -} - - -#GTK engines -{ - engines - Memcheck:Leak - fun:*alloc - ... - obj:/usr/lib*/gtk*/*/engines* - ... - obj:/usr/lib*/libgtk* -} - -#nvidia -{ - libGL - Memcheck:Leak - ... - obj:/usr/lib*/libGL.so* -} -{ - - Memcheck:Leak - fun:*alloc - ... - fun:g_socket_create_source - ... - fun:g_main_context_dispatch -} -{ - - Memcheck:Leak - fun:*alloc - ... - fun:g_type_class_ref - ... - fun:gtk_init_check - fun:gtk_init - obj:*libgtk-3* -} -{ - - Memcheck:Leak - fun:*alloc - ... - fun:g_markup_parse_context_parse - obj:*libgtk-3* -} -{ - - Memcheck:Leak - fun:*alloc - ... - fun:g_socket_new - obj:*libgio-2* - fun:g_socket_client_connect - obj:*libgio-2* -} -{ - - Memcheck:Leak - fun:*alloc - ... - fun:atk_add_focus_tracker - obj:*libgtk-3* - ... - fun:gtk_parse_args - fun:gtk_init_check -} -{ - - Memcheck:Leak - fun:m*alloc - ... - fun:g_bus_get_sync - ... - fun:g_application_register - obj:*libgio-2* - fun:g_application_run -} -{ - - Memcheck:Leak - fun:*alloc - ... - obj:*libgtk-3* - fun:g_closure_invoke - ... - fun:g_signal_emit_valist - fun:g_signal_emit -} -{ - - Memcheck:Leak - fun:*alloc - ... - fun:g_simple_async_result_complete - ... - fun:g_main_context_dispatch - ... -} -{ - - Memcheck:Leak - fun:*alloc - ... - fun:g_type_class_ref - ... - fun:atk_add_focus_tracker - obj:*libgtk-3* - ... - fun:g_option_context_parse -} -{ - - Memcheck:Leak - fun:*alloc - ... - fun:g_type_class_ref - obj:*libgtk-3* - fun:atk_add_focus_tracker - obj:*libgtk-3* -} -{ - - Memcheck:Leak - fun:*alloc - ... - fun:g_type_class_ref - obj:*libgtk-3* -} -{ - - Memcheck:Leak - fun:*alloc - ... - fun:g_dbus_proxy_new_sync - obj:*libgtk-3* -} -{ - - Memcheck:Leak - fun:*alloc - ... - fun:g_signal_new - ... - obj:*libgobject-2* - fun:g_type_class_ref - fun:g_object_new_valist - fun:g_initable_new_valist - fun:g_initable_new -} -{ - - Memcheck:Leak - fun:*alloc - ... - fun:g_signal_new - obj:*libgtk-3* - fun:g_type_class_ref - obj:*libgtk-3* - ... -} -{ - - Memcheck:Leak - fun:*alloc - ... - fun:g_signal_new - obj:*libgtk-3* - ... - fun:g_type_class_ref - obj:*libgtk-3* - ... -} -{ - - Memcheck:Leak - - fun:*alloc - ... - fun:g_type_class_ref - obj:*libgtk-3* - fun:atk_add_focus_tracker - obj:*libgtk-3* - ... - fun:g_option_context_parse -} -{ - - Memcheck:Leak - - fun:*alloc - ... - fun:g_signal_new_class_handler - obj:*libgtk-3* - ... - fun:g_type_class_ref - obj:*libgtk-3* - ... -} -{ - - Memcheck:Leak - fun:*alloc - ... - fun:g_signal_connect_data - obj:*libgtk-3* - ... - fun:gtk_widget_show - obj:*libgtk-3* - ... -} -{ - - Memcheck:Leak - fun:*alloc - ... - fun:g_type_class_ref - fun:g_object_new_valist - fun:g_initable_new_valist - fun:g_initable_new - fun:gvfs_dbus_mount_tracker_proxy_new_for_bus_sync - ... - fun:g_type_create_instance -} -{ - - Memcheck:Leak - fun:*alloc - fun:g_*alloc - ... - fun:g_type_class_ref - obj:*libgtk-3* - ... - fun:g_markup_parse_context_parse -} -{ - - Memcheck:Leak - fun:*alloc - ... - fun:g_type_class_ref - obj:*libgtk-3* - ... - fun:g_option_context_parse - fun:gtk_parse_args - fun:gtk_init_check -} -{ - - Memcheck:Leak - fun:*alloc - ... - fun:g_type_class_ref - fun:atk_bridge_adaptor_init - obj:*libgtk-3* - ... - fun:g_option_context_parse - fun:gtk_parse_args - fun:gtk_init_check - fun:gtk_init -} -{ - - Memcheck:Leak - fun:*alloc - ... - fun:g_signal_new - ... - fun:g_type_class_ref - obj:*libgtk-3* - fun:atk_add_focus_tracker - obj:*libgtk-3* -} -{ - - Memcheck:Leak - - fun:*alloc - ... - fun:g_signal_new - ... - fun:g_type_class_ref - obj:*libgtk-3* -} -{ - - Memcheck:Leak - fun:*alloc - ... - fun:g_signal_new - ... - fun:g_initable_new -} -{ - - Memcheck:Leak - fun:*alloc - ... - fun:g_signal_new - obj:*libgtk-3* - fun:g_type_class_ref - obj:*libgtk-3* - ... -} -{ - - Memcheck:Leak - fun:*alloc - ... - fun:g_signal_new - ... - fun:g_type_class_ref - obj:*libgtk-3* - ... -} -{ - - Memcheck:Leak - fun:*alloc - ... - fun:g_signal_new - ... - fun:g_type_class_ref - obj:*libgtk-3* - ... -} -{ - - Memcheck:Leak - fun:*alloc - ... - fun:g_signal_new - ... - fun:g_type_class_ref - obj:*libgtk-3* - ... -} -{ - - Memcheck:Leak - fun:*alloc - ... - fun:g_signal_new - ... - fun:g_type_class_ref - obj:*libgio-2.0.so.0.4002.0 - ... -} -{ - - Memcheck:Leak - fun:*alloc - ... - fun:g_signal_new_class_handler - ... - fun:g_type_class_ref - obj:*libgtk-3* - ... -} -{ - - Memcheck:Leak - fun:*alloc - ... - fun:g_signal_connect_data - ... - fun:gtk_widget_show - obj:*libgtk-3* - ... -} -{ - - Memcheck:Leak - fun:*alloc - ... - fun:g_initable_new - ... - fun:g_type_create_instance -} -{ - - Memcheck:Leak - fun:*alloc - ... - fun:g_type_class_ref - obj:*libgtk-3* - ... - fun:g_markup_parse_context_parse -} -{ - - Memcheck:Leak - fun:malloc - ... - fun:g_type_class_ref - obj:*libgtk-3* - ... - fun:gtk_init_check -} -{ - - Memcheck:Leak - fun:malloc - ... - fun:atk_bridge_adaptor_init - obj:*libgtk-3* - ... - fun:gtk_init -} -{ - - Memcheck:Leak - fun:*alloc - ... - fun:g_application_register - obj:*libgio-2* -} -{ - - Memcheck:Leak - fun:*alloc - ... - fun:g_thread_new - obj:*libgio-2* -} -{ - - Memcheck:Leak - fun:*alloc - ... - fun:g_thread_pool_push - ... - fun:g_bus_get - fun:g_bus_watch_name -} -{ - - Memcheck:Leak - match-leak-kinds: possible - fun:*alloc - ... - fun:g_param_spec_enum - ... - fun:g_bus_get_sync - obj:*libgio-* - fun:g_application_register -} -{ - - Memcheck:Leak - match-leak-kinds: possible - fun:*alloc - fun:g_realloc - ... - fun:gtk_button_set_image_position - fun:g_object_setv - ... - obj:*libgtk-3* -} -{ - - Memcheck:Leak - match-leak-kinds: possible - fun:*alloc - ... - fun:gtk_cell_renderer_render - ... - obj:*libgtk-3* -} \ No newline at end of file From 5006a4395d116acbfec6e87ab72e28d6f32cda99 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 31 Jan 2020 16:39:40 -0700 Subject: [PATCH 062/645] bring RRGraph object and writer online --- libs/libarchfpga/src/physical_types.h | 5 +- openfpga/test_script/s298_k6_frac.openfpga | 2 +- vpr/src/base/vpr_context.h | 5 + vpr/src/device/create_rr_graph.cpp | 138 ++++ vpr/src/device/create_rr_graph.h | 17 + vpr/src/device/write_xml_rr_graph_obj.cpp | 97 +-- vpr/src/route/rr_graph.cpp | 17 + vpr/valgrind.supp | 711 +++++++++++++++++++++ 8 files changed, 948 insertions(+), 44 deletions(-) create mode 100644 vpr/src/device/create_rr_graph.cpp create mode 100644 vpr/src/device/create_rr_graph.h create mode 100644 vpr/valgrind.supp diff --git a/libs/libarchfpga/src/physical_types.h b/libs/libarchfpga/src/physical_types.h index 153fd981e..c72a57378 100644 --- a/libs/libarchfpga/src/physical_types.h +++ b/libs/libarchfpga/src/physical_types.h @@ -163,9 +163,10 @@ enum e_interconnect { COMPLETE_INTERC = 1, DIRECT_INTERC = 2, MUX_INTERC = 3, - NUM_INTERC_TYPES + NUM_INTERC_TYPES /* Xifan Tang - Invalid types for interconnect */ }; -constexpr std::array INTERCONNECT_TYPE_STRING = {{"complete", "direct", "mux"}}; //String versions of interconnection type +/* Xifan Tang - String versions of interconnection type */ +constexpr std::array INTERCONNECT_TYPE_STRING = {{"complete", "direct", "mux"}}; /* Orientations. */ enum e_side : unsigned char { diff --git a/openfpga/test_script/s298_k6_frac.openfpga b/openfpga/test_script/s298_k6_frac.openfpga index 22e74cdb1..fa3dcf993 100644 --- a/openfpga/test_script/s298_k6_frac.openfpga +++ b/openfpga/test_script/s298_k6_frac.openfpga @@ -1,5 +1,5 @@ # Run VPR for the s298 design -vpr ./test_vpr_arch/k6_frac_N10_40nm.xml ./test_blif/s298.blif +vpr ./test_vpr_arch/k6_frac_N10_40nm.xml ./test_blif/s298.blif --write_rr_graph example_rr_graph.xml # Read OpenFPGA architecture definition read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml diff --git a/vpr/src/base/vpr_context.h b/vpr/src/base/vpr_context.h index b6a1f9859..2a99e9351 100644 --- a/vpr/src/base/vpr_context.h +++ b/vpr/src/base/vpr_context.h @@ -22,6 +22,8 @@ #include "place_macro.h" #include "compressed_grid.h" +#include "rr_graph_obj.h" + //A Context is collection of state relating to a particular part of VPR // //This is a base class who's only purpose is to disable copying of contexts. @@ -143,6 +145,9 @@ struct DeviceContext : public Context { /* chan_width is for x|y-directed channels; i.e. between rows */ t_chan_width chan_width; + /* RRGraph object */ + RRGraph rr_graph; + /* Structures to define the routing architecture of the FPGA. */ std::vector rr_nodes; /* autogenerated in build_rr_graph */ diff --git a/vpr/src/device/create_rr_graph.cpp b/vpr/src/device/create_rr_graph.cpp new file mode 100644 index 000000000..4ec054a52 --- /dev/null +++ b/vpr/src/device/create_rr_graph.cpp @@ -0,0 +1,138 @@ +/* Standard header files required go first */ +#include + +/* EXTERNAL library header files go second*/ +#include "vtr_assert.h" +#include "vtr_time.h" +#include "vtr_memory.h" + +/* VPR header files go then */ +#include "vpr_types.h" +#include "rr_graph_obj.h" +#include "check_rr_graph_obj.h" +#include "create_rr_graph.h" + +/* Finally we include global variables */ +#include "globals.h" + +/******************************************************************** + * TODO: remove when this conversion (from traditional to new data structure) + * is no longer needed + * This function will convert an existing rr_graph in device_ctx to the RRGraph + *object + * This function is used to test our RRGraph if it is acceptable in downstream + *routers + ********************************************************************/ +void convert_rr_graph(std::vector& vpr_segments) { + /* Release freed memory before start building rr_graph */ + vtr::malloc_trim(0); + + vtr::ScopedStartFinishTimer timer("Conversion to routing resource graph object"); + + /* IMPORTANT: to build clock tree, + * vpr added segments to the original arch segments + * This is why to use vpr_segments as an inputs!!! + */ + auto& device_ctx = g_vpr_ctx.mutable_device(); + + /* The number of switches are in general small, + * reserve switches may not bring significant memory efficiency + * So, we just use create_switch to push_back each time + */ + device_ctx.rr_graph.reserve_switches(device_ctx.rr_switch_inf.size()); + // Create the switches + for (size_t iswitch = 0; iswitch < device_ctx.rr_switch_inf.size(); ++iswitch) { + device_ctx.rr_graph.create_switch(device_ctx.rr_switch_inf[iswitch]); + } + + /* The number of segments are in general small, reserve segments may not bring + * significant memory efficiency */ + device_ctx.rr_graph.reserve_segments(vpr_segments.size()); + // Create the segments + for (size_t iseg = 0; iseg < vpr_segments.size(); ++iseg) { + device_ctx.rr_graph.create_segment(vpr_segments[iseg]); + } + + /* Reserve list of nodes to be memory efficient */ + device_ctx.rr_graph.reserve_nodes((unsigned long)device_ctx.rr_nodes.size()); + + // Create the nodes + for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); ++inode) { + auto& node = device_ctx.rr_nodes[inode]; + RRNodeId rr_node = device_ctx.rr_graph.create_node(node.type()); + + device_ctx.rr_graph.set_node_xlow(rr_node, node.xlow()); + device_ctx.rr_graph.set_node_ylow(rr_node, node.ylow()); + device_ctx.rr_graph.set_node_xhigh(rr_node, node.xhigh()); + device_ctx.rr_graph.set_node_yhigh(rr_node, node.yhigh()); + + device_ctx.rr_graph.set_node_capacity(rr_node, node.capacity()); + + device_ctx.rr_graph.set_node_ptc_num(rr_node, node.ptc_num()); + + device_ctx.rr_graph.set_node_cost_index(rr_node, node.cost_index()); + + if (CHANX == node.type() || CHANY == node.type()) { + device_ctx.rr_graph.set_node_direction(rr_node, node.direction()); + } + if (IPIN == node.type() || OPIN == node.type()) { + device_ctx.rr_graph.set_node_side(rr_node, node.side()); + } + device_ctx.rr_graph.set_node_R(rr_node, node.R()); + device_ctx.rr_graph.set_node_C(rr_node, node.C()); + + /* Set up segment id */ + short irc_data = node.cost_index(); + short iseg = device_ctx.rr_indexed_data[irc_data].seg_index; + device_ctx.rr_graph.set_node_segment(rr_node, RRSegmentId(iseg)); + } + + /* Reserve list of edges to be memory efficient */ + unsigned long num_edges_to_reserve = 0; + for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); ++inode) { + const auto& node = device_ctx.rr_nodes[inode]; + num_edges_to_reserve += node.num_edges(); + } + device_ctx.rr_graph.reserve_edges(num_edges_to_reserve); + + /* Add edges for each node */ + for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); ++inode) { + const auto& node = device_ctx.rr_nodes[inode]; + for (int iedge = 0; iedge < node.num_edges(); ++iedge) { + size_t isink_node = node.edge_sink_node(iedge); + int iswitch = node.edge_switch(iedge); + + device_ctx.rr_graph.create_edge(RRNodeId(inode), + RRNodeId(isink_node), + RRSwitchId(iswitch)); + } + } + + /* Ensure that we reserved what we want */ + VTR_ASSERT(num_edges_to_reserve == (unsigned long)device_ctx.rr_graph.edges().size()); + + /* Partition edges to be two class: configurable (1st part) and + * non-configurable (2nd part) + * See how the router will use the edges and determine the strategy + * if we want to partition the edges first or depends on the routing needs + */ + device_ctx.rr_graph.rebuild_node_edges(); + + /* Essential check for rr_graph, build look-up and */ + if (false == device_ctx.rr_graph.validate()) { + /* Error out if built-in validator of rr_graph fails */ + vpr_throw(VPR_ERROR_ROUTE, + __FILE__, + __LINE__, + "Fundamental errors occurred when validating rr_graph object!\n"); + } + + /* Error out if advanced checker of rr_graph fails */ + if (false == check_rr_graph(device_ctx.rr_graph)) { + vpr_throw(VPR_ERROR_ROUTE, + __FILE__, + __LINE__, + "Advanced checking rr_graph object fails! Routing may still work " + "but not smooth\n"); + } +} diff --git a/vpr/src/device/create_rr_graph.h b/vpr/src/device/create_rr_graph.h new file mode 100644 index 000000000..5cc57ec92 --- /dev/null +++ b/vpr/src/device/create_rr_graph.h @@ -0,0 +1,17 @@ +#ifndef CREATE_RR_GRAPH_H +#define CREATE_RR_GRAPH_H + +/* + * Notes in include header files in a head file + * Only include the neccessary header files + * that is required by the data types in the function/class declarations! + */ +#include "rr_graph_obj.h" + +/* IMPORTANT: to build clock tree, + * vpr added segments to the original arch segments + * This is why to use vpr_segments as an inputs!!! + */ +void convert_rr_graph(std::vector& vpr_segments); + +#endif diff --git a/vpr/src/device/write_xml_rr_graph_obj.cpp b/vpr/src/device/write_xml_rr_graph_obj.cpp index 375d1ba52..10cdb273e 100644 --- a/vpr/src/device/write_xml_rr_graph_obj.cpp +++ b/vpr/src/device/write_xml_rr_graph_obj.cpp @@ -27,62 +27,36 @@ using namespace std; constexpr int FLOAT_PRECISION = std::numeric_limits::max_digits10; /*********************** External Subroutines to this module *******************/ -void add_metadata_to_xml(fstream &fp, const char *tab_prefix, const t_metadata_dict & meta); void write_rr_channel(fstream &fp); void write_rr_grid(fstream &fp); void write_rr_block_types(fstream &fp); -/*********************** Subroutines local to this module *******************/ -void write_rr_graph_node(fstream &fp, const RRGraph& rr_graph); -void write_rr_graph_switches(fstream &fp, const RRGraph& rr_graph); -void write_rr_graph_edges(fstream &fp, const RRGraph& rr_graph); -void write_rr_graph_segments(fstream &fp, const RRGraph& rr_graph); - /************************ Subroutine definitions ****************************/ +static +void add_metadata_to_xml(std::fstream& fp, const char* tab_prefix, const t_metadata_dict& meta) { + fp << tab_prefix << "" << std::endl; -/* This function is used to write the rr_graph into xml format into a a file with name: file_name */ -void write_xml_rr_graph_obj(const char *file_name, const RRGraph& rr_graph) { - fstream fp; - fp.open(file_name, fstream::out | fstream::trunc); - - /* Prints out general info for easy error checking*/ - if (!fp.is_open() || !fp.good()) { - vpr_throw(VPR_ERROR_OTHER, __FILE__, __LINE__, - "couldn't open file \"%s\" for generating RR graph file\n", file_name); + for (const auto& meta_elem : meta) { + const std::string& key = meta_elem.first; + const std::vector& values = meta_elem.second; + for (const auto& value : values) { + fp << tab_prefix << "\t" << value.as_string() << "" << std::endl; + } } - cout << "Writing RR graph" << endl; - fp << "" << endl; - - /* Write out each individual component - * Use existing write_rr_* functions as much as possible - * 1. For those using external data structures outside RRGraph, - * leave as it is. - * 2. For those using RRGraph internal data, - * write new functions - */ - write_rr_channel(fp); - write_rr_graph_switches(fp, rr_graph); - write_rr_graph_segments(fp, rr_graph); - write_rr_block_types(fp); - write_rr_grid(fp); - write_rr_graph_node(fp, rr_graph); - write_rr_graph_edges(fp, rr_graph); - fp << ""; - - fp.close(); - - cout << "Finished generating RR graph file named " << file_name << endl << endl; + fp << tab_prefix << "" << std::endl; } /* All relevant rr node info is written out to the graph. * This includes location, timing, and segment info */ +static void write_rr_graph_node(fstream &fp, const RRGraph& rr_graph) { /* TODO: we should make it function full independent from device_ctx !!! */ auto& device_ctx = g_vpr_ctx.device(); + std::array DIRECTION_STRING_WRITE_XML = {{"INC_DIR", "DEC_DIR", "BI_DIR", "NO_DIR"}}; + fp << "\t" << endl; for (auto node : rr_graph.nodes()) { @@ -90,7 +64,7 @@ void write_rr_graph_node(fstream &fp, const RRGraph& rr_graph) { fp << " id=\"" << rr_graph.node_index(node); fp << "\" type=\"" << rr_node_typename[rr_graph.node_type(node)]; if (CHANX == rr_graph.node_type(node) || CHANY == rr_graph.node_type(node)) { - fp << "\" direction=\"" << DIRECTION_STRING[rr_graph.node_direction(node)]; + fp << "\" direction=\"" << DIRECTION_STRING_WRITE_XML[rr_graph.node_direction(node)]; } fp << "\" capacity=\"" << rr_graph.node_capacity(node); fp << "\">" << endl; @@ -128,6 +102,7 @@ void write_rr_graph_node(fstream &fp, const RRGraph& rr_graph) { /* Segment information in the t_segment_inf data structure is written out. * Information includes segment id, name, and optional timing parameters */ +static void write_rr_graph_segments(fstream &fp, const RRGraph& rr_graph) { fp << "\t" << endl; @@ -146,6 +121,7 @@ void write_rr_graph_segments(fstream &fp, const RRGraph& rr_graph) { /* Switch info is written out into xml format. This includes * general, sizing, and optional timing information */ +static void write_rr_graph_switches(fstream &fp, const RRGraph& rr_graph) { fp << "\t" << endl; @@ -176,6 +152,7 @@ void write_rr_graph_switches(fstream &fp, const RRGraph& rr_graph) { fp << "\t\t\t" << endl; fp << "\t\t\t" << endl; @@ -189,6 +166,7 @@ void write_rr_graph_switches(fstream &fp, const RRGraph& rr_graph) { /* Edges connecting to each rr node is printed out. The two nodes * it connects to are also printed */ +static void write_rr_graph_edges(fstream &fp, const RRGraph& rr_graph) { auto& device_ctx = g_vpr_ctx.device(); fp << "\t" << endl; @@ -220,3 +198,40 @@ void write_rr_graph_edges(fstream &fp, const RRGraph& rr_graph) { } fp << "\t" << endl << endl; } + +/* This function is used to write the rr_graph into xml format into a a file with name: file_name */ +void write_xml_rr_graph_obj(const char *file_name, const RRGraph& rr_graph) { + fstream fp; + fp.open(file_name, fstream::out | fstream::trunc); + + /* Prints out general info for easy error checking*/ + if (!fp.is_open() || !fp.good()) { + vpr_throw(VPR_ERROR_OTHER, __FILE__, __LINE__, + "couldn't open file \"%s\" for generating RR graph file\n", file_name); + } + cout << "Writing RR graph" << endl; + fp << "" << endl; + + /* Write out each individual component + * Use existing write_rr_* functions as much as possible + * 1. For those using external data structures outside RRGraph, + * leave as it is. + * 2. For those using RRGraph internal data, + * write new functions + */ + write_rr_channel(fp); + write_rr_graph_switches(fp, rr_graph); + write_rr_graph_segments(fp, rr_graph); + write_rr_block_types(fp); + write_rr_grid(fp); + write_rr_graph_node(fp, rr_graph); + write_rr_graph_edges(fp, rr_graph); + fp << ""; + + fp.close(); + + cout << "Finished generating RR graph file named " << file_name << endl << endl; +} + diff --git a/vpr/src/route/rr_graph.cpp b/vpr/src/route/rr_graph.cpp index df1bb8b09..6bb9892ed 100644 --- a/vpr/src/route/rr_graph.cpp +++ b/vpr/src/route/rr_graph.cpp @@ -36,6 +36,9 @@ #include "rr_types.h" +#include "create_rr_graph.h" +#include "write_xml_rr_graph_obj.h" + //#define VERBOSE struct t_mux { @@ -331,6 +334,9 @@ void create_rr_graph(const t_graph_type graph_type, base_cost_type, &det_routing_arch->wire_to_rr_ipin_switch, det_routing_arch->read_rr_graph_filename.c_str()); + + /* Xifan Tang - Create rr_graph object: load rr_nodes to the object */ + convert_rr_graph(segment_inf); } } else { if (channel_widths_unchanged(device_ctx.chan_width, nodes_per_chan) && !device_ctx.rr_nodes.empty()) { @@ -369,6 +375,9 @@ void create_rr_graph(const t_graph_type graph_type, det_routing_arch->wire_to_rr_ipin_switch, base_cost_type); } + + /* Xifan Tang - Create rr_graph object: load rr_nodes to the object */ + convert_rr_graph(segment_inf); } process_non_config_sets(); @@ -378,6 +387,11 @@ void create_rr_graph(const t_graph_type graph_type, //Write out rr graph file if needed if (!det_routing_arch->write_rr_graph_filename.empty()) { write_rr_graph(det_routing_arch->write_rr_graph_filename.c_str(), segment_inf); + + /* Just to test the writer of rr_graph_obj, give a filename in a fixed style*/ + std::string rr_graph_obj_filename(det_routing_arch->write_rr_graph_filename); + rr_graph_obj_filename += std::string(".obj"); + write_xml_rr_graph_obj(rr_graph_obj_filename.c_str(), device_ctx.rr_graph); } } @@ -1391,6 +1405,9 @@ void free_rr_graph() { device_ctx.rr_edge_metadata.clear(); invalidate_router_lookahead_cache(); + + /* Xifan Tang - Clear the rr_graph object */ + device_ctx.rr_graph.clear(); } static void build_rr_sinks_sources(const int i, diff --git a/vpr/valgrind.supp b/vpr/valgrind.supp new file mode 100644 index 000000000..6bd7272c4 --- /dev/null +++ b/vpr/valgrind.supp @@ -0,0 +1,711 @@ +# +# Valgrind suppression file for EZGL +# + +#pango +{ + libpango + Memcheck:Leak + ... + obj:/usr/lib*/libpango* +} + +#GTK +{ + g_type_register + Memcheck:Leak + fun:*alloc + ... + fun:g_type_register_* + ... + fun:_dl_init + ... +} + +{ + g_quark_from_static_string + Memcheck:Leak + fun:*alloc + ... + fun:g_quark_from_static_string + ... + fun:_dl_init + ... +} + +{ + g_main_thread + Memcheck:Leak + fun:*alloc + ... + fun:g_main* + ... + fun:start_thread + fun:clone +} + +{ + g_closure + Memcheck:Leak + match-leak-kinds:possible + fun:*alloc + ... + fun:g_cclosure_new + fun:g_signal_connect_data + ... +} + +# +{ + g_object + Memcheck:Leak + match-leak-kinds:possible + ... + fun:g_object_new + ... +} + +{ + g_type_register_static + Memcheck:Leak + match-leak-kinds:possible + ... + fun:g_type_register_static + ... +} + +{ + g_signal_connect_closure + Memcheck:Leak + match-leak-kinds:possible + ... + fun:g_signal_connect_closure + fun:gtk_*group* + ... +} + +{ + gtk_module_init + Memcheck:Leak + + fun:*alloc + ... + fun:gtk_module_init + ... +} + +{ + g_closure_invoke + Memcheck:Leak + + fun:*alloc + ... + fun:g_closure_invoke + ... +} + +{ + gtk_style_context_set_state + Memcheck:Leak + + fun:*alloc + ... + fun:gtk_style_context_set_state + ... +} + +# +{ + call_init + Memcheck:Leak + + fun:*alloc + ... + fun:call_init + fun:_dl_init + ... +} + +{ + XML_ParseBuffer + Memcheck:Leak + fun:*alloc + ... + fun:XML_ParseBuffer + ... +} + +{ + FcConfigParseAndLoad + Memcheck:Leak + fun:*alloc + ... + fun:FcConfigParseAndLoad + ... +} + +# +{ + g_objectv + Memcheck:Leak + + ... + fun:g_object_newv + ... +} + +{ + g_type_add_interface_static + Memcheck:Leak + match-leak-kinds:possible + ... + fun:g_type_add_interface_static + ... +} + +{ + gtk_container_get_children + Memcheck:Leak + fun:*alloc + ... + fun:gtk_container_get_children + ... +} + +{ + cairo_select_font_face + Memcheck:Leak + fun:*alloc + ... + fun:cairo_select_font_face + ... +} + +{ + libfontconfig + Memcheck:Leak + fun:*alloc + ... + obj:*libfontconfig.so.* + ... +} + +{ + X11_XGetDefault + Memcheck:Leak + fun:realloc + obj:*libX11.so.6.3.0 + obj:*libX11.so.6.3.0 + obj:*libX11.so.6.3.0 + fun:_XlcCreateLC + fun:_XlcDefaultLoader + fun:_XOpenLC + fun:_XrmInitParseInfo + obj:*libX11.so.6.3.0 + fun:XrmGetStringDatabase + obj:*libX11.so.6.3.0 + fun:XGetDefault +} + +{ + XInternAtom_via_event_loop + Memcheck:Leak + fun:*alloc + fun:_XEnq + obj:*libX11.so.6.3.0 + fun:_XReply + fun:XInternAtom + ... +} + +{ + cairo_deep_*alloc + Memcheck:Leak + fun:*alloc + obj:*libcairo.* + ... +} + +#openmp +{ + GOMP_parallel + Memcheck:Leak + + fun:*alloc + fun:allocate_dtv + fun:_dl_allocate_tls + fun:allocate_stack + fun:pthread_create@@GLIBC_2.2.5 + ... +} + + +#GTK engines +{ + engines + Memcheck:Leak + fun:*alloc + ... + obj:/usr/lib*/gtk*/*/engines* + ... + obj:/usr/lib*/libgtk* +} + +#nvidia +{ + libGL + Memcheck:Leak + ... + obj:/usr/lib*/libGL.so* +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:g_socket_create_source + ... + fun:g_main_context_dispatch +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:g_type_class_ref + ... + fun:gtk_init_check + fun:gtk_init + obj:*libgtk-3* +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:g_markup_parse_context_parse + obj:*libgtk-3* +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:g_socket_new + obj:*libgio-2* + fun:g_socket_client_connect + obj:*libgio-2* +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:atk_add_focus_tracker + obj:*libgtk-3* + ... + fun:gtk_parse_args + fun:gtk_init_check +} +{ + + Memcheck:Leak + fun:m*alloc + ... + fun:g_bus_get_sync + ... + fun:g_application_register + obj:*libgio-2* + fun:g_application_run +} +{ + + Memcheck:Leak + fun:*alloc + ... + obj:*libgtk-3* + fun:g_closure_invoke + ... + fun:g_signal_emit_valist + fun:g_signal_emit +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:g_simple_async_result_complete + ... + fun:g_main_context_dispatch + ... +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:g_type_class_ref + ... + fun:atk_add_focus_tracker + obj:*libgtk-3* + ... + fun:g_option_context_parse +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:g_type_class_ref + obj:*libgtk-3* + fun:atk_add_focus_tracker + obj:*libgtk-3* +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:g_type_class_ref + obj:*libgtk-3* +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:g_dbus_proxy_new_sync + obj:*libgtk-3* +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:g_signal_new + ... + obj:*libgobject-2* + fun:g_type_class_ref + fun:g_object_new_valist + fun:g_initable_new_valist + fun:g_initable_new +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:g_signal_new + obj:*libgtk-3* + fun:g_type_class_ref + obj:*libgtk-3* + ... +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:g_signal_new + obj:*libgtk-3* + ... + fun:g_type_class_ref + obj:*libgtk-3* + ... +} +{ + + Memcheck:Leak + + fun:*alloc + ... + fun:g_type_class_ref + obj:*libgtk-3* + fun:atk_add_focus_tracker + obj:*libgtk-3* + ... + fun:g_option_context_parse +} +{ + + Memcheck:Leak + + fun:*alloc + ... + fun:g_signal_new_class_handler + obj:*libgtk-3* + ... + fun:g_type_class_ref + obj:*libgtk-3* + ... +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:g_signal_connect_data + obj:*libgtk-3* + ... + fun:gtk_widget_show + obj:*libgtk-3* + ... +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:g_type_class_ref + fun:g_object_new_valist + fun:g_initable_new_valist + fun:g_initable_new + fun:gvfs_dbus_mount_tracker_proxy_new_for_bus_sync + ... + fun:g_type_create_instance +} +{ + + Memcheck:Leak + fun:*alloc + fun:g_*alloc + ... + fun:g_type_class_ref + obj:*libgtk-3* + ... + fun:g_markup_parse_context_parse +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:g_type_class_ref + obj:*libgtk-3* + ... + fun:g_option_context_parse + fun:gtk_parse_args + fun:gtk_init_check +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:g_type_class_ref + fun:atk_bridge_adaptor_init + obj:*libgtk-3* + ... + fun:g_option_context_parse + fun:gtk_parse_args + fun:gtk_init_check + fun:gtk_init +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:g_signal_new + ... + fun:g_type_class_ref + obj:*libgtk-3* + fun:atk_add_focus_tracker + obj:*libgtk-3* +} +{ + + Memcheck:Leak + + fun:*alloc + ... + fun:g_signal_new + ... + fun:g_type_class_ref + obj:*libgtk-3* +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:g_signal_new + ... + fun:g_initable_new +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:g_signal_new + obj:*libgtk-3* + fun:g_type_class_ref + obj:*libgtk-3* + ... +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:g_signal_new + ... + fun:g_type_class_ref + obj:*libgtk-3* + ... +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:g_signal_new + ... + fun:g_type_class_ref + obj:*libgtk-3* + ... +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:g_signal_new + ... + fun:g_type_class_ref + obj:*libgtk-3* + ... +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:g_signal_new + ... + fun:g_type_class_ref + obj:*libgio-2.0.so.0.4002.0 + ... +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:g_signal_new_class_handler + ... + fun:g_type_class_ref + obj:*libgtk-3* + ... +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:g_signal_connect_data + ... + fun:gtk_widget_show + obj:*libgtk-3* + ... +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:g_initable_new + ... + fun:g_type_create_instance +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:g_type_class_ref + obj:*libgtk-3* + ... + fun:g_markup_parse_context_parse +} +{ + + Memcheck:Leak + fun:malloc + ... + fun:g_type_class_ref + obj:*libgtk-3* + ... + fun:gtk_init_check +} +{ + + Memcheck:Leak + fun:malloc + ... + fun:atk_bridge_adaptor_init + obj:*libgtk-3* + ... + fun:gtk_init +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:g_application_register + obj:*libgio-2* +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:g_thread_new + obj:*libgio-2* +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:g_thread_pool_push + ... + fun:g_bus_get + fun:g_bus_watch_name +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:*alloc + ... + fun:g_param_spec_enum + ... + fun:g_bus_get_sync + obj:*libgio-* + fun:g_application_register +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:*alloc + fun:g_realloc + ... + fun:gtk_button_set_image_position + fun:g_object_setv + ... + obj:*libgtk-3* +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:*alloc + ... + fun:gtk_cell_renderer_render + ... + obj:*libgtk-3* +} \ No newline at end of file From 5e2559dc1411041ac603d53f50da031f32831c7d Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 31 Jan 2020 17:27:18 -0700 Subject: [PATCH 063/645] now RRGraph is proven to be correct as rr_node in terms of XML output --- vpr/src/device/rr_graph_obj.cpp | 22 +++++++++++++--------- vpr/src/device/write_xml_rr_graph_obj.cpp | 2 ++ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/vpr/src/device/rr_graph_obj.cpp b/vpr/src/device/rr_graph_obj.cpp index 2df4a77bf..316bc6e87 100644 --- a/vpr/src/device/rr_graph_obj.cpp +++ b/vpr/src/device/rr_graph_obj.cpp @@ -1120,19 +1120,23 @@ void RRGraph::rebuild_node_edges() { auto is_incoming_edge = [&](const RREdgeId edge) { return edge_sink_node(edge) == node; }; - std::partition(node_edges_[node].get(), - node_edges_[node].get() + node_num_in_edges_[node] + node_num_out_edges_[node], - is_incoming_edge); + /* Use stable_partition to keep the relative order, + * This is mainly for comparing the RRGraph write with rr_node writer + * so that it is easy to check consistency + */ + std::stable_partition(node_edges_[node].get(), + node_edges_[node].get() + node_num_in_edges_[node] + node_num_out_edges_[node], + is_incoming_edge); //Partition incoming by configurable/non-configurable - std::partition(node_edges_[node].get(), - node_edges_[node].get() + node_num_in_edges_[node], - is_configurable_edge); + std::stable_partition(node_edges_[node].get(), + node_edges_[node].get() + node_num_in_edges_[node], + is_configurable_edge); //Partition outgoing by configurable/non-configurable - std::partition(node_edges_[node].get() + node_num_in_edges_[node], - node_edges_[node].get() + node_num_in_edges_[node] + node_num_out_edges_[node], - is_configurable_edge); + std::stable_partition(node_edges_[node].get() + node_num_in_edges_[node], + node_edges_[node].get() + node_num_in_edges_[node] + node_num_out_edges_[node], + is_configurable_edge); #if 0 //TODO: Sanity check remove! diff --git a/vpr/src/device/write_xml_rr_graph_obj.cpp b/vpr/src/device/write_xml_rr_graph_obj.cpp index 10cdb273e..a329b7bc0 100644 --- a/vpr/src/device/write_xml_rr_graph_obj.cpp +++ b/vpr/src/device/write_xml_rr_graph_obj.cpp @@ -172,6 +172,8 @@ void write_rr_graph_edges(fstream &fp, const RRGraph& rr_graph) { fp << "\t" << endl; for (auto node : rr_graph.nodes()) { + /* Sort the edges by the ids of sink nodes */ + std::vector sorted_out_edge_ids; for (auto edge: rr_graph.node_out_edges(node)) { fp << "\t\t Date: Fri, 31 Jan 2020 19:55:32 -0700 Subject: [PATCH 064/645] start deploy RRGraph in the placement engine --- vpr/src/base/vpr_types.h | 10 ++++++++- vpr/src/draw/draw.cpp | 6 ++--- vpr/src/place/timing_place_lookup.cpp | 32 +++++++++++++-------------- vpr/src/place/timing_place_lookup.h | 3 ++- vpr/src/route/route_common.cpp | 19 ++++++++-------- vpr/src/route/route_tree_timing.cpp | 6 ++--- vpr/src/route/route_tree_type.h | 13 +++++++++++ 7 files changed, 56 insertions(+), 33 deletions(-) diff --git a/vpr/src/base/vpr_types.h b/vpr/src/base/vpr_types.h index cb38f75ee..617888225 100644 --- a/vpr/src/base/vpr_types.h +++ b/vpr/src/base/vpr_types.h @@ -40,8 +40,9 @@ #include "vtr_flat_map.h" #include "vtr_cache.h" -/* Header for rr_graph related definition */ +/* Xifan Tang - Header for rr_graph related definition */ #include "rr_graph_types.h" +#include "rr_graph_obj.h" /******************************************************************************* * Global data types and constants @@ -1137,6 +1138,10 @@ struct t_trace { t_trace* next; int index; short iswitch; + + /* Xifan Tang - RRGraph unique ids */ + RRNodeId node_id; + RRSwitchId switch_id; }; /* Extra information about each rr_node needed only during routing (i.e. * @@ -1160,6 +1165,9 @@ struct t_trace { * occ: The current occupancy of the associated rr node */ struct t_rr_node_route_inf { int prev_node; + /* Xifan Tang - prev_node for RRGraph object */ + RRNodeId prev_node_id; + t_edge_size prev_edge; float pres_cost; diff --git a/vpr/src/draw/draw.cpp b/vpr/src/draw/draw.cpp index 505928cf6..8ba6ca124 100644 --- a/vpr/src/draw/draw.cpp +++ b/vpr/src/draw/draw.cpp @@ -880,10 +880,10 @@ void init_draw_coords(float width_val) { return; //do not initialize only if --disp off and --save_graphics off /* Each time routing is on screen, need to reallocate the color of each * * rr_node, as the number of rr_nodes may change. */ - if (device_ctx.rr_nodes.size() != 0) { + if (device_ctx.rr_graph.nodes().size() != 0) { draw_state->draw_rr_node = (t_draw_rr_node*)vtr::realloc(draw_state->draw_rr_node, - (device_ctx.rr_nodes.size()) * sizeof(t_draw_rr_node)); - for (size_t i = 0; i < device_ctx.rr_nodes.size(); i++) { + (device_ctx.rr_graph.nodes().size()) * sizeof(t_draw_rr_node)); + for (size_t i = 0; i < device_ctx.rr_graph.nodes().size(); i++) { draw_state->draw_rr_node[i].color = DEFAULT_RR_NODE_COLOR; draw_state->draw_rr_node[i].node_highlighted = false; } diff --git a/vpr/src/place/timing_place_lookup.cpp b/vpr/src/place/timing_place_lookup.cpp index 4aa439aab..4dc907b4c 100644 --- a/vpr/src/place/timing_place_lookup.cpp +++ b/vpr/src/place/timing_place_lookup.cpp @@ -304,16 +304,16 @@ static float route_connection_delay( for (int driver_ptc : best_driver_ptcs) { VTR_ASSERT(driver_ptc != OPEN); - int source_rr_node = get_rr_node_index(device_ctx.rr_node_indices, source_x, source_y, SOURCE, driver_ptc); + RRNodeId source_rr_node = device_ctx.rr_graph.find_node(source_x, source_y, SOURCE, driver_ptc); - VTR_ASSERT(source_rr_node != OPEN); + VTR_ASSERT(source_rr_node != RRNodeId::INVALID()); for (int sink_ptc : best_sink_ptcs) { VTR_ASSERT(sink_ptc != OPEN); - int sink_rr_node = get_rr_node_index(device_ctx.rr_node_indices, sink_x, sink_y, SINK, sink_ptc); + RRNodeId sink_rr_node = device_ctx.rr_graph.find_node(sink_x, sink_y, SINK, sink_ptc); - VTR_ASSERT(sink_rr_node != OPEN); + VTR_ASSERT(sink_rr_node != RRNodeId::INVALID()); if (!measure_directconnect && directconnect_exists(source_rr_node, sink_rr_node)) { //Skip if we shouldn't measure direct connects and a direct connect exists @@ -322,7 +322,7 @@ static float route_connection_delay( { successfully_routed = route_profiler.calculate_delay( - source_rr_node, sink_rr_node, + size_t(source_rr_node), size_t(sink_rr_node), router_opts, &net_delay_value); } @@ -933,29 +933,29 @@ void OverrideDelayModel::compute_override_delay_model( } } -bool directconnect_exists(int src_rr_node, int sink_rr_node) { +bool directconnect_exists(RRNodeId src_rr_node, RRNodeId sink_rr_node) { //Returns true if there is a directconnect between the two RR nodes // //This is checked by looking for a SOURCE -> OPIN -> IPIN -> SINK path //which starts at src_rr_node and ends at sink_rr_node auto& device_ctx = g_vpr_ctx.device(); - auto& rr_nodes = device_ctx.rr_nodes; + auto& rr_graph = device_ctx.rr_graph; - VTR_ASSERT(rr_nodes[src_rr_node].type() == SOURCE && rr_nodes[sink_rr_node].type() == SINK); + VTR_ASSERT(rr_graph.node_type(src_rr_node) == SOURCE && rr_graph.node_type(sink_rr_node) == SINK); //TODO: This is a constant depth search, but still may be too slow - for (t_edge_size i_src_edge = 0; i_src_edge < rr_nodes[src_rr_node].num_edges(); ++i_src_edge) { - int opin_rr_node = rr_nodes[src_rr_node].edge_sink_node(i_src_edge); + for (const RREdgeId& src_edge : rr_graph.node_out_edges(src_rr_node)) { + RRNodeId opin_rr_node = rr_graph.edge_sink_node(src_edge); - if (rr_nodes[opin_rr_node].type() != OPIN) continue; + if (rr_graph.node_type(opin_rr_node) != OPIN) continue; - for (t_edge_size i_opin_edge = 0; i_opin_edge < rr_nodes[opin_rr_node].num_edges(); ++i_opin_edge) { - int ipin_rr_node = rr_nodes[opin_rr_node].edge_sink_node(i_opin_edge); + for (const RREdgeId& opin_edge : rr_graph.node_out_edges(opin_rr_node)) { + RRNodeId ipin_rr_node = rr_graph.edge_sink_node(opin_edge); - if (rr_nodes[ipin_rr_node].type() != IPIN) continue; + if (rr_graph.node_type(ipin_rr_node) != IPIN) continue; - for (t_edge_size i_ipin_edge = 0; i_ipin_edge < rr_nodes[ipin_rr_node].num_edges(); ++i_ipin_edge) { - if (sink_rr_node == rr_nodes[ipin_rr_node].edge_sink_node(i_ipin_edge)) { + for (const RREdgeId& ipin_edge : rr_graph.node_out_edges(ipin_rr_node)) { + if (sink_rr_node == rr_graph.edge_sink_node(ipin_edge)) { return true; } } diff --git a/vpr/src/place/timing_place_lookup.h b/vpr/src/place/timing_place_lookup.h index 74a20e939..e96bccdab 100644 --- a/vpr/src/place/timing_place_lookup.h +++ b/vpr/src/place/timing_place_lookup.h @@ -1,6 +1,7 @@ #ifndef TIMING_PLACE_LOOKUP_H #define TIMING_PLACE_LOOKUP_H #include "place_delay_model.h" +#include "rr_graph_obj.h" std::unique_ptr compute_place_delay_model(const t_placer_opts& placer_opts, const t_router_opts& router_opts, @@ -11,6 +12,6 @@ std::unique_ptr compute_place_delay_model(const t_placer_opts& const int num_directs); std::vector get_best_classes(enum e_pin_type pintype, t_physical_tile_type_ptr type); -bool directconnect_exists(int src_rr_node, int sink_rr_node); +bool directconnect_exists(RRNodeId src_rr_node, RRNodeId sink_rr_node); #endif diff --git a/vpr/src/route/route_common.cpp b/vpr/src/route/route_common.cpp index 6204dad98..b9a47d2d7 100644 --- a/vpr/src/route/route_common.cpp +++ b/vpr/src/route/route_common.cpp @@ -336,7 +336,7 @@ bool feasible_routing() { auto& device_ctx = g_vpr_ctx.device(); auto& route_ctx = g_vpr_ctx.routing(); - for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); inode++) { + for (size_t inode = 0; inode < device_ctx.rr_graph.nodes().size(); inode++) { if (route_ctx.rr_node_route_inf[inode].occ() > device_ctx.rr_nodes[inode].capacity()) { return (false); } @@ -351,7 +351,7 @@ std::vector collect_congested_rr_nodes() { auto& route_ctx = g_vpr_ctx.routing(); std::vector congested_rr_nodes; - for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); inode++) { + for (size_t inode = 0; inode < device_ctx.rr_graph.nodes().size(); inode++) { short occ = route_ctx.rr_node_route_inf[inode].occ(); short capacity = device_ctx.rr_nodes[inode].capacity(); @@ -362,14 +362,14 @@ std::vector collect_congested_rr_nodes() { return congested_rr_nodes; } -/* Returns a vector from [0..device_ctx.rr_nodes.size()-1] containing the set +/* Returns a vector from [0..device_ctx.rr_graph.nodes().size()-1] containing the set * of nets using each RR node */ std::vector> collect_rr_node_nets() { auto& device_ctx = g_vpr_ctx.device(); auto& route_ctx = g_vpr_ctx.routing(); auto& cluster_ctx = g_vpr_ctx.clustering(); - std::vector> rr_node_nets(device_ctx.rr_nodes.size()); + std::vector> rr_node_nets(device_ctx.rr_graph.nodes().size()); for (ClusterNetId inet : cluster_ctx.clb_nlist.nets()) { t_trace* trace_elem = route_ctx.trace[inet].head; while (trace_elem) { @@ -449,7 +449,7 @@ void pathfinder_update_cost(float pres_fac, float acc_fac) { auto& device_ctx = g_vpr_ctx.device(); auto& route_ctx = g_vpr_ctx.mutable_routing(); - for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); inode++) { + for (size_t inode = 0; inode < device_ctx.rr_graph.nodes().size(); inode++) { occ = route_ctx.rr_node_route_inf[inode].occ(); capacity = device_ctx.rr_nodes[inode].capacity(); @@ -1003,7 +1003,7 @@ void alloc_and_load_rr_node_route_structs() { auto& route_ctx = g_vpr_ctx.mutable_routing(); auto& device_ctx = g_vpr_ctx.device(); - route_ctx.rr_node_route_inf.resize(device_ctx.rr_nodes.size()); + route_ctx.rr_node_route_inf.resize(device_ctx.rr_graph.nodes().size()); reset_rr_node_route_structs(); } @@ -1014,10 +1014,11 @@ void reset_rr_node_route_structs() { auto& route_ctx = g_vpr_ctx.mutable_routing(); auto& device_ctx = g_vpr_ctx.device(); - VTR_ASSERT(route_ctx.rr_node_route_inf.size() == size_t(device_ctx.rr_nodes.size())); + VTR_ASSERT(route_ctx.rr_node_route_inf.size() == size_t(device_ctx.rr_graph.nodes().size())); - for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); inode++) { + for (size_t inode = 0; inode < device_ctx.rr_graph.nodes().size(); inode++) { route_ctx.rr_node_route_inf[inode].prev_node = NO_PREVIOUS; + route_ctx.rr_node_route_inf[inode].prev_node_id = RRNodeId::INVALID(); route_ctx.rr_node_route_inf[inode].prev_edge = NO_PREVIOUS; route_ctx.rr_node_route_inf[inode].pres_cost = 1.0; route_ctx.rr_node_route_inf[inode].acc_cost = 1.0; @@ -1826,7 +1827,7 @@ void print_invalid_routing_info() { } } - for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); inode++) { + for (size_t inode = 0; inode < device_ctx.rr_graph.nodes().size(); inode++) { int occ = route_ctx.rr_node_route_inf[inode].occ(); int cap = device_ctx.rr_nodes[inode].capacity(); if (occ > cap) { diff --git a/vpr/src/route/route_tree_timing.cpp b/vpr/src/route/route_tree_timing.cpp index 7988d02dc..faf33c7dd 100644 --- a/vpr/src/route/route_tree_timing.cpp +++ b/vpr/src/route/route_tree_timing.cpp @@ -27,7 +27,7 @@ /* Array below allows mapping from any rr_node to any rt_node currently in * the rt_tree. */ -static std::vector rr_node_to_rt_node; /* [0..device_ctx.rr_nodes.size()-1] */ +static std::vector rr_node_to_rt_node; /* [0..device_ctx.rr_graph.nodes().size()-1] */ /* Frees lists for fast addition and deletion of nodes and edges. */ @@ -73,7 +73,7 @@ bool alloc_route_tree_timing_structs(bool exists_ok) { auto& device_ctx = g_vpr_ctx.device(); - bool route_tree_structs_are_allocated = (rr_node_to_rt_node.size() == size_t(device_ctx.rr_nodes.size()) + bool route_tree_structs_are_allocated = (rr_node_to_rt_node.size() == size_t(device_ctx.rr_graph.nodes().size()) || rt_node_free_list != nullptr); if (route_tree_structs_are_allocated) { if (exists_ok) { @@ -84,7 +84,7 @@ bool alloc_route_tree_timing_structs(bool exists_ok) { } } - rr_node_to_rt_node = std::vector(device_ctx.rr_nodes.size(), nullptr); + rr_node_to_rt_node = std::vector(device_ctx.rr_graph.nodes().size(), nullptr); return true; } diff --git a/vpr/src/route/route_tree_type.h b/vpr/src/route/route_tree_type.h index 7d663aafb..5e81bd1e4 100644 --- a/vpr/src/route/route_tree_type.h +++ b/vpr/src/route/route_tree_type.h @@ -1,4 +1,5 @@ #pragma once +#include "rr_graph_obj.h" /************** Types and defines exported by route_tree_timing.c ************/ struct t_rt_node; @@ -11,6 +12,10 @@ struct t_rt_node; struct t_linked_rt_edge { t_rt_node* child; short iswitch; + + /* Xifan Tang - RRGraph switch*/ + RRSwitchId iswitch_id; + t_linked_rt_edge* next; }; @@ -41,8 +46,16 @@ struct t_rt_node { } u; t_rt_node* parent_node; short parent_switch; + + /* Xifan Tang - RRGraph switch*/ + RRSwitchId parent_switch_id; + bool re_expand; int inode; + + /* Xifan Tang - RRGraph node */ + RRNodeId inode_id; + float C_downstream; float R_upstream; float Tdel; From 9d9a2c1402454b1af63bbe9b061d01d7a749ddab Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 31 Jan 2020 21:14:20 -0700 Subject: [PATCH 065/645] deploying RRGraph to placer --- vpr/src/base/vpr_context.h | 4 +- vpr/src/base/vpr_types.h | 6 +- vpr/src/place/timing_place_lookup.cpp | 2 +- vpr/src/route/route_common.cpp | 20 +-- vpr/src/route/route_common.h | 11 +- vpr/src/route/route_timing.cpp | 167 ++++++++++---------- vpr/src/route/route_timing.h | 6 +- vpr/src/route/route_tree_timing.cpp | 19 ++- vpr/src/route/route_tree_timing.h | 3 +- vpr/src/route/route_tree_type.h | 4 +- vpr/src/route/router_delay_profiling.cpp | 7 +- vpr/src/route/router_delay_profiling.h | 2 +- vpr/src/route/router_lookahead.cpp | 40 ++--- vpr/src/route/router_lookahead.h | 10 +- vpr/src/route/spatial_route_tree_lookup.cpp | 10 +- vpr/src/util/vpr_utils.cpp | 22 +-- vpr/src/util/vpr_utils.h | 2 +- 17 files changed, 167 insertions(+), 168 deletions(-) diff --git a/vpr/src/base/vpr_context.h b/vpr/src/base/vpr_context.h index 2a99e9351..7f4ba43dc 100644 --- a/vpr/src/base/vpr_context.h +++ b/vpr/src/base/vpr_context.h @@ -160,7 +160,7 @@ struct DeviceContext : public Context { std::vector> rr_non_config_node_sets; //Reverse look-up from RR node to non-configurably connected node set (index into rr_nonconf_node_sets) - std::unordered_map rr_node_to_non_config_node_set; + std::unordered_map rr_node_to_non_config_node_set; //The indicies of rr nodes of a given type at a specific x,y grid location t_rr_node_indices rr_node_indices; //[0..NUM_RR_TYPES-1][0..grid.width()-1][0..grid.width()-1][0..size-1] @@ -290,7 +290,7 @@ struct RoutingContext : public Context { vtr::vector> rr_blk_source; /* [0..num_blocks-1][0..num_class-1] */ - std::vector rr_node_route_inf; /* [0..device_ctx.num_rr_nodes-1] */ + vtr::vector rr_node_route_inf; /* [0..device_ctx.num_rr_nodes-1] */ //Information about current routing status of each net vtr::vector net_status; //[0..cluster_ctx.clb_nlist.nets().size()-1] diff --git a/vpr/src/base/vpr_types.h b/vpr/src/base/vpr_types.h index 617888225..bf080539f 100644 --- a/vpr/src/base/vpr_types.h +++ b/vpr/src/base/vpr_types.h @@ -1164,11 +1164,9 @@ struct t_trace { * Number of times this node must be reached to fully route. * * occ: The current occupancy of the associated rr node */ struct t_rr_node_route_inf { - int prev_node; /* Xifan Tang - prev_node for RRGraph object */ - RRNodeId prev_node_id; - - t_edge_size prev_edge; + RRNodeId prev_node; + RREdgeId prev_edge; float pres_cost; float acc_cost; diff --git a/vpr/src/place/timing_place_lookup.cpp b/vpr/src/place/timing_place_lookup.cpp index 4dc907b4c..d3cdad215 100644 --- a/vpr/src/place/timing_place_lookup.cpp +++ b/vpr/src/place/timing_place_lookup.cpp @@ -322,7 +322,7 @@ static float route_connection_delay( { successfully_routed = route_profiler.calculate_delay( - size_t(source_rr_node), size_t(sink_rr_node), + source_rr_node, sink_rr_node, router_opts, &net_delay_value); } diff --git a/vpr/src/route/route_common.cpp b/vpr/src/route/route_common.cpp index b9a47d2d7..40eada542 100644 --- a/vpr/src/route/route_common.cpp +++ b/vpr/src/route/route_common.cpp @@ -706,26 +706,26 @@ static std::pair add_trace_non_configurable_recurr(int node, /* The routine sets the path_cost to HUGE_POSITIVE_FLOAT for * * all channel segments touched by previous routing phases. */ -void reset_path_costs(const std::vector& visited_rr_nodes) { +void reset_path_costs(const std::vector& visited_rr_nodes) { auto& route_ctx = g_vpr_ctx.mutable_routing(); for (auto node : visited_rr_nodes) { route_ctx.rr_node_route_inf[node].path_cost = std::numeric_limits::infinity(); route_ctx.rr_node_route_inf[node].backward_path_cost = std::numeric_limits::infinity(); - route_ctx.rr_node_route_inf[node].prev_node = NO_PREVIOUS; - route_ctx.rr_node_route_inf[node].prev_edge = NO_PREVIOUS; + route_ctx.rr_node_route_inf[node].prev_node = RRNodeId::INVALID(); + route_ctx.rr_node_route_inf[node].prev_edge = RREdgeId::INVALID(); } } /* Returns the *congestion* cost of using this rr_node. */ -float get_rr_cong_cost(int inode) { +float get_rr_cong_cost(const RRNodeId& inode) { auto& device_ctx = g_vpr_ctx.device(); float cost = get_single_rr_cong_cost(inode); auto itr = device_ctx.rr_node_to_non_config_node_set.find(inode); if (itr != device_ctx.rr_node_to_non_config_node_set.end()) { - for (int node : device_ctx.rr_non_config_node_sets[itr->second]) { + for (const RRNodeId& node : device_ctx.rr_non_config_node_sets[itr->second]) { if (node == inode) { continue; //Already included above } @@ -738,11 +738,11 @@ float get_rr_cong_cost(int inode) { /* Returns the congestion cost of using this rr_node, *ignoring* * non-configurable edges */ -static float get_single_rr_cong_cost(int inode) { +static float get_single_rr_cong_cost(const RRNodeId& inode) { auto& device_ctx = g_vpr_ctx.device(); auto& route_ctx = g_vpr_ctx.routing(); - auto cost_index = device_ctx.rr_nodes[inode].cost_index(); + auto cost_index = device_ctx.rr_graph.node_cost_index(inode); float cost = device_ctx.rr_indexed_data[cost_index].base_cost * route_ctx.rr_node_route_inf[inode].acc_cost * route_ctx.rr_node_route_inf[inode].pres_cost; @@ -1265,17 +1265,17 @@ void push_back(t_heap* const hptr) { ++heap_tail; } -void push_back_node(int inode, float total_cost, int prev_node, int prev_edge, float backward_path_cost, float R_upstream) { +void push_back_node(const RRNodeId& inode, float total_cost, const RRNodeId& prev_node, const RREdgeId& prev_edge, float backward_path_cost, float R_upstream) { /* Puts an rr_node on the heap with the same condition as node_to_heap, * but do not fix heap property yet as that is more efficiently done from * bottom up with build_heap */ auto& route_ctx = g_vpr_ctx.routing(); - if (total_cost >= route_ctx.rr_node_route_inf[inode].path_cost) + if (total_cost >= route_ctx.rr_node_route_inf[size_t(inode)].path_cost) return; t_heap* hptr = alloc_heap_data(); - hptr->index = inode; + hptr->index = size_t(inode); hptr->cost = total_cost; hptr->u.prev.node = prev_node; hptr->u.prev.edge = prev_edge; diff --git a/vpr/src/route/route_common.h b/vpr/src/route/route_common.h index 147ff9ddc..cfcfb30ce 100644 --- a/vpr/src/route/route_common.h +++ b/vpr/src/route/route_common.h @@ -3,6 +3,7 @@ #include #include "clustered_netlist.h" #include "vtr_vector.h" +#include "rr_graph_obj.h" /* Used by the heap as its fundamental data structure. * Each heap element represents a partial route. @@ -37,11 +38,11 @@ struct t_heap { float backward_path_cost = 0.; float R_upstream = 0.; - int index = OPEN; + RRNodeId index = RRNodeId::INVALID(); struct t_prev { - int node; - int edge; + RRNodeId& node; + RREdgeId& edge; }; union { @@ -67,7 +68,7 @@ t_trace* update_traceback(t_heap* hptr, ClusterNetId net_id); void reset_path_costs(const std::vector& visited_rr_nodes); -float get_rr_cong_cost(int inode); +float get_rr_cong_cost(const RRNodeId& inode); void mark_ends(ClusterNetId net_id); void mark_remaining_ends(const std::vector& remaining_sinks); @@ -89,7 +90,7 @@ void build_heap(); void sift_down(size_t hole); void sift_up(size_t tail, t_heap* const hptr); void push_back(t_heap* const hptr); -void push_back_node(int inode, float total_cost, int prev_node, int prev_edge, float backward_path_cost, float R_upstream); +void push_back_node(const RRNodeId& inode, float total_cost, const RRNodeId& prev_node, const RREdgeId& prev_edge, float backward_path_cost, float R_upstream); bool is_valid(); void pop_heap(); void print_heap(); diff --git a/vpr/src/route/route_timing.cpp b/vpr/src/route/route_timing.cpp index cc738162b..47d1a3b2c 100644 --- a/vpr/src/route/route_timing.cpp +++ b/vpr/src/route/route_timing.cpp @@ -166,11 +166,11 @@ static t_heap* timing_driven_route_connection_from_route_tree_high_fanout(t_rt_n std::vector& modified_rr_node_inf, RouterStats& router_stats); -static t_heap* timing_driven_route_connection_from_heap(int sink_node, +static t_heap* timing_driven_route_connection_from_heap(const RRNodeId& sink_node, const t_conn_cost_params cost_params, t_bb bounding_box, const RouterLookahead& router_lookahead, - std::vector& modified_rr_node_inf, + std::vector& modified_rr_node_inf, RouterStats& router_stats); static std::vector timing_driven_find_all_shortest_paths_from_heap(const t_conn_cost_params cost_params, @@ -180,17 +180,17 @@ static std::vector timing_driven_find_all_shortest_paths_from_heap(const void disable_expansion_and_remove_sink_from_route_tree_nodes(t_rt_node* node); static void timing_driven_expand_cheapest(t_heap* cheapest, - int target_node, + const RRNodeId& target_node, const t_conn_cost_params cost_params, t_bb bounding_box, const RouterLookahead& router_lookahead, - std::vector& modified_rr_node_inf, + std::vector& modified_rr_node_inf, RouterStats& router_stats); static t_rt_node* setup_routing_resources(int itry, ClusterNetId net_id, unsigned num_sinks, float pres_fac, int min_incremental_reroute_fanout, CBRR& incremental_rerouting_res, t_rt_node** rt_node_of_sink); static void add_route_tree_to_heap(t_rt_node* rt_node, - int target_node, + const RRNodeId& target_node, const t_conn_cost_params cost_params, const RouterLookahead& router_lookahead, RouterStats& router_stats); @@ -206,7 +206,7 @@ static t_bb add_high_fanout_route_tree_to_heap(t_rt_node* rt_root, static t_bb adjust_highfanout_bounding_box(t_bb highfanout_bb); static void add_route_tree_node_to_heap(t_rt_node* rt_node, - int target_node, + const RRNodeId& target_node, const t_conn_cost_params cost_params, const RouterLookahead& router_lookahead, RouterStats& router_stats); @@ -215,36 +215,36 @@ static void timing_driven_expand_neighbours(t_heap* current, const t_conn_cost_params cost_params, t_bb bounding_box, const RouterLookahead& router_lookahead, - int target_node, + const RRNodeId& target_node, RouterStats& router_stats); static void timing_driven_expand_neighbour(t_heap* current, - const int from_node, - const t_edge_size from_edge, - const int to_node, + const RRNodeId& from_node, + const RREdgeId& from_edge, + const RRNodeId& to_node, const t_conn_cost_params cost_params, const t_bb bounding_box, const RouterLookahead& router_lookahead, - int target_node, + const RRNodeId& target_node, const t_bb target_bb, RouterStats& router_stats); static void timing_driven_add_to_heap(const t_conn_cost_params cost_params, const RouterLookahead& router_lookahead, const t_heap* current, - const int from_node, - const int to_node, - const int iconn, - const int target_node, + const RRNodeId& from_node, + const RRNodeId& to_node, + const RREdgeId& iconn, + const RRNodeId& target_node, RouterStats& router_stats); static void timing_driven_expand_node(const t_conn_cost_params cost_params, const RouterLookahead& router_lookahead, t_heap* current, - const int from_node, - const int to_node, - const int iconn, - const int target_node); + const RRNodeId& from_node, + const RRNodeId& to_node, + const RREdgeId& iconn, + const RRNodeId& target_node); static void evaluate_timing_driven_node_costs(t_heap* from, const t_conn_cost_params cost_params, @@ -1296,18 +1296,18 @@ static bool timing_driven_route_sink(ClusterNetId net_id, // //Returns either the last element of the path, or nullptr if no path is found t_heap* timing_driven_route_connection_from_route_tree(t_rt_node* rt_root, - int sink_node, + const RRNodeId& sink_node, const t_conn_cost_params cost_params, t_bb bounding_box, const RouterLookahead& router_lookahead, - std::vector& modified_rr_node_inf, + std::vector& modified_rr_node_inf, RouterStats& router_stats) { // re-explore route tree from root to add any new nodes (buildheap afterwards) // route tree needs to be repushed onto the heap since each node's cost is target specific add_route_tree_to_heap(rt_root, sink_node, cost_params, router_lookahead, router_stats); heap_::build_heap(); // via sifting down everything - int source_node = rt_root->inode; + RRNodeId source_node = rt_root->inode; if (is_empty_heap()) { VTR_LOG("No source in route tree: %s\n", describe_unrouteable_connection(source_node, sink_node).c_str()); @@ -1454,11 +1454,11 @@ static t_heap* timing_driven_route_connection_from_route_tree_high_fanout(t_rt_n //This is the core maze routing routine. // //Returns either the last element of the path, or nullptr if no path is found -static t_heap* timing_driven_route_connection_from_heap(int sink_node, +static t_heap* timing_driven_route_connection_from_heap(const RRNodeId& sink_node, const t_conn_cost_params cost_params, t_bb bounding_box, const RouterLookahead& router_lookahead, - std::vector& modified_rr_node_inf, + std::vector& modified_rr_node_inf, RouterStats& router_stats) { VTR_ASSERT_SAFE(heap_::is_valid()); @@ -1472,13 +1472,13 @@ static t_heap* timing_driven_route_connection_from_heap(int sink_node, cheapest = get_heap_head(); ++router_stats.heap_pops; - int inode = cheapest->index; + RRNodeId inode = cheapest->index; VTR_LOGV_DEBUG(f_router_debug, " Popping node %d (cost: %g)\n", - inode, cheapest->cost); + size_t(inode), cheapest->cost); //Have we found the target? if (inode == sink_node) { - VTR_LOGV_DEBUG(f_router_debug, " Found target %8d (%s)\n", inode, describe_rr_node(inode).c_str()); + VTR_LOGV_DEBUG(f_router_debug, " Found target %8d (%s)\n", size_t(inode), describe_rr_node(inode).c_str()); break; } @@ -1584,15 +1584,15 @@ static std::vector timing_driven_find_all_shortest_paths_from_heap(const } static void timing_driven_expand_cheapest(t_heap* cheapest, - int target_node, + const RRNodeId& target_node, const t_conn_cost_params cost_params, t_bb bounding_box, const RouterLookahead& router_lookahead, - std::vector& modified_rr_node_inf, + std::vector& modified_rr_node_inf, RouterStats& router_stats) { auto& route_ctx = g_vpr_ctx.mutable_routing(); - int inode = cheapest->index; + RRNodeId inode = cheapest->index; float best_total_cost = route_ctx.rr_node_route_inf[inode].path_cost; float best_back_cost = route_ctx.rr_node_route_inf[inode].backward_path_cost; @@ -1611,7 +1611,7 @@ static void timing_driven_expand_cheapest(t_heap* cheapest, if (best_total_cost > new_total_cost && best_back_cost > new_back_cost) { //Explore from this node, since the current/new partial path has the best cost //found so far - VTR_LOGV_DEBUG(f_router_debug, " Better cost to %d\n", inode); + VTR_LOGV_DEBUG(f_router_debug, " Better cost to %d\n", size_t(inode)); VTR_LOGV_DEBUG(f_router_debug, " New total cost: %g\n", new_total_cost); VTR_LOGV_DEBUG(f_router_debug, " New back cost: %g\n", new_back_cost); VTR_LOGV_DEBUG(f_router_debug, " Setting path costs for assicated node %d (from %d edge %d)\n", cheapest->index, cheapest->u.prev.node, cheapest->u.prev.edge); @@ -1630,7 +1630,7 @@ static void timing_driven_expand_cheapest(t_heap* cheapest, } else { //Post-heap prune, do not re-explore from the current/new partial path as it //has worse cost than the best partial path to this node found so far - VTR_LOGV_DEBUG(f_router_debug, " Worse cost to %d\n", inode); + VTR_LOGV_DEBUG(f_router_debug, " Worse cost to %d\n", size_t(inode)); VTR_LOGV_DEBUG(f_router_debug, " Old total cost: %g\n", best_total_cost); VTR_LOGV_DEBUG(f_router_debug, " Old back cost: %g\n", best_back_cost); VTR_LOGV_DEBUG(f_router_debug, " New total cost: %g\n", new_total_cost); @@ -1797,7 +1797,7 @@ void disable_expansion_and_remove_sink_from_route_tree_nodes(t_rt_node* rt_node) } static void add_route_tree_to_heap(t_rt_node* rt_node, - int target_node, + const RRNodeId& target_node, const t_conn_cost_params cost_params, const RouterLookahead& router_lookahead, RouterStats& router_stats) { @@ -1929,11 +1929,11 @@ static t_bb adjust_highfanout_bounding_box(t_bb highfanout_bb) { //Note that if you want to respect rt_node->re_expand that is the caller's //responsibility. static void add_route_tree_node_to_heap(t_rt_node* rt_node, - int target_node, + const RRNodeId& target_node, const t_conn_cost_params cost_params, const RouterLookahead& router_lookahead, RouterStats& router_stats) { - int inode = rt_node->inode; + const RRNodeId& inode = rt_node->inode_id; float backward_path_cost = cost_params.criticality * rt_node->Tdel; float R_upstream = rt_node->R_upstream; @@ -1955,7 +1955,7 @@ static void add_route_tree_node_to_heap(t_rt_node* rt_node, VTR_LOGV_DEBUG(f_router_debug, " Adding node %8d to heap from init route tree with cost %g (%s)\n", inode, tot_cost, describe_rr_node(inode).c_str()); - heap_::push_back_node(inode, tot_cost, NO_PREVIOUS, NO_PREVIOUS, + heap_::push_back_node(inode, tot_cost, RRNodeId::INVALID(), RREdgeId::INVALID(), backward_path_cost, R_upstream); ++router_stats.heap_pushes; @@ -1965,7 +1965,7 @@ static void timing_driven_expand_neighbours(t_heap* current, const t_conn_cost_params cost_params, t_bb bounding_box, const RouterLookahead& router_lookahead, - int target_node, + const RRNodeId& target_node, RouterStats& router_stats) { /* Puts all the rr_nodes adjacent to current on the heap. */ @@ -1974,18 +1974,17 @@ static void timing_driven_expand_neighbours(t_heap* current, t_bb target_bb; if (target_node != OPEN) { - target_bb.xmin = device_ctx.rr_nodes[target_node].xlow(); - target_bb.ymin = device_ctx.rr_nodes[target_node].ylow(); - target_bb.xmax = device_ctx.rr_nodes[target_node].xhigh(); - target_bb.ymax = device_ctx.rr_nodes[target_node].yhigh(); + target_bb.xmin = device_ctx.rr_graph.node_xlow(target_node); + target_bb.ymin = device_ctx.rr_graph.node_ylow(target_node); + target_bb.xmax = device_ctx.rr_graph.node_xhigh(target_node); + target_bb.ymax = device_ctx.rr_graph.node_yhigh(target_node); } //For each node associated with the current heap element, expand all of it's neighbours - int num_edges = device_ctx.rr_nodes[current->index].num_edges(); - for (int iconn = 0; iconn < num_edges; iconn++) { - int to_node = device_ctx.rr_nodes[current->index].edge_sink_node(iconn); + for (const RREdgeId& edge : device_ctx.rr_graph.node_out_edges(current->index)) { + const RRNodeId& to_node = device_ctx.rr_graph.edge_sink_node[edge]; timing_driven_expand_neighbour(current, - current->index, iconn, to_node, + current->index, edge, to_node, cost_params, bounding_box, router_lookahead, @@ -1999,31 +1998,31 @@ static void timing_driven_expand_neighbours(t_heap* current, //RR nodes outside the expanded bounding box specified in bounding_box are not added //to the heap. static void timing_driven_expand_neighbour(t_heap* current, - const int from_node, - const t_edge_size from_edge, - const int to_node, + const RRNodeId& from_node, + const RREdgeId& from_edge, + const RRNodeId& to_node, const t_conn_cost_params cost_params, const t_bb bounding_box, const RouterLookahead& router_lookahead, - int target_node, + const RRNodeId& target_node, const t_bb target_bb, RouterStats& router_stats) { auto& device_ctx = g_vpr_ctx.device(); - int to_xlow = device_ctx.rr_nodes[to_node].xlow(); - int to_ylow = device_ctx.rr_nodes[to_node].ylow(); - int to_xhigh = device_ctx.rr_nodes[to_node].xhigh(); - int to_yhigh = device_ctx.rr_nodes[to_node].yhigh(); + int to_xlow = device_ctx.rr_graph.node_xlow(to_node); + int to_ylow = device_ctx.rr_graph.node_ylow(to_node); + int to_xhigh = device_ctx.rr_graph.node_xhigh(to_node); + int to_yhigh = device_ctx.rr_graph.node_yhigh(to_node); if (to_xhigh < bounding_box.xmin //Strictly left of BB left-edge || to_xlow > bounding_box.xmax //Strictly right of BB right-edge || to_yhigh < bounding_box.ymin //Strictly below BB bottom-edge || to_ylow > bounding_box.ymax) { //Strictly above BB top-edge VTR_LOGV_DEBUG(f_router_debug, - " Pruned expansion of node %d edge %d -> %d" + " Pruned expansion of node %ld edge %ld -> %ld" " (to node location %d,%dx%d,%d outside of expanded" " net bounding box %d,%dx%d,%d)\n", - from_node, from_edge, to_node, + size_t(from_node), size_t(from_edge), size_t(to_node), to_xlow, to_ylow, to_xhigh, to_yhigh, bounding_box.xmin, bounding_box.ymin, bounding_box.xmax, bounding_box.ymax); return; /* Node is outside (expanded) bounding box. */ @@ -2034,7 +2033,7 @@ static void timing_driven_expand_neighbour(t_heap* current, * more promising routes, but makes route-throughs (via CLBs) impossible. * * Change this if you want to investigate route-throughs. */ if (target_node != OPEN) { - t_rr_type to_type = device_ctx.rr_nodes[to_node].type(); + t_rr_type to_type = device_ctx.rr_graph.node_type(to_node); if (to_type == IPIN) { //Check if this IPIN leads to the target block // IPIN's of the target block should be contained within it's bounding box @@ -2043,10 +2042,10 @@ static void timing_driven_expand_neighbour(t_heap* current, || to_xhigh > target_bb.xmax || to_yhigh > target_bb.ymax) { VTR_LOGV_DEBUG(f_router_debug, - " Pruned expansion of node %d edge %d -> %d" + " Pruned expansion of node %ld edge %ld -> %ld" " (to node is IPIN at %d,%dx%d,%d which does not" " lead to target block %d,%dx%d,%d)\n", - from_node, from_edge, to_node, + size_t(from_node), size_t(from_edge), size_t(to_node), to_xlow, to_ylow, to_xhigh, to_yhigh, target_bb.xmin, target_bb.ymin, target_bb.xmax, target_bb.ymax); return; @@ -2054,8 +2053,8 @@ static void timing_driven_expand_neighbour(t_heap* current, } } - VTR_LOGV_DEBUG(f_router_debug, " Expanding node %d edge %d -> %d\n", - from_node, from_edge, to_node); + VTR_LOGV_DEBUG(f_router_debug, " Expanding node %ld edge %ld -> %ld\n", + size_t(from_node), size_t(from_edge), size_t(to_node)); timing_driven_add_to_heap(cost_params, router_lookahead, @@ -2066,10 +2065,10 @@ static void timing_driven_expand_neighbour(t_heap* current, static void timing_driven_add_to_heap(const t_conn_cost_params cost_params, const RouterLookahead& router_lookahead, const t_heap* current, - const int from_node, - const int to_node, - const int iconn, - const int target_node, + const RRNodeId& from_node, + const RRNodeId& to_node, + const RREdgeId& iconn, + const RRNodeId& target_node, RouterStats& router_stats) { t_heap* next = alloc_heap_data(); next->index = to_node; @@ -2085,8 +2084,8 @@ static void timing_driven_add_to_heap(const t_conn_cost_params cost_params, auto& route_ctx = g_vpr_ctx.routing(); - float best_total_cost = route_ctx.rr_node_route_inf[to_node].path_cost; - float best_back_cost = route_ctx.rr_node_route_inf[to_node].backward_path_cost; + float best_total_cost = route_ctx.rr_node_route_inf[size_t(to_node)].path_cost; + float best_back_cost = route_ctx.rr_node_route_inf[size_t(to_node)].backward_path_cost; float new_total_cost = next->cost; float new_back_cost = next->backward_path_cost; @@ -2110,11 +2109,11 @@ static void timing_driven_add_to_heap(const t_conn_cost_params cost_params, static void timing_driven_expand_node(const t_conn_cost_params cost_params, const RouterLookahead& router_lookahead, t_heap* current, - const int from_node, - const int to_node, - const int iconn, - const int target_node) { - VTR_LOGV_DEBUG(f_router_debug, " Expanding to node %d (%s)\n", to_node, describe_rr_node(to_node).c_str()); + const RRNodeId& from_node, + const RRNodeId& to_node, + const RREdgeId& iconn, + const RRNodeId& target_node) { + VTR_LOGV_DEBUG(f_router_debug, " Expanding to node %ld (%s)\n", size_t(to_node), describe_rr_node(to_node).c_str()); evaluate_timing_driven_node_costs(current, cost_params, @@ -2131,10 +2130,10 @@ static void timing_driven_expand_node(const t_conn_cost_params cost_params, static void evaluate_timing_driven_node_costs(t_heap* to, const t_conn_cost_params cost_params, const RouterLookahead& router_lookahead, - const int from_node, - const int to_node, - const int iconn, - const int target_node) { + const RRNodeId& from_node, + const RRNodeId& to_node, + const RREdgeId& iconn, + const RRNodeId& target_node) { /* new_costs.backward_cost: is the "known" part of the cost to this node -- the * congestion cost of all the routing resources back to the existing route * plus the known delay of the total path back to the source. @@ -2146,18 +2145,18 @@ static void evaluate_timing_driven_node_costs(t_heap* to, auto& device_ctx = g_vpr_ctx.device(); //Info for the switch connecting from_node to_node - int iswitch = device_ctx.rr_nodes[from_node].edge_switch(iconn); + int iswitch = size_t(device_ctx.rr_graph.edge_switch(iconn)); bool switch_buffered = device_ctx.rr_switch_inf[iswitch].buffered(); float switch_R = device_ctx.rr_switch_inf[iswitch].R; float switch_Tdel = device_ctx.rr_switch_inf[iswitch].Tdel; float switch_Cinternal = device_ctx.rr_switch_inf[iswitch].Cinternal; //To node info - float node_C = device_ctx.rr_nodes[to_node].C(); - float node_R = device_ctx.rr_nodes[to_node].R(); + float node_C = device_ctx.rr_graph.node_C(to_node); + float node_R = device_ctx.rr_graph.node_R(to_node); //From node info - float from_node_R = device_ctx.rr_nodes[from_node].R(); + float from_node_R = device_ctx.rr_graph.node_R(from_node); //Update R_upstream if (switch_buffered) { @@ -2189,7 +2188,7 @@ static void evaluate_timing_driven_node_costs(t_heap* to, //Second, we adjust the Tdel to account for the delay caused by the internal capacitance. Tdel += Rdel_adjust * switch_Cinternal; - bool reached_configurably = device_ctx.rr_nodes[from_node].edge_is_configurable(iconn); + bool reached_configurably = device_ctx.rr_graph.edge_is_configurable(iconn); float cong_cost = 0.; if (reached_configurably) { @@ -2211,8 +2210,8 @@ static void evaluate_timing_driven_node_costs(t_heap* to, to->backward_path_cost += cost_params.criticality * Tdel; //Delay cost if (cost_params.bend_cost != 0.) { - t_rr_type from_type = device_ctx.rr_nodes[from_node].type(); - t_rr_type to_type = device_ctx.rr_nodes[to_node].type(); + t_rr_type from_type = device_ctx.rr_graph.node_type(from_node); + t_rr_type to_type = device_ctx.rr_graph.node_type(to_node); if ((from_type == CHANX && to_type == CHANY) || (from_type == CHANY && to_type == CHANX)) { to->backward_path_cost += cost_params.bend_cost; //Bend cost } @@ -2736,7 +2735,7 @@ static void print_route_status(int itry, double elapsed_sec, float pres_fac, int fflush(stdout); } -static std::string describe_unrouteable_connection(const int source_node, const int sink_node) { +static std::string describe_unrouteable_connection(const RRNodeId& source_node, const RRNodeId& sink_node) { std::string msg = vtr::string_fmt( "Cannot route from %s (%s) to " "%s (%s) -- no possible path", @@ -3051,7 +3050,7 @@ static void prune_unused_non_configurable_nets(CBRR& connections_inf) { } //Returns true if both nodes are part of the same non-configurable edge set -static bool same_non_config_node_set(int from_node, int to_node) { +static bool same_non_config_node_set(const RRNodeId& from_node, const RRNodeId& to_node) { auto& device_ctx = g_vpr_ctx.device(); auto from_itr = device_ctx.rr_node_to_non_config_node_set.find(from_node); diff --git a/vpr/src/route/route_timing.h b/vpr/src/route/route_timing.h index c356b096c..e7c808627 100644 --- a/vpr/src/route/route_timing.h +++ b/vpr/src/route/route_timing.h @@ -56,7 +56,7 @@ void alloc_timing_driven_route_structs(float** pin_criticality_ptr, t_rt_node*** rt_node_of_sink_ptr); void free_timing_driven_route_structs(float* pin_criticality, int* sink_order, t_rt_node** rt_node_of_sink); -void enable_router_debug(const t_router_opts& router_opts, ClusterNetId net, int sink_rr); +void enable_router_debug(const t_router_opts& router_opts, ClusterNetId net, const RRNodeId& sink_rr); //Delay budget information for a specific connection struct t_conn_delay_budget { @@ -80,11 +80,11 @@ struct t_conn_cost_params { }; t_heap* timing_driven_route_connection_from_route_tree(t_rt_node* rt_root, - int sink_node, + const RRNodeId& sink_node, const t_conn_cost_params cost_params, t_bb bounding_box, const RouterLookahead& router_lookahead, - std::vector& modified_rr_node_inf, + std::vector& modified_rr_node_inf, RouterStats& router_stats); std::vector timing_driven_find_all_shortest_paths_from_route_tree(t_rt_node* rt_root, diff --git a/vpr/src/route/route_tree_timing.cpp b/vpr/src/route/route_tree_timing.cpp index faf33c7dd..3b947ee43 100644 --- a/vpr/src/route/route_tree_timing.cpp +++ b/vpr/src/route/route_tree_timing.cpp @@ -536,7 +536,7 @@ void load_route_tree_Tdel(t_rt_node* subtree_rt_root, float Tarrival) { * must be correct before this routine is called. Tarrival is the time at * at which the signal arrives at this node's *input*. */ - int inode; + RRNodeId inode; short iswitch; t_rt_node* child_node; t_linked_rt_edge* linked_rt_edge; @@ -550,7 +550,7 @@ void load_route_tree_Tdel(t_rt_node* subtree_rt_root, float Tarrival) { * along a wire segment's length. See discussion in net_delay.c if you want * to change this. */ - Tdel = Tarrival + 0.5 * subtree_rt_root->C_downstream * device_ctx.rr_nodes[inode].R(); + Tdel = Tarrival + 0.5 * subtree_rt_root->C_downstream * device_ctx.rr_graph.node_R(inode); subtree_rt_root->Tdel = Tdel; /* Now expand the children of this node to load their Tdel values (depth- @@ -1415,7 +1415,7 @@ bool is_uncongested_route_tree(const t_rt_node* root) { } t_rt_node* -init_route_tree_to_source_no_net(int inode) { +init_route_tree_to_source_no_net(const RRNodeId& inode) { /* Initializes the routing tree to just the net source, and returns the root * node of the rt_tree (which is just the net source). */ @@ -1428,11 +1428,14 @@ init_route_tree_to_source_no_net(int inode) { rt_root->parent_node = nullptr; rt_root->parent_switch = OPEN; rt_root->re_expand = true; - rt_root->inode = inode; - rt_root->C_downstream = device_ctx.rr_nodes[inode].C(); - rt_root->R_upstream = device_ctx.rr_nodes[inode].R(); - rt_root->Tdel = 0.5 * device_ctx.rr_nodes[inode].R() * device_ctx.rr_nodes[inode].C(); - rr_node_to_rt_node[inode] = rt_root; + rt_root->inode = size_t(inode); + + rt_root->inode_id = inode; + + rt_root->C_downstream = device_ctx.rr_graph.node_C(inode); + rt_root->R_upstream = device_ctx.rr_graph.node_R(inode); + rt_root->Tdel = 0.5 * device_ctx.rr_graph.node_R(inode) * device_ctx.rr_graph.node_C(inode); + rr_node_to_rt_node[size_t(inode)] = rt_root; return (rt_root); } diff --git a/vpr/src/route/route_tree_timing.h b/vpr/src/route/route_tree_timing.h index 01e4d88a2..781b11e5d 100644 --- a/vpr/src/route/route_tree_timing.h +++ b/vpr/src/route/route_tree_timing.h @@ -4,6 +4,7 @@ #include "connection_based_routing.h" #include "route_common.h" #include "spatial_route_tree_lookup.h" +#include "rr_graph_obj.h" /**************** Subroutines exported by route_tree_timing.c ***************/ @@ -30,7 +31,7 @@ void update_remaining_net_delays_from_route_tree(float* net_delay, void load_route_tree_Tdel(t_rt_node* rt_root, float Tarrival); void load_route_tree_rr_route_inf(t_rt_node* root); -t_rt_node* init_route_tree_to_source_no_net(int inode); +t_rt_node* init_route_tree_to_source_no_net(const RRNodeId& inode); void add_route_tree_to_rr_node_lookup(t_rt_node* node); diff --git a/vpr/src/route/route_tree_type.h b/vpr/src/route/route_tree_type.h index 5e81bd1e4..91fb61296 100644 --- a/vpr/src/route/route_tree_type.h +++ b/vpr/src/route/route_tree_type.h @@ -51,10 +51,8 @@ struct t_rt_node { RRSwitchId parent_switch_id; bool re_expand; - int inode; - /* Xifan Tang - RRGraph node */ - RRNodeId inode_id; + RRNodeId inode; float C_downstream; float R_upstream; diff --git a/vpr/src/route/router_delay_profiling.cpp b/vpr/src/route/router_delay_profiling.cpp index ab0343e43..39dbce7fa 100644 --- a/vpr/src/route/router_delay_profiling.cpp +++ b/vpr/src/route/router_delay_profiling.cpp @@ -13,7 +13,7 @@ RouterDelayProfiler::RouterDelayProfiler( const RouterLookahead* lookahead) : router_lookahead_(lookahead) {} -bool RouterDelayProfiler::calculate_delay(int source_node, int sink_node, const t_router_opts& router_opts, float* net_delay) const { +bool RouterDelayProfiler::calculate_delay(const RRNodeId& source_node, const RRNodeId& sink_node, const t_router_opts& router_opts, float* net_delay) const { /* Returns true as long as found some way to hook up this net, even if that * * way resulted in overuse of resources (congestion). If there is no way * * to route this net, even ignoring congestion, it returns false. In this * @@ -22,7 +22,8 @@ bool RouterDelayProfiler::calculate_delay(int source_node, int sink_node, const auto& route_ctx = g_vpr_ctx.routing(); t_rt_node* rt_root = setup_routing_resources_no_net(source_node); - enable_router_debug(router_opts, ClusterNetId(), sink_node); + /* TODO: This should be changed to RRNodeId */ + enable_router_debug(router_opts, ClusterNetId(), size_t(sink_node)); /* Update base costs according to fanout and criticality rules */ update_rr_base_costs(1); @@ -59,7 +60,7 @@ bool RouterDelayProfiler::calculate_delay(int source_node, int sink_node, const //find delay *net_delay = rt_node_of_sink->Tdel; - VTR_ASSERT_MSG(route_ctx.rr_node_route_inf[rt_root->inode].occ() <= device_ctx.rr_nodes[rt_root->inode].capacity(), "SOURCE should never be congested"); + VTR_ASSERT_MSG(route_ctx.rr_node_route_inf[rt_root->inode].occ() <= device_ctx.rr_graph.node_capacity(rt_root->inode_id), "SOURCE should never be congested"); free_route_tree(rt_root); } diff --git a/vpr/src/route/router_delay_profiling.h b/vpr/src/route/router_delay_profiling.h index 7bab4e2ed..7f3918685 100644 --- a/vpr/src/route/router_delay_profiling.h +++ b/vpr/src/route/router_delay_profiling.h @@ -9,7 +9,7 @@ class RouterDelayProfiler { public: RouterDelayProfiler(const RouterLookahead* lookahead); - bool calculate_delay(int source_node, int sink_node, const t_router_opts& router_opts, float* net_delay) const; + bool calculate_delay(const RRNodeId& source_node, const RRNodeId& sink_node, const t_router_opts& router_opts, float* net_delay) const; private: const RouterLookahead* router_lookahead_; diff --git a/vpr/src/route/router_lookahead.cpp b/vpr/src/route/router_lookahead.cpp index 130b862e9..1c31582b6 100644 --- a/vpr/src/route/router_lookahead.cpp +++ b/vpr/src/route/router_lookahead.cpp @@ -5,7 +5,7 @@ #include "globals.h" #include "route_timing.h" -static int get_expected_segs_to_target(int inode, int target_node, int* num_segs_ortho_dir_ptr); +static int get_expected_segs_to_target(const RRNodeId& inode, const RRNodeId& target_node, int* num_segs_ortho_dir_ptr); static int round_up(float x); static std::unique_ptr make_router_lookahead_object(e_router_lookahead router_lookahead_type) { @@ -41,10 +41,10 @@ std::unique_ptr make_router_lookahead( return router_lookahead; } -float ClassicLookahead::get_expected_cost(int current_node, int target_node, const t_conn_cost_params& params, float R_upstream) const { +float ClassicLookahead::get_expected_cost(const RRNodeId& current_node, const RRNodeId& target_node, const t_conn_cost_params& params, float R_upstream) const { auto& device_ctx = g_vpr_ctx.device(); - t_rr_type rr_type = device_ctx.rr_nodes[current_node].type(); + t_rr_type rr_type = device_ctx.rr_graph.node_type(current_node); if (rr_type == CHANX || rr_type == CHANY) { return classic_wire_lookahead_cost(current_node, target_node, params.criticality, R_upstream); @@ -55,15 +55,15 @@ float ClassicLookahead::get_expected_cost(int current_node, int target_node, con } } -float ClassicLookahead::classic_wire_lookahead_cost(int inode, int target_node, float criticality, float R_upstream) const { +float ClassicLookahead::classic_wire_lookahead_cost(const RRNodeId& inode, const RRNodeId& target_node, float criticality, float R_upstream) const { auto& device_ctx = g_vpr_ctx.device(); - VTR_ASSERT_SAFE(device_ctx.rr_nodes[inode].type() == CHANX || device_ctx.rr_nodes[inode].type() == CHANY); + VTR_ASSERT_SAFE(device_ctx.rr_graph.node_type(inode) == CHANX || device_ctx.rr_graph.node_type(inode) == CHANY); int num_segs_ortho_dir = 0; int num_segs_same_dir = get_expected_segs_to_target(inode, target_node, &num_segs_ortho_dir); - int cost_index = device_ctx.rr_nodes[inode].cost_index(); + int cost_index = device_ctx.rr_graph.node_cost_index(inode); int ortho_cost_index = device_ctx.rr_indexed_data[cost_index].ortho_cost_index; const auto& same_data = device_ctx.rr_indexed_data[cost_index]; @@ -87,10 +87,10 @@ float ClassicLookahead::classic_wire_lookahead_cost(int inode, int target_node, return (expected_cost); } -float MapLookahead::get_expected_cost(int current_node, int target_node, const t_conn_cost_params& params, float /*R_upstream*/) const { +float MapLookahead::get_expected_cost(const RRNodeId& current_node, const RRNodeId& target_node, const t_conn_cost_params& params, float /*R_upstream*/) const { auto& device_ctx = g_vpr_ctx.device(); - t_rr_type rr_type = device_ctx.rr_nodes[current_node].type(); + t_rr_type rr_type = device_ctx.rr_graph.node_type(current_node); if (rr_type == CHANX || rr_type == CHANY) { return get_lookahead_map_cost(current_node, target_node, params.criticality); @@ -105,7 +105,7 @@ void MapLookahead::compute(const std::vector& segment_inf) { compute_router_lookahead(segment_inf.size()); } -float NoOpLookahead::get_expected_cost(int /*current_node*/, int /*target_node*/, const t_conn_cost_params& /*params*/, float /*R_upstream*/) const { +float NoOpLookahead::get_expected_cost(const RRNodeId& /*current_node*/, const RRNodeId& /*target_node*/, const t_conn_cost_params& /*params*/, float /*R_upstream*/) const { return 0.; } @@ -115,7 +115,7 @@ static int round_up(float x) { return std::ceil(x - 0.001); } -static int get_expected_segs_to_target(int inode, int target_node, int* num_segs_ortho_dir_ptr) { +static int get_expected_segs_to_target(const RRNodeId& inode, const RRNodeId& target_node, int* num_segs_ortho_dir_ptr) { /* Returns the number of segments the same type as inode that will be needed * * to reach target_node (not including inode) in each direction (the same * * direction (horizontal or vertical) as inode and the orthogonal direction).*/ @@ -127,18 +127,18 @@ static int get_expected_segs_to_target(int inode, int target_node, int* num_segs int no_need_to_pass_by_clb; float inv_length, ortho_inv_length, ylow, yhigh, xlow, xhigh; - target_x = device_ctx.rr_nodes[target_node].xlow(); - target_y = device_ctx.rr_nodes[target_node].ylow(); - cost_index = device_ctx.rr_nodes[inode].cost_index(); + target_x = device_ctx.rr_graph.node_xlow(target_node); + target_y = device_ctx.rr_graph.node_ylow(target_node); + cost_index = device_ctx.rr_graph.node_cost_index(inode); inv_length = device_ctx.rr_indexed_data[cost_index].inv_length; ortho_cost_index = device_ctx.rr_indexed_data[cost_index].ortho_cost_index; ortho_inv_length = device_ctx.rr_indexed_data[ortho_cost_index].inv_length; - rr_type = device_ctx.rr_nodes[inode].type(); + rr_type = device_ctx.rr_graph.node_type(inode); if (rr_type == CHANX) { - ylow = device_ctx.rr_nodes[inode].ylow(); - xhigh = device_ctx.rr_nodes[inode].xhigh(); - xlow = device_ctx.rr_nodes[inode].xlow(); + ylow = device_ctx.rr_graph.node_ylow(inode); + xhigh = device_ctx.rr_graph.node_xhigh(inode); + xlow = device_ctx.rr_graph.node_xlow(inode); /* Count vertical (orthogonal to inode) segs first. */ @@ -163,9 +163,9 @@ static int get_expected_segs_to_target(int inode, int target_node, int* num_segs num_segs_same_dir = 0; } } else { /* inode is a CHANY */ - ylow = device_ctx.rr_nodes[inode].ylow(); - yhigh = device_ctx.rr_nodes[inode].yhigh(); - xlow = device_ctx.rr_nodes[inode].xlow(); + ylow = device_ctx.rr_graph.node_ylow(inode); + yhigh = device_ctx.rr_graph.node_yhigh(inode); + xlow = device_ctx.rr_graph.node_xlow(inode); /* Count horizontal (orthogonal to inode) segs first. */ diff --git a/vpr/src/route/router_lookahead.h b/vpr/src/route/router_lookahead.h index 283df54be..9184bc429 100644 --- a/vpr/src/route/router_lookahead.h +++ b/vpr/src/route/router_lookahead.h @@ -12,7 +12,7 @@ class RouterLookahead { // // Either compute or read methods must be invoked before invoking // get_expected_cost. - virtual float get_expected_cost(int node, int target_node, const t_conn_cost_params& params, float R_upstream) const = 0; + virtual float get_expected_cost(const RRNodeId& node, const RRNodeId& target_node, const t_conn_cost_params& params, float R_upstream) const = 0; // Compute router lookahead (if needed). virtual void compute(const std::vector& segment_inf) = 0; @@ -53,7 +53,7 @@ const RouterLookahead* get_cached_router_lookahead( class ClassicLookahead : public RouterLookahead { public: - float get_expected_cost(int node, int target_node, const t_conn_cost_params& params, float R_upstream) const override; + float get_expected_cost(const RRNodeId& node, const RRNodeId& target_node, const t_conn_cost_params& params, float R_upstream) const override; void compute(const std::vector& /*segment_inf*/) override { } @@ -65,12 +65,12 @@ class ClassicLookahead : public RouterLookahead { } private: - float classic_wire_lookahead_cost(int node, int target_node, float criticality, float R_upstream) const; + float classic_wire_lookahead_cost(const RRNodeId& node, const RRNodeId& target_node, float criticality, float R_upstream) const; }; class MapLookahead : public RouterLookahead { protected: - float get_expected_cost(int node, int target_node, const t_conn_cost_params& params, float R_upstream) const override; + float get_expected_cost(const RRNodeId& node, const RRNodeId& target_node, const t_conn_cost_params& params, float R_upstream) const override; void compute(const std::vector& segment_inf) override; void read(const std::string& /*file*/) override { VPR_THROW(VPR_ERROR_ROUTE, "MapLookahead::read unimplemented"); @@ -82,7 +82,7 @@ class MapLookahead : public RouterLookahead { class NoOpLookahead : public RouterLookahead { protected: - float get_expected_cost(int node, int target_node, const t_conn_cost_params& params, float R_upstream) const override; + float get_expected_cost(const RRNodeId& node, const RRNodeId& target_node, const t_conn_cost_params& params, float R_upstream) const override; void compute(const std::vector& /*segment_inf*/) override { } void read(const std::string& /*file*/) override { diff --git a/vpr/src/route/spatial_route_tree_lookup.cpp b/vpr/src/route/spatial_route_tree_lookup.cpp index 43891de9e..20c9b8524 100644 --- a/vpr/src/route/spatial_route_tree_lookup.cpp +++ b/vpr/src/route/spatial_route_tree_lookup.cpp @@ -32,12 +32,10 @@ SpatialRouteTreeLookup build_route_tree_spatial_lookup(ClusterNetId net, t_rt_no void update_route_tree_spatial_lookup_recur(t_rt_node* rt_node, SpatialRouteTreeLookup& spatial_lookup) { auto& device_ctx = g_vpr_ctx.device(); - auto& rr_node = device_ctx.rr_nodes[rt_node->inode]; - - int bin_xlow = grid_to_bin_x(rr_node.xlow(), spatial_lookup); - int bin_ylow = grid_to_bin_y(rr_node.ylow(), spatial_lookup); - int bin_xhigh = grid_to_bin_x(rr_node.xhigh(), spatial_lookup); - int bin_yhigh = grid_to_bin_y(rr_node.yhigh(), spatial_lookup); + int bin_xlow = grid_to_bin_x(device_ctx.rr_graph.node_xlow(rt_node->inode), spatial_lookup); + int bin_ylow = grid_to_bin_y(device_ctx.rr_graph.node_ylow(rt_node->inode), spatial_lookup); + int bin_xhigh = grid_to_bin_x(device_ctx.rr_graph.node_xhigh(rt_node->inode), spatial_lookup); + int bin_yhigh = grid_to_bin_y(device_ctx.rr_graph.node_yhigh(rt_node->inode), spatial_lookup); spatial_lookup[bin_xlow][bin_ylow].push_back(rt_node); diff --git a/vpr/src/util/vpr_utils.cpp b/vpr/src/util/vpr_utils.cpp index e19d17209..dfac49522 100644 --- a/vpr/src/util/vpr_utils.cpp +++ b/vpr/src/util/vpr_utils.cpp @@ -235,22 +235,22 @@ std::vector block_type_class_index_to_pin_names(t_physical_tile_typ return pin_names; } -std::string rr_node_arch_name(int inode) { +std::string rr_node_arch_name(const RRNodeId& inode) { auto& device_ctx = g_vpr_ctx.device(); - const t_rr_node& rr_node = device_ctx.rr_nodes[inode]; + const RRGraph& rr_graph = device_ctx.rr_graph; std::string rr_node_arch_name; - if (rr_node.type() == OPIN || rr_node.type() == IPIN) { + if (rr_graph.node_type(inode) == OPIN || rr_graph.node_type(inode) == IPIN) { //Pin names - auto type = device_ctx.grid[rr_node.xlow()][rr_node.ylow()].type; - rr_node_arch_name += block_type_pin_index_to_name(type, rr_node.ptc_num()); - } else if (rr_node.type() == SOURCE || rr_node.type() == SINK) { + auto type = device_ctx.grid[rr_graph.node_xlow(inode)][rr_graph.node_ylow(inode)].type; + rr_node_arch_name += block_type_pin_index_to_name(type, rr_graph.node_ptc_num(inode)); + } else if (rr_graph.node_type(inode) == SOURCE || rr_graph.node_type(inode) == SINK) { //Set of pins associated with SOURCE/SINK - auto type = device_ctx.grid[rr_node.xlow()][rr_node.ylow()].type; - auto pin_names = block_type_class_index_to_pin_names(type, rr_node.ptc_num()); + auto type = device_ctx.grid[rr_graph.node_xlow(inode)][rr_graph.node_ylow(inode)].type; + auto pin_names = block_type_class_index_to_pin_names(type, rr_graph.node_ptc_num(inode)); if (pin_names.size() > 1) { - rr_node_arch_name += rr_node.type_string(); + rr_node_arch_name += rr_node_typename[rr_graph.node_type(inode)]; rr_node_arch_name += " connected to "; rr_node_arch_name += "{"; rr_node_arch_name += vtr::join(pin_names, ", "); @@ -259,9 +259,9 @@ std::string rr_node_arch_name(int inode) { rr_node_arch_name += pin_names[0]; } } else { - VTR_ASSERT(rr_node.type() == CHANX || rr_node.type() == CHANY); + VTR_ASSERT(rr_graph.node_type(inode) == CHANX || rr_graph.node_type(inode) == CHANY); //Wire segment name - auto cost_index = rr_node.cost_index(); + auto cost_index = rr_graph.node_cost_index(inode); int seg_index = device_ctx.rr_indexed_data[cost_index].seg_index; rr_node_arch_name += device_ctx.arch->Segments[seg_index].name; diff --git a/vpr/src/util/vpr_utils.h b/vpr/src/util/vpr_utils.h index e43cc91da..544fc61af 100644 --- a/vpr/src/util/vpr_utils.h +++ b/vpr/src/util/vpr_utils.h @@ -48,7 +48,7 @@ std::string block_type_pin_index_to_name(t_physical_tile_type_ptr type, int pin_ std::vector block_type_class_index_to_pin_names(t_physical_tile_type_ptr type, int class_index); //Returns a user-friendly architectural identifier for the specified RR node -std::string rr_node_arch_name(int inode); +std::string rr_node_arch_name(const RRNodeId& inode); /************************************************************** * Intra-Logic Block Utility Functions From f3d9067f9b3c409c8c20edebd7bfa22c48c4b37d Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 31 Jan 2020 23:25:35 -0700 Subject: [PATCH 066/645] halfway in refactoring the drawer with RRGraph --- vpr/src/base/vpr_types.h | 6 +- vpr/src/draw/draw.cpp | 278 +++++++++++++++++++------------------- vpr/src/draw/draw.h | 7 +- vpr/src/draw/draw_types.h | 2 +- 4 files changed, 141 insertions(+), 152 deletions(-) diff --git a/vpr/src/base/vpr_types.h b/vpr/src/base/vpr_types.h index bf080539f..15508a4f8 100644 --- a/vpr/src/base/vpr_types.h +++ b/vpr/src/base/vpr_types.h @@ -1136,12 +1136,8 @@ typedef std::vector>>>> t_r * next: Pointer to the next traceback element in this route. */ struct t_trace { t_trace* next; - int index; + RRNodeId index; short iswitch; - - /* Xifan Tang - RRGraph unique ids */ - RRNodeId node_id; - RRSwitchId switch_id; }; /* Extra information about each rr_node needed only during routing (i.e. * diff --git a/vpr/src/draw/draw.cpp b/vpr/src/draw/draw.cpp index 8ba6ca124..a63e9c51c 100644 --- a/vpr/src/draw/draw.cpp +++ b/vpr/src/draw/draw.cpp @@ -25,6 +25,7 @@ #include "vtr_memory.h" #include "vtr_log.h" #include "vtr_color_map.h" +#include "vtr_vector.h" #include "vpr_utils.h" #include "vpr_error.h" @@ -88,20 +89,20 @@ void act_on_mouse_press(ezgl::application* app, GdkEventButton* event, double x, void act_on_mouse_move(ezgl::application* app, GdkEventButton* event, double x, double y); static void draw_routed_net(ClusterNetId net, ezgl::renderer* g); -void draw_partial_route(const std::vector& rr_nodes_to_draw, ezgl::renderer* g); +void draw_partial_route(const std::vector& rr_nodes_to_draw, ezgl::renderer* g); static void draw_rr(ezgl::renderer* g); -static void draw_rr_edges(int from_node, ezgl::renderer* g); -static void draw_rr_pin(int inode, const ezgl::color& color, ezgl::renderer* g); -static void draw_rr_chan(int inode, const ezgl::color color, ezgl::renderer* g); -static void draw_rr_src_sink(int inode, ezgl::color color, ezgl::renderer* g); -static void draw_pin_to_chan_edge(int pin_node, int chan_node, ezgl::renderer* g); +static void draw_rr_edges(const RRNodeId& from_node, ezgl::renderer* g); +static void draw_rr_pin(const RRNodeId& inode, const ezgl::color& color, ezgl::renderer* g); +static void draw_rr_chan(const RRNodeId& inode, const ezgl::color color, ezgl::renderer* g); +static void draw_rr_src_sink(const RRNodeId& inode, ezgl::color color, ezgl::renderer* g); +static void draw_pin_to_chan_edge(const RRNodeId& pin_node, const RRNodeId& chan_node, ezgl::renderer* g); static void draw_x(float x, float y, float size, ezgl::renderer* g); -static void draw_pin_to_pin(int opin, int ipin, ezgl::renderer* g); +static void draw_pin_to_pin(const RRNodeId& opin, const RRNodeId& ipin, ezgl::renderer* g); static void draw_rr_switch(float from_x, float from_y, float to_x, float to_y, bool buffered, bool switch_configurable, ezgl::renderer* g); -static void draw_chany_to_chany_edge(int from_node, int to_node, int to_track, short switch_type, ezgl::renderer* g); -static void draw_chanx_to_chanx_edge(int from_node, int to_node, int to_track, short switch_type, ezgl::renderer* g); -static void draw_chanx_to_chany_edge(int chanx_node, int chanx_track, int chany_node, int chany_track, enum e_edge_dir edge_dir, short switch_type, ezgl::renderer* g); -static int get_track_num(int inode, const vtr::OffsetMatrix& chanx_track, const vtr::OffsetMatrix& chany_track); +static void draw_chany_to_chany_edge(const RRNodeId& from_node, const RRNodeId& to_node, int to_track, short switch_type, ezgl::renderer* g); +static void draw_chanx_to_chanx_edge(const RRNodeId& from_node, const RRNodeId& to_node, int to_track, short switch_type, ezgl::renderer* g); +static void draw_chanx_to_chany_edge(const RRNodeId& chanx_node, int chanx_track, const RRNodeId& chany_node, int chany_track, enum e_edge_dir edge_dir, short switch_type, ezgl::renderer* g); +static int get_track_num(const RRNodeId& inode, const vtr::OffsetMatrix& chanx_track, const vtr::OffsetMatrix& chany_track); static bool draw_if_net_highlighted(ClusterNetId inet); static int draw_check_rr_node_hit(float click_x, float click_y); @@ -131,7 +132,7 @@ static void draw_block_pin_util(); static float get_router_rr_cost(const t_rr_node_route_inf node_inf, e_draw_router_rr_cost draw_router_rr_cost); static void draw_router_rr_costs(ezgl::renderer* g); -static void draw_rr_costs(ezgl::renderer* g, const std::vector& rr_costs, bool lowest_cost_first = true); +static void draw_rr_costs(ezgl::renderer* g, const vtr::vector& rr_costs, bool lowest_cost_first = true); void draw_main_canvas(ezgl::renderer* g); void initial_setup_NO_PICTURE_to_PLACEMENT(ezgl::application* app, bool is_new_window); @@ -830,8 +831,7 @@ void alloc_draw_structs(const t_arch* arch) { /* Space is allocated for draw_rr_node but not initialized because we do * * not yet know information about the routing resources. */ - draw_state->draw_rr_node = (t_draw_rr_node*)vtr::malloc( - device_ctx.rr_nodes.size() * sizeof(t_draw_rr_node)); + draw_state->draw_rr_node.resize(device_ctx.rr_graph.nodes().size()); draw_state->arch_info = arch; @@ -860,7 +860,7 @@ void free_draw_structs() { if (draw_state != nullptr) { free(draw_state->draw_rr_node); - draw_state->draw_rr_node = nullptr; + draw_state->draw_rr_node.clear(); } #else ; @@ -881,11 +881,10 @@ void init_draw_coords(float width_val) { /* Each time routing is on screen, need to reallocate the color of each * * rr_node, as the number of rr_nodes may change. */ if (device_ctx.rr_graph.nodes().size() != 0) { - draw_state->draw_rr_node = (t_draw_rr_node*)vtr::realloc(draw_state->draw_rr_node, - (device_ctx.rr_graph.nodes().size()) * sizeof(t_draw_rr_node)); - for (size_t i = 0; i < device_ctx.rr_graph.nodes().size(); i++) { - draw_state->draw_rr_node[i].color = DEFAULT_RR_NODE_COLOR; - draw_state->draw_rr_node[i].node_highlighted = false; + draw_state->draw_rr_node.resize(device_ctx.rr_graph.nodes().size()); + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { + draw_state->draw_rr_node[inode].color = DEFAULT_RR_NODE_COLOR; + draw_state->draw_rr_node[inode].node_highlighted = false; } } draw_coords->tile_width = width_val; @@ -1160,8 +1159,8 @@ static void draw_routing_costs(ezgl::renderer* g) { float min_cost = std::numeric_limits::infinity(); float max_cost = -min_cost; - std::vector rr_node_costs(device_ctx.rr_nodes.size(), 0.); - for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); inode++) { + std::vector rr_node_costs(device_ctx.rr_graph.nodes().size(), 0.); + for (size_t inode = 0; inode < device_ctx.rr_graph.nodes().size(); inode++) { float cost = 0.; if (draw_state->show_routing_costs == DRAW_TOTAL_ROUTING_COSTS || draw_state->show_routing_costs == DRAW_LOG_TOTAL_ROUTING_COSTS) { @@ -1195,7 +1194,7 @@ static void draw_routing_costs(ezgl::renderer* g) { } //Hide min value, draw_rr_costs() ignores NaN's - for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); inode++) { + for (size_t inode = 0; inode < device_ctx.rr_graph.nodes().size(); inode++) { if (rr_node_costs[inode] == min_cost) { rr_node_costs[inode] = NAN; } @@ -1293,7 +1292,7 @@ void draw_rr(ezgl::renderer* g) { g->set_line_dash(ezgl::line_dash::none); - for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); inode++) { + for (size_t inode = 0; inode < device_ctx.rr_graph.nodes().size(); inode++) { if (!draw_state->draw_rr_node[inode].node_highlighted) { /* If not highlighted node, assign color based on type. */ switch (device_ctx.rr_nodes[inode].type()) { @@ -1346,15 +1345,15 @@ void draw_rr(ezgl::renderer* g) { drawroute(HIGHLIGHTED, g); } -static void draw_rr_chan(int inode, const ezgl::color color, ezgl::renderer* g) { +static void draw_rr_chan(const RRNodeId& inode, const ezgl::color color, ezgl::renderer* g) { auto& device_ctx = g_vpr_ctx.device(); - t_rr_type type = device_ctx.rr_nodes[inode].type(); + t_rr_type type = device_ctx.rr_graph.node_type(inode); VTR_ASSERT(type == CHANX || type == CHANY); ezgl::rectangle bound_box = draw_get_rr_chan_bbox(inode); - e_direction dir = device_ctx.rr_nodes[inode].direction(); + e_direction dir = device_ctx.rr_graph.node_direction(inode); //We assume increasing direction, and swap if needed ezgl::point2d start = bound_box.bottom_left(); @@ -1380,8 +1379,8 @@ static void draw_rr_chan(int inode, const ezgl::color color, ezgl::renderer* g) int coord_min = -1; int coord_max = -1; if (type == CHANX) { - coord_min = device_ctx.rr_nodes[inode].xlow(); - coord_max = device_ctx.rr_nodes[inode].xhigh(); + coord_min = device_ctx.rr_graph.node_xlow(inode); + coord_max = device_ctx.rr_graph.node_xhigh(inode); if (dir == INC_DIRECTION) { mux_dir = RIGHT; } else { @@ -1389,8 +1388,8 @@ static void draw_rr_chan(int inode, const ezgl::color color, ezgl::renderer* g) } } else { VTR_ASSERT(type == CHANY); - coord_min = device_ctx.rr_nodes[inode].ylow(); - coord_max = device_ctx.rr_nodes[inode].yhigh(); + coord_min = device_ctx.rr_graph.node_ylow(inode); + coord_max = device_ctx.rr_graph.node_yhigh(inode); if (dir == INC_DIRECTION) { mux_dir = TOP; } else { @@ -1461,7 +1460,7 @@ static void draw_rr_chan(int inode, const ezgl::color color, ezgl::renderer* g) if (switchpoint_max == 0) { if (dir != BI_DIRECTION) { //Draw a mux at the start of each wire, labelled with it's size (#inputs) - draw_mux_with_size(start, mux_dir, WIRE_DRAWING_WIDTH, device_ctx.rr_nodes[inode].fan_in(), g); + draw_mux_with_size(start, mux_dir, WIRE_DRAWING_WIDTH, device_ctx.rr_graph.node_in_edges(inode).size(), g); } } else { //Draw arrows and label with switch point @@ -1487,30 +1486,31 @@ static void draw_rr_chan(int inode, const ezgl::color color, ezgl::renderer* g) g->set_color(color); //Ensure color is still set correctly if we drew any arrows/text } -static void draw_rr_edges(int inode, ezgl::renderer* g) { +static void draw_rr_edges(const RRNodeId& inode, ezgl::renderer* g) { /* Draws all the edges that the user wants shown between inode and what it * * connects to. inode is assumed to be a CHANX, CHANY, or IPIN. */ t_draw_state* draw_state = get_draw_state_vars(); auto& device_ctx = g_vpr_ctx.device(); t_rr_type from_type, to_type; - int to_node, from_ptc_num, to_ptc_num; + RRNodeId to_node; + int from_ptc_num, to_ptc_num; short switch_type; - from_type = device_ctx.rr_nodes[inode].type(); + from_type = device_ctx.rr_graph.node_type(inode); if ((draw_state->draw_rr_toggle == DRAW_NODES_RR) || (draw_state->draw_rr_toggle == DRAW_NODES_AND_SBOX_RR && from_type == OPIN)) { return; /* Nothing to draw. */ } - from_ptc_num = device_ctx.rr_nodes[inode].ptc_num(); + from_ptc_num = device_ctx.rr_graph.node_ptc_num(inode); - for (t_edge_size iedge = 0, l = device_ctx.rr_nodes[inode].num_edges(); iedge < l; iedge++) { - to_node = device_ctx.rr_nodes[inode].edge_sink_node(iedge); - to_type = device_ctx.rr_nodes[to_node].type(); - to_ptc_num = device_ctx.rr_nodes[to_node].ptc_num(); - bool edge_configurable = device_ctx.rr_nodes[inode].edge_is_configurable(iedge); + for (const RREdgeId& iedge : device_ctx.rr_graph.node_out_edges(inode)) { + to_node = device_ctx.rr_graph.edge_sink_node(iedge); + to_type = device_ctx.rr_graph.node_type(to_node); + to_ptc_num = device_ctx.rr_graph.node_ptc_num(to_node); + bool edge_configurable = device_ctx.rr_graph.edge_is_configurable(iedge); switch (from_type) { case OPIN: @@ -1707,7 +1707,7 @@ static void draw_x(float x, float y, float size, ezgl::renderer* g) { g->draw_line({x - size, y - size}, {x + size, y + size}); } -static void draw_chanx_to_chany_edge(int chanx_node, int chanx_track, int chany_node, int chany_track, enum e_edge_dir edge_dir, short switch_type, ezgl::renderer* g) { +static void draw_chanx_to_chany_edge(const RRNodeId& chanx_node, int chanx_track, const RRNodeId& chany_node, int chany_track, enum e_edge_dir edge_dir, short switch_type, ezgl::renderer* g) { t_draw_state* draw_state = get_draw_state_vars(); t_draw_coords* draw_coords = get_draw_coords_vars(); auto& device_ctx = g_vpr_ctx.device(); @@ -1729,16 +1729,16 @@ static void draw_chanx_to_chany_edge(int chanx_node, int chanx_track, int chany_ y1 = chanx_bbox.bottom(); x2 = chany_bbox.left(); - chanx_xlow = device_ctx.rr_nodes[chanx_node].xlow(); - chanx_y = device_ctx.rr_nodes[chanx_node].ylow(); - chany_x = device_ctx.rr_nodes[chany_node].xlow(); - chany_ylow = device_ctx.rr_nodes[chany_node].ylow(); + chanx_xlow = device_ctx.rr_graph.node_xlow(chanx_node); + chanx_y = device_ctx.rr_graph.node_ylow(chanx_node); + chany_x = device_ctx.rr_graph.node_xlow(chany_node); + chany_ylow = device_ctx.rr_graph.node_ylow(chany_node); if (chanx_xlow <= chany_x) { /* Can draw connection going right */ /* Connection not at end of the CHANX segment. */ x1 = draw_coords->tile_x[chany_x] + draw_coords->get_tile_width(); - if (device_ctx.rr_nodes[chanx_node].direction() != BI_DIRECTION) { + if (device_ctx.rr_graph.node_direction(chanx_node) != BI_DIRECTION) { if (edge_dir == FROM_X_TO_Y) { if ((chanx_track % 2) == 1) { /* If dec wire, then going left */ x1 = draw_coords->tile_x[chany_x + 1]; @@ -1754,7 +1754,7 @@ static void draw_chanx_to_chany_edge(int chanx_node, int chanx_track, int chany_ /* Connection not at end of the CHANY segment. */ y2 = draw_coords->tile_y[chanx_y] + draw_coords->get_tile_width(); - if (device_ctx.rr_nodes[chany_node].direction() != BI_DIRECTION) { + if (device_ctx.rr_graph.node_direction(chany_node) != BI_DIRECTION) { if (edge_dir == FROM_Y_TO_X) { if ((chany_track % 2) == 1) { /* If dec wire, then going down */ y2 = draw_coords->tile_y[chanx_y + 1]; @@ -1777,7 +1777,7 @@ static void draw_chanx_to_chany_edge(int chanx_node, int chanx_track, int chany_ } } -static void draw_chanx_to_chanx_edge(int from_node, int to_node, int to_track, short switch_type, ezgl::renderer* g) { +static void draw_chanx_to_chanx_edge(const RRNodeId& from_node, const RRNodeId& to_node, int to_track, short switch_type, ezgl::renderer* g) { /* Draws a connection between two x-channel segments. Passing in the track * * numbers allows this routine to be used for both rr_graph and routing * * drawing-> */ @@ -1800,10 +1800,10 @@ static void draw_chanx_to_chanx_edge(int from_node, int to_node, int to_track, s y1 = from_chan.bottom(); y2 = to_chan.bottom(); - from_xlow = device_ctx.rr_nodes[from_node].xlow(); - from_xhigh = device_ctx.rr_nodes[from_node].xhigh(); - to_xlow = device_ctx.rr_nodes[to_node].xlow(); - to_xhigh = device_ctx.rr_nodes[to_node].xhigh(); + from_xlow = device_ctx.rr_graph.node_xlow(from_node); + from_xhigh = device_ctx.rr_graph.node_xhigh(from_node); + to_xlow = device_ctx.rr_graph.node_xlow(to_node); + to_xhigh = device_ctx.rr_graph.node_xhigh(to_node); if (to_xhigh < from_xlow) { /* From right to left */ /* UDSD Note by WMF: could never happen for INC wires, unless U-turn. For DEC * wires this handles well */ @@ -1821,7 +1821,7 @@ static void draw_chanx_to_chanx_edge(int from_node, int to_node, int to_track, s * will be drawn on top of each other for bidirectional connections. */ else { - if (device_ctx.rr_nodes[to_node].direction() != BI_DIRECTION) { + if (device_ctx.rr_graph.node_direction(to_node) != BI_DIRECTION) { /* must connect to to_node's wire beginning at x2 */ if (to_track % 2 == 0) { /* INC wire starts at leftmost edge */ VTR_ASSERT(from_xlow < to_xlow); @@ -1862,7 +1862,7 @@ static void draw_chanx_to_chanx_edge(int from_node, int to_node, int to_track, s } } -static void draw_chany_to_chany_edge(int from_node, int to_node, int to_track, short switch_type, ezgl::renderer* g) { +static void draw_chany_to_chany_edge(const RRNodeId& from_node, const RRNodeId& to_node, int to_track, short switch_type, ezgl::renderer* g) { t_draw_state* draw_state = get_draw_state_vars(); t_draw_coords* draw_coords = get_draw_coords_vars(); auto& device_ctx = g_vpr_ctx.device(); @@ -1882,10 +1882,10 @@ static void draw_chany_to_chany_edge(int from_node, int to_node, int to_track, s // from_x = device_ctx.rr_nodes[from_node].xlow(); // to_x = device_ctx.rr_nodes[to_node].xlow(); - from_ylow = device_ctx.rr_nodes[from_node].ylow(); - from_yhigh = device_ctx.rr_nodes[from_node].yhigh(); - to_ylow = device_ctx.rr_nodes[to_node].ylow(); - to_yhigh = device_ctx.rr_nodes[to_node].yhigh(); + from_ylow = device_ctx.rr_graph.node_ylow(from_node); + from_yhigh = device_ctx.rr_graph.node_yhigh(from_node); + to_ylow = device_ctx.rr_graph.node_ylow(to_node); + to_yhigh = device_ctx.rr_graph.node_yhigh(to_node); /* (x1, y1) point on from_node, (x2, y2) point on to_node. */ @@ -1906,7 +1906,7 @@ static void draw_chany_to_chany_edge(int from_node, int to_node, int to_track, s /* UDSD Modification by WMF Begin */ else { - if (device_ctx.rr_nodes[to_node].direction() != BI_DIRECTION) { + if (device_ctx.rr_graph.node_direction(to_node) != BI_DIRECTION) { if (to_track % 2 == 0) { /* INC wire starts at bottom edge */ y2 = to_chan.bottom(); @@ -1950,32 +1950,32 @@ static void draw_chany_to_chany_edge(int from_node, int to_node, int to_track, s * wire has been clicked on by the user. * TODO: Fix this for global routing, currently for detailed only. */ -ezgl::rectangle draw_get_rr_chan_bbox(int inode) { +ezgl::rectangle draw_get_rr_chan_bbox(const RRNodeId& inode) { double left = 0, right = 0, top = 0, bottom = 0; t_draw_coords* draw_coords = get_draw_coords_vars(); auto& device_ctx = g_vpr_ctx.device(); switch (device_ctx.rr_nodes[inode].type()) { case CHANX: - left = draw_coords->tile_x[device_ctx.rr_nodes[inode].xlow()]; - right = draw_coords->tile_x[device_ctx.rr_nodes[inode].xhigh()] + left = draw_coords->tile_x[device_ctx.rr_graph.node_xlow(inode)]; + right = draw_coords->tile_x[device_ctx.rr_graph.node_xhigh(inode)] + draw_coords->get_tile_width(); - bottom = draw_coords->tile_y[device_ctx.rr_nodes[inode].ylow()] + bottom = draw_coords->tile_y[device_ctx.rr_graph.node_ylow(inode)] + draw_coords->get_tile_width() - + (1. + device_ctx.rr_nodes[inode].ptc_num()); - top = draw_coords->tile_y[device_ctx.rr_nodes[inode].ylow()] + + (1. + device_ctx.rr_graph.node_ptc_num(inode)); + top = draw_coords->tile_y[device_ctx.rr_graph.node_ylow(inode)] + draw_coords->get_tile_width() - + (1. + device_ctx.rr_nodes[inode].ptc_num()); + + (1. + device_ctx.rr_graph.node_ptc_num(inode)); break; case CHANY: - left = draw_coords->tile_x[device_ctx.rr_nodes[inode].xlow()] + left = draw_coords->tile_x[device_ctx.rr_graph.node_xlow(inode)] + draw_coords->get_tile_width() - + (1. + device_ctx.rr_nodes[inode].ptc_num()); - right = draw_coords->tile_x[device_ctx.rr_nodes[inode].xlow()] + + (1. + device_ctx.rr_graph.node_ptc_num(inode)); + right = draw_coords->tile_x[device_ctx.rr_graph.node_xlow(inode)] + draw_coords->get_tile_width() - + (1. + device_ctx.rr_nodes[inode].ptc_num()); - bottom = draw_coords->tile_y[device_ctx.rr_nodes[inode].ylow()]; - top = draw_coords->tile_y[device_ctx.rr_nodes[inode].yhigh()] + + (1. + device_ctx.rr_graph.node_ptc_num(inode)); + bottom = draw_coords->tile_y[device_ctx.rr_graph.node_ylow(inode)]; + top = draw_coords->tile_y[device_ctx.rr_graph.node_yhigh(inode)] + draw_coords->get_tile_width(); break; default: @@ -2012,7 +2012,7 @@ static void draw_rr_switch(float from_x, float from_y, float to_x, float to_y, b } } -static void draw_rr_pin(int inode, const ezgl::color& color, ezgl::renderer* g) { +static void draw_rr_pin(RRNodeId& inode, const ezgl::color& color, ezgl::renderer* g) { /* Draws an IPIN or OPIN rr_node. Note that the pin can appear on more * * than one side of a clb. Also note that this routine can change the * * current color to BLACK. */ @@ -2023,7 +2023,7 @@ static void draw_rr_pin(int inode, const ezgl::color& color, ezgl::renderer* g) char str[vtr::bufsize]; auto& device_ctx = g_vpr_ctx.device(); - int ipin = device_ctx.rr_nodes[inode].ptc_num(); + int ipin = device_ctx.rr_graph.node_ptc_num(inode); g->set_color(color); @@ -2040,26 +2040,21 @@ static void draw_rr_pin(int inode, const ezgl::color& color, ezgl::renderer* g) /* Returns the coordinates at which the center of this pin should be drawn. * * inode gives the node number, and iside gives the side of the clb or pad * * the physical pin is on. */ -void draw_get_rr_pin_coords(int inode, float* xcen, float* ycen) { +void draw_get_rr_pin_coords(const RRNodeId& inode, float* xcen, float* ycen) { auto& device_ctx = g_vpr_ctx.device(); - draw_get_rr_pin_coords(&device_ctx.rr_nodes[inode], xcen, ycen); -} - -void draw_get_rr_pin_coords(const t_rr_node* node, float* xcen, float* ycen) { t_draw_coords* draw_coords = get_draw_coords_vars(); int i, j, k, ipin, pins_per_sub_tile; float offset, xc, yc, step; t_physical_tile_type_ptr type; - auto& device_ctx = g_vpr_ctx.device(); - i = node->xlow(); - j = node->ylow(); + i = device_ctx.rr_graph.node_xlow(inode); + j = device_ctx.rr_graph.node_ylow(inode); xc = draw_coords->tile_x[i]; yc = draw_coords->tile_y[j]; - ipin = node->ptc_num(); + ipin = device_ctx.rr_graph.node_ptc_num(inode); type = device_ctx.grid[i][j].type; pins_per_sub_tile = type->num_pins / type->capacity; k = ipin / pins_per_sub_tile; @@ -2100,15 +2095,15 @@ void draw_get_rr_pin_coords(const t_rr_node* node, float* xcen, float* ycen) { *ycen = yc; } -static void draw_rr_src_sink(int inode, ezgl::color color, ezgl::renderer* g) { +static void draw_rr_src_sink(const RRNodeId& inode, ezgl::color color, ezgl::renderer* g) { t_draw_coords* draw_coords = get_draw_coords_vars(); auto& device_ctx = g_vpr_ctx.device(); - int xlow = device_ctx.rr_nodes[inode].xlow(); - int ylow = device_ctx.rr_nodes[inode].ylow(); - int xhigh = device_ctx.rr_nodes[inode].xhigh(); - int yhigh = device_ctx.rr_nodes[inode].yhigh(); + int xlow = device_ctx.rr_graph.node_xlow(inode); + int ylow = device_ctx.rr_graph.node_ylow(inode); + int xhigh = device_ctx.rr_graph.node_xhigh(inode); + int yhigh = device_ctx.rr_graph.node_yhigh(inode); g->set_color(color); @@ -2152,9 +2147,9 @@ static void draw_routed_net(ClusterNetId net_id, ezgl::renderer* g) { return; /* partially complete routes). */ t_trace* tptr = route_ctx.trace[net_id].head; /* SOURCE to start */ - int inode = tptr->index; + RRNodeId inode = tptr->index; - std::vector rr_nodes_to_draw; + std::vector rr_nodes_to_draw; rr_nodes_to_draw.push_back(inode); for (;;) { tptr = tptr->next; @@ -2190,7 +2185,7 @@ static void draw_routed_net(ClusterNetId net_id, ezgl::renderer* g) { } //Draws the set of rr_nodes specified, using the colors set in draw_state -void draw_partial_route(const std::vector& rr_nodes_to_draw, ezgl::renderer* g) { +void draw_partial_route(const std::vector& rr_nodes_to_draw, ezgl::renderer* g) { t_draw_state* draw_state = get_draw_state_vars(); auto& device_ctx = g_vpr_ctx.device(); @@ -2218,14 +2213,15 @@ void draw_partial_route(const std::vector& rr_nodes_to_draw, ezgl::renderer } for (size_t i = 1; i < rr_nodes_to_draw.size(); ++i) { - int inode = rr_nodes_to_draw[i]; - auto rr_type = device_ctx.rr_nodes[inode].type(); + RRNodeId inode = rr_nodes_to_draw[i]; + auto rr_type = device_ctx.rr_graph.node_type(inode); - int prev_node = rr_nodes_to_draw[i - 1]; - auto prev_type = device_ctx.rr_nodes[prev_node].type(); + RRNodeId prev_node = rr_nodes_to_draw[i - 1]; + auto prev_type = device_ctx.rr_graph.node_type(prev_node); - auto iedge = find_edge(prev_node, inode); - auto switch_type = device_ctx.rr_nodes[prev_node].edge_switch(iedge); + std::vector edges = device_ctx.rr_graph.find_edges(prev_node, inode); + VTR_ASSERT(1 == edges.size()); + auto switch_type = device_ctx.rr_graph.edge_switch(edges[0]); switch (rr_type) { case OPIN: { @@ -2277,7 +2273,7 @@ void draw_partial_route(const std::vector& rr_nodes_to_draw, ezgl::renderer } case CHANY: { if (draw_state->draw_route_type == GLOBAL) - chany_track[device_ctx.rr_nodes[inode].xlow()][device_ctx.rr_nodes[inode].ylow()]++; + chany_track[device_ctx.rr_graph.node_xlow(inode)][device_ctx.rr_graph.node_ylow(inode)]++; int itrack = get_track_num(inode, chanx_track, chany_track); draw_rr_chan(inode, draw_state->draw_rr_node[inode].color, g); @@ -2316,7 +2312,7 @@ void draw_partial_route(const std::vector& rr_nodes_to_draw, ezgl::renderer } } -static int get_track_num(int inode, const vtr::OffsetMatrix& chanx_track, const vtr::OffsetMatrix& chany_track) { +static int get_track_num(const RRNodeId& inode, const vtr::OffsetMatrix& chanx_track, const vtr::OffsetMatrix& chany_track) { /* Returns the track number of this routing resource node. */ int i, j; @@ -2324,13 +2320,13 @@ static int get_track_num(int inode, const vtr::OffsetMatrix& chanx_track, c auto& device_ctx = g_vpr_ctx.device(); if (get_draw_state_vars()->draw_route_type == DETAILED) - return (device_ctx.rr_nodes[inode].ptc_num()); + return (device_ctx.rr_graph.node_ptc_num(inode)); /* GLOBAL route stuff below. */ - rr_type = device_ctx.rr_nodes[inode].type(); - i = device_ctx.rr_nodes[inode].xlow(); /* NB: Global rr graphs must have only unit */ - j = device_ctx.rr_nodes[inode].ylow(); /* length channel segments. */ + rr_type = device_ctx.rr_graph.node_type(inode); + i = device_ctx.rr_graph.node_xlow(inode); /* NB: Global rr graphs must have only unit */ + j = device_ctx.rr_graph.node_ylow(inode); /* length channel segments. */ switch (rr_type) { case CHANX: @@ -2341,7 +2337,7 @@ static int get_track_num(int inode, const vtr::OffsetMatrix& chanx_track, c default: vpr_throw(VPR_ERROR_OTHER, __FILE__, __LINE__, - "in get_track_num: Unexpected node type %d for node %d.\n", rr_type, inode); + "in get_track_num: Unexpected node type %d for node %ld.\n", rr_type, size_t(inode)); return OPEN; } } @@ -2363,7 +2359,7 @@ static bool draw_if_net_highlighted(ClusterNetId inet) { /* If an rr_node has been clicked on, it will be highlighted in MAGENTA. * If so, and toggle nets is selected, highlight the whole net in that colour. */ -void highlight_nets(char* message, int hit_node) { +void highlight_nets(char* message, const RRNodeId& hit_node) { t_trace* tptr; auto& cluster_ctx = g_vpr_ctx.clustering(); auto& route_ctx = g_vpr_ctx.routing(); @@ -2415,7 +2411,7 @@ void draw_highlight_fan_in_fan_out(const std::set& nodes) { } /* Highlight the nodes that can fanin to this node in blue. */ - for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); inode++) { + for (size_t inode = 0; inode < device_ctx.rr_graph.nodes().size(); inode++) { for (t_edge_size iedge = 0, l = device_ctx.rr_nodes[inode].num_edges(); iedge < l; iedge++) { int fanout_node = device_ctx.rr_nodes[inode].edge_sink_node(iedge); if (fanout_node == node) { @@ -2447,16 +2443,16 @@ static int draw_check_rr_node_hit(float click_x, float click_y) { t_draw_coords* draw_coords = get_draw_coords_vars(); auto& device_ctx = g_vpr_ctx.device(); - for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); inode++) { - switch (device_ctx.rr_nodes[inode].type()) { + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { + switch (device_ctx.rr_graph.node_type(inode)) { case IPIN: case OPIN: { - int i = device_ctx.rr_nodes[inode].xlow(); - int j = device_ctx.rr_nodes[inode].ylow(); + int i = device_ctx.rr_graph.node_xlow(inode); + int j = device_ctx.rr_graph.node_ylow(inode); t_physical_tile_type_ptr type = device_ctx.grid[i][j].type; int width_offset = device_ctx.grid[i][j].width_offset; int height_offset = device_ctx.grid[i][j].height_offset; - int ipin = device_ctx.rr_nodes[inode].ptc_num(); + int ipin = device_ctx.rr_graph.node_ptc_num(inode); float xcen, ycen; int iside; for (iside = 0; iside < 4; iside++) { @@ -2727,7 +2723,7 @@ void deselect_all() { for (auto net_id : cluster_ctx.clb_nlist.nets()) draw_state->net_color[net_id] = ezgl::BLACK; - for (size_t i = 0; i < device_ctx.rr_nodes.size(); i++) { + for (size_t i = 0; i < device_ctx.rr_graph.nodes().size(); i++) { draw_state->draw_rr_node[i].color = DEFAULT_RR_NODE_COLOR; draw_state->draw_rr_node[i].node_highlighted = false; } @@ -2800,7 +2796,7 @@ void draw_triangle_along_line(ezgl::renderer* g, float xend, float yend, float x g->fill_poly(poly); } -static void draw_pin_to_chan_edge(int pin_node, int chan_node, ezgl::renderer* g) { +static void draw_pin_to_chan_edge(const RRNodeId& pin_node, const RRNodeId& chan_node, ezgl::renderer* g) { /* This routine draws an edge from the pin_node to the chan_node (CHANX or * * CHANY). The connection is made to the nearest end of the track instead * * of perpendicular to the track to symbolize a single-drive connection. */ @@ -2810,19 +2806,18 @@ static void draw_pin_to_chan_edge(int pin_node, int chan_node, ezgl::renderer* g t_draw_coords* draw_coords = get_draw_coords_vars(); auto& device_ctx = g_vpr_ctx.device(); - const t_rr_node& pin_rr = device_ctx.rr_nodes[pin_node]; - const t_rr_node& chan_rr = device_ctx.rr_nodes[chan_node]; + const RRGraph& rr_graph = device_ctx.rr_graph; - const t_grid_tile& grid_tile = device_ctx.grid[pin_rr.xlow()][pin_rr.ylow()]; + const t_grid_tile& grid_tile = device_ctx.grid[rr_graph.node_xlow(pin_node)][rr_graph.node_ylow(pin_node)]; t_physical_tile_type_ptr grid_type = grid_tile.type; - VTR_ASSERT_MSG(grid_type->pinloc[grid_tile.width_offset][grid_tile.height_offset][pin_rr.side()][pin_rr.pin_num()], + VTR_ASSERT_MSG(grid_type->pinloc[grid_tile.width_offset][grid_tile.height_offset][rr_graph.node_side(pin_node)][rr_graph.node_pin_num(pin_node)], "Pin coordinates should match block type pin locations"); float draw_pin_offset; - if (pin_rr.side() == TOP || pin_rr.side() == RIGHT) { + if (rr_graph.node_side(pin_node) == TOP || rr_graph.node_side(pin_node) == RIGHT) { draw_pin_offset = draw_coords->pin_size; } else { - VTR_ASSERT(pin_rr.side() == BOTTOM || pin_rr.side() == LEFT); + VTR_ASSERT(rr_graph.node_side(pin_node) == BOTTOM || rr_graph.node_side(pin_node) == LEFT); draw_pin_offset = -draw_coords->pin_size; } @@ -2837,10 +2832,10 @@ static void draw_pin_to_chan_edge(int pin_node, int chan_node, ezgl::renderer* g y1 += draw_pin_offset; y2 = chan_bbox.bottom(); x2 = x1; - if (is_opin(pin_rr.pin_num(), grid_type)) { - if (chan_rr.direction() == INC_DIRECTION) { + if (is_opin(rr_graph.node_pin_num(pin_node), grid_type)) { + if (rr_graph.node_direction(chan_node) == INC_DIRECTION) { x2 = chan_bbox.left(); - } else if (chan_rr.direction() == DEC_DIRECTION) { + } else if (rr_graph.node_direction(chan_node) == DEC_DIRECTION) { x2 = chan_bbox.right(); } } @@ -2850,10 +2845,10 @@ static void draw_pin_to_chan_edge(int pin_node, int chan_node, ezgl::renderer* g x1 += draw_pin_offset; x2 = chan_bbox.left(); y2 = y1; - if (is_opin(pin_rr.pin_num(), grid_type)) { - if (chan_rr.direction() == INC_DIRECTION) { + if (is_opin(rr_graph.node_pin_num(pin_node), grid_type)) { + if (rr_graph.node_direction(chan_node) == INC_DIRECTION) { y2 = chan_bbox.bottom(); - } else if (chan_rr.direction() == DEC_DIRECTION) { + } else if (rr_graph.node_direction(chan_node) == DEC_DIRECTION) { y2 = chan_bbox.top(); } } @@ -2861,13 +2856,13 @@ static void draw_pin_to_chan_edge(int pin_node, int chan_node, ezgl::renderer* g } default: { vpr_throw(VPR_ERROR_OTHER, __FILE__, __LINE__, - "in draw_pin_to_chan_edge: Invalid channel node %d.\n", chan_node); + "in draw_pin_to_chan_edge: Invalid channel node %ld.\n", size_t(chan_node)); } } g->draw_line({x1, y1}, {x2, y2}); //don't draw the ex, or triangle unless zoomed in really far - if (chan_rr.direction() == BI_DIRECTION || !is_opin(pin_rr.pin_num(), grid_type)) { + if (rr_graph.node_direction(chan_node) == BI_DIRECTION || !is_opin(rr_graph.node_pin_num(pin_node), grid_type)) { draw_x(x2, y2, 0.7 * draw_coords->pin_size, g); } else { float xend = x2 + (x1 - x2) / 10.; @@ -2876,11 +2871,11 @@ static void draw_pin_to_chan_edge(int pin_node, int chan_node, ezgl::renderer* g } } -static void draw_pin_to_pin(int opin_node, int ipin_node, ezgl::renderer* g) { +static void draw_pin_to_pin(const RRNodeId& opin_node, const RRNodeId& ipin_node, ezgl::renderer* g) { /* This routine draws an edge from the opin rr node to the ipin rr node */ auto& device_ctx = g_vpr_ctx.device(); - VTR_ASSERT(device_ctx.rr_nodes[opin_node].type() == OPIN); - VTR_ASSERT(device_ctx.rr_nodes[ipin_node].type() == IPIN); + VTR_ASSERT(device_ctx.rr_graph.node_type(opin_node) == OPIN); + VTR_ASSERT(device_ctx.rr_graph.node_type(ipin_node) == IPIN); float x1 = 0, y1 = 0; draw_get_rr_pin_coords(opin_node, &x1, &y1); @@ -3499,15 +3494,15 @@ static void draw_router_rr_costs(ezgl::renderer* g) { auto& device_ctx = g_vpr_ctx.device(); auto& routing_ctx = g_vpr_ctx.routing(); - std::vector rr_costs(device_ctx.rr_nodes.size()); + vtr::vector rr_costs(device_ctx.rr_graph.nodes().size()); - for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); ++inode) { + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { float cost = get_router_rr_cost(routing_ctx.rr_node_route_inf[inode], draw_state->show_router_rr_cost); rr_costs[inode] = cost; } bool all_nan = true; - for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); ++inode) { + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { if (std::isinf(rr_costs[inode])) { rr_costs[inode] = NAN; } else { @@ -3528,7 +3523,7 @@ static void draw_router_rr_costs(ezgl::renderer* g) { } } -static void draw_rr_costs(ezgl::renderer* g, const std::vector& rr_costs, bool lowest_cost_first) { +static void draw_rr_costs(ezgl::renderer* g, const vtr::vector& rr_costs, bool lowest_cost_first) { t_draw_state* draw_state = get_draw_state_vars(); /* Draws routing costs */ @@ -3537,11 +3532,11 @@ static void draw_rr_costs(ezgl::renderer* g, const std::vector& rr_costs, g->set_line_width(0); - VTR_ASSERT(rr_costs.size() == device_ctx.rr_nodes.size()); + VTR_ASSERT(rr_costs.size() == device_ctx.rr_graph.nodes().size()); float min_cost = std::numeric_limits::infinity(); float max_cost = -min_cost; - for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); inode++) { + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { if (std::isnan(rr_costs[inode])) continue; min_cost = std::min(min_cost, rr_costs[inode]); @@ -3553,9 +3548,8 @@ static void draw_rr_costs(ezgl::renderer* g, const std::vector& rr_costs, //Draw the nodes in ascending order of value, this ensures high valued nodes //are not overdrawn by lower value ones (e.g-> when zoomed-out far) - std::vector nodes(device_ctx.rr_nodes.size()); - std::iota(nodes.begin(), nodes.end(), 0); - auto cmp_ascending_cost = [&](int lhs_node, int rhs_node) { + std::vector nodes = device_ctx.rr_graph.nodes(); + auto cmp_ascending_cost = [&](const RRNodeId& lhs_node, const RRNodeId& rhs_node) { if (lowest_cost_first) { return rr_costs[lhs_node] > rr_costs[rhs_node]; } @@ -3563,13 +3557,13 @@ static void draw_rr_costs(ezgl::renderer* g, const std::vector& rr_costs, }; std::sort(nodes.begin(), nodes.end(), cmp_ascending_cost); - for (int inode : nodes) { + for (const RRNodeId& inode : nodes) { float cost = rr_costs[inode]; if (std::isnan(cost)) continue; ezgl::color color = to_ezgl_color(cmap->color(cost)); - switch (device_ctx.rr_nodes[inode].type()) { + switch (device_ctx.rr_graph.node_type(inode)) { case CHANX: //fallthrough case CHANY: draw_rr_chan(inode, color, g); diff --git a/vpr/src/draw/draw.h b/vpr/src/draw/draw.h index acc9d2145..98c908ba4 100644 --- a/vpr/src/draw/draw.h +++ b/vpr/src/draw/draw.h @@ -31,8 +31,7 @@ void free_draw_structs(); #ifndef NO_GRAPHICS -void draw_get_rr_pin_coords(int inode, float* xcen, float* ycen); -void draw_get_rr_pin_coords(const t_rr_node* node, float* xcen, float* ycen); +void draw_get_rr_pin_coords(const RRNodeId& inode, float* xcen, float* ycen); void draw_triangle_along_line(ezgl::renderer* g, ezgl::point2d start, ezgl::point2d end, float relative_position = 1., float arrow_size = DEFAULT_ARROW_SIZE); void draw_triangle_along_line(ezgl::renderer* g, ezgl::point2d loc, ezgl::point2d start, ezgl::point2d end, float arrow_size = DEFAULT_ARROW_SIZE); @@ -58,9 +57,9 @@ ezgl::color to_ezgl_color(vtr::Color color); void draw_screen(); // search bar related functions -ezgl::rectangle draw_get_rr_chan_bbox(int inode); +ezgl::rectangle draw_get_rr_chan_bbox(const RRNodeId& inode); void draw_highlight_blocks_color(t_logical_block_type_ptr type, ClusterBlockId blk_id); -void highlight_nets(char* message, int hit_node); +void highlight_nets(char* message, const RRNodeId& hit_node); void draw_highlight_fan_in_fan_out(const std::set& nodes); std::set draw_expand_non_configurable_rr_nodes(int hit_node); void deselect_all(); diff --git a/vpr/src/draw/draw_types.h b/vpr/src/draw/draw_types.h index 1ef320afa..1400221f0 100644 --- a/vpr/src/draw/draw_types.h +++ b/vpr/src/draw/draw_types.h @@ -172,7 +172,7 @@ struct t_draw_state { e_route_type draw_route_type = GLOBAL; char default_message[vtr::bufsize]; vtr::vector net_color; - t_draw_rr_node* draw_rr_node = nullptr; + vtr::vector draw_rr_node; std::shared_ptr setup_timing_info; const t_arch* arch_info = nullptr; std::unique_ptr color_map = nullptr; From cb98456d7e63a04627aa8624ef765b44a5152c51 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 1 Feb 2020 10:12:11 -0700 Subject: [PATCH 067/645] finish adaption for stats.cpp --- vpr/src/base/read_route.cpp | 22 ++++++++++++++-------- vpr/src/base/stats.cpp | 27 +++++++++++++-------------- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/vpr/src/base/read_route.cpp b/vpr/src/base/read_route.cpp index 9ec4069fe..00237603b 100644 --- a/vpr/src/base/read_route.cpp +++ b/vpr/src/base/read_route.cpp @@ -231,7 +231,7 @@ static void process_nodes(std::ifstream& fp, ClusterNetId inet, const char* file } else if (tokens[0] == "Node:") { /*An actual line, go through each node and add it to the route tree*/ inode = atoi(tokens[1].c_str()); - auto& node = device_ctx.rr_nodes[inode]; + const RRNodeId& node = RRNodeId(inode); /*First node needs to be source. It is isolated to correctly set heap head.*/ if (node_count == 0 && tokens[2] != "SOURCE") { @@ -240,7 +240,7 @@ static void process_nodes(std::ifstream& fp, ClusterNetId inet, const char* file } /*Check node types if match rr graph*/ - if (tokens[2] != node.type_string()) { + if (tokens[2] != rr_node_typename[device_ctx.rr_graph.node_type(node)]) { vpr_throw(VPR_ERROR_ROUTE, filename, lineno, "Node %d has a type that does not match the RR graph", inode); } @@ -249,13 +249,19 @@ static void process_nodes(std::ifstream& fp, ClusterNetId inet, const char* file if (tokens[4] == "to") { format_coordinates(x2, y2, tokens[5], inet, filename, lineno); - if (node.xlow() != x || node.xhigh() != x2 || node.yhigh() != y2 || node.ylow() != y) { + if (device_ctx.rr_graph.node_xlow(node) != x + || device_ctx.rr_graph.node_xhigh(node) != x2 + || device_ctx.rr_graph.node_yhigh(node) != y2 + || device_ctx.rr_graph.node_ylow(node) != y) { vpr_throw(VPR_ERROR_ROUTE, filename, lineno, "The coordinates of node %d does not match the rr graph", inode); } offset = 2; } else { - if (node.xlow() != x || node.xhigh() != x || node.yhigh() != y || node.ylow() != y) { + if (device_ctx.rr_graph.node_xlow(node) != x + || device_ctx.rr_graph.node_xhigh(node) != x + || device_ctx.rr_graph.node_yhigh(node) != y + || device_ctx.rr_graph.node_ylow(node) != y) { vpr_throw(VPR_ERROR_ROUTE, filename, lineno, "The coordinates of node %d does not match the rr graph", inode); } @@ -276,7 +282,7 @@ static void process_nodes(std::ifstream& fp, ClusterNetId inet, const char* file } ptc = atoi(tokens[5 + offset].c_str()); - if (node.ptc_num() != ptc) { + if (device_ctx.rr_graph.node_ptc_num(node) != ptc) { vpr_throw(VPR_ERROR_ROUTE, filename, lineno, "The ptc num of node %d does not match the rr graph", inode); } @@ -285,7 +291,7 @@ static void process_nodes(std::ifstream& fp, ClusterNetId inet, const char* file if (tokens[6 + offset] != "Switch:") { /*This is an opin or ipin, process its pin nums*/ if (!is_io_type(device_ctx.grid[x][y].type) && (tokens[2] == "IPIN" || tokens[2] == "OPIN")) { - int pin_num = device_ctx.rr_nodes[inode].ptc_num(); + int pin_num = device_ctx.rr_graph.node_ptc_num(node); int height_offset = device_ctx.grid[x][y].height_offset; ClusterBlockId iblock = place_ctx.grid_blocks[x][y - height_offset].blocks[0]; t_pb_graph_pin* pb_pin = get_pb_graph_node_pin_from_block_pin(iblock, pin_num); @@ -312,7 +318,7 @@ static void process_nodes(std::ifstream& fp, ClusterNetId inet, const char* file /* Allocate and load correct values to trace.head*/ if (node_count == 0) { route_ctx.trace[inet].head = alloc_trace_data(); - route_ctx.trace[inet].head->index = inode; + route_ctx.trace[inet].head->index = node; route_ctx.trace[inet].head->iswitch = switch_id; route_ctx.trace[inet].head->next = nullptr; tptr = route_ctx.trace[inet].head; @@ -320,7 +326,7 @@ static void process_nodes(std::ifstream& fp, ClusterNetId inet, const char* file } else { tptr->next = alloc_trace_data(); tptr = tptr->next; - tptr->index = inode; + tptr->index = node; tptr->iswitch = switch_id; tptr->next = nullptr; node_count++; diff --git a/vpr/src/base/stats.cpp b/vpr/src/base/stats.cpp index 846185ee2..9f36081e4 100644 --- a/vpr/src/base/stats.cpp +++ b/vpr/src/base/stats.cpp @@ -215,7 +215,7 @@ static void get_channel_occupancy_stats() { /* Loads the two arrays passed in with the total occupancy at each of the * * channel segments in the FPGA. */ static void load_channel_occupancies(vtr::Matrix& chanx_occ, vtr::Matrix& chany_occ) { - int i, j, inode; + int i, j; t_trace* tptr; t_rr_type rr_type; @@ -235,8 +235,8 @@ static void load_channel_occupancies(vtr::Matrix& chanx_occ, vtr::Matrixindex; - rr_type = device_ctx.rr_nodes[inode].type(); + const RRNodeId& inode = tptr->index; + rr_type = device_ctx.rr_graph.node_type(inode); if (rr_type == SINK) { tptr = tptr->next; /* Skip next segment. */ @@ -245,14 +245,14 @@ static void load_channel_occupancies(vtr::Matrix& chanx_occ, vtr::Matrixindex; - prev_type = device_ctx.rr_nodes[inode].type(); + RRNodeId inode = prevptr->index; + prev_type = device_ctx.rr_graph.node_type(inode); tptr = prevptr->next; while (tptr != nullptr) { inode = tptr->index; - curr_type = device_ctx.rr_nodes[inode].type(); + curr_type = device_ctx.rr_graph.node_type(inode); if (curr_type == SINK) { /* Starting a new segment */ tptr = tptr->next; /* Link to existing path - don't add to len. */ if (tptr == nullptr) break; - curr_type = device_ctx.rr_nodes[tptr->index].type(); + curr_type = device_ctx.rr_graph.node_type(tptr->index); } else if (curr_type == CHANX || curr_type == CHANY) { segments++; - length += 1 + device_ctx.rr_nodes[inode].xhigh() - device_ctx.rr_nodes[inode].xlow() - + device_ctx.rr_nodes[inode].yhigh() - device_ctx.rr_nodes[inode].ylow(); + length += 1 + device_ctx.rr_graph.node_xhigh(inode) - device_ctx.rr_graph.node_xlow(inode) + + device_ctx.rr_graph.node_yhigh(inode) - device_ctx.rr_graph.node_ylow(inode); if (curr_type != prev_type && (prev_type == CHANX || prev_type == CHANY)) bends++; From 653d7d5aa1dd58205eea8d96a70a69a6c565ffbb Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 1 Feb 2020 11:49:39 -0700 Subject: [PATCH 068/645] placer compilation errors elimated for RRGraph deployment --- vpr/src/base/vpr_context.h | 1 + vpr/src/device/rr_graph_obj_util.cpp | 65 +++++++++ vpr/src/device/rr_graph_obj_util.h | 21 +++ vpr/src/device/rr_graph_types.h | 3 + vpr/src/device/rr_graph_util.cpp | 30 ----- vpr/src/device/rr_graph_util.h | 15 --- vpr/src/device/write_xml_rr_graph_obj.cpp | 2 - vpr/src/draw/draw.cpp | 153 +++++++++++----------- vpr/src/draw/draw.h | 4 +- vpr/src/draw/search_bar.cpp | 18 +-- vpr/src/draw/search_bar.h | 4 +- vpr/src/place/timing_place_lookup.cpp | 42 +++--- vpr/src/route/route_common.cpp | 14 +- vpr/src/route/route_export.h | 4 +- vpr/src/route/rr_graph.cpp | 44 +++---- vpr/src/route/rr_graph.h | 2 +- 16 files changed, 230 insertions(+), 192 deletions(-) create mode 100644 vpr/src/device/rr_graph_obj_util.cpp create mode 100644 vpr/src/device/rr_graph_obj_util.h delete mode 100644 vpr/src/device/rr_graph_util.cpp delete mode 100644 vpr/src/device/rr_graph_util.h diff --git a/vpr/src/base/vpr_context.h b/vpr/src/base/vpr_context.h index 7f4ba43dc..9a59e89c8 100644 --- a/vpr/src/base/vpr_context.h +++ b/vpr/src/base/vpr_context.h @@ -286,6 +286,7 @@ struct RoutingContext : public Context { vtr::vector trace; vtr::vector> trace_nodes; + /* Xifan Tang: this should adopt RRNodeId as well */ vtr::vector> net_rr_terminals; /* [0..num_nets-1][0..num_pins-1] */ vtr::vector> rr_blk_source; /* [0..num_blocks-1][0..num_class-1] */ diff --git a/vpr/src/device/rr_graph_obj_util.cpp b/vpr/src/device/rr_graph_obj_util.cpp new file mode 100644 index 000000000..92d27e083 --- /dev/null +++ b/vpr/src/device/rr_graph_obj_util.cpp @@ -0,0 +1,65 @@ +/**************************************************************************** + * This file include most-utilized functions that manipulate on the + * RRGraph object + ***************************************************************************/ +#include "rr_graph_obj.h" +#include "rr_graph_obj_util.h" + +/**************************************************************************** + * Find the switches interconnecting two nodes + * Return a vector of switch ids + ***************************************************************************/ +std::vector find_rr_graph_switches(const RRGraph& rr_graph, + const RRNodeId& from_node, + const RRNodeId& to_node) { + std::vector switches; + std::vector edges = rr_graph.find_edges(from_node, to_node); + if (true == edges.empty()) { + /* edge is open, we return an empty vector of switches */ + return switches; + } + + /* Reach here, edge list is not empty, find switch id one by one + * and update the switch list + */ + for (auto edge : edges) { + switches.push_back(rr_graph.edge_switch(edge)); + } + + return switches; +} + +/********************************************************************* + * Like the RRGraph.find_node() but returns all matching nodes, + * rather than just the first. This is particularly useful for getting all instances + * of a specific IPIN/OPIN at a specific gird tile (x,y) location. + **********************************************************************/ +std::vector find_rr_graph_nodes(const RRGraph& rr_graph, + const int& x, + const int& y, + const t_rr_type& rr_type, + const int& ptc) { + std::vector indices; + + if (rr_type == IPIN || rr_type == OPIN) { + //For pins we need to look at all the sides of the current grid tile + + for (e_side side : SIDES) { + RRNodeId rr_node_index = rr_graph.find_node(x, y, rr_type, ptc, side); + + if (rr_node_index != RRNodeId::INVALID()) { + indices.push_back(rr_node_index); + } + } + } else { + //Sides do not effect non-pins so there should only be one per ptc + RRNodeId rr_node_index = rr_graph.find_node(x, y, rr_type, ptc); + + if (rr_node_index != RRNodeId::INVALID()) { + indices.push_back(rr_node_index); + } + } + + return indices; +} + diff --git a/vpr/src/device/rr_graph_obj_util.h b/vpr/src/device/rr_graph_obj_util.h new file mode 100644 index 000000000..9a089d68c --- /dev/null +++ b/vpr/src/device/rr_graph_obj_util.h @@ -0,0 +1,21 @@ +#ifndef RR_GRAPH_OBJ_UTIL_H +#define RR_GRAPH_OBJ_UTIL_H + +/* Include header files which include data structures used by + * the function declaration + */ +#include +#include "rr_graph_obj.h" + +/* Get node-to-node switches in a RRGraph */ +std::vector find_rr_graph_switches(const RRGraph& rr_graph, + const RRNodeId& from_node, + const RRNodeId& to_node); + +std::vector find_rr_graph_nodes(const RRGraph& rr_graph, + const int& x, + const int& y, + const t_rr_type& rr_type, + const int& ptc); + +#endif diff --git a/vpr/src/device/rr_graph_types.h b/vpr/src/device/rr_graph_types.h index 51cf3a0dd..a4df4cd7f 100644 --- a/vpr/src/device/rr_graph_types.h +++ b/vpr/src/device/rr_graph_types.h @@ -19,6 +19,9 @@ enum e_direction : unsigned char { constexpr std::array DIRECTION_STRING = {{"INC_DIRECTION", "DEC_DIRECTION", "BI_DIRECTION", "NO_DIRECTION"}}; +/* Xifan Tang - string used in describe_rr_node() and write_xml_rr_graph_obj() */ +constexpr std::array DIRECTION_STRING_WRITE_XML = {{"INC_DIR", "DEC_DIR", "BI_DIR", "NO_DIR"}}; + /* Type of a routing resource node. x-directed channel segment, * * y-directed channel segment, input pin to a clb to pad, output * * from a clb or pad (i.e. output pin of a net) and: * diff --git a/vpr/src/device/rr_graph_util.cpp b/vpr/src/device/rr_graph_util.cpp deleted file mode 100644 index 495a66581..000000000 --- a/vpr/src/device/rr_graph_util.cpp +++ /dev/null @@ -1,30 +0,0 @@ -/**************************************************************************** - * This file include most-utilized functions that manipulate on the - * RRGraph object - ***************************************************************************/ -#include "rr_graph_util.h" -#include "rr_graph_obj.h" - -/**************************************************************************** - * Find the switches interconnecting two nodes - * Return a vector of switch ids - ***************************************************************************/ -std::vector find_rr_graph_switches(const RRGraph& rr_graph, - const RRNodeId& from_node, - const RRNodeId& to_node) { - std::vector switches; - std::vector edges = rr_graph.find_edges(from_node, to_node); - if (true == edges.empty()) { - /* edge is open, we return an empty vector of switches */ - return switches; - } - - /* Reach here, edge list is not empty, find switch id one by one - * and update the switch list - */ - for (auto edge : edges) { - switches.push_back(rr_graph.edge_switch(edge)); - } - - return switches; -} diff --git a/vpr/src/device/rr_graph_util.h b/vpr/src/device/rr_graph_util.h deleted file mode 100644 index 027356b86..000000000 --- a/vpr/src/device/rr_graph_util.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef RR_GRAPH_UTIL_H -#define RR_GRAPH_UTIL_H - -/* Include header files which include data structures used by - * the function declaration - */ -#include -#include "rr_graph_fwd.h" - -/* Get node-to-node switches in a RRGraph */ -std::vector find_rr_graph_switches(const RRGraph& rr_graph, - const RRNodeId& from_node, - const RRNodeId& to_node); - -#endif diff --git a/vpr/src/device/write_xml_rr_graph_obj.cpp b/vpr/src/device/write_xml_rr_graph_obj.cpp index a329b7bc0..8cd52938e 100644 --- a/vpr/src/device/write_xml_rr_graph_obj.cpp +++ b/vpr/src/device/write_xml_rr_graph_obj.cpp @@ -55,8 +55,6 @@ void write_rr_graph_node(fstream &fp, const RRGraph& rr_graph) { /* TODO: we should make it function full independent from device_ctx !!! */ auto& device_ctx = g_vpr_ctx.device(); - std::array DIRECTION_STRING_WRITE_XML = {{"INC_DIR", "DEC_DIR", "BI_DIR", "NO_DIR"}}; - fp << "\t" << endl; for (auto node : rr_graph.nodes()) { diff --git a/vpr/src/draw/draw.cpp b/vpr/src/draw/draw.cpp index a63e9c51c..03981194c 100644 --- a/vpr/src/draw/draw.cpp +++ b/vpr/src/draw/draw.cpp @@ -104,9 +104,9 @@ static void draw_chanx_to_chanx_edge(const RRNodeId& from_node, const RRNodeId& static void draw_chanx_to_chany_edge(const RRNodeId& chanx_node, int chanx_track, const RRNodeId& chany_node, int chany_track, enum e_edge_dir edge_dir, short switch_type, ezgl::renderer* g); static int get_track_num(const RRNodeId& inode, const vtr::OffsetMatrix& chanx_track, const vtr::OffsetMatrix& chany_track); static bool draw_if_net_highlighted(ClusterNetId inet); -static int draw_check_rr_node_hit(float click_x, float click_y); +static RRNodeId draw_check_rr_node_hit(float click_x, float click_y); -static void draw_expand_non_configurable_rr_nodes_recurr(int from_node, std::set& expanded_nodes); +static void draw_expand_non_configurable_rr_nodes_recurr(const RRNodeId& from_node, std::set& expanded_nodes); static bool highlight_rr_nodes(float x, float y); static void highlight_blocks(double x, double y); static void draw_reset_blk_colors(); @@ -119,9 +119,8 @@ static inline ezgl::rectangle draw_mux(ezgl::point2d origin, e_side orientation, static void draw_flyline_timing_edge(ezgl::point2d start, ezgl::point2d end, float incr_delay, ezgl::renderer* g); static void draw_routed_timing_edge(tatum::NodeId start_tnode, tatum::NodeId end_tnode, float incr_delay, ezgl::color color, ezgl::renderer* g); static void draw_routed_timing_edge_connection(tatum::NodeId src_tnode, tatum::NodeId sink_tnode, ezgl::color color, ezgl::renderer* g); -static std::vector trace_routed_connection_rr_nodes(const ClusterNetId net_id, const int driver_pin, const int sink_pin); -static bool trace_routed_connection_rr_nodes_recurr(const t_rt_node* rt_node, int sink_rr_node, std::vector& rr_nodes_on_path); -static t_edge_size find_edge(int prev_inode, int inode); +static std::vector trace_routed_connection_rr_nodes(const ClusterNetId net_id, const int driver_pin, const int sink_pin); +static bool trace_routed_connection_rr_nodes_recurr(const t_rt_node* rt_node, const RRNodeId& sink_rr_node, std::vector& rr_nodes_on_path); static void draw_color_map_legend(const vtr::ColorMap& cmap, ezgl::renderer* g); @@ -859,7 +858,6 @@ void free_draw_structs() { } if (draw_state != nullptr) { - free(draw_state->draw_rr_node); draw_state->draw_rr_node.clear(); } #else @@ -1049,10 +1047,10 @@ static void draw_congestion(ezgl::renderer* g) { //Record min/max congestion float min_congestion_ratio = 1.; float max_congestion_ratio = min_congestion_ratio; - std::vector congested_rr_nodes = collect_congested_rr_nodes(); - for (int inode : congested_rr_nodes) { + std::vector congested_rr_nodes = collect_congested_rr_nodes(); + for (const RRNodeId& inode : congested_rr_nodes) { short occ = route_ctx.rr_node_route_inf[inode].occ(); - short capacity = device_ctx.rr_nodes[inode].capacity(); + short capacity = device_ctx.rr_graph.node_capacity(inode); float congestion_ratio = float(occ) / capacity; @@ -1072,12 +1070,12 @@ static void draw_congestion(ezgl::renderer* g) { //Sort the nodes in ascending order of value for drawing, this ensures high //valued nodes are not overdrawn by lower value ones (e.g-> when zoomed-out far) - auto cmp_ascending_acc_cost = [&](int lhs_node, int rhs_node) { + auto cmp_ascending_acc_cost = [&](const RRNodeId& lhs_node, const RRNodeId& rhs_node) { short lhs_occ = route_ctx.rr_node_route_inf[lhs_node].occ(); - short lhs_capacity = device_ctx.rr_nodes[lhs_node].capacity(); + short lhs_capacity = device_ctx.rr_graph.node_capacity(lhs_node); short rhs_occ = route_ctx.rr_node_route_inf[rhs_node].occ(); - short rhs_capacity = device_ctx.rr_nodes[rhs_node].capacity(); + short rhs_capacity = device_ctx.rr_graph.node_capacity(rhs_node); float lhs_cong_ratio = float(lhs_occ) / lhs_capacity; float rhs_cong_ratio = float(rhs_occ) / rhs_capacity; @@ -1089,7 +1087,7 @@ static void draw_congestion(ezgl::renderer* g) { if (draw_state->show_congestion == DRAW_CONGESTED_WITH_NETS) { auto rr_node_nets = collect_rr_node_nets(); - for (int inode : congested_rr_nodes) { + for (const RRNodeId& inode : congested_rr_nodes) { for (ClusterNetId net : rr_node_nets[inode]) { ezgl::color color = kelly_max_contrast_colors[size_t(net) % kelly_max_contrast_colors.size()]; draw_state->net_color[net] = color; @@ -1099,7 +1097,7 @@ static void draw_congestion(ezgl::renderer* g) { drawroute(HIGHLIGHTED, g); //Reset colors - for (int inode : congested_rr_nodes) { + for (const RRNodeId& inode : congested_rr_nodes) { for (ClusterNetId net : rr_node_nets[inode]) { draw_state->net_color[net] = DEFAULT_RR_NODE_COLOR; } @@ -1109,9 +1107,9 @@ static void draw_congestion(ezgl::renderer* g) { } //Draw each congested node - for (int inode : congested_rr_nodes) { + for (const RRNodeId& inode : congested_rr_nodes) { short occ = route_ctx.rr_node_route_inf[inode].occ(); - short capacity = device_ctx.rr_nodes[inode].capacity(); + short capacity = device_ctx.rr_graph.node_capacity(inode); float congestion_ratio = float(occ) / capacity; @@ -1120,7 +1118,7 @@ static void draw_congestion(ezgl::renderer* g) { ezgl::color color = to_ezgl_color(cmap->color(congestion_ratio)); - switch (device_ctx.rr_nodes[inode].type()) { + switch (device_ctx.rr_graph.node_type(inode)) { case CHANX: //fallthrough case CHANY: draw_rr_chan(inode, color, g); @@ -1159,18 +1157,18 @@ static void draw_routing_costs(ezgl::renderer* g) { float min_cost = std::numeric_limits::infinity(); float max_cost = -min_cost; - std::vector rr_node_costs(device_ctx.rr_graph.nodes().size(), 0.); - for (size_t inode = 0; inode < device_ctx.rr_graph.nodes().size(); inode++) { + vtr::vector rr_node_costs(device_ctx.rr_graph.nodes().size(), 0.); + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { float cost = 0.; if (draw_state->show_routing_costs == DRAW_TOTAL_ROUTING_COSTS || draw_state->show_routing_costs == DRAW_LOG_TOTAL_ROUTING_COSTS) { - int cost_index = device_ctx.rr_nodes[inode].cost_index(); + int cost_index = device_ctx.rr_graph.node_cost_index(inode); cost = device_ctx.rr_indexed_data[cost_index].base_cost + route_ctx.rr_node_route_inf[inode].acc_cost + route_ctx.rr_node_route_inf[inode].pres_cost; } else if (draw_state->show_routing_costs == DRAW_BASE_ROUTING_COSTS) { - int cost_index = device_ctx.rr_nodes[inode].cost_index(); + int cost_index = device_ctx.rr_graph.node_cost_index(inode); cost = device_ctx.rr_indexed_data[cost_index].base_cost; } else if (draw_state->show_routing_costs == DRAW_ACC_ROUTING_COSTS @@ -1194,7 +1192,7 @@ static void draw_routing_costs(ezgl::renderer* g) { } //Hide min value, draw_rr_costs() ignores NaN's - for (size_t inode = 0; inode < device_ctx.rr_graph.nodes().size(); inode++) { + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { if (rr_node_costs[inode] == min_cost) { rr_node_costs[inode] = NAN; } @@ -1292,10 +1290,10 @@ void draw_rr(ezgl::renderer* g) { g->set_line_dash(ezgl::line_dash::none); - for (size_t inode = 0; inode < device_ctx.rr_graph.nodes().size(); inode++) { + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { if (!draw_state->draw_rr_node[inode].node_highlighted) { /* If not highlighted node, assign color based on type. */ - switch (device_ctx.rr_nodes[inode].type()) { + switch (device_ctx.rr_graph.node_type(inode)) { case CHANX: case CHANY: draw_state->draw_rr_node[inode].color = DEFAULT_RR_NODE_COLOR; @@ -1312,7 +1310,7 @@ void draw_rr(ezgl::renderer* g) { } /* Now call drawing routines to draw the node. */ - switch (device_ctx.rr_nodes[inode].type()) { + switch (device_ctx.rr_graph.node_type(inode)) { case SOURCE: case SINK: break; /* Don't draw. */ @@ -1338,7 +1336,7 @@ void draw_rr(ezgl::renderer* g) { default: vpr_throw(VPR_ERROR_OTHER, __FILE__, __LINE__, - "in draw_rr: Unexpected rr_node type: %d.\n", device_ctx.rr_nodes[inode].type()); + "in draw_rr: Unexpected rr_node type: %d.\n", device_ctx.rr_graph.node_type(inode)); } } @@ -1434,7 +1432,7 @@ static void draw_rr_chan(const RRNodeId& inode, const ezgl::color color, ezgl::r if (switchpoint_min == 0) { if (dir != BI_DIRECTION) { //Draw a mux at the start of each wire, labelled with it's size (#inputs) - draw_mux_with_size(start, mux_dir, WIRE_DRAWING_WIDTH, device_ctx.rr_nodes[inode].fan_in(), g); + draw_mux_with_size(start, mux_dir, WIRE_DRAWING_WIDTH, device_ctx.rr_graph.node_in_edges(inode).size(), g); } } else { //Draw arrows and label with switch point @@ -1590,7 +1588,7 @@ static void draw_rr_edges(const RRNodeId& inode, ezgl::renderer* g) { } else { g->set_color(blk_DARKGREEN); } - switch_type = device_ctx.rr_nodes[inode].edge_switch(iedge); + switch_type = size_t(device_ctx.rr_graph.edge_switch(iedge)); draw_chanx_to_chanx_edge(inode, to_node, to_ptc_num, switch_type, g); break; @@ -1607,7 +1605,7 @@ static void draw_rr_edges(const RRNodeId& inode, ezgl::renderer* g) { } else { g->set_color(blk_DARKGREEN); } - switch_type = device_ctx.rr_nodes[inode].edge_switch(iedge); + switch_type = size_t(device_ctx.rr_graph.edge_switch(iedge)); draw_chanx_to_chany_edge(inode, from_ptc_num, to_node, to_ptc_num, FROM_X_TO_Y, switch_type, g); break; @@ -1660,7 +1658,7 @@ static void draw_rr_edges(const RRNodeId& inode, ezgl::renderer* g) { } else { g->set_color(blk_DARKGREEN); } - switch_type = device_ctx.rr_nodes[inode].edge_switch(iedge); + switch_type = size_t(device_ctx.rr_graph.edge_switch(iedge)); draw_chanx_to_chany_edge(to_node, to_ptc_num, inode, from_ptc_num, FROM_Y_TO_X, switch_type, g); break; @@ -1678,7 +1676,7 @@ static void draw_rr_edges(const RRNodeId& inode, ezgl::renderer* g) { } else { g->set_color(blk_DARKGREEN); } - switch_type = device_ctx.rr_nodes[inode].edge_switch(iedge); + switch_type = size_t(device_ctx.rr_graph.edge_switch(iedge)); draw_chany_to_chany_edge(inode, to_node, to_ptc_num, switch_type, g); break; @@ -1955,7 +1953,7 @@ ezgl::rectangle draw_get_rr_chan_bbox(const RRNodeId& inode) { t_draw_coords* draw_coords = get_draw_coords_vars(); auto& device_ctx = g_vpr_ctx.device(); - switch (device_ctx.rr_nodes[inode].type()) { + switch (device_ctx.rr_graph.node_type(inode)) { case CHANX: left = draw_coords->tile_x[device_ctx.rr_graph.node_xlow(inode)]; right = draw_coords->tile_x[device_ctx.rr_graph.node_xhigh(inode)] @@ -2012,7 +2010,7 @@ static void draw_rr_switch(float from_x, float from_y, float to_x, float to_y, b } } -static void draw_rr_pin(RRNodeId& inode, const ezgl::color& color, ezgl::renderer* g) { +static void draw_rr_pin(const RRNodeId& inode, const ezgl::color& color, ezgl::renderer* g) { /* Draws an IPIN or OPIN rr_node. Note that the pin can appear on more * * than one side of a clb. Also note that this routine can change the * * current color to BLACK. */ @@ -2066,7 +2064,7 @@ void draw_get_rr_pin_coords(const RRNodeId& inode, float* xcen, float* ycen) { step = (float)(draw_coords->get_tile_width()) / (float)(type->num_pins + type->capacity); offset = (ipin + k + 1) * step; - switch (node->side()) { + switch (device_ctx.rr_graph.node_side(inode)) { case LEFT: yc += offset; break; @@ -2087,7 +2085,8 @@ void draw_get_rr_pin_coords(const RRNodeId& inode, float* xcen, float* ycen) { default: vpr_throw(VPR_ERROR_OTHER, __FILE__, __LINE__, - "in draw_get_rr_pin_coords: Unexpected side %s.\n", node->side_string()); + "in draw_get_rr_pin_coords: Unexpected side %s.\n", + SIDE_STRING[device_ctx.rr_graph.node_side(inode)]); break; } @@ -2221,7 +2220,7 @@ void draw_partial_route(const std::vector& rr_nodes_to_draw, ezgl::ren std::vector edges = device_ctx.rr_graph.find_edges(prev_node, inode); VTR_ASSERT(1 == edges.size()); - auto switch_type = device_ctx.rr_graph.edge_switch(edges[0]); + auto switch_type = size_t(device_ctx.rr_graph.edge_switch(edges[0])); switch (rr_type) { case OPIN: { @@ -2230,7 +2229,7 @@ void draw_partial_route(const std::vector& rr_nodes_to_draw, ezgl::ren } case IPIN: { draw_rr_pin(inode, draw_state->draw_rr_node[inode].color, g); - if (device_ctx.rr_nodes[prev_node].type() == OPIN) { + if (device_ctx.rr_graph.node_type(prev_node) == OPIN) { draw_pin_to_pin(prev_node, inode, g); } else { draw_pin_to_chan_edge(inode, prev_node, g); @@ -2239,7 +2238,7 @@ void draw_partial_route(const std::vector& rr_nodes_to_draw, ezgl::ren } case CHANX: { if (draw_state->draw_route_type == GLOBAL) - chanx_track[device_ctx.rr_nodes[inode].xlow()][device_ctx.rr_nodes[inode].ylow()]++; + chanx_track[device_ctx.rr_graph.node_xlow(inode)][device_ctx.rr_graph.node_ylow(inode)]++; int itrack = get_track_num(inode, chanx_track, chany_track); draw_rr_chan(inode, draw_state->draw_rr_node[inode].color, g); @@ -2390,14 +2389,14 @@ void highlight_nets(char* message, const RRNodeId& hit_node) { * fan_in into the node in blue and fan_out from the node in red. If de-highlighted, * de-highlight its fan_in and fan_out. */ -void draw_highlight_fan_in_fan_out(const std::set& nodes) { +void draw_highlight_fan_in_fan_out(const std::set& nodes) { t_draw_state* draw_state = get_draw_state_vars(); auto& device_ctx = g_vpr_ctx.device(); for (auto node : nodes) { /* Highlight the fanout nodes in red. */ - for (t_edge_size iedge = 0, l = device_ctx.rr_nodes[node].num_edges(); iedge < l; iedge++) { - int fanout_node = device_ctx.rr_nodes[node].edge_sink_node(iedge); + for (const RREdgeId& iedge : device_ctx.rr_graph.node_out_edges(node)) { + RRNodeId fanout_node = device_ctx.rr_graph.edge_sink_node(iedge); if (draw_state->draw_rr_node[node].color == ezgl::MAGENTA && draw_state->draw_rr_node[fanout_node].color != ezgl::MAGENTA) { // If node is highlighted, highlight its fanout @@ -2411,9 +2410,9 @@ void draw_highlight_fan_in_fan_out(const std::set& nodes) { } /* Highlight the nodes that can fanin to this node in blue. */ - for (size_t inode = 0; inode < device_ctx.rr_graph.nodes().size(); inode++) { - for (t_edge_size iedge = 0, l = device_ctx.rr_nodes[inode].num_edges(); iedge < l; iedge++) { - int fanout_node = device_ctx.rr_nodes[inode].edge_sink_node(iedge); + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { + for (const RREdgeId& iedge : device_ctx.rr_graph.node_out_edges(node)) { + RRNodeId fanout_node = device_ctx.rr_graph.edge_sink_node(iedge); if (fanout_node == node) { if (draw_state->draw_rr_node[node].color == ezgl::MAGENTA && draw_state->draw_rr_node[inode].color != ezgl::MAGENTA) { // If node is highlighted, highlight its fanin @@ -2436,8 +2435,8 @@ void draw_highlight_fan_in_fan_out(const std::set& nodes) { * * It returns the hit RR node's ID (or OPEN if no hit) */ -static int draw_check_rr_node_hit(float click_x, float click_y) { - int hit_node = OPEN; +static RRNodeId draw_check_rr_node_hit(float click_x, float click_y) { + RRNodeId hit_node = RRNodeId::INVALID(); ezgl::rectangle bound_box; t_draw_coords* draw_coords = get_draw_coords_vars(); @@ -2489,20 +2488,20 @@ static int draw_check_rr_node_hit(float click_x, float click_y) { return hit_node; } -std::set draw_expand_non_configurable_rr_nodes(int from_node) { - std::set expanded_nodes; +std::set draw_expand_non_configurable_rr_nodes(const RRNodeId& from_node) { + std::set expanded_nodes; draw_expand_non_configurable_rr_nodes_recurr(from_node, expanded_nodes); return expanded_nodes; } -void draw_expand_non_configurable_rr_nodes_recurr(int from_node, std::set& expanded_nodes) { +void draw_expand_non_configurable_rr_nodes_recurr(const RRNodeId& from_node, std::set& expanded_nodes) { auto& device_ctx = g_vpr_ctx.device(); expanded_nodes.insert(from_node); - for (t_edge_size iedge = 0; iedge < device_ctx.rr_nodes[from_node].num_edges(); ++iedge) { - bool edge_configurable = device_ctx.rr_nodes[from_node].edge_is_configurable(iedge); - int to_node = device_ctx.rr_nodes[from_node].edge_sink_node(iedge); + for (const RREdgeId& iedge : device_ctx.rr_graph.node_out_edges(from_node)) { + bool edge_configurable = device_ctx.rr_graph.edge_is_configurable(iedge); + RRNodeId to_node = device_ctx.rr_graph.edge_sink_node(iedge); if (!edge_configurable && !expanded_nodes.count(to_node)) { draw_expand_non_configurable_rr_nodes_recurr(to_node, expanded_nodes); @@ -2525,7 +2524,7 @@ static bool highlight_rr_nodes(float x, float y) { } // Check which rr_node (if any) was clicked on. - int hit_node = draw_check_rr_node_hit(x, y); + RRNodeId hit_node = draw_check_rr_node_hit(x, y); return highlight_rr_nodes(hit_node); } @@ -2629,9 +2628,9 @@ void act_on_mouse_move(ezgl::application* app, GdkEventButton* event, double x, t_draw_state* draw_state = get_draw_state_vars(); if (draw_state->draw_rr_toggle != DRAW_NO_RR) { - int hit_node = draw_check_rr_node_hit(x, y); + RRNodeId hit_node = draw_check_rr_node_hit(x, y); - if (hit_node != OPEN) { + if (hit_node != RRNodeId::INVALID()) { //Update message std::string info = describe_rr_node(hit_node); @@ -2723,9 +2722,9 @@ void deselect_all() { for (auto net_id : cluster_ctx.clb_nlist.nets()) draw_state->net_color[net_id] = ezgl::BLACK; - for (size_t i = 0; i < device_ctx.rr_graph.nodes().size(); i++) { - draw_state->draw_rr_node[i].color = DEFAULT_RR_NODE_COLOR; - draw_state->draw_rr_node[i].node_highlighted = false; + for (const RRNodeId& node : device_ctx.rr_graph.nodes()) { + draw_state->draw_rr_node[node].color = DEFAULT_RR_NODE_COLOR; + draw_state->draw_rr_node[node].node_highlighted = false; } get_selected_sub_block_info().clear(); } @@ -2827,7 +2826,7 @@ static void draw_pin_to_chan_edge(const RRNodeId& pin_node, const RRNodeId& chan ezgl::rectangle chan_bbox = draw_get_rr_chan_bbox(chan_node); float x2 = 0, y2 = 0; - switch (chan_rr.type()) { + switch (rr_graph.node_type(chan_node)) { case CHANX: { y1 += draw_pin_offset; y2 = chan_bbox.bottom(); @@ -3138,15 +3137,15 @@ static void draw_routed_timing_edge_connection(tatum::NodeId src_tnode, tatum::N //Now that we have the CLB source and sink pins, we need to grab all the points on the routing connecting the pins VTR_ASSERT(cluster_ctx.clb_nlist.net_driver_block(net_id) == clb_src_block); - std::vector routed_rr_nodes = trace_routed_connection_rr_nodes(net_id, 0, sink_net_pin_index); + std::vector routed_rr_nodes = trace_routed_connection_rr_nodes(net_id, 0, sink_net_pin_index); //Mark all the nodes highlighted t_draw_state* draw_state = get_draw_state_vars(); - for (int inode : routed_rr_nodes) { + for (const RRNodeId& inode : routed_rr_nodes) { draw_state->draw_rr_node[inode].color = color; } - draw_partial_route((std::vector)routed_rr_nodes, (ezgl::renderer*)g); + draw_partial_route((std::vector)routed_rr_nodes, (ezgl::renderer*)g); } else { //Connection entirely within the CLB, we don't draw the internal routing so treat it as a fly-line VTR_ASSERT(clb_src_block == clb_sink_block); @@ -3157,7 +3156,7 @@ static void draw_routed_timing_edge_connection(tatum::NodeId src_tnode, tatum::N } //Returns the set of rr nodes which connect driver to sink -static std::vector trace_routed_connection_rr_nodes(const ClusterNetId net_id, const int driver_pin, const int sink_pin) { +static std::vector trace_routed_connection_rr_nodes(const ClusterNetId net_id, const int driver_pin, const int sink_pin) { auto& route_ctx = g_vpr_ctx.routing(); bool allocated_route_tree_structs = alloc_route_tree_timing_structs(true); //Needed for traceback_to_route_tree @@ -3165,11 +3164,11 @@ static std::vector trace_routed_connection_rr_nodes(const ClusterNetId net_ //Conver the traceback into an easily search-able t_rt_node* rt_root = traceback_to_route_tree(net_id); - VTR_ASSERT(rt_root && rt_root->inode == route_ctx.net_rr_terminals[net_id][driver_pin]); + VTR_ASSERT(rt_root && rt_root->inode == RRNodeId(route_ctx.net_rr_terminals[net_id][driver_pin])); - int sink_rr_node = route_ctx.net_rr_terminals[net_id][sink_pin]; + RRNodeId sink_rr_node = RRNodeId(route_ctx.net_rr_terminals[net_id][sink_pin]); - std::vector rr_nodes_on_path; + std::vector rr_nodes_on_path; //Collect the rr nodes trace_routed_connection_rr_nodes_recurr(rt_root, sink_rr_node, rr_nodes_on_path); @@ -3188,7 +3187,7 @@ static std::vector trace_routed_connection_rr_nodes(const ClusterNetId net_ //Helper function for trace_routed_connection_rr_nodes //Adds the rr nodes linking rt_node to sink_rr_node to rr_nodes_on_path //Returns true if rt_node is on the path -bool trace_routed_connection_rr_nodes_recurr(const t_rt_node* rt_node, int sink_rr_node, std::vector& rr_nodes_on_path) { +bool trace_routed_connection_rr_nodes_recurr(const t_rt_node* rt_node, const RRNodeId& sink_rr_node, std::vector& rr_nodes_on_path) { //DFS from the current rt_node to the sink_rr_node, when the sink is found trace back the used rr nodes if (rt_node->inode == sink_rr_node) { @@ -3211,18 +3210,6 @@ bool trace_routed_connection_rr_nodes_recurr(const t_rt_node* rt_node, int sink_ return false; //Not on path to sink } -//Find the edge between two rr nodes -static t_edge_size find_edge(int prev_inode, int inode) { - auto& device_ctx = g_vpr_ctx.device(); - for (t_edge_size iedge = 0; iedge < device_ctx.rr_nodes[prev_inode].num_edges(); ++iedge) { - if (device_ctx.rr_nodes[prev_inode].edge_sink_node(iedge) == inode) { - return iedge; - } - } - VTR_ASSERT(false); - return OPEN; -} - ezgl::color to_ezgl_color(vtr::Color color) { return ezgl::color(color.r * 255, color.g * 255, color.b * 255); } @@ -3548,7 +3535,13 @@ static void draw_rr_costs(ezgl::renderer* g, const vtr::vector& //Draw the nodes in ascending order of value, this ensures high valued nodes //are not overdrawn by lower value ones (e.g-> when zoomed-out far) - std::vector nodes = device_ctx.rr_graph.nodes(); + std::vector nodes; + /* Xifan Tang - TODO: This is not efficient, + * we should be able to give a constant vector from rr_graph object directly + */ + for (const RRNodeId& node : device_ctx.rr_graph.nodes()) { + nodes.push_back(node); + } auto cmp_ascending_cost = [&](const RRNodeId& lhs_node, const RRNodeId& rhs_node) { if (lowest_cost_first) { return rr_costs[lhs_node] > rr_costs[rhs_node]; diff --git a/vpr/src/draw/draw.h b/vpr/src/draw/draw.h index 98c908ba4..39458b968 100644 --- a/vpr/src/draw/draw.h +++ b/vpr/src/draw/draw.h @@ -60,8 +60,8 @@ void draw_screen(); ezgl::rectangle draw_get_rr_chan_bbox(const RRNodeId& inode); void draw_highlight_blocks_color(t_logical_block_type_ptr type, ClusterBlockId blk_id); void highlight_nets(char* message, const RRNodeId& hit_node); -void draw_highlight_fan_in_fan_out(const std::set& nodes); -std::set draw_expand_non_configurable_rr_nodes(int hit_node); +void draw_highlight_fan_in_fan_out(const std::set& nodes); +std::set draw_expand_non_configurable_rr_nodes(const RRNodeId& hit_node); void deselect_all(); // toggle functions diff --git a/vpr/src/draw/search_bar.cpp b/vpr/src/draw/search_bar.cpp index aaa591c20..a7ff3831b 100644 --- a/vpr/src/draw/search_bar.cpp +++ b/vpr/src/draw/search_bar.cpp @@ -74,8 +74,8 @@ void search_and_highlight(GtkWidget* /*widget*/, ezgl::application* app) { return; } - highlight_rr_nodes(rr_node_id); - auto_zoom_rr_node(rr_node_id); + highlight_rr_nodes(RRNodeId(rr_node_id)); + auto_zoom_rr_node(RRNodeId(rr_node_id)); } else if (search_type == "Block ID") { @@ -125,12 +125,12 @@ void search_and_highlight(GtkWidget* /*widget*/, ezgl::application* app) { app->refresh_drawing(); } -bool highlight_rr_nodes(int hit_node) { +bool highlight_rr_nodes(const RRNodeId& hit_node) { t_draw_state* draw_state = get_draw_state_vars(); char message[250] = ""; - if (hit_node != OPEN) { + if (hit_node != RRNodeId::INVALID()) { auto nodes = draw_expand_non_configurable_rr_nodes(hit_node); for (auto node : nodes) { if (draw_state->draw_rr_node[node].color != ezgl::MAGENTA) { @@ -177,21 +177,21 @@ bool highlight_rr_nodes(int hit_node) { return true; } -void auto_zoom_rr_node(int rr_node_id) { +void auto_zoom_rr_node(const RRNodeId& rr_node_id) { t_draw_coords* draw_coords = get_draw_coords_vars(); auto& device_ctx = g_vpr_ctx.device(); ezgl::rectangle rr_node; // find the location of the node - switch (device_ctx.rr_nodes[rr_node_id].type()) { + switch (device_ctx.rr_graph.node_type(rr_node_id)) { case IPIN: case OPIN: { - int i = device_ctx.rr_nodes[rr_node_id].xlow(); - int j = device_ctx.rr_nodes[rr_node_id].ylow(); + int i = device_ctx.rr_graph.node_xlow(rr_node_id); + int j = device_ctx.rr_graph.node_ylow(rr_node_id); t_physical_tile_type_ptr type = device_ctx.grid[i][j].type; int width_offset = device_ctx.grid[i][j].width_offset; int height_offset = device_ctx.grid[i][j].height_offset; - int ipin = device_ctx.rr_nodes[rr_node_id].ptc_num(); + int ipin = device_ctx.rr_graph.node_ptc_num(rr_node_id); float xcen, ycen; int iside; diff --git a/vpr/src/draw/search_bar.h b/vpr/src/draw/search_bar.h index c908e78c9..ac3f771cf 100644 --- a/vpr/src/draw/search_bar.h +++ b/vpr/src/draw/search_bar.h @@ -11,8 +11,8 @@ # include "draw_color.h" void search_and_highlight(GtkWidget* /*widget*/, ezgl::application* app); -bool highlight_rr_nodes(int hit_node); -void auto_zoom_rr_node(int rr_node_id); +bool highlight_rr_nodes(const RRNodeId& hit_node); +void auto_zoom_rr_node(const RRNodeId& rr_node_id); void highlight_blocks(ClusterBlockId clb_index); void highlight_nets(ClusterNetId net_id); void highlight_nets(std::string net_name); diff --git a/vpr/src/place/timing_place_lookup.cpp b/vpr/src/place/timing_place_lookup.cpp index d3cdad215..dd55adca4 100644 --- a/vpr/src/place/timing_place_lookup.cpp +++ b/vpr/src/place/timing_place_lookup.cpp @@ -34,6 +34,8 @@ #include "router_delay_profiling.h" #include "place_delay_model.h" +#include "rr_graph_obj_util.h" + /*To compute delay between blocks we calculate the delay between */ /*different nodes in the FPGA. From this procedure we generate * a lookup table which tells us the delay between different locations in*/ @@ -113,8 +115,8 @@ static bool find_direct_connect_sample_locations(const t_direct_inf* direct, t_physical_tile_type_ptr to_type, int to_pin, int to_pin_class, - int* src_rr, - int* sink_rr); + RRNodeId& src_rr, + RRNodeId& sink_rr); static bool verify_delta_delays(const vtr::Matrix& delta_delays); @@ -741,8 +743,8 @@ static bool find_direct_connect_sample_locations(const t_direct_inf* direct, t_physical_tile_type_ptr to_type, int to_pin, int to_pin_class, - int* src_rr, - int* sink_rr) { + RRNodeId& src_rr, + RRNodeId& sink_rr) { VTR_ASSERT(from_type != nullptr); VTR_ASSERT(to_type != nullptr); @@ -765,10 +767,10 @@ static bool find_direct_connect_sample_locations(const t_direct_inf* direct, //(with multi-width/height blocks pins may not exist at all locations) bool from_pin_found = false; if (direct->from_side != NUM_SIDES) { - int from_pin_rr = get_rr_node_index(device_ctx.rr_node_indices, from_x, from_y, OPIN, from_pin, direct->from_side); - from_pin_found = (from_pin_rr != OPEN); + RRNodeId from_pin_rr = device_ctx.rr_graph.find_node(from_x, from_y, OPIN, from_pin, direct->from_side); + from_pin_found = (from_pin_rr != RRNodeId::INVALID()); } else { - std::vector from_pin_rrs = get_rr_node_indices(device_ctx.rr_node_indices, from_x, from_y, OPIN, from_pin); + std::vector from_pin_rrs = find_rr_graph_nodes(device_ctx.rr_graph, from_x, from_y, OPIN, from_pin); from_pin_found = !from_pin_rrs.empty(); } if (!from_pin_found) continue; @@ -782,10 +784,10 @@ static bool find_direct_connect_sample_locations(const t_direct_inf* direct, //(with multi-width/height blocks pins may not exist at all locations) bool to_pin_found = false; if (direct->to_side != NUM_SIDES) { - int to_pin_rr = get_rr_node_index(device_ctx.rr_node_indices, to_x, to_y, IPIN, to_pin, direct->to_side); - to_pin_found = (to_pin_rr != OPEN); + RRNodeId to_pin_rr = device_ctx.rr_graph.find_node(to_x, to_y, IPIN, to_pin, direct->to_side); + to_pin_found = (to_pin_rr != RRNodeId::INVALID()); } else { - std::vector to_pin_rrs = get_rr_node_indices(device_ctx.rr_node_indices, to_x, to_y, IPIN, to_pin); + std::vector to_pin_rrs = find_rr_graph_nodes(device_ctx.rr_graph, to_x, to_y, IPIN, to_pin); to_pin_found = !to_pin_rrs.empty(); } if (!to_pin_found) continue; @@ -822,14 +824,14 @@ static bool find_direct_connect_sample_locations(const t_direct_inf* direct, //Find a source/sink RR node associated with the pins of the direct // - auto source_rr_nodes = get_rr_node_indices(device_ctx.rr_node_indices, from_x, from_y, SOURCE, from_pin_class); + std::vector source_rr_nodes = find_rr_graph_nodes(device_ctx.rr_graph, from_x, from_y, SOURCE, from_pin_class); VTR_ASSERT(source_rr_nodes.size() > 0); - auto sink_rr_nodes = get_rr_node_indices(device_ctx.rr_node_indices, to_x, to_y, SINK, to_pin_class); + std::vector sink_rr_nodes = find_rr_graph_nodes(device_ctx.rr_graph, to_x, to_y, SINK, to_pin_class); VTR_ASSERT(sink_rr_nodes.size() > 0); - *src_rr = source_rr_nodes[0]; - *sink_rr = sink_rr_nodes[0]; + src_rr = source_rr_nodes[0]; + sink_rr = sink_rr_nodes[0]; return true; } @@ -884,7 +886,7 @@ void OverrideDelayModel::compute_override_delay_model( //sampled_rr_pairs and skipping them if they occur multiple times. int missing_instances = 0; int missing_paths = 0; - std::set> sampled_rr_pairs; + std::set> sampled_rr_pairs; for (int iconn = 0; iconn < num_conns; ++iconn) { //Find the associated pins int from_pin = find_pin(from_type, from_port.port_name(), from_port.port_low_index() + iconn); @@ -899,9 +901,9 @@ void OverrideDelayModel::compute_override_delay_model( int to_pin_class = find_pin_class(to_type, to_port.port_name(), to_port.port_low_index() + iconn, RECEIVER); VTR_ASSERT(to_pin_class != OPEN); - int src_rr = OPEN; - int sink_rr = OPEN; - bool found_sample_points = find_direct_connect_sample_locations(direct, from_type, from_pin, from_pin_class, to_type, to_pin, to_pin_class, &src_rr, &sink_rr); + RRNodeId src_rr = RRNodeId::INVALID(); + RRNodeId sink_rr = RRNodeId::INVALID(); + bool found_sample_points = find_direct_connect_sample_locations(direct, from_type, from_pin, from_pin_class, to_type, to_pin, to_pin_class, src_rr, sink_rr); if (!found_sample_points) { ++missing_instances; @@ -912,8 +914,8 @@ void OverrideDelayModel::compute_override_delay_model( //sampled the associated source/sink pair and don't need to do so again if (sampled_rr_pairs.count({src_rr, sink_rr})) continue; - VTR_ASSERT(src_rr != OPEN); - VTR_ASSERT(sink_rr != OPEN); + VTR_ASSERT(src_rr != RRNodeId::INVALID()); + VTR_ASSERT(sink_rr != RRNodeId::INVALID()); float direct_connect_delay = std::numeric_limits::quiet_NaN(); bool found_routing_path = route_profiler.calculate_delay(src_rr, sink_rr, router_opts2, &direct_connect_delay); diff --git a/vpr/src/route/route_common.cpp b/vpr/src/route/route_common.cpp index 40eada542..39373cb76 100644 --- a/vpr/src/route/route_common.cpp +++ b/vpr/src/route/route_common.cpp @@ -346,14 +346,14 @@ bool feasible_routing() { } //Returns all RR nodes in the current routing which are congested -std::vector collect_congested_rr_nodes() { +std::vector collect_congested_rr_nodes() { auto& device_ctx = g_vpr_ctx.device(); auto& route_ctx = g_vpr_ctx.routing(); - std::vector congested_rr_nodes; - for (size_t inode = 0; inode < device_ctx.rr_graph.nodes().size(); inode++) { + std::vector congested_rr_nodes; + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { short occ = route_ctx.rr_node_route_inf[inode].occ(); - short capacity = device_ctx.rr_nodes[inode].capacity(); + short capacity = device_ctx.rr_graph.node_capacity(inode); if (occ > capacity) { congested_rr_nodes.push_back(inode); @@ -364,16 +364,16 @@ std::vector collect_congested_rr_nodes() { /* Returns a vector from [0..device_ctx.rr_graph.nodes().size()-1] containing the set * of nets using each RR node */ -std::vector> collect_rr_node_nets() { +vtr::vector> collect_rr_node_nets() { auto& device_ctx = g_vpr_ctx.device(); auto& route_ctx = g_vpr_ctx.routing(); auto& cluster_ctx = g_vpr_ctx.clustering(); - std::vector> rr_node_nets(device_ctx.rr_graph.nodes().size()); + vtr::vector> rr_node_nets(device_ctx.rr_graph.nodes().size()); for (ClusterNetId inet : cluster_ctx.clb_nlist.nets()) { t_trace* trace_elem = route_ctx.trace[inet].head; while (trace_elem) { - int rr_node = trace_elem->index; + const RRNodeId& rr_node = trace_elem->index; rr_node_nets[rr_node].insert(inet); diff --git a/vpr/src/route/route_export.h b/vpr/src/route/route_export.h index ec68f83b8..187ca5087 100644 --- a/vpr/src/route/route_export.h +++ b/vpr/src/route/route_export.h @@ -29,9 +29,9 @@ bool try_route(int width_fac, bool feasible_routing(); -std::vector collect_congested_rr_nodes(); +std::vector collect_congested_rr_nodes(); -std::vector> collect_rr_node_nets(); +vtr::vector> collect_rr_node_nets(); t_clb_opins_used alloc_route_structs(); diff --git a/vpr/src/route/rr_graph.cpp b/vpr/src/route/rr_graph.cpp index 6bb9892ed..e70e70d01 100644 --- a/vpr/src/route/rr_graph.cpp +++ b/vpr/src/route/rr_graph.cpp @@ -2526,53 +2526,53 @@ static vtr::NdMatrix, 4> alloc_and_load_track_to_pin_lookup(vtr return track_to_pin_lookup; } -std::string describe_rr_node(int inode) { +std::string describe_rr_node(const RRNodeId& inode) { auto& device_ctx = g_vpr_ctx.device(); - std::string msg = vtr::string_fmt("RR node: %d", inode); + std::string msg = vtr::string_fmt("RR node: %ld", size_t(inode)); - const auto& rr_node = device_ctx.rr_nodes[inode]; + const RRGraph& rr_graph = device_ctx.rr_graph; - msg += vtr::string_fmt(" type: %s", rr_node.type_string()); + msg += vtr::string_fmt(" type: %s", rr_node_typename[rr_graph.node_type(inode)]); - msg += vtr::string_fmt(" location: (%d,%d)", rr_node.xlow(), rr_node.ylow()); - if (rr_node.xlow() != rr_node.xhigh() || rr_node.ylow() != rr_node.yhigh()) { - msg += vtr::string_fmt(" <-> (%d,%d)", rr_node.xhigh(), rr_node.yhigh()); + msg += vtr::string_fmt(" location: (%d,%d)", rr_graph.node_xlow(inode), rr_graph.node_ylow(inode)); + if (rr_graph.node_xlow(inode) != rr_graph.node_xhigh(inode) || rr_graph.node_ylow(inode) != rr_graph.node_yhigh(inode)) { + msg += vtr::string_fmt(" <-> (%d,%d)", rr_graph.node_xhigh(inode), rr_graph.node_yhigh(inode)); } - if (rr_node.type() == CHANX || rr_node.type() == CHANY) { - int cost_index = rr_node.cost_index(); + if (rr_graph.node_type(inode) == CHANX || rr_graph.node_type(inode) == CHANY) { + int cost_index = rr_graph.node_cost_index(inode); int seg_index = device_ctx.rr_indexed_data[cost_index].seg_index; if (seg_index < (int)device_ctx.arch->Segments.size()) { msg += vtr::string_fmt(" track: %d len: %d longline: %d seg_type: %s dir: %s", - rr_node.track_num(), - rr_node.length(), + rr_graph.node_track_num(inode), + rr_graph.node_length(inode), device_ctx.arch->Segments[seg_index].longline, device_ctx.arch->Segments[seg_index].name.c_str(), - rr_node.direction_string()); + DIRECTION_STRING_WRITE_XML[rr_graph.node_direction(inode)]); } else { msg += vtr::string_fmt(" track: %d len: %d seg_type: ILLEGAL_SEG_INDEX %d dir: %s", - rr_node.track_num(), - rr_node.length(), + rr_graph.node_track_num(inode), + rr_graph.node_length(inode), seg_index, - rr_node.direction_string()); + DIRECTION_STRING_WRITE_XML[rr_graph.node_direction(inode)]); } - } else if (rr_node.type() == IPIN || rr_node.type() == OPIN) { - auto type = device_ctx.grid[rr_node.xlow()][rr_node.ylow()].type; - std::string pin_name = block_type_pin_index_to_name(type, rr_node.pin_num()); + } else if (rr_graph.node_type(inode) == IPIN || rr_graph.node_type(inode) == OPIN) { + auto type = device_ctx.grid[rr_graph.node_xlow(inode)][rr_graph.node_ylow(inode)].type; + std::string pin_name = block_type_pin_index_to_name(type, rr_graph.node_pin_num(inode)); msg += vtr::string_fmt(" pin: %d pin_name: %s", - rr_node.pin_num(), + rr_graph.node_pin_num(inode), pin_name.c_str()); } else { - VTR_ASSERT(rr_node.type() == SOURCE || rr_node.type() == SINK); + VTR_ASSERT(rr_graph.node_type(inode) == SOURCE || rr_graph.node_type(inode) == SINK); - msg += vtr::string_fmt(" class: %d", rr_node.class_num()); + msg += vtr::string_fmt(" class: %d", rr_graph.node_class_num(inode)); } - msg += vtr::string_fmt(" capacity: %d", rr_node.capacity()); + msg += vtr::string_fmt(" capacity: %d", rr_graph.node_capacity(inode)); return msg; } diff --git a/vpr/src/route/rr_graph.h b/vpr/src/route/rr_graph.h index f55a64f7f..1f60b1afc 100644 --- a/vpr/src/route/rr_graph.h +++ b/vpr/src/route/rr_graph.h @@ -43,7 +43,7 @@ void create_rr_graph(const t_graph_type graph_type, void free_rr_graph(); //Returns a brief one-line summary of an RR node -std::string describe_rr_node(int inode); +std::string describe_rr_node(const RRNodeId& inode); void init_fan_in(std::vector& L_rr_node, const int num_rr_nodes); From a2f231452a45b00a5609e0ce057b835fb0bbafac Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 1 Feb 2020 12:26:42 -0700 Subject: [PATCH 069/645] power estimation adapted to use RRGraph object --- vpr/src/power/power.cpp | 116 ++++++++++++++++++++-------------------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/vpr/src/power/power.cpp b/vpr/src/power/power.cpp index d4e17c0f8..b0071f06c 100644 --- a/vpr/src/power/power.cpp +++ b/vpr/src/power/power.cpp @@ -62,7 +62,7 @@ typedef enum { } e_power_breakdown_entry_type; /************************* File Scope **********************************/ -static t_rr_node_power* rr_node_power; +static vtr::vector rr_node_power; /************************* Function Declarations ********************/ /* Routing */ @@ -794,7 +794,7 @@ static void power_usage_routing(t_power_usage* power_usage, power_ctx.commonly_used->total_cb_buffer_size = 0.; /* Reset rr graph net indices */ - for (size_t rr_node_idx = 0; rr_node_idx < device_ctx.rr_nodes.size(); rr_node_idx++) { + for (const RRNodeId& rr_node_idx : device_ctx.rr_graph.nodes()) { rr_node_power[rr_node_idx].net_num = ClusterNetId::INVALID(); rr_node_power[rr_node_idx].num_inputs = 0; rr_node_power[rr_node_idx].selected_input = 0; @@ -815,19 +815,20 @@ static void power_usage_routing(t_power_usage* power_usage, t_trace* trace; for (trace = route_ctx.trace[net_id].head; trace != nullptr; trace = trace->next) { - auto node = &device_ctx.rr_nodes[trace->index]; + RRNodeId node = trace->index; + const RRGraph& rr_graph = device_ctx.rr_graph; t_rr_node_power* node_power = &rr_node_power[trace->index]; if (node_power->visited) { continue; } - for (t_edge_size edge_idx = 0; edge_idx < node->num_edges(); edge_idx++) { - if (node->edge_sink_node(edge_idx) != OPEN) { - auto next_node = &device_ctx.rr_nodes[node->edge_sink_node(edge_idx)]; - t_rr_node_power* next_node_power = &rr_node_power[node->edge_sink_node(edge_idx)]; + for (const RREdgeId& edge_idx : rr_graph.node_out_edges(node)) { + if (rr_graph.edge_sink_node(edge_idx) != RRNodeId::INVALID()) { + RRNodeId next_node = rr_graph.edge_sink_node(edge_idx); + t_rr_node_power* next_node_power = &rr_node_power[next_node]; - switch (next_node->type()) { + switch (rr_graph.node_type(next_node)) { case CHANX: case CHANY: case IPIN: @@ -837,9 +838,9 @@ static void power_usage_routing(t_power_usage* power_usage, next_node_power->in_dens[next_node_power->num_inputs] = clb_net_density(node_power->net_num); next_node_power->in_prob[next_node_power->num_inputs] = clb_net_prob(node_power->net_num); next_node_power->num_inputs++; - if (next_node_power->num_inputs > next_node->fan_in()) { + if (next_node_power->num_inputs > rr_graph.node_in_edges(next_node).size()) { VTR_LOG("%d %d\n", next_node_power->num_inputs, - next_node->fan_in()); + rr_graph.node_in_edges(next_node).size()); fflush(nullptr); VTR_ASSERT(0); } @@ -855,9 +856,9 @@ static void power_usage_routing(t_power_usage* power_usage, } /* Calculate power of all routing entities */ - for (size_t rr_node_idx = 0; rr_node_idx < device_ctx.rr_nodes.size(); rr_node_idx++) { + for (const RRNodeId& rr_node_idx : device_ctx.rr_graph.nodes()) { t_power_usage sub_power_usage; - auto node = &device_ctx.rr_nodes[rr_node_idx]; + const RRGraph& rr_graph = device_ctx.rr_graph; t_rr_node_power* node_power = &rr_node_power[rr_node_idx]; float C_wire; float buffer_size; @@ -866,7 +867,7 @@ static void power_usage_routing(t_power_usage* power_usage, //float C_per_seg_split; int wire_length; - switch (node->type()) { + switch (rr_graph.node_type(rr_node_idx)) { case SOURCE: case SINK: case OPIN: @@ -877,13 +878,13 @@ static void power_usage_routing(t_power_usage* power_usage, * - Driver (accounted for at end of CHANX/Y - see below) * - Multiplexor */ - if (node->fan_in()) { + if (rr_graph.node_in_edges(rr_node_idx).size()) { VTR_ASSERT(node_power->in_dens); VTR_ASSERT(node_power->in_prob); /* Multiplexor */ power_usage_mux_multilevel(&sub_power_usage, - power_get_mux_arch(node->fan_in(), + power_get_mux_arch(rr_graph.node_in_edges(rr_node_idx).size(), power_ctx.arch->mux_transistor_size), node_power->in_prob, node_power->in_dens, node_power->selected_input, true, @@ -904,19 +905,19 @@ static void power_usage_routing(t_power_usage* power_usage, VTR_ASSERT(node_power->in_prob); wire_length = 0; - if (node->type() == CHANX) { - wire_length = node->xhigh() - node->xlow() + 1; - } else if (node->type() == CHANY) { - wire_length = node->yhigh() - node->ylow() + 1; + if (rr_graph.node_type(rr_node_idx) == CHANX) { + wire_length = rr_graph.node_xhigh(rr_node_idx) - rr_graph.node_xlow(rr_node_idx) + 1; + } else if (rr_graph.node_type(rr_node_idx) == CHANY) { + wire_length = rr_graph.node_yhigh(rr_node_idx) - rr_graph.node_ylow(rr_node_idx) + 1; } C_wire = wire_length - * segment_inf[device_ctx.rr_indexed_data[node->cost_index()].seg_index].Cmetal; + * segment_inf[device_ctx.rr_indexed_data[rr_graph.node_cost_index(rr_node_idx)].seg_index].Cmetal; //(double)power_ctx.commonly_used->tile_length); - VTR_ASSERT(node_power->selected_input < node->fan_in()); + VTR_ASSERT(node_power->selected_input < rr_graph.node_in_edges(rr_node_idx).size()); /* Multiplexor */ power_usage_mux_multilevel(&sub_power_usage, - power_get_mux_arch(node->fan_in(), + power_get_mux_arch(rr_graph.node_in_edges(rr_node_idx).size(), power_ctx.arch->mux_transistor_size), node_power->in_prob, node_power->in_dens, node_power->selected_input, true, power_ctx.solution_inf.T_crit); @@ -979,10 +980,10 @@ static void power_usage_routing(t_power_usage* power_usage, /* Determine types of switches that this wire drives */ connectionbox_fanout = 0; switchbox_fanout = 0; - for (t_edge_size iedge = 0; iedge < node->num_edges(); iedge++) { - if (node->edge_switch(iedge) == routing_arch->wire_to_rr_ipin_switch) { + for (const RREdgeId& iedge : rr_graph.node_out_edges(rr_node_idx)) { + if ((short)size_t(rr_graph.edge_switch(iedge)) == routing_arch->wire_to_rr_ipin_switch) { connectionbox_fanout++; - } else if (node->edge_switch(iedge) == routing_arch->delayless_switch) { + } else if ((short)size_t(rr_graph.edge_switch(iedge)) == routing_arch->delayless_switch) { /* Do nothing */ } else { switchbox_fanout++; @@ -1191,9 +1192,8 @@ void power_routing_init(const t_det_routing_arch* routing_arch) { } /* Initialize RR Graph Structures */ - rr_node_power = (t_rr_node_power*)vtr::calloc(device_ctx.rr_nodes.size(), - sizeof(t_rr_node_power)); - for (size_t rr_node_idx = 0; rr_node_idx < device_ctx.rr_nodes.size(); rr_node_idx++) { + rr_node_power.resize(device_ctx.rr_graph.nodes().size()); + for (const RRNodeId& rr_node_idx : device_ctx.rr_graph.nodes()) { rr_node_power[rr_node_idx].driver_switch_type = OPEN; } @@ -1202,40 +1202,40 @@ void power_routing_init(const t_det_routing_arch* routing_arch) { max_IPIN_fanin = 0; max_seg_to_seg_fanout = 0; max_seg_to_IPIN_fanout = 0; - for (size_t rr_node_idx = 0; rr_node_idx < device_ctx.rr_nodes.size(); rr_node_idx++) { + for (const RRNodeId& rr_node_idx : device_ctx.rr_graph.nodes()) { int fanout_to_IPIN = 0; int fanout_to_seg = 0; - auto node = &device_ctx.rr_nodes[rr_node_idx]; + const RRGraph& rr_graph = device_ctx.rr_graph; t_rr_node_power* node_power = &rr_node_power[rr_node_idx]; - switch (node->type()) { + switch (rr_graph.node_type(rr_node_idx)) { case IPIN: max_IPIN_fanin = std::max(max_IPIN_fanin, - static_cast(node->fan_in())); - max_fanin = std::max(max_fanin, static_cast(node->fan_in())); + static_cast(rr_graph.node_in_edges(rr_node_idx).size())); + max_fanin = std::max(max_fanin, static_cast(rr_graph.node_in_edges(rr_node_idx).size())); - node_power->in_dens = (float*)vtr::calloc(node->fan_in(), + node_power->in_dens = (float*)vtr::calloc(rr_graph.node_in_edges(rr_node_idx).size(), sizeof(float)); - node_power->in_prob = (float*)vtr::calloc(node->fan_in(), + node_power->in_prob = (float*)vtr::calloc(rr_graph.node_in_edges(rr_node_idx).size(), sizeof(float)); break; case CHANX: case CHANY: - for (t_edge_size iedge = 0; iedge < node->num_edges(); iedge++) { - if (node->edge_switch(iedge) == routing_arch->wire_to_rr_ipin_switch) { + for (const RREdgeId& iedge : rr_graph.node_out_edges(rr_node_idx)) { + if ((short)size_t(rr_graph.edge_switch(iedge)) == routing_arch->wire_to_rr_ipin_switch) { fanout_to_IPIN++; - } else if (node->edge_switch(iedge) != routing_arch->delayless_switch) { + } else if ((short)size_t(rr_graph.edge_switch(iedge)) != routing_arch->delayless_switch) { fanout_to_seg++; } } max_seg_to_IPIN_fanout = std::max(max_seg_to_IPIN_fanout, fanout_to_IPIN); max_seg_to_seg_fanout = std::max(max_seg_to_seg_fanout, fanout_to_seg); - max_fanin = std::max(max_fanin, static_cast(node->fan_in())); + max_fanin = std::max(max_fanin, static_cast(rr_graph.node_in_edges(rr_node_idx).size())); - node_power->in_dens = (float*)vtr::calloc(node->fan_in(), + node_power->in_dens = (float*)vtr::calloc(rr_graph.node_in_edges(rr_node_idx).size(), sizeof(float)); - node_power->in_prob = (float*)vtr::calloc(node->fan_in(), + node_power->in_prob = (float*)vtr::calloc(rr_graph.node_in_edges(rr_node_idx).size(), sizeof(float)); break; default: @@ -1253,15 +1253,15 @@ void power_routing_init(const t_det_routing_arch* routing_arch) { #endif /* Populate driver switch type */ - for (size_t rr_node_idx = 0; rr_node_idx < device_ctx.rr_nodes.size(); rr_node_idx++) { - auto node = &device_ctx.rr_nodes[rr_node_idx]; + for (const RRNodeId& rr_node_idx : device_ctx.rr_graph.nodes()) { + const RRGraph& rr_graph = device_ctx.rr_graph; - for (t_edge_size edge_idx = 0; edge_idx < node->num_edges(); edge_idx++) { - if (node->edge_sink_node(edge_idx) != OPEN) { - if (rr_node_power[node->edge_sink_node(edge_idx)].driver_switch_type == OPEN) { - rr_node_power[node->edge_sink_node(edge_idx)].driver_switch_type = node->edge_switch(edge_idx); + for (const RREdgeId& edge_idx : rr_graph.node_out_edges(rr_node_idx)) { + if (rr_graph.edge_sink_node(edge_idx) != RRNodeId::INVALID()) { + if (rr_node_power[rr_graph.edge_sink_node(edge_idx)].driver_switch_type == OPEN) { + rr_node_power[rr_graph.edge_sink_node(edge_idx)].driver_switch_type = (short)size_t(rr_graph.edge_switch(edge_idx)); } else { - VTR_ASSERT(rr_node_power[node->edge_sink_node(edge_idx)].driver_switch_type == node->edge_switch(edge_idx)); + VTR_ASSERT(rr_node_power[rr_graph.edge_sink_node(edge_idx)].driver_switch_type == (short)size_t(rr_graph.edge_switch(edge_idx))); } } } @@ -1269,14 +1269,14 @@ void power_routing_init(const t_det_routing_arch* routing_arch) { /* Find Max Fanout of Routing Buffer */ t_edge_size max_seg_fanout = 0; - for (size_t rr_node_idx = 0; rr_node_idx < device_ctx.rr_nodes.size(); rr_node_idx++) { - auto node = &device_ctx.rr_nodes[rr_node_idx]; + for (const RRNodeId& rr_node_idx : device_ctx.rr_graph.nodes()) { + const RRGraph& rr_graph = device_ctx.rr_graph; - switch (node->type()) { + switch (rr_graph.node_type(rr_node_idx)) { case CHANX: case CHANY: - if (node->num_edges() > max_seg_fanout) { - max_seg_fanout = node->num_edges(); + if (rr_graph.node_out_edges(rr_node_idx).size() > max_seg_fanout) { + max_seg_fanout = rr_graph.node_out_edges(rr_node_idx).size(); } break; default: @@ -1357,15 +1357,15 @@ bool power_uninit() { auto& power_ctx = g_vpr_ctx.power(); bool error = false; - for (size_t rr_node_idx = 0; rr_node_idx < device_ctx.rr_nodes.size(); rr_node_idx++) { - auto node = &device_ctx.rr_nodes[rr_node_idx]; + for (const RRNodeId& rr_node_idx : device_ctx.rr_graph.nodes()) { + const RRGraph& rr_graph = device_ctx.rr_graph; t_rr_node_power* node_power = &rr_node_power[rr_node_idx]; - switch (node->type()) { + switch (rr_graph.node_type(rr_node_idx)) { case CHANX: case CHANY: case IPIN: - if (node->fan_in()) { + if (rr_graph.node_in_edges(rr_node_idx).size()) { free(node_power->in_dens); free(node_power->in_prob); } @@ -1375,7 +1375,7 @@ bool power_uninit() { break; } } - free(rr_node_power); + rr_node_power.clear(); /* Free mux architectures */ for (std::map::iterator it = power_ctx.commonly_used->mux_info.begin(); From bc7988bb9ed011cd98751cc904565c9808cfb6fe Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 1 Feb 2020 13:19:47 -0700 Subject: [PATCH 070/645] check_route adopt RRGraph object --- vpr/src/base/vpr_types.h | 10 +- vpr/src/route/check_route.cpp | 158 ++++++++++++++++--------------- vpr/src/route/check_rr_graph.cpp | 46 ++++----- vpr/src/route/check_rr_graph.h | 2 +- 4 files changed, 110 insertions(+), 106 deletions(-) diff --git a/vpr/src/base/vpr_types.h b/vpr/src/base/vpr_types.h index 15508a4f8..dee5fd7e6 100644 --- a/vpr/src/base/vpr_types.h +++ b/vpr/src/base/vpr_types.h @@ -1188,13 +1188,13 @@ struct t_net_routing_status { }; struct t_node_edge { - t_node_edge(int fnode, int tnode) { + t_node_edge(const RRNodeId& fnode, const RRNodeId& tnode) { from_node = fnode; to_node = tnode; } - int from_node; - int to_node; + RRNodeId from_node; + RRNodeId to_node; //For std::set friend bool operator<(const t_node_edge& lhs, const t_node_edge& rhs) { @@ -1204,7 +1204,7 @@ struct t_node_edge { //Non-configurably connected nodes and edges in the RR graph struct t_non_configurable_rr_sets { - std::set> node_sets; + std::set> node_sets; std::set> edge_sets; }; @@ -1290,6 +1290,6 @@ class RouteStatus { int chan_width_ = -1; }; -typedef vtr::vector>> t_clb_opins_used; //[0..num_blocks-1][0..class-1][0..used_pins-1] +typedef vtr::vector>> t_clb_opins_used; //[0..num_blocks-1][0..class-1][0..used_pins-1] #endif diff --git a/vpr/src/route/check_route.cpp b/vpr/src/route/check_route.cpp index e9fa20673..9fa3aa2c6 100644 --- a/vpr/src/route/check_route.cpp +++ b/vpr/src/route/check_route.cpp @@ -17,13 +17,13 @@ #include "route_tree_timing.h" /******************** Subroutines local to this module **********************/ -static void check_node_and_range(int inode, enum e_route_type route_type); -static void check_source(int inode, ClusterNetId net_id); -static void check_sink(int inode, ClusterNetId net_id, bool* pin_done); +static void check_node_and_range(const RRNodeId& inode, enum e_route_type route_type); +static void check_source(const RRNodeId& inode, ClusterNetId net_id); +static void check_sink(const RRNodeId& inode, ClusterNetId net_id, bool* pin_done); static void check_switch(t_trace* tptr, int num_switch); -static bool check_adjacent(int from_node, int to_node); -static int chanx_chany_adjacent(int chanx_node, int chany_node); -static void reset_flags(ClusterNetId inet, bool* connected_to_route); +static bool check_adjacent(const RRNodeId& from_node, const RRNodeId& to_node); +static int chanx_chany_adjacent(const RRNodeId& chanx_node, const RRNodeId& chany_node); +static void reset_flags(ClusterNetId inet, vtr::vector& connected_to_route); static void check_locally_used_clb_opins(const t_clb_opins_used& clb_opins_used_locally, enum e_route_type route_type); @@ -39,10 +39,11 @@ void check_route(enum e_route_type route_type) { * oversubscribed (the occupancy of everything is recomputed from * * scratch). */ - int max_pins, inode, prev_node; + int max_pins; + RRNodeId inode, prev_node; unsigned int ipin; bool valid, connects; - bool* connected_to_route; /* [0 .. device_ctx.rr_nodes.size()-1] */ + vtr::vector connected_to_route; /* [0 .. device_ctx.rr_nodes.size()-1] */ t_trace* tptr; bool* pin_done; @@ -70,7 +71,7 @@ void check_route(enum e_route_type route_type) { auto non_configurable_rr_sets = identify_non_configurable_rr_sets(); - connected_to_route = (bool*)vtr::calloc(device_ctx.rr_nodes.size(), sizeof(bool)); + connected_to_route.resize(device_ctx.rr_graph.nodes().size(), false); max_pins = 0; for (auto net_id : cluster_ctx.clb_nlist.nets()) @@ -115,7 +116,7 @@ void check_route(enum e_route_type route_type) { if (prev_switch == OPEN) { //Start of a new branch if (connected_to_route[inode] == false) { VPR_ERROR(VPR_ERROR_ROUTE, - "in check_route: node %d does not link into existing routing for net %d.\n", inode, size_t(net_id)); + "in check_route: node %d does not link into existing routing for net %d.\n", size_t(inode), size_t(net_id)); } } else { //Continuing along existing branch connects = check_adjacent(prev_node, inode); @@ -131,7 +132,7 @@ void check_route(enum e_route_type route_type) { connected_to_route[inode] = true; /* Mark as in path. */ - if (device_ctx.rr_nodes[inode].type() == SINK) { + if (device_ctx.rr_graph.node_type(inode) == SINK) { check_sink(inode, net_id, pin_done); num_sinks += 1; } @@ -165,24 +166,24 @@ void check_route(enum e_route_type route_type) { } /* End for each net */ free(pin_done); - free(connected_to_route); + connected_to_route.clear(); VTR_LOG("Completed routing consistency check successfully.\n"); VTR_LOG("\n"); } /* Checks that this SINK node is one of the terminals of inet, and marks * * the appropriate pin as being reached. */ -static void check_sink(int inode, ClusterNetId net_id, bool* pin_done) { +static void check_sink(const RRNodeId& inode, ClusterNetId net_id, bool* pin_done) { auto& device_ctx = g_vpr_ctx.device(); auto& cluster_ctx = g_vpr_ctx.clustering(); auto& place_ctx = g_vpr_ctx.placement(); - VTR_ASSERT(device_ctx.rr_nodes[inode].type() == SINK); - int i = device_ctx.rr_nodes[inode].xlow(); - int j = device_ctx.rr_nodes[inode].ylow(); + VTR_ASSERT(device_ctx.rr_graph.node_type(inode) == SINK); + int i = device_ctx.rr_graph.node_xlow(inode); + int j = device_ctx.rr_graph.node_ylow(inode); auto type = device_ctx.grid[i][j].type; /* For sinks, ptc_num is the class */ - int ptc_num = device_ctx.rr_nodes[inode].ptc_num(); + int ptc_num = device_ctx.rr_graph.node_ptc_num(inode); int ifound = 0; for (int iblk = 0; iblk < type->capacity; iblk++) { @@ -215,26 +216,26 @@ static void check_sink(int inode, ClusterNetId net_id, bool* pin_done) { "in check_sink: node %d does not connect to any terminal of net %s #%lu.\n" "This error is usually caused by incorrectly specified logical equivalence in your architecture file.\n" "You should try to respecify what pins are equivalent or turn logical equivalence off.\n", - inode, cluster_ctx.clb_nlist.net_name(net_id).c_str(), size_t(net_id)); + size_t(inode), cluster_ctx.clb_nlist.net_name(net_id).c_str(), size_t(net_id)); } } /* Checks that the node passed in is a valid source for this net. */ -static void check_source(int inode, ClusterNetId net_id) { +static void check_source(const RRNodeId& inode, ClusterNetId net_id) { auto& device_ctx = g_vpr_ctx.device(); auto& cluster_ctx = g_vpr_ctx.clustering(); auto& place_ctx = g_vpr_ctx.placement(); - t_rr_type rr_type = device_ctx.rr_nodes[inode].type(); + t_rr_type rr_type = device_ctx.rr_graph.node_type(inode); if (rr_type != SOURCE) { VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "in check_source: net %d begins with a node of type %d.\n", size_t(net_id), rr_type); } - int i = device_ctx.rr_nodes[inode].xlow(); - int j = device_ctx.rr_nodes[inode].ylow(); + int i = device_ctx.rr_graph.node_xlow(inode); + int j = device_ctx.rr_graph.node_ylow(inode); /* for sinks and sources, ptc_num is class */ - int ptc_num = device_ctx.rr_nodes[inode].ptc_num(); + int ptc_num = device_ctx.rr_graph.node_ptc_num(inode); /* First node_block for net is the source */ ClusterBlockId blk_id = cluster_ctx.clb_nlist.net_driver_block(net_id); auto type = device_ctx.grid[i][j].type; @@ -259,7 +260,7 @@ static void check_switch(t_trace* tptr, int num_switch) { /* Checks that the switch leading from this traceback element to the next * * one is a legal switch type. */ - int inode; + RRNodeId inode; short switch_type; auto& device_ctx = g_vpr_ctx.device(); @@ -267,12 +268,12 @@ static void check_switch(t_trace* tptr, int num_switch) { inode = tptr->index; switch_type = tptr->iswitch; - if (device_ctx.rr_nodes[inode].type() != SINK) { + if (device_ctx.rr_graph.node_type(inode) != SINK) { if (switch_type >= num_switch) { VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "in check_switch: rr_node %d left via switch type %d.\n" "\tSwitch type is out of range.\n", - inode, switch_type); + size_t(inode), switch_type); } } @@ -283,19 +284,19 @@ static void check_switch(t_trace* tptr, int num_switch) { if (switch_type != OPEN) { VPR_FATAL_ERROR(VPR_ERROR_ROUTE, - "in check_switch: rr_node %d is a SINK, but attempts to use a switch of type %d.\n", inode, switch_type); + "in check_switch: rr_node %d is a SINK, but attempts to use a switch of type %d.\n", size_t(inode), switch_type); } } } -static void reset_flags(ClusterNetId inet, bool* connected_to_route) { +static void reset_flags(ClusterNetId inet, vtr::vector& connected_to_route) { /* This routine resets the flags of all the channel segments contained * * in the traceback of net inet to 0. This allows us to check the * * next net for connectivity (and the default state of the flags * * should always be zero after they have been used). */ t_trace* tptr; - int inode; + RRNodeId inode; auto& route_ctx = g_vpr_ctx.routing(); @@ -308,7 +309,7 @@ static void reset_flags(ClusterNetId inet, bool* connected_to_route) { } } -static bool check_adjacent(int from_node, int to_node) { +static bool check_adjacent(const RRNodeId& from_node, const RRNodeId& to_node) { /* This routine checks if the rr_node to_node is reachable from from_node. * * It returns true if is reachable and false if it is not. Check_node has * * already been used to verify that both nodes are valid rr_nodes, so only * @@ -327,8 +328,8 @@ static bool check_adjacent(int from_node, int to_node) { reached = false; - for (t_edge_size iconn = 0; iconn < device_ctx.rr_nodes[from_node].num_edges(); iconn++) { - if (device_ctx.rr_nodes[from_node].edge_sink_node(iconn) == to_node) { + for (const RREdgeId& iconn : device_ctx.rr_graph.node_out_edges(from_node)) { + if (device_ctx.rr_graph.edge_sink_node(iconn) == to_node) { reached = true; break; } @@ -343,18 +344,18 @@ static bool check_adjacent(int from_node, int to_node) { num_adj = 0; - from_type = device_ctx.rr_nodes[from_node].type(); - from_xlow = device_ctx.rr_nodes[from_node].xlow(); - from_ylow = device_ctx.rr_nodes[from_node].ylow(); - from_xhigh = device_ctx.rr_nodes[from_node].xhigh(); - from_yhigh = device_ctx.rr_nodes[from_node].yhigh(); - from_ptc = device_ctx.rr_nodes[from_node].ptc_num(); - to_type = device_ctx.rr_nodes[to_node].type(); - to_xlow = device_ctx.rr_nodes[to_node].xlow(); - to_ylow = device_ctx.rr_nodes[to_node].ylow(); - to_xhigh = device_ctx.rr_nodes[to_node].xhigh(); - to_yhigh = device_ctx.rr_nodes[to_node].yhigh(); - to_ptc = device_ctx.rr_nodes[to_node].ptc_num(); + from_type = device_ctx.rr_graph.node_type(from_node); + from_xlow = device_ctx.rr_graph.node_xlow(from_node); + from_ylow = device_ctx.rr_graph.node_ylow(from_node); + from_xhigh = device_ctx.rr_graph.node_xhigh(from_node); + from_yhigh = device_ctx.rr_graph.node_yhigh(from_node); + from_ptc = device_ctx.rr_graph.node_ptc_num(from_node); + to_type = device_ctx.rr_graph.node_type(to_node); + to_xlow = device_ctx.rr_graph.node_xlow(to_node); + to_ylow = device_ctx.rr_graph.node_ylow(to_node); + to_xhigh = device_ctx.rr_graph.node_xhigh(to_node); + to_yhigh = device_ctx.rr_graph.node_yhigh(to_node); + to_ptc = device_ctx.rr_graph.node_ptc_num(to_node); switch (from_type) { case SOURCE: @@ -411,8 +412,8 @@ static bool check_adjacent(int from_node, int to_node) { if (to_type == IPIN) { num_adj += 1; //adjacent } else if (to_type == CHANX) { - from_xhigh = device_ctx.rr_nodes[from_node].xhigh(); - to_xhigh = device_ctx.rr_nodes[to_node].xhigh(); + from_xhigh = device_ctx.rr_graph.node_xhigh(from_node); + to_xhigh = device_ctx.rr_graph.node_xhigh(to_node); if (from_ylow == to_ylow) { /* UDSD Modification by WMF Begin */ /*For Fs > 3, can connect to overlapping wire segment */ @@ -436,7 +437,7 @@ static bool check_adjacent(int from_node, int to_node) { num_adj += chanx_chany_adjacent(from_node, to_node); } else { VPR_FATAL_ERROR(VPR_ERROR_ROUTE, - "in check_adjacent: %d and %d are not adjacent", from_node, to_node); + "in check_adjacent: %d and %d are not adjacent", size_t(from_node), size_t(to_node)); } break; @@ -444,8 +445,8 @@ static bool check_adjacent(int from_node, int to_node) { if (to_type == IPIN) { num_adj += 1; //adjacent } else if (to_type == CHANY) { - from_yhigh = device_ctx.rr_nodes[from_node].yhigh(); - to_yhigh = device_ctx.rr_nodes[to_node].yhigh(); + from_yhigh = device_ctx.rr_graph.node_yhigh(from_node); + to_yhigh = device_ctx.rr_graph.node_yhigh(to_node); if (from_xlow == to_xlow) { /* UDSD Modification by WMF Begin */ if (to_yhigh == from_ylow - 1 || from_yhigh == to_ylow - 1) { @@ -468,7 +469,7 @@ static bool check_adjacent(int from_node, int to_node) { num_adj += chanx_chany_adjacent(to_node, from_node); } else { VPR_FATAL_ERROR(VPR_ERROR_ROUTE, - "in check_adjacent: %d and %d are not adjacent", from_node, to_node); + "in check_adjacent: %d and %d are not adjacent", size_t(from_node), size_t(to_node)); } break; @@ -486,7 +487,7 @@ static bool check_adjacent(int from_node, int to_node) { return false; //Should not reach here once thrown } -static int chanx_chany_adjacent(int chanx_node, int chany_node) { +static int chanx_chany_adjacent(const RRNodeId& chanx_node, const RRNodeId& chany_node) { /* Returns 1 if the specified CHANX and CHANY nodes are adjacent, 0 * * otherwise. */ @@ -495,13 +496,13 @@ static int chanx_chany_adjacent(int chanx_node, int chany_node) { auto& device_ctx = g_vpr_ctx.device(); - chanx_y = device_ctx.rr_nodes[chanx_node].ylow(); - chanx_xlow = device_ctx.rr_nodes[chanx_node].xlow(); - chanx_xhigh = device_ctx.rr_nodes[chanx_node].xhigh(); + chanx_y = device_ctx.rr_graph.node_ylow(chanx_node); + chanx_xlow = device_ctx.rr_graph.node_xlow(chanx_node); + chanx_xhigh = device_ctx.rr_graph.node_xhigh(chanx_node); - chany_x = device_ctx.rr_nodes[chany_node].xlow(); - chany_ylow = device_ctx.rr_nodes[chany_node].ylow(); - chany_yhigh = device_ctx.rr_nodes[chany_node].yhigh(); + chany_x = device_ctx.rr_graph.node_xlow(chany_node); + chany_ylow = device_ctx.rr_graph.node_ylow(chany_node); + chany_yhigh = device_ctx.rr_graph.node_yhigh(chany_node); if (chany_ylow > chanx_y + 1 || chany_yhigh < chanx_y) return (0); @@ -519,7 +520,8 @@ void recompute_occupancy_from_scratch() { * brute force recompute from scratch that is useful for sanity checking. */ - int inode, iclass, ipin, num_local_opins; + int iclass, ipin, num_local_opins; + RRNodeId inode; t_trace* tptr; auto& route_ctx = g_vpr_ctx.mutable_routing(); @@ -528,8 +530,9 @@ void recompute_occupancy_from_scratch() { /* First set the occupancy of everything to zero. */ - for (size_t inode_idx = 0; inode_idx < device_ctx.rr_nodes.size(); inode_idx++) + for (const RRNodeId& inode_idx : device_ctx.rr_graph.nodes()) { route_ctx.rr_node_route_inf[inode_idx].set_occ(0); + } /* Now go through each net and count the tracks and pins used everywhere */ @@ -575,7 +578,8 @@ static void check_locally_used_clb_opins(const t_clb_opins_used& clb_opins_used_ /* Checks that enough OPINs on CLBs have been set aside (used up) to make a * * legal routing if subblocks connect to OPINs directly. */ - int iclass, num_local_opins, inode, ipin; + int iclass, num_local_opins, ipin; + RRNodeId inode; t_rr_type rr_type; auto& cluster_ctx = g_vpr_ctx.clustering(); @@ -592,35 +596,35 @@ static void check_locally_used_clb_opins(const t_clb_opins_used& clb_opins_used_ /* Now check that node is an OPIN of the right type. */ - rr_type = device_ctx.rr_nodes[inode].type(); + rr_type = device_ctx.rr_graph.node_type(inode); if (rr_type != OPIN) { VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "in check_locally_used_opins: block #%lu (%s)\n" "\tClass %d local OPIN is wrong rr_type -- rr_node #%d of type %d.\n", - size_t(blk_id), cluster_ctx.clb_nlist.block_name(blk_id).c_str(), iclass, inode, rr_type); + size_t(blk_id), cluster_ctx.clb_nlist.block_name(blk_id).c_str(), iclass, size_t(inode), rr_type); } - ipin = device_ctx.rr_nodes[inode].ptc_num(); + ipin = device_ctx.rr_graph.node_ptc_num(inode); if (physical_tile_type(blk_id)->pin_class[ipin] != iclass) { VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "in check_locally_used_opins: block #%lu (%s):\n" "\tExpected class %d local OPIN has class %d -- rr_node #: %d.\n", - size_t(blk_id), cluster_ctx.clb_nlist.block_name(blk_id).c_str(), iclass, physical_tile_type(blk_id)->pin_class[ipin], inode); + size_t(blk_id), cluster_ctx.clb_nlist.block_name(blk_id).c_str(), iclass, physical_tile_type(blk_id)->pin_class[ipin], size_t(inode)); } } } } } -static void check_node_and_range(int inode, enum e_route_type route_type) { +static void check_node_and_range(const RRNodeId& inode, enum e_route_type route_type) { /* Checks that inode is within the legal range, then calls check_node to * * check that everything else about the node is OK. */ auto& device_ctx = g_vpr_ctx.device(); - if (inode < 0 || inode >= (int)device_ctx.rr_nodes.size()) { + if (false == device_ctx.rr_graph.valid_node_id(inode)) { VPR_FATAL_ERROR(VPR_ERROR_ROUTE, - "in check_node_and_range: rr_node #%d is out of legal, range (0 to %d).\n", inode, device_ctx.rr_nodes.size() - 1); + "in check_node_and_range: rr_node #%d is out of legal, range (0 to %d).\n", size_t(inode), device_ctx.rr_graph.nodes().size() - 1); } check_rr_node(inode, route_type, device_ctx); } @@ -637,16 +641,16 @@ static bool check_non_configurable_edges(ClusterNetId net, const t_non_configura //Collect all the edges used by this net's routing std::set routing_edges; - std::set routing_nodes; + std::set routing_nodes; for (t_trace* trace = head; trace != nullptr; trace = trace->next) { - int inode = trace->index; + RRNodeId inode = trace->index; routing_nodes.insert(inode); if (trace->iswitch == OPEN) { continue; //End of branch } else if (trace->next) { - int inode_next = trace->next->index; + RRNodeId inode_next = trace->next->index; t_node_edge edge = {inode, inode_next}; @@ -668,7 +672,7 @@ static bool check_non_configurable_edges(ClusterNetId net, const t_non_configura //within a set is used by the routing for (const auto& rr_nodes : non_configurable_rr_sets.node_sets) { //Compute the intersection of the routing and current non-configurable nodes set - std::vector intersection; + std::vector intersection; std::set_intersection(routing_nodes.begin(), routing_nodes.end(), rr_nodes.begin(), rr_nodes.end(), std::back_inserter(intersection)); @@ -683,7 +687,7 @@ static bool check_non_configurable_edges(ClusterNetId net, const t_non_configura //Compute the difference to identify the missing nodes //for detailed error reporting -- the nodes //which are in rr_nodes but not in routing_nodes. - std::vector difference; + std::vector difference; std::set_difference(rr_nodes.begin(), rr_nodes.end(), routing_nodes.begin(), routing_nodes.end(), std::back_inserter(difference)); @@ -762,7 +766,7 @@ static bool check_non_configurable_edges(ClusterNetId net, const t_non_configura for (t_node_edge missing_edge : dedupped_difference) { msg += vtr::string_fmt(" Expected RR Node: %d and RR Node: %d to be non-configurably connected, but edge missing from routing:\n", - missing_edge.from_node, missing_edge.to_node); + size_t(missing_edge.from_node), size_t(missing_edge.to_node)); msg += vtr::string_fmt(" %s\n", describe_rr_node(missing_edge.from_node).c_str()); msg += vtr::string_fmt(" %s\n", describe_rr_node(missing_edge.to_node).c_str()); } @@ -786,7 +790,7 @@ class StubFinder { bool CheckNet(ClusterNetId net); // Returns set of stub nodes. - const std::set& stub_nodes() { + const std::set& stub_nodes() { return stub_nodes_; } @@ -796,7 +800,7 @@ class StubFinder { // Set of stub nodes // Note this is an ordered set so that node output is sorted by node // id. - std::set stub_nodes_; + std::set stub_nodes_; }; //Cheks for stubs in a net's routing. @@ -813,7 +817,7 @@ void check_net_for_stubs(ClusterNetId net) { auto& cluster_ctx = g_vpr_ctx.clustering(); std::string msg = vtr::string_fmt("Route tree for net '%s' (#%zu) contains stub branches rooted at:\n", cluster_ctx.clb_nlist.net_name(net).c_str(), size_t(net)); - for (int inode : stub_finder.stub_nodes()) { + for (const RRNodeId& inode : stub_finder.stub_nodes()) { msg += vtr::string_fmt(" %s\n", describe_rr_node(inode).c_str()); } @@ -837,7 +841,7 @@ bool StubFinder::RecurseTree(t_rt_node* rt_root) { if (rt_root->u.child_list == nullptr) { //If a leaf of the route tree is not a SINK, then it is a stub - if (device_ctx.rr_nodes[rt_root->inode].type() != SINK) { + if (device_ctx.rr_graph.node_type(rt_root->inode) != SINK) { return true; //It is the current root of this stub } else { return false; diff --git a/vpr/src/route/check_rr_graph.cpp b/vpr/src/route/check_rr_graph.cpp index 666099176..388915b01 100644 --- a/vpr/src/route/check_rr_graph.cpp +++ b/vpr/src/route/check_rr_graph.cpp @@ -14,7 +14,7 @@ static bool rr_node_is_global_clb_ipin(int inode); static void check_unbuffered_edges(int from_node); -static bool has_adjacent_channel(const t_rr_node& node, const DeviceGrid& grid); +static bool has_adjacent_channel(const RRGraph& rr_graph, const RRNodeId& node, const DeviceGrid& grid); static void check_rr_edge(int from_node, int from_edge, int to_node); @@ -220,7 +220,7 @@ static bool rr_node_is_global_clb_ipin(int inode) { return type->is_ignored_pin[ipin]; } -void check_rr_node(int inode, enum e_route_type route_type, const DeviceContext& device_ctx) { +void check_rr_node(const RRNodeId& inode, enum e_route_type route_type, const DeviceContext& device_ctx) { /* This routine checks that the rr_node is inside the grid and has a valid * pin number, etc. */ @@ -231,14 +231,14 @@ void check_rr_node(int inode, enum e_route_type route_type, const DeviceContext& int nodes_per_chan, tracks_per_node, num_edges, cost_index; float C, R; - rr_type = device_ctx.rr_nodes[inode].type(); - xlow = device_ctx.rr_nodes[inode].xlow(); - xhigh = device_ctx.rr_nodes[inode].xhigh(); - ylow = device_ctx.rr_nodes[inode].ylow(); - yhigh = device_ctx.rr_nodes[inode].yhigh(); - ptc_num = device_ctx.rr_nodes[inode].ptc_num(); - capacity = device_ctx.rr_nodes[inode].capacity(); - cost_index = device_ctx.rr_nodes[inode].cost_index(); + rr_type = device_ctx.rr_graph.node_type(inode); + xlow = device_ctx.rr_graph.node_xlow(inode); + xhigh = device_ctx.rr_graph.node_xhigh(inode); + ylow = device_ctx.rr_graph.node_ylow(inode); + yhigh = device_ctx.rr_graph.node_yhigh(inode); + ptc_num = device_ctx.rr_graph.node_ptc_num(inode); + capacity = device_ctx.rr_graph.node_capacity(inode); + cost_index = device_ctx.rr_graph.node_cost_index(inode); type = nullptr; const auto& grid = device_ctx.grid; @@ -413,7 +413,7 @@ void check_rr_node(int inode, enum e_route_type route_type, const DeviceContext& } /* Check that the number of (out) edges is reasonable. */ - num_edges = device_ctx.rr_nodes[inode].num_edges(); + num_edges = device_ctx.rr_graph.node_out_edges(inode).size(); if (rr_type != SINK && rr_type != IPIN) { if (num_edges <= 0) { @@ -424,7 +424,7 @@ void check_rr_node(int inode, enum e_route_type route_type, const DeviceContext& //Don't worry about disconnect PINs which have no adjacent channels (i.e. on the device perimeter) bool check_for_out_edges = true; if (rr_type == IPIN || rr_type == OPIN) { - if (!has_adjacent_channel(device_ctx.rr_nodes[inode], device_ctx.grid)) { + if (!has_adjacent_channel(device_ctx.rr_graph, inode, device_ctx.grid)) { check_for_out_edges = false; } } @@ -437,23 +437,23 @@ void check_rr_node(int inode, enum e_route_type route_type, const DeviceContext& } else if (rr_type == SINK) { /* SINK -- remove this check if feedthroughs allowed */ if (num_edges != 0) { VPR_FATAL_ERROR(VPR_ERROR_ROUTE, - "in check_rr_node: node %d is a sink, but has %d edges.\n", inode, num_edges); + "in check_rr_node: node %d is a sink, but has %d edges.\n", size_t(inode), num_edges); } } /* Check that the capacitance and resistance are reasonable. */ - C = device_ctx.rr_nodes[inode].C(); - R = device_ctx.rr_nodes[inode].R(); + C = device_ctx.rr_graph.node_C(inode); + R = device_ctx.rr_graph.node_R(inode); if (rr_type == CHANX || rr_type == CHANY) { if (C < 0. || R < 0.) { VPR_ERROR(VPR_ERROR_ROUTE, - "in check_rr_node: node %d of type %d has R = %g and C = %g.\n", inode, rr_type, R, C); + "in check_rr_node: node %d of type %d has R = %g and C = %g.\n", size_t(inode), rr_type, R, C); } } else { if (C != 0. || R != 0.) { VPR_ERROR(VPR_ERROR_ROUTE, - "in check_rr_node: node %d of type %d has R = %g and C = %g.\n", inode, rr_type, R, C); + "in check_rr_node: node %d of type %d has R = %g and C = %g.\n", size_t(inode), rr_type, R, C); } } } @@ -513,13 +513,13 @@ static void check_unbuffered_edges(int from_node) { } /* End for all from_node edges */ } -static bool has_adjacent_channel(const t_rr_node& node, const DeviceGrid& grid) { - VTR_ASSERT(node.type() == IPIN || node.type() == OPIN); +static bool has_adjacent_channel(const RRGraph& rr_graph, const RRNodeId& node, const DeviceGrid& grid) { + VTR_ASSERT(rr_graph.node_type(node) == IPIN || rr_graph.node_type(node) == OPIN); - if ((node.xlow() == 0 && node.side() != RIGHT) //left device edge connects only along block's right side - || (node.ylow() == int(grid.height() - 1) && node.side() != BOTTOM) //top device edge connects only along block's bottom side - || (node.xlow() == int(grid.width() - 1) && node.side() != LEFT) //right deivce edge connects only along block's left side - || (node.ylow() == 0 && node.side() != TOP) //bottom deivce edge connects only along block's top side + if ((rr_graph.node_xlow(node) == 0 && rr_graph.node_side(node) != RIGHT) //left device edge connects only along block's right side + || (rr_graph.node_ylow(node) == int(grid.height() - 1) && rr_graph.node_side(node) != BOTTOM) //top device edge connects only along block's bottom side + || (rr_graph.node_xlow(node) == int(grid.width() - 1) && rr_graph.node_side(node) != LEFT) //right deivce edge connects only along block's left side + || (rr_graph.node_ylow(node) == 0 && rr_graph.node_side(node) != TOP) //bottom deivce edge connects only along block's top side ) { return false; } diff --git a/vpr/src/route/check_rr_graph.h b/vpr/src/route/check_rr_graph.h index 5e7d69d2c..71d435e70 100644 --- a/vpr/src/route/check_rr_graph.h +++ b/vpr/src/route/check_rr_graph.h @@ -6,6 +6,6 @@ void check_rr_graph(const t_graph_type graph_type, const DeviceGrid& grid, const std::vector& types); -void check_rr_node(int inode, enum e_route_type route_type, const DeviceContext& device_ctx); +void check_rr_node(const RRNodeId& inode, enum e_route_type route_type, const DeviceContext& device_ctx); #endif From 3536cc8ed54d99c98159c189d07545b3cd235294 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 1 Feb 2020 14:17:02 -0700 Subject: [PATCH 071/645] check rr graph adopt RRGraph object --- vpr/src/route/check_rr_graph.cpp | 138 +++++++++++++++---------------- 1 file changed, 65 insertions(+), 73 deletions(-) diff --git a/vpr/src/route/check_rr_graph.cpp b/vpr/src/route/check_rr_graph.cpp index 388915b01..e7213a764 100644 --- a/vpr/src/route/check_rr_graph.cpp +++ b/vpr/src/route/check_rr_graph.cpp @@ -10,13 +10,13 @@ /*********************** Subroutines local to this module *******************/ -static bool rr_node_is_global_clb_ipin(int inode); +static bool rr_node_is_global_clb_ipin(const RRNodeId& inode); -static void check_unbuffered_edges(int from_node); +static void check_unbuffered_edges(const RRNodeId& from_node); static bool has_adjacent_channel(const RRGraph& rr_graph, const RRNodeId& node, const DeviceGrid& grid); -static void check_rr_edge(int from_node, int from_edge, int to_node); +static void check_rr_edge(const RREdgeId& from_edge, const RRNodeId& to_node); /************************ Subroutine definitions ****************************/ @@ -30,69 +30,67 @@ void check_rr_graph(const t_graph_type graph_type, auto& device_ctx = g_vpr_ctx.device(); - auto total_edges_to_node = std::vector(device_ctx.rr_nodes.size()); - auto switch_types_from_current_to_node = std::vector(device_ctx.rr_nodes.size()); + auto total_edges_to_node = vtr::vector(device_ctx.rr_graph.nodes().size()); + auto switch_types_from_current_to_node = vtr::vector(device_ctx.rr_graph.nodes().size()); const int num_rr_switches = device_ctx.rr_switch_inf.size(); - for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); inode++) { - device_ctx.rr_nodes[inode].validate(); + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { /* Ignore any uninitialized rr_graph nodes */ - if ((device_ctx.rr_nodes[inode].type() == SOURCE) - && (device_ctx.rr_nodes[inode].xlow() == 0) && (device_ctx.rr_nodes[inode].ylow() == 0) - && (device_ctx.rr_nodes[inode].xhigh() == 0) && (device_ctx.rr_nodes[inode].yhigh() == 0)) { + if ((device_ctx.rr_graph.node_type(inode) == SOURCE) + && (device_ctx.rr_graph.node_xlow(inode) == 0) && (device_ctx.rr_graph.node_ylow(inode) == 0) + && (device_ctx.rr_graph.node_xhigh(inode) == 0) && (device_ctx.rr_graph.node_yhigh(inode) == 0)) { continue; } - t_rr_type rr_type = device_ctx.rr_nodes[inode].type(); - int num_edges = device_ctx.rr_nodes[inode].num_edges(); + t_rr_type rr_type = device_ctx.rr_graph.node_type(inode); check_rr_node(inode, route_type, device_ctx); /* Check all the connectivity (edges, etc.) information. */ - std::map> edges_from_current_to_node; - for (int iedge = 0; iedge < num_edges; iedge++) { - int to_node = device_ctx.rr_nodes[inode].edge_sink_node(iedge); + std::map> edges_from_current_to_node; + for (const RREdgeId& iedge : device_ctx.rr_graph.node_out_edges(inode)) { + RRNodeId to_node = device_ctx.rr_graph.edge_sink_node(iedge); - if (to_node < 0 || to_node >= (int)device_ctx.rr_nodes.size()) { + if (false == device_ctx.rr_graph.valid_node_id(to_node)) { VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "in check_rr_graph: node %d has an edge %d.\n" "\tEdge is out of range.\n", - inode, to_node); + size_t(inode), size_t(to_node)); } - check_rr_edge(inode, iedge, to_node); + check_rr_edge(iedge, to_node); edges_from_current_to_node[to_node].push_back(iedge); total_edges_to_node[to_node]++; - auto switch_type = device_ctx.rr_nodes[inode].edge_switch(iedge); + auto switch_type = size_t(device_ctx.rr_graph.edge_switch(iedge)); - if (switch_type < 0 || switch_type >= num_rr_switches) { + if (switch_type >= (size_t)num_rr_switches) { VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "in check_rr_graph: node %d has a switch type %d.\n" "\tSwitch type is out of range.\n", - inode, switch_type); + size_t(inode), switch_type); } } /* End for all edges of node. */ //Check that multiple edges between the same from/to nodes make sense - for (int iedge = 0; iedge < num_edges; iedge++) { - int to_node = device_ctx.rr_nodes[inode].edge_sink_node(iedge); + for (const RREdgeId& iedge : device_ctx.rr_graph.node_out_edges(inode)) { + RRNodeId to_node = device_ctx.rr_graph.edge_sink_node(iedge); if (edges_from_current_to_node[to_node].size() == 1) continue; //Single edges are always OK VTR_ASSERT_MSG(edges_from_current_to_node[to_node].size() > 1, "Expect multiple edges"); - t_rr_type to_rr_type = device_ctx.rr_nodes[to_node].type(); + t_rr_type to_rr_type = device_ctx.rr_graph.node_type(to_node); //Only expect chan <-> chan connections to have multiple edges if ((to_rr_type != CHANX && to_rr_type != CHANY) || (rr_type != CHANX && rr_type != CHANY)) { VPR_ERROR(VPR_ERROR_ROUTE, "in check_rr_graph: node %d (%s) connects to node %d (%s) %zu times - multi-connections only expected for CHAN->CHAN.\n", - inode, rr_node_typename[rr_type], to_node, rr_node_typename[to_rr_type], edges_from_current_to_node[to_node].size()); + size_t(inode), rr_node_typename[rr_type], size_t(to_node), rr_node_typename[to_rr_type], edges_from_current_to_node[to_node].size()); } //Between two wire segments @@ -105,7 +103,7 @@ void check_rr_graph(const t_graph_type graph_type, //Identify any such edges with identical switches std::map switch_counts; for (auto edge : edges_from_current_to_node[to_node]) { - auto edge_switch = device_ctx.rr_nodes[inode].edge_switch(edge); + auto edge_switch = size_t(device_ctx.rr_graph.edge_switch(edge)); switch_counts[edge_switch]++; } @@ -117,7 +115,7 @@ void check_rr_graph(const t_graph_type graph_type, auto switch_type = device_ctx.rr_switch_inf[kv.first].type(); VPR_ERROR(VPR_ERROR_ROUTE, "in check_rr_graph: node %d has %d redundant connections to node %d of switch type %d (%s)", - inode, kv.second, to_node, kv.first, SWITCH_TYPE_STRINGS[size_t(switch_type)]); + size_t(inode), kv.second, size_t(to_node), kv.first, SWITCH_TYPE_STRINGS[size_t(switch_type)]); } } @@ -125,17 +123,17 @@ void check_rr_graph(const t_graph_type graph_type, check_unbuffered_edges(inode); //Check that all config/non-config edges are appropriately organized - for (auto edge : device_ctx.rr_nodes[inode].configurable_edges()) { - if (!device_ctx.rr_nodes[inode].edge_is_configurable(edge)) { + for (auto edge : device_ctx.rr_graph.node_configurable_out_edges(inode)) { + if (!device_ctx.rr_graph.edge_is_configurable(edge)) { VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "in check_rr_graph: node %d edge %d is non-configurable, but in configurable edges", - inode, edge); + size_t(inode), size_t(edge)); } } - for (auto edge : device_ctx.rr_nodes[inode].non_configurable_edges()) { - if (device_ctx.rr_nodes[inode].edge_is_configurable(edge)) { + for (auto edge : device_ctx.rr_graph.node_non_configurable_out_edges(inode)) { + if (device_ctx.rr_graph.edge_is_configurable(edge)) { VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "in check_rr_graph: node %d edge %d is configurable, but in non-configurable edges", - inode, edge); + size_t(inode), size_t(edge)); } } @@ -145,8 +143,8 @@ void check_rr_graph(const t_graph_type graph_type, * now I check that everything is reachable. */ bool is_fringe_warning_sent = false; - for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); inode++) { - t_rr_type rr_type = device_ctx.rr_nodes[inode].type(); + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { + t_rr_type rr_type = device_ctx.rr_graph.node_type(inode); if (rr_type != SOURCE) { if (total_edges_to_node[inode] < 1 && !rr_node_is_global_clb_ipin(inode)) { @@ -156,7 +154,7 @@ void check_rr_graph(const t_graph_type graph_type, */ bool is_chain = false; if (rr_type == IPIN) { - t_physical_tile_type_ptr type = device_ctx.grid[device_ctx.rr_nodes[inode].xlow()][device_ctx.rr_nodes[inode].ylow()].type; + t_physical_tile_type_ptr type = device_ctx.grid[device_ctx.rr_graph.node_xlow(inode)][device_ctx.rr_graph.node_ylow(inode)].type; for (const t_fc_specification& fc_spec : types[type->index].fc_specs) { if (fc_spec.fc_value == 0 && fc_spec.seg_index == 0) { is_chain = true; @@ -164,45 +162,43 @@ void check_rr_graph(const t_graph_type graph_type, } } - const auto& node = device_ctx.rr_nodes[inode]; - - bool is_fringe = ((device_ctx.rr_nodes[inode].xlow() == 1) - || (device_ctx.rr_nodes[inode].ylow() == 1) - || (device_ctx.rr_nodes[inode].xhigh() == int(grid.width()) - 2) - || (device_ctx.rr_nodes[inode].yhigh() == int(grid.height()) - 2)); - bool is_wire = (device_ctx.rr_nodes[inode].type() == CHANX - || device_ctx.rr_nodes[inode].type() == CHANY); + bool is_fringe = ((device_ctx.rr_graph.node_xlow(inode) == 1) + || (device_ctx.rr_graph.node_ylow(inode) == 1) + || (device_ctx.rr_graph.node_xhigh(inode) == int(grid.width()) - 2) + || (device_ctx.rr_graph.node_yhigh(inode) == int(grid.height()) - 2)); + bool is_wire = (device_ctx.rr_graph.node_type(inode) == CHANX + || device_ctx.rr_graph.node_type(inode) == CHANY); if (!is_chain && !is_fringe && !is_wire) { - if (node.type() == IPIN || node.type() == OPIN) { - if (has_adjacent_channel(node, device_ctx.grid)) { - auto block_type = device_ctx.grid[node.xlow()][node.ylow()].type; - std::string pin_name = block_type_pin_index_to_name(block_type, node.pin_num()); + if (device_ctx.rr_graph.node_type(inode) == IPIN || device_ctx.rr_graph.node_type(inode) == OPIN) { + if (has_adjacent_channel(device_ctx.rr_graph, inode, device_ctx.grid)) { + auto block_type = device_ctx.grid[device_ctx.rr_graph.node_xlow(inode)][device_ctx.rr_graph.node_ylow(inode)].type; + std::string pin_name = block_type_pin_index_to_name(block_type, device_ctx.rr_graph.node_pin_num(inode)); VTR_LOG_ERROR("in check_rr_graph: node %d (%s) at (%d,%d) block=%s side=%s pin=%s has no fanin.\n", - inode, node.type_string(), node.xlow(), node.ylow(), block_type->name, node.side_string(), pin_name.c_str()); + size_t(inode), rr_node_typename[device_ctx.rr_graph.node_type(inode)], device_ctx.rr_graph.node_xlow(inode), device_ctx.rr_graph.node_ylow(inode), block_type->name, SIDE_STRING[device_ctx.rr_graph.node_side(inode)], pin_name.c_str()); } } else { VTR_LOG_ERROR("in check_rr_graph: node %d (%s) has no fanin.\n", - inode, device_ctx.rr_nodes[inode].type_string()); + size_t(inode), rr_node_typename[device_ctx.rr_graph.node_type(inode)]); } } else if (!is_chain && !is_fringe_warning_sent) { VTR_LOG_WARN( "in check_rr_graph: fringe node %d %s at (%d,%d) has no fanin.\n" "\t This is possible on a fringe node based on low Fc_out, N, and certain lengths.\n", - inode, device_ctx.rr_nodes[inode].type_string(), device_ctx.rr_nodes[inode].xlow(), device_ctx.rr_nodes[inode].ylow()); + size_t(inode), rr_node_typename[device_ctx.rr_graph.node_type(inode)], device_ctx.rr_graph.node_xlow(inode), device_ctx.rr_graph.node_ylow(inode)); is_fringe_warning_sent = true; } } } else { /* SOURCE. No fanin for now; change if feedthroughs allowed. */ if (total_edges_to_node[inode] != 0) { VTR_LOG_ERROR("in check_rr_graph: SOURCE node %d has a fanin of %d, expected 0.\n", - inode, total_edges_to_node[inode]); + size_t(inode), total_edges_to_node[inode]); } } } } -static bool rr_node_is_global_clb_ipin(int inode) { +static bool rr_node_is_global_clb_ipin(const RRNodeId& inode) { /* Returns true if inode refers to a global CLB input pin node. */ int ipin; @@ -210,12 +206,12 @@ static bool rr_node_is_global_clb_ipin(int inode) { auto& device_ctx = g_vpr_ctx.device(); - type = device_ctx.grid[device_ctx.rr_nodes[inode].xlow()][device_ctx.rr_nodes[inode].ylow()].type; + type = device_ctx.grid[device_ctx.rr_graph.node_xlow(inode)][device_ctx.rr_graph.node_ylow(inode)].type; - if (device_ctx.rr_nodes[inode].type() != IPIN) + if (device_ctx.rr_graph.node_type(inode) != IPIN) return (false); - ipin = device_ctx.rr_nodes[inode].ptc_num(); + ipin = device_ctx.rr_graph.node_ptc_num(inode); return type->is_ignored_pin[ipin]; } @@ -458,31 +454,28 @@ void check_rr_node(const RRNodeId& inode, enum e_route_type route_type, const De } } -static void check_unbuffered_edges(int from_node) { +static void check_unbuffered_edges(const RRNodeId& from_node) { /* This routine checks that all pass transistors in the routing truly are * * bidirectional. It may be a slow check, so don't use it all the time. */ - int from_edge, to_node, to_edge, from_num_edges, to_num_edges; t_rr_type from_rr_type, to_rr_type; short from_switch_type; bool trans_matched; auto& device_ctx = g_vpr_ctx.device(); - from_rr_type = device_ctx.rr_nodes[from_node].type(); + from_rr_type = device_ctx.rr_graph.node_type(from_node); if (from_rr_type != CHANX && from_rr_type != CHANY) return; - from_num_edges = device_ctx.rr_nodes[from_node].num_edges(); - - for (from_edge = 0; from_edge < from_num_edges; from_edge++) { - to_node = device_ctx.rr_nodes[from_node].edge_sink_node(from_edge); - to_rr_type = device_ctx.rr_nodes[to_node].type(); + for (const RREdgeId& from_edge : device_ctx.rr_graph.node_out_edges(from_node)) { + RRNodeId to_node = device_ctx.rr_graph.edge_sink_node(from_edge); + to_rr_type = device_ctx.rr_graph.node_type(to_node); if (to_rr_type != CHANX && to_rr_type != CHANY) continue; - from_switch_type = device_ctx.rr_nodes[from_node].edge_switch(from_edge); + from_switch_type = size_t(device_ctx.rr_graph.edge_switch(from_edge)); if (device_ctx.rr_switch_inf[from_switch_type].buffered()) continue; @@ -491,12 +484,11 @@ static void check_unbuffered_edges(int from_node) { * check that there is a corresponding edge from to_node back to * * from_node. */ - to_num_edges = device_ctx.rr_nodes[to_node].num_edges(); trans_matched = false; - for (to_edge = 0; to_edge < to_num_edges; to_edge++) { - if (device_ctx.rr_nodes[to_node].edge_sink_node(to_edge) == from_node - && device_ctx.rr_nodes[to_node].edge_switch(to_edge) == from_switch_type) { + for (const RREdgeId& to_edge : device_ctx.rr_graph.node_out_edges(to_node)) { + if (device_ctx.rr_graph.edge_sink_node(to_edge) == from_node + && (short)size_t(device_ctx.rr_graph.edge_switch(to_edge)) == from_switch_type) { trans_matched = true; break; } @@ -507,7 +499,7 @@ static void check_unbuffered_edges(int from_node) { "in check_unbuffered_edges:\n" "connection from node %d to node %d uses an unbuffered switch (switch type %d '%s')\n" "but there is no corresponding unbuffered switch edge in the other direction.\n", - from_node, to_node, from_switch_type, device_ctx.rr_switch_inf[from_switch_type].name); + size_t(from_node), size_t(to_node), from_switch_type, device_ctx.rr_switch_inf[from_switch_type].name); } } /* End for all from_node edges */ @@ -526,14 +518,14 @@ static bool has_adjacent_channel(const RRGraph& rr_graph, const RRNodeId& node, return true; //All other blocks will be surrounded on all sides by channels } -static void check_rr_edge(int from_node, int iedge, int to_node) { +static void check_rr_edge(const RREdgeId& iedge, const RRNodeId& to_node) { auto& device_ctx = g_vpr_ctx.device(); //Check that to to_node's fan-in is correct, given the switch type - int iswitch = device_ctx.rr_nodes[from_node].edge_switch(iedge); + int iswitch = (int)size_t(device_ctx.rr_graph.edge_switch(iedge)); auto switch_type = device_ctx.rr_switch_inf[iswitch].type(); - int to_fanin = device_ctx.rr_nodes[to_node].fan_in(); + int to_fanin = device_ctx.rr_graph.node_in_edges(to_node).size(); switch (switch_type) { case SwitchType::BUFFER: //Buffer switches are non-configurable, and uni-directional -- they must have only one driver From 266b4b6fbe4dd8b01652795b8ce948deea7f3242 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 1 Feb 2020 14:45:59 -0700 Subject: [PATCH 072/645] Breadth first router adopt RRGraph object --- vpr/src/base/vpr_context.h | 2 +- vpr/src/route/route_breadth_first.cpp | 66 +++++++++++++-------------- vpr/src/route/route_common.cpp | 20 ++++---- vpr/src/route/route_common.h | 8 ++-- 4 files changed, 47 insertions(+), 49 deletions(-) diff --git a/vpr/src/base/vpr_context.h b/vpr/src/base/vpr_context.h index 9a59e89c8..3195226a2 100644 --- a/vpr/src/base/vpr_context.h +++ b/vpr/src/base/vpr_context.h @@ -287,7 +287,7 @@ struct RoutingContext : public Context { vtr::vector> trace_nodes; /* Xifan Tang: this should adopt RRNodeId as well */ - vtr::vector> net_rr_terminals; /* [0..num_nets-1][0..num_pins-1] */ + vtr::vector> net_rr_terminals; /* [0..num_nets-1][0..num_pins-1] */ vtr::vector> rr_blk_source; /* [0..num_blocks-1][0..num_class-1] */ diff --git a/vpr/src/route/route_breadth_first.cpp b/vpr/src/route/route_breadth_first.cpp index cd0809a03..4e74ab128 100644 --- a/vpr/src/route/route_breadth_first.cpp +++ b/vpr/src/route/route_breadth_first.cpp @@ -17,13 +17,13 @@ static bool breadth_first_route_net(ClusterNetId net_id, float bend_cost); static void breadth_first_expand_trace_segment(t_trace* start_ptr, int remaining_connections_to_sink, - std::vector& modified_rr_node_inf); + std::vector& modified_rr_node_inf); -static void breadth_first_expand_neighbours(int inode, float pcost, ClusterNetId net_id, float bend_cost); +static void breadth_first_expand_neighbours(const RRNodeId& inode, float pcost, ClusterNetId net_id, float bend_cost); -static void breadth_first_add_to_heap(const float path_cost, const float bend_cost, const int from_node, const int to_node, const int iconn); +static void breadth_first_add_to_heap(const float path_cost, const float bend_cost, const RRNodeId& from_node, const RRNodeId& to_node, const RREdgeId& iconn); -static float evaluate_node_cost(const float prev_path_cost, const float bend_cost, const int from_node, const int to_node); +static float evaluate_node_cost(const float prev_path_cost, const float bend_cost, const RRNodeId& from_node, const RRNodeId& to_node); static void breadth_first_add_source_to_heap(ClusterNetId net_id); @@ -156,7 +156,8 @@ static bool breadth_first_route_net(ClusterNetId net_id, float bend_cost) { * lack of potential paths, rather than congestion), it returns false, as * * routing is impossible on this architecture. Otherwise it returns true. */ - int inode, remaining_connections_to_sink; + int remaining_connections_to_sink; + RRNodeId inode; float pcost, new_pcost; t_heap* current; t_trace* tptr; @@ -178,7 +179,7 @@ static bool breadth_first_route_net(ClusterNetId net_id, float bend_cost) { auto src_pin_id = cluster_ctx.clb_nlist.net_driver(net_id); - std::vector modified_rr_node_inf; //RR node indicies with modified rr_node_route_inf + std::vector modified_rr_node_inf; //RR node indicies with modified rr_node_route_inf for (auto pin_id : cluster_ctx.clb_nlist.net_sinks(net_id)) { /* Need n-1 wires to connect n pins */ @@ -197,7 +198,7 @@ static bool breadth_first_route_net(ClusterNetId net_id, float bend_cost) { inode = current->index; #ifdef ROUTER_DEBUG - VTR_LOG(" Popped node %d\n", inode); + VTR_LOG(" Popped node %d\n", size_t(inode)); #endif while (route_ctx.rr_node_route_inf[inode].target_flag == 0) { @@ -269,7 +270,7 @@ static bool breadth_first_route_net(ClusterNetId net_id, float bend_cost) { static void breadth_first_expand_trace_segment(t_trace* start_ptr, int remaining_connections_to_sink, - std::vector& modified_rr_node_inf) { + std::vector& modified_rr_node_inf) { /* Adds all the rr_nodes in the traceback segment starting at tptr (and * * continuing to the end of the traceback) to the heap with a cost of zero. * * This allows expansion to begin from the existing wiring. The * @@ -287,13 +288,13 @@ static void breadth_first_expand_trace_segment(t_trace* start_ptr, * this means two connections to the same SINK. */ t_trace *tptr, *next_ptr; - int inode, sink_node, last_ipin_node; + RRNodeId inode, sink_node, last_ipin_node; auto& device_ctx = g_vpr_ctx.device(); auto& route_ctx = g_vpr_ctx.mutable_routing(); tptr = start_ptr; - if (tptr != nullptr && device_ctx.rr_nodes[tptr->index].type() == SINK) { + if (tptr != nullptr && device_ctx.rr_graph.node_type(tptr->index) == SINK) { /* During logical equivalence case, only use one opin */ tptr = tptr->next; } @@ -301,9 +302,9 @@ static void breadth_first_expand_trace_segment(t_trace* start_ptr, if (remaining_connections_to_sink == 0) { /* Usual case. */ while (tptr != nullptr) { #ifdef ROUTER_DEBUG - VTR_LOG(" Adding previous routing node %d to heap\n", tptr->index); + VTR_LOG(" Adding previous routing node %d to heap\n", size_t(tptr->index)); #endif - node_to_heap(tptr->index, 0., NO_PREVIOUS, NO_PREVIOUS, OPEN, OPEN); + node_to_heap(tptr->index, 0., RRNodeId::INVALID(), RREdgeId::INVALID(), OPEN, OPEN); tptr = tptr->next; } } else { /* This case never executes for most logic blocks. */ @@ -318,7 +319,7 @@ static void breadth_first_expand_trace_segment(t_trace* start_ptr, return; /* No route yet */ next_ptr = tptr->next; - last_ipin_node = OPEN; /* Stops compiler from complaining. */ + last_ipin_node = RRNodeId::INVALID(); /* Stops compiler from complaining. */ /* Can't put last SINK on heap with NO_PREVIOUS, etc, since that won't let * * us reach it again. Instead, leave the last traceback element (SINK) off * @@ -327,17 +328,17 @@ static void breadth_first_expand_trace_segment(t_trace* start_ptr, while (next_ptr != nullptr) { inode = tptr->index; #ifdef ROUTER_DEBUG - VTR_LOG(" Adding previous routing node %d to heap*\n", tptr->index); + VTR_LOG(" Adding previous routing node %d to heap*\n", size_t(tptr->index)); #endif - node_to_heap(inode, 0., NO_PREVIOUS, NO_PREVIOUS, OPEN, OPEN); + node_to_heap(inode, 0., RRNodeId::INVALID(), RREdgeId::INVALID(), OPEN, OPEN); - if (device_ctx.rr_nodes[inode].type() == IPIN) + if (device_ctx.rr_graph.node_type(inode) == IPIN) last_ipin_node = inode; tptr = next_ptr; next_ptr = tptr->next; } - VTR_ASSERT(last_ipin_node >= 0); + VTR_ASSERT(true == device_ctx.rr_graph.valid_node_id(last_ipin_node)); /* This will stop the IPIN node used to get to this SINK from being * * reexpanded for the remainder of this net's routing. This will make us * @@ -363,24 +364,21 @@ static void breadth_first_expand_trace_segment(t_trace* start_ptr, } } -static void breadth_first_expand_neighbours(int inode, float pcost, ClusterNetId net_id, float bend_cost) { +static void breadth_first_expand_neighbours(const RRNodeId& inode, float pcost, ClusterNetId net_id, float bend_cost) { /* Puts all the rr_nodes adjacent to inode on the heap. rr_nodes outside * * the expanded bounding box specified in route_bb are not added to the * * heap. pcost is the path_cost to get to inode. */ - int iconn, to_node, num_edges; - auto& device_ctx = g_vpr_ctx.device(); auto& route_ctx = g_vpr_ctx.routing(); - num_edges = device_ctx.rr_nodes[inode].num_edges(); - for (iconn = 0; iconn < num_edges; iconn++) { - to_node = device_ctx.rr_nodes[inode].edge_sink_node(iconn); + for (const RREdgeId& iconn : device_ctx.rr_graph.node_out_edges(inode)) { + const RRNodeId& to_node = device_ctx.rr_graph.edge_sink_node(iconn); - if (device_ctx.rr_nodes[to_node].xhigh() < route_ctx.route_bb[net_id].xmin - || device_ctx.rr_nodes[to_node].xlow() > route_ctx.route_bb[net_id].xmax - || device_ctx.rr_nodes[to_node].yhigh() < route_ctx.route_bb[net_id].ymin - || device_ctx.rr_nodes[to_node].ylow() > route_ctx.route_bb[net_id].ymax) + if (device_ctx.rr_graph.node_xhigh(to_node) < route_ctx.route_bb[net_id].xmin + || device_ctx.rr_graph.node_xlow(to_node) > route_ctx.route_bb[net_id].xmax + || device_ctx.rr_graph.node_yhigh(to_node) < route_ctx.route_bb[net_id].ymin + || device_ctx.rr_graph.node_ylow(to_node) > route_ctx.route_bb[net_id].ymax) continue; /* Node is outside (expanded) bounding box. */ breadth_first_add_to_heap(pcost, bend_cost, inode, to_node, iconn); @@ -388,9 +386,9 @@ static void breadth_first_expand_neighbours(int inode, float pcost, ClusterNetId } //Add to_node to the heap, and also add any nodes which are connected by non-configurable edges -static void breadth_first_add_to_heap(const float path_cost, const float bend_cost, const int from_node, const int to_node, const int iconn) { +static void breadth_first_add_to_heap(const float path_cost, const float bend_cost, const RRNodeId& from_node, const RRNodeId& to_node, const RREdgeId& iconn) { #ifdef ROUTER_DEBUG - VTR_LOG(" Expanding node %d\n", to_node); + VTR_LOG(" Expanding node %d\n", size_t(to_node)); #endif //Create a heap element to represent this node (and any non-configurably connected nodes) @@ -413,14 +411,14 @@ static void breadth_first_add_to_heap(const float path_cost, const float bend_co add_to_heap(next); } -static float evaluate_node_cost(const float prev_path_cost, const float bend_cost, const int from_node, const int to_node) { +static float evaluate_node_cost(const float prev_path_cost, const float bend_cost, const RRNodeId& from_node, const RRNodeId& to_node) { auto& device_ctx = g_vpr_ctx.device(); float tot_cost = prev_path_cost + get_rr_cong_cost(to_node); if (bend_cost != 0.) { - t_rr_type from_type = device_ctx.rr_nodes[from_node].type(); - t_rr_type to_type = device_ctx.rr_nodes[to_node].type(); + t_rr_type from_type = device_ctx.rr_graph.node_type(from_node); + t_rr_type to_type = device_ctx.rr_graph.node_type(to_node); if ((from_type == CHANX && to_type == CHANY) || (from_type == CHANY && to_type == CHANX)) tot_cost += bend_cost; @@ -432,7 +430,7 @@ static float evaluate_node_cost(const float prev_path_cost, const float bend_cos static void breadth_first_add_source_to_heap(ClusterNetId net_id) { /* Adds the SOURCE of this net to the heap. Used to start a net's routing. */ - int inode; + RRNodeId inode; float cost; auto& route_ctx = g_vpr_ctx.routing(); @@ -444,5 +442,5 @@ static void breadth_first_add_source_to_heap(ClusterNetId net_id) { VTR_LOG(" Adding Source node %d to heap\n", inode); #endif - node_to_heap(inode, cost, NO_PREVIOUS, NO_PREVIOUS, OPEN, OPEN); + node_to_heap(inode, cost, RRNodeId::INVALID(), RREdgeId::INVALID(), OPEN, OPEN); } diff --git a/vpr/src/route/route_common.cpp b/vpr/src/route/route_common.cpp index 39373cb76..a6ad81a5f 100644 --- a/vpr/src/route/route_common.cpp +++ b/vpr/src/route/route_common.cpp @@ -104,7 +104,7 @@ static void adjust_one_rr_occ_and_apcost(int inode, int add_or_sub, float pres_f bool validate_traceback_recurr(t_trace* trace, std::set& seen_rr_nodes); static bool validate_trace_nodes(t_trace* head, const std::unordered_set& trace_nodes); -static float get_single_rr_cong_cost(int inode); +static float get_single_rr_cong_cost(const RRNodeId& inode); /************************** Subroutine definitions ***************************/ @@ -774,7 +774,7 @@ void mark_remaining_ends(const std::vector& remaining_sinks) { ++route_ctx.rr_node_route_inf[sink_node].target_flag; } -void node_to_heap(int inode, float total_cost, int prev_node, int prev_edge, float backward_path_cost, float R_upstream) { +void node_to_heap(const RRNodeId& inode, float total_cost, const RRNodeId& prev_node, const RREdgeId& prev_edge, float backward_path_cost, float R_upstream) { /* Puts an rr_node on the heap, if the new cost given is lower than the * * current path_cost to this channel segment. The index of its predecessor * * is stored to make traceback easy. The index of the edge used to get * @@ -790,8 +790,8 @@ void node_to_heap(int inode, float total_cost, int prev_node, int prev_edge, flo t_heap* hptr = alloc_heap_data(); hptr->index = inode; hptr->cost = total_cost; - VTR_ASSERT_SAFE(hptr->u.prev.node == NO_PREVIOUS); - VTR_ASSERT_SAFE(hptr->u.prev.edge == NO_PREVIOUS); + VTR_ASSERT_SAFE(hptr->u.prev.node == RRNodeId::INVALID()); + VTR_ASSERT_SAFE(hptr->u.prev.edge == RREdgeId::INVALID()); hptr->u.prev.node = prev_node; hptr->u.prev.edge = prev_edge; hptr->backward_path_cost = backward_path_cost; @@ -1193,7 +1193,7 @@ t_bb load_net_route_bb(ClusterNetId net_id, int bb_factor) { return bb; } -void add_to_mod_list(int inode, std::vector& modified_rr_node_inf) { +void add_to_mod_list(const RRNodeId& inode, std::vector& modified_rr_node_inf) { auto& route_ctx = g_vpr_ctx.routing(); if (std::isinf(route_ctx.rr_node_route_inf[inode].path_cost)) { @@ -1413,7 +1413,7 @@ void free_heap_data(t_heap* hptr) { num_heap_allocated--; } -void invalidate_heap_entries(int sink_node, int ipin_node) { +void invalidate_heap_entries(const RRNodeId& sink_node, const RRNodeId& ipin_node) { /* Marks all the heap entries consisting of sink_node, where it was reached * * via ipin_node, as invalid (OPEN). Used only by the breadth_first router * * and even then only in rare circumstances. */ @@ -1421,7 +1421,7 @@ void invalidate_heap_entries(int sink_node, int ipin_node) { for (int i = 1; i < heap_tail; i++) { if (heap[i]->index == sink_node) { if (heap[i]->u.prev.node == ipin_node) { - heap[i]->index = OPEN; /* Invalid. */ + heap[i]->index = RRNodeId::INVALID(); /* Invalid. */ break; } } @@ -1816,7 +1816,7 @@ void print_invalid_routing_info() { auto& route_ctx = g_vpr_ctx.routing(); //Build a look-up of nets using each RR node - std::multimap rr_node_nets; + std::multimap rr_node_nets; for (auto net_id : cluster_ctx.clb_nlist.nets()) { t_trace* tptr = route_ctx.trace[net_id].head; @@ -1827,9 +1827,9 @@ void print_invalid_routing_info() { } } - for (size_t inode = 0; inode < device_ctx.rr_graph.nodes().size(); inode++) { + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { int occ = route_ctx.rr_node_route_inf[inode].occ(); - int cap = device_ctx.rr_nodes[inode].capacity(); + int cap = device_ctx.rr_graph.node_capacity(inode); if (occ > cap) { VTR_LOG(" %s is overused (occ=%d capacity=%d)\n", describe_rr_node(inode).c_str(), occ, cap); diff --git a/vpr/src/route/route_common.h b/vpr/src/route/route_common.h index cfcfb30ce..86fb216a5 100644 --- a/vpr/src/route/route_common.h +++ b/vpr/src/route/route_common.h @@ -66,7 +66,7 @@ void pathfinder_update_cost(float pres_fac, float acc_fac); t_trace* update_traceback(t_heap* hptr, ClusterNetId net_id); -void reset_path_costs(const std::vector& visited_rr_nodes); +void reset_path_costs(const std::vector& visited_rr_nodes); float get_rr_cong_cost(const RRNodeId& inode); @@ -75,7 +75,7 @@ void mark_remaining_ends(const std::vector& remaining_sinks); void add_to_heap(t_heap* hptr); t_heap* alloc_heap_data(); -void node_to_heap(int inode, float cost, int prev_node, int prev_edge, float backward_path_cost, float R_upstream); +void node_to_heap(const RRNodeId& inode, float cost, const RRNodeId& prev_node, const RREdgeId& prev_edge, float backward_path_cost, float R_upstream); bool is_empty_heap(); @@ -83,7 +83,7 @@ void free_traceback(ClusterNetId net_id); void drop_traceback_tail(ClusterNetId net_id); void free_traceback(t_trace* tptr); -void add_to_mod_list(int inode, std::vector& modified_rr_node_inf); +void add_to_mod_list(const RRNodeId& inode, std::vector& modified_rr_node_inf); namespace heap_ { void build_heap(); @@ -103,7 +103,7 @@ void empty_heap(); void free_heap_data(t_heap* hptr); -void invalidate_heap_entries(int sink_node, int ipin_node); +void invalidate_heap_entries(const RRNodeId& sink_node, const RRNodeId& ipin_node); void init_route_structs(int bb_factor); From 26523f1b63549fb11bc27882c3fb39e497908720 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 1 Feb 2020 16:12:54 -0700 Subject: [PATCH 073/645] route_common functions now adopt RRGraph object --- vpr/src/base/vpr_context.h | 8 +- vpr/src/route/route_common.cpp | 300 +++++++++++++++++---------------- vpr/src/route/route_common.h | 17 +- 3 files changed, 168 insertions(+), 157 deletions(-) diff --git a/vpr/src/base/vpr_context.h b/vpr/src/base/vpr_context.h index 3195226a2..ecad22007 100644 --- a/vpr/src/base/vpr_context.h +++ b/vpr/src/base/vpr_context.h @@ -157,10 +157,10 @@ struct DeviceContext : public Context { std::vector rr_rc_data; //Sets of non-configurably connected nodes - std::vector> rr_non_config_node_sets; + std::vector> rr_non_config_node_sets; //Reverse look-up from RR node to non-configurably connected node set (index into rr_nonconf_node_sets) - std::unordered_map rr_node_to_non_config_node_set; + std::unordered_map rr_node_to_non_config_node_set; //The indicies of rr nodes of a given type at a specific x,y grid location t_rr_node_indices rr_node_indices; //[0..NUM_RR_TYPES-1][0..grid.width()-1][0..grid.width()-1][0..size-1] @@ -284,12 +284,12 @@ struct PlacementContext : public Context { struct RoutingContext : public Context { /* [0..num_nets-1] of linked list start pointers. Defines the routing. */ vtr::vector trace; - vtr::vector> trace_nodes; + vtr::vector> trace_nodes; /* Xifan Tang: this should adopt RRNodeId as well */ vtr::vector> net_rr_terminals; /* [0..num_nets-1][0..num_pins-1] */ - vtr::vector> rr_blk_source; /* [0..num_blocks-1][0..num_class-1] */ + vtr::vector> rr_blk_source; /* [0..num_blocks-1][0..num_class-1] */ vtr::vector rr_node_route_inf; /* [0..device_ctx.num_rr_nodes-1] */ diff --git a/vpr/src/route/route_common.cpp b/vpr/src/route/route_common.cpp index a6ad81a5f..5ae9086c3 100644 --- a/vpr/src/route/route_common.cpp +++ b/vpr/src/route/route_common.cpp @@ -92,18 +92,18 @@ static int num_linked_f_pointer_allocated = 0; * */ /******************** Subroutines local to route_common.c *******************/ -static t_trace_branch traceback_branch(int node, std::unordered_set& main_branch_visited); -static std::pair add_trace_non_configurable(t_trace* head, t_trace* tail, int node, std::unordered_set& visited); -static std::pair add_trace_non_configurable_recurr(int node, std::unordered_set& visited, int depth = 0); +static t_trace_branch traceback_branch(const RRNodeId& node, std::unordered_set& main_branch_visited); +static std::pair add_trace_non_configurable(t_trace* head, t_trace* tail, const RRNodeId& node, std::unordered_set& visited); +static std::pair add_trace_non_configurable_recurr(const RRNodeId& node, std::unordered_set& visited, int depth = 0); -static vtr::vector> load_net_rr_terminals(const t_rr_node_indices& L_rr_node_indices); -static vtr::vector> load_rr_clb_sources(const t_rr_node_indices& L_rr_node_indices); +static vtr::vector> load_net_rr_terminals(const RRGraph& rr_graph); +static vtr::vector> load_rr_clb_sources(const RRGraph& rr_graph); static t_clb_opins_used alloc_and_load_clb_opins_used_locally(); -static void adjust_one_rr_occ_and_apcost(int inode, int add_or_sub, float pres_fac, float acc_fac); +static void adjust_one_rr_occ_and_apcost(const RRNodeId& inode, int add_or_sub, float pres_fac, float acc_fac); -bool validate_traceback_recurr(t_trace* trace, std::set& seen_rr_nodes); -static bool validate_trace_nodes(t_trace* head, const std::unordered_set& trace_nodes); +bool validate_traceback_recurr(t_trace* trace, std::set& seen_rr_nodes); +static bool validate_trace_nodes(t_trace* head, const std::unordered_set& trace_nodes); static float get_single_rr_cong_cost(const RRNodeId& inode); /************************** Subroutine definitions ***************************/ @@ -179,7 +179,8 @@ void restore_routing(vtr::vector& best_routing, * Use this number as a routing serial number to ensure that programming * * changes do not break the router. */ void get_serial_num() { - int serial_num, inode; + int serial_num; + RRNodeId inode; t_trace* tptr; auto& cluster_ctx = g_vpr_ctx.clustering(); @@ -196,11 +197,11 @@ void get_serial_num() { while (tptr != nullptr) { inode = tptr->index; serial_num += (size_t(net_id) + 1) - * (device_ctx.rr_nodes[inode].xlow() * (device_ctx.grid.width()) - device_ctx.rr_nodes[inode].yhigh()); + * (device_ctx.rr_graph.node_xlow(inode) * (device_ctx.grid.width()) - device_ctx.rr_graph.node_yhigh(inode)); - serial_num -= device_ctx.rr_nodes[inode].ptc_num() * (size_t(net_id) + 1) * 10; + serial_num -= device_ctx.rr_graph.node_ptc_num(inode) * (size_t(net_id) + 1) * 10; - serial_num -= device_ctx.rr_nodes[inode].type() * (size_t(net_id) + 1) * 100; + serial_num -= device_ctx.rr_graph.node_type(inode) * (size_t(net_id) + 1) * 100; serial_num %= 2000000000; /* Prevent overflow */ tptr = tptr->next; } @@ -336,8 +337,8 @@ bool feasible_routing() { auto& device_ctx = g_vpr_ctx.device(); auto& route_ctx = g_vpr_ctx.routing(); - for (size_t inode = 0; inode < device_ctx.rr_graph.nodes().size(); inode++) { - if (route_ctx.rr_node_route_inf[inode].occ() > device_ctx.rr_nodes[inode].capacity()) { + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { + if (route_ctx.rr_node_route_inf[inode].occ() > device_ctx.rr_graph.node_capacity(inode)) { return (false); } } @@ -414,7 +415,7 @@ void pathfinder_update_path_cost(t_trace* route_segment_start, } /* End while loop -- did an entire traceback. */ } -void pathfinder_update_single_node_cost(int inode, int add_or_sub, float pres_fac) { +void pathfinder_update_single_node_cost(const RRNodeId& inode, int add_or_sub, float pres_fac) { /* Updates pathfinder's congestion cost by either adding or removing the * usage of a resource node. pres_cost is Pn in the Pathfinder paper. * pres_cost is set according to the overuse that would result from having @@ -428,7 +429,7 @@ void pathfinder_update_single_node_cost(int inode, int add_or_sub, float pres_fa // can't have negative occupancy VTR_ASSERT(occ >= 0); - int capacity = device_ctx.rr_nodes[inode].capacity(); + int capacity = device_ctx.rr_graph.node_capacity(inode); if (occ < capacity) { route_ctx.rr_node_route_inf[inode].pres_cost = 1.0; } else { @@ -449,9 +450,9 @@ void pathfinder_update_cost(float pres_fac, float acc_fac) { auto& device_ctx = g_vpr_ctx.device(); auto& route_ctx = g_vpr_ctx.mutable_routing(); - for (size_t inode = 0; inode < device_ctx.rr_graph.nodes().size(); inode++) { + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { occ = route_ctx.rr_node_route_inf[inode].occ(); - capacity = device_ctx.rr_nodes[inode].capacity(); + capacity = device_ctx.rr_graph.node_capacity(inode); if (occ > capacity) { route_ctx.rr_node_route_inf[inode].acc_cost += (occ - capacity) * acc_fac; @@ -496,9 +497,9 @@ void init_route_structs(int bb_factor) { init_heap(device_ctx.grid); //Various look-ups - route_ctx.net_rr_terminals = load_net_rr_terminals(device_ctx.rr_node_indices); + route_ctx.net_rr_terminals = load_net_rr_terminals(device_ctx.rr_graph); route_ctx.route_bb = load_route_bb(bb_factor); - route_ctx.rr_blk_source = load_rr_clb_sources(device_ctx.rr_node_indices); + route_ctx.rr_blk_source = load_rr_clb_sources(device_ctx.rr_graph); route_ctx.clb_opins_used_locally = alloc_and_load_clb_opins_used_locally(); route_ctx.net_status.resize(cluster_ctx.clb_nlist.nets().size()); @@ -550,14 +551,15 @@ update_traceback(t_heap* hptr, ClusterNetId net_id) { //Traces back a new routing branch starting from the specified 'node' and working backwards to any existing routing. //Returns the new branch, and also updates trace_nodes for any new nodes which are included in the branches traceback. -static t_trace_branch traceback_branch(int node, std::unordered_set& trace_nodes) { +static t_trace_branch traceback_branch(const RRNodeId& node, std::unordered_set& trace_nodes) { auto& device_ctx = g_vpr_ctx.device(); auto& route_ctx = g_vpr_ctx.routing(); - auto rr_type = device_ctx.rr_nodes[node].type(); + auto rr_type = device_ctx.rr_graph.node_type(node); if (rr_type != SINK) { VPR_FATAL_ERROR(VPR_ERROR_ROUTE, - "in traceback_branch: Expected type = SINK (%d).\n"); + "in traceback_branch: Expected type = SINK (%d).\n", + size_t(node)); } //We construct the main traceback by walking from the given node back to the source, @@ -570,16 +572,16 @@ static t_trace_branch traceback_branch(int node, std::unordered_set& trace_ trace_nodes.insert(node); - std::vector new_nodes_added_to_traceback = {node}; + std::vector new_nodes_added_to_traceback = {node}; - auto iedge = route_ctx.rr_node_route_inf[node].prev_edge; - int inode = route_ctx.rr_node_route_inf[node].prev_node; + RREdgeId iedge = route_ctx.rr_node_route_inf[node].prev_edge; + RRNodeId inode = route_ctx.rr_node_route_inf[node].prev_node; - while (inode != NO_PREVIOUS) { + while (inode != RRNodeId::INVALID()) { //Add the current node to the head of traceback t_trace* prev_ptr = alloc_trace_data(); prev_ptr->index = inode; - prev_ptr->iswitch = device_ctx.rr_nodes[inode].edge_switch(iedge); + prev_ptr->iswitch = (short)size_t(device_ctx.rr_graph.edge_switch(iedge)); prev_ptr->next = branch_head; branch_head = prev_ptr; @@ -597,7 +599,7 @@ static t_trace_branch traceback_branch(int node, std::unordered_set& trace_ //We next re-expand all the main-branch nodes to add any non-configurably connected side branches // We are careful to do this *after* the main branch is constructed to ensure nodes which are both // non-configurably connected *and* part of the main branch are only added to the traceback once. - for (int new_node : new_nodes_added_to_traceback) { + for (const RRNodeId& new_node : new_nodes_added_to_traceback) { //Expand each main branch node std::tie(branch_head, branch_tail) = add_trace_non_configurable(branch_head, branch_tail, new_node, trace_nodes); } @@ -608,7 +610,7 @@ static t_trace_branch traceback_branch(int node, std::unordered_set& trace_ //Traces any non-configurable subtrees from branch_head, returning the new branch_head and updating trace_nodes // //This effectively does a depth-first traversal -static std::pair add_trace_non_configurable(t_trace* head, t_trace* tail, int node, std::unordered_set& trace_nodes) { +static std::pair add_trace_non_configurable(t_trace* head, t_trace* tail, const RRNodeId& node, std::unordered_set& trace_nodes) { //Trace any non-configurable subtrees t_trace* subtree_head = nullptr; t_trace* subtree_tail = nullptr; @@ -632,17 +634,17 @@ static std::pair add_trace_non_configurable(t_trace* head, t } //Recursive helper function for add_trace_non_configurable() -static std::pair add_trace_non_configurable_recurr(int node, std::unordered_set& trace_nodes, int depth) { +static std::pair add_trace_non_configurable_recurr(const RRNodeId& node, std::unordered_set& trace_nodes, int depth) { t_trace* head = nullptr; t_trace* tail = nullptr; //Record the non-configurable out-going edges - std::vector unvisited_non_configurable_edges; + std::vector unvisited_non_configurable_edges; auto& device_ctx = g_vpr_ctx.device(); - for (auto iedge : device_ctx.rr_nodes[node].non_configurable_edges()) { - VTR_ASSERT_SAFE(!device_ctx.rr_nodes[node].edge_is_configurable(iedge)); + for (auto iedge : device_ctx.rr_graph.node_non_configurable_out_edges(node)) { + VTR_ASSERT_SAFE(!device_ctx.rr_graph.edge_is_configurable(iedge)); - int to_node = device_ctx.rr_nodes[node].edge_sink_node(iedge); + const RRNodeId& to_node = device_ctx.rr_graph.edge_sink_node(iedge); if (!trace_nodes.count(to_node)) { unvisited_non_configurable_edges.push_back(iedge); @@ -665,8 +667,8 @@ static std::pair add_trace_non_configurable_recurr(int node, } else { //Recursive case: intermediate node with non-configurable edges for (auto iedge : unvisited_non_configurable_edges) { - int to_node = device_ctx.rr_nodes[node].edge_sink_node(iedge); - int iswitch = device_ctx.rr_nodes[node].edge_switch(iedge); + const RRNodeId& to_node = device_ctx.rr_graph.edge_sink_node(iedge); + int iswitch = (int)size_t(device_ctx.rr_graph.edge_switch(iedge)); VTR_ASSERT(!trace_nodes.count(to_node)); trace_nodes.insert(node); @@ -725,7 +727,7 @@ float get_rr_cong_cost(const RRNodeId& inode) { auto itr = device_ctx.rr_node_to_non_config_node_set.find(inode); if (itr != device_ctx.rr_node_to_non_config_node_set.end()) { - for (const RRNodeId& node : device_ctx.rr_non_config_node_sets[itr->second]) { + for (RRNodeId node : device_ctx.rr_non_config_node_sets[itr->second]) { if (node == inode) { continue; //Already included above } @@ -755,22 +757,20 @@ static float get_single_rr_cong_cost(const RRNodeId& inode) { * the same net to two inputs of an and-gate (and-gate inputs are logically * * equivalent, so both will connect to the same SINK). */ void mark_ends(ClusterNetId net_id) { - unsigned int ipin; - int inode; auto& cluster_ctx = g_vpr_ctx.clustering(); auto& route_ctx = g_vpr_ctx.mutable_routing(); - for (ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net_id).size(); ipin++) { - inode = route_ctx.net_rr_terminals[net_id][ipin]; + for (unsigned int ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net_id).size(); ipin++) { + const RRNodeId& inode = route_ctx.net_rr_terminals[net_id][ipin]; route_ctx.rr_node_route_inf[inode].target_flag++; } } -void mark_remaining_ends(const std::vector& remaining_sinks) { +void mark_remaining_ends(const std::vector& remaining_sinks) { // like mark_ends, but only performs it for the remaining sinks of a net auto& route_ctx = g_vpr_ctx.mutable_routing(); - for (int sink_node : remaining_sinks) + for (const RRNodeId& sink_node : remaining_sinks) ++route_ctx.rr_node_route_inf[sink_node].target_flag; } @@ -1016,10 +1016,9 @@ void reset_rr_node_route_structs() { VTR_ASSERT(route_ctx.rr_node_route_inf.size() == size_t(device_ctx.rr_graph.nodes().size())); - for (size_t inode = 0; inode < device_ctx.rr_graph.nodes().size(); inode++) { - route_ctx.rr_node_route_inf[inode].prev_node = NO_PREVIOUS; - route_ctx.rr_node_route_inf[inode].prev_node_id = RRNodeId::INVALID(); - route_ctx.rr_node_route_inf[inode].prev_edge = NO_PREVIOUS; + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { + route_ctx.rr_node_route_inf[inode].prev_node = RRNodeId::INVALID(); + route_ctx.rr_node_route_inf[inode].prev_edge = RREdgeId::INVALID(); route_ctx.rr_node_route_inf[inode].pres_cost = 1.0; route_ctx.rr_node_route_inf[inode].acc_cost = 1.0; route_ctx.rr_node_route_inf[inode].path_cost = std::numeric_limits::infinity(); @@ -1032,8 +1031,8 @@ void reset_rr_node_route_structs() { /* Allocates and loads the route_ctx.net_rr_terminals data structure. For each net it stores the rr_node * * index of the SOURCE of the net and all the SINKs of the net [clb_nlist.nets()][clb_nlist.net_pins()]. * * Entry [inet][pnum] stores the rr index corresponding to the SOURCE (opin) or SINK (ipin) of the pin. */ -static vtr::vector> load_net_rr_terminals(const t_rr_node_indices& L_rr_node_indices) { - vtr::vector> net_rr_terminals; +static vtr::vector> load_net_rr_terminals(const RRGraph& rr_graph) { + vtr::vector> net_rr_terminals; auto& cluster_ctx = g_vpr_ctx.clustering(); auto& place_ctx = g_vpr_ctx.placement(); @@ -1059,8 +1058,8 @@ static vtr::vector> load_net_rr_terminals(const t int iclass = type->pin_class[phys_pin]; - int inode = get_rr_node_index(L_rr_node_indices, i, j, (pin_count == 0 ? SOURCE : SINK), /* First pin is driver */ - iclass); + RRNodeId inode = rr_graph.find_node(i, j, (pin_count == 0 ? SOURCE : SINK), /* First pin is driver */ + iclass); net_rr_terminals[net_id][pin_count] = inode; pin_count++; } @@ -1074,10 +1073,9 @@ static vtr::vector> load_net_rr_terminals(const t * they are used only to reserve pins for locally used OPINs in the router. * * [0..cluster_ctx.clb_nlist.blocks().size()-1][0..num_class-1]. * * The values for blocks that are padsare NOT valid. */ -static vtr::vector> load_rr_clb_sources(const t_rr_node_indices& L_rr_node_indices) { - vtr::vector> rr_blk_source; +static vtr::vector> load_rr_clb_sources(const RRGraph& rr_graph) { + vtr::vector> rr_blk_source; - int i, j, iclass, inode; int class_low, class_high; t_rr_type rr_type; @@ -1090,20 +1088,20 @@ static vtr::vector> load_rr_clb_sources(const t auto type = physical_tile_type(blk_id); get_class_range_for_block(blk_id, &class_low, &class_high); rr_blk_source[blk_id].resize(type->num_class); - for (iclass = 0; iclass < type->num_class; iclass++) { + for (int iclass = 0; iclass < type->num_class; iclass++) { if (iclass >= class_low && iclass <= class_high) { - i = place_ctx.block_locs[blk_id].loc.x; - j = place_ctx.block_locs[blk_id].loc.y; + int i = place_ctx.block_locs[blk_id].loc.x; + int j = place_ctx.block_locs[blk_id].loc.y; if (type->class_inf[iclass].type == DRIVER) rr_type = SOURCE; else rr_type = SINK; - inode = get_rr_node_index(L_rr_node_indices, i, j, rr_type, iclass); + const RRNodeId& inode = rr_graph.find_node(i, j, rr_type, iclass); rr_blk_source[blk_id][iclass] = inode; } else { - rr_blk_source[blk_id][iclass] = OPEN; + rr_blk_source[blk_id][iclass] = RRNodeId::INVALID(); } } } @@ -1149,31 +1147,29 @@ t_bb load_net_route_bb(ClusterNetId net_id, int bb_factor) { int max_dim = std::max(device_ctx.grid.width() - 1, device_ctx.grid.height() - 1); bb_factor = std::min(bb_factor, max_dim); - int driver_rr = route_ctx.net_rr_terminals[net_id][0]; - const t_rr_node& source_node = device_ctx.rr_nodes[driver_rr]; - VTR_ASSERT(source_node.type() == SOURCE); + const RRNodeId& driver_rr = route_ctx.net_rr_terminals[net_id][0]; + VTR_ASSERT(device_ctx.rr_graph.node_type(driver_rr) == SOURCE); - VTR_ASSERT(source_node.xlow() <= source_node.xhigh()); - VTR_ASSERT(source_node.ylow() <= source_node.yhigh()); + VTR_ASSERT(device_ctx.rr_graph.node_xlow(driver_rr) <= device_ctx.rr_graph.node_xhigh(driver_rr)); + VTR_ASSERT(device_ctx.rr_graph.node_ylow(driver_rr) <= device_ctx.rr_graph.node_yhigh(driver_rr)); - int xmin = source_node.xlow(); - int ymin = source_node.ylow(); - int xmax = source_node.xhigh(); - int ymax = source_node.yhigh(); + int xmin = device_ctx.rr_graph.node_xlow(driver_rr); + int ymin = device_ctx.rr_graph.node_ylow(driver_rr); + int xmax = device_ctx.rr_graph.node_xhigh(driver_rr); + int ymax = device_ctx.rr_graph.node_yhigh(driver_rr); auto net_sinks = cluster_ctx.clb_nlist.net_sinks(net_id); for (size_t ipin = 1; ipin < net_sinks.size() + 1; ++ipin) { //Start at 1 since looping through sinks - int sink_rr = route_ctx.net_rr_terminals[net_id][ipin]; - const t_rr_node& sink_node = device_ctx.rr_nodes[sink_rr]; - VTR_ASSERT(sink_node.type() == SINK); + const RRNodeId& sink_rr = route_ctx.net_rr_terminals[net_id][ipin]; + VTR_ASSERT(device_ctx.rr_graph.node_type(sink_rr) == SINK); - VTR_ASSERT(sink_node.xlow() <= sink_node.xhigh()); - VTR_ASSERT(sink_node.ylow() <= sink_node.yhigh()); + VTR_ASSERT(device_ctx.rr_graph.node_xlow(sink_rr) <= device_ctx.rr_graph.node_xhigh(sink_rr)); + VTR_ASSERT(device_ctx.rr_graph.node_ylow(sink_rr) <= device_ctx.rr_graph.node_yhigh(sink_rr)); - xmin = std::min(xmin, sink_node.xlow()); - xmax = std::max(xmax, sink_node.xhigh()); - ymin = std::min(ymin, sink_node.ylow()); - ymax = std::max(ymax, sink_node.yhigh()); + xmin = std::min(xmin, device_ctx.rr_graph.node_xlow(sink_rr)); + xmax = std::max(xmax, device_ctx.rr_graph.node_xhigh(sink_rr)); + ymin = std::min(ymin, device_ctx.rr_graph.node_ylow(sink_rr)); + ymax = std::max(ymax, device_ctx.rr_graph.node_yhigh(sink_rr)); } /* Want the channels on all 4 sides to be usuable, even if bb_factor = 0. */ @@ -1271,11 +1267,11 @@ void push_back_node(const RRNodeId& inode, float total_cost, const RRNodeId& pre * bottom up with build_heap */ auto& route_ctx = g_vpr_ctx.routing(); - if (total_cost >= route_ctx.rr_node_route_inf[size_t(inode)].path_cost) + if (total_cost >= route_ctx.rr_node_route_inf[inode].path_cost) return; t_heap* hptr = alloc_heap_data(); - hptr->index = size_t(inode); + hptr->index = inode; hptr->cost = total_cost; hptr->u.prev.node = prev_node; hptr->u.prev.edge = prev_edge; @@ -1317,7 +1313,7 @@ void verify_extract_top() { std::cout << "starting to compare top elements\n"; size_t i = 0; while (!is_empty_heap()) { - while (heap_copy[i]->index == OPEN) + while (heap_copy[i]->index == RRNodeId::INVALID()) ++i; // skip the ones that won't be extracted auto top = get_heap_head(); if (abs(top->cost - heap_copy[i]->cost) > float_epsilon) @@ -1372,7 +1368,7 @@ get_heap_head() { } heap_::sift_up(hole, heap[heap_tail]); - } while (cheapest->index == OPEN); /* Get another one if invalid entry. */ + } while (cheapest->index == RRNodeId::INVALID()); /* Get another one if invalid entry. */ return (cheapest); } @@ -1401,9 +1397,9 @@ alloc_heap_data() { temp_ptr->cost = 0.; temp_ptr->backward_path_cost = 0.; temp_ptr->R_upstream = 0.; - temp_ptr->index = OPEN; - temp_ptr->u.prev.node = NO_PREVIOUS; - temp_ptr->u.prev.edge = NO_PREVIOUS; + temp_ptr->index = RRNodeId::INVALID(); + temp_ptr->u.prev.node = RRNodeId::INVALID(); + temp_ptr->u.prev.edge = RREdgeId::INVALID(); return (temp_ptr); } @@ -1467,18 +1463,18 @@ void print_route(FILE* fp, const vtr::vector& traceba t_trace* tptr = route_ctx.trace[net_id].head; while (tptr != nullptr) { - int inode = tptr->index; - t_rr_type rr_type = device_ctx.rr_nodes[inode].type(); - int ilow = device_ctx.rr_nodes[inode].xlow(); - int jlow = device_ctx.rr_nodes[inode].ylow(); + RRNodeId inode = tptr->index; + t_rr_type rr_type = device_ctx.rr_graph.node_type(inode); + int ilow = device_ctx.rr_graph.node_xlow(inode); + int jlow = device_ctx.rr_graph.node_ylow(inode); - fprintf(fp, "Node:\t%d\t%6s (%d,%d) ", inode, - device_ctx.rr_nodes[inode].type_string(), ilow, jlow); + fprintf(fp, "Node:\t%ld\t%6s (%d,%d) ", size_t(inode), + rr_node_typename[device_ctx.rr_graph.node_type(inode)], ilow, jlow); - if ((ilow != device_ctx.rr_nodes[inode].xhigh()) - || (jlow != device_ctx.rr_nodes[inode].yhigh())) - fprintf(fp, "to (%d,%d) ", device_ctx.rr_nodes[inode].xhigh(), - device_ctx.rr_nodes[inode].yhigh()); + if ((ilow != device_ctx.rr_graph.node_xhigh(inode)) + || (jlow != device_ctx.rr_graph.node_yhigh(inode))) + fprintf(fp, "to (%d,%d) ", device_ctx.rr_graph.node_xhigh(inode), + device_ctx.rr_graph.node_yhigh(inode)); switch (rr_type) { case IPIN: @@ -1507,14 +1503,14 @@ void print_route(FILE* fp, const vtr::vector& traceba default: VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "in print_route: Unexpected traceback element type: %d (%s).\n", - rr_type, device_ctx.rr_nodes[inode].type_string()); + rr_type, rr_node_typename[device_ctx.rr_graph.node_type(inode)]); break; } - fprintf(fp, "%d ", device_ctx.rr_nodes[inode].ptc_num()); + fprintf(fp, "%d ", device_ctx.rr_graph.node_ptc_num(inode)); if (!is_io_type(device_ctx.grid[ilow][jlow].type) && (rr_type == IPIN || rr_type == OPIN)) { - int pin_num = device_ctx.rr_nodes[inode].ptc_num(); + int pin_num = device_ctx.rr_graph.node_ptc_num(inode); int xoffset = device_ctx.grid[ilow][jlow].width_offset; int yoffset = device_ctx.grid[ilow][jlow].height_offset; ClusterBlockId iblock = place_ctx.grid_blocks[ilow - xoffset][jlow - yoffset].blocks[0]; @@ -1600,8 +1596,7 @@ void print_route(const char* placement_file, const char* route_file) { // To model this we 'reserve' these locally used outputs, ensuring that the router will not use them (as if it did // this would equate to duplicating a BLE into an already in-use BLE instance, which is clearly incorrect). void reserve_locally_used_opins(float pres_fac, float acc_fac, bool rip_up_local_opins) { - int num_local_opin, inode, from_node, iconn, num_edges, to_node; - int iclass, ipin; + int num_local_opin; float cost; t_heap* heap_head_ptr; t_physical_tile_type_ptr type; @@ -1613,15 +1608,15 @@ void reserve_locally_used_opins(float pres_fac, float acc_fac, bool rip_up_local if (rip_up_local_opins) { for (auto blk_id : cluster_ctx.clb_nlist.blocks()) { type = physical_tile_type(blk_id); - for (iclass = 0; iclass < type->num_class; iclass++) { + for (int iclass = 0; iclass < type->num_class; iclass++) { num_local_opin = route_ctx.clb_opins_used_locally[blk_id][iclass].size(); if (num_local_opin == 0) continue; VTR_ASSERT(type->class_inf[iclass].equivalence == PortEquivalence::INSTANCE); /* Always 0 for pads and for RECEIVER (IPIN) classes */ - for (ipin = 0; ipin < num_local_opin; ipin++) { - inode = route_ctx.clb_opins_used_locally[blk_id][iclass][ipin]; + for (int ipin = 0; ipin < num_local_opin; ipin++) { + const RRNodeId& inode = route_ctx.clb_opins_used_locally[blk_id][iclass][ipin]; adjust_one_rr_occ_and_apcost(inode, -1, pres_fac, acc_fac); } } @@ -1630,7 +1625,7 @@ void reserve_locally_used_opins(float pres_fac, float acc_fac, bool rip_up_local for (auto blk_id : cluster_ctx.clb_nlist.blocks()) { type = physical_tile_type(blk_id); - for (iclass = 0; iclass < type->num_class; iclass++) { + for (int iclass = 0; iclass < type->num_class; iclass++) { num_local_opin = route_ctx.clb_opins_used_locally[blk_id][iclass].size(); if (num_local_opin == 0) continue; @@ -1642,25 +1637,24 @@ void reserve_locally_used_opins(float pres_fac, float acc_fac, bool rip_up_local //congestion cost are popped-off/reserved first. (Intuitively, we want //the reserved OPINs to move out of the way of congestion, by preferring //to reserve OPINs with lower congestion costs). - from_node = route_ctx.rr_blk_source[blk_id][iclass]; - num_edges = device_ctx.rr_nodes[from_node].num_edges(); - for (iconn = 0; iconn < num_edges; iconn++) { - to_node = device_ctx.rr_nodes[from_node].edge_sink_node(iconn); + const RRNodeId& from_node = route_ctx.rr_blk_source[blk_id][iclass]; + for (const RREdgeId& iconn : device_ctx.rr_graph.node_out_edges(from_node)) { + const RRNodeId& to_node = device_ctx.rr_graph.edge_sink_node(iconn); - VTR_ASSERT(device_ctx.rr_nodes[to_node].type() == OPIN); + VTR_ASSERT(device_ctx.rr_graph.node_type(to_node) == OPIN); //Add the OPIN to the heap according to it's congestion cost cost = get_rr_cong_cost(to_node); - node_to_heap(to_node, cost, OPEN, OPEN, 0., 0.); + node_to_heap(to_node, cost, RRNodeId::INVALID(), RREdgeId::INVALID(), 0., 0.); } - for (ipin = 0; ipin < num_local_opin; ipin++) { + for (int ipin = 0; ipin < num_local_opin; ipin++) { //Pop the nodes off the heap. We get them from the heap so we //reserve those pins with lowest congestion cost first. heap_head_ptr = get_heap_head(); - inode = heap_head_ptr->index; + const RRNodeId& inode = heap_head_ptr->index; - VTR_ASSERT(device_ctx.rr_nodes[inode].type() == OPIN); + VTR_ASSERT(device_ctx.rr_graph.node_type(inode) == OPIN); adjust_one_rr_occ_and_apcost(inode, 1, pres_fac, acc_fac); route_ctx.clb_opins_used_locally[blk_id][iclass][ipin] = inode; @@ -1672,7 +1666,7 @@ void reserve_locally_used_opins(float pres_fac, float acc_fac, bool rip_up_local } } -static void adjust_one_rr_occ_and_apcost(int inode, int add_or_sub, float pres_fac, float acc_fac) { +static void adjust_one_rr_occ_and_apcost(const RRNodeId& inode, int add_or_sub, float pres_fac, float acc_fac) { /* Increments or decrements (depending on add_or_sub) the occupancy of * * one rr_node, and adjusts the present cost of that node appropriately. */ @@ -1680,7 +1674,7 @@ static void adjust_one_rr_occ_and_apcost(int inode, int add_or_sub, float pres_f auto& device_ctx = g_vpr_ctx.device(); int new_occ = route_ctx.rr_node_route_inf[inode].occ() + add_or_sub; - int capacity = device_ctx.rr_nodes[inode].capacity(); + int capacity = device_ctx.rr_graph.node_capacity(inode); route_ctx.rr_node_route_inf[inode].set_occ(new_occ); if (new_occ < capacity) { @@ -1711,11 +1705,11 @@ void print_traceback(ClusterNetId net_id) { VTR_LOG("traceback %zu: ", size_t(net_id)); t_trace* head = route_ctx.trace[net_id].head; while (head) { - int inode{head->index}; - if (device_ctx.rr_nodes[inode].type() == SINK) - VTR_LOG("%d(sink)(%d)->", inode, route_ctx.rr_node_route_inf[inode].occ()); + RRNodeId inode{head->index}; + if (device_ctx.rr_graph.node_type(inode) == SINK) + VTR_LOG("%ld(sink)(%d)->", size_t(inode), route_ctx.rr_node_route_inf[inode].occ()); else - VTR_LOG("%d(%d)->", inode, route_ctx.rr_node_route_inf[inode].occ()); + VTR_LOG("%ld(%d)->", size_t(inode), route_ctx.rr_node_route_inf[inode].occ()); head = head->next; } VTR_LOG("\n"); @@ -1726,8 +1720,8 @@ void print_traceback(const t_trace* trace) { auto& route_ctx = g_vpr_ctx.routing(); const t_trace* prev = nullptr; while (trace) { - int inode = trace->index; - VTR_LOG("%d (%s)", inode, rr_node_typename[device_ctx.rr_nodes[inode].type()]); + RRNodeId inode = trace->index; + VTR_LOG("%ld (%s)", size_t(inode), rr_node_typename[device_ctx.rr_graph.node_type(inode)]); if (trace->iswitch == OPEN) { VTR_LOG(" !"); //End of branch @@ -1737,7 +1731,7 @@ void print_traceback(const t_trace* trace) { VTR_LOG("*"); //Reached non-configurably } - if (route_ctx.rr_node_route_inf[inode].occ() > device_ctx.rr_nodes[inode].capacity()) { + if (route_ctx.rr_node_route_inf[inode].occ() > device_ctx.rr_graph.node_capacity(inode)) { VTR_LOG(" x"); //Overused } VTR_LOG("\n"); @@ -1748,12 +1742,12 @@ void print_traceback(const t_trace* trace) { } bool validate_traceback(t_trace* trace) { - std::set seen_rr_nodes; + std::set seen_rr_nodes; return validate_traceback_recurr(trace, seen_rr_nodes); } -bool validate_traceback_recurr(t_trace* trace, std::set& seen_rr_nodes) { +bool validate_traceback_recurr(t_trace* trace, std::set& seen_rr_nodes) { if (!trace) { return true; } @@ -1767,7 +1761,7 @@ bool validate_traceback_recurr(t_trace* trace, std::set& seen_rr_nodes) { //Verify that the next element (branch point) has been already seen in the traceback so far if (!seen_rr_nodes.count(next->index)) { - VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "Traceback branch point %d not found", next->index); + VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "Traceback branch point %ld not found", size_t(next->index)); } else { //Recurse along the new branch return validate_traceback_recurr(next, seen_rr_nodes); @@ -1779,25 +1773,25 @@ bool validate_traceback_recurr(t_trace* trace, std::set& seen_rr_nodes) { auto& device_ctx = g_vpr_ctx.device(); bool found = false; - for (t_edge_size iedge = 0; iedge < device_ctx.rr_nodes[trace->index].num_edges(); ++iedge) { - int to_node = device_ctx.rr_nodes[trace->index].edge_sink_node(iedge); + for (const RREdgeId& iedge : device_ctx.rr_graph.node_out_edges(trace->index)) { + RRNodeId to_node = device_ctx.rr_graph.edge_sink_node(iedge); if (to_node == next->index) { found = true; //Verify that the switch matches - int rr_iswitch = device_ctx.rr_nodes[trace->index].edge_switch(iedge); + int rr_iswitch = (int)size_t(device_ctx.rr_graph.edge_switch(iedge)); if (trace->iswitch != rr_iswitch) { - VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "Traceback mismatched switch type: traceback %d rr_graph %d (RR nodes %d -> %d)\n", + VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "Traceback mismatched switch type: traceback %d rr_graph %d (RR nodes %ld -> %ld)\n", trace->iswitch, rr_iswitch, - trace->index, to_node); + size_t(trace->index), size_t(to_node)); } break; } } if (!found) { - VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "Traceback no RR edge between RR nodes %d -> %d\n", trace->index, next->index); + VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "Traceback no RR edge between RR nodes %ld -> %ld\n", size_t(trace->index), size_t(next->index)); } //Recurse @@ -1845,14 +1839,14 @@ void print_invalid_routing_info() { void print_rr_node_route_inf() { auto& route_ctx = g_vpr_ctx.routing(); auto& device_ctx = g_vpr_ctx.device(); - for (size_t inode = 0; inode < route_ctx.rr_node_route_inf.size(); ++inode) { + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { if (!std::isinf(route_ctx.rr_node_route_inf[inode].path_cost)) { - int prev_node = route_ctx.rr_node_route_inf[inode].prev_node; - int prev_edge = route_ctx.rr_node_route_inf[inode].prev_edge; + RRNodeId prev_node = route_ctx.rr_node_route_inf[inode].prev_node; + RREdgeId prev_edge = route_ctx.rr_node_route_inf[inode].prev_edge; VTR_LOG("rr_node: %d prev_node: %d prev_edge: %d", - inode, prev_node, prev_edge); + size_t(inode), size_t(prev_node), size_t(prev_edge)); - if (prev_node != OPEN && prev_edge != OPEN && !device_ctx.rr_nodes[prev_node].edge_is_configurable(prev_edge)) { + if (prev_node != RRNodeId::INVALID() && prev_edge != RREdgeId::INVALID() && !device_ctx.rr_graph.edge_is_configurable(prev_edge)) { VTR_LOG("*"); } @@ -1868,23 +1862,24 @@ void print_rr_node_route_inf_dot() { VTR_LOG("digraph G {\n"); VTR_LOG("\tnode[shape=record]\n"); - for (size_t inode = 0; inode < route_ctx.rr_node_route_inf.size(); ++inode) { + + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { if (!std::isinf(route_ctx.rr_node_route_inf[inode].path_cost)) { - VTR_LOG("\tnode%zu[label=\"{%zu (%s)", inode, inode, device_ctx.rr_nodes[inode].type_string()); - if (route_ctx.rr_node_route_inf[inode].occ() > device_ctx.rr_nodes[inode].capacity()) { + VTR_LOG("\tnode%zu[label=\"{%zu (%s)", size_t(inode), size_t(inode), rr_node_typename[device_ctx.rr_graph.node_type(inode)]); + if (route_ctx.rr_node_route_inf[inode].occ() > device_ctx.rr_graph.node_capacity(inode)) { VTR_LOG(" x"); } VTR_LOG("}\"]\n"); } } - for (size_t inode = 0; inode < route_ctx.rr_node_route_inf.size(); ++inode) { + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { if (!std::isinf(route_ctx.rr_node_route_inf[inode].path_cost)) { - int prev_node = route_ctx.rr_node_route_inf[inode].prev_node; - int prev_edge = route_ctx.rr_node_route_inf[inode].prev_edge; + RRNodeId prev_node = route_ctx.rr_node_route_inf[inode].prev_node; + RREdgeId prev_edge = route_ctx.rr_node_route_inf[inode].prev_edge; - if (prev_node != OPEN && prev_edge != OPEN) { - VTR_LOG("\tnode%d -> node%zu [", prev_node, inode); - if (prev_node != OPEN && prev_edge != OPEN && !device_ctx.rr_nodes[prev_node].edge_is_configurable(prev_edge)) { + if (prev_node != RRNodeId::INVALID() && prev_edge != RREdgeId::INVALID()) { + VTR_LOG("\tnode%ld -> node%ld [", size_t(prev_node), size_t(inode)); + if (prev_node != RRNodeId::INVALID() && prev_edge != RREdgeId::INVALID() && !device_ctx.rr_graph.edge_is_configurable(prev_edge)) { VTR_LOG("label=\"*\""); } @@ -1896,26 +1891,35 @@ void print_rr_node_route_inf_dot() { VTR_LOG("}\n"); } -static bool validate_trace_nodes(t_trace* head, const std::unordered_set& trace_nodes) { +static bool validate_trace_nodes(t_trace* head, const std::unordered_set& trace_nodes) { //Verifies that all nodes in the traceback 'head' are conatined in 'trace_nodes' if (!head) { return true; } - std::vector missing_from_trace_nodes; + std::vector missing_from_trace_nodes; for (t_trace* tptr = head; tptr != nullptr; tptr = tptr->next) { if (!trace_nodes.count(tptr->index)) { missing_from_trace_nodes.push_back(tptr->index); } } + if (!missing_from_trace_nodes.empty()) { + std::string missing_from_trace_nodes_str; + for (const RRNodeId& node : missing_from_trace_nodes) { + if (&node != &missing_from_trace_nodes[0]) { + missing_from_trace_nodes_str += std::string(", "); + } + missing_from_trace_nodes_str += std::to_string(size_t(node)); + } + std::string msg = vtr::string_fmt( "The following %zu nodes were found in traceback" " but were missing from trace_nodes: %s\n", missing_from_trace_nodes.size(), - vtr::join(missing_from_trace_nodes, ", ").c_str()); + missing_from_trace_nodes_str.c_str()); VPR_FATAL_ERROR(VPR_ERROR_ROUTE, msg.c_str()); return false; diff --git a/vpr/src/route/route_common.h b/vpr/src/route/route_common.h index 86fb216a5..bf02d76bc 100644 --- a/vpr/src/route/route_common.h +++ b/vpr/src/route/route_common.h @@ -41,11 +41,18 @@ struct t_heap { RRNodeId index = RRNodeId::INVALID(); struct t_prev { - RRNodeId& node; - RREdgeId& edge; + RRNodeId node; + RREdgeId edge; }; - union { + /* Xifan Tang - type union was used here, + * but it causes an error in vtr_memory.h + * when allocating the data structure. + * I change to struct here. + * TODO: investigate the source of errors + * and see if this will cause memory overhead + */ + struct { t_heap* next; t_prev prev; } u; @@ -60,7 +67,7 @@ t_bb load_net_route_bb(ClusterNetId net_id, int bb_factor); void pathfinder_update_path_cost(t_trace* route_segment_start, int add_or_sub, float pres_fac); -void pathfinder_update_single_node_cost(int inode, int add_or_sub, float pres_fac); +void pathfinder_update_single_node_cost(const RRNodeId& inode, int add_or_sub, float pres_fac); void pathfinder_update_cost(float pres_fac, float acc_fac); @@ -71,7 +78,7 @@ void reset_path_costs(const std::vector& visited_rr_nodes); float get_rr_cong_cost(const RRNodeId& inode); void mark_ends(ClusterNetId net_id); -void mark_remaining_ends(const std::vector& remaining_sinks); +void mark_remaining_ends(const std::vector& remaining_sinks); void add_to_heap(t_heap* hptr); t_heap* alloc_heap_data(); From 5139b5d194eec67fe2e2ea69a2ca3980ff91d0cb Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 1 Feb 2020 17:16:31 -0700 Subject: [PATCH 074/645] adapt route_timing to use RRGraph object --- vpr/src/route/connection_based_routing.h | 2 +- vpr/src/route/route_common.cpp | 6 +- vpr/src/route/route_common.h | 2 +- vpr/src/route/route_timing.cpp | 167 ++++++++++++----------- vpr/src/route/route_timing.h | 4 +- 5 files changed, 92 insertions(+), 89 deletions(-) diff --git a/vpr/src/route/connection_based_routing.h b/vpr/src/route/connection_based_routing.h index 0a4049e8c..d604607f8 100644 --- a/vpr/src/route/connection_based_routing.h +++ b/vpr/src/route/connection_based_routing.h @@ -34,7 +34,7 @@ class Connection_based_routing_resources { Connection_based_routing_resources(); // adding to the resources when they are reached during pruning // mark rr sink node as something that still needs to be reached - void toreach_rr_sink(int rr_sink_node) { remaining_targets.push_back(rr_sink_node); } + void toreach_rr_sink(const int& rr_sink_node) { remaining_targets.push_back(rr_sink_node); } // mark rt sink node as something that has been legally reached void reached_rt_sink(t_rt_node* rt_sink) { reached_rt_sinks.push_back(rt_sink); } diff --git a/vpr/src/route/route_common.cpp b/vpr/src/route/route_common.cpp index 5ae9086c3..30fd9811a 100644 --- a/vpr/src/route/route_common.cpp +++ b/vpr/src/route/route_common.cpp @@ -767,11 +767,11 @@ void mark_ends(ClusterNetId net_id) { } } -void mark_remaining_ends(const std::vector& remaining_sinks) { +void mark_remaining_ends(const std::vector& remaining_sinks) { // like mark_ends, but only performs it for the remaining sinks of a net auto& route_ctx = g_vpr_ctx.mutable_routing(); - for (const RRNodeId& sink_node : remaining_sinks) - ++route_ctx.rr_node_route_inf[sink_node].target_flag; + for (const int& sink_node : remaining_sinks) + ++route_ctx.rr_node_route_inf[RRNodeId(sink_node)].target_flag; } void node_to_heap(const RRNodeId& inode, float total_cost, const RRNodeId& prev_node, const RREdgeId& prev_edge, float backward_path_cost, float R_upstream) { diff --git a/vpr/src/route/route_common.h b/vpr/src/route/route_common.h index bf02d76bc..a7aa9503c 100644 --- a/vpr/src/route/route_common.h +++ b/vpr/src/route/route_common.h @@ -78,7 +78,7 @@ void reset_path_costs(const std::vector& visited_rr_nodes); float get_rr_cong_cost(const RRNodeId& inode); void mark_ends(ClusterNetId net_id); -void mark_remaining_ends(const std::vector& remaining_sinks); +void mark_remaining_ends(const std::vector& remaining_sinks); void add_to_heap(t_heap* hptr); t_heap* alloc_heap_data(); diff --git a/vpr/src/route/route_timing.cpp b/vpr/src/route/route_timing.cpp index 47d1a3b2c..156197be7 100644 --- a/vpr/src/route/route_timing.cpp +++ b/vpr/src/route/route_timing.cpp @@ -148,7 +148,7 @@ static bool timing_driven_route_sink(ClusterNetId net_id, static bool timing_driven_pre_route_to_clock_root( ClusterNetId net_id, - int sink_node, + const RRNodeId& sink_node, const t_conn_cost_params cost_params, float pres_fac, int high_fanout_threshold, @@ -158,12 +158,12 @@ static bool timing_driven_pre_route_to_clock_root( RouterStats& router_stats); static t_heap* timing_driven_route_connection_from_route_tree_high_fanout(t_rt_node* rt_root, - int sink_node, + const RRNodeId& sink_node, const t_conn_cost_params cost_params, t_bb bounding_box, const RouterLookahead& router_lookahead, const SpatialRouteTreeLookup& spatial_rt_lookup, - std::vector& modified_rr_node_inf, + std::vector& modified_rr_node_inf, RouterStats& router_stats); static t_heap* timing_driven_route_connection_from_heap(const RRNodeId& sink_node, @@ -173,9 +173,9 @@ static t_heap* timing_driven_route_connection_from_heap(const RRNodeId& sink_nod std::vector& modified_rr_node_inf, RouterStats& router_stats); -static std::vector timing_driven_find_all_shortest_paths_from_heap(const t_conn_cost_params cost_params, +static vtr::vector timing_driven_find_all_shortest_paths_from_heap(const t_conn_cost_params cost_params, t_bb bounding_box, - std::vector& modified_rr_node_inf, + std::vector& modified_rr_node_inf, RouterStats& router_stats); void disable_expansion_and_remove_sink_from_route_tree_nodes(t_rt_node* node); @@ -196,7 +196,7 @@ static void add_route_tree_to_heap(t_rt_node* rt_node, RouterStats& router_stats); static t_bb add_high_fanout_route_tree_to_heap(t_rt_node* rt_root, - int target_node, + const RRNodeId& target_node, const t_conn_cost_params cost_params, const RouterLookahead& router_lookahead, const SpatialRouteTreeLookup& spatial_route_tree_lookup, @@ -246,13 +246,13 @@ static void timing_driven_expand_node(const t_conn_cost_params cost_params, const RREdgeId& iconn, const RRNodeId& target_node); -static void evaluate_timing_driven_node_costs(t_heap* from, +static void evaluate_timing_driven_node_costs(t_heap* to, const t_conn_cost_params cost_params, const RouterLookahead& router_lookahead, - const int from_node, - const int to_node, - const int iconn, - const int target_node); + const RRNodeId& from_node, + const RRNodeId& to_node, + const RREdgeId& iconn, + const RRNodeId& target_node); static bool timing_driven_check_net_delays(vtr::vector& net_delay); @@ -286,7 +286,7 @@ static void print_route_status(int itry, std::shared_ptr timing_info, float est_success_iteration); -static std::string describe_unrouteable_connection(const int source_node, const int sink_node); +static std::string describe_unrouteable_connection(const RRNodeId& source_node, const RRNodeId& sink_node); static bool is_high_fanout(int fanout, int fanout_threshold); @@ -310,7 +310,7 @@ static void generate_route_timing_reports(const t_router_opts& router_opts, static void prune_unused_non_configurable_nets(CBRR& connections_inf); -static bool same_non_config_node_set(int from_node, int to_node); +static bool same_non_config_node_set(const RRNodeId& from_node, const RRNodeId& to_node); /************************ Subroutine definitions *****************************/ bool try_timing_driven_route(const t_router_opts& router_opts, @@ -1008,7 +1008,7 @@ bool timing_driven_route_net(ClusterNetId net_id, // Pre-route to clock source for clock nets (marked as global nets) if (cluster_ctx.clb_nlist.net_is_global(net_id) && router_opts.two_stage_clock_routing) { VTR_ASSERT(router_opts.clock_modeling == DEDICATED_NETWORK); - int sink_node = device_ctx.virtual_clock_network_root_idx; + RRNodeId sink_node = RRNodeId(device_ctx.virtual_clock_network_root_idx); enable_router_debug(router_opts, net_id, sink_node); // Set to the max timing criticality which should intern minimize clock insertion // delay by selecting a direct route from the clock source to the virtual sink @@ -1030,7 +1030,7 @@ bool timing_driven_route_net(ClusterNetId net_id, for (unsigned itarget = 0; itarget < remaining_targets.size(); ++itarget) { int target_pin = remaining_targets[itarget]; - int sink_rr = route_ctx.net_rr_terminals[net_id][target_pin]; + RRNodeId sink_rr = route_ctx.net_rr_terminals[net_id][target_pin]; enable_router_debug(router_opts, net_id, sink_rr); @@ -1069,12 +1069,12 @@ bool timing_driven_route_net(ClusterNetId net_id, if (!cluster_ctx.clb_nlist.net_is_ignored(net_id)) { for (unsigned ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net_id).size(); ++ipin) { if (net_delay[ipin] == 0) { // should be SOURCE->OPIN->IPIN->SINK - VTR_ASSERT(device_ctx.rr_nodes[rt_node_of_sink[ipin]->parent_node->parent_node->inode].type() == OPIN); + VTR_ASSERT(device_ctx.rr_graph.node_type(RRNodeId(rt_node_of_sink[ipin]->parent_node->parent_node->inode)) == OPIN); } } } - VTR_ASSERT_MSG(route_ctx.rr_node_route_inf[rt_root->inode].occ() <= device_ctx.rr_nodes[rt_root->inode].capacity(), "SOURCE should never be congested"); + VTR_ASSERT_MSG(route_ctx.rr_node_route_inf[rt_root->inode].occ() <= device_ctx.rr_graph.node_capacity(rt_root->inode), "SOURCE should never be congested"); // route tree is not kept persistent since building it from the traceback the next iteration takes almost 0 time VTR_LOGV_DEBUG(f_router_debug, "Routed Net %zu (%zu sinks)\n", size_t(net_id), num_sinks); @@ -1084,7 +1084,7 @@ bool timing_driven_route_net(ClusterNetId net_id, static bool timing_driven_pre_route_to_clock_root( ClusterNetId net_id, - int sink_node, + const RRNodeId& sink_node, const t_conn_cost_params cost_params, float pres_fac, int high_fanout_threshold, @@ -1100,7 +1100,7 @@ static bool timing_driven_pre_route_to_clock_root( VTR_LOGV_DEBUG(f_router_debug, "Net %zu pre-route to (%s)\n", size_t(net_id), describe_rr_node(sink_node).c_str()); - std::vector modified_rr_node_inf; + std::vector modified_rr_node_inf; profiling::sink_criticality_start(); @@ -1198,13 +1198,13 @@ static bool timing_driven_route_sink(ClusterNetId net_id, profiling::sink_criticality_start(); - int sink_node = route_ctx.net_rr_terminals[net_id][target_pin]; + RRNodeId sink_node = route_ctx.net_rr_terminals[net_id][target_pin]; VTR_LOGV_DEBUG(f_router_debug, "Net %zu Target %d (%s)\n", size_t(net_id), itarget, describe_rr_node(sink_node).c_str()); VTR_ASSERT_DEBUG(verify_traceback_route_tree_equivalent(route_ctx.trace[net_id].head, rt_root)); - std::vector modified_rr_node_inf; + std::vector modified_rr_node_inf; t_heap* cheapest = nullptr; t_bb bounding_box = route_ctx.route_bb[net_id]; @@ -1265,7 +1265,7 @@ static bool timing_driven_route_sink(ClusterNetId net_id, * lets me reuse all the routines written for breadth-first routing, which * * all take a traceback structure as input. */ - int inode = cheapest->index; + RRNodeId inode = cheapest->index; route_ctx.rr_node_route_inf[inode].target_flag--; /* Connected to this SINK. */ t_trace* new_route_start_tptr = update_traceback(cheapest, net_id); VTR_ASSERT_DEBUG(validate_traceback(route_ctx.trace[net_id].head)); @@ -1388,19 +1388,19 @@ t_heap* timing_driven_route_connection_from_route_tree(t_rt_node* rt_root, //Unlike timing_driven_route_connection_from_route_tree(), only part of the route tree //which is spatially close to the sink is added to the heap. static t_heap* timing_driven_route_connection_from_route_tree_high_fanout(t_rt_node* rt_root, - int sink_node, + const RRNodeId& sink_node, const t_conn_cost_params cost_params, t_bb net_bounding_box, const RouterLookahead& router_lookahead, const SpatialRouteTreeLookup& spatial_rt_lookup, - std::vector& modified_rr_node_inf, + std::vector& modified_rr_node_inf, RouterStats& router_stats) { // re-explore route tree from root to add any new nodes (buildheap afterwards) // route tree needs to be repushed onto the heap since each node's cost is target specific t_bb high_fanout_bb = add_high_fanout_route_tree_to_heap(rt_root, sink_node, cost_params, router_lookahead, spatial_rt_lookup, net_bounding_box, router_stats); heap_::build_heap(); // via sifting down everything - int source_node = rt_root->inode; + RRNodeId source_node = rt_root->inode; if (is_empty_heap()) { VTR_LOG("No source in route tree: %s\n", describe_unrouteable_connection(source_node, sink_node).c_str()); @@ -1409,7 +1409,8 @@ static t_heap* timing_driven_route_connection_from_route_tree_high_fanout(t_rt_n return nullptr; } - VTR_LOGV_DEBUG(f_router_debug, " Routing to %d as high fanout net (BB: %d,%d x %d,%d)\n", sink_node, + VTR_LOGV_DEBUG(f_router_debug, " Routing to %ld as high fanout net (BB: %d,%d x %d,%d)\n", + size_t(sink_node), high_fanout_bb.xmin, high_fanout_bb.ymin, high_fanout_bb.xmax, high_fanout_bb.ymax); @@ -1423,7 +1424,7 @@ static t_heap* timing_driven_route_connection_from_route_tree_high_fanout(t_rt_n if (cheapest == nullptr) { //Found no path, that may be due to an unlucky choice of existing route tree sub-set, //try again with the full route tree to be sure this is not an artifact of high-fanout routing - VTR_LOG_WARN("No routing path found in high-fanout mode for net connection (to sink_rr %d), retrying with full route tree\n", sink_node); + VTR_LOG_WARN("No routing path found in high-fanout mode for net connection (to sink_rr %ld), retrying with full route tree\n", size_t(sink_node)); //Reset any previously recorded node costs so timing_driven_route_connection() //starts over from scratch. @@ -1504,13 +1505,13 @@ static t_heap* timing_driven_route_connection_from_heap(const RRNodeId& sink_nod } //Find shortest paths from specified route tree to all nodes in the RR graph -std::vector timing_driven_find_all_shortest_paths_from_route_tree(t_rt_node* rt_root, +vtr::vector timing_driven_find_all_shortest_paths_from_route_tree(t_rt_node* rt_root, const t_conn_cost_params cost_params, t_bb bounding_box, - std::vector& modified_rr_node_inf, + std::vector& modified_rr_node_inf, RouterStats& router_stats) { //Add the route tree to the heap with no specific target node - int target_node = OPEN; + RRNodeId target_node = RRNodeId::INVALID(); auto router_lookahead = make_router_lookahead(e_router_lookahead::NO_OP, /*write_lookahead=*/"", /*read_lookahead=*/"", /*segment_inf=*/{}); @@ -1529,16 +1530,16 @@ std::vector timing_driven_find_all_shortest_paths_from_route_tree(t_rt_n // //Note that to re-use code used for the regular A*-based router we use a //no-operation lookahead which always returns zero. -static std::vector timing_driven_find_all_shortest_paths_from_heap(const t_conn_cost_params cost_params, +static vtr::vector timing_driven_find_all_shortest_paths_from_heap(const t_conn_cost_params cost_params, t_bb bounding_box, - std::vector& modified_rr_node_inf, + std::vector& modified_rr_node_inf, RouterStats& router_stats) { auto router_lookahead = make_router_lookahead(e_router_lookahead::NO_OP, /*write_lookahead=*/"", /*read_lookahead=*/"", /*segment_inf=*/{}); auto& device_ctx = g_vpr_ctx.device(); - std::vector cheapest_paths(device_ctx.rr_nodes.size()); + vtr::vector cheapest_paths(device_ctx.rr_graph.nodes().size()); VTR_ASSERT_SAFE(heap_::is_valid()); @@ -1551,16 +1552,16 @@ static std::vector timing_driven_find_all_shortest_paths_from_heap(const t_heap* cheapest = get_heap_head(); ++router_stats.heap_pops; - int inode = cheapest->index; - VTR_LOGV_DEBUG(f_router_debug, " Popping node %d (cost: %g)\n", - inode, cheapest->cost); + RRNodeId inode = cheapest->index; + VTR_LOGV_DEBUG(f_router_debug, " Popping node %ld (cost: %g)\n", + size_t(inode), cheapest->cost); //Since we want to find shortest paths to all nodes in the graph //we do not specify a target node. // //By setting the target_node to OPEN in combination with the NoOp router //lookahead we can re-use the node exploration code from the regular router - int target_node = OPEN; + RRNodeId target_node = RRNodeId::INVALID(); timing_driven_expand_cheapest(cheapest, target_node, @@ -1570,11 +1571,11 @@ static std::vector timing_driven_find_all_shortest_paths_from_heap(const modified_rr_node_inf, router_stats); - if (cheapest_paths[inode].index == OPEN || cheapest_paths[inode].cost >= cheapest->cost) { - VTR_LOGV_DEBUG(f_router_debug, " Better cost to node %d: %g (was %g)\n", inode, cheapest->cost, cheapest_paths[inode].cost); + if (cheapest_paths[inode].index == RRNodeId::INVALID() || cheapest_paths[inode].cost >= cheapest->cost) { + VTR_LOGV_DEBUG(f_router_debug, " Better cost to node %ld: %g (was %g)\n", size_t(inode), cheapest->cost, cheapest_paths[inode].cost); cheapest_paths[inode] = *cheapest; } else { - VTR_LOGV_DEBUG(f_router_debug, " Worse cost to node %d: %g (better %g)\n", inode, cheapest->cost, cheapest_paths[inode].cost); + VTR_LOGV_DEBUG(f_router_debug, " Worse cost to node %ld: %g (better %g)\n", size_t(inode), cheapest->cost, cheapest_paths[inode].cost); } free_heap_data(cheapest); @@ -1779,9 +1780,9 @@ void disable_expansion_and_remove_sink_from_route_tree_nodes(t_rt_node* rt_node) while (linked_rt_edge != nullptr) { child_node = linked_rt_edge->child; - if (device_ctx.rr_nodes[child_node->inode].type() == SINK) { + if (device_ctx.rr_graph.node_type(child_node->inode) == SINK) { VTR_LOGV_DEBUG(f_router_debug, - "Removing sink %d from route tree\n", child_node->inode); + "Removing sink %ld from route tree\n", size_t(child_node->inode)); rt_node->u.child_list = nullptr; rt_node->u.next = nullptr; free(child_node); @@ -1789,7 +1790,7 @@ void disable_expansion_and_remove_sink_from_route_tree_nodes(t_rt_node* rt_node) } else { rt_node->re_expand = false; VTR_LOGV_DEBUG(f_router_debug, - "unexpanding: %d in route tree\n", rt_node->inode); + "unexpanding: %ld in route tree\n", size_t(rt_node->inode)); } disable_expansion_and_remove_sink_from_route_tree_nodes(child_node); linked_rt_edge = linked_rt_edge->next; @@ -1830,7 +1831,7 @@ static void add_route_tree_to_heap(t_rt_node* rt_node, } } -static t_bb add_high_fanout_route_tree_to_heap(t_rt_node* rt_root, int target_node, const t_conn_cost_params cost_params, const RouterLookahead& router_lookahead, const SpatialRouteTreeLookup& spatial_rt_lookup, t_bb net_bounding_box, RouterStats& router_stats) { +static t_bb add_high_fanout_route_tree_to_heap(t_rt_node* rt_root, const RRNodeId& target_node, const t_conn_cost_params cost_params, const RouterLookahead& router_lookahead, const SpatialRouteTreeLookup& spatial_rt_lookup, t_bb net_bounding_box, RouterStats& router_stats) { //For high fanout nets we only add those route tree nodes which are spatially close //to the sink. // @@ -1844,18 +1845,16 @@ static t_bb add_high_fanout_route_tree_to_heap(t_rt_node* rt_root, int target_no auto& device_ctx = g_vpr_ctx.device(); //Determine which bin the target node is located in - auto& target_rr_node = device_ctx.rr_nodes[target_node]; - - int target_bin_x = grid_to_bin_x(target_rr_node.xlow(), spatial_rt_lookup); - int target_bin_y = grid_to_bin_y(target_rr_node.ylow(), spatial_rt_lookup); + int target_bin_x = grid_to_bin_x(device_ctx.rr_graph.node_xlow(target_node), spatial_rt_lookup); + int target_bin_y = grid_to_bin_y(device_ctx.rr_graph.node_ylow(target_node), spatial_rt_lookup); int nodes_added = 0; t_bb highfanout_bb; - highfanout_bb.xmin = target_rr_node.xlow(); - highfanout_bb.xmax = target_rr_node.xhigh(); - highfanout_bb.ymin = target_rr_node.ylow(); - highfanout_bb.ymax = target_rr_node.yhigh(); + highfanout_bb.xmin = device_ctx.rr_graph.node_xlow(target_node); + highfanout_bb.xmax = device_ctx.rr_graph.node_xhigh(target_node); + highfanout_bb.ymin = device_ctx.rr_graph.node_ylow(target_node); + highfanout_bb.ymax = device_ctx.rr_graph.node_yhigh(target_node); //Add existing routing starting from the target bin. //If the target's bin has insufficient existing routing add from the surrounding bins @@ -1877,11 +1876,11 @@ static t_bb add_high_fanout_route_tree_to_heap(t_rt_node* rt_root, int target_no add_route_tree_node_to_heap(rt_node, target_node, cost_params, router_lookahead, router_stats); //Update Bounding Box - auto& rr_node = device_ctx.rr_nodes[rt_node->inode]; - highfanout_bb.xmin = std::min(highfanout_bb.xmin, rr_node.xlow()); - highfanout_bb.ymin = std::min(highfanout_bb.ymin, rr_node.ylow()); - highfanout_bb.xmax = std::max(highfanout_bb.xmax, rr_node.xhigh()); - highfanout_bb.ymax = std::max(highfanout_bb.ymax, rr_node.yhigh()); + auto& rr_node = rt_node->inode; + highfanout_bb.xmin = std::min(highfanout_bb.xmin, device_ctx.rr_graph.node_xlow(rr_node)); + highfanout_bb.ymin = std::min(highfanout_bb.ymin, device_ctx.rr_graph.node_ylow(rr_node)); + highfanout_bb.xmax = std::max(highfanout_bb.xmax, device_ctx.rr_graph.node_xhigh(rr_node)); + highfanout_bb.ymax = std::max(highfanout_bb.ymax, device_ctx.rr_graph.node_yhigh(rr_node)); ++nodes_added; } @@ -1933,7 +1932,7 @@ static void add_route_tree_node_to_heap(t_rt_node* rt_node, const t_conn_cost_params cost_params, const RouterLookahead& router_lookahead, RouterStats& router_stats) { - const RRNodeId& inode = rt_node->inode_id; + const RRNodeId& inode = rt_node->inode; float backward_path_cost = cost_params.criticality * rt_node->Tdel; float R_upstream = rt_node->R_upstream; @@ -1973,7 +1972,7 @@ static void timing_driven_expand_neighbours(t_heap* current, auto& device_ctx = g_vpr_ctx.device(); t_bb target_bb; - if (target_node != OPEN) { + if (target_node != RRNodeId::INVALID()) { target_bb.xmin = device_ctx.rr_graph.node_xlow(target_node); target_bb.ymin = device_ctx.rr_graph.node_ylow(target_node); target_bb.xmax = device_ctx.rr_graph.node_xhigh(target_node); @@ -1982,7 +1981,7 @@ static void timing_driven_expand_neighbours(t_heap* current, //For each node associated with the current heap element, expand all of it's neighbours for (const RREdgeId& edge : device_ctx.rr_graph.node_out_edges(current->index)) { - const RRNodeId& to_node = device_ctx.rr_graph.edge_sink_node[edge]; + const RRNodeId& to_node = device_ctx.rr_graph.edge_sink_node(edge); timing_driven_expand_neighbour(current, current->index, edge, to_node, cost_params, @@ -2032,7 +2031,7 @@ static void timing_driven_expand_neighbour(t_heap* current, * the issue of how to cost them properly so they don't get expanded before * * more promising routes, but makes route-throughs (via CLBs) impossible. * * Change this if you want to investigate route-throughs. */ - if (target_node != OPEN) { + if (target_node != RRNodeId::INVALID()) { t_rr_type to_type = device_ctx.rr_graph.node_type(to_node); if (to_type == IPIN) { //Check if this IPIN leads to the target block @@ -2084,8 +2083,8 @@ static void timing_driven_add_to_heap(const t_conn_cost_params cost_params, auto& route_ctx = g_vpr_ctx.routing(); - float best_total_cost = route_ctx.rr_node_route_inf[size_t(to_node)].path_cost; - float best_back_cost = route_ctx.rr_node_route_inf[size_t(to_node)].backward_path_cost; + float best_total_cost = route_ctx.rr_node_route_inf[to_node].path_cost; + float best_back_cost = route_ctx.rr_node_route_inf[to_node].backward_path_cost; float new_total_cost = next->cost; float new_back_cost = next->backward_path_cost; @@ -2316,9 +2315,9 @@ static bool should_route_net(ClusterNetId net_id, CBRR& connections_inf, bool if } for (;;) { - int inode = tptr->index; + RRNodeId inode = tptr->index; int occ = route_ctx.rr_node_route_inf[inode].occ(); - int capacity = device_ctx.rr_nodes[inode].capacity(); + int capacity = device_ctx.rr_graph.node_capacity(inode); if (occ > capacity) { return true; /* overuse detected */ @@ -2327,7 +2326,8 @@ static bool should_route_net(ClusterNetId net_id, CBRR& connections_inf, bool if if (tptr->iswitch == OPEN) { //End of a branch // even if net is fully routed, not complete if parts of it should get ripped up (EXPERIMENTAL) if (if_force_reroute) { - if (connections_inf.should_force_reroute_connection(inode)) { + /* Xifan Tang - TODO: should use RRNodeId */ + if (connections_inf.should_force_reroute_connection(size_t(inode))) { return true; } } @@ -2405,8 +2405,8 @@ Connection_based_routing_resources::Connection_based_routing_resources() // rr sink node index corresponding to this connection terminal auto rr_sink_node = route_ctx.net_rr_terminals[net_id][ipin]; - net_node_to_pin.insert({rr_sink_node, ipin}); - net_forcible_reroute_connection_flag.insert({rr_sink_node, false}); + net_node_to_pin.insert({size_t(rr_sink_node), ipin}); + net_forcible_reroute_connection_flag.insert({size_t(rr_sink_node), false}); } } } @@ -2440,7 +2440,8 @@ void Connection_based_routing_resources::put_sink_rt_nodes_in_net_pins_lookup(co const auto& node_to_pin_mapping = rr_sink_node_to_pin[current_inet]; for (t_rt_node* rt_node : sink_rt_nodes) { - auto mapping = node_to_pin_mapping.find(rt_node->inode); + /* Xifan Tang - TODO: should use RRNodeId later */ + auto mapping = node_to_pin_mapping.find(size_t(rt_node->inode)); if (mapping != node_to_pin_mapping.end()) { rt_node_of_sink[mapping->second] = rt_node; @@ -2463,7 +2464,7 @@ bool Connection_based_routing_resources::sanity_check_lookup() const { VTR_LOG("%d cannot find itself (net %lu)\n", mapping.first, size_t(net_id)); return false; } - VTR_ASSERT(route_ctx.net_rr_terminals[net_id][mapping.second] == mapping.first); + VTR_ASSERT(route_ctx.net_rr_terminals[net_id][mapping.second] == RRNodeId(mapping.first)); } } return true; @@ -2506,7 +2507,8 @@ bool Connection_based_routing_resources::forcibly_reroute_connections(float max_ auto rr_sink_node = route_ctx.net_rr_terminals[net_id][ipin]; //Clear any forced re-routing from the previuos iteration - forcible_reroute_connection_flag[net_id][rr_sink_node] = false; + /* Xifan Tang - TODO: should use RRNodeId later */ + forcible_reroute_connection_flag[net_id][size_t(rr_sink_node)] = false; // skip if connection is internal to a block such that SOURCE->OPIN->IPIN->SINK directly, which would have 0 time delay if (lower_bound_connection_delay[net_id][ipin - 1] == 0) @@ -2527,7 +2529,8 @@ bool Connection_based_routing_resources::forcibly_reroute_connections(float max_ if (net_delay[net_id][ipin] < (lower_bound_connection_delay[net_id][ipin - 1] * connection_delay_optimality_tolerance)) continue; - forcible_reroute_connection_flag[net_id][rr_sink_node] = true; + /* Xifan Tang - TODO: should use RRNodeId later */ + forcible_reroute_connection_flag[net_id][size_t(rr_sink_node)] = true; // note that we don't set forcible_reroute_connection_flag to false when the converse is true // resetting back to false will be done during tree pruning, after the sink has been legally reached any_connection_rerouted = true; @@ -2562,7 +2565,7 @@ static OveruseInfo calculate_overuse_info() { auto& cluster_ctx = g_vpr_ctx.clustering(); auto& route_ctx = g_vpr_ctx.routing(); - std::unordered_set checked_nodes; + std::unordered_set checked_nodes; size_t overused_nodes = 0; size_t total_overuse = 0; @@ -2578,14 +2581,14 @@ static OveruseInfo calculate_overuse_info() { //used by routing, particularly on large devices). for (auto net_id : cluster_ctx.clb_nlist.nets()) { for (t_trace* tptr = route_ctx.trace[net_id].head; tptr != nullptr; tptr = tptr->next) { - int inode = tptr->index; + RRNodeId inode = tptr->index; auto result = checked_nodes.insert(inode); if (!result.second) { //Already counted continue; } - int overuse = route_ctx.rr_node_route_inf[inode].occ() - device_ctx.rr_nodes[inode].capacity(); + int overuse = route_ctx.rr_node_route_inf[inode].occ() - device_ctx.rr_graph.node_capacity(inode); if (overuse > 0) { overused_nodes += 1; @@ -2867,15 +2870,15 @@ static t_bb calc_current_bb(const t_trace* head) { bb.ymax = 0; for (const t_trace* elem = head; elem != nullptr; elem = elem->next) { - const t_rr_node& node = device_ctx.rr_nodes[elem->index]; + const RRNodeId& node = elem->index; //The router interprets RR nodes which cross the boundary as being //'within' of the BB. Only thos which are *strictly* out side the //box are exluded, hence we use the nodes xhigh/yhigh for xmin/xmax, //and xlow/ylow for xmax/ymax calculations - bb.xmin = std::min(bb.xmin, node.xhigh()); - bb.ymin = std::min(bb.ymin, node.yhigh()); - bb.xmax = std::max(bb.xmax, node.xlow()); - bb.ymax = std::max(bb.ymax, node.ylow()); + bb.xmin = std::min(bb.xmin, device_ctx.rr_graph.node_xhigh(node)); + bb.ymin = std::min(bb.ymin, device_ctx.rr_graph.node_yhigh(node)); + bb.xmax = std::max(bb.xmax, device_ctx.rr_graph.node_xlow(node)); + bb.ymax = std::max(bb.ymax, device_ctx.rr_graph.node_ylow(node)); } VTR_ASSERT(bb.xmin <= bb.xmax); @@ -2884,14 +2887,14 @@ static t_bb calc_current_bb(const t_trace* head) { return bb; } -void enable_router_debug(const t_router_opts& router_opts, ClusterNetId net, int sink_rr) { +void enable_router_debug(const t_router_opts& router_opts, ClusterNetId net, const RRNodeId& sink_rr) { bool all_net_debug = (router_opts.router_debug_net == -1); bool specific_net_debug = (router_opts.router_debug_net >= 0); bool specific_sink_debug = (router_opts.router_debug_sink_rr >= 0); bool match_net = (ClusterNetId(router_opts.router_debug_net) == net); - bool match_sink = (router_opts.router_debug_sink_rr == sink_rr); + bool match_sink = (router_opts.router_debug_sink_rr == (int)size_t(sink_rr)); if (all_net_debug) { VTR_ASSERT(!specific_net_debug); diff --git a/vpr/src/route/route_timing.h b/vpr/src/route/route_timing.h index e7c808627..a6232bf88 100644 --- a/vpr/src/route/route_timing.h +++ b/vpr/src/route/route_timing.h @@ -87,10 +87,10 @@ t_heap* timing_driven_route_connection_from_route_tree(t_rt_node* rt_root, std::vector& modified_rr_node_inf, RouterStats& router_stats); -std::vector timing_driven_find_all_shortest_paths_from_route_tree(t_rt_node* rt_root, +vtr::vector timing_driven_find_all_shortest_paths_from_route_tree(t_rt_node* rt_root, const t_conn_cost_params cost_params, t_bb bounding_box, - std::vector& modified_rr_node_inf, + std::vector& modified_rr_node_inf, RouterStats& router_stats); struct timing_driven_route_structs { From 400bd76bdc89e55ea22dbeb31b9ecb88a6722006 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 1 Feb 2020 17:56:14 -0700 Subject: [PATCH 075/645] route_tree_timing adopt RRGraph object --- vpr/src/route/route_tree_timing.cpp | 195 ++++++++++++++-------------- vpr/src/route/route_tree_timing.h | 2 +- 2 files changed, 97 insertions(+), 100 deletions(-) diff --git a/vpr/src/route/route_tree_timing.cpp b/vpr/src/route/route_tree_timing.cpp index 3b947ee43..96f59272f 100644 --- a/vpr/src/route/route_tree_timing.cpp +++ b/vpr/src/route/route_tree_timing.cpp @@ -27,7 +27,7 @@ /* Array below allows mapping from any rr_node to any rt_node currently in * the rt_tree. */ -static std::vector rr_node_to_rt_node; /* [0..device_ctx.rr_graph.nodes().size()-1] */ +static vtr::vector rr_node_to_rt_node; /* [0..device_ctx.rr_graph.nodes().size()-1] */ /* Frees lists for fast addition and deletion of nodes and edges. */ @@ -47,19 +47,19 @@ static void free_linked_rt_edge(t_linked_rt_edge* rt_edge); static t_rt_node* add_subtree_to_route_tree(t_heap* hptr, t_rt_node** sink_rt_node_ptr); -static t_rt_node* add_non_configurable_to_route_tree(const int rr_node, const bool reached_by_non_configurable_edge, std::unordered_set& visited); +static t_rt_node* add_non_configurable_to_route_tree(const RRNodeId& rr_node, const bool reached_by_non_configurable_edge, std::unordered_set& visited); static t_rt_node* update_unbuffered_ancestors_C_downstream(t_rt_node* start_of_new_subtree_rt_node); -bool verify_route_tree_recurr(t_rt_node* node, std::set& seen_nodes); +bool verify_route_tree_recurr(t_rt_node* node, std::set& seen_nodes); static t_rt_node* prune_route_tree_recurr(t_rt_node* node, CBRR& connections_inf, bool congested, std::vector* non_config_node_set_usage); -static t_trace* traceback_to_route_tree_branch(t_trace* trace, std::map& rr_node_to_rt, std::vector* non_config_node_set_usage); +static t_trace* traceback_to_route_tree_branch(t_trace* trace, std::map& rr_node_to_rt, std::vector* non_config_node_set_usage); static std::pair traceback_from_route_tree_recurr(t_trace* head, t_trace* tail, const t_rt_node* node); -void collect_route_tree_connections(const t_rt_node* node, std::set>& connections); +void collect_route_tree_connections(const t_rt_node* node, std::set>& connections); /************************** Subroutine definitions ***************************/ @@ -84,7 +84,7 @@ bool alloc_route_tree_timing_structs(bool exists_ok) { } } - rr_node_to_rt_node = std::vector(device_ctx.rr_graph.nodes().size(), nullptr); + rr_node_to_rt_node = vtr::vector(device_ctx.rr_graph.nodes().size(), nullptr); return true; } @@ -173,7 +173,7 @@ static void free_linked_rt_edge(t_linked_rt_edge* rt_edge) { * node of the rt_tree (which is just the net source). */ t_rt_node* init_route_tree_to_source(ClusterNetId inet) { t_rt_node* rt_root; - int inode; + RRNodeId inode; auto& route_ctx = g_vpr_ctx.routing(); auto& device_ctx = g_vpr_ctx.device(); @@ -187,9 +187,9 @@ t_rt_node* init_route_tree_to_source(ClusterNetId inet) { inode = route_ctx.net_rr_terminals[inet][0]; /* Net source */ rt_root->inode = inode; - rt_root->C_downstream = device_ctx.rr_nodes[inode].C(); - rt_root->R_upstream = device_ctx.rr_nodes[inode].R(); - rt_root->Tdel = 0.5 * device_ctx.rr_nodes[inode].R() * device_ctx.rr_nodes[inode].C(); + rt_root->C_downstream = device_ctx.rr_graph.node_C(inode); + rt_root->R_upstream = device_ctx.rr_graph.node_R(inode); + rt_root->Tdel = 0.5 * device_ctx.rr_graph.node_R(inode) * device_ctx.rr_graph.node_C(inode); rr_node_to_rt_node[inode] = rt_root; return (rt_root); @@ -263,7 +263,7 @@ add_subtree_to_route_tree(t_heap* hptr, t_rt_node** sink_rt_node_ptr) { auto& device_ctx = g_vpr_ctx.device(); auto& route_ctx = g_vpr_ctx.routing(); - int inode = hptr->index; + RRNodeId inode = hptr->index; //if (device_ctx.rr_nodes[inode].type() != SINK) { //VPR_FATAL_ERROR(VPR_ERROR_ROUTE, @@ -286,11 +286,11 @@ add_subtree_to_route_tree(t_heap* hptr, t_rt_node** sink_rt_node_ptr) { downstream_rt_node = sink_rt_node; - std::unordered_set main_branch_visited; - std::unordered_set all_visited; + std::unordered_set main_branch_visited; + std::unordered_set all_visited; inode = hptr->u.prev.node; - t_edge_size iedge = hptr->u.prev.edge; - short iswitch = device_ctx.rr_nodes[inode].edge_switch(iedge); + RREdgeId iedge = hptr->u.prev.edge; + short iswitch = (short)size_t(device_ctx.rr_graph.edge_switch(iedge)); /* For all "new" nodes in the main path */ // inode is node index of previous node @@ -319,7 +319,7 @@ add_subtree_to_route_tree(t_heap* hptr, t_rt_node** sink_rt_node_ptr) { rr_node_to_rt_node[inode] = rt_node; - if (device_ctx.rr_nodes[inode].type() == IPIN) { + if (device_ctx.rr_graph.node_type(inode) == IPIN) { rt_node->re_expand = false; } else { rt_node->re_expand = true; @@ -328,7 +328,7 @@ add_subtree_to_route_tree(t_heap* hptr, t_rt_node** sink_rt_node_ptr) { downstream_rt_node = rt_node; iedge = route_ctx.rr_node_route_inf[inode].prev_edge; inode = route_ctx.rr_node_route_inf[inode].prev_node; - iswitch = device_ctx.rr_nodes[inode].edge_switch(iedge); + iswitch = (short)size_t(device_ctx.rr_graph.edge_switch(iedge)); } //Inode is now the branch point to the old routing; do not need @@ -347,7 +347,7 @@ add_subtree_to_route_tree(t_heap* hptr, t_rt_node** sink_rt_node_ptr) { //Expand (recursively) each of the main-branch nodes adding any //non-configurably connected nodes - for (int rr_node : main_branch_visited) { + for (const RRNodeId& rr_node : main_branch_visited) { add_non_configurable_to_route_tree(rr_node, false, all_visited); } @@ -355,7 +355,7 @@ add_subtree_to_route_tree(t_heap* hptr, t_rt_node** sink_rt_node_ptr) { return (downstream_rt_node); } -static t_rt_node* add_non_configurable_to_route_tree(const int rr_node, const bool reached_by_non_configurable_edge, std::unordered_set& visited) { +static t_rt_node* add_non_configurable_to_route_tree(const RRNodeId& rr_node, const bool reached_by_non_configurable_edge, std::unordered_set& visited) { t_rt_node* rt_node = nullptr; if (!visited.count(rr_node) || !reached_by_non_configurable_edge) { @@ -375,7 +375,7 @@ static t_rt_node* add_non_configurable_to_route_tree(const int rr_node, const bo rt_node->u.child_list = nullptr; rt_node->inode = rr_node; - if (device_ctx.rr_nodes[rr_node].type() == IPIN) { + if (device_ctx.rr_graph.node_type(rr_node) == IPIN) { rt_node->re_expand = false; } else { rt_node->re_expand = true; @@ -385,18 +385,18 @@ static t_rt_node* add_non_configurable_to_route_tree(const int rr_node, const bo } } - for (int iedge : device_ctx.rr_nodes[rr_node].non_configurable_edges()) { + for (const RREdgeId& iedge : device_ctx.rr_graph.node_non_configurable_out_edges(rr_node)) { //Recursive case: expand children - VTR_ASSERT(!device_ctx.rr_nodes[rr_node].edge_is_configurable(iedge)); + VTR_ASSERT(!device_ctx.rr_graph.edge_is_configurable(iedge)); - int to_rr_node = device_ctx.rr_nodes[rr_node].edge_sink_node(iedge); + RRNodeId to_rr_node = device_ctx.rr_graph.edge_sink_node(iedge); //Recurse t_rt_node* child_rt_node = add_non_configurable_to_route_tree(to_rr_node, true, visited); if (!child_rt_node) continue; - int iswitch = device_ctx.rr_nodes[rr_node].edge_switch(iedge); + int iswitch = (short)size_t(device_ctx.rr_graph.edge_switch(iedge)); //Create the edge t_linked_rt_edge* linked_rt_edge = alloc_linked_rt_edge(); @@ -428,7 +428,7 @@ void load_new_subtree_R_upstream(t_rt_node* rt_node) { auto& device_ctx = g_vpr_ctx.device(); t_rt_node* parent_rt_node = rt_node->parent_node; - int inode = rt_node->inode; + RRNodeId inode = rt_node->inode; //Calculate upstream resistance float R_upstream = 0.; @@ -441,7 +441,7 @@ void load_new_subtree_R_upstream(t_rt_node* rt_node) { } R_upstream += device_ctx.rr_switch_inf[iswitch].R; //Parent switch R } - R_upstream += device_ctx.rr_nodes[inode].R(); //Current node R + R_upstream += device_ctx.rr_graph.node_R(inode); //Current node R rt_node->R_upstream = R_upstream; @@ -457,7 +457,7 @@ float load_new_subtree_C_downstream(t_rt_node* rt_node) { if (rt_node) { auto& device_ctx = g_vpr_ctx.device(); - C_downstream += device_ctx.rr_nodes[rt_node->inode].C(); + C_downstream += device_ctx.rr_graph.node_C(rt_node->inode); for (t_linked_rt_edge* edge = rt_node->u.child_list; edge != nullptr; edge = edge->next) { /*Similar to net_delay.cpp, this for loop traverses a rc subtree, whose edges represent enabled switches. * When switches such as multiplexers and tristate buffers are enabled, their fanout @@ -581,9 +581,9 @@ void load_route_tree_rr_route_inf(t_rt_node* root) { t_linked_rt_edge* edge{root->u.child_list}; for (;;) { - int inode = root->inode; - route_ctx.rr_node_route_inf[inode].prev_node = NO_PREVIOUS; - route_ctx.rr_node_route_inf[inode].prev_edge = NO_PREVIOUS; + RRNodeId inode = root->inode; + route_ctx.rr_node_route_inf[inode].prev_node = RRNodeId::INVALID(); + route_ctx.rr_node_route_inf[inode].prev_edge = RREdgeId::INVALID(); // path cost should be unset VTR_ASSERT(std::isinf(route_ctx.rr_node_route_inf[inode].path_cost)); VTR_ASSERT(std::isinf(route_ctx.rr_node_route_inf[inode].backward_path_cost)); @@ -606,13 +606,13 @@ void load_route_tree_rr_route_inf(t_rt_node* root) { } bool verify_route_tree(t_rt_node* root) { - std::set seen_nodes; + std::set seen_nodes; return verify_route_tree_recurr(root, seen_nodes); } -bool verify_route_tree_recurr(t_rt_node* node, std::set& seen_nodes) { +bool verify_route_tree_recurr(t_rt_node* node, std::set& seen_nodes) { if (seen_nodes.count(node->inode)) { - VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "Duplicate route tree nodes found for node %d", node->inode); + VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "Duplicate route tree nodes found for node %ld", size_t(node->inode)); } seen_nodes.insert(node->inode); @@ -657,7 +657,7 @@ void print_route_tree(const t_rt_node* rt_node, int depth) { } auto& device_ctx = g_vpr_ctx.device(); - VTR_LOG("%srt_node: %d (%s)", indent.c_str(), rt_node->inode, device_ctx.rr_nodes[rt_node->inode].type_string()); + VTR_LOG("%srt_node: %ld (%s)", indent.c_str(), size_t(rt_node->inode), rr_node_typename[device_ctx.rr_graph.node_type(rt_node->inode)]); if (rt_node->parent_switch != OPEN) { bool parent_edge_configurable = device_ctx.rr_switch_inf[rt_node->parent_switch].configurable(); @@ -668,7 +668,7 @@ void print_route_tree(const t_rt_node* rt_node, int depth) { auto& route_ctx = g_vpr_ctx.routing(); - if (route_ctx.rr_node_route_inf[rt_node->inode].occ() > device_ctx.rr_nodes[rt_node->inode].capacity()) { + if (route_ctx.rr_node_route_inf[rt_node->inode].occ() > device_ctx.rr_graph.node_capacity(rt_node->inode)) { VTR_LOG(" x"); } @@ -727,7 +727,7 @@ t_rt_node* traceback_to_route_tree(t_trace* head, std::vector* non_config_n VTR_ASSERT_DEBUG(validate_traceback(head)); - std::map rr_node_to_rt; + std::map rr_node_to_rt; t_trace* trace = head; while (trace) { //Each branch @@ -749,14 +749,14 @@ t_rt_node* traceback_to_route_tree(t_trace* head, std::vector* non_config_n // //Returns the t_trace defining the start of the next branch static t_trace* traceback_to_route_tree_branch(t_trace* trace, - std::map& rr_node_to_rt, + std::map& rr_node_to_rt, std::vector* non_config_node_set_usage) { t_trace* next = nullptr; if (trace) { t_rt_node* node = nullptr; - int inode = trace->index; + RRNodeId inode = trace->index; int iswitch = trace->iswitch; auto itr = rr_node_to_rt.find(trace->index); @@ -773,7 +773,7 @@ static t_trace* traceback_to_route_tree_branch(t_trace* trace, node->Tdel = std::numeric_limits::quiet_NaN(); auto& device_ctx = g_vpr_ctx.device(); - auto node_type = device_ctx.rr_nodes[inode].type(); + auto node_type = device_ctx.rr_graph.node_type(inode); if (node_type == IPIN || node_type == SINK) node->re_expand = false; else @@ -899,7 +899,7 @@ t_trace* traceback_from_route_tree(ClusterNetId inet, const t_rt_node* root, int t_trace* head; t_trace* tail; - std::unordered_set nodes; + std::unordered_set nodes; std::tie(head, tail) = traceback_from_route_tree_recurr(nullptr, nullptr, root); @@ -912,7 +912,7 @@ t_trace* traceback_from_route_tree(ClusterNetId inet, const t_rt_node* root, int nodes.insert(trace->index); //Sanity check that number of sinks match expected - if (device_ctx.rr_nodes[trace->index].type() == SINK) { + if (device_ctx.rr_graph.node_type(trace->index) == SINK) { num_trace_sinks += 1; } } @@ -937,7 +937,7 @@ static t_rt_node* prune_route_tree_recurr(t_rt_node* node, CBRR& connections_inf auto& device_ctx = g_vpr_ctx.device(); auto& route_ctx = g_vpr_ctx.routing(); - bool congested = (route_ctx.rr_node_route_inf[node->inode].occ() > device_ctx.rr_nodes[node->inode].capacity()); + bool congested = (route_ctx.rr_node_route_inf[node->inode].occ() > device_ctx.rr_graph.node_capacity(node->inode)); int node_set = -1; auto itr = device_ctx.rr_node_to_non_config_node_set.find(node->inode); if (itr != device_ctx.rr_node_to_non_config_node_set.end()) { @@ -949,7 +949,7 @@ static t_rt_node* prune_route_tree_recurr(t_rt_node* node, CBRR& connections_inf force_prune = true; } - if (connections_inf.should_force_reroute_connection(node->inode)) { + if (connections_inf.should_force_reroute_connection(size_t(node->inode))) { //Forcibly re-route (e.g. to improve delay) force_prune = true; } @@ -995,7 +995,7 @@ static t_rt_node* prune_route_tree_recurr(t_rt_node* node, CBRR& connections_inf } } - if (device_ctx.rr_nodes[node->inode].type() == SINK) { + if (device_ctx.rr_graph.node_type(node->inode) == SINK) { if (!force_prune) { //Valid path to sink @@ -1007,7 +1007,7 @@ static t_rt_node* prune_route_tree_recurr(t_rt_node* node, CBRR& connections_inf VTR_ASSERT(force_prune); //Record as not reached - connections_inf.toreach_rr_sink(node->inode); + connections_inf.toreach_rr_sink(size_t(node->inode)); free_rt_node(node); return nullptr; //Pruned @@ -1131,9 +1131,9 @@ t_rt_node* prune_route_tree(t_rt_node* rt_root, CBRR& connections_inf, std::vect auto& device_ctx = g_vpr_ctx.device(); auto& route_ctx = g_vpr_ctx.routing(); - VTR_ASSERT_MSG(device_ctx.rr_nodes[rt_root->inode].type() == SOURCE, "Root of route tree must be SOURCE"); + VTR_ASSERT_MSG(device_ctx.rr_graph.node_type(rt_root->inode) == SOURCE, "Root of route tree must be SOURCE"); - VTR_ASSERT_MSG(route_ctx.rr_node_route_inf[rt_root->inode].occ() <= device_ctx.rr_nodes[rt_root->inode].capacity(), + VTR_ASSERT_MSG(route_ctx.rr_node_route_inf[rt_root->inode].occ() <= device_ctx.rr_graph.node_capacity(rt_root->inode), "Route tree root/SOURCE should never be congested"); return prune_route_tree_recurr(rt_root, connections_inf, false, non_config_node_set_usage); @@ -1206,7 +1206,7 @@ void print_edge(const t_linked_rt_edge* edge) { return; } while (edge) { - VTR_LOG("%d(%d) ", edge->child->inode, edge->iswitch); + VTR_LOG("%ld(%d) ", size_t(edge->child->inode), edge->iswitch); edge = edge->next; } VTR_LOG("\n"); @@ -1215,31 +1215,30 @@ void print_edge(const t_linked_rt_edge* edge) { static void print_node(const t_rt_node* rt_node) { auto& device_ctx = g_vpr_ctx.device(); - int inode = rt_node->inode; - t_rr_type node_type = device_ctx.rr_nodes[inode].type(); + RRNodeId inode = rt_node->inode; + t_rr_type node_type = device_ctx.rr_graph.node_type(inode); VTR_LOG("%5.1e %5.1e %2d%6s|%-6d-> ", rt_node->C_downstream, rt_node->R_upstream, - rt_node->re_expand, rr_node_typename[node_type], inode); + rt_node->re_expand, rr_node_typename[node_type], size_t(inode)); } static void print_node_inf(const t_rt_node* rt_node) { auto& route_ctx = g_vpr_ctx.routing(); - int inode = rt_node->inode; + RRNodeId inode = rt_node->inode; const auto& node_inf = route_ctx.rr_node_route_inf[inode]; VTR_LOG("%5.1e %5.1e%6d%3d|%-6d-> ", node_inf.path_cost, node_inf.backward_path_cost, - node_inf.prev_node, node_inf.prev_edge, inode); + node_inf.prev_node, node_inf.prev_edge, size_t(inode)); } static void print_node_congestion(const t_rt_node* rt_node) { auto& device_ctx = g_vpr_ctx.device(); auto& route_ctx = g_vpr_ctx.routing(); - int inode = rt_node->inode; + RRNodeId inode = rt_node->inode; const auto& node_inf = route_ctx.rr_node_route_inf[inode]; - const auto& node = device_ctx.rr_nodes[inode]; const auto& node_state = route_ctx.rr_node_route_inf[inode]; VTR_LOG("%2d %2d|%-6d-> ", node_inf.pres_cost, rt_node->Tdel, - node_state.occ(), node.capacity(), inode); + node_state.occ(), device_ctx.rr_graph.node_capacity(inode), size_t(inode)); } void print_route_tree_inf(const t_rt_node* rt_root) { @@ -1263,8 +1262,8 @@ bool is_equivalent_route_tree(const t_rt_node* root, const t_rt_node* root_clone if (!root && !root_clone) return true; if (!root || !root_clone) return false; // one of them is null if ((root->inode != root_clone->inode) || (!equal_approx(root->R_upstream, root_clone->R_upstream)) || (!equal_approx(root->C_downstream, root_clone->C_downstream)) || (!equal_approx(root->Tdel, root_clone->Tdel))) { - VTR_LOG("mismatch i %d|%d R %e|%e C %e|%e T %e %e\n", - root->inode, root_clone->inode, + VTR_LOG("mismatch i %ld|%ld R %e|%e C %e|%e T %e %e\n", + size_t(root->inode), size_t(root_clone->inode), root->R_upstream, root_clone->R_upstream, root->C_downstream, root_clone->C_downstream, root->Tdel, root_clone->Tdel); @@ -1274,8 +1273,8 @@ bool is_equivalent_route_tree(const t_rt_node* root, const t_rt_node* root_clone t_linked_rt_edge* clone_edge{root_clone->u.child_list}; while (orig_edge && clone_edge) { if (orig_edge->iswitch != clone_edge->iswitch) - VTR_LOG("mismatch i %d|%d edge switch %d|%d\n", - root->inode, root_clone->inode, + VTR_LOG("mismatch i %ld|%ld edge switch %d|%d\n", + size_t(root->inode), size_t(root_clone->inode), orig_edge->iswitch, clone_edge->iswitch); if (!is_equivalent_route_tree(orig_edge->child, clone_edge->child)) return false; // child trees not equivalent orig_edge = orig_edge->next; @@ -1290,23 +1289,23 @@ bool is_equivalent_route_tree(const t_rt_node* root, const t_rt_node* root_clone // check only the connections are correct, ignore R and C bool is_valid_skeleton_tree(const t_rt_node* root) { - int inode = root->inode; + RRNodeId inode = root->inode; t_linked_rt_edge* edge = root->u.child_list; while (edge) { if (edge->child->parent_node != root) { - VTR_LOG("parent-child relationship not mutually acknowledged by parent %d->%d child %d<-%d\n", - inode, edge->child->inode, - edge->child->inode, edge->child->parent_node->inode); + VTR_LOG("parent-child relationship not mutually acknowledged by parent %ld->%ld child %ld<-%ld\n", + size_t(inode), size_t(edge->child->inode), + size_t(edge->child->inode), size_t(edge->child->parent_node->inode)); return false; } if (edge->iswitch != edge->child->parent_switch) { - VTR_LOG("parent(%d)-child(%d) connected switch not equivalent parent %d child %d\n", - inode, edge->child->inode, edge->iswitch, edge->child->parent_switch); + VTR_LOG("parent(%ld)-child(%ld) connected switch not equivalent parent %d child %d\n", + size_t(inode), size_t(edge->child->inode), edge->iswitch, edge->child->parent_switch); return false; } if (!is_valid_skeleton_tree(edge->child)) { - VTR_LOG("subtree %d invalid, propagating up\n", edge->child->inode); + VTR_LOG("subtree %ld invalid, propagating up\n", size_t(edge->child->inode)); return false; } edge = edge->next; @@ -1324,24 +1323,24 @@ bool is_valid_route_tree(const t_rt_node* root) { constexpr float RES_REL_TOL = 1e-6; constexpr float RES_ABS_TOL = vtr::DEFAULT_ABS_TOL; - int inode = root->inode; + RRNodeId inode = root->inode; short iswitch = root->parent_switch; if (root->parent_node) { if (device_ctx.rr_switch_inf[iswitch].buffered()) { - float R_upstream_check = device_ctx.rr_nodes[inode].R() + device_ctx.rr_switch_inf[iswitch].R; + float R_upstream_check = device_ctx.rr_graph.node_R(inode) + device_ctx.rr_switch_inf[iswitch].R; if (!vtr::isclose(root->R_upstream, R_upstream_check, RES_REL_TOL, RES_ABS_TOL)) { - VTR_LOG("%d mismatch R upstream %e supposed %e\n", inode, root->R_upstream, R_upstream_check); + VTR_LOG("%ld mismatch R upstream %e supposed %e\n", size_t(inode), root->R_upstream, R_upstream_check); return false; } } else { - float R_upstream_check = device_ctx.rr_nodes[inode].R() + root->parent_node->R_upstream + device_ctx.rr_switch_inf[iswitch].R; + float R_upstream_check = device_ctx.rr_graph.node_R(inode) + root->parent_node->R_upstream + device_ctx.rr_switch_inf[iswitch].R; if (!vtr::isclose(root->R_upstream, R_upstream_check, RES_REL_TOL, RES_ABS_TOL)) { - VTR_LOG("%d mismatch R upstream %e supposed %e\n", inode, root->R_upstream, R_upstream_check); + VTR_LOG("%ld mismatch R upstream %e supposed %e\n", size_t(inode), root->R_upstream, R_upstream_check); return false; } } - } else if (root->R_upstream != device_ctx.rr_nodes[inode].R()) { - VTR_LOG("%d mismatch R upstream %e supposed %e\n", inode, root->R_upstream, device_ctx.rr_nodes[inode].R()); + } else if (root->R_upstream != device_ctx.rr_graph.node_R(inode)) { + VTR_LOG("%ld mismatch R upstream %e supposed %e\n", size_t(inode), root->R_upstream, device_ctx.rr_graph.node_R(inode)); return false; } @@ -1351,22 +1350,22 @@ bool is_valid_route_tree(const t_rt_node* root) { // sink, must not be congested if (!edge) { int occ = route_ctx.rr_node_route_inf[inode].occ(); - int capacity = device_ctx.rr_nodes[inode].capacity(); + int capacity = device_ctx.rr_graph.node_capacity(inode); if (occ > capacity) { - VTR_LOG("SINK %d occ %d > cap %d\n", inode, occ, capacity); + VTR_LOG("SINK %ld occ %d > cap %d\n", size_t(inode), occ, capacity); return false; } } while (edge) { if (edge->child->parent_node != root) { - VTR_LOG("parent-child relationship not mutually acknowledged by parent %d->%d child %d<-%d\n", - inode, edge->child->inode, - edge->child->inode, edge->child->parent_node->inode); + VTR_LOG("parent-child relationship not mutually acknowledged by parent %ld->%ld child %ld<-%ld\n", + size_t(inode), size_t(edge->child->inode), + size_t(edge->child->inode), size_t(edge->child->parent_node->inode)); return false; } if (edge->iswitch != edge->child->parent_switch) { - VTR_LOG("parent(%d)-child(%d) connected switch not equivalent parent %d child %d\n", - inode, edge->child->inode, edge->iswitch, edge->child->parent_switch); + VTR_LOG("parent(%ld)-child(%ld) connected switch not equivalent parent %d child %d\n", + size_t(inode), size_t(edge->child->inode), edge->iswitch, edge->child->parent_switch); return false; } @@ -1383,9 +1382,9 @@ bool is_valid_route_tree(const t_rt_node* root) { edge = edge->next; } - float C_downstream_check = C_downstream_children + device_ctx.rr_nodes[inode].C(); + float C_downstream_check = C_downstream_children + device_ctx.rr_graph.node_C(inode); if (!vtr::isclose(root->C_downstream, C_downstream_check, CAP_REL_TOL, CAP_ABS_TOL)) { - VTR_LOG("%d mismatch C downstream %e supposed %e\n", inode, root->C_downstream, C_downstream_check); + VTR_LOG("%ld mismatch C downstream %e supposed %e\n", size_t(inode), root->C_downstream, C_downstream_check); return false; } @@ -1397,8 +1396,8 @@ bool is_uncongested_route_tree(const t_rt_node* root) { auto& route_ctx = g_vpr_ctx.routing(); auto& device_ctx = g_vpr_ctx.device(); - int inode = root->inode; - if (route_ctx.rr_node_route_inf[inode].occ() > device_ctx.rr_nodes[inode].capacity()) { + RRNodeId inode = root->inode; + if (route_ctx.rr_node_route_inf[inode].occ() > device_ctx.rr_graph.node_capacity(inode)) { //This node is congested return false; } @@ -1428,30 +1427,28 @@ init_route_tree_to_source_no_net(const RRNodeId& inode) { rt_root->parent_node = nullptr; rt_root->parent_switch = OPEN; rt_root->re_expand = true; - rt_root->inode = size_t(inode); - - rt_root->inode_id = inode; + rt_root->inode = inode; rt_root->C_downstream = device_ctx.rr_graph.node_C(inode); rt_root->R_upstream = device_ctx.rr_graph.node_R(inode); rt_root->Tdel = 0.5 * device_ctx.rr_graph.node_R(inode) * device_ctx.rr_graph.node_C(inode); - rr_node_to_rt_node[size_t(inode)] = rt_root; + rr_node_to_rt_node[inode] = rt_root; return (rt_root); } bool verify_traceback_route_tree_equivalent(const t_trace* head, const t_rt_node* rt_root) { //Walk the route tree saving all the used connections - std::set> route_tree_connections; + std::set> route_tree_connections; collect_route_tree_connections(rt_root, route_tree_connections); //Remove the extra parent connection to root (not included in traceback) - route_tree_connections.erase(std::make_tuple(OPEN, OPEN, rt_root->inode)); + route_tree_connections.erase(std::make_tuple(RRNodeId::INVALID(), OPEN, rt_root->inode)); //Walk the traceback and verify that every connection exists in the route tree set - int prev_node = OPEN; + RRNodeId prev_node = RRNodeId::INVALID(); int prev_switch = OPEN; - int to_node = OPEN; + RRNodeId to_node = RRNodeId::INVALID(); for (const t_trace* trace = head; trace != nullptr; trace = trace->next) { to_node = trace->index; @@ -1474,7 +1471,7 @@ bool verify_traceback_route_tree_equivalent(const t_trace* head, const t_rt_node std::string msg = "Found route tree connection(s) not in traceback:\n"; for (auto conn : route_tree_connections) { std::tie(prev_node, prev_switch, to_node) = conn; - msg += vtr::string_fmt("\tnode %d -> %d (switch %d)\n", prev_node, to_node, prev_switch); + msg += vtr::string_fmt("\tnode %ld -> %ld (switch %d)\n", size_t(prev_node), size_t(to_node), prev_switch); } VPR_FATAL_ERROR(VPR_ERROR_ROUTE, msg.c_str()); @@ -1483,12 +1480,12 @@ bool verify_traceback_route_tree_equivalent(const t_trace* head, const t_rt_node return true; } -void collect_route_tree_connections(const t_rt_node* node, std::set>& connections) { +void collect_route_tree_connections(const t_rt_node* node, std::set>& connections) { if (node) { //Record reaching connection - int prev_node = OPEN; + RRNodeId prev_node = RRNodeId::INVALID(); int prev_switch = OPEN; - int to_node = node->inode; + RRNodeId to_node = node->inode; if (node->parent_node) { prev_node = node->parent_node->inode; prev_switch = node->parent_switch; @@ -1517,13 +1514,13 @@ t_rt_node* find_sink_rt_node(t_rt_node* rt_root, ClusterNetId net_id, ClusterPin auto& route_ctx = g_vpr_ctx.routing(); int ipin = cluster_ctx.clb_nlist.pin_net_index(sink_pin); - int sink_rr_inode = route_ctx.net_rr_terminals[net_id][ipin]; //obtain the value of the routing resource sink + RRNodeId sink_rr_inode = route_ctx.net_rr_terminals[net_id][ipin]; //obtain the value of the routing resource sink t_rt_node* sink_rt_node = find_sink_rt_node_recurr(rt_root, sink_rr_inode); //find pointer to route tree node corresponding to sink_rr_inode VTR_ASSERT(sink_rt_node); return sink_rt_node; } -t_rt_node* find_sink_rt_node_recurr(t_rt_node* node, int sink_rr_inode) { +t_rt_node* find_sink_rt_node_recurr(t_rt_node* node, const RRNodeId& sink_rr_inode) { if (node->inode == sink_rr_inode) { //check if current node matches sink_rr_inode return node; } diff --git a/vpr/src/route/route_tree_timing.h b/vpr/src/route/route_tree_timing.h index 781b11e5d..6a3f36c84 100644 --- a/vpr/src/route/route_tree_timing.h +++ b/vpr/src/route/route_tree_timing.h @@ -39,7 +39,7 @@ bool verify_route_tree(t_rt_node* root); bool verify_traceback_route_tree_equivalent(const t_trace* trace_head, const t_rt_node* rt_root); t_rt_node* find_sink_rt_node(t_rt_node* rt_root, ClusterNetId net_id, ClusterPinId sink_pin); -t_rt_node* find_sink_rt_node_recurr(t_rt_node* node, int sink_inode); +t_rt_node* find_sink_rt_node_recurr(t_rt_node* node, const RRNodeId& sink_inode); /********** Incremental reroute ***********/ // instead of ripping up a net that has some congestion, cut the branches From f139a844ca67f3afff9dcff6ef31abf71c276618 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 1 Feb 2020 18:01:05 -0700 Subject: [PATCH 076/645] route util adopt RRGraph object --- vpr/src/route/route_util.cpp | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/vpr/src/route/route_util.cpp b/vpr/src/route/route_util.cpp index b04f0cf12..6d8417963 100644 --- a/vpr/src/route/route_util.cpp +++ b/vpr/src/route/route_util.cpp @@ -11,13 +11,13 @@ vtr::Matrix calculate_routing_usage(t_rr_type rr_type) { vtr::Matrix usage({{device_ctx.grid.width(), device_ctx.grid.height()}}, 0.); //Collect all the in-use RR nodes - std::set rr_nodes; + std::set rr_nodes; for (auto net : cluster_ctx.clb_nlist.nets()) { t_trace* tptr = route_ctx.trace[net].head; while (tptr != nullptr) { - int inode = tptr->index; + RRNodeId inode = tptr->index; - if (device_ctx.rr_nodes[inode].type() == rr_type) { + if (device_ctx.rr_graph.node_type(inode) == rr_type) { rr_nodes.insert(inode); } tptr = tptr->next; @@ -25,24 +25,22 @@ vtr::Matrix calculate_routing_usage(t_rr_type rr_type) { } //Record number of used resources in each x/y channel - for (int inode : rr_nodes) { - auto& rr_node = device_ctx.rr_nodes[inode]; - + for (const RRNodeId& inode : rr_nodes) { if (rr_type == CHANX) { - VTR_ASSERT(rr_node.type() == CHANX); - VTR_ASSERT(rr_node.ylow() == rr_node.yhigh()); + VTR_ASSERT(device_ctx.rr_graph.node_type(inode) == CHANX); + VTR_ASSERT(device_ctx.rr_graph.node_ylow(inode) == device_ctx.rr_graph.node_yhigh(inode)); - int y = rr_node.ylow(); - for (int x = rr_node.xlow(); x <= rr_node.xhigh(); ++x) { + int y = device_ctx.rr_graph.node_ylow(inode); + for (int x = device_ctx.rr_graph.node_xlow(inode); x <= device_ctx.rr_graph.node_xhigh(inode); ++x) { usage[x][y] += route_ctx.rr_node_route_inf[inode].occ(); } } else { VTR_ASSERT(rr_type == CHANY); - VTR_ASSERT(rr_node.type() == CHANY); - VTR_ASSERT(rr_node.xlow() == rr_node.xhigh()); + VTR_ASSERT(device_ctx.rr_graph.node_type(inode) == CHANY); + VTR_ASSERT(device_ctx.rr_graph.node_xlow(inode) == device_ctx.rr_graph.node_xhigh(inode)); - int x = rr_node.xlow(); - for (int y = rr_node.ylow(); y <= rr_node.yhigh(); ++y) { + int x = device_ctx.rr_graph.node_xlow(inode); + for (int y = device_ctx.rr_graph.node_ylow(inode); y <= device_ctx.rr_graph.node_yhigh(inode); ++y) { usage[x][y] += route_ctx.rr_node_route_inf[inode].occ(); } } From 01a2ba78d4efbbe6b78b8fb1bcd293d1f4646f93 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 1 Feb 2020 20:44:55 -0700 Subject: [PATCH 077/645] router lookahead and delay profiling adopt RRGraph object --- vpr/src/route/router_delay_profiling.cpp | 24 +++--- vpr/src/route/router_delay_profiling.h | 2 +- vpr/src/route/router_lookahead_map.cpp | 103 ++++++++++++----------- vpr/src/route/router_lookahead_map.h | 2 +- vpr/src/route/rr_graph.cpp | 4 +- 5 files changed, 68 insertions(+), 67 deletions(-) diff --git a/vpr/src/route/router_delay_profiling.cpp b/vpr/src/route/router_delay_profiling.cpp index 39dbce7fa..60ef4267f 100644 --- a/vpr/src/route/router_delay_profiling.cpp +++ b/vpr/src/route/router_delay_profiling.cpp @@ -7,7 +7,7 @@ #include "route_export.h" #include "rr_graph.h" -static t_rt_node* setup_routing_resources_no_net(int source_node); +static t_rt_node* setup_routing_resources_no_net(const RRNodeId& source_node); RouterDelayProfiler::RouterDelayProfiler( const RouterLookahead* lookahead) @@ -23,7 +23,7 @@ bool RouterDelayProfiler::calculate_delay(const RRNodeId& source_node, const RRN t_rt_node* rt_root = setup_routing_resources_no_net(source_node); /* TODO: This should be changed to RRNodeId */ - enable_router_debug(router_opts, ClusterNetId(), size_t(sink_node)); + enable_router_debug(router_opts, ClusterNetId(), sink_node); /* Update base costs according to fanout and criticality rules */ update_rr_base_costs(1); @@ -44,7 +44,7 @@ bool RouterDelayProfiler::calculate_delay(const RRNodeId& source_node, const RRN init_heap(device_ctx.grid); - std::vector modified_rr_node_inf; + std::vector modified_rr_node_inf; RouterStats router_stats; t_heap* cheapest = timing_driven_route_connection_from_route_tree(rt_root, sink_node, cost_params, bounding_box, *router_lookahead_, @@ -60,7 +60,7 @@ bool RouterDelayProfiler::calculate_delay(const RRNodeId& source_node, const RRN //find delay *net_delay = rt_node_of_sink->Tdel; - VTR_ASSERT_MSG(route_ctx.rr_node_route_inf[rt_root->inode].occ() <= device_ctx.rr_graph.node_capacity(rt_root->inode_id), "SOURCE should never be congested"); + VTR_ASSERT_MSG(route_ctx.rr_node_route_inf[rt_root->inode].occ() <= device_ctx.rr_graph.node_capacity(rt_root->inode), "SOURCE should never be congested"); free_route_tree(rt_root); } @@ -72,10 +72,10 @@ bool RouterDelayProfiler::calculate_delay(const RRNodeId& source_node, const RRN } //Returns the shortest path delay from src_node to all RR nodes in the RR graph, or NaN if no path exists -std::vector calculate_all_path_delays_from_rr_node(int src_rr_node, const t_router_opts& router_opts) { +vtr::vector calculate_all_path_delays_from_rr_node(const RRNodeId& src_rr_node, const t_router_opts& router_opts) { auto& device_ctx = g_vpr_ctx.device(); - std::vector path_delays_to(device_ctx.rr_nodes.size(), std::numeric_limits::quiet_NaN()); + vtr::vector path_delays_to(device_ctx.rr_graph.nodes().size(), std::numeric_limits::quiet_NaN()); t_rt_node* rt_root = setup_routing_resources_no_net(src_rr_node); @@ -90,12 +90,12 @@ std::vector calculate_all_path_delays_from_rr_node(int src_rr_node, const cost_params.astar_fac = router_opts.astar_fac; cost_params.bend_cost = router_opts.bend_cost; - std::vector modified_rr_node_inf; + std::vector modified_rr_node_inf; RouterStats router_stats; init_heap(device_ctx.grid); - std::vector shortest_paths = timing_driven_find_all_shortest_paths_from_route_tree(rt_root, + vtr::vector shortest_paths = timing_driven_find_all_shortest_paths_from_route_tree(rt_root, cost_params, bounding_box, modified_rr_node_inf, @@ -103,12 +103,12 @@ std::vector calculate_all_path_delays_from_rr_node(int src_rr_node, const free_route_tree(rt_root); - VTR_ASSERT(shortest_paths.size() == device_ctx.rr_nodes.size()); - for (int sink_rr_node = 0; sink_rr_node < (int)device_ctx.rr_nodes.size(); ++sink_rr_node) { + VTR_ASSERT(shortest_paths.size() == device_ctx.rr_graph.nodes().size()); + for (const RRNodeId& sink_rr_node : device_ctx.rr_graph.nodes()) { if (sink_rr_node == src_rr_node) { path_delays_to[sink_rr_node] = 0.; } else { - if (shortest_paths[sink_rr_node].index == OPEN) continue; + if (shortest_paths[sink_rr_node].index == RRNodeId::INVALID()) continue; VTR_ASSERT(shortest_paths[sink_rr_node].index == sink_rr_node); @@ -155,7 +155,7 @@ std::vector calculate_all_path_delays_from_rr_node(int src_rr_node, const return path_delays_to; } -static t_rt_node* setup_routing_resources_no_net(int source_node) { +static t_rt_node* setup_routing_resources_no_net(const RRNodeId& source_node) { /* Build and return a partial route tree from the legal connections from last iteration. * along the way do: * update pathfinder costs to be accurate to the partial route tree diff --git a/vpr/src/route/router_delay_profiling.h b/vpr/src/route/router_delay_profiling.h index 7f3918685..63b8bed65 100644 --- a/vpr/src/route/router_delay_profiling.h +++ b/vpr/src/route/router_delay_profiling.h @@ -15,7 +15,7 @@ class RouterDelayProfiler { const RouterLookahead* router_lookahead_; }; -std::vector calculate_all_path_delays_from_rr_node(int src_rr_node, const t_router_opts& router_opts); +vtr::vector calculate_all_path_delays_from_rr_node(int src_rr_node, const t_router_opts& router_opts); void alloc_routing_structs(t_chan_width chan_width, const t_router_opts& router_opts, diff --git a/vpr/src/route/router_lookahead_map.cpp b/vpr/src/route/router_lookahead_map.cpp index c2ad6aad7..42de703b5 100644 --- a/vpr/src/route/router_lookahead_map.cpp +++ b/vpr/src/route/router_lookahead_map.cpp @@ -131,7 +131,7 @@ class Expansion_Cost_Entry { /* a class that represents an entry in the Dijkstra expansion priority queue */ class PQ_Entry { public: - int rr_node_ind; //index in device_ctx.rr_nodes that this entry represents + RRNodeId rr_node_ind; //index in device_ctx.rr_nodes that this entry represents float cost; //the cost of the path to get to this node /* store backward delay, R and congestion info */ @@ -139,7 +139,7 @@ class PQ_Entry { float R_upstream; float congestion_upstream; - PQ_Entry(int set_rr_node_ind, int /*switch_ind*/, float parent_delay, float parent_R_upstream, float parent_congestion_upstream, bool starting_node) { + PQ_Entry(const RRNodeId& set_rr_node_ind, int /*switch_ind*/, float parent_delay, float parent_R_upstream, float parent_congestion_upstream, bool starting_node) { this->rr_node_ind = set_rr_node_ind; auto& device_ctx = g_vpr_ctx.device(); @@ -147,7 +147,7 @@ class PQ_Entry { this->congestion_upstream = parent_congestion_upstream; this->R_upstream = parent_R_upstream; if (!starting_node) { - int cost_index = device_ctx.rr_nodes[set_rr_node_ind].cost_index(); + int cost_index = device_ctx.rr_graph.node_cost_index(set_rr_node_ind); //this->delay += g_rr_nodes[set_rr_node_ind].C() * (g_rr_switch_inf[switch_ind].R + 0.5*g_rr_nodes[set_rr_node_ind].R()) + // g_rr_switch_inf[switch_ind].Tdel; @@ -191,12 +191,12 @@ t_cost_map f_cost_map; static void alloc_cost_map(int num_segments); static void free_cost_map(); /* returns index of a node from which to start routing */ -static int get_start_node_ind(int start_x, int start_y, int target_x, int target_y, t_rr_type rr_type, int seg_index, int track_offset); +static RRNodeId get_start_node_ind(int start_x, int start_y, int target_x, int target_y, t_rr_type rr_type, int seg_index, int track_offset); /* runs Dijkstra's algorithm from specified node until all nodes have been visited. Each time a pin is visited, the delay/congestion information * to that pin is stored is added to an entry in the routing_cost_map */ -static void run_dijkstra(int start_node_ind, int start_x, int start_y, t_routing_cost_map& routing_cost_map); +static void run_dijkstra(const RRNodeId& start_node_ind, int start_x, int start_y, t_routing_cost_map& routing_cost_map); /* iterates over the children of the specified node and selectively pushes them onto the priority queue */ -static void expand_dijkstra_neighbours(PQ_Entry parent_entry, std::vector& node_visited_costs, std::vector& node_expanded, std::priority_queue& pq); +static void expand_dijkstra_neighbours(PQ_Entry parent_entry, vtr::vector& node_visited_costs, vtr::vector& node_expanded, std::priority_queue& pq); /* sets the lookahead cost map entries based on representative cost entries from routing_cost_map */ static void set_lookahead_map_costs(int segment_index, e_rr_type chan_type, t_routing_cost_map& routing_cost_map); /* fills in missing lookahead map entries by copying the cost of the closest valid entry */ @@ -204,19 +204,18 @@ static void fill_in_missing_lookahead_entries(int segment_index, e_rr_type chan_ /* returns a cost entry in the f_cost_map that is near the specified coordinates (and preferably towards (0,0)) */ static Cost_Entry get_nearby_cost_entry(int x, int y, int segment_index, int chan_index); /* returns the absolute delta_x and delta_y offset required to reach to_node from from_node */ -static void get_xy_deltas(int from_node_ind, int to_node_ind, int* delta_x, int* delta_y); +static void get_xy_deltas(const RRNodeId& from_node_ind, const RRNodeId& to_node_ind, int* delta_x, int* delta_y); static void print_cost_map(); /******** Function Definitions ********/ /* queries the lookahead_map (should have been computed prior to routing) to get the expected cost * from the specified source to the specified target */ -float get_lookahead_map_cost(int from_node_ind, int to_node_ind, float criticality_fac) { +float get_lookahead_map_cost(const RRNodeId& from_node_ind, const RRNodeId& to_node_ind, float criticality_fac) { auto& device_ctx = g_vpr_ctx.device(); - auto& from_node = device_ctx.rr_nodes[from_node_ind]; - e_rr_type from_type = from_node.type(); - int from_cost_index = from_node.cost_index(); + e_rr_type from_type = device_ctx.rr_graph.node_type(from_node_ind); + int from_cost_index = device_ctx.rr_graph.node_cost_index(from_node_ind); int from_seg_index = device_ctx.rr_indexed_data[from_cost_index].seg_index; VTR_ASSERT(from_seg_index >= 0); @@ -262,11 +261,11 @@ void compute_router_lookahead(int num_segments) { for (int ref_inc = 0; ref_inc < 3; ref_inc++) { for (int track_offset = 0; track_offset < MAX_TRACK_OFFSET; track_offset += 2) { /* get the rr node index from which to start routing */ - int start_node_ind = get_start_node_ind(REF_X + ref_inc, REF_Y + ref_inc, + RRNodeId start_node_ind = get_start_node_ind(REF_X + ref_inc, REF_Y + ref_inc, device_ctx.grid.width() - 2, device_ctx.grid.height() - 2, //non-corner upper right chan_type, iseg, track_offset); - if (start_node_ind == UNDEFINED) { + if (start_node_ind == RRNodeId::INVALID()) { continue; } @@ -289,8 +288,8 @@ void compute_router_lookahead(int num_segments) { } /* returns index of a node from which to start routing */ -static int get_start_node_ind(int start_x, int start_y, int target_x, int target_y, t_rr_type rr_type, int seg_index, int track_offset) { - int result = UNDEFINED; +static RRNodeId get_start_node_ind(int start_x, int start_y, int target_x, int target_y, t_rr_type rr_type, int seg_index, int track_offset) { + RRNodeId result = RRNodeId::INVALID(); if (rr_type != CHANX && rr_type != CHANY) { VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "Must start lookahead routing from CHANX or CHANY node\n"); @@ -306,14 +305,21 @@ static int get_start_node_ind(int start_x, int start_y, int target_x, int target VTR_ASSERT(rr_type == CHANX || rr_type == CHANY); - const std::vector& channel_node_list = device_ctx.rr_node_indices[rr_type][start_x][start_y][0]; + std::vector channel_node_list; + /* As rr_node_indices is now an internal data of RRGraph object, + * we can get the number of tracks for a + * routing channel and then get the node one by one + */ + short num_tracks = device_ctx.rr_graph.chan_num_tracks(start_x, start_y, rr_type); + for (short i = 0; i < num_tracks; ++i) { + channel_node_list.push_back(device_ctx.rr_graph.find_node(start_x, start_y, rr_type, i)); + } /* find first node in channel that has specified segment index and goes in the desired direction */ - for (unsigned itrack = 0; itrack < channel_node_list.size(); itrack++) { - int node_ind = channel_node_list[itrack]; + for (const RRNodeId& node_ind : channel_node_list) { - e_direction node_direction = device_ctx.rr_nodes[node_ind].direction(); - int node_cost_ind = device_ctx.rr_nodes[node_ind].cost_index(); + e_direction node_direction = device_ctx.rr_graph.node_direction(node_ind); + int node_cost_ind = device_ctx.rr_graph.node_cost_index(node_ind); int node_seg_ind = device_ctx.rr_indexed_data[node_cost_ind].seg_index; if ((node_direction == direction || node_direction == BI_DIRECTION) && node_seg_ind == seg_index) { @@ -343,14 +349,14 @@ static void free_cost_map() { /* runs Dijkstra's algorithm from specified node until all nodes have been visited. Each time a pin is visited, the delay/congestion information * to that pin is stored is added to an entry in the routing_cost_map */ -static void run_dijkstra(int start_node_ind, int start_x, int start_y, t_routing_cost_map& routing_cost_map) { +static void run_dijkstra(const RRNodeId& start_node_ind, int start_x, int start_y, t_routing_cost_map& routing_cost_map) { auto& device_ctx = g_vpr_ctx.device(); /* a list of boolean flags (one for each rr node) to figure out if a certain node has already been expanded */ - std::vector node_expanded(device_ctx.rr_nodes.size(), false); + vtr::vector node_expanded(device_ctx.rr_graph.nodes().size(), false); /* for each node keep a list of the cost with which that node has been visited (used to determine whether to push * a candidate node onto the expansion queue */ - std::vector node_visited_costs(device_ctx.rr_nodes.size(), -1.0); + vtr::vector node_visited_costs(device_ctx.rr_graph.nodes().size(), -1.0); /* a priority queue for expansion */ std::priority_queue pq; @@ -364,7 +370,7 @@ static void run_dijkstra(int start_node_ind, int start_x, int start_y, t_routing PQ_Entry current = pq.top(); pq.pop(); - int node_ind = current.rr_node_ind; + RRNodeId node_ind = current.rr_node_ind; /* check that we haven't already expanded from this node */ if (node_expanded[node_ind]) { @@ -372,9 +378,9 @@ static void run_dijkstra(int start_node_ind, int start_x, int start_y, t_routing } /* if this node is an ipin record its congestion/delay in the routing_cost_map */ - if (device_ctx.rr_nodes[node_ind].type() == IPIN) { - int ipin_x = device_ctx.rr_nodes[node_ind].xlow(); - int ipin_y = device_ctx.rr_nodes[node_ind].ylow(); + if (device_ctx.rr_graph.node_type(node_ind) == IPIN) { + int ipin_x = device_ctx.rr_graph.node_xlow(node_ind); + int ipin_y = device_ctx.rr_graph.node_ylow(node_ind); if (ipin_x >= start_x && ipin_y >= start_y) { int delta_x, delta_y; @@ -390,16 +396,14 @@ static void run_dijkstra(int start_node_ind, int start_x, int start_y, t_routing } /* iterates over the children of the specified node and selectively pushes them onto the priority queue */ -static void expand_dijkstra_neighbours(PQ_Entry parent_entry, std::vector& node_visited_costs, std::vector& node_expanded, std::priority_queue& pq) { +static void expand_dijkstra_neighbours(PQ_Entry parent_entry, vtr::vector& node_visited_costs, vtr::vector& node_expanded, std::priority_queue& pq) { auto& device_ctx = g_vpr_ctx.device(); - int parent_ind = parent_entry.rr_node_ind; + RRNodeId parent_ind = parent_entry.rr_node_ind; - auto& parent_node = device_ctx.rr_nodes[parent_ind]; - - for (t_edge_size iedge = 0; iedge < parent_node.num_edges(); iedge++) { - int child_node_ind = parent_node.edge_sink_node(iedge); - int switch_ind = parent_node.edge_switch(iedge); + for (const RREdgeId& iedge : device_ctx.rr_graph.node_out_edges(parent_ind)) { + RRNodeId child_node_ind = device_ctx.rr_graph.edge_sink_node(iedge); + int switch_ind = (int)size_t(device_ctx.rr_graph.edge_switch(iedge)); /* skip this child if it has already been expanded from */ if (node_expanded[child_node_ind]) { @@ -595,25 +599,22 @@ Cost_Entry Expansion_Cost_Entry::get_median_entry() { } /* returns the absolute delta_x and delta_y offset required to reach to_node from from_node */ -static void get_xy_deltas(int from_node_ind, int to_node_ind, int* delta_x, int* delta_y) { +static void get_xy_deltas(const RRNodeId& from_node_ind, const RRNodeId& to_node_ind, int* delta_x, int* delta_y) { auto& device_ctx = g_vpr_ctx.device(); - auto& from = device_ctx.rr_nodes[from_node_ind]; - auto& to = device_ctx.rr_nodes[to_node_ind]; - /* get chan/seg coordinates of the from/to nodes. seg coordinate is along the wire, * chan coordinate is orthogonal to the wire */ - int from_seg_low = from.xlow(); - int from_seg_high = from.xhigh(); - int from_chan = from.ylow(); - int to_seg = to.xlow(); - int to_chan = to.ylow(); - if (from.type() == CHANY) { - from_seg_low = from.ylow(); - from_seg_high = from.yhigh(); - from_chan = from.xlow(); - to_seg = to.ylow(); - to_chan = to.xlow(); + int from_seg_low = device_ctx.rr_graph.node_xlow(from_node_ind); + int from_seg_high = device_ctx.rr_graph.node_xhigh(from_node_ind); + int from_chan = device_ctx.rr_graph.node_ylow(from_node_ind); + int to_seg = device_ctx.rr_graph.node_xlow(to_node_ind); + int to_chan = device_ctx.rr_graph.node_ylow(to_node_ind); + if (device_ctx.rr_graph.node_type(from_node_ind) == CHANY) { + from_seg_low = device_ctx.rr_graph.node_ylow(from_node_ind); + from_seg_high = device_ctx.rr_graph.node_yhigh(from_node_ind); + from_chan = device_ctx.rr_graph.node_xlow(from_node_ind); + to_seg = device_ctx.rr_graph.node_ylow(to_node_ind); + to_chan = device_ctx.rr_graph.node_xlow(to_node_ind); } /* now we want to count the minimum number of *channel segments* between the from and to nodes */ @@ -649,13 +650,13 @@ static void get_xy_deltas(int from_node_ind, int to_node_ind, int* delta_x, int* /* account for wire direction. lookahead map was computed by looking up and to the right starting at INC wires. for targets * that are opposite of the wire direction, let's add 1 to delta_seg */ - if ((to_seg < from_seg_low && from.direction() == INC_DIRECTION) || (to_seg > from_seg_high && from.direction() == DEC_DIRECTION)) { + if ((to_seg < from_seg_low && device_ctx.rr_graph.node_direction(from_node_ind) == INC_DIRECTION) || (to_seg > from_seg_high && device_ctx.rr_graph.node_direction(from_node_ind) == DEC_DIRECTION)) { delta_seg++; } *delta_x = delta_seg; *delta_y = delta_chan; - if (from.type() == CHANY) { + if (device_ctx.rr_graph.node_type(from_node_ind) == CHANY) { *delta_x = delta_chan; *delta_y = delta_seg; } diff --git a/vpr/src/route/router_lookahead_map.h b/vpr/src/route/router_lookahead_map.h index 4a31497e5..ac78b0737 100644 --- a/vpr/src/route/router_lookahead_map.h +++ b/vpr/src/route/router_lookahead_map.h @@ -6,4 +6,4 @@ void compute_router_lookahead(int num_segments); /* queries the lookahead_map (should have been computed prior to routing) to get the expected cost * from the specified source to the specified target */ -float get_lookahead_map_cost(int from_node_ind, int to_node_ind, float criticality_fac); +float get_lookahead_map_cost(const RRNodeId& from_node_ind, const RRNodeId& to_node_ind, float criticality_fac); diff --git a/vpr/src/route/rr_graph.cpp b/vpr/src/route/rr_graph.cpp index e70e70d01..85934a6d6 100644 --- a/vpr/src/route/rr_graph.cpp +++ b/vpr/src/route/rr_graph.cpp @@ -3185,8 +3185,8 @@ class EdgeGroups { t_non_configurable_rr_sets output_sets() { t_non_configurable_rr_sets sets; for (auto& item : rr_non_config_node_sets_map_) { - std::set edge_set; - std::set node_set(item.second.begin(), item.second.end()); + std::set edge_set; + std::set node_set(item.second.begin(), item.second.end()); for (const auto& edge : node_edges_) { if (node_set.find(edge.first) != node_set.end()) { From 12a8f798690ac7e50deabcbb3e0f414654dc2d25 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 1 Feb 2020 21:27:33 -0700 Subject: [PATCH 078/645] rr_graph adopt RRGraph object --- vpr/src/route/rr_graph.cpp | 36 ++++++++++++++++----------------- vpr/src/route/rr_graph_area.cpp | 25 +++++++++++------------ 2 files changed, 30 insertions(+), 31 deletions(-) diff --git a/vpr/src/route/rr_graph.cpp b/vpr/src/route/rr_graph.cpp index 85934a6d6..d9c387cd2 100644 --- a/vpr/src/route/rr_graph.cpp +++ b/vpr/src/route/rr_graph.cpp @@ -3093,7 +3093,7 @@ class EdgeGroups { // Adds non-configurable edge to be group. // // Returns true if this is a new edge. - bool add_non_config_edge(int from_node, int to_node) { + bool add_non_config_edge(const RRNodeId& from_node, const RRNodeId& to_node) { auto result = node_edges_.insert(std::make_pair(from_node, to_node)); return result.second; } @@ -3108,13 +3108,13 @@ class EdgeGroups { std::vector> merges; VTR_ASSERT(node_count_ != std::numeric_limits::max()); - std::vector node_to_node_set(node_count_, OPEN); + vtr::vector node_to_node_set(node_count_, OPEN); // First nievely make node groups. When an edge joins two groups, // mark it for cleanup latter. for (const auto& edge : node_edges_) { - VTR_ASSERT(edge.first >= 0 && static_cast(edge.first) < node_count_); - VTR_ASSERT(edge.second >= 0 && static_cast(edge.second) < node_count_); + VTR_ASSERT(size_t(edge.first) < node_count_); + VTR_ASSERT(size_t(edge.second) < node_count_); int& from_set = node_to_node_set[edge.first]; int& to_set = node_to_node_set[edge.second]; @@ -3175,8 +3175,8 @@ class EdgeGroups { // Create compact set of sets. for (size_t inode = 0; inode < node_to_node_set.size(); ++inode) { - if (node_to_node_set[inode] != OPEN) { - rr_non_config_node_sets_map_[node_to_node_set[inode]].push_back(inode); + if (node_to_node_set[RRNodeId(inode)] != OPEN) { + rr_non_config_node_sets_map_[node_to_node_set[RRNodeId(inode)]].push_back(RRNodeId(inode)); } } } @@ -3185,7 +3185,7 @@ class EdgeGroups { t_non_configurable_rr_sets output_sets() { t_non_configurable_rr_sets sets; for (auto& item : rr_non_config_node_sets_map_) { - std::set edge_set; + std::set edge_set; std::set node_set(item.second.begin(), item.second.end()); for (const auto& edge : node_edges_) { @@ -3203,12 +3203,12 @@ class EdgeGroups { // Set device context structures for non-configurable node sets. void set_device_context() { - std::vector> rr_non_config_node_sets; + std::vector> rr_non_config_node_sets; for (auto& item : rr_non_config_node_sets_map_) { rr_non_config_node_sets.emplace_back(std::move(item.second)); } - std::unordered_map rr_node_to_non_config_node_set; + std::unordered_map rr_node_to_non_config_node_set; for (size_t set = 0; set < rr_non_config_node_sets.size(); ++set) { for (const auto inode : rr_non_config_node_sets[set]) { rr_node_to_non_config_node_set.insert( @@ -3244,22 +3244,22 @@ class EdgeGroups { size_t node_count_; // Set of non-configurable edges. - std::set> node_edges_; + std::set> node_edges_; // Compact set of node sets. Map key is arbitrary. - std::map> rr_non_config_node_sets_map_; + std::map> rr_non_config_node_sets_map_; }; -static void expand_non_configurable(int inode, EdgeGroups* groups); +static void expand_non_configurable(const RRNodeId& inode, EdgeGroups* groups); //Collects the sets of connected non-configurable edges in the RR graph static void create_edge_groups(EdgeGroups* groups) { //Walk through the RR graph and recursively expand non-configurable edges //to collect the sets of non-configurably connected nodes auto& device_ctx = g_vpr_ctx.device(); - groups->set_node_count(device_ctx.rr_nodes.size()); + groups->set_node_count(device_ctx.rr_graph.nodes().size()); - for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); ++inode) { + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { expand_non_configurable(inode, groups); } @@ -3267,14 +3267,14 @@ static void create_edge_groups(EdgeGroups* groups) { } //Builds a set of non-configurably connected RR graph edges -static void expand_non_configurable(int inode, EdgeGroups* groups) { +static void expand_non_configurable(const RRNodeId& inode, EdgeGroups* groups) { auto& device_ctx = g_vpr_ctx.device(); - for (t_edge_size iedge = 0; iedge < device_ctx.rr_nodes[inode].num_edges(); ++iedge) { - bool edge_non_configurable = !device_ctx.rr_nodes[inode].edge_is_configurable(iedge); + for (const RREdgeId& iedge : device_ctx.rr_graph.node_out_edges(inode)) { + bool edge_non_configurable = !device_ctx.rr_graph.edge_is_configurable(iedge); if (edge_non_configurable) { - int to_node = device_ctx.rr_nodes[inode].edge_sink_node(iedge); + const RRNodeId& to_node = device_ctx.rr_graph.edge_sink_node(iedge); if (groups->add_non_config_edge(inode, to_node)) { expand_non_configurable(to_node, groups); diff --git a/vpr/src/route/rr_graph_area.cpp b/vpr/src/route/rr_graph_area.cpp index 963bba43e..056cc1421 100644 --- a/vpr/src/route/rr_graph_area.cpp +++ b/vpr/src/route/rr_graph_area.cpp @@ -308,7 +308,8 @@ void count_unidir_routing_transistors(std::vector& /*segment_inf* /* corresponding to IPINs will be 0. */ t_rr_type from_rr_type, to_rr_type; - int i, j, iseg, to_node, iedge, num_edges, maxlen; + int i, j, iseg, iedge, num_edges, maxlen; + RRNodeId to_node; int max_inputs_to_cblock; float input_cblock_trans; @@ -318,7 +319,7 @@ void count_unidir_routing_transistors(std::vector& /*segment_inf* * switches of all rr nodes. Thus we keep track of which muxes we have already * counted via the variable below. */ bool* chan_node_switch_done; - chan_node_switch_done = (bool*)vtr::calloc(device_ctx.rr_nodes.size(), sizeof(bool)); + chan_node_switch_done = (bool*)vtr::calloc(device_ctx.rr_graph.nodes().size(), sizeof(bool)); /* The variable below is an accumulator variable that will add up all the * * transistors in the routing. Make double so that it doesn't stop * @@ -348,28 +349,26 @@ void count_unidir_routing_transistors(std::vector& /*segment_inf* trans_track_to_cblock_buf = 0; } - num_inputs_to_cblock = (int*)vtr::calloc(device_ctx.rr_nodes.size(), sizeof(int)); + num_inputs_to_cblock = (int*)vtr::calloc(device_ctx.rr_graph.nodes().size(), sizeof(int)); maxlen = std::max(device_ctx.grid.width(), device_ctx.grid.height()); cblock_counted = (bool*)vtr::calloc(maxlen, sizeof(bool)); ntrans = 0; - for (size_t from_node = 0; from_node < device_ctx.rr_nodes.size(); from_node++) { - from_rr_type = device_ctx.rr_nodes[from_node].type(); + for (const RRNodeId& from_node : device_ctx.rr_graph.nodes()) { + from_rr_type = device_ctx.rr_graph.node_type(from_node); switch (from_rr_type) { case CHANX: case CHANY: - num_edges = device_ctx.rr_nodes[from_node].num_edges(); - /* Increment number of inputs per cblock if IPIN */ - for (iedge = 0; iedge < num_edges; iedge++) { - to_node = device_ctx.rr_nodes[from_node].edge_sink_node(iedge); - to_rr_type = device_ctx.rr_nodes[to_node].type(); + for (const RREdgeId& iedge : device_ctx.rr_graph.node_out_edges(from_node)) { + RRNodeId to_node = device_ctx.rr_graph.edge_sink_node(iedge); + to_rr_type = device_ctx.rr_graph.node_type(to_node); /* Ignore any uninitialized rr_graph nodes */ - if ((device_ctx.rr_nodes[to_node].type() == SOURCE) - && (device_ctx.rr_nodes[to_node].xlow() == 0) && (device_ctx.rr_nodes[to_node].ylow() == 0) - && (device_ctx.rr_nodes[to_node].xhigh() == 0) && (device_ctx.rr_nodes[to_node].yhigh() == 0)) { + if ((device_ctx.rr_graph.node_type(to_node) == SOURCE) + && (device_ctx.rr_graph.node_xlow(to_node) == 0) && (device_ctx.rr_graph.node_ylow(to_node) == 0) + && (device_ctx.rr_graph.node_xhigh(to_node) == 0) && (device_ctx.rr_graph.node_yhigh(to_node) == 0)) { continue; } From a99835e1c121bc7052c6201f610046acb4a5d7bc Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 1 Feb 2020 21:53:31 -0700 Subject: [PATCH 079/645] rr_graph area estimator adopted RRGraph object --- vpr/src/route/rr_graph_area.cpp | 81 ++++++++++++++++----------------- vpr/src/route/rr_graph_util.cpp | 40 ++++++++-------- vpr/src/route/rr_graph_util.h | 4 +- 3 files changed, 60 insertions(+), 65 deletions(-) diff --git a/vpr/src/route/rr_graph_area.cpp b/vpr/src/route/rr_graph_area.cpp index 056cc1421..a6e515159 100644 --- a/vpr/src/route/rr_graph_area.cpp +++ b/vpr/src/route/rr_graph_area.cpp @@ -33,7 +33,7 @@ static void count_unidir_routing_transistors(std::vector& segment float R_minW_pmos, const float trans_sram_bit); -static float get_cblock_trans(int* num_inputs_to_cblock, int wire_to_ipin_switch, int max_inputs_to_cblock, float trans_sram_bit); +static float get_cblock_trans(const vtr::vector& num_inputs_to_cblock, int wire_to_ipin_switch, int max_inputs_to_cblock, float trans_sram_bit); static float* alloc_and_load_unsharable_switch_trans(int num_switch, float trans_sram_bit, @@ -101,7 +101,7 @@ void count_bidir_routing_transistors(int num_switch, int wire_to_ipin_switch, fl * optimistic (but I still think it's pretty reasonable). */ auto& device_ctx = g_vpr_ctx.device(); - int* num_inputs_to_cblock; /* [0..device_ctx.rr_nodes.size()-1], but all entries not */ + vtr::vector num_inputs_to_cblock; /* [0..device_ctx.rr_nodes.size()-1], but all entries not */ /* corresponding to IPINs will be 0. */ @@ -110,7 +110,7 @@ void count_bidir_routing_transistors(int num_switch, int wire_to_ipin_switch, fl float *unsharable_switch_trans, *sharable_switch_trans; /* [0..num_switch-1] */ t_rr_type from_rr_type, to_rr_type; - int iedge, num_edges, maxlen; + int maxlen; int iswitch, i, j, iseg, max_inputs_to_cblock; float input_cblock_trans, shared_opin_buffer_trans; @@ -144,7 +144,7 @@ void count_bidir_routing_transistors(int num_switch, int wire_to_ipin_switch, fl trans_track_to_cblock_buf = 0; } - num_inputs_to_cblock = (int*)vtr::calloc(device_ctx.rr_nodes.size(), sizeof(int)); + num_inputs_to_cblock.resize(device_ctx.rr_graph.nodes().size(), 0); maxlen = std::max(device_ctx.grid.width(), device_ctx.grid.height()); cblock_counted = (bool*)vtr::calloc(maxlen, sizeof(bool)); @@ -156,29 +156,27 @@ void count_bidir_routing_transistors(int num_switch, int wire_to_ipin_switch, fl sharable_switch_trans = alloc_and_load_sharable_switch_trans(num_switch, R_minW_nmos, R_minW_pmos); - for (size_t from_node = 0; from_node < device_ctx.rr_nodes.size(); from_node++) { - from_rr_type = device_ctx.rr_nodes[from_node].type(); + for (const RRNodeId& from_node : device_ctx.rr_graph.nodes()) { + from_rr_type = device_ctx.rr_graph.node_type(from_node); switch (from_rr_type) { case CHANX: case CHANY: - num_edges = device_ctx.rr_nodes[from_node].num_edges(); - - for (iedge = 0; iedge < num_edges; iedge++) { - size_t to_node = device_ctx.rr_nodes[from_node].edge_sink_node(iedge); - to_rr_type = device_ctx.rr_nodes[to_node].type(); + for (const RREdgeId& iedge : device_ctx.rr_graph.node_out_edges(from_node)) { + RRNodeId to_node = device_ctx.rr_graph.edge_sink_node(iedge); + to_rr_type = device_ctx.rr_graph.node_type(to_node); /* Ignore any uninitialized rr_graph nodes */ - if ((device_ctx.rr_nodes[to_node].type() == SOURCE) - && (device_ctx.rr_nodes[to_node].xlow() == 0) && (device_ctx.rr_nodes[to_node].ylow() == 0) - && (device_ctx.rr_nodes[to_node].xhigh() == 0) && (device_ctx.rr_nodes[to_node].yhigh() == 0)) { + if ((device_ctx.rr_graph.node_type(to_node) == SOURCE) + && (device_ctx.rr_graph.node_xlow(to_node) == 0) && (device_ctx.rr_graph.node_ylow(to_node) == 0) + && (device_ctx.rr_graph.node_xhigh(to_node) == 0) && (device_ctx.rr_graph.node_yhigh(to_node) == 0)) { continue; } switch (to_rr_type) { case CHANX: case CHANY: - iswitch = device_ctx.rr_nodes[from_node].edge_switch(iedge); + iswitch = (short)size_t(device_ctx.rr_graph.edge_switch(iedge)); if (device_ctx.rr_switch_inf[iswitch].buffered()) { iseg = seg_index_of_sblock(from_node, to_node); @@ -214,8 +212,8 @@ void count_bidir_routing_transistors(int num_switch, int wire_to_ipin_switch, fl default: VPR_ERROR(VPR_ERROR_ROUTE, "in count_routing_transistors:\n" - "\tUnexpected connection from node %d (type %s) to node %d (type %s).\n", - from_node, rr_node_typename[from_rr_type], to_node, rr_node_typename[to_rr_type]); + "\tUnexpected connection from node %ld (type %s) to node %ld (type %s).\n", + size_t(from_node), rr_node_typename[from_rr_type], size_t(to_node), rr_node_typename[to_rr_type]); break; } /* End switch on to_rr_type. */ @@ -225,35 +223,34 @@ void count_bidir_routing_transistors(int num_switch, int wire_to_ipin_switch, fl /* Now add in the shared buffer transistors, and reset some flags. */ if (from_rr_type == CHANX) { - for (i = device_ctx.rr_nodes[from_node].xlow() - 1; - i <= device_ctx.rr_nodes[from_node].xhigh(); i++) { + for (i = device_ctx.rr_graph.node_xlow(from_node) - 1; + i <= device_ctx.rr_graph.node_xhigh(from_node); i++) { ntrans_sharing += shared_buffer_trans[i]; shared_buffer_trans[i] = 0.; } - for (i = device_ctx.rr_nodes[from_node].xlow(); i <= device_ctx.rr_nodes[from_node].xhigh(); + for (i = device_ctx.rr_graph.node_xlow(from_node); i <= device_ctx.rr_graph.node_xhigh(from_node); i++) cblock_counted[i] = false; } else { /* CHANY */ - for (j = device_ctx.rr_nodes[from_node].ylow() - 1; - j <= device_ctx.rr_nodes[from_node].yhigh(); j++) { + for (j = device_ctx.rr_graph.node_ylow(from_node) - 1; + j <= device_ctx.rr_graph.node_yhigh(from_node); j++) { ntrans_sharing += shared_buffer_trans[j]; shared_buffer_trans[j] = 0.; } - for (j = device_ctx.rr_nodes[from_node].ylow(); j <= device_ctx.rr_nodes[from_node].yhigh(); + for (j = device_ctx.rr_graph.node_ylow(from_node); j <= device_ctx.rr_graph.node_yhigh(from_node); j++) cblock_counted[j] = false; } break; case OPIN: - num_edges = device_ctx.rr_nodes[from_node].num_edges(); shared_opin_buffer_trans = 0.; - for (iedge = 0; iedge < num_edges; iedge++) { - iswitch = device_ctx.rr_nodes[from_node].edge_switch(iedge); + for (const RREdgeId& iedge : device_ctx.rr_graph.node_out_edges(from_node)) { + iswitch = (short)size_t(device_ctx.rr_graph.edge_switch(iedge)); ntrans_no_sharing += unsharable_switch_trans[iswitch] + sharable_switch_trans[iswitch]; ntrans_sharing += unsharable_switch_trans[iswitch]; @@ -281,7 +278,7 @@ void count_bidir_routing_transistors(int num_switch, int wire_to_ipin_switch, fl input_cblock_trans = get_cblock_trans(num_inputs_to_cblock, wire_to_ipin_switch, max_inputs_to_cblock, trans_sram_bit); - free(num_inputs_to_cblock); + num_inputs_to_cblock.clear(); ntrans_sharing += input_cblock_trans; ntrans_no_sharing += input_cblock_trans; @@ -303,13 +300,12 @@ void count_unidir_routing_transistors(std::vector& /*segment_inf* auto& device_ctx = g_vpr_ctx.device(); bool* cblock_counted; /* [0..max(device_ctx.grid.width(),device_ctx.grid.height())] -- 0th element unused. */ - int* num_inputs_to_cblock; /* [0..device_ctx.rr_nodes.size()-1], but all entries not */ + vtr::vector num_inputs_to_cblock; /* [0..device_ctx.rr_nodes.size()-1], but all entries not */ /* corresponding to IPINs will be 0. */ t_rr_type from_rr_type, to_rr_type; - int i, j, iseg, iedge, num_edges, maxlen; - RRNodeId to_node; + int i, j, iseg, maxlen; int max_inputs_to_cblock; float input_cblock_trans; @@ -318,8 +314,7 @@ void count_unidir_routing_transistors(std::vector& /*segment_inf* * a single mux. We should count this mux only once as we look at the outgoing * switches of all rr nodes. Thus we keep track of which muxes we have already * counted via the variable below. */ - bool* chan_node_switch_done; - chan_node_switch_done = (bool*)vtr::calloc(device_ctx.rr_graph.nodes().size(), sizeof(bool)); + vtr::vector chan_node_switch_done(device_ctx.rr_graph.nodes().size(), false); /* The variable below is an accumulator variable that will add up all the * * transistors in the routing. Make double so that it doesn't stop * @@ -349,7 +344,7 @@ void count_unidir_routing_transistors(std::vector& /*segment_inf* trans_track_to_cblock_buf = 0; } - num_inputs_to_cblock = (int*)vtr::calloc(device_ctx.rr_graph.nodes().size(), sizeof(int)); + num_inputs_to_cblock.resize(device_ctx.rr_graph.nodes().size(), 0); maxlen = std::max(device_ctx.grid.width(), device_ctx.grid.height()); cblock_counted = (bool*)vtr::calloc(maxlen, sizeof(bool)); @@ -376,10 +371,10 @@ void count_unidir_routing_transistors(std::vector& /*segment_inf* case CHANX: case CHANY: if (!chan_node_switch_done[to_node]) { - int switch_index = device_ctx.rr_nodes[from_node].edge_switch(iedge); + int switch_index = (int)size_t(device_ctx.rr_graph.edge_switch(iedge)); auto switch_type = device_ctx.rr_switch_inf[switch_index].type(); - int fan_in = device_ctx.rr_nodes[to_node].fan_in(); + int fan_in = device_ctx.rr_graph.node_in_edges(to_node).size(); if (device_ctx.rr_switch_inf[switch_index].type() == SwitchType::MUX) { /* Each wire segment begins with a multipexer followed by a driver for unidirectional */ @@ -433,8 +428,8 @@ void count_unidir_routing_transistors(std::vector& /*segment_inf* default: VPR_ERROR(VPR_ERROR_ROUTE, "in count_routing_transistors:\n" - "\tUnexpected connection from node %d (type %d) to node %d (type %d).\n", - from_node, from_rr_type, to_node, to_rr_type); + "\tUnexpected connection from node %ld (type %d) to node %ld (type %d).\n", + size_t(from_node), from_rr_type, size_t(to_node), to_rr_type); break; } /* End switch on to_rr_type. */ @@ -443,11 +438,11 @@ void count_unidir_routing_transistors(std::vector& /*segment_inf* /* Reset some flags */ if (from_rr_type == CHANX) { - for (i = device_ctx.rr_nodes[from_node].xlow(); i <= device_ctx.rr_nodes[from_node].xhigh(); i++) + for (i = device_ctx.rr_graph.node_xlow(from_node); i <= device_ctx.rr_graph.node_xhigh(from_node); i++) cblock_counted[i] = false; } else { /* CHANY */ - for (j = device_ctx.rr_nodes[from_node].ylow(); j <= device_ctx.rr_nodes[from_node].yhigh(); + for (j = device_ctx.rr_graph.node_ylow(from_node); j <= device_ctx.rr_graph.node_yhigh(from_node); j++) cblock_counted[j] = false; } @@ -467,8 +462,8 @@ void count_unidir_routing_transistors(std::vector& /*segment_inf* max_inputs_to_cblock, trans_sram_bit); free(cblock_counted); - free(num_inputs_to_cblock); - free(chan_node_switch_done); + num_inputs_to_cblock.clear(); + chan_node_switch_done.clear(); ntrans += input_cblock_trans; @@ -477,7 +472,7 @@ void count_unidir_routing_transistors(std::vector& /*segment_inf* VTR_LOG("\tTotal routing area: %#g, per logic tile: %#g\n", ntrans, ntrans / (float)(device_ctx.grid.width() * device_ctx.grid.height())); } -static float get_cblock_trans(int* num_inputs_to_cblock, int wire_to_ipin_switch, int max_inputs_to_cblock, float trans_sram_bit) { +static float get_cblock_trans(const vtr::vector& num_inputs_to_cblock, int wire_to_ipin_switch, int max_inputs_to_cblock, float trans_sram_bit) { /* Computes the transistors in the input connection block multiplexers and * * the buffers from connection block outputs to the logic block input pins. * * For speed, I precompute the number of transistors in the multiplexers of * @@ -505,7 +500,7 @@ static float get_cblock_trans(int* num_inputs_to_cblock, int wire_to_ipin_switch trans_count = 0.; - for (size_t i = 0; i < device_ctx.rr_nodes.size(); i++) { + for (const RRNodeId& i : device_ctx.rr_graph.nodes()) { num_inputs = num_inputs_to_cblock[i]; trans_count += trans_per_cblock[num_inputs]; } diff --git a/vpr/src/route/rr_graph_util.cpp b/vpr/src/route/rr_graph_util.cpp index c91ef8109..dd808e440 100644 --- a/vpr/src/route/rr_graph_util.cpp +++ b/vpr/src/route/rr_graph_util.cpp @@ -6,20 +6,20 @@ #include "globals.h" #include "rr_graph_util.h" -int seg_index_of_cblock(t_rr_type from_rr_type, int to_node) { +int seg_index_of_cblock(t_rr_type from_rr_type, const RRNodeId& to_node) { /* Returns the segment number (distance along the channel) of the connection * * box from from_rr_type (CHANX or CHANY) to to_node (IPIN). */ auto& device_ctx = g_vpr_ctx.device(); if (from_rr_type == CHANX) - return (device_ctx.rr_nodes[to_node].xlow()); + return (device_ctx.rr_graph.node_xlow(to_node)); else /* CHANY */ - return (device_ctx.rr_nodes[to_node].ylow()); + return (device_ctx.rr_graph.node_ylow(to_node)); } -int seg_index_of_sblock(int from_node, int to_node) { +int seg_index_of_sblock(const RRNodeId& from_node, const RRNodeId& to_node) { /* Returns the segment number (distance along the channel) of the switch box * * box from from_node (CHANX or CHANY) to to_node (CHANX or CHANY). The * * switch box on the left side of a CHANX segment at (i,j) has seg_index = * @@ -31,47 +31,47 @@ int seg_index_of_sblock(int from_node, int to_node) { auto& device_ctx = g_vpr_ctx.device(); - from_rr_type = device_ctx.rr_nodes[from_node].type(); - to_rr_type = device_ctx.rr_nodes[to_node].type(); + from_rr_type = device_ctx.rr_graph.node_type(from_node); + to_rr_type = device_ctx.rr_graph.node_type(to_node); if (from_rr_type == CHANX) { if (to_rr_type == CHANY) { - return (device_ctx.rr_nodes[to_node].xlow()); + return (device_ctx.rr_graph.node_xlow(to_node)); } else if (to_rr_type == CHANX) { - if (device_ctx.rr_nodes[to_node].xlow() > device_ctx.rr_nodes[from_node].xlow()) { /* Going right */ - return (device_ctx.rr_nodes[from_node].xhigh()); + if (device_ctx.rr_graph.node_xlow(to_node) > device_ctx.rr_graph.node_xlow(from_node)) { /* Going right */ + return (device_ctx.rr_graph.node_xhigh(from_node)); } else { /* Going left */ - return (device_ctx.rr_nodes[to_node].xhigh()); + return (device_ctx.rr_graph.node_xhigh(to_node)); } } else { VPR_FATAL_ERROR(VPR_ERROR_ROUTE, - "in seg_index_of_sblock: to_node %d is of type %d.\n", - to_node, to_rr_type); + "in seg_index_of_sblock: to_node %ld is of type %d.\n", + size_t(to_node), to_rr_type); return OPEN; //Should not reach here once thrown } } /* End from_rr_type is CHANX */ else if (from_rr_type == CHANY) { if (to_rr_type == CHANX) { - return (device_ctx.rr_nodes[to_node].ylow()); + return (device_ctx.rr_graph.node_ylow(to_node)); } else if (to_rr_type == CHANY) { - if (device_ctx.rr_nodes[to_node].ylow() > device_ctx.rr_nodes[from_node].ylow()) { /* Going up */ - return (device_ctx.rr_nodes[from_node].yhigh()); + if (device_ctx.rr_graph.node_ylow(to_node) > device_ctx.rr_graph.node_ylow(from_node)) { /* Going up */ + return (device_ctx.rr_graph.node_yhigh(from_node)); } else { /* Going down */ - return (device_ctx.rr_nodes[to_node].yhigh()); + return (device_ctx.rr_graph.node_yhigh(to_node)); } } else { VPR_FATAL_ERROR(VPR_ERROR_ROUTE, - "in seg_index_of_sblock: to_node %d is of type %d.\n", - to_node, to_rr_type); + "in seg_index_of_sblock: to_node %ld is of type %d.\n", + size_t(to_node), to_rr_type); return OPEN; //Should not reach here once thrown } } /* End from_rr_type is CHANY */ else { VPR_FATAL_ERROR(VPR_ERROR_ROUTE, - "in seg_index_of_sblock: from_node %d is of type %d.\n", - from_node, from_rr_type); + "in seg_index_of_sblock: from_node %ld is of type %d.\n", + size_t(from_node), from_rr_type); return OPEN; //Should not reach here once thrown } } diff --git a/vpr/src/route/rr_graph_util.h b/vpr/src/route/rr_graph_util.h index c065500bd..14da78eac 100644 --- a/vpr/src/route/rr_graph_util.h +++ b/vpr/src/route/rr_graph_util.h @@ -1,8 +1,8 @@ #ifndef RR_GRAPH_UTIL_H #define RR_GRAPH_UTIL_H -int seg_index_of_cblock(t_rr_type from_rr_type, int to_node); +int seg_index_of_cblock(t_rr_type from_rr_type, const RRNodeId& to_node); -int seg_index_of_sblock(int from_node, int to_node); +int seg_index_of_sblock(const RRNodeId& from_node, const RRNodeId& to_node); #endif From b49b8208d1d3160ac3cf007bebd7e0353f76f4c9 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 1 Feb 2020 22:19:31 -0700 Subject: [PATCH 080/645] rr_graph timing parameter builder adopt RRGraph object --- vpr/src/device/rr_graph_obj.cpp | 13 +++++ vpr/src/device/rr_graph_obj.h | 12 +++++ vpr/src/route/rr_graph_timing_params.cpp | 66 ++++++++++++------------ 3 files changed, 58 insertions(+), 33 deletions(-) diff --git a/vpr/src/device/rr_graph_obj.cpp b/vpr/src/device/rr_graph_obj.cpp index 316bc6e87..6a05b6a21 100644 --- a/vpr/src/device/rr_graph_obj.cpp +++ b/vpr/src/device/rr_graph_obj.cpp @@ -194,6 +194,11 @@ float RRGraph::node_C(const RRNodeId& node) const { return node_Cs_[node]; } +short RRGraph::node_rc_data_index(const RRNodeId& node) const { + VTR_ASSERT_SAFE(valid_node_id(node)); + return node_rc_data_indices_[node]; +} + /* * Get a segment id of a node in rr_graph */ @@ -769,6 +774,7 @@ void RRGraph::reserve_nodes(const unsigned long& num_nodes) { this->node_sides_.reserve(num_nodes); this->node_Rs_.reserve(num_nodes); this->node_Cs_.reserve(num_nodes); + this->node_rc_data_indices_.reserve(num_nodes); this->node_segments_.reserve(num_nodes); /* Edge-related vectors */ @@ -818,6 +824,7 @@ RRNodeId RRGraph::create_node(const t_rr_type& type) { node_sides_.push_back(NUM_SIDES); node_Rs_.push_back(0.); node_Cs_.push_back(0.); + node_rc_data_indices_.push_back(-1); node_segments_.push_back(RRSegmentId::INVALID()); node_edges_.emplace_back(); //Initially empty @@ -1028,6 +1035,12 @@ void RRGraph::set_node_C(const RRNodeId& node, const float& C) { node_Cs_[node] = C; } +void RRGraph::set_node_rc_data_index(const RRNodeId& node, const short& rc_data_index) { + VTR_ASSERT(valid_node_id(node)); + + node_rc_data_indices_[node] = rc_data_index; +} + /* * Set a segment id for a node in rr_graph */ diff --git a/vpr/src/device/rr_graph_obj.h b/vpr/src/device/rr_graph_obj.h index 85bf7de4b..abafa59b6 100644 --- a/vpr/src/device/rr_graph_obj.h +++ b/vpr/src/device/rr_graph_obj.h @@ -454,6 +454,13 @@ class RRGraph { /* Get capacitance of a node, used to built RC tree for timing analysis */ float node_C(const RRNodeId& node) const; + /* Get the index of rc data in the list of rc_data data structure + * It contains the RC parasitics for different nodes in the RRGraph + * when used in evaluate different routing paths + * See cross-reference section in this header file for more details + */ + short node_rc_data_index(const RRNodeId& node) const; + /* Get segment id of a node, containing the information of the routing * segment that the node represents. See more details in the data structure t_segment_inf */ @@ -694,6 +701,10 @@ class RRGraph { void set_node_R(const RRNodeId& node, const float& R); void set_node_C(const RRNodeId& node, const float& C); + /* Set the flyweight RC data index for node, see node_rc_data_index() for details */ + /* TODO: the cost index should be changed to a StrongId!!! */ + void set_node_rc_data_index(const RRNodeId& node, const short& rc_data_index); + /* Set the routing segment linked to a node, only applicable to CHANX and CHANY */ void set_node_segment(const RRNodeId& node, const RRSegmentId& segment_index); @@ -841,6 +852,7 @@ class RRGraph { vtr::vector node_sides_; vtr::vector node_Rs_; vtr::vector node_Cs_; + vtr::vector node_rc_data_indices_; vtr::vector node_segments_; /* Segment ids for each node */ /* diff --git a/vpr/src/route/rr_graph_timing_params.cpp b/vpr/src/route/rr_graph_timing_params.cpp index 69ea0203a..8d581f957 100644 --- a/vpr/src/route/rr_graph_timing_params.cpp +++ b/vpr/src/route/rr_graph_timing_params.cpp @@ -28,14 +28,14 @@ void add_rr_graph_C_from_switches(float C_ipin_cblock) { * INCLUDE_TRACK_BUFFERS) */ int switch_index, maxlen; - size_t to_node; + RRNodeId to_node; int icblock, isblock, iseg_low, iseg_high; float Cin, Cout; t_rr_type from_rr_type, to_rr_type; bool* cblock_counted; /* [0..maxlen-1] -- 0th element unused. */ float* buffer_Cin; /* [0..maxlen-1] */ bool buffered; - float* Couts_to_add; /* UDSD */ + vtr::vector Couts_to_add; /* UDSD */ auto& device_ctx = g_vpr_ctx.device(); auto& mutable_device_ctx = g_vpr_ctx.mutable_device(); @@ -44,21 +44,21 @@ void add_rr_graph_C_from_switches(float C_ipin_cblock) { cblock_counted = (bool*)vtr::calloc(maxlen, sizeof(bool)); buffer_Cin = (float*)vtr::calloc(maxlen, sizeof(float)); - std::vector rr_node_C(device_ctx.rr_nodes.size(), 0.); //Stores the final C + vtr::vector rr_node_C(device_ctx.rr_graph.nodes().size(), 0.); //Stores the final C - for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); inode++) { + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { //The C may have already been partly initialized (e.g. with metal capacitance) - rr_node_C[inode] += device_ctx.rr_nodes[inode].C(); + rr_node_C[inode] += device_ctx.rr_graph.node_C(inode); - from_rr_type = device_ctx.rr_nodes[inode].type(); + from_rr_type = device_ctx.rr_graph.node_type(inode); if (from_rr_type == CHANX || from_rr_type == CHANY) { - for (t_edge_size iedge = 0; iedge < device_ctx.rr_nodes[inode].num_edges(); iedge++) { - to_node = device_ctx.rr_nodes[inode].edge_sink_node(iedge); - to_rr_type = device_ctx.rr_nodes[to_node].type(); + for (const RREdgeId& iedge : device_ctx.rr_graph.node_out_edges(inode)) { + to_node = device_ctx.rr_graph.edge_sink_node(iedge); + to_rr_type = device_ctx.rr_graph.node_type(to_node); if (to_rr_type == CHANX || to_rr_type == CHANY) { - switch_index = device_ctx.rr_nodes[inode].edge_switch(iedge); + switch_index = (int)size_t(device_ctx.rr_graph.edge_switch(iedge)); Cin = device_ctx.rr_switch_inf[switch_index].Cin; Cout = device_ctx.rr_switch_inf[switch_index].Cout; buffered = device_ctx.rr_switch_inf[switch_index].buffered(); @@ -80,14 +80,14 @@ void add_rr_graph_C_from_switches(float C_ipin_cblock) { * the buffers at that location have different sizes, I use the * * input capacitance of the largest one. */ - if (!buffered && inode < to_node) { /* Pass transistor. */ + if (!buffered && size_t(inode) < size_t(to_node)) { /* Pass transistor. */ rr_node_C[inode] += Cin; rr_node_C[to_node] += Cout; } else if (buffered) { /* Prevent double counting of capacitance for UDSD */ - if (device_ctx.rr_nodes[to_node].direction() == BI_DIRECTION) { + if (device_ctx.rr_graph.node_direction(to_node) == BI_DIRECTION) { /* For multiple-driver architectures the output capacitance can * be added now since each edge is actually a driver */ rr_node_C[to_node] += Cout; @@ -129,11 +129,11 @@ void add_rr_graph_C_from_switches(float C_ipin_cblock) { * } */ if (from_rr_type == CHANX) { - iseg_low = device_ctx.rr_nodes[inode].xlow(); - iseg_high = device_ctx.rr_nodes[inode].xhigh(); + iseg_low = device_ctx.rr_graph.node_xlow(inode); + iseg_high = device_ctx.rr_graph.node_xhigh(inode); } else { /* CHANY */ - iseg_low = device_ctx.rr_nodes[inode].ylow(); - iseg_high = device_ctx.rr_nodes[inode].yhigh(); + iseg_low = device_ctx.rr_graph.node_ylow(inode); + iseg_high = device_ctx.rr_graph.node_yhigh(inode); } for (icblock = iseg_low; icblock <= iseg_high; icblock++) { @@ -148,17 +148,17 @@ void add_rr_graph_C_from_switches(float C_ipin_cblock) { } /* End node is CHANX or CHANY */ else if (from_rr_type == OPIN) { - for (t_edge_size iedge = 0; iedge < device_ctx.rr_nodes[inode].num_edges(); iedge++) { - switch_index = device_ctx.rr_nodes[inode].edge_switch(iedge); - to_node = device_ctx.rr_nodes[inode].edge_sink_node(iedge); - to_rr_type = device_ctx.rr_nodes[to_node].type(); + for (const RREdgeId& iedge : device_ctx.rr_graph.node_out_edges(inode)) { + switch_index = (int)size_t(device_ctx.rr_graph.edge_switch(iedge)); + to_node = device_ctx.rr_graph.edge_sink_node(iedge); + to_rr_type = device_ctx.rr_graph.node_type(to_node); if (to_rr_type != CHANX && to_rr_type != CHANY) continue; - if (device_ctx.rr_nodes[to_node].direction() == BI_DIRECTION) { + if (device_ctx.rr_graph.node_direction(to_node) == BI_DIRECTION) { Cout = device_ctx.rr_switch_inf[switch_index].Cout; - to_node = device_ctx.rr_nodes[inode].edge_sink_node(iedge); /* Will be CHANX or CHANY */ + to_node = device_ctx.rr_graph.edge_sink_node(iedge); /* Will be CHANX or CHANY */ rr_node_C[to_node] += Cout; } } @@ -170,30 +170,30 @@ void add_rr_graph_C_from_switches(float C_ipin_cblock) { * Current structures only keep switch information from a node to the next node and * not the reverse. Therefore I need to go through all the possible edges to figure * out what the Cout's should be */ - Couts_to_add = (float*)vtr::calloc(device_ctx.rr_nodes.size(), sizeof(float)); - for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); inode++) { - for (t_edge_size iedge = 0; iedge < device_ctx.rr_nodes[inode].num_edges(); iedge++) { - switch_index = device_ctx.rr_nodes[inode].edge_switch(iedge); - to_node = device_ctx.rr_nodes[inode].edge_sink_node(iedge); - to_rr_type = device_ctx.rr_nodes[to_node].type(); + Couts_to_add.resize(device_ctx.rr_graph.nodes().size(), 0.); + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { + for (const RREdgeId& iedge : device_ctx.rr_graph.node_out_edges(inode)) { + switch_index = (int)size_t(device_ctx.rr_graph.edge_switch(iedge)); + to_node = device_ctx.rr_graph.edge_sink_node(iedge); + to_rr_type = device_ctx.rr_graph.node_type(to_node); if (to_rr_type == CHANX || to_rr_type == CHANY) { - if (device_ctx.rr_nodes[to_node].direction() != BI_DIRECTION) { + if (device_ctx.rr_graph.node_direction(to_node) != BI_DIRECTION) { /* Cout was not added in these cases */ Couts_to_add[to_node] = std::max(Couts_to_add[to_node], device_ctx.rr_switch_inf[switch_index].Cout); } } } } - for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); inode++) { + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { rr_node_C[inode] += Couts_to_add[inode]; } //Create the final flywieghted t_rr_rc_data - for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); inode++) { - mutable_device_ctx.rr_nodes[inode].set_rc_index(find_create_rr_rc_data(device_ctx.rr_nodes[inode].R(), rr_node_C[inode])); + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { + mutable_device_ctx.rr_graph.set_node_rc_data_index(inode, find_create_rr_rc_data(device_ctx.rr_graph.node_R(inode), rr_node_C[inode])); } - free(Couts_to_add); + Couts_to_add.clear(); free(cblock_counted); free(buffer_Cin); } From 74c532e95392cd14b59e25b45da2b347b4664757 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 1 Feb 2020 22:22:16 -0700 Subject: [PATCH 081/645] segment stats adopt RRGraph object --- vpr/src/route/segment_stats.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vpr/src/route/segment_stats.cpp b/vpr/src/route/segment_stats.cpp index 7af48b119..f42231e1b 100644 --- a/vpr/src/route/segment_stats.cpp +++ b/vpr/src/route/segment_stats.cpp @@ -43,9 +43,9 @@ void get_segment_usage_stats(std::vector& segment_inf) { seg_occ_by_type = (int*)vtr::calloc(segment_inf.size(), sizeof(int)); seg_cap_by_type = (int*)vtr::calloc(segment_inf.size(), sizeof(int)); - for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); inode++) { - if (device_ctx.rr_nodes[inode].type() == CHANX || device_ctx.rr_nodes[inode].type() == CHANY) { - cost_index = device_ctx.rr_nodes[inode].cost_index(); + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { + if (device_ctx.rr_graph.node_type(inode) == CHANX || device_ctx.rr_graph.node_type(inode) == CHANY) { + cost_index = device_ctx.rr_graph.node_cost_index(inode); size_t seg_type = device_ctx.rr_indexed_data[cost_index].seg_index; if (!segment_inf[seg_type].longline) @@ -54,9 +54,9 @@ void get_segment_usage_stats(std::vector& segment_inf) { length = LONGLINE; seg_occ_by_length[length] += route_ctx.rr_node_route_inf[inode].occ(); - seg_cap_by_length[length] += device_ctx.rr_nodes[inode].capacity(); + seg_cap_by_length[length] += device_ctx.rr_graph.node_capacity(inode); seg_occ_by_type[seg_type] += route_ctx.rr_node_route_inf[inode].occ(); - seg_cap_by_type[seg_type] += device_ctx.rr_nodes[inode].capacity(); + seg_cap_by_type[seg_type] += device_ctx.rr_graph.node_capacity(inode); } } From d2485789a4a4b9a3443783fc08582deaf1d10886 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 1 Feb 2020 22:26:13 -0700 Subject: [PATCH 082/645] spatial route tree lookup adopt RRGraph object --- vpr/src/route/spatial_route_tree_lookup.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/vpr/src/route/spatial_route_tree_lookup.cpp b/vpr/src/route/spatial_route_tree_lookup.cpp index 20c9b8524..e27b6aa32 100644 --- a/vpr/src/route/spatial_route_tree_lookup.cpp +++ b/vpr/src/route/spatial_route_tree_lookup.cpp @@ -75,27 +75,27 @@ size_t grid_to_bin_y(size_t grid_y, const SpatialRouteTreeLookup& spatial_lookup bool validate_route_tree_spatial_lookup(t_rt_node* rt_node, const SpatialRouteTreeLookup& spatial_lookup) { auto& device_ctx = g_vpr_ctx.device(); - auto& rr_node = device_ctx.rr_nodes[rt_node->inode]; + const RRNodeId& rr_node = rt_node->inode; - int bin_xlow = grid_to_bin_x(rr_node.xlow(), spatial_lookup); - int bin_ylow = grid_to_bin_y(rr_node.ylow(), spatial_lookup); - int bin_xhigh = grid_to_bin_x(rr_node.xhigh(), spatial_lookup); - int bin_yhigh = grid_to_bin_y(rr_node.yhigh(), spatial_lookup); + int bin_xlow = grid_to_bin_x(device_ctx.rr_graph.node_xlow(rr_node), spatial_lookup); + int bin_ylow = grid_to_bin_y(device_ctx.rr_graph.node_ylow(rr_node), spatial_lookup); + int bin_xhigh = grid_to_bin_x(device_ctx.rr_graph.node_xhigh(rr_node), spatial_lookup); + int bin_yhigh = grid_to_bin_y(device_ctx.rr_graph.node_yhigh(rr_node), spatial_lookup); bool valid = true; auto& low_bin_rt_nodes = spatial_lookup[bin_xlow][bin_ylow]; if (std::find(low_bin_rt_nodes.begin(), low_bin_rt_nodes.end(), rt_node) == low_bin_rt_nodes.end()) { valid = false; - VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "Failed to find route tree node %d at (low coord %d,%d) in spatial lookup [bin %d,%d]", - rt_node->inode, rr_node.xlow(), rr_node.ylow(), bin_xlow, bin_ylow); + VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "Failed to find route tree node %ld at (low coord %d,%d) in spatial lookup [bin %d,%d]", + size_t(rt_node->inode), device_ctx.rr_graph.node_xlow(rr_node), device_ctx.rr_graph.node_ylow(rr_node), bin_xlow, bin_ylow); } auto& high_bin_rt_nodes = spatial_lookup[bin_xhigh][bin_yhigh]; if (std::find(high_bin_rt_nodes.begin(), high_bin_rt_nodes.end(), rt_node) == high_bin_rt_nodes.end()) { valid = false; - VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "Failed to find route tree node %d at (high coord %d,%d) in spatial lookup [bin %d,%d]", - rt_node->inode, rr_node.xhigh(), rr_node.yhigh(), bin_xhigh, bin_yhigh); + VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "Failed to find route tree node %ld at (high coord %d,%d) in spatial lookup [bin %d,%d]", + size_t(rt_node->inode), device_ctx.rr_graph.node_xhigh(rr_node), device_ctx.rr_graph.node_yhigh(rr_node), bin_xhigh, bin_yhigh); } //Recurse From 7b406e28ab4da8d12c2a2217ba085c46d4f172aa Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 1 Feb 2020 22:35:29 -0700 Subject: [PATCH 083/645] Vpr timing graph resolver adopt RRGraph Object --- vpr/src/timing/VprTimingGraphResolver.cpp | 46 +++++++++++------------ 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/vpr/src/timing/VprTimingGraphResolver.cpp b/vpr/src/timing/VprTimingGraphResolver.cpp index 39be7cd0c..6a21b2180 100644 --- a/vpr/src/timing/VprTimingGraphResolver.cpp +++ b/vpr/src/timing/VprTimingGraphResolver.cpp @@ -280,45 +280,45 @@ void VprTimingGraphResolver::get_detailed_interconnect_components_helper(std::ve while (node != nullptr) { //Process the current interconnect component if it is of type OPIN, CHANX, CHANY, IPIN - if (device_ctx.rr_nodes[node->inode].type() == OPIN - || device_ctx.rr_nodes[node->inode].type() == IPIN - || device_ctx.rr_nodes[node->inode].type() == CHANX - || device_ctx.rr_nodes[node->inode].type() == CHANY) { + if (device_ctx.rr_graph.node_type(node->inode) == OPIN + || device_ctx.rr_graph.node_type(node->inode) == IPIN + || device_ctx.rr_graph.node_type(node->inode) == CHANX + || device_ctx.rr_graph.node_type(node->inode) == CHANY) { tatum::DelayComponent net_component; //declare a new instance of DelayComponent - net_component.type_name = device_ctx.rr_nodes[node->inode].type_string(); //write the component's type as a routing resource node - net_component.type_name += ":" + std::to_string(node->inode) + " "; //add the index of the routing resource node - if (device_ctx.rr_nodes[node->inode].type() == OPIN || device_ctx.rr_nodes[node->inode].type() == IPIN) { + net_component.type_name = std::string(rr_node_typename[device_ctx.rr_graph.node_type(node->inode)]); //write the component's type as a routing resource node + net_component.type_name += ":" + std::to_string(size_t(node->inode)) + " "; //add the index of the routing resource node + if (device_ctx.rr_graph.node_type(node->inode) == OPIN || device_ctx.rr_graph.node_type(node->inode) == IPIN) { net_component.type_name += "side:"; //add the side of the routing resource node - net_component.type_name += device_ctx.rr_nodes[node->inode].side_string(); //add the side of the routing resource node + net_component.type_name += std::string(SIDE_STRING[device_ctx.rr_graph.node_side(node->inode)]); //add the side of the routing resource node // For OPINs and IPINs the starting and ending coordinate are identical, so we can just arbitrarily assign the start to larger values // and the end to the lower coordinate - start_x = " (" + std::to_string(device_ctx.rr_nodes[node->inode].xhigh()) + ","; //start and end coordinates are the same for OPINs and IPINs - start_y = std::to_string(device_ctx.rr_nodes[node->inode].yhigh()) + ")"; + start_x = " (" + std::to_string(device_ctx.rr_graph.node_xhigh(node->inode)) + ","; //start and end coordinates are the same for OPINs and IPINs + start_y = std::to_string(device_ctx.rr_graph.node_yhigh(node->inode)) + ")"; end_x = ""; end_y = ""; arrow = ""; } - if (device_ctx.rr_nodes[node->inode].type() == CHANX || device_ctx.rr_nodes[node->inode].type() == CHANY) { //for channels, we would like to describe the component with segment specific information - net_component.type_name += device_ctx.arch->Segments[device_ctx.rr_indexed_data[device_ctx.rr_nodes[node->inode].cost_index()].seg_index].name; //Write the segment name - net_component.type_name += " length:" + std::to_string(device_ctx.rr_nodes[node->inode].length()); //add the length of the segment + if (device_ctx.rr_graph.node_type(node->inode) == CHANX || device_ctx.rr_graph.node_type(node->inode) == CHANY) { //for channels, we would like to describe the component with segment specific information + net_component.type_name += device_ctx.arch->Segments[device_ctx.rr_indexed_data[device_ctx.rr_graph.node_cost_index(node->inode)].seg_index].name; //Write the segment name + net_component.type_name += " length:" + std::to_string(device_ctx.rr_graph.node_length(node->inode)); //add the length of the segment //Figure out the starting and ending coordinate of the segment depending on the direction arrow = "->"; //we will point the coordinates from start to finish, left to right - if (device_ctx.rr_nodes[node->inode].direction() == DEC_DIRECTION) { //signal travels along decreasing direction - start_x = " (" + std::to_string(device_ctx.rr_nodes[node->inode].xhigh()) + ","; //start coordinates have large value - start_y = std::to_string(device_ctx.rr_nodes[node->inode].yhigh()) + ")"; - end_x = "(" + std::to_string(device_ctx.rr_nodes[node->inode].xlow()) + ","; //end coordinates have smaller value - end_y = std::to_string(device_ctx.rr_nodes[node->inode].ylow()) + ")"; + if (device_ctx.rr_graph.node_direction(node->inode) == DEC_DIRECTION) { //signal travels along decreasing direction + start_x = " (" + std::to_string(device_ctx.rr_graph.node_xhigh(node->inode)) + ","; //start coordinates have large value + start_y = std::to_string(device_ctx.rr_graph.node_yhigh(node->inode)) + ")"; + end_x = "(" + std::to_string(device_ctx.rr_graph.node_xlow(node->inode)) + ","; //end coordinates have smaller value + end_y = std::to_string(device_ctx.rr_graph.node_ylow(node->inode)) + ")"; } else { // signal travels in increasing direction, stays at same point, or can travel both directions - start_x = " (" + std::to_string(device_ctx.rr_nodes[node->inode].xlow()) + ","; //start coordinates have smaller value - start_y = std::to_string(device_ctx.rr_nodes[node->inode].ylow()) + ")"; - end_x = "(" + std::to_string(device_ctx.rr_nodes[node->inode].xhigh()) + ","; //end coordinates have larger value - end_y = std::to_string(device_ctx.rr_nodes[node->inode].yhigh()) + ")"; - if (device_ctx.rr_nodes[node->inode].direction() == BI_DIRECTION) { + start_x = " (" + std::to_string(device_ctx.rr_graph.node_xlow(node->inode)) + ","; //start coordinates have smaller value + start_y = std::to_string(device_ctx.rr_graph.node_ylow(node->inode)) + ")"; + end_x = "(" + std::to_string(device_ctx.rr_graph.node_xhigh(node->inode)) + ","; //end coordinates have larger value + end_y = std::to_string(device_ctx.rr_graph.node_yhigh(node->inode)) + ")"; + if (device_ctx.rr_graph.node_direction(node->inode) == BI_DIRECTION) { arrow = "<->"; //indicate that signal can travel both direction } } From 6933ff51e92b13eb9aaaacbf57bef6e230a9813b Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 1 Feb 2020 22:38:21 -0700 Subject: [PATCH 084/645] net delay adopt RRGraph object, compile with no errors --- vpr/src/timing/net_delay.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vpr/src/timing/net_delay.cpp b/vpr/src/timing/net_delay.cpp index a2cbc73a3..1bebb173d 100644 --- a/vpr/src/timing/net_delay.cpp +++ b/vpr/src/timing/net_delay.cpp @@ -29,7 +29,7 @@ * associated with that node. The map will be used to store delays while * * traversing the nodes of the route tree in load_one_net_delay_recurr. */ -static std::unordered_map inode_to_Tdel_map; +static std::unordered_map inode_to_Tdel_map; /*********************** Subroutines local to this module ********************/ @@ -107,7 +107,7 @@ static void load_one_net_delay(vtr::vector& net_delay, Clu } auto& cluster_ctx = g_vpr_ctx.clustering(); - int inode; + RRNodeId inode; t_rt_node* rt_root = traceback_to_route_tree(net_id); // obtain the root of the tree constructed from the traceback load_new_subtree_R_upstream(rt_root); // load in the resistance values for the route tree From 529a7ecab49a14f9cccaba4237d099fc7ca8f84f Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 3 Feb 2020 11:04:56 -0700 Subject: [PATCH 085/645] start refactor rr graph XML reader. Notice that rr_indexed_data builder should be signficantly reworked --- vpr/src/device/rr_graph_obj.h | 7 +- vpr/src/route/rr_graph_reader.cpp | 148 ++++++++++++++++++------------ 2 files changed, 92 insertions(+), 63 deletions(-) diff --git a/vpr/src/device/rr_graph_obj.h b/vpr/src/device/rr_graph_obj.h index abafa59b6..df46df754 100644 --- a/vpr/src/device/rr_graph_obj.h +++ b/vpr/src/device/rr_graph_obj.h @@ -890,8 +890,11 @@ class RRGraph { vtr::vector> node_edges_; /* Edge related data */ - size_t num_edges_; /* Range of edge ids */ - std::unordered_set invalid_edge_ids_; /* Invalid edge ids */ + /* Range of edge ids, use the unsigned long as + * the number of edges could be >10 times larger than the number of nodes! + */ + unsigned long num_edges_; + std::unordered_set invalid_edge_ids_; /* Invalid edge ids */ vtr::vector edge_src_nodes_; vtr::vector edge_sink_nodes_; vtr::vector edge_switches_; diff --git a/vpr/src/route/rr_graph_reader.cpp b/vpr/src/route/rr_graph_reader.cpp index e9380b12d..7200786ad 100644 --- a/vpr/src/route/rr_graph_reader.cpp +++ b/vpr/src/route/rr_graph_reader.cpp @@ -142,7 +142,7 @@ void load_rr_file(const t_graph_type graph_type, int num_rr_nodes = count_children(next_component, "node", loc_data); - device_ctx.rr_nodes.resize(num_rr_nodes); + device_ctx.rr_graph.reserve_nodes(num_rr_nodes); process_nodes(next_component, loc_data); /* Loads edges, switches, and node look up tables*/ @@ -158,17 +158,22 @@ void load_rr_file(const t_graph_type graph_type, //Partition the rr graph edges for efficient access to configurable/non-configurable //edge subsets. Must be done after RR switches have been allocated - partition_rr_graph_edges(device_ctx); + device_ctx.rr_graph.rebuild_node_edges(); - process_rr_node_indices(grid); - - init_fan_in(device_ctx.rr_nodes, device_ctx.rr_nodes.size()); + /* Essential check for rr_graph, build look-up */ + if (false == device_ctx.rr_graph.validate()) { + /* Error out if built-in validator of rr_graph fails */ + vpr_throw(VPR_ERROR_ROUTE, + __FILE__, + __LINE__, + "Fundamental errors occurred when validating rr_graph object!\n"); + } //sets the cost index and seg id information next_component = get_single_child(rr_graph, "rr_nodes", loc_data); set_cost_indices(next_component, loc_data, is_global_graph, segment_inf.size()); - alloc_and_load_rr_indexed_data(segment_inf, device_ctx.rr_node_indices, + alloc_and_load_rr_indexed_data(segment_inf, device_ctx.rr_graph, max_chan_width, *wire_to_rr_ipin_switch, base_cost_type); process_seg_id(next_component, loc_data); @@ -177,7 +182,14 @@ void load_rr_file(const t_graph_type graph_type, device_ctx.read_rr_graph_filename = std::string(read_rr_graph_name); check_rr_graph(graph_type, grid, device_ctx.physical_tile_types); - + /* Error out if advanced checker of rr_graph fails */ + if (false == check_rr_graph(device_ctx.rr_graph)) { + vpr_throw(VPR_ERROR_ROUTE, + __FILE__, + __LINE__, + "Advanced checking rr_graph object fails! Routing may still work " + "but not smooth\n"); + } } catch (pugiutil::XmlError& e) { vpr_throw(VPR_ERROR_ROUTE, read_rr_graph_name, e.line(), "%s", e.what()); } @@ -247,6 +259,13 @@ void process_switches(pugi::xml_node parent, const pugiutil::loc_data& loc_data) Switch = Switch.next_sibling(Switch.name()); } + + /* Add the switch to RRGraph local data */ + device_ctx.rr_graph.reserve_switches(device_ctx.rr_switch_inf.size()); + // Create the switches + for (size_t iswitch = 0; iswitch < device_ctx.rr_switch_inf.size(); ++iswitch) { + device_ctx.rr_graph.create_switch(device_ctx.rr_switch_inf[iswitch]); + } } /*Only CHANX and CHANY components have a segment id. This function @@ -289,41 +308,43 @@ void process_nodes(pugi::xml_node parent, const pugiutil::loc_data& loc_data) { while (rr_node) { int inode = get_attribute(rr_node, "id", loc_data).as_int(); - auto& node = device_ctx.rr_nodes[inode]; + t_rr_type node_type = NUM_RR_TYPES; const char* node_type = get_attribute(rr_node, "type", loc_data).as_string(); if (strcmp(node_type, "CHANX") == 0) { - node.set_type(CHANX); + node_type = CHANX; } else if (strcmp(node_type, "CHANY") == 0) { - node.set_type(CHANY); + node_type = CHANY; } else if (strcmp(node_type, "SOURCE") == 0) { - node.set_type(SOURCE); + node_type = SOURCE; } else if (strcmp(node_type, "SINK") == 0) { - node.set_type(SINK); + node_type = SINK; } else if (strcmp(node_type, "OPIN") == 0) { - node.set_type(OPIN); + node_type = OPIN; } else if (strcmp(node_type, "IPIN") == 0) { - node.set_type(IPIN); + node_type = IPIN; } else { VPR_FATAL_ERROR(VPR_ERROR_OTHER, "Valid inputs for class types are \"CHANX\", \"CHANY\",\"SOURCE\", \"SINK\",\"OPIN\", and \"IPIN\"."); } - if (node.type() == CHANX || node.type() == CHANY) { + const RRNodeId& node = device_ctx.rr_graph.create_node(node_type); + + if (device_ctx.rr_graph.node_type(node) == CHANX || device_ctx.rr_graph.node_type(node) == CHANY) { const char* correct_direction = get_attribute(rr_node, "direction", loc_data).as_string(); if (strcmp(correct_direction, "INC_DIR") == 0) { - node.set_direction(INC_DIRECTION); + device_ctx.rr_graph.set_node_direction(node, INC_DIRECTION); } else if (strcmp(correct_direction, "DEC_DIR") == 0) { - node.set_direction(DEC_DIRECTION); + device_ctx.rr_graph.set_node_direction(node, DEC_DIRECTION); } else if (strcmp(correct_direction, "BI_DIR") == 0) { - node.set_direction(BI_DIRECTION); + device_ctx.rr_graph.set_node_direction(node, BI_DIRECTION); } else { VTR_ASSERT((strcmp(correct_direction, "NO_DIR") == 0)); - node.set_direction(NO_DIRECTION); + device_ctx.rr_graph.set_node_direction(node, NO_DIRECTION); } } - node.set_capacity(get_attribute(rr_node, "capacity", loc_data).as_float()); + device_ctx.rr_graph.set_node_capacity(node, get_attribute(rr_node, "capacity", loc_data).as_float()); //-------------- locSubnode = get_single_child(rr_node, "loc", loc_data); @@ -334,7 +355,7 @@ void process_nodes(pugi::xml_node parent, const pugiutil::loc_data& loc_data) { y1 = get_attribute(locSubnode, "ylow", loc_data).as_float(); y2 = get_attribute(locSubnode, "yhigh", loc_data).as_float(); - if (node.type() == IPIN || node.type() == OPIN) { + if (device_ctx.rr_graph.node_type(node) == IPIN || device_ctx.rr_graph.node_type(node) == OPIN) { e_side side; std::string side_str = get_attribute(locSubnode, "side", loc_data).as_string(); if (side_str == "LEFT") { @@ -347,11 +368,11 @@ void process_nodes(pugi::xml_node parent, const pugiutil::loc_data& loc_data) { VTR_ASSERT(side_str == "BOTTOM"); side = BOTTOM; } - node.set_side(side); + device_ctx.rr_graph.set_node_side(node, side); } - node.set_coordinates(x1, y1, x2, y2); - node.set_ptc_num(get_attribute(locSubnode, "ptc", loc_data).as_int()); + device_ctx.rr_graph.set_node_bouding_box(node, vtr::Rect(x1, y1, x2, y2)); + device_ctx.rr_graph.set_node_ptc_num(node, get_attribute(locSubnode, "ptc", loc_data).as_int()); //------- timingSubnode = get_single_child(rr_node, "timing", loc_data, pugiutil::OPTIONAL); @@ -362,10 +383,7 @@ void process_nodes(pugi::xml_node parent, const pugiutil::loc_data& loc_data) { R = get_attribute(timingSubnode, "R", loc_data).as_float(); C = get_attribute(timingSubnode, "C", loc_data).as_float(); } - node.set_rc_index(find_create_rr_rc_data(R, C)); - - //clear each node edge - node.set_num_edges(0); + device_ctx.rr_graph.set_node_rc_data_index(node, find_create_rr_rc_data(R, C)); // // CLBLL_L_ @@ -394,32 +412,42 @@ void process_edges(pugi::xml_node parent, const pugiutil::loc_data& loc_data, in edges = get_first_child(parent, "edge", loc_data); //count the number of edges and store it in a vector - std::vector num_edges_for_node; - num_edges_for_node.resize(device_ctx.rr_nodes.size(), 0); + vtr::vector num_edges_for_node; + num_edges_for_node.resize(device_ctx.rr_graph.nodes().size(), 0); + + unsigned long num_edges_to_reserve = 0; while (edges) { - size_t source_node = get_attribute(edges, "src_node", loc_data).as_uint(); - if (source_node >= device_ctx.rr_nodes.size()) { + RRNodeId source_node = RRNodeId(get_attribute(edges, "src_node", loc_data).as_uint()); + if (false == device_ctx.rr_graph.valid_node_id(source_node)) { VPR_FATAL_ERROR(VPR_ERROR_OTHER, "source_node %d is larger than rr_nodes.size() %d", - source_node, device_ctx.rr_nodes.size()); + size_t(source_node), device_ctx.rr_graph.nodes().size()); } num_edges_for_node[source_node]++; + num_edges_to_reserve++; edges = edges.next_sibling(edges.name()); } //reset this vector in order to start count for num edges again - for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); inode++) { - if (num_edges_for_node[inode] > std::numeric_limits::max()) { + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { + /* uint16_t is the data type for each type of edges in RRGraph object + * Multiplied by 4 is due to the fact that each node has 4 groups of edges + * and each group is bounded by uint16_t + * See rr_graph_obj.h for more details + */ + if (num_edges_for_node[inode] > 4 * std::numeric_limits::max()) { VPR_FATAL_ERROR(VPR_ERROR_OTHER, "source node %d edge count %d is too high", - inode, num_edges_for_node[inode]); + size_t(inode), num_edges_for_node[inode]); } - device_ctx.rr_nodes[inode].set_num_edges(num_edges_for_node[inode]); num_edges_for_node[inode] = 0; } + /* Reserve the memory for edges */ + device_ctx.rr_graph.reserve_edges(num_edges_to_reserve); + edges = get_first_child(parent, "edge", loc_data); /*initialize a vector that keeps track of the number of wire to ipin switches * There should be only one wire to ipin switch. In case there are more, make sure to @@ -430,14 +458,14 @@ void process_edges(pugi::xml_node parent, const pugiutil::loc_data& loc_data, in std::pair most_frequent_switch(-1, 0); while (edges) { - size_t source_node = get_attribute(edges, "src_node", loc_data).as_uint(); - size_t sink_node = get_attribute(edges, "sink_node", loc_data).as_uint(); + RRNodeId source_node = RRNodeId(get_attribute(edges, "src_node", loc_data).as_uint()); + RRNodeId sink_node = RRNodeId(get_attribute(edges, "sink_node", loc_data).as_uint()); int switch_id = get_attribute(edges, "switch_id", loc_data).as_int(); - if (sink_node >= device_ctx.rr_nodes.size()) { + if (false == device_ctx.rr_graph.valid_node_id(sink_node)) { VPR_FATAL_ERROR(VPR_ERROR_OTHER, "sink_node %d is larger than rr_nodes.size() %d", - sink_node, device_ctx.rr_nodes.size()); + size_t(sink_node), device_ctx.rr_graph.nodes().size()); } if (switch_id >= num_rr_switches) { @@ -448,8 +476,8 @@ void process_edges(pugi::xml_node parent, const pugiutil::loc_data& loc_data, in /*Keeps track of the number of the specific type of switch that connects a wire to an ipin * use the pair data structure to keep the maximum*/ - if (device_ctx.rr_nodes[source_node].type() == CHANX || device_ctx.rr_nodes[source_node].type() == CHANY) { - if (device_ctx.rr_nodes[sink_node].type() == IPIN) { + if (device_ctx.rr_graph.node_type(source_node) == CHANX || device_ctx.rr_graph.node_type(source_node) == CHANY) { + if (device_ctx.rr_graph.node_type(sink_node) == IPIN) { count_for_wire_to_ipin_switches[switch_id]++; if (count_for_wire_to_ipin_switches[switch_id] > most_frequent_switch.second) { most_frequent_switch.first = switch_id; @@ -458,8 +486,7 @@ void process_edges(pugi::xml_node parent, const pugiutil::loc_data& loc_data, in } } //set edge in correct rr_node data structure - device_ctx.rr_nodes[source_node].set_edge_sink_node(num_edges_for_node[source_node], sink_node); - device_ctx.rr_nodes[source_node].set_edge_switch(num_edges_for_node[source_node], switch_id); + device_ctx.rr_graph.create_edge(source_node, sink_node, RRSwitchId(switch_id)); // Read the metadata for the edge auto metadata = get_single_child(edges, "metadata", loc_data, pugiutil::OPTIONAL); @@ -842,15 +869,15 @@ void set_cost_indices(pugi::xml_node parent, const pugiutil::loc_data& loc_data, auto& device_ctx = g_vpr_ctx.mutable_device(); //set the cost index in order to load the segment information, rr nodes should be set already - for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); inode++) { - if (device_ctx.rr_nodes[inode].type() == SOURCE) { - device_ctx.rr_nodes[inode].set_cost_index(SOURCE_COST_INDEX); - } else if (device_ctx.rr_nodes[inode].type() == SINK) { - device_ctx.rr_nodes[inode].set_cost_index(SINK_COST_INDEX); - } else if (device_ctx.rr_nodes[inode].type() == IPIN) { - device_ctx.rr_nodes[inode].set_cost_index(IPIN_COST_INDEX); - } else if (device_ctx.rr_nodes[inode].type() == OPIN) { - device_ctx.rr_nodes[inode].set_cost_index(OPIN_COST_INDEX); + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { + if (device_ctx.rr_graph.node_type(inode) == SOURCE) { + device_ctx.rr_graph.set_node_cost_index(inode, SOURCE_COST_INDEX); + } else if (device_ctx.rr_graph.node_type(inode) == SINK) { + device_ctx.rr_graph.set_node_cost_index(inode, SINK_COST_INDEX); + } else if (device_ctx.rr_graph.node_type(inode) == IPIN) { + device_ctx.rr_graph.set_node_cost_index(inode, IPIN_COST_INDEX); + } else if (device_ctx.rr_graph.node_type(inode) == OPIN) { + device_ctx.rr_graph.set_node_cost_index(inode, OPIN_COST_INDEX); } } @@ -862,8 +889,7 @@ void set_cost_indices(pugi::xml_node parent, const pugiutil::loc_data& loc_data, rr_node = get_first_child(parent, "node", loc_data); while (rr_node) { - int inode = get_attribute(rr_node, "id", loc_data).as_int(); - auto& node = device_ctx.rr_nodes[inode]; + RRNodeId inode = RRNodeId(get_attribute(rr_node, "id", loc_data).as_int()); /*CHANX and CHANY cost index is dependent on the segment id*/ @@ -873,11 +899,11 @@ void set_cost_indices(pugi::xml_node parent, const pugiutil::loc_data& loc_data, if (attribute) { int seg_id = get_attribute(segmentSubnode, "segment_id", loc_data).as_int(0); if (is_global_graph) { - node.set_cost_index(0); - } else if (node.type() == CHANX) { - node.set_cost_index(CHANX_COST_INDEX_START + seg_id); - } else if (node.type() == CHANY) { - node.set_cost_index(CHANX_COST_INDEX_START + num_seg_types + seg_id); + device_ctx.rr_graph.set_node_cost_index(0); + } else if (device_ctx.rr_graph.node_type(inode) == CHANX) { + device_ctx.rr_graph.set_node_cost_index(inode, CHANX_COST_INDEX_START + seg_id); + } else if (device_ctx.rr_graph.node_type(inode) == CHANY) { + device_ctx.rr_graph.set_node_cost_index(inode, CHANX_COST_INDEX_START + num_seg_types + seg_id); } } } From 9df76769508457bb372a603c57d1cd3aff73866e Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 3 Feb 2020 11:25:05 -0700 Subject: [PATCH 086/645] refactored rr_graph indexed data builder --- vpr/src/route/rr_graph2.cpp | 34 +++++++------- vpr/src/route/rr_graph2.h | 10 ++--- vpr/src/route/rr_graph_indexed_data.cpp | 59 +++++++++++++------------ vpr/src/route/rr_graph_indexed_data.h | 3 +- 4 files changed, 54 insertions(+), 52 deletions(-) diff --git a/vpr/src/route/rr_graph2.cpp b/vpr/src/route/rr_graph2.cpp index 556a87922..2b7ddb4da 100644 --- a/vpr/src/route/rr_graph2.cpp +++ b/vpr/src/route/rr_graph2.cpp @@ -1343,29 +1343,29 @@ int get_rr_node_index(const t_rr_node_indices& L_rr_node_indices, return ((unsigned)ptc < lookup.size() ? lookup[ptc] : -1); } -int find_average_rr_node_index(int device_width, - int device_height, - t_rr_type rr_type, - int ptc, - const t_rr_node_indices& L_rr_node_indices) { +RRNodeId find_average_rr_node_index(int device_width, + int device_height, + t_rr_type rr_type, + int ptc, + const RRGraph& rr_graph) { /* Find and return the index to a rr_node that is located at the "center" * * of the current grid array, if possible. In the event the "center" of * * the grid array is an EMPTY or IO node, then retry alterate locations. * * Worst case, this function will simply return the 1st non-EMPTY and * * non-IO node. */ - int inode = get_rr_node_index(L_rr_node_indices, (device_width) / 2, (device_height) / 2, - rr_type, ptc); + RRNodeId inode = rr_graph.find_node((device_width) / 2, (device_height) / 2, + rr_type, ptc); - if (inode == OPEN) { - inode = get_rr_node_index(L_rr_node_indices, (device_width) / 4, (device_height) / 4, - rr_type, ptc); + if (inode == RRNodeId::INVALID()) { + inode = rr_graph.find_node((device_width) / 4, (device_height) / 4, + rr_type, ptc); } - if (inode == OPEN) { - inode = get_rr_node_index(L_rr_node_indices, (device_width) / 4 * 3, (device_height) / 4 * 3, - rr_type, ptc); + if (inode == RRNodeId::INVALID()) { + inode = rr_graph.find_node((device_width) / 4 * 3, (device_height) / 4 * 3, + rr_type, ptc); } - if (inode == OPEN) { + if (inode == RRNodeId::INVALID()) { auto& device_ctx = g_vpr_ctx.device(); for (int x = 0; x < device_width; ++x) { @@ -1375,11 +1375,11 @@ int find_average_rr_node_index(int device_width, if (is_io_type(device_ctx.grid[x][y].type)) continue; - inode = get_rr_node_index(L_rr_node_indices, x, y, rr_type, ptc); - if (inode != OPEN) + inode = rr_graph.find_node(x, y, rr_type, ptc); + if (inode != RRNodeId::INVALID()) break; } - if (inode != OPEN) + if (inode != RRNodeId::INVALID()) break; } } diff --git a/vpr/src/route/rr_graph2.h b/vpr/src/route/rr_graph2.h index 617730a0d..dae44608c 100644 --- a/vpr/src/route/rr_graph2.h +++ b/vpr/src/route/rr_graph2.h @@ -73,11 +73,11 @@ int get_rr_node_index(const t_rr_node_indices& L_rr_node_indices, int ptc, e_side side = NUM_SIDES); -int find_average_rr_node_index(int device_width, - int device_height, - t_rr_type rr_type, - int ptc, - const t_rr_node_indices& L_rr_node_indices); +RRNodeId find_average_rr_node_index(int device_width, + int device_height, + t_rr_type rr_type, + int ptc, + const RRGraph& rr_graph); t_seg_details* alloc_and_load_seg_details(int* max_chan_width, const int max_len, diff --git a/vpr/src/route/rr_graph_indexed_data.cpp b/vpr/src/route/rr_graph_indexed_data.cpp index 0d8fc8532..bc73a1511 100644 --- a/vpr/src/route/rr_graph_indexed_data.cpp +++ b/vpr/src/route/rr_graph_indexed_data.cpp @@ -16,17 +16,17 @@ /******************* Subroutines local to this module ************************/ static void load_rr_indexed_data_base_costs(int nodes_per_chan, - const t_rr_node_indices& L_rr_node_indices, + const RRGraph& rr_graph, enum e_base_cost_type base_cost_type); static float get_delay_normalization_fac(int nodes_per_chan, - const t_rr_node_indices& L_rr_node_indices); + const RRGraph& rr_graph); static void load_rr_indexed_data_T_values(int index_start, int num_indices_to_load, t_rr_type rr_type, int nodes_per_chan, - const t_rr_node_indices& L_rr_node_indices); + const RRGraph& rr_graph); static void fixup_rr_indexed_data_T_values(size_t num_segment); @@ -48,7 +48,7 @@ static std::vector count_rr_segment_types(); * x-channel its own cost_index, and each segment type in a y-channel its * * own cost_index. */ void alloc_and_load_rr_indexed_data(const std::vector& segment_inf, - const t_rr_node_indices& L_rr_node_indices, + const RRGraph& rr_graph, const int nodes_per_chan, int wire_to_ipin_switch, enum e_base_cost_type base_cost_type) { @@ -93,7 +93,7 @@ void alloc_and_load_rr_indexed_data(const std::vector& segment_in device_ctx.rr_indexed_data[index].seg_index = iseg; } load_rr_indexed_data_T_values(CHANX_COST_INDEX_START, num_segment, CHANX, - nodes_per_chan, L_rr_node_indices); + nodes_per_chan, rr_graph); /* Y-directed segments. */ for (iseg = 0; iseg < num_segment; iseg++) { @@ -114,11 +114,11 @@ void alloc_and_load_rr_indexed_data(const std::vector& segment_in device_ctx.rr_indexed_data[index].seg_index = iseg; } load_rr_indexed_data_T_values((CHANX_COST_INDEX_START + num_segment), - num_segment, CHANY, nodes_per_chan, L_rr_node_indices); + num_segment, CHANY, nodes_per_chan, rr_graph); fixup_rr_indexed_data_T_values(num_segment); - load_rr_indexed_data_base_costs(nodes_per_chan, L_rr_node_indices, + load_rr_indexed_data_base_costs(nodes_per_chan, rr_graph, base_cost_type); } @@ -143,7 +143,7 @@ void load_rr_index_segments(const int num_segment) { } static void load_rr_indexed_data_base_costs(int nodes_per_chan, - const t_rr_node_indices& L_rr_node_indices, + const RRGraph& rr_graph, enum e_base_cost_type base_cost_type) { /* Loads the base_cost member of device_ctx.rr_indexed_data according to the specified * * base_cost_type. */ @@ -156,7 +156,7 @@ static void load_rr_indexed_data_base_costs(int nodes_per_chan, if (base_cost_type == DEMAND_ONLY || base_cost_type == DEMAND_ONLY_NORMALIZED_LENGTH) { delay_normalization_fac = 1.; } else { - delay_normalization_fac = get_delay_normalization_fac(nodes_per_chan, L_rr_node_indices); + delay_normalization_fac = get_delay_normalization_fac(nodes_per_chan, rr_graph); } device_ctx.rr_indexed_data[SOURCE_COST_INDEX].base_cost = delay_normalization_fac; @@ -240,12 +240,13 @@ static std::vector count_rr_segment_types() { } static float get_delay_normalization_fac(int nodes_per_chan, - const t_rr_node_indices& L_rr_node_indices) { + const RRGraph& rr_graph) { /* Returns the average delay to go 1 CLB distance along a wire. */ const int clb_dist = 3; /* Number of CLBs I think the average conn. goes. */ - int inode, itrack, cost_index; + RRNodeId inode; + int itrack, cost_index; float Tdel, Tdel_sum, frac_num_seg; auto& device_ctx = g_vpr_ctx.device(); @@ -254,10 +255,10 @@ static float get_delay_normalization_fac(int nodes_per_chan, for (itrack = 0; itrack < nodes_per_chan; itrack++) { inode = find_average_rr_node_index(device_ctx.grid.width(), device_ctx.grid.height(), CHANX, itrack, - L_rr_node_indices); - if (inode == -1) + rr_graph); + if (inode == RRNodeId::INVALID()) continue; - cost_index = device_ctx.rr_nodes[inode].cost_index(); + cost_index = device_ctx.rr_graph.node_cost_index(inode); frac_num_seg = clb_dist * device_ctx.rr_indexed_data[cost_index].inv_length; Tdel = frac_num_seg * device_ctx.rr_indexed_data[cost_index].T_linear + frac_num_seg * frac_num_seg @@ -267,10 +268,10 @@ static float get_delay_normalization_fac(int nodes_per_chan, for (itrack = 0; itrack < nodes_per_chan; itrack++) { inode = find_average_rr_node_index(device_ctx.grid.width(), device_ctx.grid.height(), CHANY, itrack, - L_rr_node_indices); - if (inode == -1) + rr_graph); + if (inode == RRNodeId::INVALID()) continue; - cost_index = device_ctx.rr_nodes[inode].cost_index(); + cost_index = device_ctx.rr_graph.node_cost_index(inode); frac_num_seg = clb_dist * device_ctx.rr_indexed_data[cost_index].inv_length; Tdel = frac_num_seg * device_ctx.rr_indexed_data[cost_index].T_linear + frac_num_seg * frac_num_seg @@ -285,7 +286,7 @@ static void load_rr_indexed_data_T_values(int index_start, int num_indices_to_load, t_rr_type rr_type, int nodes_per_chan, - const t_rr_node_indices& L_rr_node_indices) { + const RRGraph& rr_graph) { /* Loads the average propagation times through segments of each index type * * for either all CHANX segment types or all CHANY segment types. It does * * this by looking at all the segments in one channel in the middle of the * @@ -293,7 +294,8 @@ static void load_rr_indexed_data_T_values(int index_start, * same type and using them to compute average delay values for this type of * * segment. */ - int itrack, inode, cost_index; + int itrack, cost_index; + RRNodeId inode; float *C_total, *R_total; /* [0..device_ctx.rr_indexed_data.size() - 1] */ double *switch_R_total, *switch_T_total, *switch_Cinternal_total; /* [0..device_ctx.rr_indexed_data.size() - 1] */ short* switches_buffered; @@ -327,26 +329,25 @@ static void load_rr_indexed_data_T_values(int index_start, for (itrack = 0; itrack < nodes_per_chan; itrack++) { inode = find_average_rr_node_index(device_ctx.grid.width(), device_ctx.grid.height(), rr_type, itrack, - L_rr_node_indices); - if (inode == -1) + rr_graph); + if (inode == RRNodeId::INVALID()) continue; - cost_index = device_ctx.rr_nodes[inode].cost_index(); + cost_index = rr_graph.node_cost_index(inode); num_nodes_of_index[cost_index]++; - C_total[cost_index] += device_ctx.rr_nodes[inode].C(); - R_total[cost_index] += device_ctx.rr_nodes[inode].R(); + C_total[cost_index] += rr_graph.node_C(inode); + R_total[cost_index] += rr_graph.node_R(inode); /* get average switch parameters */ - int num_edges = device_ctx.rr_nodes[inode].num_edges(); double avg_switch_R = 0; double avg_switch_T = 0; double avg_switch_Cinternal = 0; int num_switches = 0; short buffered = UNDEFINED; - for (int iedge = 0; iedge < num_edges; iedge++) { - int to_node_index = device_ctx.rr_nodes[inode].edge_sink_node(iedge); + for (const RREdgeId& iedge : rr_graph.node_out_edges(inode)) { + RRNodeId to_node_index = device_ctx.rr_graph.edge_sink_node(iedge); /* want to get C/R/Tdel/Cinternal of switches that connect this track segment to other track segments */ - if (device_ctx.rr_nodes[to_node_index].type() == CHANX || device_ctx.rr_nodes[to_node_index].type() == CHANY) { - int switch_index = device_ctx.rr_nodes[inode].edge_switch(iedge); + if (device_ctx.rr_graph.node_type(to_node_index) == CHANX || device_ctx.rr_graph.node_type(to_node_index) == CHANY) { + int switch_index = device_ctx.rr_graph.edge_switch(iedge); avg_switch_R += device_ctx.rr_switch_inf[switch_index].R; avg_switch_T += device_ctx.rr_switch_inf[switch_index].Tdel; avg_switch_Cinternal += device_ctx.rr_switch_inf[switch_index].Cinternal; diff --git a/vpr/src/route/rr_graph_indexed_data.h b/vpr/src/route/rr_graph_indexed_data.h index 4e5f8e051..fe2a2a141 100644 --- a/vpr/src/route/rr_graph_indexed_data.h +++ b/vpr/src/route/rr_graph_indexed_data.h @@ -1,9 +1,10 @@ #ifndef RR_GRAPH_INDEXED_DATA_H #define RR_GRAPH_INDEXED_DATA_H #include "physical_types.h" +#include "rr_graph_obj.h" void alloc_and_load_rr_indexed_data(const std::vector& segment_inf, - const t_rr_node_indices& L_rr_node_indices, + const RRGraph& rr_graph, int nodes_per_chan, int wire_to_ipin_switch, enum e_base_cost_type base_cost_type); From 4fb6d7e3aeb8207aab968453253689940eb55770 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 3 Feb 2020 13:21:48 -0700 Subject: [PATCH 087/645] halfway in refactoring the rr_graph builder --- vpr/src/route/rr_graph.cpp | 319 +++++++++++++++++++----------------- vpr/src/route/rr_graph2.cpp | 80 ++++----- vpr/src/route/rr_graph2.h | 22 +-- 3 files changed, 221 insertions(+), 200 deletions(-) diff --git a/vpr/src/route/rr_graph.cpp b/vpr/src/route/rr_graph.cpp index d9c387cd2..5ff1f9f35 100644 --- a/vpr/src/route/rr_graph.cpp +++ b/vpr/src/route/rr_graph.cpp @@ -108,7 +108,7 @@ static void build_bidir_rr_opins(const int i, const int j, const e_side side, const t_rr_node_indices& L_rr_node_indices, - const std::vector& rr_nodes, + const RRGraph& rr_nodes, const t_pin_to_track_lookup& opin_to_track_map, const std::vector>& Fc_out, t_rr_edge_info_set& created_rr_edges, @@ -139,20 +139,20 @@ static void build_unidir_rr_opins(const int i, const t_clb_to_clb_directs* clb_to_clb_directs, const int num_seg_types); -static int get_opin_direct_connecions(int x, - int y, - e_side side, - int opin, - int from_rr_node, - t_rr_edge_info_set& rr_edges_to_create, - const t_rr_node_indices& L_rr_node_indices, - const std::vector& rr_nodes, - const t_direct_inf* directs, - const int num_directs, - const t_clb_to_clb_directs* clb_to_clb_directs); +static int get_opin_direct_connections(int x, + int y, + e_side side, + int opin, + const RRNodeId& from_rr_node, + t_rr_edge_info_set& rr_edges_to_create, + const t_rr_node_indices& L_rr_node_indices, + const RRGraph& rr_nodes, + const t_direct_inf* directs, + const int num_directs, + const t_clb_to_clb_directs* clb_to_clb_directs); static void alloc_and_load_rr_graph(const int num_nodes, - std::vector& L_rr_node, + RRGraph& rr_graph, const int num_seg_types, const t_chan_details& chan_details_x, const t_chan_details& chan_details_y, @@ -212,7 +212,7 @@ static std::vector> alloc_and_load_perturb_ipins(const int L_n static void build_rr_sinks_sources(const int i, const int j, - std::vector& L_rr_node, + RRGraph& rr_graph, t_rr_edge_info_set& rr_edges_to_create, const t_rr_node_indices& L_rr_node_indices, const int delayless_switch, @@ -234,13 +234,13 @@ static void build_rr_chan(const int i, const t_chan_details& chan_details_y, const t_rr_node_indices& L_rr_node_indices, t_rr_edge_info_set& created_rr_edges, - std::vector& L_rr_node, + RRGraph& rr_graph, const int wire_to_ipin_switch, const enum e_directionality directionality); void uniquify_edges(t_rr_edge_info_set& rr_edges_to_create); -void alloc_and_load_edges(std::vector& L_rr_node, +void alloc_and_load_edges(RRGraph& rr_graph, const t_rr_edge_info_set& rr_edges_to_create); static void alloc_and_load_rr_switch_inf(const int num_arch_switches, @@ -278,9 +278,9 @@ static std::vector> alloc_and_load_actual_fc(const std::vector< const enum e_directionality directionality, bool* Fc_clipped); -static int pick_best_direct_connect_target_rr_node(const std::vector& rr_nodes, - int from_rr, - const std::vector& candidate_rr_nodes); +static RRNodeId pick_best_direct_connect_target_rr_node(const RRGraph& rr_graph, + const RRNodeId& from_rr, + const std::vector& candidate_rr_nodes); static void process_non_config_sets(); @@ -336,7 +336,7 @@ void create_rr_graph(const t_graph_type graph_type, det_routing_arch->read_rr_graph_filename.c_str()); /* Xifan Tang - Create rr_graph object: load rr_nodes to the object */ - convert_rr_graph(segment_inf); + //convert_rr_graph(segment_inf); } } else { if (channel_widths_unchanged(device_ctx.chan_width, nodes_per_chan) && !device_ctx.rr_nodes.empty()) { @@ -591,9 +591,13 @@ static void build_rr_graph(const t_graph_type graph_type, /* Alloc node lookups, count nodes, alloc rr nodes */ int num_rr_nodes = 0; + /* Xifan Tang - Keep the node indices here since it counts the number of rr_nodes + * We will simplify this function as indices will be built inside the RRGraph object + */ device_ctx.rr_node_indices = alloc_and_load_rr_node_indices(max_chan_width, grid, &num_rr_nodes, chan_details_x, chan_details_y); - device_ctx.rr_nodes.resize(num_rr_nodes); + /* Reserve the node for the RRGraph object */ + device_ctx.rr_graph.reserve_node(num_rr_nodes); /* These are data structures used by the the unidir opin mapping. They are used * to spread connections evenly for each segment type among the available @@ -692,7 +696,7 @@ static void build_rr_graph(const t_graph_type graph_type, /* END OPIN MAP */ bool Fc_clipped = false; - alloc_and_load_rr_graph(device_ctx.rr_nodes.size(), device_ctx.rr_nodes, segment_inf.size(), + alloc_and_load_rr_graph(device_ctx.rr_graph.nodes().size(), device_ctx.rr_graph, segment_inf.size(), chan_details_x, chan_details_y, track_to_pin_lookup, opin_to_track_map, switch_block_conn, sb_conn_map, grid, Fs, unidir_sb_pattern, @@ -708,14 +712,14 @@ static void build_rr_graph(const t_graph_type graph_type, /* Update rr_nodes capacities if global routing */ if (graph_type == GRAPH_GLOBAL) { - for (size_t i = 0; i < device_ctx.rr_nodes.size(); i++) { - if (device_ctx.rr_nodes[i].type() == CHANX) { - int ylow = device_ctx.rr_nodes[i].ylow(); - device_ctx.rr_nodes[i].set_capacity(nodes_per_chan.x_list[ylow]); + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { + if (device_ctx.rr_graph.node_type(inode) == CHANX) { + int ylow = device_ctx.rr_graph.node_ylow(inode); + device_ctx.rr_graph.set_node_capacity(inode, nodes_per_chan.x_list[ylow]); } - if (device_ctx.rr_nodes[i].type() == CHANY) { - int xlow = device_ctx.rr_nodes[i].xlow(); - device_ctx.rr_nodes[i].set_capacity(nodes_per_chan.y_list[xlow]); + if (device_ctx.rr_graph.node_type(inode) == CHANY) { + int xlow = device_ctx.rr_graph.node_xlow(inode); + device_ctx.rr_graph.set_node_capacity(inode, nodes_per_chan.y_list[xlow]); } } } @@ -726,7 +730,16 @@ static void build_rr_graph(const t_graph_type graph_type, //Partition the rr graph edges for efficient access to configurable/non-configurable //edge subsets. Must be done after RR switches have been allocated - partition_rr_graph_edges(device_ctx); + device_ctx.rr_graph.rebuild_node_edges(); + + /* Essential check for rr_graph, build look-up and */ + if (false == device_ctx.rr_graph.validate()) { + /* Error out if built-in validator of rr_graph fails */ + vpr_throw(VPR_ERROR_ROUTE, + __FILE__, + __LINE__, + "Fundamental errors occurred when validating rr_graph object!\n"); + } //Save the channel widths for the newly constructed graph device_ctx.chan_width = nodes_per_chan; @@ -735,6 +748,14 @@ static void build_rr_graph(const t_graph_type graph_type, *wire_to_rr_ipin_switch, base_cost_type); check_rr_graph(graph_type, grid, types); + /* Error out if advanced checker of rr_graph fails */ + if (false == check_rr_graph(device_ctx.rr_graph)) { + vpr_throw(VPR_ERROR_ROUTE, + __FILE__, + __LINE__, + "Advanced checking rr_graph object fails! Routing may still work " + "but not smooth\n"); + } /* Free all temp structs */ if (seg_details) { @@ -829,11 +850,11 @@ static void alloc_rr_switch_inf(t_arch_switch_fanin& arch_switch_fanins) { // //Note that since we don't store backward edge info in the RR graph we need to walk //the whole graph to get the per-switch-type fanin info - std::vector> inward_switch_inf(device_ctx.rr_nodes.size()); //[to_node][arch_switch] -> fanin - for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); ++inode) { - for (auto iedge : device_ctx.rr_nodes[inode].edges()) { - int iswitch = device_ctx.rr_nodes[inode].edge_switch(iedge); - int to_node = device_ctx.rr_nodes[inode].edge_sink_node(iedge); + std::vector> inward_switch_inf(device_ctx.rr_graph.nodes().size()); //[to_node][arch_switch] -> fanin + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { + for (const RREdgeId& iedge : device_ctx.rr_graph.node_out_edges(inode)) { + int iswitch = device_ctx.rr_graph.edge_switch(iedge); + const RRNodeId& to_node = device_ctx.rr_graph.edge_sink_node(iedge); if (inward_switch_inf[to_node].count(iswitch) == 0) { inward_switch_inf[to_node][iswitch] = 0; @@ -843,7 +864,7 @@ static void alloc_rr_switch_inf(t_arch_switch_fanin& arch_switch_fanins) { } //Record the unique switch type/fanin combinations - for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); ++inode) { + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { for (auto& switch_fanin : inward_switch_inf[inode]) { int iswitch, fanin; std::tie(iswitch, fanin) = switch_fanin; @@ -891,6 +912,14 @@ static void load_rr_switch_inf(const int num_arch_switches, const float R_minW_n load_rr_switch_from_arch_switch(i_arch_switch, i_rr_switch, fanin, R_minW_nmos, R_minW_pmos); } } + + /* Create switches as internal data of RRGraph object */ + device_ctx.rr_graph.reserve_switches(device_ctx.rr_switch_inf.size()); + // Create the switches + for (size_t iswitch = 0; iswitch < device_ctx.rr_switch_inf.size(); ++iswitch) { + device_ctx.rr_graph.create_switch(device_ctx.rr_switch_inf[iswitch]); + } + } void load_rr_switch_from_arch_switch(int arch_switch_idx, @@ -931,14 +960,13 @@ void load_rr_switch_from_arch_switch(int arch_switch_idx, static void remap_rr_node_switch_indices(const t_arch_switch_fanin& switch_fanin) { auto& device_ctx = g_vpr_ctx.mutable_device(); - for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); inode++) { - auto& from_node = device_ctx.rr_nodes[inode]; - int num_edges = from_node.num_edges(); - for (int iedge = 0; iedge < num_edges; iedge++) { - const t_rr_node& to_node = device_ctx.rr_nodes[from_node.edge_sink_node(iedge)]; + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { + const RRNodeId& from_node = inode; + for (const RREdgeId& iedge : device_ctx.rr_graph.node_out_edges(from_node)) { + const RRNodeId& to_node = device_ctx.rr_graph.edge_sink_node(iedge); /* get the switch which this edge uses and its fanin */ - int switch_index = from_node.edge_switch(iedge); - int fanin = to_node.fan_in(); + int switch_index = device_ctx.rr_graph.edge_switch(iedge); + int fanin = device_ctx.rr_graph.node_in_edges(to_node).size(); if (switch_fanin[switch_index].count(UNDEFINED) == 1) { fanin = UNDEFINED; @@ -949,7 +977,7 @@ static void remap_rr_node_switch_indices(const t_arch_switch_fanin& switch_fanin int rr_switch_index = itr->second; - from_node.set_edge_switch(iedge, rr_switch_index); + device_ctx.rr_graph.set_edge_switch(iedge, rr_switch_index); } } } @@ -961,7 +989,7 @@ static void rr_graph_externals(const std::vector& segment_inf, auto& device_ctx = g_vpr_ctx.device(); add_rr_graph_C_from_switches(device_ctx.rr_switch_inf[wire_to_rr_ipin_switch].Cin); - alloc_and_load_rr_indexed_data(segment_inf, device_ctx.rr_node_indices, + alloc_and_load_rr_indexed_data(segment_inf, device_ctx.rr_graph, max_chan_width, wire_to_rr_ipin_switch, base_cost_type); load_rr_index_segments(segment_inf.size()); } @@ -1187,7 +1215,7 @@ static void free_type_track_to_pin_map(t_track_to_pin_lookup& track_to_pin_map, /* Does the actual work of allocating the rr_graph and filling all the * * appropriate values. Everything up to this was just a prelude! */ static void alloc_and_load_rr_graph(const int num_nodes, - std::vector& L_rr_node, + RRGraph& rr_graph, const int num_seg_types, const t_chan_details& chan_details_x, const t_chan_details& chan_details_y, @@ -1233,12 +1261,12 @@ static void alloc_and_load_rr_graph(const int num_nodes, /* Connection SINKS and SOURCES to their pins. */ for (size_t i = 0; i < grid.width(); ++i) { for (size_t j = 0; j < grid.height(); ++j) { - build_rr_sinks_sources(i, j, L_rr_node, rr_edges_to_create, L_rr_node_indices, + build_rr_sinks_sources(i, j, rr_graph, rr_edges_to_create, L_rr_node_indices, delayless_switch, grid); //Create the actual SOURCE->OPIN, IPIN->SINK edges uniquify_edges(rr_edges_to_create); - alloc_and_load_edges(L_rr_node, rr_edges_to_create); + alloc_and_load_edges(rr_graph, rr_edges_to_create); rr_edges_to_create.clear(); } } @@ -1248,7 +1276,7 @@ static void alloc_and_load_rr_graph(const int num_nodes, for (size_t j = 0; j < grid.height(); ++j) { for (e_side side : SIDES) { if (BI_DIRECTIONAL == directionality) { - build_bidir_rr_opins(i, j, side, L_rr_node_indices, L_rr_node, + build_bidir_rr_opins(i, j, side, L_rr_node_indices, rr_graph, opin_to_track_map, Fc_out, rr_edges_to_create, chan_details_x, chan_details_y, grid, directs, num_directs, clb_to_clb_directs, num_seg_types); @@ -1266,7 +1294,7 @@ static void alloc_and_load_rr_graph(const int num_nodes, //Create the actual OPIN->CHANX/CHANY edges uniquify_edges(rr_edges_to_create); - alloc_and_load_edges(L_rr_node, rr_edges_to_create); + alloc_and_load_edges(rr_graph, rr_edges_to_create); rr_edges_to_create.clear(); } } @@ -1282,13 +1310,13 @@ static void alloc_and_load_rr_graph(const int num_nodes, CHANX_COST_INDEX_START, max_chan_width, grid, tracks_per_chan, sblock_pattern, Fs / 3, chan_details_x, chan_details_y, - L_rr_node_indices, rr_edges_to_create, L_rr_node, + L_rr_node_indices, rr_edges_to_create, rr_graph, wire_to_ipin_switch, directionality); //Create the actual CHAN->CHAN edges uniquify_edges(rr_edges_to_create); - alloc_and_load_edges(L_rr_node, rr_edges_to_create); + alloc_and_load_edges(rr_graph, rr_edges_to_create); rr_edges_to_create.clear(); } if (j > 0) { @@ -1297,26 +1325,25 @@ static void alloc_and_load_rr_graph(const int num_nodes, CHANX_COST_INDEX_START + num_seg_types, max_chan_width, grid, tracks_per_chan, sblock_pattern, Fs / 3, chan_details_x, chan_details_y, - L_rr_node_indices, rr_edges_to_create, L_rr_node, + L_rr_node_indices, rr_edges_to_create, rr_graph, wire_to_ipin_switch, directionality); //Create the actual CHAN->CHAN edges uniquify_edges(rr_edges_to_create); - alloc_and_load_edges(L_rr_node, rr_edges_to_create); + alloc_and_load_edges(rr_graph, rr_edges_to_create); rr_edges_to_create.clear(); } } } - init_fan_in(L_rr_node, num_nodes); } static void build_bidir_rr_opins(const int i, const int j, const e_side side, const t_rr_node_indices& L_rr_node_indices, - const std::vector& rr_nodes, + const RRGraph& rr_graph, const t_pin_to_track_lookup& opin_to_track_map, const std::vector>& Fc_out, t_rr_edge_info_set& rr_edges_to_create, @@ -1358,20 +1385,20 @@ static void build_bidir_rr_opins(const int i, total_pin_Fc += Fc[pin_index][iseg]; } - int node_index = get_rr_node_index(L_rr_node_indices, i, j, OPIN, pin_index, side); + RRNodeId node_index = rr_graph.find_node(i, j, OPIN, pin_index, side); VTR_ASSERT(node_index >= 0); if (total_pin_Fc > 0) { get_bidir_opin_connections(i, j, pin_index, - node_index, rr_edges_to_create, opin_to_track_map, L_rr_node_indices, + node_index, rr_edges_to_create, opin_to_track_map, rr_graph, chan_details_x, chan_details_y); } /* Add in direct connections */ - get_opin_direct_connecions(i, j, side, pin_index, - node_index, rr_edges_to_create, L_rr_node_indices, rr_nodes, - directs, num_directs, clb_to_clb_directs); + get_opin_direct_connections(i, j, side, pin_index, + node_index, rr_edges_to_create, L_rr_node_indices, rr_nodes, + directs, num_directs, clb_to_clb_directs); } } @@ -1412,7 +1439,7 @@ void free_rr_graph() { static void build_rr_sinks_sources(const int i, const int j, - std::vector& L_rr_node, + RRGraph& rr_graph, t_rr_edge_info_set& rr_edges_to_create, const t_rr_node_indices& L_rr_node_indices, const int delayless_switch, @@ -1433,18 +1460,18 @@ static void build_rr_sinks_sources(const int i, /* SINK and SOURCE-to-OPIN edges */ for (int iclass = 0; iclass < num_class; ++iclass) { - int inode = 0; + RRNodeId inode = 0; if (class_inf[iclass].type == DRIVER) { /* SOURCE */ - inode = get_rr_node_index(L_rr_node_indices, i, j, SOURCE, iclass); + inode = rr_graph.find_node(i, j, SOURCE, iclass); //Retrieve all the physical OPINs associated with this source, this includes //those at different grid tiles of this block - std::vector opin_nodes; + std::vector opin_nodes; for (int width_offset = 0; width_offset < type->width; ++width_offset) { for (int height_offset = 0; height_offset < type->height; ++height_offset) { for (int ipin = 0; ipin < class_inf[iclass].num_pins; ++ipin) { int pin_num = class_inf[iclass].pinlist[ipin]; - auto physical_pins = get_rr_node_indices(L_rr_node_indices, i + width_offset, j + height_offset, OPIN, pin_num); + auto physical_pins = find_rr_graph_nodes(rr_graph, i + width_offset, j + height_offset, OPIN, pin_num); opin_nodes.insert(opin_nodes.end(), physical_pins.begin(), physical_pins.end()); } } @@ -1455,11 +1482,10 @@ static void build_rr_sinks_sources(const int i, rr_edges_to_create.emplace_back(inode, opin_nodes[iedge], delayless_switch); } - L_rr_node[inode].set_cost_index(SOURCE_COST_INDEX); - L_rr_node[inode].set_type(SOURCE); + rr_graph.set_node_cost_index(inode, SOURCE_COST_INDEX); } else { /* SINK */ VTR_ASSERT(class_inf[iclass].type == RECEIVER); - inode = get_rr_node_index(L_rr_node_indices, i, j, SINK, iclass); + inode = rr_graph.find_node(L_rr_node_indices, i, j, SINK, iclass); /* NOTE: To allow route throughs through clbs, change the lines below to * * make an edge from the input SINK to the output SOURCE. Do for just the * @@ -1468,19 +1494,16 @@ static void build_rr_sinks_sources(const int i, * base cost of OPINs and/or SOURCES so they aren't used excessively. */ /* Initialize to unconnected */ - L_rr_node[inode].set_num_edges(0); - - L_rr_node[inode].set_cost_index(SINK_COST_INDEX); - L_rr_node[inode].set_type(SINK); + rr_graph.set_node_cost_index(RRNodeId(inode), SINK_COST_INDEX); } /* Things common to both SOURCEs and SINKs. */ - L_rr_node[inode].set_capacity(class_inf[iclass].num_pins); - L_rr_node[inode].set_coordinates(i, j, i + type->width - 1, j + type->height - 1); + rr_graph.set_node_capacity(inode, class_inf[iclass].num_pins); + rr_graph.set_node_bounding_box(inode, vtr::Rect(i, j, i + type->width - 1, j + type->height - 1)); float R = 0.; float C = 0.; - L_rr_node[inode].set_rc_index(find_create_rr_rc_data(R, C)); - L_rr_node[inode].set_ptc_num(iclass); + rr_graph.set_node_rc_index(inode, find_create_rr_rc_data(R, C)); + rr_graph.set_node_ptc_num(inode, iclass); } /* Connect IPINS to SINKS and initialize OPINS */ @@ -1496,40 +1519,38 @@ static void build_rr_sinks_sources(const int i, if (class_inf[iclass].type == RECEIVER) { //Connect the input pin to the sink - inode = get_rr_node_index(L_rr_node_indices, i + width_offset, j + height_offset, IPIN, ipin, side); + inode = rr_graph.find_node(i + width_offset, j + height_offset, IPIN, ipin, side); - int to_node = get_rr_node_index(L_rr_node_indices, i, j, SINK, iclass); + RRNodeId to_node = rr_graph.find_node(i, j, SINK, iclass); //Add info about the edge to be created rr_edges_to_create.emplace_back(inode, to_node, delayless_switch); VTR_ASSERT(inode >= 0); - L_rr_node[inode].set_cost_index(IPIN_COST_INDEX); - L_rr_node[inode].set_type(IPIN); + rr_graph.set_node_cost_index(IPIN_COST_INDEX); } else { VTR_ASSERT(class_inf[iclass].type == DRIVER); //Initialize the output pin // Note that we leave it's out-going edges unconnected (they will be hooked up to global routing later) - inode = get_rr_node_index(L_rr_node_indices, i + width_offset, j + height_offset, OPIN, ipin, side); + inode = rr_graph.find_node(i + width_offset, j + height_offset, OPIN, ipin, side); //Initially left unconnected - L_rr_node[inode].set_cost_index(OPIN_COST_INDEX); - L_rr_node[inode].set_type(OPIN); + rr_graph.set_node_cost_index(inode, OPIN_COST_INDEX); } /* Common to both DRIVERs and RECEIVERs */ - L_rr_node[inode].set_capacity(1); + rr_graph.set_node_capacity(inode, 1); float R = 0.; float C = 0.; - L_rr_node[inode].set_rc_index(find_create_rr_rc_data(R, C)); - L_rr_node[inode].set_ptc_num(ipin); + rr_graph.set_node_rc_index(inode, find_create_rr_rc_data(R, C)); + rr_graph.set_node_ptc_num(inode, ipin); //Note that we store the grid tile location and side where the pin is located, //which greatly simplifies the drawing code - L_rr_node[inode].set_coordinates(i + width_offset, j + height_offset, i + width_offset, j + height_offset); - L_rr_node[inode].set_side(side); + rr_graph.set_node_bounding_box(inode, vtr::Rect(i + width_offset, j + height_offset, i + width_offset, j + height_offset)); + rr_graph.set_node_side(inode, side); VTR_ASSERT(type->pinloc[width_offset][height_offset][L_rr_node[inode].side()][L_rr_node[inode].pin_num()]); } @@ -1577,7 +1598,7 @@ static void build_rr_chan(const int x_coord, const t_chan_details& chan_details_y, const t_rr_node_indices& L_rr_node_indices, t_rr_edge_info_set& rr_edges_to_create, - std::vector& L_rr_node, + RRGraph& rr_graph, const int wire_to_ipin_switch, const enum e_directionality directionality) { /* this function builds both x and y-directed channel segments, so set up our @@ -1632,16 +1653,16 @@ static void build_rr_chan(const int x_coord, from_seg_details = chan_details_x[start][y_coord].data(); } - int node = get_rr_node_index(L_rr_node_indices, x_coord, y_coord, chan_type, track); + RRNodeId node = rr_graph.find_node(x_coord, y_coord, chan_type, track); - if (node == OPEN) { + if (node == RRNodeId::INVALID()) { continue; } /* Add the edges from this track to all it's connected pins into the list */ int num_edges = 0; num_edges += get_track_to_pins(start, chan_coord, track, tracks_per_chan, node, rr_edges_to_create, - L_rr_node_indices, track_to_pin_lookup, seg_details, chan_type, seg_dimension, + rr_graph, track_to_pin_lookup, seg_details, chan_type, seg_dimension, wire_to_ipin_switch, directionality); /* get edges going from the current track into channel segments which are perpendicular to it */ @@ -1659,7 +1680,7 @@ static void build_rr_chan(const int x_coord, Fs_per_side, sblock_pattern, node, rr_edges_to_create, from_seg_details, to_seg_details, opposite_chan_details, directionality, - L_rr_node_indices, + rr_graph, switch_block_conn, sb_conn_map); } } @@ -1677,7 +1698,7 @@ static void build_rr_chan(const int x_coord, Fs_per_side, sblock_pattern, node, rr_edges_to_create, from_seg_details, to_seg_details, opposite_chan_details, directionality, - L_rr_node_indices, + rr_graph, switch_block_conn, sb_conn_map); } } @@ -1707,31 +1728,32 @@ static void build_rr_chan(const int x_coord, Fs_per_side, sblock_pattern, node, rr_edges_to_create, from_seg_details, to_seg_details, from_chan_details, directionality, - L_rr_node_indices, + rr_graph, switch_block_conn, sb_conn_map); } } } /* Edge arrays have now been built up. Do everything else. */ - L_rr_node[node].set_cost_index(cost_index_offset + seg_details[track].index()); - L_rr_node[node].set_capacity(1); /* GLOBAL routing handled elsewhere */ + rr_graph.node[node].set_node_cost_index(node, cost_index_offset + seg_details[track].index()); + rr_graph.set_node_capacity(node, 1); /* GLOBAL routing handled elsewhere */ if (chan_type == CHANX) { - L_rr_node[node].set_coordinates(start, y_coord, end, y_coord); + rr_graph.set_node_bounding_box(node, vtr::Rect(start, y_coord, end, y_coord)); } else { VTR_ASSERT(chan_type == CHANY); L_rr_node[node].set_coordinates(x_coord, start, x_coord, end); + rr_graph.set_node_bounding_box(node, vtr::Rect(x_coord, start, x_coord, end)); } int length = end - start + 1; float R = length * seg_details[track].Rmetal(); float C = length * seg_details[track].Cmetal(); - L_rr_node[node].set_rc_index(find_create_rr_rc_data(R, C)); + rr_graph.set_node_rc_index(node, find_create_rr_rc_data(R, C)); - L_rr_node[node].set_ptc_num(track); - L_rr_node[node].set_type(chan_type); - L_rr_node[node].set_direction(seg_details[track].direction()); + rr_graph.set_node_ptc_num(node, track); + rr_graph.set_node_type(node, chan_type); + rr_graph.set_node_direction(node, seg_details[track].direction()); } } @@ -1740,53 +1762,50 @@ void uniquify_edges(t_rr_edge_info_set& rr_edges_to_create) { rr_edges_to_create.erase(std::unique(rr_edges_to_create.begin(), rr_edges_to_create.end()), rr_edges_to_create.end()); } -void alloc_and_load_edges(std::vector& L_rr_node, +void alloc_and_load_edges(RRGraph& rr_graph, const t_rr_edge_info_set& rr_edges_to_create) { /* Sets up all the edge related information for rr_node */ struct compare_from_node { - auto operator()(const t_rr_edge_info& lhs, const int from_node) { - return lhs.from_node < from_node; + auto operator()(const t_rr_edge_info& lhs, const RRNodeId& from_node) { + return size_t(lhs.from_node) < size_t(from_node); } - auto operator()(const int from_node, const t_rr_edge_info& rhs) { - return from_node < rhs.from_node; + auto operator()(const RRNodeId& from_node, const t_rr_edge_info& rhs) { + return size_t(from_node) < size_t(rhs.from_node); } }; - std::set from_nodes; + std::set from_nodes; for (auto& edge : rr_edges_to_create) { from_nodes.insert(edge.from_node); } VTR_ASSERT_SAFE(std::is_sorted(rr_edges_to_create.begin(), rr_edges_to_create.end())); - for (int inode : from_nodes) { + for (RRNodeId inode : from_nodes) { auto edge_range = std::equal_range(rr_edges_to_create.begin(), rr_edges_to_create.end(), inode, compare_from_node()); size_t edge_count = std::distance(edge_range.first, edge_range.second); - if (L_rr_node[inode].num_edges() == 0) { + if (rr_graph.node_out_edges(inode).size() == 0) { //Create initial edges // //Note that we do this in bulk instead of via add_edge() to reduce //memory fragmentation - L_rr_node[inode].set_num_edges(edge_count); + rr_graph.reserve_edges(edge_count + rr_graph.edges().size()); - int iedge = 0; for (auto itr = edge_range.first; itr != edge_range.second; ++itr) { VTR_ASSERT(itr->from_node == inode); - L_rr_node[inode].set_edge_sink_node(iedge, itr->to_node); - L_rr_node[inode].set_edge_switch(iedge, itr->switch_type); - ++iedge; + rr_graph.create_edge(inode, itr->to_node, itr->switch_type); } } else { //Add new edge incrementally // //This should occur relatively rarely (e.g. a backward bidir edge) so memory fragmentation shouldn't be a big problem for (auto itr = edge_range.first; itr != edge_range.second; ++itr) { - L_rr_node[inode].add_edge(itr->to_node, itr->switch_type); + rr_graph.create_edge(inode, itr->to_node, itr->switch_type); } } } @@ -2576,7 +2595,7 @@ std::string describe_rr_node(const RRNodeId& inode) { return msg; } -static void build_unidir_rr_opins(const int i, const int j, const e_side side, const DeviceGrid& grid, const std::vector>& Fc_out, const int max_chan_width, const t_chan_details& chan_details_x, const t_chan_details& chan_details_y, vtr::NdMatrix& Fc_xofs, vtr::NdMatrix& Fc_yofs, t_rr_edge_info_set& rr_edges_to_create, bool* Fc_clipped, const t_rr_node_indices& L_rr_node_indices, const std::vector& rr_nodes, const t_direct_inf* directs, const int num_directs, const t_clb_to_clb_directs* clb_to_clb_directs, const int num_seg_types) { +static void build_unidir_rr_opins(const int i, const int j, const e_side side, const DeviceGrid& grid, const std::vector>& Fc_out, const int max_chan_width, const t_chan_details& chan_details_x, const t_chan_details& chan_details_y, vtr::NdMatrix& Fc_xofs, vtr::NdMatrix& Fc_yofs, t_rr_edge_info_set& rr_edges_to_create, bool* Fc_clipped, const t_rr_node_indices& L_rr_node_indices, const RRGraph& rr_graph, const t_direct_inf* directs, const int num_directs, const t_clb_to_clb_directs* clb_to_clb_directs, const int num_seg_types) { /* * This routine adds the edges from opins to channels at the specified * grid location (i,j) and grid tile side @@ -2599,8 +2618,8 @@ static void build_unidir_rr_opins(const int i, const int j, const e_side side, c continue; } - int opin_node_index = get_rr_node_index(L_rr_node_indices, i, j, OPIN, pin_index, side); - if (opin_node_index < 0) continue; //No valid from node + RRNodeId opin_node_index = rr_graph.find_node(i, j, OPIN, pin_index, side); + if (false == rr_graph.valid_node_id(opin_node_index)) continue; //No valid from node for (int iseg = 0; iseg < num_seg_types; iseg++) { /* get Fc for this segment type */ @@ -2653,15 +2672,15 @@ static void build_unidir_rr_opins(const int i, const int j, const e_side side, c opin_node_index, rr_edges_to_create, Fc_ofs, max_len, max_chan_width, - L_rr_node_indices, &clipped); + rr_graph, &clipped); if (clipped) { *Fc_clipped = true; } } /* Add in direct connections */ - get_opin_direct_connecions(i, j, side, pin_index, opin_node_index, rr_edges_to_create, L_rr_node_indices, rr_nodes, - directs, num_directs, clb_to_clb_directs); + get_opin_direct_connections(i, j, side, pin_index, opin_node_index, rr_edges_to_create, L_rr_node_indices, rr_graph, + directs, num_directs, clb_to_clb_directs); } } @@ -2804,17 +2823,17 @@ static t_clb_to_clb_directs* alloc_and_load_clb_to_clb_directs(const t_direct_in * * The current opin is located at (x,y) along the specified side */ -static int get_opin_direct_connecions(int x, - int y, - e_side side, - int opin, - int from_rr_node, - t_rr_edge_info_set& rr_edges_to_create, - const t_rr_node_indices& L_rr_node_indices, - const std::vector& rr_nodes, - const t_direct_inf* directs, - const int num_directs, - const t_clb_to_clb_directs* clb_to_clb_directs) { +static int get_opin_direct_connections(int x, + int y, + e_side side, + int opin, + const RRNodeId& from_rr_node, + t_rr_edge_info_set& rr_edges_to_create, + const t_rr_node_indices& L_rr_node_indices, + const RRGraph& rr_nodes, + const t_direct_inf* directs, + const int num_directs, + const t_clb_to_clb_directs* clb_to_clb_directs) { auto& device_ctx = g_vpr_ctx.device(); t_physical_tile_type_ptr curr_type = device_ctx.grid[x][y].type; @@ -2889,18 +2908,18 @@ static int get_opin_direct_connecions(int x, //if (ipin > target_type->num_pins) continue; //Invalid z-offset /* Add new ipin edge to list of edges */ - std::vector inodes; + std::vector inodes; if (directs[i].to_side != NUM_SIDES) { //Explicit side specified, only create if pin exists on that side - int inode = get_rr_node_index(L_rr_node_indices, x + directs[i].x_offset, y + directs[i].y_offset, + RRNodeId inode = rr_graph.find_node(x + directs[i].x_offset, y + directs[i].y_offset, IPIN, ipin, directs[i].to_side); - if (inode != OPEN) { + if (inode != RRNodeId::INVALID()) { inodes.push_back(inode); } } else { //No side specified, get all candidates - inodes = get_rr_node_indices(L_rr_node_indices, x + directs[i].x_offset, y + directs[i].y_offset, + inodes = rr_graph.find_node(x + directs[i].x_offset, y + directs[i].y_offset, IPIN, ipin); } @@ -2909,7 +2928,7 @@ static int get_opin_direct_connecions(int x, //target ipin. We only need to connect to one of them (since the physical pins //are logically equivalent). This also ensures the graphics look reasonable and map //back fairly directly to the architecture file in the case of pin equivalence - int inode = pick_best_direct_connect_target_rr_node(rr_nodes, from_rr_node, inodes); + RRNodeId inode = pick_best_direct_connect_target_rr_node(rr_graph, from_rr_node, inodes); rr_edges_to_create.emplace_back(from_rr_node, inode, clb_to_clb_directs[i].switch_index); ++num_pins; @@ -3023,9 +3042,9 @@ static std::vector alloc_and_load_perturb_opins(const t_physical_tile_type return perturb_opins; } -static int pick_best_direct_connect_target_rr_node(const std::vector& rr_nodes, - int from_rr, - const std::vector& candidate_rr_nodes) { +static RRNodeId pick_best_direct_connect_target_rr_node(const RRGraph& rr_nodes, + const RRNodeId& from_rr, + const std::vector& candidate_rr_nodes) { //With physically equivalent pins there may be multiple candidate rr nodes (which are equivalent) //to connect the direct edge to. //As a result it does not matter (from a correctness standpoint) which is picked. @@ -3035,18 +3054,18 @@ static int pick_best_direct_connect_target_rr_node(const std::vector& // //This function attempts to pick the 'best/closest' of the candidates. - VTR_ASSERT(rr_nodes[from_rr].type() == OPIN); - e_side from_side = rr_nodes[from_rr].side(); + VTR_ASSERT(rr_graph.node_type(from_rr) == OPIN); + e_side from_side = rr_graph.node_side(from_rr); float best_dist = std::numeric_limits::infinity(); - int best_rr = OPEN; + RRNodeId best_rr = RRNodeId::INVALID(); for (int to_rr : candidate_rr_nodes) { - VTR_ASSERT(rr_nodes[to_rr].type() == IPIN); - float to_dist = std::abs(rr_nodes[from_rr].xlow() - rr_nodes[to_rr].xlow()) - + std::abs(rr_nodes[from_rr].ylow() - rr_nodes[to_rr].ylow()); + VTR_ASSERT(rr_graph.node_type(to_rr) == IPIN); + float to_dist = std::abs(rr_graph.node_xlow(from_rr) - rr_graph.node_xlow(to_rr)) + + std::abs(rr_graph.node_ylow(from_rr) - rr_graph.node_ylow(to_rr)); - e_side to_side = rr_nodes[to_rr].side(); + e_side to_side = rr_graph.node_side(to_rr); //Include a partial unit of distance based on side alignment to ensure //we preferr facing sides @@ -3072,7 +3091,7 @@ static int pick_best_direct_connect_target_rr_node(const std::vector& } } - VTR_ASSERT(best_rr != OPEN); + VTR_ASSERT(best_rr != RRNodeId::INVALID()); return best_rr; } diff --git a/vpr/src/route/rr_graph2.cpp b/vpr/src/route/rr_graph2.cpp index 2b7ddb4da..59759b532 100644 --- a/vpr/src/route/rr_graph2.cpp +++ b/vpr/src/route/rr_graph2.cpp @@ -39,7 +39,7 @@ static void load_block_rr_indices(const DeviceGrid& grid, int* index); static int get_bidir_track_to_chan_seg(const std::vector conn_tracks, - const t_rr_node_indices& L_rr_node_indices, + const RRGraph& rr_graph, const int to_chan, const int to_seg, const int to_sb, @@ -49,7 +49,7 @@ static int get_bidir_track_to_chan_seg(const std::vector conn_tracks, const int from_switch, const int switch_override, const enum e_directionality directionality, - const int from_rr_node, + const RRNodeId& from_rr_node, t_rr_edge_info_set& rr_edges_to_create); static int get_unidir_track_to_chan_seg(const int from_track, @@ -64,7 +64,7 @@ static int get_unidir_track_to_chan_seg(const int from_track, const int Fs_per_side, t_sblock_pattern& sblock_pattern, const int switch_override, - const t_rr_node_indices& L_rr_node_indices, + const RRGraph& rr_graph, const t_chan_seg_details* seg_details, bool* Fs_clipped, const int from_rr_node, @@ -77,9 +77,9 @@ static int get_track_to_chan_seg(const int from_track, const e_side from_side, const e_side to_side, const int swtich_override, - const t_rr_node_indices& L_rr_node_indices, + const RRGraph& rr_graph, t_sb_connection_map* sb_conn_map, - const int from_rr_node, + const RRNodeId& from_rr_node, t_rr_edge_info_set& rr_edges_to_create); static int vpr_to_phy_track(const int itrack, @@ -664,14 +664,15 @@ int get_seg_end(const t_chan_seg_details* seg_details, const int itrack, const i int get_bidir_opin_connections(const int i, const int j, const int ipin, - const int from_rr_node, + const RRNodeId& from_rr_node, t_rr_edge_info_set& rr_edges_to_create, const t_pin_to_track_lookup& opin_to_track_map, - const t_rr_node_indices& L_rr_node_indices, + const RRGraph& rr_graph, const t_chan_details& chan_details_x, const t_chan_details& chan_details_y) { int num_conn, tr_i, tr_j, chan, seg; - int to_switch, to_node; + int to_switch; + RRNodeId to_node; int is_connected_track; t_physical_tile_type_ptr type; t_rr_type to_type; @@ -730,9 +731,9 @@ int get_bidir_opin_connections(const int i, /* Only connect to wire if there is a CB */ if (is_cblock(chan, seg, to_track, seg_details)) { to_switch = seg_details[to_track].arch_wire_switch(); - to_node = get_rr_node_index(L_rr_node_indices, tr_i, tr_j, to_type, to_track); + to_node = rr_graph.find_node(tr_i, tr_j, to_type, to_track); - if (to_node == OPEN) { + if (to_node == RRNodeId::INVALID()) { continue; } @@ -751,12 +752,12 @@ int get_unidir_opin_connections(const int chan, const int seg_type_index, const t_rr_type chan_type, const t_chan_seg_details* seg_details, - const int from_rr_node, + const RRNodeId& from_rr_node, t_rr_edge_info_set& rr_edges_to_create, vtr::NdMatrix& Fc_ofs, const int max_len, const int max_chan_width, - const t_rr_node_indices& L_rr_node_indices, + const RRGraph& rr_graph, bool* Fc_clipped) { /* Gets a linked list of Fc nodes of specified seg_type_index to connect * to in given chan seg. Fc_ofs is used for the opin staggering pattern. */ @@ -808,10 +809,10 @@ int get_unidir_opin_connections(const int chan, dec_track = dec_muxes[dec_mux]; /* Figure the inodes of those muxes */ - inc_inode_index = get_rr_node_index(L_rr_node_indices, x, y, chan_type, inc_track); - dec_inode_index = get_rr_node_index(L_rr_node_indices, x, y, chan_type, dec_track); + inc_inode_index = rr_graph.find_node(x, y, chan_type, inc_track); + dec_inode_index = rr_graph.find_node(x, y, chan_type, dec_track); - if (inc_inode_index == OPEN || dec_inode_index == OPEN) { + if (inc_inode_index == RRNodeId::INVALID() || dec_inode_index == RRNodeId::INVALID()) { continue; } @@ -1390,9 +1391,9 @@ int get_track_to_pins(int seg, int chan, int track, int tracks_per_chan, - int from_rr_node, + const RRNodeId& from_rr_node, t_rr_edge_info_set& rr_edges_to_create, - const t_rr_node_indices& L_rr_node_indices, + const RRGraph& rr_graph, const t_track_to_pin_lookup& track_to_pin_lookup, const t_chan_seg_details* seg_details, enum e_rr_type chan_type, @@ -1450,7 +1451,7 @@ int get_track_to_pins(int seg, /* Check there is a connection and Fc map isn't wrong */ /*int to_node = get_rr_node_index(L_rr_node_indices, x + width_offset, y + height_offset, IPIN, ipin, side);*/ - int to_node = get_rr_node_index(L_rr_node_indices, x, y, IPIN, ipin, side); + RRNodeId to_node = rr_graph.find_node(x, y, IPIN, ipin, side); if (to_node >= 0) { rr_edges_to_create.emplace_back(from_rr_node, to_node, wire_to_ipin_switch); ++num_conn; @@ -1491,13 +1492,13 @@ int get_track_to_tracks(const int from_chan, const DeviceGrid& grid, const int Fs_per_side, t_sblock_pattern& sblock_pattern, - const int from_rr_node, + const RRNodeId& from_rr_node, t_rr_edge_info_set& rr_edges_to_create, const t_chan_seg_details* from_seg_details, const t_chan_seg_details* to_seg_details, const t_chan_details& to_chan_details, const enum e_directionality directionality, - const t_rr_node_indices& L_rr_node_indices, + const RRGraph& rr_graph, const vtr::NdMatrix, 3>& switch_block_conn, t_sb_connection_map* sb_conn_map) { int to_chan, to_sb; @@ -1630,7 +1631,7 @@ int get_track_to_tracks(const int from_chan, num_conn += get_track_to_chan_seg(from_track, to_chan, to_seg, to_type, from_side_a, to_side, switch_override, - L_rr_node_indices, + rr_graph, sb_conn_map, from_rr_node, rr_edges_to_create); } } else { @@ -1639,7 +1640,7 @@ int get_track_to_tracks(const int from_chan, * switchbox, so we follow through regardless of whether the current segment has an SB */ conn_tracks = switch_block_conn[from_side_a][to_side][from_track]; num_conn += get_bidir_track_to_chan_seg(conn_tracks, - L_rr_node_indices, to_chan, to_seg, to_sb, to_type, + rr_graph, to_chan, to_seg, to_sb, to_type, to_seg_details, from_is_sblock, from_switch, switch_override, directionality, from_rr_node, rr_edges_to_create); @@ -1654,7 +1655,7 @@ int get_track_to_tracks(const int from_chan, from_side_a, to_side, Fs_per_side, sblock_pattern, switch_override, - L_rr_node_indices, to_seg_details, + rr_graph, to_seg_details, &Fs_clipped, from_rr_node, rr_edges_to_create); } } @@ -1669,7 +1670,7 @@ int get_track_to_tracks(const int from_chan, num_conn += get_track_to_chan_seg(from_track, to_chan, to_seg, to_type, from_side_b, to_side, switch_override, - L_rr_node_indices, + rr_graph, sb_conn_map, from_rr_node, rr_edges_to_create); } } else { @@ -1678,7 +1679,7 @@ int get_track_to_tracks(const int from_chan, * switchbox, so we follow through regardless of whether the current segment has an SB */ conn_tracks = switch_block_conn[from_side_b][to_side][from_track]; num_conn += get_bidir_track_to_chan_seg(conn_tracks, - L_rr_node_indices, to_chan, to_seg, to_sb, to_type, + rr_graph, to_chan, to_seg, to_sb, to_type, to_seg_details, from_is_sblock, from_switch, switch_override, directionality, from_rr_node, rr_edges_to_create); @@ -1694,7 +1695,7 @@ int get_track_to_tracks(const int from_chan, from_side_b, to_side, Fs_per_side, sblock_pattern, switch_override, - L_rr_node_indices, to_seg_details, + rr_graph, to_seg_details, &Fs_clipped, from_rr_node, rr_edges_to_create); } } @@ -1706,7 +1707,7 @@ int get_track_to_tracks(const int from_chan, } static int get_bidir_track_to_chan_seg(const std::vector conn_tracks, - const t_rr_node_indices& L_rr_node_indices, + const RRGraph& rr_graph, const int to_chan, const int to_seg, const int to_sb, @@ -1716,10 +1717,11 @@ static int get_bidir_track_to_chan_seg(const std::vector conn_tracks, const int from_switch, const int switch_override, const enum e_directionality directionality, - const int from_rr_node, + const RRNodeId& from_rr_node, t_rr_edge_info_set& rr_edges_to_create) { unsigned iconn; - int to_track, to_node, to_switch, num_conn, to_x, to_y, i; + int to_track, to_switch, num_conn, to_x, to_y, i; + RRNodeId to_node; bool to_is_sblock; short switch_types[2]; @@ -1737,9 +1739,9 @@ static int get_bidir_track_to_chan_seg(const std::vector conn_tracks, num_conn = 0; for (iconn = 0; iconn < conn_tracks.size(); ++iconn) { to_track = conn_tracks[iconn]; - to_node = get_rr_node_index(L_rr_node_indices, to_x, to_y, to_type, to_track); + to_node = rr_graph.find_node(to_x, to_y, to_type, to_track); - if (to_node == OPEN) { + if (to_node == RRNodeId::INVALID()) { continue; } @@ -1780,9 +1782,9 @@ static int get_track_to_chan_seg(const int from_wire, const e_side from_side, const e_side to_side, const int switch_override, - const t_rr_node_indices& L_rr_node_indices, + const RRGraph& rr_graph, t_sb_connection_map* sb_conn_map, - const int from_rr_node, + const RRNodeId& from_rr_node, t_rr_edge_info_set& rr_edges_to_create) { int edge_count = 0; int to_x, to_y; @@ -1816,9 +1818,9 @@ static int get_track_to_chan_seg(const int from_wire, if (conn_vector.at(iconn).from_wire != from_wire) continue; int to_wire = conn_vector.at(iconn).to_wire; - int to_node = get_rr_node_index(L_rr_node_indices, to_x, to_y, to_chan_type, to_wire); + RRNodeId to_node = rr_graph.find_node(to_x, to_y, to_chan_type, to_wire); - if (to_node == OPEN) { + if (to_node == RRNodeId::INVALID()) { continue; } @@ -1859,10 +1861,10 @@ static int get_unidir_track_to_chan_seg(const int from_track, const int Fs_per_side, t_sblock_pattern& sblock_pattern, const int switch_override, - const t_rr_node_indices& L_rr_node_indices, + const RRGraph& rr_graph, const t_chan_seg_details* seg_details, bool* Fs_clipped, - const int from_rr_node, + const RRNodeId& from_rr_node, t_rr_edge_info_set& rr_edges_to_create) { int num_labels = 0; int* mux_labels = nullptr; @@ -1916,9 +1918,9 @@ static int get_unidir_track_to_chan_seg(const int from_track, sblock_pattern[sb_x][sb_y][from_side][to_side][from_track][j + 1] = to_track; } - int to_node = get_rr_node_index(L_rr_node_indices, to_x, to_y, to_type, to_track); + RRNodeId to_node = rr_graph.find_node(to_x, to_y, to_type, to_track); - if (to_node == OPEN) { + if (to_node == RRNodeId::INVALID()) { continue; } diff --git a/vpr/src/route/rr_graph2.h b/vpr/src/route/rr_graph2.h index dae44608c..0bbe884ad 100644 --- a/vpr/src/route/rr_graph2.h +++ b/vpr/src/route/rr_graph2.h @@ -15,21 +15,21 @@ enum e_seg_details_type { }; struct t_rr_edge_info { - t_rr_edge_info(int from, int to, short type) noexcept + t_rr_edge_info(const RRNodeId& from, const RRNodeId& to, const short& type) noexcept : from_node(from) , to_node(to) , switch_type(type) {} - int from_node = OPEN; - int to_node = OPEN; + RRNodeId from_node = RRNodeId::INVALID(); + RRNodeId to_node = RRNodeId::INVALID(); short switch_type = OPEN; friend bool operator<(const t_rr_edge_info& lhs, const t_rr_edge_info& rhs) { - return std::tie(lhs.from_node, lhs.to_node, lhs.switch_type) < std::tie(rhs.from_node, rhs.to_node, rhs.switch_type); + return std::tie(size_t(lhs.from_node), size_t(lhs.to_node), lhs.switch_type) < std::tie(size_t(rhs.from_node), size_t(rhs.to_node), rhs.switch_type); } friend bool operator==(const t_rr_edge_info& lhs, const t_rr_edge_info& rhs) { - return std::tie(lhs.from_node, lhs.to_node, lhs.switch_type) == std::tie(rhs.from_node, rhs.to_node, rhs.switch_type); + return std::tie(size_t(lhs.from_node), size_t(lhs.to_node), lhs.switch_type) == std::tie(size_t(rhs.from_node), size_t(rhs.to_node), rhs.switch_type); } }; @@ -158,21 +158,21 @@ int get_unidir_opin_connections(const int chan, const int seg_type_index, const t_rr_type chan_type, const t_chan_seg_details* seg_details, - const int from_rr_node, + const RRNodeId& from_rr_node, t_rr_edge_info_set& rr_edges_to_create, vtr::NdMatrix& Fc_ofs, const int max_len, const int max_chan_width, - const t_rr_node_indices& L_rr_node_indices, + const RRGraph& rr_graph, bool* Fc_clipped); int get_track_to_pins(int seg, int chan, int track, int tracks_per_chan, - int from_rr_node, + const RRNodeId& from_rr_node, t_rr_edge_info_set& rr_edges_to_create, - const t_rr_node_indices& L_rr_node_indices, + const RRGraph& rr_graph, const t_track_to_pin_lookup& track_to_pin_lookup, const t_chan_seg_details* seg_details, enum e_rr_type chan_type, @@ -191,13 +191,13 @@ int get_track_to_tracks(const int from_chan, const DeviceGrid& grid, const int Fs_per_side, t_sblock_pattern& sblock_pattern, - const int from_rr_node, + const RRNodeId& from_rr_node, t_rr_edge_info_set& rr_edges_to_create, const t_chan_seg_details* from_seg_details, const t_chan_seg_details* to_seg_details, const t_chan_details& to_chan_details, const enum e_directionality directionality, - const t_rr_node_indices& L_rr_node_indices, + const RRGraph& rr_graph, const vtr::NdMatrix, 3>& switch_block_conn, t_sb_connection_map* sb_conn_map); From fc6389b5c0d8dda666c397564738ee4649148c5f Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 3 Feb 2020 14:40:04 -0700 Subject: [PATCH 088/645] refactored RRGraph builder --- vpr/src/device/rr_graph_obj.cpp | 68 +++++++++--- vpr/src/route/rr_graph.cpp | 10 +- vpr/src/route/rr_graph2.cpp | 186 ++++++++++++++++---------------- vpr/src/route/rr_graph2.h | 10 +- 4 files changed, 154 insertions(+), 120 deletions(-) diff --git a/vpr/src/device/rr_graph_obj.cpp b/vpr/src/device/rr_graph_obj.cpp index 6a05b6a21..cb2fb7cd2 100644 --- a/vpr/src/device/rr_graph_obj.cpp +++ b/vpr/src/device/rr_graph_obj.cpp @@ -65,10 +65,22 @@ short RRGraph::node_ylow(const RRNodeId& node) const { } short RRGraph::node_xhigh(const RRNodeId& node) const { + /* Special for SOURCE and SINK node, we always return the xlow + * This is due to the convention in creating RRGraph + * so that we can guarantee unique SOURCE/SINK nodes searching + */ + if ( (SOURCE == node_type(node) + || (SINK == node_type(node)) { + return node_bounding_box(node).xmin(); + } return node_bounding_box(node).xmax(); } short RRGraph::node_yhigh(const RRNodeId& node) const { + if ( (SOURCE == node_type(node) + || (SINK == node_type(node)) { + return node_bounding_box(node).ymin(); + } return node_bounding_box(node).ymax(); } @@ -1200,28 +1212,50 @@ void RRGraph::build_fast_node_lookup() const { continue; } RRNodeId node = RRNodeId(id); - size_t x = node_xlow(node); - size_t y = node_ylow(node); - size_t itype = node_type(node); + /* Special for SOURCE and SINK, we should annotate in the look-up + * for all the (x,y) upto (xhigh, yhigh) + */ + std::vector xlows; + std::vector ylows; - size_t ptc = node_ptc_num(node); - if (ptc >= node_lookup_[x][y][itype].size()) { - node_lookup_[x][y][itype].resize(ptc + 1); + if ( (SOURCE == node_type(node) + || (SINK == node_type(node)) { + xlows.resize(node_bounding_boxes_[node].xmax() - node_bounding_boxes_[node].xmin() + 1); + ylows.resize(node_bounding_boxes_[node].ymax() - node_bounding_boxes_[node].ymin() + 1); + std::iota(xlows.begin(), xlows.end(), node_xlow(node)); + std::iota(ylows.begin(), ylows.end(), node_ylow(node)); + /* Sanity check */ + VTR_ASSERT(node_bounding_boxes_[node].xmax() == xlows.back()); + VTR_ASSERT(node_bounding_boxes_[node].ymax() == ylows.back()); + } else { + xlows.push_back(node_xlow(node)); + ylows.push_back(node_ylow(node)); } - size_t iside = -1; - if (node_type(node) == OPIN || node_type(node) == IPIN) { - iside = node_side(node); - } else { - iside = NUM_SIDES; - } + for (size_t x : xlows) { + for (size_t y : ylows) { + size_t itype = node_type(node); - if (iside >= node_lookup_[x][y][itype][ptc].size()) { - node_lookup_[x][y][itype][ptc].resize(iside + 1); - } + size_t ptc = node_ptc_num(node); + if (ptc >= node_lookup_[x][y][itype].size()) { + node_lookup_[x][y][itype].resize(ptc + 1); + } - //Save node in lookup - node_lookup_[x][y][itype][ptc][iside] = node; + size_t iside = -1; + if (node_type(node) == OPIN || node_type(node) == IPIN) { + iside = node_side(node); + } else { + iside = NUM_SIDES; + } + + if (iside >= node_lookup_[x][y][itype][ptc].size()) { + node_lookup_[x][y][itype][ptc].resize(iside + 1); + } + + //Save node in lookup + node_lookup_[x][y][itype][ptc][iside] = node; + } + } } } diff --git a/vpr/src/route/rr_graph.cpp b/vpr/src/route/rr_graph.cpp index 5ff1f9f35..f1d5f4929 100644 --- a/vpr/src/route/rr_graph.cpp +++ b/vpr/src/route/rr_graph.cpp @@ -591,13 +591,11 @@ static void build_rr_graph(const t_graph_type graph_type, /* Alloc node lookups, count nodes, alloc rr nodes */ int num_rr_nodes = 0; - /* Xifan Tang - Keep the node indices here since it counts the number of rr_nodes - * We will simplify this function as indices will be built inside the RRGraph object + /* Xifan Tang - + * We create all the nodes in the RRGraph object here */ - device_ctx.rr_node_indices = alloc_and_load_rr_node_indices(max_chan_width, grid, - &num_rr_nodes, chan_details_x, chan_details_y); - /* Reserve the node for the RRGraph object */ - device_ctx.rr_graph.reserve_node(num_rr_nodes); + device_ctx.rr_graph = alloc_and_load_rr_node_indices(max_chan_width, grid, + &num_rr_nodes, chan_details_x, chan_details_y); /* These are data structures used by the the unidir opin mapping. They are used * to spread connections evenly for each segment type among the available diff --git a/vpr/src/route/rr_graph2.cpp b/vpr/src/route/rr_graph2.cpp index 59759b532..b861be721 100644 --- a/vpr/src/route/rr_graph2.cpp +++ b/vpr/src/route/rr_graph2.cpp @@ -31,11 +31,11 @@ static void load_chan_rr_indices(const int max_chan_width, const int num_chans, const t_rr_type type, const t_chan_details& chan_details, - t_rr_node_indices& indices, + RRGraph& rr_graph, int* index); static void load_block_rr_indices(const DeviceGrid& grid, - t_rr_node_indices& indices, + RRGraph& rr_graph, int* index); static int get_bidir_track_to_chan_seg(const std::vector conn_tracks, @@ -1024,25 +1024,43 @@ void dump_sblock_pattern(const t_sblock_pattern& sblock_pattern, fclose(fp); } +/******************************************************************** + * Create all the nodes for routing tracks + * num_chans : number of routing channels to be created + * chan_len : maximum length of routing channels + * + * X-direction channel + * |<-------chan_len-------->| + * -----+-------------------------- + * ^ | + * | | + * | | + * num_chans | FPGA + * | | + * | | + * v | + * -----+-------------------------- + * + * Y-direction channel + * |<-------num_chans-------->| + * -----+-------------------------- + * ^ | + * | | + * | | + * chan_len | FPGA + * | | + * | | + * v | + * -----+-------------------------- + * + *******************************************************************/ static void load_chan_rr_indices(const int max_chan_width, const int chan_len, const int num_chans, const t_rr_type type, const t_chan_details& chan_details, - t_rr_node_indices& indices, + RRGraph& rr_graph, int* index) { - VTR_ASSERT(indices[type].size() == size_t(num_chans)); - for (int chan = 0; chan < num_chans - 1; ++chan) { - VTR_ASSERT(indices[type][chan].size() == size_t(chan_len)); - - for (int seg = 1; seg < chan_len - 1; ++seg) { - VTR_ASSERT(indices[type][chan][seg].size() == NUM_SIDES); - - /* Alloc the track inode lookup list */ - //Since channels have no side, we just use the first side - indices[type][chan][seg][0].resize(max_chan_width, OPEN); - } - } for (int chan = 0; chan < num_chans - 1; ++chan) { for (int seg = 1; seg < chan_len - 1; ++seg) { @@ -1051,31 +1069,31 @@ static void load_chan_rr_indices(const int max_chan_width, int y = (type == CHANX ? chan : seg); const t_chan_seg_details* seg_details = chan_details[x][y].data(); - for (unsigned track = 0; track < indices[type][chan][seg][0].size(); ++track) { + for (unsigned track = 0; track < num_chans - 1; ++track) { if (seg_details[track].length() <= 0) continue; int start = get_seg_start(seg_details, track, chan, seg); - - /* If the start of the wire doesn't have a inode, - * assign one to it. */ - int inode = indices[type][chan][start][0][track]; - if (OPEN == inode) { - inode = *index; - ++(*index); - - indices[type][chan][start][0][track] = inode; + + /* If this segment does not start from the current position, we do not allocate any nodes */ + if (start != seg) { + continue; } - /* Assign inode of start of wire to current position */ - indices[type][chan][seg][0][track] = inode; + RRNodeId node = rr_graph.create_node(type); + short xlow = x; + short ylow = y; + short xhigh = (type == CHANX ? xlow + seg_details[track].length() - 1 : xlow); + short yhigh = (type == CHANY ? ylow + seg_details[track].length() - 1 : ylow); + rr_graph.set_node_bounding_box(node, vtr::Rect(xlow, ylow, xhigh, yhigh)); + rr_graph.set_node_track_num(node, track); } } } } static void load_block_rr_indices(const DeviceGrid& grid, - t_rr_node_indices& indices, + RRGraph& rr_graph, int* index) { //Walk through the grid assigning indices to SOURCE/SINK IPIN/OPIN for (size_t x = 0; x < grid.width(); x++) { @@ -1089,17 +1107,17 @@ static void load_block_rr_indices(const DeviceGrid& grid, for (int iclass = 0; iclass < type->num_class; ++iclass) { auto class_type = type->class_inf[iclass].type; if (class_type == DRIVER) { - indices[SOURCE][x][y][0].push_back(*index); - indices[SINK][x][y][0].push_back(OPEN); + RRNodeId node = rr_graph.create_node(SOURCE); + rr_graph.set_node_bounding_box(node, vtr::Rect(x, y, x, y)); + rr_graph.set_node_class_num(iclass); } else { VTR_ASSERT(class_type == RECEIVER); - indices[SINK][x][y][0].push_back(*index); - indices[SOURCE][x][y][0].push_back(OPEN); + RRNodeId node = rr_graph.create_node(SINK); + rr_graph.set_node_bounding_box(node, vtr::Rect(x, y, x, y)); + rr_graph.set_node_class_num(iclass); } ++(*index); } - VTR_ASSERT(indices[SOURCE][x][y][0].size() == size_t(type->num_class)); - VTR_ASSERT(indices[SINK][x][y][0].size() == size_t(type->num_class)); //Assign indicies for IPINs and OPINs at all offsets from root for (int ipin = 0; ipin < type->num_pins; ++ipin) { @@ -1113,34 +1131,23 @@ static void load_block_rr_indices(const DeviceGrid& grid, auto class_type = type->class_inf[iclass].type; if (class_type == DRIVER) { - indices[OPIN][x_tile][y_tile][side].push_back(*index); - indices[IPIN][x_tile][y_tile][side].push_back(OPEN); + RRNodeId node = rr_graph.create_node(OPIN); + rr_graph.set_node_bounding_box(node, vtr::Rect(xtile, ytile, xtile, ytile)); + rr_graph.set_node_pin_num(ipin); + rr_graph.set_node_side(side); } else { VTR_ASSERT(class_type == RECEIVER); - indices[IPIN][x_tile][y_tile][side].push_back(*index); - indices[OPIN][x_tile][y_tile][side].push_back(OPEN); + RRNodeId node = rr_graph.create_node(IPIN); + rr_graph.set_node_bounding_box(node, vtr::Rect(xtile, ytile, xtile, ytile)); + rr_graph.set_node_pin_num(ipin); + rr_graph.set_node_side(side); } ++(*index); - } else { - indices[IPIN][x_tile][y_tile][side].push_back(OPEN); - indices[OPIN][x_tile][y_tile][side].push_back(OPEN); } } } } } - - //Sanity check - for (int width_offset = 0; width_offset < type->width; ++width_offset) { - int x_tile = x + width_offset; - for (int height_offset = 0; height_offset < type->height; ++height_offset) { - int y_tile = y + height_offset; - for (e_side side : SIDES) { - VTR_ASSERT(indices[IPIN][x_tile][y_tile][side].size() == size_t(type->num_pins)); - VTR_ASSERT(indices[OPIN][x_tile][y_tile][side].size() == size_t(type->num_pins)); - } - } - } } } } @@ -1151,59 +1158,54 @@ static void load_block_rr_indices(const DeviceGrid& grid, for (size_t y = 0; y < grid.height(); y++) { int width_offset = grid[x][y].width_offset; int height_offset = grid[x][y].height_offset; - if (width_offset != 0 || height_offset != 0) { + /* We only care the largest offset in both x and y direction + * this can avoid runtime cost to rebuild the fast look-up inside RRGraph object + */ + if ( (grid[x][y].type->height == height_offset && grid[x][y].type->width == width_offset) + && (0 != height_offset && 0 != width_offset) ) { int root_x = x - width_offset; int root_y = y - height_offset; - indices[SOURCE][x][y] = indices[SOURCE][root_x][root_y]; - indices[SINK][x][y] = indices[SINK][root_x][root_y]; + //Process each block from it's root location + auto type = grid[x][y].type; + + //Assign indicies for SINKs and SOURCEs + // Note that SINKS/SOURCES have no side, so we always use side 0 + for (int iclass = 0; iclass < type->num_class; ++iclass) { + auto class_type = type->class_inf[iclass].type; + if (class_type == DRIVER) { + RRNodeId node = rr_graph.find_node(root_x, root_y, SOURCE, iclass); + /* Update the internal look-up so that we can find the SOURCE/SINK node using their offset coordinates */ + rr_graph.set_node_bounding_box(node, vtr::Rect(root_x, root_y, x, y)); + } else { + VTR_ASSERT(class_type == RECEIVER); + RRNodeId node = rr_graph.find_node(root_x, root_y, SINK, iclass); + /* Update the internal look-up so that we can find the SOURCE/SINK node using their offset coordinates */ + rr_graph.set_node_bounding_box(node, vtr::Rect(root_x, root_y, x, y)); + } + } } } } } -t_rr_node_indices alloc_and_load_rr_node_indices(const int max_chan_width, - const DeviceGrid& grid, - int* index, - const t_chan_details& chan_details_x, - const t_chan_details& chan_details_y) { - /* Allocates and loads all the structures needed for fast lookups of the * - * index of an rr_node. rr_node_indices is a matrix containing the index * - * of the *first* rr_node at a given (i,j) location. */ - - t_rr_node_indices indices; - - /* Alloc the lookup table */ - indices.resize(NUM_RR_TYPES); - for (t_rr_type rr_type : RR_TYPES) { - if (rr_type == CHANX) { - indices[rr_type].resize(grid.height()); - for (size_t y = 0; y < grid.height(); ++y) { - indices[rr_type][y].resize(grid.width()); - for (size_t x = 0; x < grid.width(); ++x) { - indices[rr_type][y][x].resize(NUM_SIDES); - } - } - } else { - indices[rr_type].resize(grid.width()); - for (size_t x = 0; x < grid.width(); ++x) { - indices[rr_type][x].resize(grid.height()); - for (size_t y = 0; y < grid.height(); ++y) { - indices[rr_type][x][y].resize(NUM_SIDES); - } - } - } - } +RRGraph alloc_and_load_rr_node_indices(const int max_chan_width, + const DeviceGrid& grid, + int* index, + const t_chan_details& chan_details_x, + const t_chan_details& chan_details_y) { + /* Allocates and loads all the nodes in a RRGraph object */ + RRGraph rr_graph; /* Assign indices for block nodes */ - load_block_rr_indices(grid, indices, index); + load_block_rr_indices(grid, rr_graph, index); /* Load the data for x and y channels */ load_chan_rr_indices(max_chan_width, grid.width(), grid.height(), - CHANX, chan_details_x, indices, index); + CHANX, chan_details_x, rr_graph, index); load_chan_rr_indices(max_chan_width, grid.height(), grid.width(), - CHANY, chan_details_y, indices, index); - return indices; + CHANY, chan_details_y, rr_graph, index); + return rr_graph; } std::vector get_rr_node_chan_wires_at_location(const t_rr_node_indices& L_rr_node_indices, diff --git a/vpr/src/route/rr_graph2.h b/vpr/src/route/rr_graph2.h index 0bbe884ad..55791b8d4 100644 --- a/vpr/src/route/rr_graph2.h +++ b/vpr/src/route/rr_graph2.h @@ -39,11 +39,11 @@ typedef vtr::NdMatrix t_sblock_pattern; /******************* Subroutines exported by rr_graph2.c *********************/ -t_rr_node_indices alloc_and_load_rr_node_indices(const int max_chan_width, - const DeviceGrid& grid, - int* index, - const t_chan_details& chan_details_x, - const t_chan_details& chan_details_y); +RRGraph alloc_and_load_rr_node_indices(const int max_chan_width, + const DeviceGrid& grid, + int* index, + const t_chan_details& chan_details_x, + const t_chan_details& chan_details_y); int get_rr_node_index(int x, int y, From 9f3afb5a46a61640691199ac8e086da126f01775 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 3 Feb 2020 15:11:24 -0700 Subject: [PATCH 089/645] refactored rr_graph clock builder --- vpr/src/route/clock_connection_builders.cpp | 2 +- vpr/src/route/clock_network_builders.cpp | 45 ++++++++++----------- vpr/src/route/rr_graph.cpp | 10 ++++- vpr/src/route/rr_graph_clock.cpp | 32 +++++++-------- vpr/src/route/rr_graph_clock.h | 4 +- vpr/src/route/rr_graph_reader.cpp | 6 +-- 6 files changed, 53 insertions(+), 46 deletions(-) diff --git a/vpr/src/route/clock_connection_builders.cpp b/vpr/src/route/clock_connection_builders.cpp index e8fca6977..5ba6fefea 100644 --- a/vpr/src/route/clock_connection_builders.cpp +++ b/vpr/src/route/clock_connection_builders.cpp @@ -137,7 +137,7 @@ void ClockToClockConneciton::set_fc_val(float fc_val) { void ClockToClockConneciton::create_switches(const ClockRRGraphBuilder& clock_graph) { auto& device_ctx = g_vpr_ctx.mutable_device(); auto& grid = device_ctx.grid; - auto& rr_nodes = device_ctx.rr_nodes; + auto& rr_graph = device_ctx.rr_graph; auto to_locations = clock_graph.get_switch_locations(to_clock, to_switch); diff --git a/vpr/src/route/clock_network_builders.cpp b/vpr/src/route/clock_network_builders.cpp index ea367bdbb..af04c86d0 100644 --- a/vpr/src/route/clock_network_builders.cpp +++ b/vpr/src/route/clock_network_builders.cpp @@ -180,7 +180,7 @@ void ClockRib::create_rr_nodes_and_internal_edges_for_one_instance(ClockRRGraphB (void)num_segments; auto& device_ctx = g_vpr_ctx.mutable_device(); - auto& rr_nodes = device_ctx.rr_nodes; + auto& rr_graph = device_ctx.rr_graph; auto& grid = device_ctx.grid; int ptc_num = clock_graph.get_and_increment_chanx_ptc_num(); // used for drawing @@ -231,7 +231,7 @@ void ClockRib::create_rr_nodes_and_internal_edges_for_one_instance(ClockRRGraphB y, ptc_num, BI_DIRECTION, - rr_nodes); + rr_graph); clock_graph.add_switch_location(get_name(), drive.name, drive_x, y, drive_node_idx); // create rib wire to the right and left of the drive point @@ -240,13 +240,13 @@ void ClockRib::create_rr_nodes_and_internal_edges_for_one_instance(ClockRRGraphB y, ptc_num, DEC_DIRECTION, - rr_nodes); + rr_graph); auto right_node_idx = create_chanx_wire(drive_x + 1, x_end, y, ptc_num, INC_DIRECTION, - rr_nodes); + rr_graph); record_tap_locations(x_start + x_offset, x_end, y, @@ -255,28 +255,27 @@ void ClockRib::create_rr_nodes_and_internal_edges_for_one_instance(ClockRRGraphB clock_graph); // connect drive point to each half rib using a directed switch - rr_nodes[drive_node_idx].add_edge(left_node_idx, drive.switch_idx); - rr_nodes[drive_node_idx].add_edge(right_node_idx, drive.switch_idx); + rr_graph.create_edge(drive_node_idx, left_node_idx, drive.switch_idx); + rr_graph.create_edge(drive_node_idx, right_node_idx, drive.switch_idx); } } } -int ClockRib::create_chanx_wire(int x_start, - int x_end, - int y, - int ptc_num, - e_direction direction, - std::vector& rr_nodes) { - rr_nodes.emplace_back(); - auto node_index = rr_nodes.size() - 1; +RRNodeId ClockRib::create_chanx_wire(int x_start, + int x_end, + int y, + int ptc_num, + e_direction direction, + RRGraph& rr_graph) { + RRNodeId node_index rr_graph.create_node(CHANX); - rr_nodes[node_index].set_coordinates(x_start, y, x_end, y); - rr_nodes[node_index].set_type(CHANX); - rr_nodes[node_index].set_capacity(1); - rr_nodes[node_index].set_track_num(ptc_num); - rr_nodes[node_index].set_rc_index(find_create_rr_rc_data( + rr_graph.set_node_bounding_box(node_index, vtr::Rect(x_start, y, x_end, y)); + rr_graph.set_node_type(node_index, CHANX); + rr_graph.set_node_capacity(node_index, 1); + rr_graph.set_node_track_num(node_index, ptc_num); + rr_graph.set_node_rc_index(node_index, find_create_rr_rc_data( x_chan_wire.layer.r_metal, x_chan_wire.layer.c_metal)); - rr_nodes[node_index].set_direction(direction); + rr_graph.set_node_direction(node_index, direction); short seg_index = 0; switch (direction) { @@ -293,7 +292,7 @@ int ClockRib::create_chanx_wire(int x_start, VTR_ASSERT_MSG(false, "Unidentified direction type for clock rib"); break; } - rr_nodes[node_index].set_cost_index(CHANX_COST_INDEX_START + seg_index); // Actual value set later + rr_graph.set_node_cost_index(node_index, CHANX_COST_INDEX_START + seg_index); // Actual value set later return node_index; } @@ -301,8 +300,8 @@ int ClockRib::create_chanx_wire(int x_start, void ClockRib::record_tap_locations(unsigned x_start, unsigned x_end, unsigned y, - int left_rr_node_idx, - int right_rr_node_idx, + const RRNodeId& left_rr_node_idx, + const RRNodeId& right_rr_node_idx, ClockRRGraphBuilder& clock_graph) { for (unsigned x = x_start + tap.offset; x <= x_end; x += tap.increment) { if (x < (x_start + drive.offset - 1)) { diff --git a/vpr/src/route/rr_graph.cpp b/vpr/src/route/rr_graph.cpp index f1d5f4929..32f86e2f8 100644 --- a/vpr/src/route/rr_graph.cpp +++ b/vpr/src/route/rr_graph.cpp @@ -377,7 +377,7 @@ void create_rr_graph(const t_graph_type graph_type, } /* Xifan Tang - Create rr_graph object: load rr_nodes to the object */ - convert_rr_graph(segment_inf); + //convert_rr_graph(segment_inf); } process_non_config_sets(); @@ -597,6 +597,14 @@ static void build_rr_graph(const t_graph_type graph_type, device_ctx.rr_graph = alloc_and_load_rr_node_indices(max_chan_width, grid, &num_rr_nodes, chan_details_x, chan_details_y); + /* The number of segments are in general small, reserve segments may not bring + * significant memory efficiency */ + device_ctx.rr_graph.reserve_segments(segment_inf.size()); + // Create the segments + for (size_t iseg = 0; iseg < segment_inf.size(); ++iseg) { + device_ctx.rr_graph.create_segment(segment_inf[iseg]); + } + /* These are data structures used by the the unidir opin mapping. They are used * to spread connections evenly for each segment type among the available * wire start points */ diff --git a/vpr/src/route/rr_graph_clock.cpp b/vpr/src/route/rr_graph_clock.cpp index 47746c0f8..fa9f09e34 100644 --- a/vpr/src/route/rr_graph_clock.cpp +++ b/vpr/src/route/rr_graph_clock.cpp @@ -24,22 +24,20 @@ void ClockRRGraphBuilder::create_and_append_clock_rr_graph(std::vectormax, wire_to_rr_ipin_switch, base_cost_type); float elapsed_time = (float)(clock() - begin) / CLOCKS_PER_SEC; @@ -55,8 +53,8 @@ void ClockRRGraphBuilder::create_clock_networks_wires(std::vector node_start_idx); + VTR_ASSERT(true == rr_graph.valid_node_id(RRNodeId(node_start_idx)); std::unordered_map arch_switch_to_rr_switch; // The following assumes that arch_switch was specified earlier when the edges where added - for (size_t node_idx = node_start_idx; node_idx < rr_nodes.size(); node_idx++) { - auto& from_node = rr_nodes[node_idx]; - for (t_edge_size edge_idx = 0; edge_idx < from_node.num_edges(); edge_idx++) { - int arch_switch_idx = from_node.edge_switch(edge_idx); + for (size_t node_idx = node_start_idx; node_idx < rr_graph.nodes().size(); node_idx++) { + const RRNodeId& from_node = RRNodeId(node_idx); + for (const RREdgeId& edge_idx : rr_graph.node_out_edges(from_node)) { + int arch_switch_idx = rr_graph.edge_switch(edge_idx); int rr_switch_idx; auto itter = arch_switch_to_rr_switch.find(arch_switch_idx); @@ -94,7 +92,7 @@ void ClockRRGraphBuilder::add_rr_switches_and_map_to_nodes(size_t node_start_idx rr_switch_idx = itter->second; } - from_node.set_edge_switch(edge_idx, rr_switch_idx); + rr_graph.set_edge_switch(edge_idx, rr_switch_idx); } } @@ -125,17 +123,17 @@ void ClockRRGraphBuilder::add_switch_location(std::string clock_name, std::string switch_point_name, int x, int y, - int node_index) { + const RRNodeId& node_index) { // Note use of operator[] will automatically insert clock name if it doesn't exist clock_name_to_switch_points[clock_name].insert_switch_node_idx(switch_point_name, x, y, node_index); } -void SwitchPoints::insert_switch_node_idx(std::string switch_point_name, int x, int y, int node_idx) { +void SwitchPoints::insert_switch_node_idx(std::string switch_point_name, int x, int y, const RRNodeId& node_idx) { // Note use of operator[] will automatically insert switch name if it doesn't exit switch_point_name_to_switch_location[switch_point_name].insert_node_idx(x, y, node_idx); } -void SwitchPoint::insert_node_idx(int x, int y, int node_idx) { +void SwitchPoint::insert_node_idx(int x, int y, const RRNodeId& node_idx) { // allocate 2d vector of grid size if (rr_node_indices.empty()) { auto& grid = g_vpr_ctx.device().grid; diff --git a/vpr/src/route/rr_graph_clock.h b/vpr/src/route/rr_graph_clock.h index ccab33d8b..df6aff1b9 100644 --- a/vpr/src/route/rr_graph_clock.h +++ b/vpr/src/route/rr_graph_clock.h @@ -13,6 +13,8 @@ #include "clock_network_builders.h" #include "clock_connection_builders.h" +#include "rr_graph_obj_fwd.h" + class ClockNetwork; class ClockConnection; @@ -23,7 +25,7 @@ class SwitchPoint { * Examples of SwitchPoint(s) are rib-to-spine, driver-to-spine. */ public: // [grid_width][grid_height][0..nodes_at_this_location-1] - std::vector>> rr_node_indices; + std::vector>> rr_node_indices; // Set of all the locations for this switch point. Used to quickly find // if the switch point exists at a certian location. std::set> locations; // x,y diff --git a/vpr/src/route/rr_graph_reader.cpp b/vpr/src/route/rr_graph_reader.cpp index 7200786ad..ccaf6af1b 100644 --- a/vpr/src/route/rr_graph_reader.cpp +++ b/vpr/src/route/rr_graph_reader.cpp @@ -281,17 +281,17 @@ void process_seg_id(pugi::xml_node parent, const pugiutil::loc_data& loc_data) { while (rr_node) { id = get_attribute(rr_node, "id", loc_data).as_int(); - auto& node = device_ctx.rr_nodes[id]; + auto& node = RRNodeId(id); segmentSubnode = get_single_child(rr_node, "segment", loc_data, pugiutil::OPTIONAL); if (segmentSubnode) { attribute = get_attribute(segmentSubnode, "segment_id", loc_data, pugiutil::OPTIONAL); if (attribute) { int seg_id = get_attribute(segmentSubnode, "segment_id", loc_data).as_int(0); - device_ctx.rr_indexed_data[node.cost_index()].seg_index = seg_id; + device_ctx.rr_indexed_data[device_ctx.rr_graph.node_cost_index(node)].seg_index = seg_id; } else { //-1 for non chanx or chany nodes - device_ctx.rr_indexed_data[node.cost_index()].seg_index = -1; + device_ctx.rr_indexed_data[device_ctx.rr_graph.node_cost_index(node)].seg_index = -1; } } rr_node = rr_node.next_sibling(rr_node.name()); From 898ed891d2187b47db0a38c584423fab8cb6d18c Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 3 Feb 2020 19:05:18 -0700 Subject: [PATCH 090/645] midway in debugging the refactored rr_graph builder --- vpr/src/base/vpr_context.h | 2 +- vpr/src/device/rr_graph_obj.cpp | 32 ++++++----- vpr/src/device/rr_graph_obj.h | 1 + vpr/src/device/rr_graph_obj_util.cpp | 18 +++++++ vpr/src/device/rr_graph_obj_util.h | 6 +++ vpr/src/route/clock_connection_builders.cpp | 47 ++++++++-------- vpr/src/route/clock_connection_builders.h | 2 +- vpr/src/route/clock_network_builders.cpp | 57 ++++++++++---------- vpr/src/route/clock_network_builders.h | 34 ++++++------ vpr/src/route/rr_graph.cpp | 57 ++++++++++---------- vpr/src/route/rr_graph2.cpp | 60 ++++++++++----------- vpr/src/route/rr_graph2.h | 18 +++++-- vpr/src/route/rr_graph_clock.cpp | 14 ++--- vpr/src/route/rr_graph_clock.h | 14 ++--- vpr/src/route/rr_graph_indexed_data.cpp | 2 +- vpr/src/route/rr_graph_reader.cpp | 25 +++++---- 16 files changed, 214 insertions(+), 175 deletions(-) diff --git a/vpr/src/base/vpr_context.h b/vpr/src/base/vpr_context.h index ecad22007..487382480 100644 --- a/vpr/src/base/vpr_context.h +++ b/vpr/src/base/vpr_context.h @@ -178,7 +178,7 @@ struct DeviceContext : public Context { // Useful for two stage clock routing // XXX: currently only one place to source the clock networks so only storing // a single value - int virtual_clock_network_root_idx; + RRNodeId virtual_clock_network_root_idx; /** Attributes for each rr_node. * key: rr_node index diff --git a/vpr/src/device/rr_graph_obj.cpp b/vpr/src/device/rr_graph_obj.cpp index cb2fb7cd2..37ab22729 100644 --- a/vpr/src/device/rr_graph_obj.cpp +++ b/vpr/src/device/rr_graph_obj.cpp @@ -69,16 +69,16 @@ short RRGraph::node_xhigh(const RRNodeId& node) const { * This is due to the convention in creating RRGraph * so that we can guarantee unique SOURCE/SINK nodes searching */ - if ( (SOURCE == node_type(node) - || (SINK == node_type(node)) { + if ( (SOURCE == node_type(node)) + || (SINK == node_type(node)) ) { return node_bounding_box(node).xmin(); } return node_bounding_box(node).xmax(); } short RRGraph::node_yhigh(const RRNodeId& node) const { - if ( (SOURCE == node_type(node) - || (SINK == node_type(node)) { + if ( (SOURCE == node_type(node)) + || (SINK == node_type(node)) ) { return node_bounding_box(node).ymin(); } return node_bounding_box(node).ymax(); @@ -391,7 +391,8 @@ RRNodeId RRGraph::find_node(const short& x, const short& y, const t_rr_type& typ /* Check if x, y, type and ptc, side is valid */ if ((ptc < 0) /* See if ptc is smaller than the index of first element */ - || (size_t(ptc) > node_lookup_[x][y][type].size() - 1)) { /* See if ptc is large than the index of last element */ + || (size_t(ptc) > node_lookup_[x][y][type].size() - 1) /* See if ptc is large than the index of last element */ + || (0 == node_lookup_[x][y][type].size())) { /* See if ptc is large than the index of last element */ /* Return a zero range! */ return RRNodeId::INVALID(); } @@ -856,7 +857,7 @@ RRNodeId RRGraph::create_node(const t_rr_type& type) { RREdgeId RRGraph::create_edge(const RRNodeId& source, const RRNodeId& sink, const RRSwitchId& switch_id) { VTR_ASSERT(valid_node_id(source)); VTR_ASSERT(valid_node_id(sink)); - VTR_ASSERT(valid_switch_id(switch_id)); + //VTR_ASSERT(valid_switch_id(switch_id)); /* Allocate an ID */ RREdgeId edge_id = RREdgeId(num_edges_); @@ -878,6 +879,11 @@ RREdgeId RRGraph::create_edge(const RRNodeId& source, const RRNodeId& sink, cons return edge_id; } +void RRGraph::set_edge_switch(const RREdgeId& edge, const RRSwitchId& switch_id) { + VTR_ASSERT(valid_edge_id(edge)); + edge_switches_[edge] = switch_id; +} + RRSwitchId RRGraph::create_switch(const t_rr_switch_inf& switch_info) { //Allocate an ID RRSwitchId switch_id = RRSwitchId(switch_ids_.size()); @@ -1200,8 +1206,8 @@ void RRGraph::build_fast_node_lookup() const { /* Skip this id */ continue; } - max_coord.set_x(std::max(max_coord.x(), node_xlow(RRNodeId(id)))); - max_coord.set_y(std::max(max_coord.y(), node_ylow(RRNodeId(id)))); + max_coord.set_x(std::max(max_coord.x(), std::max(node_bounding_boxes_[RRNodeId(id)].xmax(), node_bounding_boxes_[RRNodeId(id)].xmin()))); + max_coord.set_y(std::max(max_coord.y(), std::max(node_bounding_boxes_[RRNodeId(id)].ymax(), node_bounding_boxes_[RRNodeId(id)].ymin()))); } node_lookup_.resize({(size_t)max_coord.x() + 1, (size_t)max_coord.y() + 1, NUM_RR_TYPES + 1}); @@ -1218,15 +1224,17 @@ void RRGraph::build_fast_node_lookup() const { std::vector xlows; std::vector ylows; - if ( (SOURCE == node_type(node) - || (SINK == node_type(node)) { + if ( (SOURCE == node_type(node)) + || (SINK == node_type(node)) + || (CHANX == node_type(node)) + || (CHANY == node_type(node)) ) { xlows.resize(node_bounding_boxes_[node].xmax() - node_bounding_boxes_[node].xmin() + 1); ylows.resize(node_bounding_boxes_[node].ymax() - node_bounding_boxes_[node].ymin() + 1); std::iota(xlows.begin(), xlows.end(), node_xlow(node)); std::iota(ylows.begin(), ylows.end(), node_ylow(node)); /* Sanity check */ - VTR_ASSERT(node_bounding_boxes_[node].xmax() == xlows.back()); - VTR_ASSERT(node_bounding_boxes_[node].ymax() == ylows.back()); + VTR_ASSERT(size_t(node_bounding_boxes_[node].xmax()) == xlows.back()); + VTR_ASSERT(size_t(node_bounding_boxes_[node].ymax()) == ylows.back()); } else { xlows.push_back(node_xlow(node)); ylows.push_back(node_ylow(node)); diff --git a/vpr/src/device/rr_graph_obj.h b/vpr/src/device/rr_graph_obj.h index df46df754..fedc90612 100644 --- a/vpr/src/device/rr_graph_obj.h +++ b/vpr/src/device/rr_graph_obj.h @@ -615,6 +615,7 @@ class RRGraph { * configure the nodes and edges in connection */ RREdgeId create_edge(const RRNodeId& source, const RRNodeId& sink, const RRSwitchId& switch_id); + void set_edge_switch(const RREdgeId& edge, const RRSwitchId& switch_id); RRSwitchId create_switch(const t_rr_switch_inf& switch_info); RRSegmentId create_segment(const t_segment_inf& segment_info); diff --git a/vpr/src/device/rr_graph_obj_util.cpp b/vpr/src/device/rr_graph_obj_util.cpp index 92d27e083..373c48fa1 100644 --- a/vpr/src/device/rr_graph_obj_util.cpp +++ b/vpr/src/device/rr_graph_obj_util.cpp @@ -63,3 +63,21 @@ std::vector find_rr_graph_nodes(const RRGraph& rr_graph, return indices; } +std::vector find_rr_graph_chan_nodes(const RRGraph& rr_graph, + const int& x, + const int& y, + const t_rr_type& rr_type) { + std::vector indices; + + VTR_ASSERT(rr_type == CHANX || rr_type == CHANY); + + for (short track = 0; track < rr_graph.chan_num_tracks(x, y, rr_type); ++track) { + RRNodeId rr_node_index = rr_graph.find_node(x, y, rr_type, track); + + if (rr_node_index != RRNodeId::INVALID()) { + indices.push_back(rr_node_index); + } + } + + return indices; +} diff --git a/vpr/src/device/rr_graph_obj_util.h b/vpr/src/device/rr_graph_obj_util.h index 9a089d68c..71d97dcf9 100644 --- a/vpr/src/device/rr_graph_obj_util.h +++ b/vpr/src/device/rr_graph_obj_util.h @@ -18,4 +18,10 @@ std::vector find_rr_graph_nodes(const RRGraph& rr_graph, const t_rr_type& rr_type, const int& ptc); +std::vector find_rr_graph_chan_nodes(const RRGraph& rr_graph, + const int& x, + const int& y, + const t_rr_type& rr_type); + + #endif diff --git a/vpr/src/route/clock_connection_builders.cpp b/vpr/src/route/clock_connection_builders.cpp index 5ba6fefea..b36fee966 100644 --- a/vpr/src/route/clock_connection_builders.cpp +++ b/vpr/src/route/clock_connection_builders.cpp @@ -2,6 +2,7 @@ #include "globals.h" #include "rr_graph2.h" +#include "rr_graph_obj_util.h" #include "vtr_assert.h" #include "vtr_log.h" @@ -46,17 +47,16 @@ void RoutingToClockConnection::create_switches(const ClockRRGraphBuilder& clock_ std::srand(seed); auto& device_ctx = g_vpr_ctx.mutable_device(); - auto& rr_nodes = device_ctx.rr_nodes; - auto& rr_node_indices = device_ctx.rr_node_indices; + auto& rr_graph = device_ctx.rr_graph; - int virtual_clock_network_root_idx = create_virtual_clock_network_sink_node(switch_location.x, switch_location.y); + RRNodeId virtual_clock_network_root_idx = create_virtual_clock_network_sink_node(switch_location.x, switch_location.y); device_ctx.virtual_clock_network_root_idx = virtual_clock_network_root_idx; // rr_node indices for x and y channel routing wires and clock wires to connect to - auto x_wire_indices = get_rr_node_chan_wires_at_location( - rr_node_indices, CHANX, switch_location.x, switch_location.y); - auto y_wire_indices = get_rr_node_chan_wires_at_location( - rr_node_indices, CHANY, switch_location.x, switch_location.y); + auto x_wire_indices = find_rr_graph_chan_nodes( + rr_graph, switch_location.x, switch_location.y, CHANX); + auto y_wire_indices = find_rr_graph_chan_nodes( + rr_graph, switch_location.x, switch_location.y, CHANY); auto clock_indices = clock_graph.get_rr_node_indices_at_switch_location( clock_to_connect_to, switch_point_name, switch_location.x, switch_location.y); @@ -68,36 +68,35 @@ void RoutingToClockConnection::create_switches(const ClockRRGraphBuilder& clock_ // Connect to x-channel wires unsigned num_wires_x = x_wire_indices.size() * fc; for (size_t i = 0; i < num_wires_x; i++) { - rr_nodes[x_wire_indices[i]].add_edge(clock_index, rr_switch_idx); + rr_graph.create_edge(x_wire_indices[i], clock_index, RRSwitchId(rr_switch_idx)); } // Connect to y-channel wires unsigned num_wires_y = y_wire_indices.size() * fc; for (size_t i = 0; i < num_wires_y; i++) { - rr_nodes[y_wire_indices[i]].add_edge(clock_index, rr_switch_idx); + rr_graph.create_edge(y_wire_indices[i], clock_index, RRSwitchId(rr_switch_idx)); } // Connect to virtual clock sink node // used by the two stage router - rr_nodes[clock_index].add_edge(virtual_clock_network_root_idx, rr_switch_idx); + rr_graph.create_edge(clock_index, virtual_clock_network_root_idx, RRSwitchId(rr_switch_idx)); } } -int RoutingToClockConnection::create_virtual_clock_network_sink_node( +RRNodeId RoutingToClockConnection::create_virtual_clock_network_sink_node( int x, int y) { auto& device_ctx = g_vpr_ctx.mutable_device(); - auto& rr_nodes = device_ctx.rr_nodes; - rr_nodes.emplace_back(); - auto node_index = rr_nodes.size() - 1; + auto& rr_graph = device_ctx.rr_graph; - rr_nodes[node_index].set_coordinates(x, y, x, y); - rr_nodes[node_index].set_capacity(1); - rr_nodes[node_index].set_cost_index(SINK_COST_INDEX); - rr_nodes[node_index].set_type(SINK); + RRNodeId node_index = rr_graph.create_node(SINK); + + rr_graph.set_node_bounding_box(node_index, vtr::Rect(x, y, x, y)); + rr_graph.set_node_capacity(node_index, 1); + rr_graph.set_node_cost_index(node_index, SINK_COST_INDEX); float R = 0.; float C = 0.; - rr_nodes[node_index].set_rc_index(find_create_rr_rc_data(R, C)); + rr_graph.set_node_rc_data_index(node_index, find_create_rr_rc_data(R, C)); return node_index; } @@ -179,7 +178,7 @@ void ClockToClockConneciton::create_switches(const ClockRRGraphBuilder& clock_gr if (from_itter == from_rr_node_indices.end()) { from_itter = from_rr_node_indices.begin(); } - rr_nodes[*from_itter].add_edge(to_index, rr_switch_idx); + rr_graph.create_edge(*from_itter, to_index, RRSwitchId(rr_switch_idx)); from_itter++; } } @@ -213,8 +212,7 @@ void ClockToPinsConnection::set_fc_val(float fc_val) { void ClockToPinsConnection::create_switches(const ClockRRGraphBuilder& clock_graph) { auto& device_ctx = g_vpr_ctx.mutable_device(); - auto& rr_nodes = device_ctx.rr_nodes; - auto& rr_node_indices = device_ctx.rr_node_indices; + auto& rr_graph = device_ctx.rr_graph; auto& grid = device_ctx.grid; for (size_t x = 0; x < grid.width(); x++) { @@ -274,8 +272,7 @@ void ClockToPinsConnection::create_switches(const ClockRRGraphBuilder& clock_gra clock_y_offset = -1; // pick the chanx below the block } - auto clock_pin_node_idx = get_rr_node_index( - rr_node_indices, + auto clock_pin_node_idx = rr_graph.find_node( x, y, IPIN, @@ -290,7 +287,7 @@ void ClockToPinsConnection::create_switches(const ClockRRGraphBuilder& clock_gra //Create edges depending on Fc for (size_t i = 0; i < clock_network_indices.size() * fc; i++) { - rr_nodes[clock_network_indices[i]].add_edge(clock_pin_node_idx, rr_switch_idx); + rr_graph.create_edge(clock_network_indices[i], clock_pin_node_idx, RRSwitchId(rr_switch_idx)); } } } diff --git a/vpr/src/route/clock_connection_builders.h b/vpr/src/route/clock_connection_builders.h index 8076907b6..707f16af7 100644 --- a/vpr/src/route/clock_connection_builders.h +++ b/vpr/src/route/clock_connection_builders.h @@ -54,7 +54,7 @@ class RoutingToClockConnection : public ClockConnection { */ /* Connects the inter-block routing to the clock source at the specified coordinates */ void create_switches(const ClockRRGraphBuilder& clock_graph); - int create_virtual_clock_network_sink_node(int x, int y); + RRNodeId create_virtual_clock_network_sink_node(int x, int y); }; class ClockToClockConneciton : public ClockConnection { diff --git a/vpr/src/route/clock_network_builders.cpp b/vpr/src/route/clock_network_builders.cpp index af04c86d0..b90babdae 100644 --- a/vpr/src/route/clock_network_builders.cpp +++ b/vpr/src/route/clock_network_builders.cpp @@ -255,8 +255,8 @@ void ClockRib::create_rr_nodes_and_internal_edges_for_one_instance(ClockRRGraphB clock_graph); // connect drive point to each half rib using a directed switch - rr_graph.create_edge(drive_node_idx, left_node_idx, drive.switch_idx); - rr_graph.create_edge(drive_node_idx, right_node_idx, drive.switch_idx); + rr_graph.create_edge(drive_node_idx, left_node_idx, RRSwitchId(drive.switch_idx)); + rr_graph.create_edge(drive_node_idx, right_node_idx, RRSwitchId(drive.switch_idx)); } } } @@ -267,13 +267,12 @@ RRNodeId ClockRib::create_chanx_wire(int x_start, int ptc_num, e_direction direction, RRGraph& rr_graph) { - RRNodeId node_index rr_graph.create_node(CHANX); + RRNodeId node_index = rr_graph.create_node(CHANX); - rr_graph.set_node_bounding_box(node_index, vtr::Rect(x_start, y, x_end, y)); - rr_graph.set_node_type(node_index, CHANX); + rr_graph.set_node_bounding_box(node_index, vtr::Rect(x_start, y, x_end, y)); rr_graph.set_node_capacity(node_index, 1); rr_graph.set_node_track_num(node_index, ptc_num); - rr_graph.set_node_rc_index(node_index, find_create_rr_rc_data( + rr_graph.set_node_rc_data_index(node_index, find_create_rr_rc_data( x_chan_wire.layer.r_metal, x_chan_wire.layer.c_metal)); rr_graph.set_node_direction(node_index, direction); @@ -421,7 +420,7 @@ void ClockSpine::create_segments(std::vector& segment_inf) { void ClockSpine::create_rr_nodes_and_internal_edges_for_one_instance(ClockRRGraphBuilder& clock_graph, int num_segments) { auto& device_ctx = g_vpr_ctx.mutable_device(); - auto& rr_nodes = device_ctx.rr_nodes; + auto& rr_graph = device_ctx.rr_graph; auto& grid = device_ctx.grid; int ptc_num = clock_graph.get_and_increment_chany_ptc_num(); // used for drawing @@ -472,7 +471,7 @@ void ClockSpine::create_rr_nodes_and_internal_edges_for_one_instance(ClockRRGrap x, ptc_num, BI_DIRECTION, - rr_nodes, + rr_graph, num_segments); clock_graph.add_switch_location(get_name(), drive.name, x, drive_y, drive_node_idx); @@ -482,14 +481,14 @@ void ClockSpine::create_rr_nodes_and_internal_edges_for_one_instance(ClockRRGrap x, ptc_num, DEC_DIRECTION, - rr_nodes, + rr_graph, num_segments); auto right_node_idx = create_chany_wire(drive_y + 1, y_end, x, ptc_num, INC_DIRECTION, - rr_nodes, + rr_graph, num_segments); // Keep a record of the rr_node idx that we will use to connects switches to at @@ -502,29 +501,27 @@ void ClockSpine::create_rr_nodes_and_internal_edges_for_one_instance(ClockRRGrap clock_graph); // connect drive point to each half spine using a directed switch - rr_nodes[drive_node_idx].add_edge(left_node_idx, drive.switch_idx); - rr_nodes[drive_node_idx].add_edge(right_node_idx, drive.switch_idx); + rr_graph.create_edge(drive_node_idx, left_node_idx, RRSwitchId(drive.switch_idx)); + rr_graph.create_edge(drive_node_idx, right_node_idx, RRSwitchId(drive.switch_idx)); } } } -int ClockSpine::create_chany_wire(int y_start, - int y_end, - int x, - int ptc_num, - e_direction direction, - std::vector& rr_nodes, - int num_segments) { - rr_nodes.emplace_back(); - auto node_index = rr_nodes.size() - 1; +RRNodeId ClockSpine::create_chany_wire(int y_start, + int y_end, + int x, + int ptc_num, + e_direction direction, + RRGraph& rr_graph, + int num_segments) { + RRNodeId node_index = rr_graph.create_node(CHANY); - rr_nodes[node_index].set_coordinates(x, y_start, x, y_end); - rr_nodes[node_index].set_type(CHANY); - rr_nodes[node_index].set_capacity(1); - rr_nodes[node_index].set_track_num(ptc_num); - rr_nodes[node_index].set_rc_index(find_create_rr_rc_data( + rr_graph.set_node_bounding_box(node_index, vtr::Rect(x, y_start, x, y_end)); + rr_graph.set_node_capacity(node_index, 1); + rr_graph.set_node_track_num(node_index, ptc_num); + rr_graph.set_node_rc_data_index(node_index, find_create_rr_rc_data( y_chan_wire.layer.r_metal, y_chan_wire.layer.c_metal)); - rr_nodes[node_index].set_direction(direction); + rr_graph.set_node_direction(node_index, direction); short seg_index = 0; switch (direction) { @@ -541,7 +538,7 @@ int ClockSpine::create_chany_wire(int y_start, VTR_ASSERT_MSG(false, "Unidentified direction type for clock rib"); break; } - rr_nodes[node_index].set_cost_index(CHANX_COST_INDEX_START + num_segments + seg_index); + rr_graph.set_node_cost_index(node_index, CHANX_COST_INDEX_START + num_segments + seg_index); return node_index; } @@ -549,8 +546,8 @@ int ClockSpine::create_chany_wire(int y_start, void ClockSpine::record_tap_locations(unsigned y_start, unsigned y_end, unsigned x, - int left_node_idx, - int right_node_idx, + const RRNodeId& left_node_idx, + const RRNodeId& right_node_idx, ClockRRGraphBuilder& clock_graph) { for (unsigned y = y_start + tap.offset; y <= y_end; y += tap.increment) { if (y < (y_start + drive.offset - 1)) { diff --git a/vpr/src/route/clock_network_builders.h b/vpr/src/route/clock_network_builders.h index c4db346ae..f768614d6 100644 --- a/vpr/src/route/clock_network_builders.h +++ b/vpr/src/route/clock_network_builders.h @@ -155,17 +155,17 @@ class ClockRib : public ClockNetwork { void create_segments(std::vector& segment_inf); void create_rr_nodes_and_internal_edges_for_one_instance(ClockRRGraphBuilder& clock_graph, int num_segments); - int create_chanx_wire(int x_start, - int x_end, - int y, - int ptc_num, - e_direction direction, - std::vector& rr_nodes); + RRNodeId create_chanx_wire(int x_start, + int x_end, + int y, + int ptc_num, + e_direction direction, + RRGraph& rr_graph); void record_tap_locations(unsigned x_start, unsigned x_end, unsigned y, - int left_rr_node_idx, - int right_rr_node_idx, + const RRNodeId& left_rr_node_idx, + const RRNodeId& right_rr_node_idx, ClockRRGraphBuilder& clock_graph); }; @@ -211,18 +211,18 @@ class ClockSpine : public ClockNetwork { void create_segments(std::vector& segment_inf); void create_rr_nodes_and_internal_edges_for_one_instance(ClockRRGraphBuilder& clock_graph, int num_segments); - int create_chany_wire(int y_start, - int y_end, - int x, - int ptc_num, - e_direction direction, - std::vector& rr_nodes, - int num_segments); + RRNodeId create_chany_wire(int y_start, + int y_end, + int x, + int ptc_num, + e_direction direction, + RRGraph& rr_graph, + int num_segments); void record_tap_locations(unsigned y_start, unsigned y_end, unsigned x, - int left_node_idx, - int right_node_idx, + const RRNodeId& left_node_idx, + const RRNodeId& right_node_idx, ClockRRGraphBuilder& clock_graph); }; diff --git a/vpr/src/route/rr_graph.cpp b/vpr/src/route/rr_graph.cpp index 32f86e2f8..6e5ded8b1 100644 --- a/vpr/src/route/rr_graph.cpp +++ b/vpr/src/route/rr_graph.cpp @@ -38,6 +38,8 @@ #include "create_rr_graph.h" #include "write_xml_rr_graph_obj.h" +#include "rr_graph_obj_util.h" +#include "check_rr_graph_obj.h" //#define VERBOSE @@ -133,7 +135,7 @@ static void build_unidir_rr_opins(const int i, t_rr_edge_info_set& created_rr_edges, bool* Fc_clipped, const t_rr_node_indices& L_rr_node_indices, - const std::vector& rr_nodes, + const RRGraph& rr_graph, const t_direct_inf* directs, const int num_directs, const t_clb_to_clb_directs* clb_to_clb_directs, @@ -146,7 +148,7 @@ static int get_opin_direct_connections(int x, const RRNodeId& from_rr_node, t_rr_edge_info_set& rr_edges_to_create, const t_rr_node_indices& L_rr_node_indices, - const RRGraph& rr_nodes, + const RRGraph& rr_graph, const t_direct_inf* directs, const int num_directs, const t_clb_to_clb_directs* clb_to_clb_directs); @@ -856,11 +858,11 @@ static void alloc_rr_switch_inf(t_arch_switch_fanin& arch_switch_fanins) { // //Note that since we don't store backward edge info in the RR graph we need to walk //the whole graph to get the per-switch-type fanin info - std::vector> inward_switch_inf(device_ctx.rr_graph.nodes().size()); //[to_node][arch_switch] -> fanin + vtr::vector> inward_switch_inf(device_ctx.rr_graph.nodes().size()); //[to_node][arch_switch] -> fanin for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { for (const RREdgeId& iedge : device_ctx.rr_graph.node_out_edges(inode)) { - int iswitch = device_ctx.rr_graph.edge_switch(iedge); - const RRNodeId& to_node = device_ctx.rr_graph.edge_sink_node(iedge); + int iswitch = (int)size_t(device_ctx.rr_graph.edge_switch(iedge)); + RRNodeId to_node = device_ctx.rr_graph.edge_sink_node(iedge); if (inward_switch_inf[to_node].count(iswitch) == 0) { inward_switch_inf[to_node][iswitch] = 0; @@ -971,7 +973,7 @@ static void remap_rr_node_switch_indices(const t_arch_switch_fanin& switch_fanin for (const RREdgeId& iedge : device_ctx.rr_graph.node_out_edges(from_node)) { const RRNodeId& to_node = device_ctx.rr_graph.edge_sink_node(iedge); /* get the switch which this edge uses and its fanin */ - int switch_index = device_ctx.rr_graph.edge_switch(iedge); + int switch_index = (int)size_t(device_ctx.rr_graph.edge_switch(iedge)); int fanin = device_ctx.rr_graph.node_in_edges(to_node).size(); if (switch_fanin[switch_index].count(UNDEFINED) == 1) { @@ -983,7 +985,7 @@ static void remap_rr_node_switch_indices(const t_arch_switch_fanin& switch_fanin int rr_switch_index = itr->second; - device_ctx.rr_graph.set_edge_switch(iedge, rr_switch_index); + device_ctx.rr_graph.set_edge_switch(iedge, RRSwitchId(rr_switch_index)); } } } @@ -1291,7 +1293,7 @@ static void alloc_and_load_rr_graph(const int num_nodes, bool clipped; build_unidir_rr_opins(i, j, side, grid, Fc_out, max_chan_width, chan_details_x, chan_details_y, Fc_xofs, Fc_yofs, - rr_edges_to_create, &clipped, L_rr_node_indices, L_rr_node, + rr_edges_to_create, &clipped, L_rr_node_indices, const_cast(rr_graph), directs, num_directs, clb_to_clb_directs, num_seg_types); if (clipped) { *Fc_clipped = true; @@ -1392,7 +1394,7 @@ static void build_bidir_rr_opins(const int i, } RRNodeId node_index = rr_graph.find_node(i, j, OPIN, pin_index, side); - VTR_ASSERT(node_index >= 0); + VTR_ASSERT(true == rr_graph.valid_node_id(node_index)); if (total_pin_Fc > 0) { get_bidir_opin_connections(i, j, pin_index, @@ -1403,7 +1405,7 @@ static void build_bidir_rr_opins(const int i, /* Add in direct connections */ get_opin_direct_connections(i, j, side, pin_index, - node_index, rr_edges_to_create, L_rr_node_indices, rr_nodes, + node_index, rr_edges_to_create, L_rr_node_indices, rr_graph, directs, num_directs, clb_to_clb_directs); } } @@ -1466,7 +1468,7 @@ static void build_rr_sinks_sources(const int i, /* SINK and SOURCE-to-OPIN edges */ for (int iclass = 0; iclass < num_class; ++iclass) { - RRNodeId inode = 0; + RRNodeId inode = RRNodeId::INVALID(); if (class_inf[iclass].type == DRIVER) { /* SOURCE */ inode = rr_graph.find_node(i, j, SOURCE, iclass); @@ -1491,7 +1493,7 @@ static void build_rr_sinks_sources(const int i, rr_graph.set_node_cost_index(inode, SOURCE_COST_INDEX); } else { /* SINK */ VTR_ASSERT(class_inf[iclass].type == RECEIVER); - inode = rr_graph.find_node(L_rr_node_indices, i, j, SINK, iclass); + inode = rr_graph.find_node(i, j, SINK, iclass); /* NOTE: To allow route throughs through clbs, change the lines below to * * make an edge from the input SINK to the output SOURCE. Do for just the * @@ -1508,7 +1510,7 @@ static void build_rr_sinks_sources(const int i, rr_graph.set_node_bounding_box(inode, vtr::Rect(i, j, i + type->width - 1, j + type->height - 1)); float R = 0.; float C = 0.; - rr_graph.set_node_rc_index(inode, find_create_rr_rc_data(R, C)); + rr_graph.set_node_rc_data_index(inode, find_create_rr_rc_data(R, C)); rr_graph.set_node_ptc_num(inode, iclass); } @@ -1520,7 +1522,7 @@ static void build_rr_sinks_sources(const int i, for (e_side side : {TOP, BOTTOM, LEFT, RIGHT}) { for (int ipin = 0; ipin < num_pins; ++ipin) { if (type->pinloc[width_offset][height_offset][side][ipin]) { - int inode; + RRNodeId inode; int iclass = pin_class[ipin]; if (class_inf[iclass].type == RECEIVER) { @@ -1532,8 +1534,8 @@ static void build_rr_sinks_sources(const int i, //Add info about the edge to be created rr_edges_to_create.emplace_back(inode, to_node, delayless_switch); - VTR_ASSERT(inode >= 0); - rr_graph.set_node_cost_index(IPIN_COST_INDEX); + VTR_ASSERT(true == rr_graph.valid_node_id(inode)); + rr_graph.set_node_cost_index(inode, IPIN_COST_INDEX); } else { VTR_ASSERT(class_inf[iclass].type == DRIVER); @@ -1550,7 +1552,7 @@ static void build_rr_sinks_sources(const int i, rr_graph.set_node_capacity(inode, 1); float R = 0.; float C = 0.; - rr_graph.set_node_rc_index(inode, find_create_rr_rc_data(R, C)); + rr_graph.set_node_rc_data_index(inode, find_create_rr_rc_data(R, C)); rr_graph.set_node_ptc_num(inode, ipin); //Note that we store the grid tile location and side where the pin is located, @@ -1558,7 +1560,7 @@ static void build_rr_sinks_sources(const int i, rr_graph.set_node_bounding_box(inode, vtr::Rect(i + width_offset, j + height_offset, i + width_offset, j + height_offset)); rr_graph.set_node_side(inode, side); - VTR_ASSERT(type->pinloc[width_offset][height_offset][L_rr_node[inode].side()][L_rr_node[inode].pin_num()]); + VTR_ASSERT(type->pinloc[width_offset][height_offset][rr_graph.node_side(inode)][rr_graph.node_pin_num(inode)]); } } } @@ -1741,24 +1743,23 @@ static void build_rr_chan(const int x_coord, } /* Edge arrays have now been built up. Do everything else. */ - rr_graph.node[node].set_node_cost_index(node, cost_index_offset + seg_details[track].index()); + rr_graph.set_node_cost_index(node, cost_index_offset + seg_details[track].index()); rr_graph.set_node_capacity(node, 1); /* GLOBAL routing handled elsewhere */ if (chan_type == CHANX) { rr_graph.set_node_bounding_box(node, vtr::Rect(start, y_coord, end, y_coord)); } else { VTR_ASSERT(chan_type == CHANY); - L_rr_node[node].set_coordinates(x_coord, start, x_coord, end); rr_graph.set_node_bounding_box(node, vtr::Rect(x_coord, start, x_coord, end)); } int length = end - start + 1; float R = length * seg_details[track].Rmetal(); float C = length * seg_details[track].Cmetal(); - rr_graph.set_node_rc_index(node, find_create_rr_rc_data(R, C)); + rr_graph.set_node_rc_data_index(node, find_create_rr_rc_data(R, C)); rr_graph.set_node_ptc_num(node, track); - rr_graph.set_node_type(node, chan_type); + VTR_ASSERT(chan_type == rr_graph.node_type(node)); rr_graph.set_node_direction(node, seg_details[track].direction()); } } @@ -1804,14 +1805,14 @@ void alloc_and_load_edges(RRGraph& rr_graph, for (auto itr = edge_range.first; itr != edge_range.second; ++itr) { VTR_ASSERT(itr->from_node == inode); - rr_graph.create_edge(inode, itr->to_node, itr->switch_type); + rr_graph.create_edge(inode, itr->to_node, RRSwitchId(itr->switch_type)); } } else { //Add new edge incrementally // //This should occur relatively rarely (e.g. a backward bidir edge) so memory fragmentation shouldn't be a big problem for (auto itr = edge_range.first; itr != edge_range.second; ++itr) { - rr_graph.create_edge(inode, itr->to_node, itr->switch_type); + rr_graph.create_edge(inode, itr->to_node, RRSwitchId(itr->switch_type)); } } } @@ -2836,7 +2837,7 @@ static int get_opin_direct_connections(int x, const RRNodeId& from_rr_node, t_rr_edge_info_set& rr_edges_to_create, const t_rr_node_indices& L_rr_node_indices, - const RRGraph& rr_nodes, + const RRGraph& rr_graph, const t_direct_inf* directs, const int num_directs, const t_clb_to_clb_directs* clb_to_clb_directs) { @@ -2925,7 +2926,7 @@ static int get_opin_direct_connections(int x, } } else { //No side specified, get all candidates - inodes = rr_graph.find_node(x + directs[i].x_offset, y + directs[i].y_offset, + inodes = find_rr_graph_nodes(rr_graph, x + directs[i].x_offset, y + directs[i].y_offset, IPIN, ipin); } @@ -3048,7 +3049,7 @@ static std::vector alloc_and_load_perturb_opins(const t_physical_tile_type return perturb_opins; } -static RRNodeId pick_best_direct_connect_target_rr_node(const RRGraph& rr_nodes, +static RRNodeId pick_best_direct_connect_target_rr_node(const RRGraph& rr_graph, const RRNodeId& from_rr, const std::vector& candidate_rr_nodes) { //With physically equivalent pins there may be multiple candidate rr nodes (which are equivalent) @@ -3066,7 +3067,7 @@ static RRNodeId pick_best_direct_connect_target_rr_node(const RRGraph& rr_nodes, float best_dist = std::numeric_limits::infinity(); RRNodeId best_rr = RRNodeId::INVALID(); - for (int to_rr : candidate_rr_nodes) { + for (const RRNodeId& to_rr : candidate_rr_nodes) { VTR_ASSERT(rr_graph.node_type(to_rr) == IPIN); float to_dist = std::abs(rr_graph.node_xlow(from_rr) - rr_graph.node_xlow(to_rr)) + std::abs(rr_graph.node_ylow(from_rr) - rr_graph.node_ylow(to_rr)); diff --git a/vpr/src/route/rr_graph2.cpp b/vpr/src/route/rr_graph2.cpp index b861be721..21d69f296 100644 --- a/vpr/src/route/rr_graph2.cpp +++ b/vpr/src/route/rr_graph2.cpp @@ -67,7 +67,7 @@ static int get_unidir_track_to_chan_seg(const int from_track, const RRGraph& rr_graph, const t_chan_seg_details* seg_details, bool* Fs_clipped, - const int from_rr_node, + const RRNodeId& from_rr_node, t_rr_edge_info_set& rr_edges_to_create); static int get_track_to_chan_seg(const int from_track, @@ -765,7 +765,7 @@ int get_unidir_opin_connections(const int chan, int* inc_muxes = nullptr; int* dec_muxes = nullptr; int num_inc_muxes, num_dec_muxes, iconn; - int inc_inode_index, dec_inode_index; + RRNodeId inc_inode_index, dec_inode_index; int inc_mux, dec_mux; int inc_track, dec_track; int x, y; @@ -809,8 +809,8 @@ int get_unidir_opin_connections(const int chan, dec_track = dec_muxes[dec_mux]; /* Figure the inodes of those muxes */ - inc_inode_index = rr_graph.find_node(x, y, chan_type, inc_track); - dec_inode_index = rr_graph.find_node(x, y, chan_type, dec_track); + inc_inode_index = rr_graph.find_node(chan, seg, chan_type, inc_track); + dec_inode_index = rr_graph.find_node(chan, seg, chan_type, dec_track); if (inc_inode_index == RRNodeId::INVALID() || dec_inode_index == RRNodeId::INVALID()) { continue; @@ -1069,24 +1069,24 @@ static void load_chan_rr_indices(const int max_chan_width, int y = (type == CHANX ? chan : seg); const t_chan_seg_details* seg_details = chan_details[x][y].data(); - for (unsigned track = 0; track < num_chans - 1; ++track) { + for (int track = 0; track < num_chans - 1; ++track) { if (seg_details[track].length() <= 0) continue; int start = get_seg_start(seg_details, track, chan, seg); - /* If this segment does not start from the current position, we do not allocate any nodes */ - if (start != seg) { - continue; + /* We give a fake coordinator here, to ease the downstream builder */ + short xlow = chan; + short ylow = start; + RRNodeId node = rr_graph.find_node(xlow, ylow, type, track); + if (false == rr_graph.valid_node_id(node)) { + + RRNodeId new_node = rr_graph.create_node(type); + rr_graph.set_node_bounding_box(new_node, vtr::Rect(xlow, ylow, xlow, ylow)); + rr_graph.set_node_track_num(new_node, track); + + (*index)++; } - - RRNodeId node = rr_graph.create_node(type); - short xlow = x; - short ylow = y; - short xhigh = (type == CHANX ? xlow + seg_details[track].length() - 1 : xlow); - short yhigh = (type == CHANY ? ylow + seg_details[track].length() - 1 : ylow); - rr_graph.set_node_bounding_box(node, vtr::Rect(xlow, ylow, xhigh, yhigh)); - rr_graph.set_node_track_num(node, track); } } } @@ -1108,13 +1108,13 @@ static void load_block_rr_indices(const DeviceGrid& grid, auto class_type = type->class_inf[iclass].type; if (class_type == DRIVER) { RRNodeId node = rr_graph.create_node(SOURCE); - rr_graph.set_node_bounding_box(node, vtr::Rect(x, y, x, y)); - rr_graph.set_node_class_num(iclass); + rr_graph.set_node_bounding_box(node, vtr::Rect(x, y, x, y)); + rr_graph.set_node_class_num(node, iclass); } else { VTR_ASSERT(class_type == RECEIVER); RRNodeId node = rr_graph.create_node(SINK); - rr_graph.set_node_bounding_box(node, vtr::Rect(x, y, x, y)); - rr_graph.set_node_class_num(iclass); + rr_graph.set_node_bounding_box(node, vtr::Rect(x, y, x, y)); + rr_graph.set_node_class_num(node, iclass); } ++(*index); } @@ -1132,15 +1132,15 @@ static void load_block_rr_indices(const DeviceGrid& grid, if (class_type == DRIVER) { RRNodeId node = rr_graph.create_node(OPIN); - rr_graph.set_node_bounding_box(node, vtr::Rect(xtile, ytile, xtile, ytile)); - rr_graph.set_node_pin_num(ipin); - rr_graph.set_node_side(side); + rr_graph.set_node_bounding_box(node, vtr::Rect(x_tile, y_tile, x_tile, y_tile)); + rr_graph.set_node_pin_num(node, ipin); + rr_graph.set_node_side(node, side); } else { VTR_ASSERT(class_type == RECEIVER); RRNodeId node = rr_graph.create_node(IPIN); - rr_graph.set_node_bounding_box(node, vtr::Rect(xtile, ytile, xtile, ytile)); - rr_graph.set_node_pin_num(ipin); - rr_graph.set_node_side(side); + rr_graph.set_node_bounding_box(node, vtr::Rect(x_tile, y_tile, x_tile, y_tile)); + rr_graph.set_node_pin_num(node, ipin); + rr_graph.set_node_side(node, side); } ++(*index); } @@ -1162,7 +1162,7 @@ static void load_block_rr_indices(const DeviceGrid& grid, * this can avoid runtime cost to rebuild the fast look-up inside RRGraph object */ if ( (grid[x][y].type->height == height_offset && grid[x][y].type->width == width_offset) - && (0 != height_offset && 0 != width_offset) ) { + && (0 != height_offset || 0 != width_offset) ) { int root_x = x - width_offset; int root_y = y - height_offset; @@ -1176,12 +1176,12 @@ static void load_block_rr_indices(const DeviceGrid& grid, if (class_type == DRIVER) { RRNodeId node = rr_graph.find_node(root_x, root_y, SOURCE, iclass); /* Update the internal look-up so that we can find the SOURCE/SINK node using their offset coordinates */ - rr_graph.set_node_bounding_box(node, vtr::Rect(root_x, root_y, x, y)); + rr_graph.set_node_bounding_box(node, vtr::Rect(root_x, root_y, x, y)); } else { VTR_ASSERT(class_type == RECEIVER); RRNodeId node = rr_graph.find_node(root_x, root_y, SINK, iclass); /* Update the internal look-up so that we can find the SOURCE/SINK node using their offset coordinates */ - rr_graph.set_node_bounding_box(node, vtr::Rect(root_x, root_y, x, y)); + rr_graph.set_node_bounding_box(node, vtr::Rect(root_x, root_y, x, y)); } } } @@ -1454,7 +1454,7 @@ int get_track_to_pins(int seg, /* Check there is a connection and Fc map isn't wrong */ /*int to_node = get_rr_node_index(L_rr_node_indices, x + width_offset, y + height_offset, IPIN, ipin, side);*/ RRNodeId to_node = rr_graph.find_node(x, y, IPIN, ipin, side); - if (to_node >= 0) { + if (rr_graph.valid_node_id(to_node)) { rr_edges_to_create.emplace_back(from_rr_node, to_node, wire_to_ipin_switch); ++num_conn; } diff --git a/vpr/src/route/rr_graph2.h b/vpr/src/route/rr_graph2.h index 55791b8d4..6aa5cae68 100644 --- a/vpr/src/route/rr_graph2.h +++ b/vpr/src/route/rr_graph2.h @@ -15,7 +15,7 @@ enum e_seg_details_type { }; struct t_rr_edge_info { - t_rr_edge_info(const RRNodeId& from, const RRNodeId& to, const short& type) noexcept + t_rr_edge_info(RRNodeId from, RRNodeId to, short type) noexcept : from_node(from) , to_node(to) , switch_type(type) {} @@ -25,11 +25,19 @@ struct t_rr_edge_info { short switch_type = OPEN; friend bool operator<(const t_rr_edge_info& lhs, const t_rr_edge_info& rhs) { - return std::tie(size_t(lhs.from_node), size_t(lhs.to_node), lhs.switch_type) < std::tie(size_t(rhs.from_node), size_t(rhs.to_node), rhs.switch_type); + size_t lhs_from_node = size_t(lhs.from_node); + size_t lhs_to_node = size_t(lhs.to_node); + size_t rhs_from_node = size_t(rhs.from_node); + size_t rhs_to_node = size_t(rhs.to_node); + return std::tie(lhs_from_node, lhs_to_node, lhs.switch_type) < std::tie(rhs_from_node, rhs_to_node, rhs.switch_type); } friend bool operator==(const t_rr_edge_info& lhs, const t_rr_edge_info& rhs) { - return std::tie(size_t(lhs.from_node), size_t(lhs.to_node), lhs.switch_type) == std::tie(size_t(rhs.from_node), size_t(rhs.to_node), rhs.switch_type); + size_t lhs_from_node = size_t(lhs.from_node); + size_t lhs_to_node = size_t(lhs.to_node); + size_t rhs_from_node = size_t(rhs.from_node); + size_t rhs_to_node = size_t(rhs.to_node); + return std::tie(lhs_from_node, lhs_to_node, lhs.switch_type) == std::tie(rhs_from_node, rhs_to_node, rhs.switch_type); } }; @@ -145,10 +153,10 @@ bool is_sblock(const int chan, int get_bidir_opin_connections(const int i, const int j, const int ipin, - const int from_rr_node, + const RRNodeId& from_rr_node, t_rr_edge_info_set& rr_edges_to_create, const t_pin_to_track_lookup& opin_to_track_map, - const t_rr_node_indices& L_rr_node_indices, + const RRGraph& rr_graph, const t_chan_details& chan_details_x, const t_chan_details& chan_details_y); diff --git a/vpr/src/route/rr_graph_clock.cpp b/vpr/src/route/rr_graph_clock.cpp index fa9f09e34..3d3a8c27a 100644 --- a/vpr/src/route/rr_graph_clock.cpp +++ b/vpr/src/route/rr_graph_clock.cpp @@ -71,7 +71,7 @@ void ClockRRGraphBuilder::add_rr_switches_and_map_to_nodes(size_t node_start_idx auto& rr_graph = device_ctx.rr_graph; // Check to see that clock nodes were sucessfully appended to rr_nodes - VTR_ASSERT(true == rr_graph.valid_node_id(RRNodeId(node_start_idx)); + VTR_ASSERT(true == rr_graph.valid_node_id(RRNodeId(node_start_idx))); std::unordered_map arch_switch_to_rr_switch; @@ -79,7 +79,7 @@ void ClockRRGraphBuilder::add_rr_switches_and_map_to_nodes(size_t node_start_idx for (size_t node_idx = node_start_idx; node_idx < rr_graph.nodes().size(); node_idx++) { const RRNodeId& from_node = RRNodeId(node_idx); for (const RREdgeId& edge_idx : rr_graph.node_out_edges(from_node)) { - int arch_switch_idx = rr_graph.edge_switch(edge_idx); + int arch_switch_idx = (int)size_t(rr_graph.edge_switch(edge_idx)); int rr_switch_idx; auto itter = arch_switch_to_rr_switch.find(arch_switch_idx); @@ -92,7 +92,7 @@ void ClockRRGraphBuilder::add_rr_switches_and_map_to_nodes(size_t node_start_idx rr_switch_idx = itter->second; } - rr_graph.set_edge_switch(edge_idx, rr_switch_idx); + rr_graph.set_edge_switch(edge_idx, RRSwitchId(rr_switch_idx)); } } @@ -148,7 +148,7 @@ void SwitchPoint::insert_node_idx(int x, int y, const RRNodeId& node_idx) { locations.insert({x, y}); } -std::vector ClockRRGraphBuilder::get_rr_node_indices_at_switch_location(std::string clock_name, +std::vector ClockRRGraphBuilder::get_rr_node_indices_at_switch_location(std::string clock_name, std::string switch_point_name, int x, int y) const { @@ -161,7 +161,7 @@ std::vector ClockRRGraphBuilder::get_rr_node_indices_at_switch_location(std return switch_points.get_rr_node_indices_at_location(switch_point_name, x, y); } -std::vector SwitchPoints::get_rr_node_indices_at_location(std::string switch_point_name, +std::vector SwitchPoints::get_rr_node_indices_at_location(std::string switch_point_name, int x, int y) const { auto itter = switch_point_name_to_switch_location.find(switch_point_name); @@ -170,11 +170,11 @@ std::vector SwitchPoints::get_rr_node_indices_at_location(std::string switc VTR_ASSERT(itter != switch_point_name_to_switch_location.end()); auto& switch_point = itter->second; - std::vector rr_node_indices = switch_point.get_rr_node_indices_at_location(x, y); + std::vector rr_node_indices = switch_point.get_rr_node_indices_at_location(x, y); return rr_node_indices; } -std::vector SwitchPoint::get_rr_node_indices_at_location(int x, int y) const { +std::vector SwitchPoint::get_rr_node_indices_at_location(int x, int y) const { // assert that switch is connected to nodes at the location VTR_ASSERT(!rr_node_indices[x][y].empty()); diff --git a/vpr/src/route/rr_graph_clock.h b/vpr/src/route/rr_graph_clock.h index df6aff1b9..727c9865c 100644 --- a/vpr/src/route/rr_graph_clock.h +++ b/vpr/src/route/rr_graph_clock.h @@ -13,7 +13,7 @@ #include "clock_network_builders.h" #include "clock_connection_builders.h" -#include "rr_graph_obj_fwd.h" +#include "rr_graph_fwd.h" class ClockNetwork; class ClockConnection; @@ -31,12 +31,12 @@ class SwitchPoint { std::set> locations; // x,y public: /** Getters **/ - std::vector get_rr_node_indices_at_location(int x, int y) const; + std::vector get_rr_node_indices_at_location(int x, int y) const; std::set> get_switch_locations() const; /** Setters **/ - void insert_node_idx(int x, int y, int node_idx); + void insert_node_idx(int x, int y, const RRNodeId& node_idx); }; class SwitchPoints { @@ -52,14 +52,14 @@ class SwitchPoints { /* Example: x,y = middle of the chip, switch_point_name == name of main drive * of global clock spine, returns the rr_nodes of all the clock spines that * start the newtork there*/ - std::vector get_rr_node_indices_at_location(std::string switch_point_name, + std::vector get_rr_node_indices_at_location(std::string switch_point_name, int x, int y) const; std::set> get_switch_locations(std::string switch_point_name) const; /** Setters **/ - void insert_switch_node_idx(std::string switch_point_name, int x, int y, int node_idx); + void insert_switch_node_idx(std::string switch_point_name, int x, int y, const RRNodeId& node_idx); }; class ClockRRGraphBuilder { @@ -80,10 +80,10 @@ class ClockRRGraphBuilder { std::string switch_point_name, int x, int y, - int node_index); + const RRNodeId& node_index); /* Returns the rr_node idx of the switch at location {x, y} */ - std::vector get_rr_node_indices_at_switch_location(std::string clock_name, + std::vector get_rr_node_indices_at_switch_location(std::string clock_name, std::string switch_point_name, int x, int y) const; diff --git a/vpr/src/route/rr_graph_indexed_data.cpp b/vpr/src/route/rr_graph_indexed_data.cpp index bc73a1511..3982bcb64 100644 --- a/vpr/src/route/rr_graph_indexed_data.cpp +++ b/vpr/src/route/rr_graph_indexed_data.cpp @@ -347,7 +347,7 @@ static void load_rr_indexed_data_T_values(int index_start, RRNodeId to_node_index = device_ctx.rr_graph.edge_sink_node(iedge); /* want to get C/R/Tdel/Cinternal of switches that connect this track segment to other track segments */ if (device_ctx.rr_graph.node_type(to_node_index) == CHANX || device_ctx.rr_graph.node_type(to_node_index) == CHANY) { - int switch_index = device_ctx.rr_graph.edge_switch(iedge); + int switch_index = (int)size_t(device_ctx.rr_graph.edge_switch(iedge)); avg_switch_R += device_ctx.rr_switch_inf[switch_index].R; avg_switch_T += device_ctx.rr_switch_inf[switch_index].Tdel; avg_switch_Cinternal += device_ctx.rr_switch_inf[switch_index].Cinternal; diff --git a/vpr/src/route/rr_graph_reader.cpp b/vpr/src/route/rr_graph_reader.cpp index ccaf6af1b..51dff1bfe 100644 --- a/vpr/src/route/rr_graph_reader.cpp +++ b/vpr/src/route/rr_graph_reader.cpp @@ -44,6 +44,9 @@ #include "vpr_utils.h" #include "vpr_error.h" +#include "rr_graph_obj.h" +#include "check_rr_graph_obj.h" + #include "rr_graph_reader.h" /*********************** Subroutines local to this module *******************/ @@ -281,7 +284,7 @@ void process_seg_id(pugi::xml_node parent, const pugiutil::loc_data& loc_data) { while (rr_node) { id = get_attribute(rr_node, "id", loc_data).as_int(); - auto& node = RRNodeId(id); + RRNodeId node = RRNodeId(id); segmentSubnode = get_single_child(rr_node, "segment", loc_data, pugiutil::OPTIONAL); if (segmentSubnode) { @@ -310,18 +313,18 @@ void process_nodes(pugi::xml_node parent, const pugiutil::loc_data& loc_data) { int inode = get_attribute(rr_node, "id", loc_data).as_int(); t_rr_type node_type = NUM_RR_TYPES; - const char* node_type = get_attribute(rr_node, "type", loc_data).as_string(); - if (strcmp(node_type, "CHANX") == 0) { + const char* node_type_str = get_attribute(rr_node, "type", loc_data).as_string(); + if (strcmp(node_type_str, "CHANX") == 0) { node_type = CHANX; - } else if (strcmp(node_type, "CHANY") == 0) { + } else if (strcmp(node_type_str, "CHANY") == 0) { node_type = CHANY; - } else if (strcmp(node_type, "SOURCE") == 0) { + } else if (strcmp(node_type_str, "SOURCE") == 0) { node_type = SOURCE; - } else if (strcmp(node_type, "SINK") == 0) { + } else if (strcmp(node_type_str, "SINK") == 0) { node_type = SINK; - } else if (strcmp(node_type, "OPIN") == 0) { + } else if (strcmp(node_type_str, "OPIN") == 0) { node_type = OPIN; - } else if (strcmp(node_type, "IPIN") == 0) { + } else if (strcmp(node_type_str, "IPIN") == 0) { node_type = IPIN; } else { VPR_FATAL_ERROR(VPR_ERROR_OTHER, @@ -371,7 +374,7 @@ void process_nodes(pugi::xml_node parent, const pugiutil::loc_data& loc_data) { device_ctx.rr_graph.set_node_side(node, side); } - device_ctx.rr_graph.set_node_bouding_box(node, vtr::Rect(x1, y1, x2, y2)); + device_ctx.rr_graph.set_node_bounding_box(node, vtr::Rect(x1, y1, x2, y2)); device_ctx.rr_graph.set_node_ptc_num(node, get_attribute(locSubnode, "ptc", loc_data).as_int()); //------- @@ -495,7 +498,7 @@ void process_edges(pugi::xml_node parent, const pugiutil::loc_data& loc_data, in while (edges_meta) { auto key = get_attribute(edges_meta, "name", loc_data).as_string(); - vpr::add_rr_edge_metadata(source_node, sink_node, switch_id, + vpr::add_rr_edge_metadata(size_t(source_node), size_t(sink_node), switch_id, key, edges_meta.child_value()); edges_meta = edges_meta.next_sibling(edges_meta.name()); @@ -899,7 +902,7 @@ void set_cost_indices(pugi::xml_node parent, const pugiutil::loc_data& loc_data, if (attribute) { int seg_id = get_attribute(segmentSubnode, "segment_id", loc_data).as_int(0); if (is_global_graph) { - device_ctx.rr_graph.set_node_cost_index(0); + device_ctx.rr_graph.set_node_cost_index(inode, 0); } else if (device_ctx.rr_graph.node_type(inode) == CHANX) { device_ctx.rr_graph.set_node_cost_index(inode, CHANX_COST_INDEX_START + seg_id); } else if (device_ctx.rr_graph.node_type(inode) == CHANY) { From 6bd71f198e4405c643bf8013d8c86ef805b6b8ae Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 3 Feb 2020 21:05:50 -0700 Subject: [PATCH 091/645] keep debugging the rr_graph generator. Definitely should rework the RREdge creation functions --- vpr/src/device/rr_graph_obj.cpp | 14 ++-- vpr/src/device/rr_graph_obj.h | 4 +- vpr/src/route/router_delay_profiling.h | 2 +- vpr/src/route/rr_graph.cpp | 88 +++++++++++++++++++++++--- 4 files changed, 92 insertions(+), 16 deletions(-) diff --git a/vpr/src/device/rr_graph_obj.cpp b/vpr/src/device/rr_graph_obj.cpp index 37ab22729..588edb17e 100644 --- a/vpr/src/device/rr_graph_obj.cpp +++ b/vpr/src/device/rr_graph_obj.cpp @@ -370,21 +370,24 @@ RRNodeId RRGraph::find_node(const short& x, const short& y, const t_rr_type& typ /* Check if x, y, type and ptc, side is valid */ if ((x < 0) /* See if x is smaller than the index of first element */ - || (size_t(x) > node_lookup_.dim_size(0) - 1)) { /* See if x is large than the index of last element */ + || (size_t(x) > node_lookup_.dim_size(0) - 1) /* See if x is large than the index of last element */ + || (0 == node_lookup_.dim_size(0))) { /* See if x is large than the index of last element */ /* Return a zero range! */ return RRNodeId::INVALID(); } /* Check if x, y, type and ptc, side is valid */ if ((y < 0) /* See if y is smaller than the index of first element */ - || (size_t(y) > node_lookup_.dim_size(1) - 1)) { /* See if y is large than the index of last element */ + || (size_t(y) > node_lookup_.dim_size(1) - 1) /* See if y is large than the index of last element */ + || (0 == node_lookup_.dim_size(1))) { /* See if y is large than the index of last element */ /* Return a zero range! */ return RRNodeId::INVALID(); } /* Check if x, y, type and ptc, side is valid */ /* itype is always larger than -1, we can skip checking */ - if (itype > node_lookup_.dim_size(2) - 1) { /* See if type is large than the index of last element */ + if ( (itype > node_lookup_.dim_size(2) - 1) /* See if type is large than the index of last element */ + || (0 == node_lookup_.dim_size(2))) { /* See if type is large than the index of last element */ /* Return a zero range! */ return RRNodeId::INVALID(); } @@ -399,7 +402,8 @@ RRNodeId RRGraph::find_node(const short& x, const short& y, const t_rr_type& typ /* Check if x, y, type and ptc, side is valid */ /* iside is always larger than -1, we can skip checking */ - if (iside > node_lookup_[x][y][type][ptc].size() - 1) { /* See if side is large than the index of last element */ + if ((iside > node_lookup_[x][y][type][ptc].size() - 1) /* See if side is large than the index of last element */ + || (0 == node_lookup_[x][y][type][ptc].size()) ) { /* See if side is large than the index of last element */ /* Return a zero range! */ return RRNodeId::INVALID(); } @@ -857,7 +861,7 @@ RRNodeId RRGraph::create_node(const t_rr_type& type) { RREdgeId RRGraph::create_edge(const RRNodeId& source, const RRNodeId& sink, const RRSwitchId& switch_id) { VTR_ASSERT(valid_node_id(source)); VTR_ASSERT(valid_node_id(sink)); - //VTR_ASSERT(valid_switch_id(switch_id)); + VTR_ASSERT(valid_switch_id(switch_id)); /* Allocate an ID */ RREdgeId edge_id = RREdgeId(num_edges_); diff --git a/vpr/src/device/rr_graph_obj.h b/vpr/src/device/rr_graph_obj.h index fedc90612..a81a5b301 100644 --- a/vpr/src/device/rr_graph_obj.h +++ b/vpr/src/device/rr_graph_obj.h @@ -725,6 +725,9 @@ class RRGraph { /* top-level function to free, should be called when to delete a RRGraph */ void clear(); + + /* Due to the rr_graph builder, we have to make this method public!!!! */ + void clear_switches(); public: /* Type implementations */ /* @@ -776,7 +779,6 @@ class RRGraph { private: /* Internal free functions */ void clear_nodes(); void clear_edges(); - void clear_switches(); void clear_segments(); private: /* Graph Compression related */ diff --git a/vpr/src/route/router_delay_profiling.h b/vpr/src/route/router_delay_profiling.h index 63b8bed65..0bd5e3296 100644 --- a/vpr/src/route/router_delay_profiling.h +++ b/vpr/src/route/router_delay_profiling.h @@ -15,7 +15,7 @@ class RouterDelayProfiler { const RouterLookahead* router_lookahead_; }; -vtr::vector calculate_all_path_delays_from_rr_node(int src_rr_node, const t_router_opts& router_opts); +vtr::vector calculate_all_path_delays_from_rr_node(const RRNodeId& src_rr_node, const t_router_opts& router_opts); void alloc_routing_structs(t_chan_width chan_width, const t_router_opts& router_opts, diff --git a/vpr/src/route/rr_graph.cpp b/vpr/src/route/rr_graph.cpp index 6e5ded8b1..a801098c6 100644 --- a/vpr/src/route/rr_graph.cpp +++ b/vpr/src/route/rr_graph.cpp @@ -251,6 +251,11 @@ static void alloc_and_load_rr_switch_inf(const int num_arch_switches, const int wire_to_arch_ipin_switch, int* wire_to_rr_ipin_switch); +static +t_rr_switch_inf create_rr_switch_from_arch_switch(int arch_switch_idx, + const float R_minW_nmos, + const float R_minW_pmos); + static void remap_rr_node_switch_indices(const t_arch_switch_fanin& switch_fanin); static void load_rr_switch_inf(const int num_arch_switches, const float R_minW_nmos, const float R_minW_pmos, const t_arch_switch_fanin& switch_fanin); @@ -704,6 +709,20 @@ static void build_rr_graph(const t_graph_type graph_type, /* END OPIN MAP */ bool Fc_clipped = false; + + /* Draft the switches as internal data of RRGraph object + * These are temporary switches copied from arch switches + * We use them to build the edges + * We will reset all the switches in the function + * alloc_and_load_rr_switch_inf() + */ + device_ctx.rr_graph.reserve_switches(device_ctx.num_arch_switches); + // Create the switches + for (size_t iswitch = 0; iswitch < device_ctx.num_arch_switches; ++iswitch) { + const t_rr_switch_inf& temp_rr_switch = create_rr_switch_from_arch_switch(iswitch, R_minW_nmos, R_minW_pmos); + device_ctx.rr_graph.create_switch(temp_rr_switch); + } + alloc_and_load_rr_graph(device_ctx.rr_graph.nodes().size(), device_ctx.rr_graph, segment_inf.size(), chan_details_x, chan_details_y, track_to_pin_lookup, opin_to_track_map, @@ -732,13 +751,34 @@ static void build_rr_graph(const t_graph_type graph_type, } } + /* First time to build edges so that we can remap the architecture switch to rr_switch + * This is a must-do before function alloc_and_load_rr_switch_inf() + */ + device_ctx.rr_graph.rebuild_node_edges(); + /* Allocate and load routing resource switches, which are derived from the switches from the architecture file, * based on their fanin in the rr graph. This routine also adjusts the rr nodes to point to these new rr switches */ alloc_and_load_rr_switch_inf(num_arch_switches, R_minW_nmos, R_minW_pmos, wire_to_arch_ipin_switch, wire_to_rr_ipin_switch); - //Partition the rr graph edges for efficient access to configurable/non-configurable - //edge subsets. Must be done after RR switches have been allocated - device_ctx.rr_graph.rebuild_node_edges(); + //Save the channel widths for the newly constructed graph + device_ctx.chan_width = nodes_per_chan; + + rr_graph_externals(segment_inf, max_chan_width, + *wire_to_rr_ipin_switch, base_cost_type); + + /* Rebuild the link between RRGraph node and segments + * Should be called only AFTER the function + * rr_graph_externals() + */ + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { + if ( (CHANX != device_ctx.rr_graph.node_type(inode)) + && (CHANY != device_ctx.rr_graph.node_type(inode)) ) { + continue; + } + short irc_data = device_ctx.rr_graph.node_cost_index(inode); + short iseg = device_ctx.rr_indexed_data[irc_data].seg_index; + device_ctx.rr_graph.set_node_segment(inode, RRSegmentId(iseg)); + } /* Essential check for rr_graph, build look-up and */ if (false == device_ctx.rr_graph.validate()) { @@ -749,12 +789,6 @@ static void build_rr_graph(const t_graph_type graph_type, "Fundamental errors occurred when validating rr_graph object!\n"); } - //Save the channel widths for the newly constructed graph - device_ctx.chan_width = nodes_per_chan; - - rr_graph_externals(segment_inf, max_chan_width, - *wire_to_rr_ipin_switch, base_cost_type); - check_rr_graph(graph_type, grid, types); /* Error out if advanced checker of rr_graph fails */ if (false == check_rr_graph(device_ctx.rr_graph)) { @@ -922,6 +956,7 @@ static void load_rr_switch_inf(const int num_arch_switches, const float R_minW_n } /* Create switches as internal data of RRGraph object */ + device_ctx.rr_graph.clear_switches(); device_ctx.rr_graph.reserve_switches(device_ctx.rr_switch_inf.size()); // Create the switches for (size_t iswitch = 0; iswitch < device_ctx.rr_switch_inf.size(); ++iswitch) { @@ -930,6 +965,41 @@ static void load_rr_switch_inf(const int num_arch_switches, const float R_minW_n } +static +t_rr_switch_inf create_rr_switch_from_arch_switch(int arch_switch_idx, + const float R_minW_nmos, + const float R_minW_pmos) { + auto& device_ctx = g_vpr_ctx.mutable_device(); + t_rr_switch_inf rr_switch_inf; + + /* figure out, by looking at the arch switch's Tdel map, what the delay of the new + * rr switch should be */ + double rr_switch_Tdel = device_ctx.arch_switch_inf[arch_switch_idx].Tdel(0); + + /* copy over the arch switch to rr_switch_inf[rr_switch_idx], but with the changed Tdel value */ + rr_switch_inf.set_type(device_ctx.arch_switch_inf[arch_switch_idx].type()); + rr_switch_inf.R = device_ctx.arch_switch_inf[arch_switch_idx].R; + rr_switch_inf.Cin = device_ctx.arch_switch_inf[arch_switch_idx].Cin; + rr_switch_inf.Cinternal = device_ctx.arch_switch_inf[arch_switch_idx].Cinternal; + rr_switch_inf.Cout = device_ctx.arch_switch_inf[arch_switch_idx].Cout; + rr_switch_inf.Tdel = rr_switch_Tdel; + rr_switch_inf.mux_trans_size = device_ctx.arch_switch_inf[arch_switch_idx].mux_trans_size; + if (device_ctx.arch_switch_inf[arch_switch_idx].buf_size_type == BufferSize::AUTO) { + //Size based on resistance + rr_switch_inf.buf_size = trans_per_buf(device_ctx.arch_switch_inf[arch_switch_idx].R, R_minW_nmos, R_minW_pmos); + } else { + VTR_ASSERT(device_ctx.arch_switch_inf[arch_switch_idx].buf_size_type == BufferSize::ABSOLUTE); + //Use the specified size + rr_switch_inf.buf_size = device_ctx.arch_switch_inf[arch_switch_idx].buf_size; + } + rr_switch_inf.name = device_ctx.arch_switch_inf[arch_switch_idx].name; + rr_switch_inf.power_buffer_type = device_ctx.arch_switch_inf[arch_switch_idx].power_buffer_type; + rr_switch_inf.power_buffer_size = device_ctx.arch_switch_inf[arch_switch_idx].power_buffer_size; + + return rr_switch_inf; +} + + void load_rr_switch_from_arch_switch(int arch_switch_idx, int rr_switch_idx, int fanin, From b6a2013565cd12106bf8ae41ac9af89fc507151d Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 3 Feb 2020 21:50:02 -0700 Subject: [PATCH 092/645] minor bug fix for RRGraph data structure --- vpr/src/device/rr_graph_obj.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/vpr/src/device/rr_graph_obj.cpp b/vpr/src/device/rr_graph_obj.cpp index 588edb17e..cf616538c 100644 --- a/vpr/src/device/rr_graph_obj.cpp +++ b/vpr/src/device/rr_graph_obj.cpp @@ -885,6 +885,7 @@ RREdgeId RRGraph::create_edge(const RRNodeId& source, const RRNodeId& sink, cons void RRGraph::set_edge_switch(const RREdgeId& edge, const RRSwitchId& switch_id) { VTR_ASSERT(valid_edge_id(edge)); + VTR_ASSERT(valid_switch_id(switch_id)); edge_switches_[edge] = switch_id; } From 15167c9bfb42dc1ea2b5b90350956e2e708aafc2 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 4 Feb 2020 11:37:59 -0700 Subject: [PATCH 093/645] bug fixing for building routing channels in build_rr_graph() --- vpr/src/device/rr_graph_obj.cpp | 11 +++++-- vpr/src/route/rr_graph.cpp | 51 ++++++++++++++++++++------------- vpr/src/route/rr_graph2.cpp | 7 +++-- 3 files changed, 44 insertions(+), 25 deletions(-) diff --git a/vpr/src/device/rr_graph_obj.cpp b/vpr/src/device/rr_graph_obj.cpp index cf616538c..74ee29667 100644 --- a/vpr/src/device/rr_graph_obj.cpp +++ b/vpr/src/device/rr_graph_obj.cpp @@ -68,19 +68,21 @@ short RRGraph::node_xhigh(const RRNodeId& node) const { /* Special for SOURCE and SINK node, we always return the xlow * This is due to the convention in creating RRGraph * so that we can guarantee unique SOURCE/SINK nodes searching - */ if ( (SOURCE == node_type(node)) || (SINK == node_type(node)) ) { return node_bounding_box(node).xmin(); } + */ return node_bounding_box(node).xmax(); } short RRGraph::node_yhigh(const RRNodeId& node) const { + /* if ( (SOURCE == node_type(node)) || (SINK == node_type(node)) ) { return node_bounding_box(node).ymin(); } + */ return node_bounding_box(node).ymax(); } @@ -1229,6 +1231,7 @@ void RRGraph::build_fast_node_lookup() const { std::vector xlows; std::vector ylows; + /* if ( (SOURCE == node_type(node)) || (SINK == node_type(node)) || (CHANX == node_type(node)) @@ -1237,13 +1240,15 @@ void RRGraph::build_fast_node_lookup() const { ylows.resize(node_bounding_boxes_[node].ymax() - node_bounding_boxes_[node].ymin() + 1); std::iota(xlows.begin(), xlows.end(), node_xlow(node)); std::iota(ylows.begin(), ylows.end(), node_ylow(node)); - /* Sanity check */ VTR_ASSERT(size_t(node_bounding_boxes_[node].xmax()) == xlows.back()); VTR_ASSERT(size_t(node_bounding_boxes_[node].ymax()) == ylows.back()); - } else { + } else { + */ xlows.push_back(node_xlow(node)); ylows.push_back(node_ylow(node)); + /* } + */ for (size_t x : xlows) { for (size_t y : ylows) { diff --git a/vpr/src/route/rr_graph.cpp b/vpr/src/route/rr_graph.cpp index a801098c6..b24c2f6ef 100644 --- a/vpr/src/route/rr_graph.cpp +++ b/vpr/src/route/rr_graph.cpp @@ -718,7 +718,7 @@ static void build_rr_graph(const t_graph_type graph_type, */ device_ctx.rr_graph.reserve_switches(device_ctx.num_arch_switches); // Create the switches - for (size_t iswitch = 0; iswitch < device_ctx.num_arch_switches; ++iswitch) { + for (int iswitch = 0; iswitch < device_ctx.num_arch_switches; ++iswitch) { const t_rr_switch_inf& temp_rr_switch = create_rr_switch_from_arch_switch(iswitch, R_minW_nmos, R_minW_pmos); device_ctx.rr_graph.create_switch(temp_rr_switch); } @@ -1864,26 +1864,17 @@ void alloc_and_load_edges(RRGraph& rr_graph, size_t edge_count = std::distance(edge_range.first, edge_range.second); - if (rr_graph.node_out_edges(inode).size() == 0) { - //Create initial edges - // - //Note that we do this in bulk instead of via add_edge() to reduce - //memory fragmentation + //Create initial edges + // + //Note that we do this in bulk instead of via add_edge() to reduce + //memory fragmentation - rr_graph.reserve_edges(edge_count + rr_graph.edges().size()); + rr_graph.reserve_edges(edge_count + rr_graph.edges().size()); - for (auto itr = edge_range.first; itr != edge_range.second; ++itr) { - VTR_ASSERT(itr->from_node == inode); + for (auto itr = edge_range.first; itr != edge_range.second; ++itr) { + VTR_ASSERT(itr->from_node == inode); - rr_graph.create_edge(inode, itr->to_node, RRSwitchId(itr->switch_type)); - } - } else { - //Add new edge incrementally - // - //This should occur relatively rarely (e.g. a backward bidir edge) so memory fragmentation shouldn't be a big problem - for (auto itr = edge_range.first; itr != edge_range.second; ++itr) { - rr_graph.create_edge(inode, itr->to_node, RRSwitchId(itr->switch_type)); - } + rr_graph.create_edge(inode, itr->to_node, RRSwitchId(itr->switch_type)); } } } @@ -2672,7 +2663,23 @@ std::string describe_rr_node(const RRNodeId& inode) { return msg; } -static void build_unidir_rr_opins(const int i, const int j, const e_side side, const DeviceGrid& grid, const std::vector>& Fc_out, const int max_chan_width, const t_chan_details& chan_details_x, const t_chan_details& chan_details_y, vtr::NdMatrix& Fc_xofs, vtr::NdMatrix& Fc_yofs, t_rr_edge_info_set& rr_edges_to_create, bool* Fc_clipped, const t_rr_node_indices& L_rr_node_indices, const RRGraph& rr_graph, const t_direct_inf* directs, const int num_directs, const t_clb_to_clb_directs* clb_to_clb_directs, const int num_seg_types) { +static void build_unidir_rr_opins(const int i, const int j, + const e_side side, + const DeviceGrid& grid, + const std::vector>& Fc_out, + const int max_chan_width, + const t_chan_details& chan_details_x, + const t_chan_details& chan_details_y, + vtr::NdMatrix& Fc_xofs, + vtr::NdMatrix& Fc_yofs, + t_rr_edge_info_set& rr_edges_to_create, + bool* Fc_clipped, + const t_rr_node_indices& L_rr_node_indices, + const RRGraph& rr_graph, + const t_direct_inf* directs, + const int num_directs, + const t_clb_to_clb_directs* clb_to_clb_directs, + const int num_seg_types) { /* * This routine adds the edges from opins to channels at the specified * grid location (i,j) and grid tile side @@ -2696,7 +2703,11 @@ static void build_unidir_rr_opins(const int i, const int j, const e_side side, c } RRNodeId opin_node_index = rr_graph.find_node(i, j, OPIN, pin_index, side); - if (false == rr_graph.valid_node_id(opin_node_index)) continue; //No valid from node + //if (false == rr_graph.valid_node_id(opin_node_index)) continue; //No valid from node + + if (1 == type->pinloc[width_offset][height_offset][side][pin_index]) { + VTR_ASSERT(true == rr_graph.valid_node_id(opin_node_index)); + } for (int iseg = 0; iseg < num_seg_types; iseg++) { /* get Fc for this segment type */ diff --git a/vpr/src/route/rr_graph2.cpp b/vpr/src/route/rr_graph2.cpp index 21d69f296..ede1fdff5 100644 --- a/vpr/src/route/rr_graph2.cpp +++ b/vpr/src/route/rr_graph2.cpp @@ -809,8 +809,8 @@ int get_unidir_opin_connections(const int chan, dec_track = dec_muxes[dec_mux]; /* Figure the inodes of those muxes */ - inc_inode_index = rr_graph.find_node(chan, seg, chan_type, inc_track); - dec_inode_index = rr_graph.find_node(chan, seg, chan_type, dec_track); + inc_inode_index = rr_graph.find_node(x, y, chan_type, inc_track); + dec_inode_index = rr_graph.find_node(x, y, chan_type, dec_track); if (inc_inode_index == RRNodeId::INVALID() || dec_inode_index == RRNodeId::INVALID()) { continue; @@ -1078,6 +1078,9 @@ static void load_chan_rr_indices(const int max_chan_width, /* We give a fake coordinator here, to ease the downstream builder */ short xlow = chan; short ylow = start; + if (CHANX == type) { + std::swap(xlow, ylow); + } RRNodeId node = rr_graph.find_node(xlow, ylow, type, track); if (false == rr_graph.valid_node_id(node)) { From 688186350650f5b170010af4448e5f6e9a006e14 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 4 Feb 2020 15:21:45 -0700 Subject: [PATCH 094/645] keep debugging rr_graph builder --- vpr/src/device/rr_graph_obj.cpp | 79 ++++------- vpr/src/device/rr_graph_obj.h | 2 + vpr/src/route/rr_graph.cpp | 81 ++++++----- vpr/src/route/rr_graph2.cpp | 244 +++++++++++++++++++++----------- vpr/src/route/rr_graph2.h | 30 +++- 5 files changed, 260 insertions(+), 176 deletions(-) diff --git a/vpr/src/device/rr_graph_obj.cpp b/vpr/src/device/rr_graph_obj.cpp index 74ee29667..1b738d212 100644 --- a/vpr/src/device/rr_graph_obj.cpp +++ b/vpr/src/device/rr_graph_obj.cpp @@ -65,24 +65,10 @@ short RRGraph::node_ylow(const RRNodeId& node) const { } short RRGraph::node_xhigh(const RRNodeId& node) const { - /* Special for SOURCE and SINK node, we always return the xlow - * This is due to the convention in creating RRGraph - * so that we can guarantee unique SOURCE/SINK nodes searching - if ( (SOURCE == node_type(node)) - || (SINK == node_type(node)) ) { - return node_bounding_box(node).xmin(); - } - */ return node_bounding_box(node).xmax(); } short RRGraph::node_yhigh(const RRNodeId& node) const { - /* - if ( (SOURCE == node_type(node)) - || (SINK == node_type(node)) ) { - return node_bounding_box(node).ymin(); - } - */ return node_bounding_box(node).ymax(); } @@ -966,6 +952,12 @@ void RRGraph::remove_edge(const RREdgeId& edge) { set_dirty(); } +void RRGraph::set_node_type(const RRNodeId& node, const t_rr_type& type) { + VTR_ASSERT(valid_node_id(node)); + + node_types_[node] = type; +} + void RRGraph::set_node_xlow(const RRNodeId& node, const short& xlow) { VTR_ASSERT(valid_node_id(node)); @@ -1228,52 +1220,29 @@ void RRGraph::build_fast_node_lookup() const { /* Special for SOURCE and SINK, we should annotate in the look-up * for all the (x,y) upto (xhigh, yhigh) */ - std::vector xlows; - std::vector ylows; + size_t x = node_xlow(node); + size_t y = node_ylow(node); - /* - if ( (SOURCE == node_type(node)) - || (SINK == node_type(node)) - || (CHANX == node_type(node)) - || (CHANY == node_type(node)) ) { - xlows.resize(node_bounding_boxes_[node].xmax() - node_bounding_boxes_[node].xmin() + 1); - ylows.resize(node_bounding_boxes_[node].ymax() - node_bounding_boxes_[node].ymin() + 1); - std::iota(xlows.begin(), xlows.end(), node_xlow(node)); - std::iota(ylows.begin(), ylows.end(), node_ylow(node)); - VTR_ASSERT(size_t(node_bounding_boxes_[node].xmax()) == xlows.back()); - VTR_ASSERT(size_t(node_bounding_boxes_[node].ymax()) == ylows.back()); + size_t itype = node_type(node); + + size_t ptc = node_ptc_num(node); + if (ptc >= node_lookup_[x][y][itype].size()) { + node_lookup_[x][y][itype].resize(ptc + 1); + } + + size_t iside = -1; + if (node_type(node) == OPIN || node_type(node) == IPIN) { + iside = node_side(node); } else { - */ - xlows.push_back(node_xlow(node)); - ylows.push_back(node_ylow(node)); - /* + iside = NUM_SIDES; } - */ - for (size_t x : xlows) { - for (size_t y : ylows) { - size_t itype = node_type(node); - - size_t ptc = node_ptc_num(node); - if (ptc >= node_lookup_[x][y][itype].size()) { - node_lookup_[x][y][itype].resize(ptc + 1); - } - - size_t iside = -1; - if (node_type(node) == OPIN || node_type(node) == IPIN) { - iside = node_side(node); - } else { - iside = NUM_SIDES; - } - - if (iside >= node_lookup_[x][y][itype][ptc].size()) { - node_lookup_[x][y][itype][ptc].resize(iside + 1); - } - - //Save node in lookup - node_lookup_[x][y][itype][ptc][iside] = node; - } + if (iside >= node_lookup_[x][y][itype][ptc].size()) { + node_lookup_[x][y][itype][ptc].resize(iside + 1); } + + //Save node in lookup + node_lookup_[x][y][itype][ptc][iside] = node; } } diff --git a/vpr/src/device/rr_graph_obj.h b/vpr/src/device/rr_graph_obj.h index a81a5b301..fd8439868 100644 --- a/vpr/src/device/rr_graph_obj.h +++ b/vpr/src/device/rr_graph_obj.h @@ -646,6 +646,8 @@ class RRGraph { void remove_edge(const RREdgeId& edge); /* Set node-level information */ + void set_node_type(const RRNodeId& node, const t_rr_type& type); + void set_node_xlow(const RRNodeId& node, const short& xlow); void set_node_ylow(const RRNodeId& node, const short& ylow); void set_node_xhigh(const RRNodeId& node, const short& xhigh); diff --git a/vpr/src/route/rr_graph.cpp b/vpr/src/route/rr_graph.cpp index b24c2f6ef..f9a87bfea 100644 --- a/vpr/src/route/rr_graph.cpp +++ b/vpr/src/route/rr_graph.cpp @@ -153,8 +153,7 @@ static int get_opin_direct_connections(int x, const int num_directs, const t_clb_to_clb_directs* clb_to_clb_directs); -static void alloc_and_load_rr_graph(const int num_nodes, - RRGraph& rr_graph, +static void alloc_and_load_rr_graph(RRGraph& rr_graph, const int num_seg_types, const t_chan_details& chan_details_x, const t_chan_details& chan_details_y, @@ -599,10 +598,19 @@ static void build_rr_graph(const t_graph_type graph_type, int num_rr_nodes = 0; /* Xifan Tang - - * We create all the nodes in the RRGraph object here + * Reuse the legacy rr_node indice because it has many out-of-law exceptions during the graph building + * which is not allowed by RRGraph object */ - device_ctx.rr_graph = alloc_and_load_rr_node_indices(max_chan_width, grid, - &num_rr_nodes, chan_details_x, chan_details_y); + t_rr_node_indices L_rr_node_indices = alloc_and_load_rr_node_indices(max_chan_width, grid, + &num_rr_nodes, chan_details_x, chan_details_y); + + /* Allocate the nodes in RR Graph */ + device_ctx.rr_graph.reserve_nodes(num_rr_nodes); + for (int i = 0; i < num_rr_nodes; ++i) { + /* Give a fake node type, will be corrected later in the builder */ + device_ctx.rr_graph.create_node(SOURCE); + } + /* The number of segments are in general small, reserve segments may not bring * significant memory efficiency */ @@ -723,11 +731,11 @@ static void build_rr_graph(const t_graph_type graph_type, device_ctx.rr_graph.create_switch(temp_rr_switch); } - alloc_and_load_rr_graph(device_ctx.rr_graph.nodes().size(), device_ctx.rr_graph, segment_inf.size(), + alloc_and_load_rr_graph(device_ctx.rr_graph, segment_inf.size(), chan_details_x, chan_details_y, track_to_pin_lookup, opin_to_track_map, switch_block_conn, sb_conn_map, grid, Fs, unidir_sb_pattern, - Fc_out, Fc_xofs, Fc_yofs, device_ctx.rr_node_indices, + Fc_out, Fc_xofs, Fc_yofs, L_rr_node_indices, max_chan_width, nodes_per_chan, wire_to_arch_ipin_switch, @@ -820,6 +828,8 @@ static void build_rr_graph(const t_graph_type graph_type, if (clb_to_clb_directs != nullptr) { free(clb_to_clb_directs); } + + L_rr_node_indices.clear(); } /* Allocates and loads the global rr_switch_inf array based on the global @@ -1292,8 +1302,7 @@ static void free_type_track_to_pin_map(t_track_to_pin_lookup& track_to_pin_map, /* Does the actual work of allocating the rr_graph and filling all the * * appropriate values. Everything up to this was just a prelude! */ -static void alloc_and_load_rr_graph(const int num_nodes, - RRGraph& rr_graph, +static void alloc_and_load_rr_graph(RRGraph& rr_graph, const int num_seg_types, const t_chan_details& chan_details_x, const t_chan_details& chan_details_y, @@ -1463,12 +1472,12 @@ static void build_bidir_rr_opins(const int i, total_pin_Fc += Fc[pin_index][iseg]; } - RRNodeId node_index = rr_graph.find_node(i, j, OPIN, pin_index, side); + RRNodeId node_index = RRNodeId(get_rr_node_index(L_rr_node_indices, i, j, OPIN, pin_index, side)); VTR_ASSERT(true == rr_graph.valid_node_id(node_index)); if (total_pin_Fc > 0) { get_bidir_opin_connections(i, j, pin_index, - node_index, rr_edges_to_create, opin_to_track_map, rr_graph, + node_index, rr_edges_to_create, opin_to_track_map, L_rr_node_indices, chan_details_x, chan_details_y); } @@ -1540,7 +1549,7 @@ static void build_rr_sinks_sources(const int i, for (int iclass = 0; iclass < num_class; ++iclass) { RRNodeId inode = RRNodeId::INVALID(); if (class_inf[iclass].type == DRIVER) { /* SOURCE */ - inode = rr_graph.find_node(i, j, SOURCE, iclass); + inode = RRNodeId(get_rr_node_index(L_rr_node_indices, i, j, SOURCE, iclass)); //Retrieve all the physical OPINs associated with this source, this includes //those at different grid tiles of this block @@ -1549,7 +1558,7 @@ static void build_rr_sinks_sources(const int i, for (int height_offset = 0; height_offset < type->height; ++height_offset) { for (int ipin = 0; ipin < class_inf[iclass].num_pins; ++ipin) { int pin_num = class_inf[iclass].pinlist[ipin]; - auto physical_pins = find_rr_graph_nodes(rr_graph, i + width_offset, j + height_offset, OPIN, pin_num); + auto physical_pins = get_rr_graph_node_indices(L_rr_node_indices, i + width_offset, j + height_offset, OPIN, pin_num); opin_nodes.insert(opin_nodes.end(), physical_pins.begin(), physical_pins.end()); } } @@ -1560,10 +1569,11 @@ static void build_rr_sinks_sources(const int i, rr_edges_to_create.emplace_back(inode, opin_nodes[iedge], delayless_switch); } + rr_graph.set_node_type(inode, SOURCE); rr_graph.set_node_cost_index(inode, SOURCE_COST_INDEX); } else { /* SINK */ VTR_ASSERT(class_inf[iclass].type == RECEIVER); - inode = rr_graph.find_node(i, j, SINK, iclass); + inode = get_rr_graph_node_index(L_rr_node_indices, i, j, SINK, iclass); /* NOTE: To allow route throughs through clbs, change the lines below to * * make an edge from the input SINK to the output SOURCE. Do for just the * @@ -1572,7 +1582,8 @@ static void build_rr_sinks_sources(const int i, * base cost of OPINs and/or SOURCES so they aren't used excessively. */ /* Initialize to unconnected */ - rr_graph.set_node_cost_index(RRNodeId(inode), SINK_COST_INDEX); + rr_graph.set_node_type(inode, SINK); + rr_graph.set_node_cost_index(inode, SINK_COST_INDEX); } /* Things common to both SOURCEs and SINKs. */ @@ -1597,24 +1608,29 @@ static void build_rr_sinks_sources(const int i, if (class_inf[iclass].type == RECEIVER) { //Connect the input pin to the sink - inode = rr_graph.find_node(i + width_offset, j + height_offset, IPIN, ipin, side); + inode = RRNodeId(get_rr_node_index(L_rr_node_indices, i + width_offset, j + height_offset, IPIN, ipin, side)); - RRNodeId to_node = rr_graph.find_node(i, j, SINK, iclass); + RRNodeId to_node = RRNodeId(get_rr_node_index(L_rr_node_indices, i, j, SINK, iclass)); + + VTR_ASSERT(true == rr_graph.valid_node_id(inode)); + VTR_ASSERT(true == rr_graph.valid_node_id(to_node)); //Add info about the edge to be created rr_edges_to_create.emplace_back(inode, to_node, delayless_switch); - VTR_ASSERT(true == rr_graph.valid_node_id(inode)); + rr_graph.set_node_type(inode, IPIN); rr_graph.set_node_cost_index(inode, IPIN_COST_INDEX); } else { VTR_ASSERT(class_inf[iclass].type == DRIVER); //Initialize the output pin // Note that we leave it's out-going edges unconnected (they will be hooked up to global routing later) - inode = rr_graph.find_node(i + width_offset, j + height_offset, OPIN, ipin, side); + inode = RRNodeId(get_rr_node_index(L_rr_node_indices, i + width_offset, j + height_offset, OPIN, ipin, side)); + VTR_ASSERT(true == rr_graph.valid_node_id(inode)); //Initially left unconnected + rr_graph.set_node_type(inode, OPIN); rr_graph.set_node_cost_index(inode, OPIN_COST_INDEX); } @@ -1731,7 +1747,7 @@ static void build_rr_chan(const int x_coord, from_seg_details = chan_details_x[start][y_coord].data(); } - RRNodeId node = rr_graph.find_node(x_coord, y_coord, chan_type, track); + RRNodeId node = get_rr_graph_node_index(L_rr_node_indices, x_coord, y_coord, chan_type, track); if (node == RRNodeId::INVALID()) { continue; @@ -1740,7 +1756,7 @@ static void build_rr_chan(const int x_coord, /* Add the edges from this track to all it's connected pins into the list */ int num_edges = 0; num_edges += get_track_to_pins(start, chan_coord, track, tracks_per_chan, node, rr_edges_to_create, - rr_graph, track_to_pin_lookup, seg_details, chan_type, seg_dimension, + L_rr_node_indices, rr_graph, track_to_pin_lookup, seg_details, chan_type, seg_dimension, wire_to_ipin_switch, directionality); /* get edges going from the current track into channel segments which are perpendicular to it */ @@ -1758,7 +1774,7 @@ static void build_rr_chan(const int x_coord, Fs_per_side, sblock_pattern, node, rr_edges_to_create, from_seg_details, to_seg_details, opposite_chan_details, directionality, - rr_graph, + L_rr_node_indices, rr_graph, switch_block_conn, sb_conn_map); } } @@ -1776,7 +1792,7 @@ static void build_rr_chan(const int x_coord, Fs_per_side, sblock_pattern, node, rr_edges_to_create, from_seg_details, to_seg_details, opposite_chan_details, directionality, - rr_graph, + L_rr_node_indices, rr_graph, switch_block_conn, sb_conn_map); } } @@ -1806,13 +1822,14 @@ static void build_rr_chan(const int x_coord, Fs_per_side, sblock_pattern, node, rr_edges_to_create, from_seg_details, to_seg_details, from_chan_details, directionality, - rr_graph, + L_rr_node_indices, rr_graph, switch_block_conn, sb_conn_map); } } } /* Edge arrays have now been built up. Do everything else. */ + rr_graph.set_node_type(node, chan_type); /* GLOBAL routing handled elsewhere */ rr_graph.set_node_cost_index(node, cost_index_offset + seg_details[track].index()); rr_graph.set_node_capacity(node, 1); /* GLOBAL routing handled elsewhere */ @@ -1869,7 +1886,7 @@ void alloc_and_load_edges(RRGraph& rr_graph, //Note that we do this in bulk instead of via add_edge() to reduce //memory fragmentation - rr_graph.reserve_edges(edge_count + rr_graph.edges().size()); + //rr_graph.reserve_edges(edge_count + rr_graph.edges().size()); for (auto itr = edge_range.first; itr != edge_range.second; ++itr) { VTR_ASSERT(itr->from_node == inode); @@ -2702,12 +2719,8 @@ static void build_unidir_rr_opins(const int i, const int j, continue; } - RRNodeId opin_node_index = rr_graph.find_node(i, j, OPIN, pin_index, side); - //if (false == rr_graph.valid_node_id(opin_node_index)) continue; //No valid from node - - if (1 == type->pinloc[width_offset][height_offset][side][pin_index]) { - VTR_ASSERT(true == rr_graph.valid_node_id(opin_node_index)); - } + RRNodeId opin_node_index = get_rr_graph_node_index(L_rr_node_indices, i, j, OPIN, pin_index, side); + if (false == rr_graph.valid_node_id(opin_node_index)) continue; //No valid from node for (int iseg = 0; iseg < num_seg_types; iseg++) { /* get Fc for this segment type */ @@ -2760,7 +2773,7 @@ static void build_unidir_rr_opins(const int i, const int j, opin_node_index, rr_edges_to_create, Fc_ofs, max_len, max_chan_width, - rr_graph, &clipped); + L_rr_node_indices, &clipped); if (clipped) { *Fc_clipped = true; } @@ -3007,8 +3020,8 @@ static int get_opin_direct_connections(int x, } } else { //No side specified, get all candidates - inodes = find_rr_graph_nodes(rr_graph, x + directs[i].x_offset, y + directs[i].y_offset, - IPIN, ipin); + inodes = get_rr_graph_node_indices(L_rr_node_indices, x + directs[i].x_offset, y + directs[i].y_offset, + IPIN, ipin); } if (inodes.size() > 0) { diff --git a/vpr/src/route/rr_graph2.cpp b/vpr/src/route/rr_graph2.cpp index ede1fdff5..6317b53b7 100644 --- a/vpr/src/route/rr_graph2.cpp +++ b/vpr/src/route/rr_graph2.cpp @@ -31,14 +31,15 @@ static void load_chan_rr_indices(const int max_chan_width, const int num_chans, const t_rr_type type, const t_chan_details& chan_details, - RRGraph& rr_graph, + t_rr_node_indices& indices, int* index); static void load_block_rr_indices(const DeviceGrid& grid, - RRGraph& rr_graph, + t_rr_node_indices& indices, int* index); static int get_bidir_track_to_chan_seg(const std::vector conn_tracks, + const t_rr_node_indices& L_rr_node_indices, const RRGraph& rr_graph, const int to_chan, const int to_seg, @@ -64,6 +65,7 @@ static int get_unidir_track_to_chan_seg(const int from_track, const int Fs_per_side, t_sblock_pattern& sblock_pattern, const int switch_override, + const t_rr_node_indices& L_rr_node_indices, const RRGraph& rr_graph, const t_chan_seg_details* seg_details, bool* Fs_clipped, @@ -77,6 +79,7 @@ static int get_track_to_chan_seg(const int from_track, const e_side from_side, const e_side to_side, const int swtich_override, + const t_rr_node_indices& L_rr_node_indices, const RRGraph& rr_graph, t_sb_connection_map* sb_conn_map, const RRNodeId& from_rr_node, @@ -667,7 +670,7 @@ int get_bidir_opin_connections(const int i, const RRNodeId& from_rr_node, t_rr_edge_info_set& rr_edges_to_create, const t_pin_to_track_lookup& opin_to_track_map, - const RRGraph& rr_graph, + const t_rr_node_indices& L_rr_node_indices, const t_chan_details& chan_details_x, const t_chan_details& chan_details_y) { int num_conn, tr_i, tr_j, chan, seg; @@ -731,7 +734,7 @@ int get_bidir_opin_connections(const int i, /* Only connect to wire if there is a CB */ if (is_cblock(chan, seg, to_track, seg_details)) { to_switch = seg_details[to_track].arch_wire_switch(); - to_node = rr_graph.find_node(tr_i, tr_j, to_type, to_track); + to_node = RRNodeId(get_rr_node_index(L_rr_node_indices, tr_i, tr_j, to_type, to_track)); if (to_node == RRNodeId::INVALID()) { continue; @@ -757,7 +760,7 @@ int get_unidir_opin_connections(const int chan, vtr::NdMatrix& Fc_ofs, const int max_len, const int max_chan_width, - const RRGraph& rr_graph, + const t_rr_node_indices& L_rr_node_indices, bool* Fc_clipped) { /* Gets a linked list of Fc nodes of specified seg_type_index to connect * to in given chan seg. Fc_ofs is used for the opin staggering pattern. */ @@ -809,8 +812,8 @@ int get_unidir_opin_connections(const int chan, dec_track = dec_muxes[dec_mux]; /* Figure the inodes of those muxes */ - inc_inode_index = rr_graph.find_node(x, y, chan_type, inc_track); - dec_inode_index = rr_graph.find_node(x, y, chan_type, dec_track); + inc_inode_index = get_rr_graph_node_index(L_rr_node_indices, x, y, chan_type, inc_track); + dec_inode_index = get_rr_graph_node_index(L_rr_node_indices, x, y, chan_type, dec_track); if (inc_inode_index == RRNodeId::INVALID() || dec_inode_index == RRNodeId::INVALID()) { continue; @@ -1059,8 +1062,20 @@ static void load_chan_rr_indices(const int max_chan_width, const int num_chans, const t_rr_type type, const t_chan_details& chan_details, - RRGraph& rr_graph, + t_rr_node_indices& indices, int* index) { + VTR_ASSERT(indices[type].size() == size_t(num_chans)); + for (int chan = 0; chan < num_chans - 1; ++chan) { + VTR_ASSERT(indices[type][chan].size() == size_t(chan_len)); + + for (int seg = 1; seg < chan_len - 1; ++seg) { + VTR_ASSERT(indices[type][chan][seg].size() == NUM_SIDES); + + /* Alloc the track inode lookup list */ + //Since channels have no side, we just use the first side + indices[type][chan][seg][0].resize(max_chan_width, OPEN); + } + } for (int chan = 0; chan < num_chans - 1; ++chan) { for (int seg = 1; seg < chan_len - 1; ++seg) { @@ -1069,34 +1084,32 @@ static void load_chan_rr_indices(const int max_chan_width, int y = (type == CHANX ? chan : seg); const t_chan_seg_details* seg_details = chan_details[x][y].data(); - for (int track = 0; track < num_chans - 1; ++track) { + for (unsigned track = 0; track < indices[type][chan][seg][0].size(); ++track) { if (seg_details[track].length() <= 0) continue; int start = get_seg_start(seg_details, track, chan, seg); - /* We give a fake coordinator here, to ease the downstream builder */ - short xlow = chan; - short ylow = start; - if (CHANX == type) { - std::swap(xlow, ylow); - } - RRNodeId node = rr_graph.find_node(xlow, ylow, type, track); - if (false == rr_graph.valid_node_id(node)) { - - RRNodeId new_node = rr_graph.create_node(type); - rr_graph.set_node_bounding_box(new_node, vtr::Rect(xlow, ylow, xlow, ylow)); - rr_graph.set_node_track_num(new_node, track); - - (*index)++; + /* If the start of the wire doesn't have a inode, + * assign one to it. */ + int inode = indices[type][chan][start][0][track]; + if (OPEN == inode) { + inode = *index; + ++(*index); + + indices[type][chan][start][0][track] = inode; } + + /* Assign inode of start of wire to current position */ + indices[type][chan][seg][0][track] = inode; + } } } } static void load_block_rr_indices(const DeviceGrid& grid, - RRGraph& rr_graph, + t_rr_node_indices& indices, int* index) { //Walk through the grid assigning indices to SOURCE/SINK IPIN/OPIN for (size_t x = 0; x < grid.width(); x++) { @@ -1110,14 +1123,12 @@ static void load_block_rr_indices(const DeviceGrid& grid, for (int iclass = 0; iclass < type->num_class; ++iclass) { auto class_type = type->class_inf[iclass].type; if (class_type == DRIVER) { - RRNodeId node = rr_graph.create_node(SOURCE); - rr_graph.set_node_bounding_box(node, vtr::Rect(x, y, x, y)); - rr_graph.set_node_class_num(node, iclass); + indices[SOURCE][x][y][0].push_back(*index); + indices[SINK][x][y][0].push_back(OPEN); } else { VTR_ASSERT(class_type == RECEIVER); - RRNodeId node = rr_graph.create_node(SINK); - rr_graph.set_node_bounding_box(node, vtr::Rect(x, y, x, y)); - rr_graph.set_node_class_num(node, iclass); + indices[SINK][x][y][0].push_back(*index); + indices[SOURCE][x][y][0].push_back(OPEN); } ++(*index); } @@ -1134,23 +1145,34 @@ static void load_block_rr_indices(const DeviceGrid& grid, auto class_type = type->class_inf[iclass].type; if (class_type == DRIVER) { - RRNodeId node = rr_graph.create_node(OPIN); - rr_graph.set_node_bounding_box(node, vtr::Rect(x_tile, y_tile, x_tile, y_tile)); - rr_graph.set_node_pin_num(node, ipin); - rr_graph.set_node_side(node, side); + indices[OPIN][x_tile][y_tile][side].push_back(*index); + indices[IPIN][x_tile][y_tile][side].push_back(OPEN); } else { VTR_ASSERT(class_type == RECEIVER); - RRNodeId node = rr_graph.create_node(IPIN); - rr_graph.set_node_bounding_box(node, vtr::Rect(x_tile, y_tile, x_tile, y_tile)); - rr_graph.set_node_pin_num(node, ipin); - rr_graph.set_node_side(node, side); + indices[IPIN][x_tile][y_tile][side].push_back(*index); + indices[OPIN][x_tile][y_tile][side].push_back(OPEN); } ++(*index); + + } else { + indices[IPIN][x_tile][y_tile][side].push_back(OPEN); + indices[OPIN][x_tile][y_tile][side].push_back(OPEN); } } } } } + //Sanity check + for (int width_offset = 0; width_offset < type->width; ++width_offset) { + int x_tile = x + width_offset; + for (int height_offset = 0; height_offset < type->height; ++height_offset) { + int y_tile = y + height_offset; + for (e_side side : SIDES) { + VTR_ASSERT(indices[IPIN][x_tile][y_tile][side].size() == size_t(type->num_pins)); + VTR_ASSERT(indices[OPIN][x_tile][y_tile][side].size() == size_t(type->num_pins)); + } + } + } } } } @@ -1169,46 +1191,56 @@ static void load_block_rr_indices(const DeviceGrid& grid, int root_x = x - width_offset; int root_y = y - height_offset; - //Process each block from it's root location - auto type = grid[x][y].type; - - //Assign indicies for SINKs and SOURCEs - // Note that SINKS/SOURCES have no side, so we always use side 0 - for (int iclass = 0; iclass < type->num_class; ++iclass) { - auto class_type = type->class_inf[iclass].type; - if (class_type == DRIVER) { - RRNodeId node = rr_graph.find_node(root_x, root_y, SOURCE, iclass); - /* Update the internal look-up so that we can find the SOURCE/SINK node using their offset coordinates */ - rr_graph.set_node_bounding_box(node, vtr::Rect(root_x, root_y, x, y)); - } else { - VTR_ASSERT(class_type == RECEIVER); - RRNodeId node = rr_graph.find_node(root_x, root_y, SINK, iclass); - /* Update the internal look-up so that we can find the SOURCE/SINK node using their offset coordinates */ - rr_graph.set_node_bounding_box(node, vtr::Rect(root_x, root_y, x, y)); - } - } + indices[SOURCE][x][y] = indices[SOURCE][root_x][root_y]; + indices[SINK][x][y] = indices[SINK][root_x][root_y]; } } } } -RRGraph alloc_and_load_rr_node_indices(const int max_chan_width, - const DeviceGrid& grid, - int* index, - const t_chan_details& chan_details_x, - const t_chan_details& chan_details_y) { - /* Allocates and loads all the nodes in a RRGraph object */ - RRGraph rr_graph; +t_rr_node_indices alloc_and_load_rr_node_indices(const int max_chan_width, + const DeviceGrid& grid, + int* index, + const t_chan_details& chan_details_x, + const t_chan_details& chan_details_y) { + /* Allocates and loads all the structures needed for fast lookups of the * + * index of an rr_node. rr_node_indices is a matrix containing the index * + * of the *first* rr_node at a given (i,j) location. */ + + t_rr_node_indices indices; + + /* Alloc the lookup table */ + indices.resize(NUM_RR_TYPES); + for (t_rr_type rr_type : RR_TYPES) { + if (rr_type == CHANX) { + indices[rr_type].resize(grid.height()); + for (size_t y = 0; y < grid.height(); ++y) { + indices[rr_type][y].resize(grid.width()); + for (size_t x = 0; x < grid.width(); ++x) { + indices[rr_type][y][x].resize(NUM_SIDES); + } + } + } else { + indices[rr_type].resize(grid.width()); + for (size_t x = 0; x < grid.width(); ++x) { + indices[rr_type][x].resize(grid.height()); + for (size_t y = 0; y < grid.height(); ++y) { + indices[rr_type][x][y].resize(NUM_SIDES); + } + } + } + } + /* Assign indices for block nodes */ - load_block_rr_indices(grid, rr_graph, index); + load_block_rr_indices(grid, indices, index); /* Load the data for x and y channels */ load_chan_rr_indices(max_chan_width, grid.width(), grid.height(), - CHANX, chan_details_x, rr_graph, index); + CHANX, chan_details_x, indices, index); load_chan_rr_indices(max_chan_width, grid.height(), grid.width(), - CHANY, chan_details_y, rr_graph, index); - return rr_graph; + CHANY, chan_details_y, indices, index); + return indices; } std::vector get_rr_node_chan_wires_at_location(const t_rr_node_indices& L_rr_node_indices, @@ -1225,6 +1257,44 @@ std::vector get_rr_node_chan_wires_at_location(const t_rr_node_indices& L_r return L_rr_node_indices[rr_type][x][y][SIDES[0]]; } +/******************************************************************** + * A wrapper for the old function to return a vector of RRNodeId + *******************************************************************/ +std::vector get_rr_graph_node_indices(const t_rr_node_indices& L_rr_node_indices, + int x, + int y, + t_rr_type rr_type, + int ptc) { + /* + * Like get_rr_node_index() but returns all matching nodes, + * rather than just the first. This is particularly useful for getting all instances + * of a specific IPIN/OPIN at a specific gird tile (x,y) location. + */ + std::vector indices; + + if (rr_type == IPIN || rr_type == OPIN) { + //For pins we need to look at all the sides of the current grid tile + + for (e_side side : SIDES) { + int rr_node_index = get_rr_node_index(L_rr_node_indices, x, y, rr_type, ptc, side); + + if (rr_node_index >= 0) { + indices.push_back(RRNodeId(rr_node_index)); + } + } + } else { + //Sides do not effect non-pins so there should only be one per ptc + int rr_node_index = get_rr_node_index(L_rr_node_indices, x, y, rr_type, ptc); + + if (rr_node_index != OPEN) { + indices.push_back(RRNodeId(rr_node_index)); + } + } + + return indices; +} + + std::vector get_rr_node_indices(const t_rr_node_indices& L_rr_node_indices, int x, int y, @@ -1259,6 +1329,15 @@ std::vector get_rr_node_indices(const t_rr_node_indices& L_rr_node_indices, return indices; } +RRNodeId get_rr_graph_node_index(const t_rr_node_indices& L_rr_node_indices, + int x, + int y, + t_rr_type rr_type, + int ptc, + e_side side) { + return RRNodeId(get_rr_node_index(L_rr_node_indices, x, y, rr_type, ptc, side)); +} + int get_rr_node_index(const t_rr_node_indices& L_rr_node_indices, int x, int y, @@ -1398,6 +1477,7 @@ int get_track_to_pins(int seg, int tracks_per_chan, const RRNodeId& from_rr_node, t_rr_edge_info_set& rr_edges_to_create, + const t_rr_node_indices& L_rr_node_indices, const RRGraph& rr_graph, const t_track_to_pin_lookup& track_to_pin_lookup, const t_chan_seg_details* seg_details, @@ -1456,7 +1536,7 @@ int get_track_to_pins(int seg, /* Check there is a connection and Fc map isn't wrong */ /*int to_node = get_rr_node_index(L_rr_node_indices, x + width_offset, y + height_offset, IPIN, ipin, side);*/ - RRNodeId to_node = rr_graph.find_node(x, y, IPIN, ipin, side); + RRNodeId to_node = RRNodeId(get_rr_node_index(L_rr_node_indices, x, y, IPIN, ipin, side)); if (rr_graph.valid_node_id(to_node)) { rr_edges_to_create.emplace_back(from_rr_node, to_node, wire_to_ipin_switch); ++num_conn; @@ -1503,6 +1583,7 @@ int get_track_to_tracks(const int from_chan, const t_chan_seg_details* to_seg_details, const t_chan_details& to_chan_details, const enum e_directionality directionality, + const t_rr_node_indices& L_rr_node_indices, const RRGraph& rr_graph, const vtr::NdMatrix, 3>& switch_block_conn, t_sb_connection_map* sb_conn_map) { @@ -1636,7 +1717,7 @@ int get_track_to_tracks(const int from_chan, num_conn += get_track_to_chan_seg(from_track, to_chan, to_seg, to_type, from_side_a, to_side, switch_override, - rr_graph, + L_rr_node_indices, rr_graph, sb_conn_map, from_rr_node, rr_edges_to_create); } } else { @@ -1645,7 +1726,7 @@ int get_track_to_tracks(const int from_chan, * switchbox, so we follow through regardless of whether the current segment has an SB */ conn_tracks = switch_block_conn[from_side_a][to_side][from_track]; num_conn += get_bidir_track_to_chan_seg(conn_tracks, - rr_graph, to_chan, to_seg, to_sb, to_type, + L_rr_node_indices, rr_graph, to_chan, to_seg, to_sb, to_type, to_seg_details, from_is_sblock, from_switch, switch_override, directionality, from_rr_node, rr_edges_to_create); @@ -1660,7 +1741,7 @@ int get_track_to_tracks(const int from_chan, from_side_a, to_side, Fs_per_side, sblock_pattern, switch_override, - rr_graph, to_seg_details, + L_rr_node_indices, rr_graph, to_seg_details, &Fs_clipped, from_rr_node, rr_edges_to_create); } } @@ -1675,7 +1756,7 @@ int get_track_to_tracks(const int from_chan, num_conn += get_track_to_chan_seg(from_track, to_chan, to_seg, to_type, from_side_b, to_side, switch_override, - rr_graph, + L_rr_node_indices, rr_graph, sb_conn_map, from_rr_node, rr_edges_to_create); } } else { @@ -1684,7 +1765,7 @@ int get_track_to_tracks(const int from_chan, * switchbox, so we follow through regardless of whether the current segment has an SB */ conn_tracks = switch_block_conn[from_side_b][to_side][from_track]; num_conn += get_bidir_track_to_chan_seg(conn_tracks, - rr_graph, to_chan, to_seg, to_sb, to_type, + L_rr_node_indices, rr_graph, to_chan, to_seg, to_sb, to_type, to_seg_details, from_is_sblock, from_switch, switch_override, directionality, from_rr_node, rr_edges_to_create); @@ -1700,7 +1781,7 @@ int get_track_to_tracks(const int from_chan, from_side_b, to_side, Fs_per_side, sblock_pattern, switch_override, - rr_graph, to_seg_details, + L_rr_node_indices, rr_graph, to_seg_details, &Fs_clipped, from_rr_node, rr_edges_to_create); } } @@ -1712,6 +1793,7 @@ int get_track_to_tracks(const int from_chan, } static int get_bidir_track_to_chan_seg(const std::vector conn_tracks, + const t_rr_node_indices& L_rr_node_indices, const RRGraph& rr_graph, const int to_chan, const int to_seg, @@ -1744,9 +1826,9 @@ static int get_bidir_track_to_chan_seg(const std::vector conn_tracks, num_conn = 0; for (iconn = 0; iconn < conn_tracks.size(); ++iconn) { to_track = conn_tracks[iconn]; - to_node = rr_graph.find_node(to_x, to_y, to_type, to_track); + to_node = get_rr_graph_node_index(L_rr_node_indices, to_x, to_y, to_type, to_track); - if (to_node == RRNodeId::INVALID()) { + if (false == rr_graph.valid_node_id(to_node)) { continue; } @@ -1787,6 +1869,7 @@ static int get_track_to_chan_seg(const int from_wire, const e_side from_side, const e_side to_side, const int switch_override, + const t_rr_node_indices& L_rr_node_indices, const RRGraph& rr_graph, t_sb_connection_map* sb_conn_map, const RRNodeId& from_rr_node, @@ -1823,9 +1906,9 @@ static int get_track_to_chan_seg(const int from_wire, if (conn_vector.at(iconn).from_wire != from_wire) continue; int to_wire = conn_vector.at(iconn).to_wire; - RRNodeId to_node = rr_graph.find_node(to_x, to_y, to_chan_type, to_wire); + RRNodeId to_node = RRNodeId(get_rr_node_index(L_rr_node_indices, to_x, to_y, to_chan_type, to_wire)); - if (to_node == RRNodeId::INVALID()) { + if (false == rr_graph.valid_node_id(to_node)) { continue; } @@ -1866,6 +1949,7 @@ static int get_unidir_track_to_chan_seg(const int from_track, const int Fs_per_side, t_sblock_pattern& sblock_pattern, const int switch_override, + const t_rr_node_indices& L_rr_node_indices, const RRGraph& rr_graph, const t_chan_seg_details* seg_details, bool* Fs_clipped, @@ -1923,9 +2007,9 @@ static int get_unidir_track_to_chan_seg(const int from_track, sblock_pattern[sb_x][sb_y][from_side][to_side][from_track][j + 1] = to_track; } - RRNodeId to_node = rr_graph.find_node(to_x, to_y, to_type, to_track); + RRNodeId to_node = get_rr_graph_node_index(L_rr_node_indices, to_x, to_y, to_type, to_track); - if (to_node == RRNodeId::INVALID()) { + if (false == rr_graph.valid_node_id(to_node)) { continue; } diff --git a/vpr/src/route/rr_graph2.h b/vpr/src/route/rr_graph2.h index 6aa5cae68..30d68d6db 100644 --- a/vpr/src/route/rr_graph2.h +++ b/vpr/src/route/rr_graph2.h @@ -47,11 +47,11 @@ typedef vtr::NdMatrix t_sblock_pattern; /******************* Subroutines exported by rr_graph2.c *********************/ -RRGraph alloc_and_load_rr_node_indices(const int max_chan_width, - const DeviceGrid& grid, - int* index, - const t_chan_details& chan_details_x, - const t_chan_details& chan_details_y); +t_rr_node_indices alloc_and_load_rr_node_indices(const int max_chan_width, + const DeviceGrid& grid, + int* index, + const t_chan_details& chan_details_x, + const t_chan_details& chan_details_y); int get_rr_node_index(int x, int y, @@ -59,7 +59,14 @@ int get_rr_node_index(int x, int ptc, const t_rr_node_indices& L_rr_node_indices); + //Returns all the rr nodes associated with the specified coordinate (i.e. accross sides) +std::vector get_rr_graph_node_indices(const t_rr_node_indices& L_rr_node_indices, + int x, + int y, + t_rr_type rr_type, + int ptc); + std::vector get_rr_node_indices(const t_rr_node_indices& L_rr_node_indices, int x, int y, @@ -74,6 +81,13 @@ std::vector get_rr_node_chan_wires_at_location(const t_rr_node_indices& L_r //Return the first rr node of the specified type and coordinates // For non-IPIN/OPIN types 'side' is ignored +RRNodeId get_rr_graph_node_index(const t_rr_node_indices& L_rr_node_indices, + int x, + int y, + t_rr_type rr_type, + int ptc, + e_side side = NUM_SIDES); + int get_rr_node_index(const t_rr_node_indices& L_rr_node_indices, int x, int y, @@ -156,7 +170,7 @@ int get_bidir_opin_connections(const int i, const RRNodeId& from_rr_node, t_rr_edge_info_set& rr_edges_to_create, const t_pin_to_track_lookup& opin_to_track_map, - const RRGraph& rr_graph, + const t_rr_node_indices& L_rr_node_indices, const t_chan_details& chan_details_x, const t_chan_details& chan_details_y); @@ -171,7 +185,7 @@ int get_unidir_opin_connections(const int chan, vtr::NdMatrix& Fc_ofs, const int max_len, const int max_chan_width, - const RRGraph& rr_graph, + const t_rr_node_indices& L_rr_node_indices, bool* Fc_clipped); int get_track_to_pins(int seg, @@ -180,6 +194,7 @@ int get_track_to_pins(int seg, int tracks_per_chan, const RRNodeId& from_rr_node, t_rr_edge_info_set& rr_edges_to_create, + const t_rr_node_indices& L_rr_node_indices, const RRGraph& rr_graph, const t_track_to_pin_lookup& track_to_pin_lookup, const t_chan_seg_details* seg_details, @@ -205,6 +220,7 @@ int get_track_to_tracks(const int from_chan, const t_chan_seg_details* to_seg_details, const t_chan_details& to_chan_details, const enum e_directionality directionality, + const t_rr_node_indices& L_rr_node_indices, const RRGraph& rr_graph, const vtr::NdMatrix, 3>& switch_block_conn, t_sb_connection_map* sb_conn_map); From 969dd7c4676dec75cc462640abefdc016ba272e4 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 4 Feb 2020 15:33:15 -0700 Subject: [PATCH 095/645] rr_graph working --- vpr/src/route/route_timing.cpp | 12 ++++++------ vpr/src/route/route_util.cpp | 27 +++++++++++++-------------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/vpr/src/route/route_timing.cpp b/vpr/src/route/route_timing.cpp index 156197be7..131044b68 100644 --- a/vpr/src/route/route_timing.cpp +++ b/vpr/src/route/route_timing.cpp @@ -2598,19 +2598,19 @@ static OveruseInfo calculate_overuse_info() { } } - return OveruseInfo(device_ctx.rr_nodes.size(), overused_nodes, total_overuse, worst_overuse); + return OveruseInfo(device_ctx.rr_graph.nodes().size(), overused_nodes, total_overuse, worst_overuse); } static size_t calculate_wirelength_available() { auto& device_ctx = g_vpr_ctx.device(); size_t available_wirelength = 0; - for (size_t i = 0; i < device_ctx.rr_nodes.size(); ++i) { - if (device_ctx.rr_nodes[i].type() == CHANX || device_ctx.rr_nodes[i].type() == CHANY) { - size_t length_x = device_ctx.rr_nodes[i].xhigh() - device_ctx.rr_nodes[i].xlow(); - size_t length_y = device_ctx.rr_nodes[i].yhigh() - device_ctx.rr_nodes[i].ylow(); + for (const RRNodeId& node : device_ctx.rr_graph.nodes()) { + if (device_ctx.rr_graph.node_type(node) == CHANX || device_ctx.rr_graph.node_type(node) == CHANY) { + size_t length_x = device_ctx.rr_graph.node_xhigh(node) - device_ctx.rr_graph.node_xlow(node); + size_t length_y = device_ctx.rr_graph.node_yhigh(node) - device_ctx.rr_graph.node_ylow(node); - available_wirelength += device_ctx.rr_nodes[i].capacity() * (length_x + length_y + 1); + available_wirelength += device_ctx.rr_graph.node_capacity(node) * (length_x + length_y + 1); } } return available_wirelength; diff --git a/vpr/src/route/route_util.cpp b/vpr/src/route/route_util.cpp index 6d8417963..d61e8d8f6 100644 --- a/vpr/src/route/route_util.cpp +++ b/vpr/src/route/route_util.cpp @@ -55,24 +55,23 @@ vtr::Matrix calculate_routing_avail(t_rr_type rr_type) { auto& device_ctx = g_vpr_ctx.device(); vtr::Matrix avail({{device_ctx.grid.width(), device_ctx.grid.height()}}, 0.); - for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); ++inode) { - auto& rr_node = device_ctx.rr_nodes[inode]; + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { - if (rr_node.type() == CHANX && rr_type == CHANX) { - VTR_ASSERT(rr_node.type() == CHANX); - VTR_ASSERT(rr_node.ylow() == rr_node.yhigh()); + if (device_ctx.rr_graph.node_type(inode) == CHANX && rr_type == CHANX) { + VTR_ASSERT(device_ctx.rr_graph.node_type(inode) == CHANX); + VTR_ASSERT(device_ctx.rr_graph.node_ylow(inode) == device_ctx.rr_graph.node_yhigh(inode)); - int y = rr_node.ylow(); - for (int x = rr_node.xlow(); x <= rr_node.xhigh(); ++x) { - avail[x][y] += rr_node.capacity(); + int y = device_ctx.rr_graph.node_ylow(inode); + for (int x = device_ctx.rr_graph.node_xlow(inode); x <= device_ctx.rr_graph.node_xhigh(inode); ++x) { + avail[x][y] += device_ctx.rr_graph.node_capacity(inode); } - } else if (rr_node.type() == CHANY && rr_type == CHANY) { - VTR_ASSERT(rr_node.type() == CHANY); - VTR_ASSERT(rr_node.xlow() == rr_node.xhigh()); + } else if (device_ctx.rr_graph.node_type(inode) == CHANY && rr_type == CHANY) { + VTR_ASSERT(device_ctx.rr_graph.node_type(inode) == CHANY); + VTR_ASSERT(device_ctx.rr_graph.node_xlow(inode) == device_ctx.rr_graph.node_xhigh(inode)); - int x = rr_node.xlow(); - for (int y = rr_node.ylow(); y <= rr_node.yhigh(); ++y) { - avail[x][y] += rr_node.capacity(); + int x = device_ctx.rr_graph.node_xlow(inode); + for (int y = device_ctx.rr_graph.node_ylow(inode); y <= device_ctx.rr_graph.node_yhigh(inode); ++y) { + avail[x][y] += device_ctx.rr_graph.node_capacity(inode); } } } From e3db937daadb79c6cdbdae0370bf594c766ecf08 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 4 Feb 2020 16:20:25 -0700 Subject: [PATCH 096/645] fixed routing stats --- vpr/src/route/rr_graph.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/vpr/src/route/rr_graph.cpp b/vpr/src/route/rr_graph.cpp index f9a87bfea..1b39dfc35 100644 --- a/vpr/src/route/rr_graph.cpp +++ b/vpr/src/route/rr_graph.cpp @@ -404,13 +404,8 @@ void create_rr_graph(const t_graph_type graph_type, void print_rr_graph_stats() { auto& device_ctx = g_vpr_ctx.device(); - size_t num_rr_edges = 0; - for (auto& rr_node : device_ctx.rr_nodes) { - num_rr_edges += rr_node.edges().size(); - } - - VTR_LOG(" RR Graph Nodes: %zu\n", device_ctx.rr_nodes.size()); - VTR_LOG(" RR Graph Edges: %zu\n", num_rr_edges); + VTR_LOG(" RR Graph Nodes: %zu\n", device_ctx.rr_graph.nodes().size()); + VTR_LOG(" RR Graph Edges: %zu\n", device_ctx.rr_graph.edges().size()); } bool channel_widths_unchanged(const t_chan_width& current, const t_chan_width& proposed) { From f098d40af1b9f54f36cefe12c5888e96d924633d Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 4 Feb 2020 16:48:15 -0700 Subject: [PATCH 097/645] correct missing rr_nodes usage to rr_graph obj --- vpr/src/draw/search_bar.cpp | 2 +- vpr/src/route/rr_graph.cpp | 2 -- vpr/src/route/rr_graph_indexed_data.cpp | 6 +++--- vpr/src/util/vpr_utils.cpp | 18 ++++++++---------- 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/vpr/src/draw/search_bar.cpp b/vpr/src/draw/search_bar.cpp index a7ff3831b..be8705ff1 100644 --- a/vpr/src/draw/search_bar.cpp +++ b/vpr/src/draw/search_bar.cpp @@ -68,7 +68,7 @@ void search_and_highlight(GtkWidget* /*widget*/, ezgl::application* app) { ss >> rr_node_id; // valid rr node id check - if (rr_node_id < 0 || rr_node_id >= int(device_ctx.rr_nodes.size())) { + if (rr_node_id < 0 || rr_node_id >= int(device_ctx.rr_graph.nodes().size())) { warning_dialog_box("Invalid RR Node ID"); app->refresh_drawing(); return; diff --git a/vpr/src/route/rr_graph.cpp b/vpr/src/route/rr_graph.cpp index 1b39dfc35..25d4ed387 100644 --- a/vpr/src/route/rr_graph.cpp +++ b/vpr/src/route/rr_graph.cpp @@ -1874,8 +1874,6 @@ void alloc_and_load_edges(RRGraph& rr_graph, for (RRNodeId inode : from_nodes) { auto edge_range = std::equal_range(rr_edges_to_create.begin(), rr_edges_to_create.end(), inode, compare_from_node()); - size_t edge_count = std::distance(edge_range.first, edge_range.second); - //Create initial edges // //Note that we do this in bulk instead of via add_edge() to reduce diff --git a/vpr/src/route/rr_graph_indexed_data.cpp b/vpr/src/route/rr_graph_indexed_data.cpp index 3982bcb64..b131532f8 100644 --- a/vpr/src/route/rr_graph_indexed_data.cpp +++ b/vpr/src/route/rr_graph_indexed_data.cpp @@ -219,10 +219,10 @@ static std::vector count_rr_segment_types() { auto& device_ctx = g_vpr_ctx.device(); - for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); ++inode) { - if (device_ctx.rr_nodes[inode].type() != CHANX && device_ctx.rr_nodes[inode].type() != CHANY) continue; + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { + if (device_ctx.rr_graph.node_type(inode) != CHANX && device_ctx.rr_graph.node_type(inode) != CHANY) continue; - int cost_index = device_ctx.rr_nodes[inode].cost_index(); + int cost_index = device_ctx.rr_graph.node_cost_index(inode); int seg_index = device_ctx.rr_indexed_data[cost_index].seg_index; diff --git a/vpr/src/util/vpr_utils.cpp b/vpr/src/util/vpr_utils.cpp index dfac49522..1f28401ca 100644 --- a/vpr/src/util/vpr_utils.cpp +++ b/vpr/src/util/vpr_utils.cpp @@ -1951,13 +1951,11 @@ void print_switch_usage() { switch_fanin_delay = new std::map[device_ctx.num_arch_switches]; // a node can have multiple inward switches, so // map key: switch index; map value: count (fanin) - std::map* inward_switch_inf = new std::map[device_ctx.rr_nodes.size()]; - for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); inode++) { - const t_rr_node& from_node = device_ctx.rr_nodes[inode]; - int num_edges = from_node.num_edges(); - for (int iedge = 0; iedge < num_edges; iedge++) { - int switch_index = from_node.edge_switch(iedge); - int to_node_index = from_node.edge_sink_node(iedge); + vtr::vector> inward_switch_inf(device_ctx.rr_graph.nodes().size()); + for (const RRNodeId& from_node : device_ctx.rr_graph.nodes()) { + for (const RREdgeId& iedge : device_ctx.rr_graph.node_out_edges(from_node)) { + int switch_index = (int)size_t(device_ctx.rr_graph.edge_switch(iedge)); + const RRNodeId& to_node_index = device_ctx.rr_graph.edge_sink_node(iedge); // Assumption: suppose for a L4 wire (bi-directional): ----+----+----+----, it can be driven from any point (0, 1, 2, 3). // physically, the switch driving from point 1 & 3 should be the same. But we will assign then different switch // index; or there is no way to differentiate them after abstracting a 2D wire into a 1D node @@ -1967,7 +1965,7 @@ void print_switch_usage() { inward_switch_inf[to_node_index][switch_index]++; } } - for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); inode++) { + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { std::map::iterator itr; for (itr = inward_switch_inf[inode].begin(); itr != inward_switch_inf[inode].end(); itr++) { int switch_index = itr->first; @@ -1977,7 +1975,7 @@ void print_switch_usage() { if (status == -1) { delete[] switch_fanin_count; delete[] switch_fanin_delay; - delete[] inward_switch_inf; + inward_switch_inf.clear(); return; } if (switch_fanin_count[switch_index].count(fanin) == 0) { @@ -2003,7 +2001,7 @@ void print_switch_usage() { VTR_LOG("\n==================================================\n\n"); delete[] switch_fanin_count; delete[] switch_fanin_delay; - delete[] inward_switch_inf; + inward_switch_inf.clear(); } /* From 0cce1f4efcc6e3d8fadf7d65a767298122f40161 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 4 Feb 2020 17:31:39 -0700 Subject: [PATCH 098/645] bug fixing for heterogenenous FPGA when using the RRGraph object --- vpr/src/place/timing_place_lookup.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/vpr/src/place/timing_place_lookup.cpp b/vpr/src/place/timing_place_lookup.cpp index dd55adca4..a067c3cc3 100644 --- a/vpr/src/place/timing_place_lookup.cpp +++ b/vpr/src/place/timing_place_lookup.cpp @@ -302,18 +302,24 @@ static float route_connection_delay( //Get the rr nodes to route between auto best_driver_ptcs = get_best_classes(DRIVER, device_ctx.grid[source_x][source_y].type); auto best_sink_ptcs = get_best_classes(RECEIVER, device_ctx.grid[sink_x][sink_y].type); + + int src_width_offset = device_ctx.grid[source_x][source_y].width_offset; + int src_height_offset = device_ctx.grid[source_x][source_y].height_offset; + + int sink_width_offset = device_ctx.grid[sink_x][sink_y].width_offset; + int sink_height_offset = device_ctx.grid[sink_x][sink_y].height_offset; for (int driver_ptc : best_driver_ptcs) { VTR_ASSERT(driver_ptc != OPEN); - RRNodeId source_rr_node = device_ctx.rr_graph.find_node(source_x, source_y, SOURCE, driver_ptc); + RRNodeId source_rr_node = device_ctx.rr_graph.find_node(source_x - src_width_offset, source_y - src_height_offset, SOURCE, driver_ptc); VTR_ASSERT(source_rr_node != RRNodeId::INVALID()); for (int sink_ptc : best_sink_ptcs) { VTR_ASSERT(sink_ptc != OPEN); - RRNodeId sink_rr_node = device_ctx.rr_graph.find_node(sink_x, sink_y, SINK, sink_ptc); + RRNodeId sink_rr_node = device_ctx.rr_graph.find_node(sink_x - sink_width_offset, sink_y - sink_height_offset, SINK, sink_ptc); VTR_ASSERT(sink_rr_node != RRNodeId::INVALID()); From a3a85bf259a3f6c6c4a8ab1f5d2d9f861d0566d6 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 4 Feb 2020 20:45:14 -0700 Subject: [PATCH 099/645] bug fix for direct connections in rr_graph builder --- vpr/src/route/rr_graph.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vpr/src/route/rr_graph.cpp b/vpr/src/route/rr_graph.cpp index 25d4ed387..e17a0c912 100644 --- a/vpr/src/route/rr_graph.cpp +++ b/vpr/src/route/rr_graph.cpp @@ -3006,14 +3006,14 @@ static int get_opin_direct_connections(int x, if (directs[i].to_side != NUM_SIDES) { //Explicit side specified, only create if pin exists on that side - RRNodeId inode = rr_graph.find_node(x + directs[i].x_offset, y + directs[i].y_offset, + RRNodeId inode = get_rr_graph_node_index(L_rr_node_indices, x + directs[i].x_offset - width_offset, y + directs[i].y_offset - height_offset, IPIN, ipin, directs[i].to_side); if (inode != RRNodeId::INVALID()) { inodes.push_back(inode); } } else { //No side specified, get all candidates - inodes = get_rr_graph_node_indices(L_rr_node_indices, x + directs[i].x_offset, y + directs[i].y_offset, + inodes = get_rr_graph_node_indices(L_rr_node_indices, x + directs[i].x_offset - width_offset, y + directs[i].y_offset - height_offset, IPIN, ipin); } From ecc3b8a4f079c64665bdfa13f9ab101dabc25f27 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 4 Feb 2020 21:02:55 -0700 Subject: [PATCH 100/645] bug fix in router lookhead map when find rr_graph nodes --- vpr/src/route/router_lookahead_map.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/vpr/src/route/router_lookahead_map.cpp b/vpr/src/route/router_lookahead_map.cpp index 42de703b5..a4ad36225 100644 --- a/vpr/src/route/router_lookahead_map.cpp +++ b/vpr/src/route/router_lookahead_map.cpp @@ -25,6 +25,7 @@ #include "vtr_log.h" #include "vtr_assert.h" #include "vtr_time.h" +#include "rr_graph_obj_util.h" #include "router_lookahead_map.h" /* the cost map is computed by running a Dijkstra search from channel segment rr nodes at the specified reference coordinate */ @@ -310,10 +311,7 @@ static RRNodeId get_start_node_ind(int start_x, int start_y, int target_x, int t * we can get the number of tracks for a * routing channel and then get the node one by one */ - short num_tracks = device_ctx.rr_graph.chan_num_tracks(start_x, start_y, rr_type); - for (short i = 0; i < num_tracks; ++i) { - channel_node_list.push_back(device_ctx.rr_graph.find_node(start_x, start_y, rr_type, i)); - } + channel_node_list = find_rr_graph_chan_nodes(device_ctx.rr_graph, start_x, start_y, rr_type); /* find first node in channel that has specified segment index and goes in the desired direction */ for (const RRNodeId& node_ind : channel_node_list) { From e2f408cc2d055fabc665ebb6560116294d04682d Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 4 Feb 2020 21:32:05 -0700 Subject: [PATCH 101/645] bug fix for clock network builder using rr_graph object --- vpr/src/device/rr_graph_obj.cpp | 6 ++++-- vpr/src/device/rr_graph_obj.h | 2 +- vpr/src/route/clock_network_builders.cpp | 8 ++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/vpr/src/device/rr_graph_obj.cpp b/vpr/src/device/rr_graph_obj.cpp index 1b738d212..da528cf26 100644 --- a/vpr/src/device/rr_graph_obj.cpp +++ b/vpr/src/device/rr_graph_obj.cpp @@ -846,10 +846,12 @@ RRNodeId RRGraph::create_node(const t_rr_type& type) { return node_id; } -RREdgeId RRGraph::create_edge(const RRNodeId& source, const RRNodeId& sink, const RRSwitchId& switch_id) { +RREdgeId RRGraph::create_edge(const RRNodeId& source, const RRNodeId& sink, const RRSwitchId& switch_id, const bool& fake_switch) { VTR_ASSERT(valid_node_id(source)); VTR_ASSERT(valid_node_id(sink)); - VTR_ASSERT(valid_switch_id(switch_id)); + if (false == fake_switch) { + VTR_ASSERT(valid_switch_id(switch_id)); + } /* Allocate an ID */ RREdgeId edge_id = RREdgeId(num_edges_); diff --git a/vpr/src/device/rr_graph_obj.h b/vpr/src/device/rr_graph_obj.h index fd8439868..08abf682c 100644 --- a/vpr/src/device/rr_graph_obj.h +++ b/vpr/src/device/rr_graph_obj.h @@ -614,7 +614,7 @@ class RRGraph { * This function will automatically create a node and * configure the nodes and edges in connection */ - RREdgeId create_edge(const RRNodeId& source, const RRNodeId& sink, const RRSwitchId& switch_id); + RREdgeId create_edge(const RRNodeId& source, const RRNodeId& sink, const RRSwitchId& switch_id, const bool& fake_switch=false); void set_edge_switch(const RREdgeId& edge, const RRSwitchId& switch_id); RRSwitchId create_switch(const t_rr_switch_inf& switch_info); RRSegmentId create_segment(const t_segment_inf& segment_info); diff --git a/vpr/src/route/clock_network_builders.cpp b/vpr/src/route/clock_network_builders.cpp index b90babdae..5fbd854ca 100644 --- a/vpr/src/route/clock_network_builders.cpp +++ b/vpr/src/route/clock_network_builders.cpp @@ -255,8 +255,8 @@ void ClockRib::create_rr_nodes_and_internal_edges_for_one_instance(ClockRRGraphB clock_graph); // connect drive point to each half rib using a directed switch - rr_graph.create_edge(drive_node_idx, left_node_idx, RRSwitchId(drive.switch_idx)); - rr_graph.create_edge(drive_node_idx, right_node_idx, RRSwitchId(drive.switch_idx)); + rr_graph.create_edge(drive_node_idx, left_node_idx, RRSwitchId(drive.switch_idx), true); + rr_graph.create_edge(drive_node_idx, right_node_idx, RRSwitchId(drive.switch_idx), true); } } } @@ -501,8 +501,8 @@ void ClockSpine::create_rr_nodes_and_internal_edges_for_one_instance(ClockRRGrap clock_graph); // connect drive point to each half spine using a directed switch - rr_graph.create_edge(drive_node_idx, left_node_idx, RRSwitchId(drive.switch_idx)); - rr_graph.create_edge(drive_node_idx, right_node_idx, RRSwitchId(drive.switch_idx)); + rr_graph.create_edge(drive_node_idx, left_node_idx, RRSwitchId(drive.switch_idx), true); + rr_graph.create_edge(drive_node_idx, right_node_idx, RRSwitchId(drive.switch_idx), true); } } } From 7092ddbde658473fa11ba06b1b2504fa69371458 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 4 Feb 2020 21:54:03 -0700 Subject: [PATCH 102/645] bug fix for root node builder by including ptc_num!! --- vpr/src/route/clock_connection_builders.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vpr/src/route/clock_connection_builders.cpp b/vpr/src/route/clock_connection_builders.cpp index b36fee966..f27295590 100644 --- a/vpr/src/route/clock_connection_builders.cpp +++ b/vpr/src/route/clock_connection_builders.cpp @@ -98,6 +98,11 @@ RRNodeId RoutingToClockConnection::create_virtual_clock_network_sink_node( float C = 0.; rr_graph.set_node_rc_data_index(node_index, find_create_rr_rc_data(R, C)); + /* Set a ptc_num here as RRGraph object does need it to build fast look-up + * Legacy codes fail to do this! + */ + rr_graph.set_node_ptc_num(node_index, device_ctx.grid[x][y].type->num_class); + return node_index; } From e89d8e4493abe58a82fd467ddddddb166ca47b57 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 4 Feb 2020 21:56:54 -0700 Subject: [PATCH 103/645] bug fix for clock connection builder by supporting fake switch when adding edges to RRGraph object --- vpr/src/route/clock_connection_builders.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vpr/src/route/clock_connection_builders.cpp b/vpr/src/route/clock_connection_builders.cpp index f27295590..96e4fd722 100644 --- a/vpr/src/route/clock_connection_builders.cpp +++ b/vpr/src/route/clock_connection_builders.cpp @@ -68,18 +68,18 @@ void RoutingToClockConnection::create_switches(const ClockRRGraphBuilder& clock_ // Connect to x-channel wires unsigned num_wires_x = x_wire_indices.size() * fc; for (size_t i = 0; i < num_wires_x; i++) { - rr_graph.create_edge(x_wire_indices[i], clock_index, RRSwitchId(rr_switch_idx)); + rr_graph.create_edge(x_wire_indices[i], clock_index, RRSwitchId(rr_switch_idx), true); } // Connect to y-channel wires unsigned num_wires_y = y_wire_indices.size() * fc; for (size_t i = 0; i < num_wires_y; i++) { - rr_graph.create_edge(y_wire_indices[i], clock_index, RRSwitchId(rr_switch_idx)); + rr_graph.create_edge(y_wire_indices[i], clock_index, RRSwitchId(rr_switch_idx), true); } // Connect to virtual clock sink node // used by the two stage router - rr_graph.create_edge(clock_index, virtual_clock_network_root_idx, RRSwitchId(rr_switch_idx)); + rr_graph.create_edge(clock_index, virtual_clock_network_root_idx, RRSwitchId(rr_switch_idx), true); } } @@ -183,7 +183,7 @@ void ClockToClockConneciton::create_switches(const ClockRRGraphBuilder& clock_gr if (from_itter == from_rr_node_indices.end()) { from_itter = from_rr_node_indices.begin(); } - rr_graph.create_edge(*from_itter, to_index, RRSwitchId(rr_switch_idx)); + rr_graph.create_edge(*from_itter, to_index, RRSwitchId(rr_switch_idx), true); from_itter++; } } @@ -292,7 +292,7 @@ void ClockToPinsConnection::create_switches(const ClockRRGraphBuilder& clock_gra //Create edges depending on Fc for (size_t i = 0; i < clock_network_indices.size() * fc; i++) { - rr_graph.create_edge(clock_network_indices[i], clock_pin_node_idx, RRSwitchId(rr_switch_idx)); + rr_graph.create_edge(clock_network_indices[i], clock_pin_node_idx, RRSwitchId(rr_switch_idx), true); } } } From dad204674bcf4cbb803c497d77dfdc599a9eb8aa Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 5 Feb 2020 21:50:52 -0700 Subject: [PATCH 104/645] done an initial version of clustering net fix-up based on routing results. Debugging on the way --- .../annotation/vpr_clustering_annotation.cpp | 49 +++++ .../annotation/vpr_clustering_annotation.h | 40 ++++ .../src/annotation/vpr_routing_annotation.cpp | 47 +++++ .../src/annotation/vpr_routing_annotation.h | 40 ++++ openfpga/src/base/openfpga_context.h | 9 + openfpga/src/base/openfpga_link_arch.cpp | 8 + openfpga/src/base/openfpga_pb_pin_fixup.cpp | 184 ++++++++++++++++++ openfpga/src/base/openfpga_pb_pin_fixup.h | 23 +++ openfpga/src/base/openfpga_setup_command.cpp | 20 +- openfpga/test_script/s298_k6_frac.openfpga | 3 + 10 files changed, 422 insertions(+), 1 deletion(-) create mode 100644 openfpga/src/annotation/vpr_clustering_annotation.cpp create mode 100644 openfpga/src/annotation/vpr_clustering_annotation.h create mode 100644 openfpga/src/annotation/vpr_routing_annotation.cpp create mode 100644 openfpga/src/annotation/vpr_routing_annotation.h create mode 100644 openfpga/src/base/openfpga_pb_pin_fixup.cpp create mode 100644 openfpga/src/base/openfpga_pb_pin_fixup.h diff --git a/openfpga/src/annotation/vpr_clustering_annotation.cpp b/openfpga/src/annotation/vpr_clustering_annotation.cpp new file mode 100644 index 000000000..747978023 --- /dev/null +++ b/openfpga/src/annotation/vpr_clustering_annotation.cpp @@ -0,0 +1,49 @@ +/************************************************************************ + * Member functions for class VprClusteringAnnotation + ***********************************************************************/ +#include "vtr_log.h" +#include "vtr_assert.h" +#include "vpr_clustering_annotation.h" + +/* namespace openfpga begins */ +namespace openfpga { + +/************************************************************************ + * Constructors + ***********************************************************************/ +VprClusteringAnnotation::VprClusteringAnnotation() { + return; +} + +/************************************************************************ + * Public accessors + ***********************************************************************/ +bool VprClusteringAnnotation::is_net_renamed(const ClusterBlockId& block_id, const int& pin_index) const { + /* Ensure that the block_id is in the list */ + if (net_names_.end() == net_names_.find(block_id)) { + return false; + } + return (net_names_.at(block_id).end() != net_names_.at(block_id).find(pin_index)); +} + +ClusterNetId VprClusteringAnnotation::net(const ClusterBlockId& block_id, const int& pin_index) const { + VTR_ASSERT(true == is_net_renamed(block_id, pin_index)); + return net_names_.at(block_id).at(pin_index); +} + +/************************************************************************ + * Public mutators + ***********************************************************************/ +void VprClusteringAnnotation::rename_net(const ClusterBlockId& block_id, const int& pin_index, + const ClusterNetId& net_id) { + /* Warn any override attempt */ + if ( (net_names_.end() != net_names_.find(block_id)) + && (net_names_.at(block_id).end() != net_names_.at(block_id).find(pin_index)) ) { + VTR_LOG_WARN("Override the net '%ld' for block '%ld' pin '%d' with in clustering context annotation!\n", + size_t(net_id), size_t(block_id), pin_index); + } + + net_names_[block_id][pin_index] = net_id; +} + +} /* End namespace openfpga*/ diff --git a/openfpga/src/annotation/vpr_clustering_annotation.h b/openfpga/src/annotation/vpr_clustering_annotation.h new file mode 100644 index 000000000..5918a10b8 --- /dev/null +++ b/openfpga/src/annotation/vpr_clustering_annotation.h @@ -0,0 +1,40 @@ +#ifndef VPR_CLUSTERING_ANNOTATION_H +#define VPR_CLUSTERING_ANNOTATION_H + +/******************************************************************** + * Include header files required by the data structure definition + *******************************************************************/ +#include + +/* Header from vpr library */ +#include "clustered_netlist.h" + +/* Begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * This is the critical data structure to link the pb_type in VPR + * to openfpga annotations + * With a given pb_type pointer, it aims to identify: + * 1. if the pb_type is a physical pb_type or a operating pb_type + * 2. what is the circuit model id linked to a physical pb_type + * 3. what is the physical pb_type for an operating pb_type + * 4. what is the mode pointer that represents the physical mode for a pb_type + *******************************************************************/ +class VprClusteringAnnotation { + public: /* Constructor */ + VprClusteringAnnotation(); + public: /* Public accessors */ + bool is_net_renamed(const ClusterBlockId& block_id, const int& pin_index) const; + ClusterNetId net(const ClusterBlockId& block_id, const int& pin_index) const; + public: /* Public mutators */ + void rename_net(const ClusterBlockId& block_id, const int& pin_index, + const ClusterNetId& net_id); + private: /* Internal data */ + /* Pair a regular pb_type to its physical pb_type */ + std::map> net_names_; +}; + +} /* End namespace openfpga*/ + +#endif diff --git a/openfpga/src/annotation/vpr_routing_annotation.cpp b/openfpga/src/annotation/vpr_routing_annotation.cpp new file mode 100644 index 000000000..0eaa4b5c1 --- /dev/null +++ b/openfpga/src/annotation/vpr_routing_annotation.cpp @@ -0,0 +1,47 @@ +/************************************************************************ + * Member functions for class VprRoutingAnnotation + ***********************************************************************/ +#include "vtr_log.h" +#include "vtr_assert.h" +#include "vpr_routing_annotation.h" + +/* namespace openfpga begins */ +namespace openfpga { + +/************************************************************************ + * Constructors + ***********************************************************************/ +VprRoutingAnnotation::VprRoutingAnnotation() { + return; +} + +/************************************************************************ + * Public accessors + ***********************************************************************/ +ClusterNetId VprRoutingAnnotation::rr_node_net(const RRNodeId& rr_node) const { + /* Ensure that the node_id is in the list */ + VTR_ASSERT(size_t(rr_node) < rr_node_nets_.size()); + return rr_node_nets_[rr_node]; +} + +/************************************************************************ + * Public mutators + ***********************************************************************/ +void VprRoutingAnnotation::init(const RRGraph& rr_graph) { + rr_node_nets_.resize(rr_graph.nodes().size(), ClusterNetId::INVALID()); +} + +void VprRoutingAnnotation::set_rr_node_net(const RRNodeId& rr_node, + const ClusterNetId& net_id) { + /* Ensure that the node_id is in the list */ + VTR_ASSERT(size_t(rr_node) < rr_node_nets_.size()); + /* Warn any override attempt */ + if (ClusterNetId::INVALID() != rr_node_nets_[rr_node]) { + VTR_LOG_WARN("Override the net '%ld' for node'%ld' with in routing context annotation!\n", + size_t(net_id), size_t(rr_node)); + } + + rr_node_nets_[rr_node] = net_id; +} + +} /* End namespace openfpga*/ diff --git a/openfpga/src/annotation/vpr_routing_annotation.h b/openfpga/src/annotation/vpr_routing_annotation.h new file mode 100644 index 000000000..5508ae3b0 --- /dev/null +++ b/openfpga/src/annotation/vpr_routing_annotation.h @@ -0,0 +1,40 @@ +#ifndef VPR_ROUTING_ANNOTATION_H +#define VPR_ROUTING_ANNOTATION_H + +/******************************************************************** + * Include header files required by the data structure definition + *******************************************************************/ +#include + +/* Header from vpr library */ +#include "vpr_context.h" + +/* Begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * This is the critical data structure to link the pb_type in VPR + * to openfpga annotations + * With a given pb_type pointer, it aims to identify: + * 1. if the pb_type is a physical pb_type or a operating pb_type + * 2. what is the circuit model id linked to a physical pb_type + * 3. what is the physical pb_type for an operating pb_type + * 4. what is the mode pointer that represents the physical mode for a pb_type + *******************************************************************/ +class VprRoutingAnnotation { + public: /* Constructor */ + VprRoutingAnnotation(); + public: /* Public accessors */ + ClusterNetId rr_node_net(const RRNodeId& rr_node) const; + public: /* Public mutators */ + void init(const RRGraph& rr_graph); + void set_rr_node_net(const RRNodeId& rr_node, + const ClusterNetId& net_id); + private: /* Internal data */ + /* Pair a regular pb_type to its physical pb_type */ + vtr::vector rr_node_nets_; +}; + +} /* End namespace openfpga*/ + +#endif diff --git a/openfpga/src/base/openfpga_context.h b/openfpga/src/base/openfpga_context.h index cac5877d5..74e01d0de 100644 --- a/openfpga/src/base/openfpga_context.h +++ b/openfpga/src/base/openfpga_context.h @@ -5,6 +5,8 @@ #include "openfpga_arch.h" #include "vpr_netlist_annotation.h" #include "vpr_pb_type_annotation.h" +#include "vpr_clustering_annotation.h" +#include "vpr_routing_annotation.h" /******************************************************************** * This file includes the declaration of the date structure @@ -38,10 +40,14 @@ class OpenfpgaContext : public Context { const openfpga::Arch& arch() const { return arch_; } const openfpga::VprPbTypeAnnotation& vpr_pb_type_annotation() const { return vpr_pb_type_annotation_; } const openfpga::VprNetlistAnnotation& vpr_netlist_annotation() const { return vpr_netlist_annotation_; } + const openfpga::VprClusteringAnnotation& vpr_clustering_annotation() const { return vpr_clustering_annotation_; } + const openfpga::VprRoutingAnnotation& vpr_routing_annotation() const { return vpr_routing_annotation_; } public: /* Public mutators */ openfpga::Arch& mutable_arch() { return arch_; } openfpga::VprPbTypeAnnotation& mutable_vpr_pb_type_annotation() { return vpr_pb_type_annotation_; } openfpga::VprNetlistAnnotation& mutable_vpr_netlist_annotation() { return vpr_netlist_annotation_; } + openfpga::VprClusteringAnnotation& mutable_vpr_clustering_annotation() { return vpr_clustering_annotation_; } + openfpga::VprRoutingAnnotation& mutable_vpr_routing_annotation() { return vpr_routing_annotation_; } private: /* Internal data */ /* Data structure to store information from read_openfpga_arch library */ openfpga::Arch arch_; @@ -49,6 +55,9 @@ class OpenfpgaContext : public Context { openfpga::VprPbTypeAnnotation vpr_pb_type_annotation_; /* Naming fix to netlist */ openfpga::VprNetlistAnnotation vpr_netlist_annotation_; + /* TODO: Pin net fix to cluster results */ + openfpga::VprClusteringAnnotation vpr_clustering_annotation_; + openfpga::VprRoutingAnnotation vpr_routing_annotation_; }; #endif diff --git a/openfpga/src/base/openfpga_link_arch.cpp b/openfpga/src/base/openfpga_link_arch.cpp index 8f7b5b54d..186f95f22 100644 --- a/openfpga/src/base/openfpga_link_arch.cpp +++ b/openfpga/src/base/openfpga_link_arch.cpp @@ -11,6 +11,7 @@ #include "pb_type_utils.h" #include "annotate_pb_types.h" #include "annotate_pb_graph.h" +#include "annotate_routing.h" #include "openfpga_link_arch.h" /* Include global variables of VPR */ @@ -51,6 +52,13 @@ void link_arch(OpenfpgaContext& openfpga_context, annotate_pb_graph(g_vpr_ctx.device(), openfpga_context.mutable_vpr_pb_type_annotation(), cmd_context.option_enable(cmd, opt_verbose)); + + /* Annotate net mapping to each rr_node + */ + openfpga_context.mutable_vpr_routing_annotation().init(g_vpr_ctx.device().rr_graph); + + annotate_rr_node_nets(g_vpr_ctx.clustering(), g_vpr_ctx.routing(), + openfpga_context.mutable_vpr_routing_annotation()); } } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_pb_pin_fixup.cpp b/openfpga/src/base/openfpga_pb_pin_fixup.cpp new file mode 100644 index 000000000..4dc5e0b57 --- /dev/null +++ b/openfpga/src/base/openfpga_pb_pin_fixup.cpp @@ -0,0 +1,184 @@ +/******************************************************************** + * This file includes functions to fix up the pb pin mapping results + * after routing optimization + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_time.h" +#include "vtr_assert.h" +#include "vtr_log.h" + +/* Headers from vpr library */ +#include "vpr_utils.h" + +#include "pb_type_utils.h" +#include "openfpga_pb_pin_fixup.h" + +/* Include global variables of VPR */ +#include "globals.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Give a given pin index, find the side where this pin is located + * on the physical tile + * Note: + * - Need to check if the pin_width_offset and pin_height_offset + * are properly set in VPR!!! + *******************************************************************/ +static +std::vector find_logic_tile_pin_side(t_physical_tile_type_ptr physical_tile, + const int& physical_pin) { + std::vector pin_sides; + for (const e_side& side_cand : {TOP, RIGHT, BOTTOM, LEFT}) { + int pin_width_offset = physical_tile->pin_width_offset[physical_pin]; + int pin_height_offset = physical_tile->pin_height_offset[physical_pin]; + if (true == physical_tile->pinloc[pin_width_offset][pin_height_offset][side_cand][physical_pin]) { + pin_sides.push_back(side_cand); + } + } + + return pin_sides; +} + +/******************************************************************** + * Fix up the pb pin mapping results for a given clustered block + * 1. For each input/output pin of a clustered pb, + * - find a corresponding node in RRGraph object + * - find the net id for the node in routing context + * - find the net id for the node in clustering context + * - if the net id does not match, we update the clustering context + *******************************************************************/ +static +void update_cluster_pin_with_post_routing_results(const DeviceContext& device_ctx, + const ClusteringContext& clustering_ctx, + const VprRoutingAnnotation& vpr_routing_annotation, + VprClusteringAnnotation& vpr_clustering_annotation, + const vtr::Point& grid_coord, + const ClusterBlockId& blk_id, + const bool& verbose) { + /* Handle each pin */ + auto logical_block = clustering_ctx.clb_nlist.block_type(blk_id); + auto physical_tile = pick_best_physical_type(logical_block); + + for (int j = 0; j < logical_block->pb_type->num_pins; j++) { + /* Get the ptc num for the pin in rr_graph */ + int physical_pin = get_physical_pin(physical_tile, logical_block, j); + auto pin_class = physical_tile->pin_class[physical_pin]; + auto class_inf = physical_tile->class_inf[pin_class]; + t_rr_type rr_node_type; + if (class_inf.type == DRIVER) { + rr_node_type = OPIN; + } else { + VTR_ASSERT(class_inf.type == RECEIVER); + rr_node_type = IPIN; + } + std::vector pin_sides = find_logic_tile_pin_side(physical_tile, physical_pin); + /* As some grid go across columns or rows, we may not have the pin on any side */ + if (0 == pin_sides.size()) { + continue; + } + + for (const e_side& pin_side : pin_sides) { + /* Find the net mapped to this pin in routing results */ + const RRNodeId& rr_node = device_ctx.rr_graph.find_node(grid_coord.x(), grid_coord.y(), rr_node_type, physical_pin, pin_side); + if (false == device_ctx.rr_graph.valid_node_id(rr_node)) { + continue; + } + /* Get the cluster net id which has been mapped to this net */ + ClusterNetId routing_net_id = vpr_routing_annotation.rr_node_net(rr_node); + + /* Find the net mapped to this pin in clustering results*/ + ClusterNetId cluster_net_id = clustering_ctx.clb_nlist.block_net(blk_id, j); + + /* If matched, we finish here */ + if (routing_net_id == cluster_net_id) { + continue; + } + /* Add to net modification */ + vpr_clustering_annotation.rename_net(blk_id, j, routing_net_id); + + std::string routing_net_name("unmapped"); + if (ClusterNetId::INVALID() != routing_net_id) { + routing_net_name = clustering_ctx.clb_nlist.net_name(routing_net_id); + } + + std::string cluster_net_name("unmapped"); + if (ClusterNetId::INVALID() != cluster_net_id) { + cluster_net_name = clustering_ctx.clb_nlist.net_name(cluster_net_id); + } + + VTR_LOGV(verbose, + "Fixed up net '%s' mapping mismatch at clustered block '%s' pin '%s[%d]' (was net '%s')\n", + routing_net_name.c_str(), + clustering_ctx.clb_nlist.block_pb(blk_id)->name, + get_pb_graph_node_pin_from_block_pin(blk_id, physical_pin)->port->name, + get_pb_graph_node_pin_from_block_pin(blk_id, physical_pin)->pin_number, + cluster_net_name.c_str() + ); + } + } +} + +/******************************************************************** + * Main function to fix up the pb pin mapping results + * This function will walk through each grid + *******************************************************************/ +static +void update_pb_pin_with_post_routing_results(const DeviceContext& device_ctx, + const ClusteringContext& clustering_ctx, + const PlacementContext& placement_ctx, + const VprRoutingAnnotation& vpr_routing_annotation, + VprClusteringAnnotation& vpr_clustering_annotation, + const bool& verbose) { + for (size_t x = 0; x < device_ctx.grid.width(); ++x) { + for (size_t y = 0; y < device_ctx.grid.height(); ++y) { + /* Bypass the EMPTY tiles */ + if (device_ctx.EMPTY_PHYSICAL_TILE_TYPE == device_ctx.grid[x][y].type) { + continue; + } + /* Get the mapped blocks to this grid */ + for (const ClusterBlockId& cluster_blk_id : placement_ctx.grid_blocks[x][y].blocks) { + /* Skip invalid ids */ + if (ClusterBlockId::INVALID() == cluster_blk_id) { + continue; + } + /* We know the entrance to grid info and mapping results, do the fix-up for this block */ + vtr::Point grid_coord(x, y); + update_cluster_pin_with_post_routing_results(device_ctx, clustering_ctx, + vpr_routing_annotation, vpr_clustering_annotation, + grid_coord, cluster_blk_id, + verbose); + } + } + } +} + +/******************************************************************** + * Top-level function to fix up the pb pin mapping results + * The problem comes from a mismatch between the packing and routing results + * When there are equivalent input/output for any grids, router will try + * to swap the net mapping among these pins so as to achieve best + * routing optimization. + * However, it will cause the packing results out-of-date as the net mapping + * of each grid remain untouched once packing is done. + * This function aims to fix the mess after routing so that the net mapping + * can be synchronized + *******************************************************************/ +void pb_pin_fixup(OpenfpgaContext& openfpga_context, + const Command& cmd, const CommandContext& cmd_context) { + + vtr::ScopedStartFinishTimer timer("Fix up pb pin mapping results after routing optimization"); + + CommandOptionId opt_verbose = cmd.option("verbose"); + + /* Apply fix-up to each grid */ + update_pb_pin_with_post_routing_results(g_vpr_ctx.device(), + g_vpr_ctx.clustering(), + g_vpr_ctx.placement(), + openfpga_context.vpr_routing_annotation(), + openfpga_context.mutable_vpr_clustering_annotation(), + cmd_context.option_enable(cmd, opt_verbose)); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_pb_pin_fixup.h b/openfpga/src/base/openfpga_pb_pin_fixup.h new file mode 100644 index 000000000..e924c6067 --- /dev/null +++ b/openfpga/src/base/openfpga_pb_pin_fixup.h @@ -0,0 +1,23 @@ +#ifndef OPENFPGA_PB_PIN_FIXUP_H +#define OPENFPGA_PB_PIN_FIXUP_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "command.h" +#include "command_context.h" +#include "openfpga_context.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void pb_pin_fixup(OpenfpgaContext& openfpga_context, + const Command& cmd, const CommandContext& cmd_context); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/base/openfpga_setup_command.cpp b/openfpga/src/base/openfpga_setup_command.cpp index ef392a4c4..2a1edacbc 100644 --- a/openfpga/src/base/openfpga_setup_command.cpp +++ b/openfpga/src/base/openfpga_setup_command.cpp @@ -5,6 +5,7 @@ *******************************************************************/ #include "openfpga_read_arch.h" #include "openfpga_link_arch.h" +#include "openfpga_pb_pin_fixup.h" #include "check_netlist_naming_conflict.h" #include "openfpga_setup_command.h" @@ -79,9 +80,26 @@ void add_openfpga_setup_commands(openfpga::Shell& shell) { ShellCommandId shell_cmd_check_netlist_naming_conflict_id = shell.add_command(shell_cmd_check_netlist_naming_conflict, "Check any block/net naming in users' BLIF netlist violates the syntax of fabric generator"); shell.set_command_class(shell_cmd_check_netlist_naming_conflict_id, openfpga_setup_cmd_class); shell.set_command_execute_function(shell_cmd_check_netlist_naming_conflict_id, check_netlist_naming_conflict); - /* The 'link_openfpga_arch' command should NOT be executed before 'read_openfpga_arch' and 'vpr' */ + /* The 'link_openfpga_arch' command should NOT be executed before 'vpr' */ std::vector cmd_dependency_check_netlist_naming_conflict(1, shell_cmd_vpr_id); shell.set_command_dependency(shell_cmd_link_openfpga_arch_id, cmd_dependency_check_netlist_naming_conflict); + + /******************************** + * Command 'pb_pin_fixup' + */ + Command shell_cmd_pb_pin_fixup("pb_pin_fixup"); + /* Add an option '--verbose' */ + shell_cmd_pb_pin_fixup.add_option("verbose", false, "Show verbose outputs"); + + /* Add command 'pb_pin_fixup' to the Shell */ + ShellCommandId shell_cmd_pb_pin_fixup_id = shell.add_command(shell_cmd_pb_pin_fixup, "Fix up the packing results due to pin swapping during routing stage"); + shell.set_command_class(shell_cmd_pb_pin_fixup_id, openfpga_setup_cmd_class); + shell.set_command_execute_function(shell_cmd_pb_pin_fixup_id, pb_pin_fixup); + /* The 'pb_pin_fixup' command should NOT be executed before 'read_openfpga_arch' and 'vpr' */ + std::vector cmd_dependency_pb_pin_fixup; + cmd_dependency_pb_pin_fixup.push_back(shell_cmd_read_arch_id); + cmd_dependency_pb_pin_fixup.push_back(shell_cmd_vpr_id); + shell.set_command_dependency(shell_cmd_pb_pin_fixup_id, cmd_dependency_pb_pin_fixup); } } /* end namespace openfpga */ diff --git a/openfpga/test_script/s298_k6_frac.openfpga b/openfpga/test_script/s298_k6_frac.openfpga index fa3dcf993..9681918c4 100644 --- a/openfpga/test_script/s298_k6_frac.openfpga +++ b/openfpga/test_script/s298_k6_frac.openfpga @@ -10,5 +10,8 @@ link_openfpga_arch # Check and correct any naming conflicts in the BLIF netlist check_netlist_naming_conflict --fix --report ./netlist_renaming.xml +# Apply fix-up to clustering nets based on routing results +pb_pin_fixup --verbose + # Finish and exit OpenFPGA exit From cccbb9fd4942cd54506dc244db6566b44c66bf5b Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 5 Feb 2020 22:12:44 -0700 Subject: [PATCH 105/645] add missing files --- openfpga/src/annotation/annotate_routing.cpp | 47 ++++++++++++++++++++ openfpga/src/annotation/annotate_routing.h | 24 ++++++++++ 2 files changed, 71 insertions(+) create mode 100644 openfpga/src/annotation/annotate_routing.cpp create mode 100644 openfpga/src/annotation/annotate_routing.h diff --git a/openfpga/src/annotation/annotate_routing.cpp b/openfpga/src/annotation/annotate_routing.cpp new file mode 100644 index 000000000..2f16a2bff --- /dev/null +++ b/openfpga/src/annotation/annotate_routing.cpp @@ -0,0 +1,47 @@ +/******************************************************************** + * This file includes functions that are used to annotate routing results + * from VPR to OpenFPGA + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" + +#include "annotate_routing.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Create a mapping between each rr_node and its mapped nets + * based on VPR routing results + * - Unmapped rr_node will use invalid ids + *******************************************************************/ +void annotate_rr_node_nets(const ClusteringContext& vpr_clustering_ctx, + const RoutingContext& vpr_routing_ctx, + VprRoutingAnnotation& vpr_routing_annotation) { + size_t counter = 0; + VTR_LOG("Annotating rr_node with routed nets..."); + + for (auto net_id : vpr_clustering_ctx.clb_nlist.nets()) { + /* Ignore nets that are not routed */ + if (true == vpr_clustering_ctx.clb_nlist.net_is_ignored(net_id)) { + continue; + } + /* Ignore used in local cluster only, reserved one CLB pin */ + if (false == vpr_clustering_ctx.clb_nlist.net_sinks(net_id).size()) { + continue; + } + t_trace* tptr = vpr_routing_ctx.trace[net_id].head; + while (tptr != nullptr) { + RRNodeId rr_node = tptr->index; + vpr_routing_annotation.set_rr_node_net(rr_node, net_id); + counter++; + tptr = tptr->next; + } + } + + VTR_LOG("Done with %d nodes mapping\n", counter); +} + +} /* end namespace openfpga */ + diff --git a/openfpga/src/annotation/annotate_routing.h b/openfpga/src/annotation/annotate_routing.h new file mode 100644 index 000000000..e0c571278 --- /dev/null +++ b/openfpga/src/annotation/annotate_routing.h @@ -0,0 +1,24 @@ +#ifndef ANNOTATE_ROUTING_H +#define ANNOTATE_ROUTING_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "vpr_context.h" +#include "openfpga_context.h" +#include "vpr_routing_annotation.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void annotate_rr_node_nets(const ClusteringContext& vpr_clustering_ctx, + const RoutingContext& vpr_routing_ctx, + VprRoutingAnnotation& vpr_routing_annotation); + +} /* end namespace openfpga */ + +#endif From 99f5a86b49e979a66e7d31d4455cbe4c2c776e29 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 6 Feb 2020 12:54:55 -0700 Subject: [PATCH 106/645] bug fixed for routing annotation and routing net fix-up --- libopenfpga/libopenfpgautil/CMakeLists.txt | 1 + .../src/openfpga_side_manager.cpp | 175 ++++++++++++++++++ .../src/openfpga_side_manager.h | 49 +++++ openfpga/src/annotation/annotate_routing.cpp | 25 ++- openfpga/src/annotation/annotate_routing.h | 8 +- .../src/annotation/vpr_routing_annotation.cpp | 2 +- openfpga/src/base/openfpga_link_arch.cpp | 5 +- openfpga/src/base/openfpga_pb_pin_fixup.cpp | 165 ++++++++++++----- 8 files changed, 373 insertions(+), 57 deletions(-) create mode 100644 libopenfpga/libopenfpgautil/src/openfpga_side_manager.cpp create mode 100644 libopenfpga/libopenfpgautil/src/openfpga_side_manager.h diff --git a/libopenfpga/libopenfpgautil/CMakeLists.txt b/libopenfpga/libopenfpgautil/CMakeLists.txt index 66c3dc471..9cb09e7b4 100644 --- a/libopenfpga/libopenfpgautil/CMakeLists.txt +++ b/libopenfpga/libopenfpgautil/CMakeLists.txt @@ -19,6 +19,7 @@ set_target_properties(libopenfpgautil PROPERTIES PREFIX "") #Avoid extra 'lib' p #Specify link-time dependancies target_link_libraries(libopenfpgautil + libarchfpga libvtrutil) #Create the test executable diff --git a/libopenfpga/libopenfpgautil/src/openfpga_side_manager.cpp b/libopenfpga/libopenfpgautil/src/openfpga_side_manager.cpp new file mode 100644 index 000000000..00ff9912c --- /dev/null +++ b/libopenfpga/libopenfpgautil/src/openfpga_side_manager.cpp @@ -0,0 +1,175 @@ +/******************************************************************** + * Memeber function for class SideManagerManager + *******************************************************************/ +#include "openfpga_side_manager.h" + +/* namespace openfpga begins */ +namespace openfpga { + +/* Constructors */ +SideManager::SideManager(enum e_side side) { + side_ = side; +} + +SideManager::SideManager() { + side_ = NUM_SIDES; +} + +SideManager::SideManager(size_t side) { + set_side(side); +} + +/* Public Accessors */ +enum e_side SideManager::get_side() const { + return side_; +} + +enum e_side SideManager::get_opposite() const { + switch (side_) { + case TOP: + return BOTTOM; + case RIGHT: + return LEFT; + case BOTTOM: + return TOP; + case LEFT: + return RIGHT; + default: + return NUM_SIDES; + } +} + +enum e_side SideManager::get_rotate_clockwise() const { + switch (side_) { + case TOP: + return RIGHT; + case RIGHT: + return BOTTOM; + case BOTTOM: + return LEFT; + case LEFT: + return TOP; + default: + return NUM_SIDES; + } +} + +enum e_side SideManager::get_rotate_counterclockwise() const { + switch (side_) { + case TOP: + return LEFT; + case RIGHT: + return TOP; + case BOTTOM: + return RIGHT; + case LEFT: + return BOTTOM; + default: + return NUM_SIDES; + } +} + +bool SideManager::validate() const { + if (NUM_SIDES == side_) { + return false; + } + return true; +} + +size_t SideManager::to_size_t() const { + switch (side_) { + case TOP: + return 0; + case RIGHT: + return 1; + case BOTTOM: + return 2; + case LEFT: + return 3; + default: + return 4; + } +} + +/* Convert to char* */ +const char* SideManager::c_str() const { + switch (side_) { + case TOP: + return "top"; + case RIGHT: + return "right"; + case BOTTOM: + return "bottom"; + case LEFT: + return "left"; + default: + return "invalid_side"; + } +} + +/* Convert to char* */ +std::string SideManager::to_string() const { + std::string ret; + switch (side_) { + case TOP: + ret.assign("top"); + break; + case RIGHT: + ret.assign("right"); + break; + case BOTTOM: + ret.assign("bottom"); + break; + case LEFT: + ret.assign("left"); + break; + default: + ret.assign("invalid_side"); + break; + } + + return ret; +} + +/* Public Mutators */ +void SideManager::set_side(size_t side) { + switch (side) { + case 0: + side_ = TOP; + return; + case 1: + side_ = RIGHT; + return; + case 2: + side_ = BOTTOM; + return; + case 3: + side_ = LEFT; + return; + default: + side_ = NUM_SIDES; + return; + } +} + +void SideManager::set_side(enum e_side side) { + side_ = side; + return; +} + +void SideManager::set_opposite() { + side_ = get_opposite(); + return; +} + +void SideManager::rotate_clockwise() { + side_ = get_rotate_clockwise(); + return; +} + +void SideManager::rotate_counterclockwise() { + side_ = get_rotate_counterclockwise(); + return; +} + +} /* namespace openfpga ends */ diff --git a/libopenfpga/libopenfpgautil/src/openfpga_side_manager.h b/libopenfpga/libopenfpgautil/src/openfpga_side_manager.h new file mode 100644 index 000000000..4903f7b4b --- /dev/null +++ b/libopenfpga/libopenfpgautil/src/openfpga_side_manager.h @@ -0,0 +1,49 @@ +#ifndef OPENFPGA_SIDE_MANAGER_H +#define OPENFPGA_SIDE_MANAGER_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include + +/* Header files form archfpga library */ +#include "physical_types.h" + +/* namespace openfpga begins */ +namespace openfpga { + +/******************************************************************** + * Define a class for the sides of a physical block in FPGA architecture + * Basically, each block has four sides : + * TOP, RIGHT, BOTTOM, LEFT + * This class aims to provide a easy proctol for manipulating a side + ********************************************************************/ + +class SideManager { + public: /* Constructor */ + SideManager(enum e_side side); + SideManager(); + SideManager(size_t side); + public: /* Accessors */ + enum e_side get_side() const; + enum e_side get_opposite() const; + enum e_side get_rotate_clockwise() const; + enum e_side get_rotate_counterclockwise() const; + bool validate() const; + size_t to_size_t() const; + const char* c_str() const; + std::string to_string() const; + public: /* Mutators */ + void set_side(size_t side); + void set_side(enum e_side side); + void set_opposite(); + void rotate_clockwise(); + void rotate_counterclockwise(); + private: /* internal data */ + enum e_side side_; +}; + +} /* namespace openfpga ends */ + +#endif diff --git a/openfpga/src/annotation/annotate_routing.cpp b/openfpga/src/annotation/annotate_routing.cpp index 2f16a2bff..5e9dc4144 100644 --- a/openfpga/src/annotation/annotate_routing.cpp +++ b/openfpga/src/annotation/annotate_routing.cpp @@ -16,26 +16,33 @@ namespace openfpga { * based on VPR routing results * - Unmapped rr_node will use invalid ids *******************************************************************/ -void annotate_rr_node_nets(const ClusteringContext& vpr_clustering_ctx, - const RoutingContext& vpr_routing_ctx, - VprRoutingAnnotation& vpr_routing_annotation) { +void annotate_rr_node_nets(const DeviceContext& device_ctx, + const ClusteringContext& clustering_ctx, + const RoutingContext& routing_ctx, + VprRoutingAnnotation& vpr_routing_annotation, + const bool& verbose) { size_t counter = 0; VTR_LOG("Annotating rr_node with routed nets..."); + VTR_LOGV(verbose, "\n"); - for (auto net_id : vpr_clustering_ctx.clb_nlist.nets()) { + for (auto net_id : clustering_ctx.clb_nlist.nets()) { /* Ignore nets that are not routed */ - if (true == vpr_clustering_ctx.clb_nlist.net_is_ignored(net_id)) { + if (true == clustering_ctx.clb_nlist.net_is_ignored(net_id)) { continue; } /* Ignore used in local cluster only, reserved one CLB pin */ - if (false == vpr_clustering_ctx.clb_nlist.net_sinks(net_id).size()) { + if (false == clustering_ctx.clb_nlist.net_sinks(net_id).size()) { continue; } - t_trace* tptr = vpr_routing_ctx.trace[net_id].head; + t_trace* tptr = routing_ctx.trace[net_id].head; while (tptr != nullptr) { RRNodeId rr_node = tptr->index; - vpr_routing_annotation.set_rr_node_net(rr_node, net_id); - counter++; + /* Ignore source and sink nodes, they are the common node multiple starting and ending points */ + if ( (SOURCE != device_ctx.rr_graph.node_type(rr_node)) + && (SINK != device_ctx.rr_graph.node_type(rr_node)) ) { + vpr_routing_annotation.set_rr_node_net(rr_node, net_id); + counter++; + } tptr = tptr->next; } } diff --git a/openfpga/src/annotation/annotate_routing.h b/openfpga/src/annotation/annotate_routing.h index e0c571278..9496c6e81 100644 --- a/openfpga/src/annotation/annotate_routing.h +++ b/openfpga/src/annotation/annotate_routing.h @@ -15,9 +15,11 @@ /* begin namespace openfpga */ namespace openfpga { -void annotate_rr_node_nets(const ClusteringContext& vpr_clustering_ctx, - const RoutingContext& vpr_routing_ctx, - VprRoutingAnnotation& vpr_routing_annotation); +void annotate_rr_node_nets(const DeviceContext& device_ctx, + const ClusteringContext& clustering_ctx, + const RoutingContext& routing_ctx, + VprRoutingAnnotation& vpr_routing_annotation, + const bool& verbose); } /* end namespace openfpga */ diff --git a/openfpga/src/annotation/vpr_routing_annotation.cpp b/openfpga/src/annotation/vpr_routing_annotation.cpp index 0eaa4b5c1..1c17450ed 100644 --- a/openfpga/src/annotation/vpr_routing_annotation.cpp +++ b/openfpga/src/annotation/vpr_routing_annotation.cpp @@ -37,7 +37,7 @@ void VprRoutingAnnotation::set_rr_node_net(const RRNodeId& rr_node, VTR_ASSERT(size_t(rr_node) < rr_node_nets_.size()); /* Warn any override attempt */ if (ClusterNetId::INVALID() != rr_node_nets_[rr_node]) { - VTR_LOG_WARN("Override the net '%ld' for node'%ld' with in routing context annotation!\n", + VTR_LOG_WARN("Override the net '%ld' for node '%ld' with in routing context annotation!\n", size_t(net_id), size_t(rr_node)); } diff --git a/openfpga/src/base/openfpga_link_arch.cpp b/openfpga/src/base/openfpga_link_arch.cpp index 186f95f22..dee7662a6 100644 --- a/openfpga/src/base/openfpga_link_arch.cpp +++ b/openfpga/src/base/openfpga_link_arch.cpp @@ -57,8 +57,9 @@ void link_arch(OpenfpgaContext& openfpga_context, */ openfpga_context.mutable_vpr_routing_annotation().init(g_vpr_ctx.device().rr_graph); - annotate_rr_node_nets(g_vpr_ctx.clustering(), g_vpr_ctx.routing(), - openfpga_context.mutable_vpr_routing_annotation()); + annotate_rr_node_nets(g_vpr_ctx.device(), g_vpr_ctx.clustering(), g_vpr_ctx.routing(), + openfpga_context.mutable_vpr_routing_annotation(), + cmd_context.option_enable(cmd, opt_verbose)); } } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_pb_pin_fixup.cpp b/openfpga/src/base/openfpga_pb_pin_fixup.cpp index 4dc5e0b57..1c0beee27 100644 --- a/openfpga/src/base/openfpga_pb_pin_fixup.cpp +++ b/openfpga/src/base/openfpga_pb_pin_fixup.cpp @@ -10,6 +10,9 @@ /* Headers from vpr library */ #include "vpr_utils.h" +/* Headers from openfpgautil library */ +#include "openfpga_side_manager.h" + #include "pb_type_utils.h" #include "openfpga_pb_pin_fixup.h" @@ -56,6 +59,7 @@ void update_cluster_pin_with_post_routing_results(const DeviceContext& device_ct VprClusteringAnnotation& vpr_clustering_annotation, const vtr::Point& grid_coord, const ClusterBlockId& blk_id, + const e_side& border_side, const bool& verbose) { /* Handle each pin */ auto logical_block = clustering_ctx.clb_nlist.block_type(blk_id); @@ -66,6 +70,7 @@ void update_cluster_pin_with_post_routing_results(const DeviceContext& device_ct int physical_pin = get_physical_pin(physical_tile, logical_block, j); auto pin_class = physical_tile->pin_class[physical_pin]; auto class_inf = physical_tile->class_inf[pin_class]; + t_rr_type rr_node_type; if (class_inf.type == DRIVER) { rr_node_type = OPIN; @@ -74,49 +79,74 @@ void update_cluster_pin_with_post_routing_results(const DeviceContext& device_ct rr_node_type = IPIN; } std::vector pin_sides = find_logic_tile_pin_side(physical_tile, physical_pin); - /* As some grid go across columns or rows, we may not have the pin on any side */ + /* As some grid has height/width offset, we may not have the pin on any side */ if (0 == pin_sides.size()) { continue; } - for (const e_side& pin_side : pin_sides) { - /* Find the net mapped to this pin in routing results */ - const RRNodeId& rr_node = device_ctx.rr_graph.find_node(grid_coord.x(), grid_coord.y(), rr_node_type, physical_pin, pin_side); - if (false == device_ctx.rr_graph.valid_node_id(rr_node)) { - continue; - } - /* Get the cluster net id which has been mapped to this net */ - ClusterNetId routing_net_id = vpr_routing_annotation.rr_node_net(rr_node); - - /* Find the net mapped to this pin in clustering results*/ - ClusterNetId cluster_net_id = clustering_ctx.clb_nlist.block_net(blk_id, j); - - /* If matched, we finish here */ - if (routing_net_id == cluster_net_id) { - continue; - } - /* Add to net modification */ - vpr_clustering_annotation.rename_net(blk_id, j, routing_net_id); - - std::string routing_net_name("unmapped"); - if (ClusterNetId::INVALID() != routing_net_id) { - routing_net_name = clustering_ctx.clb_nlist.net_name(routing_net_id); - } - - std::string cluster_net_name("unmapped"); - if (ClusterNetId::INVALID() != cluster_net_id) { - cluster_net_name = clustering_ctx.clb_nlist.net_name(cluster_net_id); - } - - VTR_LOGV(verbose, - "Fixed up net '%s' mapping mismatch at clustered block '%s' pin '%s[%d]' (was net '%s')\n", - routing_net_name.c_str(), - clustering_ctx.clb_nlist.block_pb(blk_id)->name, - get_pb_graph_node_pin_from_block_pin(blk_id, physical_pin)->port->name, - get_pb_graph_node_pin_from_block_pin(blk_id, physical_pin)->pin_number, - cluster_net_name.c_str() - ); + /* For regular grid, we should have pin only one side! + * I/O grids: VPR creates the grid with duplicated pins on every side + * but the expected side (only used side) will be opposite side of the border side! + */ + e_side pin_side = NUM_SIDES; + if (NUM_SIDES == border_side) { + VTR_ASSERT(1 == pin_sides.size()); + pin_side = pin_sides[0]; + } else { + SideManager side_manager(border_side); + VTR_ASSERT(pin_sides.end() != std::find(pin_sides.begin(), pin_sides.end(), side_manager.get_opposite())); + pin_side = side_manager.get_opposite(); } + + /* Find the net mapped to this pin in routing results */ + const RRNodeId& rr_node = device_ctx.rr_graph.find_node(grid_coord.x(), grid_coord.y(), rr_node_type, physical_pin, pin_side); + if (false == device_ctx.rr_graph.valid_node_id(rr_node)) { + continue; + } + /* Get the cluster net id which has been mapped to this net */ + ClusterNetId routing_net_id = vpr_routing_annotation.rr_node_net(rr_node); + + /* Find the net mapped to this pin in clustering results*/ + ClusterNetId cluster_net_id = clustering_ctx.clb_nlist.block_net(blk_id, j); + + /* Ignore those net have never been routed */ + if ( (ClusterNetId::INVALID() != cluster_net_id) + && (true == clustering_ctx.clb_nlist.net_is_ignored(cluster_net_id))) { + continue; + } + + /* Ignore used in local cluster only, reserved one CLB pin */ + if (false == clustering_ctx.clb_nlist.net_sinks(cluster_net_id).size()) { + continue; + } + + /* If matched, we finish here */ + if (routing_net_id == cluster_net_id) { + continue; + } + /* Add to net modification */ + vpr_clustering_annotation.rename_net(blk_id, j, routing_net_id); + + std::string routing_net_name("unmapped"); + if (ClusterNetId::INVALID() != routing_net_id) { + routing_net_name = clustering_ctx.clb_nlist.net_name(routing_net_id); + } + + std::string cluster_net_name("unmapped"); + if (ClusterNetId::INVALID() != cluster_net_id) { + cluster_net_name = clustering_ctx.clb_nlist.net_name(cluster_net_id); + } + + VTR_LOGV(verbose, + "Fixed up net '%s' mapping mismatch at clustered block '%s' pin 'grid[%ld][%ld].%s.%s[%d]' (was net '%s')\n", + routing_net_name.c_str(), + clustering_ctx.clb_nlist.block_pb(blk_id)->name, + grid_coord.x(), grid_coord.y(), + clustering_ctx.clb_nlist.block_pb(blk_id)->pb_graph_node->pb_type->name, + get_pb_graph_node_pin_from_block_pin(blk_id, physical_pin)->port->name, + get_pb_graph_node_pin_from_block_pin(blk_id, physical_pin)->pin_number, + cluster_net_name.c_str() + ); } } @@ -131,12 +161,15 @@ void update_pb_pin_with_post_routing_results(const DeviceContext& device_ctx, const VprRoutingAnnotation& vpr_routing_annotation, VprClusteringAnnotation& vpr_clustering_annotation, const bool& verbose) { - for (size_t x = 0; x < device_ctx.grid.width(); ++x) { - for (size_t y = 0; y < device_ctx.grid.height(); ++y) { + /* Update the core logic (center blocks of the FPGA) */ + for (size_t x = 1; x < device_ctx.grid.width() - 1; ++x) { + for (size_t y = 1; y < device_ctx.grid.height() - 1; ++y) { /* Bypass the EMPTY tiles */ - if (device_ctx.EMPTY_PHYSICAL_TILE_TYPE == device_ctx.grid[x][y].type) { + if (true == is_empty_type(device_ctx.grid[x][y].type)) { continue; } + /* We must have an regular (non-I/O) type here */ + VTR_ASSERT(false == is_io_type(device_ctx.grid[x][y].type)); /* Get the mapped blocks to this grid */ for (const ClusterBlockId& cluster_blk_id : placement_ctx.grid_blocks[x][y].blocks) { /* Skip invalid ids */ @@ -147,11 +180,59 @@ void update_pb_pin_with_post_routing_results(const DeviceContext& device_ctx, vtr::Point grid_coord(x, y); update_cluster_pin_with_post_routing_results(device_ctx, clustering_ctx, vpr_routing_annotation, vpr_clustering_annotation, - grid_coord, cluster_blk_id, + grid_coord, cluster_blk_id, NUM_SIDES, verbose); } } } + + /* Update the periperal I/O blocks at fours sides of FPGA */ + std::vector io_sides{TOP, RIGHT, BOTTOM, LEFT}; + std::map>> io_coords; + + /* TOP side */ + for (size_t x = 1; x < device_ctx.grid.width() - 1; ++x) { + io_coords[TOP].push_back(vtr::Point(x, device_ctx.grid.height() -1)); + } + + /* RIGHT side */ + for (size_t y = 1; y < device_ctx.grid.height() - 1; ++y) { + io_coords[RIGHT].push_back(vtr::Point(device_ctx.grid.width() -1, y)); + } + + /* BOTTOM side */ + for (size_t x = 1; x < device_ctx.grid.width() - 1; ++x) { + io_coords[BOTTOM].push_back(vtr::Point(x, 0)); + } + + /* LEFT side */ + for (size_t y = 1; y < device_ctx.grid.height() - 1; ++y) { + io_coords[LEFT].push_back(vtr::Point(0, y)); + } + + /* Walk through io grid on by one */ + for (const e_side& io_side : io_sides) { + for (const vtr::Point& io_coord : io_coords[io_side]) { + /* Bypass EMPTY grid */ + if (true == is_empty_type(device_ctx.grid[io_coord.x()][io_coord.y()].type)) { + continue; + } + /* We must have an I/O type here */ + VTR_ASSERT(true == is_io_type(device_ctx.grid[io_coord.x()][io_coord.y()].type)); + /* Get the mapped blocks to this grid */ + for (const ClusterBlockId& cluster_blk_id : placement_ctx.grid_blocks[io_coord.x()][io_coord.y()].blocks) { + /* Skip invalid ids */ + if (ClusterBlockId::INVALID() == cluster_blk_id) { + continue; + } + /* Update on I/O grid */ + update_cluster_pin_with_post_routing_results(device_ctx, clustering_ctx, + vpr_routing_annotation, vpr_clustering_annotation, + io_coord, cluster_blk_id, io_side, + verbose); + } + } + } } /******************************************************************** From ed9e0388450b766a9893f5bab55b4635ec47edd9 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 6 Feb 2020 17:14:29 -0700 Subject: [PATCH 107/645] add functionality of LUT truth table fix-up --- .../annotation/vpr_clustering_annotation.cpp | 21 ++ .../annotation/vpr_clustering_annotation.h | 8 + .../base/openfpga_lut_truth_table_fixup.cpp | 185 ++++++++++++++++++ .../src/base/openfpga_lut_truth_table_fixup.h | 23 +++ openfpga/src/base/openfpga_setup_command.cpp | 18 ++ openfpga/src/utils/lut_utils.cpp | 93 +++++++++ openfpga/src/utils/lut_utils.h | 25 +++ openfpga/test_script/s298_k6_frac.openfpga | 3 + 8 files changed, 376 insertions(+) create mode 100644 openfpga/src/base/openfpga_lut_truth_table_fixup.cpp create mode 100644 openfpga/src/base/openfpga_lut_truth_table_fixup.h create mode 100644 openfpga/src/utils/lut_utils.cpp create mode 100644 openfpga/src/utils/lut_utils.h diff --git a/openfpga/src/annotation/vpr_clustering_annotation.cpp b/openfpga/src/annotation/vpr_clustering_annotation.cpp index 747978023..cd32fdeac 100644 --- a/openfpga/src/annotation/vpr_clustering_annotation.cpp +++ b/openfpga/src/annotation/vpr_clustering_annotation.cpp @@ -31,6 +31,16 @@ ClusterNetId VprClusteringAnnotation::net(const ClusterBlockId& block_id, const return net_names_.at(block_id).at(pin_index); } +bool VprClusteringAnnotation::is_truth_table_adapted(t_pb* pb) const { + /* Ensure that the block_id is in the list */ + return (block_truth_tables_.end() != block_truth_tables_.find(pb)); +} + +AtomNetlist::TruthTable VprClusteringAnnotation::truth_table(t_pb* pb) const { + VTR_ASSERT(true == is_truth_table_adapted(pb)); + return block_truth_tables_.at(pb); +} + /************************************************************************ * Public mutators ***********************************************************************/ @@ -46,4 +56,15 @@ void VprClusteringAnnotation::rename_net(const ClusterBlockId& block_id, const i net_names_[block_id][pin_index] = net_id; } +void VprClusteringAnnotation::adapt_truth_table(t_pb* pb, + const AtomNetlist::TruthTable& tt) { + /* Warn any override attempt */ + if (block_truth_tables_.end() != block_truth_tables_.find(pb)) { + VTR_LOG_WARN("Override the truth table for pb '%s' in clustering context annotation!\n", + pb->name); + } + + block_truth_tables_[pb] = tt; +} + } /* End namespace openfpga*/ diff --git a/openfpga/src/annotation/vpr_clustering_annotation.h b/openfpga/src/annotation/vpr_clustering_annotation.h index 5918a10b8..2951ef226 100644 --- a/openfpga/src/annotation/vpr_clustering_annotation.h +++ b/openfpga/src/annotation/vpr_clustering_annotation.h @@ -25,14 +25,22 @@ class VprClusteringAnnotation { public: /* Constructor */ VprClusteringAnnotation(); public: /* Public accessors */ + /* Xifan Tang: I created two functions for each data query in purpose! + * As this is an annotation, some net/block may be changed to invalid id + * In this case, return an invalid value does not mean that a net is not renamed + */ bool is_net_renamed(const ClusterBlockId& block_id, const int& pin_index) const; ClusterNetId net(const ClusterBlockId& block_id, const int& pin_index) const; + bool is_truth_table_adapted(t_pb* pb) const; + AtomNetlist::TruthTable truth_table(t_pb* pb) const; public: /* Public mutators */ void rename_net(const ClusterBlockId& block_id, const int& pin_index, const ClusterNetId& net_id); + void adapt_truth_table(t_pb* pb, const AtomNetlist::TruthTable& tt); private: /* Internal data */ /* Pair a regular pb_type to its physical pb_type */ std::map> net_names_; + std::map block_truth_tables_; }; } /* End namespace openfpga*/ diff --git a/openfpga/src/base/openfpga_lut_truth_table_fixup.cpp b/openfpga/src/base/openfpga_lut_truth_table_fixup.cpp new file mode 100644 index 000000000..9280e96bc --- /dev/null +++ b/openfpga/src/base/openfpga_lut_truth_table_fixup.cpp @@ -0,0 +1,185 @@ +/******************************************************************** + * This file includes functions to fix up the pb pin mapping results + * after routing optimization + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_time.h" +#include "vtr_assert.h" +#include "vtr_log.h" + +/* Headers from vpr library */ +#include "vpr_utils.h" + +#include "pb_type_utils.h" +#include "lut_utils.h" +#include "openfpga_lut_truth_table_fixup.h" + +/* Include global variables of VPR */ +#include "globals.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Apply the fix-up to truth table of LUT according to its pin + * rotation status by packer + * + * Note: + * - pb must represents a LUT pb in the graph and it should be primitive + *******************************************************************/ +static +void fix_up_lut_atom_block_truth_table(const AtomContext& atom_ctx, + t_pb* pb, + const t_pb_routes& pb_route, + VprClusteringAnnotation& vpr_clustering_annotation, + const bool& verbose) { + t_pb_graph_node* pb_graph_node = pb->pb_graph_node; + t_pb_type* pb_type = pb->pb_graph_node->pb_type; + + VTR_ASSERT(LUT_CLASS == pb_type->class_type); + + for (int iport = 0; iport < pb_type->num_ports; ++iport) { + /* We only care about input ports whose pins are equivalent */ + if (IN_PORT != pb_type->ports[iport].type || true == pb_type->ports[iport].is_clock) { + continue; + } + if (pb_type->ports[iport].equivalent == PortEquivalence::NONE) { + continue; + } + /* Reach here, we have to apply a fix-up */ + AtomBlockId atom_blk = atom_ctx.nlist.find_block(pb->name); + VTR_ASSERT(atom_blk); + + /* Port exists (some LUTs may have no input and hence no port in the atom netlist) */ + AtomPortId atom_port = atom_ctx.nlist.find_atom_port(atom_blk, pb_type->ports[iport].model_port); + if (atom_port) { + continue; + } + + /* Find the pin rotation status and record it , + * Note that some LUT inputs may not be used, we set them to be open by default + */ + std::vector rotated_pin_map(pb_type->ports[iport].num_pins, -1); + for (int ipin = 0; ipin < pb_type->ports[iport].num_pins; ++ipin) { + int node_index = pb_graph_node->input_pins[iport][ipin].pin_count_in_cluster; + if (pb_route.count(node_index)) { + /* The pin is mapped to a net, find the original pin in the atom netlist */ + AtomNetId atom_net = pb_route[node_index].atom_net_id; + + VTR_ASSERT(atom_net); + + for (AtomPinId atom_pin : atom_ctx.nlist.port_pins(atom_port)) { + + AtomNetId atom_pin_net = atom_ctx.nlist.pin_net(atom_pin); + + if (atom_pin_net == atom_net) { + rotated_pin_map[ipin] = atom_ctx.nlist.pin_port_bit(atom_pin); + break; + } + } + } + } + + /* We can apply truth table adaption now + * For unused inputs : insert dont care + * For used inputs : find the bit in the truth table rows and move it by the given mapping + */ + const AtomNetlist::TruthTable& orig_tt = atom_ctx.nlist.block_truth_table(atom_blk); + const AtomNetlist::TruthTable& adapt_tt = lut_truth_table_adaption(orig_tt, rotated_pin_map); + vpr_clustering_annotation.adapt_truth_table(pb, adapt_tt); + + /* Print info is in the verbose mode */ + VTR_LOGV(verbose, "Original truth table\n"); + for (const std::string& tt_line : truth_table_to_string(orig_tt)) { + VTR_LOGV(verbose, "\t%s\n", tt_line.c_str()); + } + VTR_LOGV(verbose, "\n"); + VTR_LOGV(verbose, "Adapt truth table\n"); + VTR_LOGV(verbose, "-----------------\n"); + for (const std::string& tt_line : truth_table_to_string(adapt_tt)) { + VTR_LOGV(verbose, "\t%s\n", tt_line.c_str()); + } + VTR_LOGV(verbose, "\n"); + } +} + +/******************************************************************** + * This function recursively visits the pb graph until we reach a + * LUT pb_type (primitive node in the pb_graph with a class type + * of LUT_CLASS + * Once we find a LUT node, we will apply the fix-up + *******************************************************************/ +static +void rec_adapt_lut_pb_tt(const AtomContext& atom_ctx, + t_pb* pb, + const t_pb_routes& pb_route, + VprClusteringAnnotation& vpr_clustering_annotation, + const bool& verbose) { + t_pb_graph_node* pb_graph_node = pb->pb_graph_node; + + /* If we reach a primitive pb_graph node, we return */ + if (true == is_primitive_pb_type(pb_graph_node->pb_type)) { + if (LUT_CLASS == pb_graph_node->pb_type->class_type) { + /* Do fix-up here */ + fix_up_lut_atom_block_truth_table(atom_ctx, pb, pb_route, vpr_clustering_annotation, verbose); + } + return; + } + + /* Recursively visit all the used pbs in the graph */ + t_mode* mapped_mode = &(pb_graph_node->pb_type->modes[pb->mode]); + for (int ipb = 0; ipb < mapped_mode->num_pb_type_children; ++ipb) { + /* Each child may exist multiple times in the hierarchy*/ + for (int jpb = 0; jpb < mapped_mode->pb_type_children[ipb].num_pb; ++jpb) { + /* See if we still have any pb children to walk through */ + if ((pb->child_pbs[ipb] != nullptr) && (pb->child_pbs[ipb][jpb].name != nullptr)) { + rec_adapt_lut_pb_tt(atom_ctx, &(pb->child_pbs[ipb][jpb]), pb_route, vpr_clustering_annotation, verbose); + } + } + } +} + +/******************************************************************** + * Main function to fix up truth table for each LUT used in FPGA + * This function will walk through each clustered block + *******************************************************************/ +static +void update_lut_tt_with_post_packing_results(const AtomContext& atom_ctx, + const ClusteringContext& clustering_ctx, + VprClusteringAnnotation& vpr_clustering_annotation, + const bool& verbose) { + for (auto blk_id : clustering_ctx.clb_nlist.blocks()) { + rec_adapt_lut_pb_tt(atom_ctx, + clustering_ctx.clb_nlist.block_pb(blk_id), + clustering_ctx.clb_nlist.block_pb(blk_id)->pb_route, + vpr_clustering_annotation, verbose); + } +} + + +/******************************************************************** + * Top-level function to fix up the lut truth table results after packing is done + * The problem comes from a mismatch between the packing results and + * original truth tables in users' BLIF file + * As LUT inputs are equivalent in nature, the router of packer will try + * to swap the net mapping among these pins so as to achieve best + * routing optimization. + * However, it will cause the original truth table out-of-date when packing is done. + * This function aims to fix the mess after packing so that the truth table + * can be synchronized + *******************************************************************/ +void lut_truth_table_fixup(OpenfpgaContext& openfpga_context, + const Command& cmd, const CommandContext& cmd_context) { + + vtr::ScopedStartFinishTimer timer("Fix up LUT truth tables after packing optimization"); + + CommandOptionId opt_verbose = cmd.option("verbose"); + + /* Apply fix-up to each packed block */ + update_lut_tt_with_post_packing_results(g_vpr_ctx.atom(), + g_vpr_ctx.clustering(), + openfpga_context.mutable_vpr_clustering_annotation(), + cmd_context.option_enable(cmd, opt_verbose)); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_lut_truth_table_fixup.h b/openfpga/src/base/openfpga_lut_truth_table_fixup.h new file mode 100644 index 000000000..f11abc7fb --- /dev/null +++ b/openfpga/src/base/openfpga_lut_truth_table_fixup.h @@ -0,0 +1,23 @@ +#ifndef OPENFPGA_LUT_TRUTH_TABLE_FIXUP_H +#define OPENFPGA_LUT_TRUTH_TABLE_FIXUP_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "command.h" +#include "command_context.h" +#include "openfpga_context.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void lut_truth_table_fixup(OpenfpgaContext& openfpga_context, + const Command& cmd, const CommandContext& cmd_context); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/base/openfpga_setup_command.cpp b/openfpga/src/base/openfpga_setup_command.cpp index 2a1edacbc..629300bf2 100644 --- a/openfpga/src/base/openfpga_setup_command.cpp +++ b/openfpga/src/base/openfpga_setup_command.cpp @@ -6,6 +6,7 @@ #include "openfpga_read_arch.h" #include "openfpga_link_arch.h" #include "openfpga_pb_pin_fixup.h" +#include "openfpga_lut_truth_table_fixup.h" #include "check_netlist_naming_conflict.h" #include "openfpga_setup_command.h" @@ -100,6 +101,23 @@ void add_openfpga_setup_commands(openfpga::Shell& shell) { cmd_dependency_pb_pin_fixup.push_back(shell_cmd_read_arch_id); cmd_dependency_pb_pin_fixup.push_back(shell_cmd_vpr_id); shell.set_command_dependency(shell_cmd_pb_pin_fixup_id, cmd_dependency_pb_pin_fixup); + + /******************************** + * Command 'lut_truth_table_fixup' + */ + Command shell_cmd_lut_truth_table_fixup("lut_truth_table_fixup"); + /* Add an option '--verbose' */ + shell_cmd_lut_truth_table_fixup.add_option("verbose", false, "Show verbose outputs"); + + /* Add command 'lut_truth_table_fixup' to the Shell */ + ShellCommandId shell_cmd_lut_truth_table_fixup_id = shell.add_command(shell_cmd_lut_truth_table_fixup, "Fix up the truth table of Look-Up Tables due to pin swapping during packing stage"); + shell.set_command_class(shell_cmd_lut_truth_table_fixup_id, openfpga_setup_cmd_class); + shell.set_command_execute_function(shell_cmd_lut_truth_table_fixup_id, lut_truth_table_fixup); + /* The 'pb_pin_fixup' command should NOT be executed before 'read_openfpga_arch' and 'vpr' */ + std::vector cmd_dependency_lut_truth_table_fixup; + cmd_dependency_lut_truth_table_fixup.push_back(shell_cmd_read_arch_id); + cmd_dependency_lut_truth_table_fixup.push_back(shell_cmd_vpr_id); + shell.set_command_dependency(shell_cmd_lut_truth_table_fixup_id, cmd_dependency_lut_truth_table_fixup); } } /* end namespace openfpga */ diff --git a/openfpga/src/utils/lut_utils.cpp b/openfpga/src/utils/lut_utils.cpp new file mode 100644 index 000000000..505502782 --- /dev/null +++ b/openfpga/src/utils/lut_utils.cpp @@ -0,0 +1,93 @@ +/******************************************************************** + * This file includes most utilized functions to manipulate LUTs, + * especially their truth tables, in the OpenFPGA context + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" + +#include "lut_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * This function aims to adapt the truth table to a mapped physical LUT + * subject to a pin rotation map + * The modification is applied to line by line + * - For unused inputs : insert dont care + * - For used inputs : find the bit in the truth table rows and move it by the given mapping + * + * The rotated pin map is the reference to adapt the truth table. + * Each element of the map represents the input index in the original truth table + * The sequence of the rotate pin map is the final sequence of how + * each line of the original truth table should be shuffled + * Example: + * output_value(we do not modify) + * | + * v + * Truth table line: 00111 + * rotated_pin_map: 2310 + * Adapt truth table line: 11001 + *******************************************************************/ +AtomNetlist::TruthTable lut_truth_table_adaption(const AtomNetlist::TruthTable& orig_tt, + const std::vector& rotated_pin_map) { + AtomNetlist::TruthTable tt; + + for (auto row : orig_tt) { + VTR_ASSERT(row.size() - 1 == rotated_pin_map.size()); + + std::vector tt_line; + /* We do not care about the last digit, which is the output value */ + for (size_t i = 0; i < row.size() - 1; ++i) { + if (-1 == rotated_pin_map[i]) { + tt_line.push_back(vtr::LogicValue::DONT_CARE); + } else { + /* Ensure we never access the last digit, i.e., the output value! */ + VTR_ASSERT((size_t)rotated_pin_map[i] < row.size() - 1); + tt_line.push_back(row[rotated_pin_map[i]]); + } + } + + /* Do not miss the last digit in the final result */ + tt_line.push_back(row.back()); + tt.push_back(tt_line); + } + + return tt; +} + +/******************************************************************** + * Convert a truth table to strings, which are ready to be printed out + *******************************************************************/ +std::vector truth_table_to_string(const AtomNetlist::TruthTable& tt) { + std::vector tt_str; + for (auto row : tt) { + std::string row_str; + for (size_t i = 0; i < row.size(); ++i) { + /* Add a gap between inputs and outputs */ + if (i == row.size() - 1) { + row_str += std::string(" "); + } + switch (row[i]) { + case vtr::LogicValue::TRUE: + row_str += std::string("1"); + break; + case vtr::LogicValue::FALSE: + row_str += std::string("0"); + break; + case vtr::LogicValue::DONT_CARE: + row_str += std::string("-"); + break; + default: + VTR_ASSERT_MSG(false, "Valid single-output cover logic value"); + } + } + tt_str.push_back(row_str); + } + + return tt_str; +} + +} /* end namespace openfpga */ + diff --git a/openfpga/src/utils/lut_utils.h b/openfpga/src/utils/lut_utils.h new file mode 100644 index 000000000..800c1fc59 --- /dev/null +++ b/openfpga/src/utils/lut_utils.h @@ -0,0 +1,25 @@ +#ifndef LUT_UTILS_H +#define LUT_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "atom_netlist.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +AtomNetlist::TruthTable lut_truth_table_adaption(const AtomNetlist::TruthTable& orig_tt, + const std::vector& rotated_pin_map); + +std::vector truth_table_to_string(const AtomNetlist::TruthTable& tt); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/test_script/s298_k6_frac.openfpga b/openfpga/test_script/s298_k6_frac.openfpga index 9681918c4..f4a5bc979 100644 --- a/openfpga/test_script/s298_k6_frac.openfpga +++ b/openfpga/test_script/s298_k6_frac.openfpga @@ -13,5 +13,8 @@ check_netlist_naming_conflict --fix --report ./netlist_renaming.xml # Apply fix-up to clustering nets based on routing results pb_pin_fixup --verbose +# Apply fix-up to Look-Up Table truth tables based on packing results +lut_truth_table_fixup --verbose + # Finish and exit OpenFPGA exit From 3d7eff64b997842fb0c6d75d5b367ce3c6658c5f Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 6 Feb 2020 17:47:25 -0700 Subject: [PATCH 108/645] bug fixed for lut truth table fixup. Results look good --- .../base/openfpga_lut_truth_table_fixup.cpp | 34 ++++++++++++++++--- openfpga/src/utils/lut_utils.cpp | 4 +-- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/openfpga/src/base/openfpga_lut_truth_table_fixup.cpp b/openfpga/src/base/openfpga_lut_truth_table_fixup.cpp index 9280e96bc..45ecbca31 100644 --- a/openfpga/src/base/openfpga_lut_truth_table_fixup.cpp +++ b/openfpga/src/base/openfpga_lut_truth_table_fixup.cpp @@ -52,7 +52,7 @@ void fix_up_lut_atom_block_truth_table(const AtomContext& atom_ctx, /* Port exists (some LUTs may have no input and hence no port in the atom netlist) */ AtomPortId atom_port = atom_ctx.nlist.find_atom_port(atom_blk, pb_type->ports[iport].model_port); - if (atom_port) { + if (!atom_port) { continue; } @@ -90,12 +90,31 @@ void fix_up_lut_atom_block_truth_table(const AtomContext& atom_ctx, /* Print info is in the verbose mode */ VTR_LOGV(verbose, "Original truth table\n"); + VTR_LOGV(verbose, "Index: "); + for (size_t i = 0; i < rotated_pin_map.size(); ++i) { + if (0 < i) { + VTR_LOGV(verbose, ","); + } + VTR_LOGV(verbose, "%lu", i); + } + VTR_LOGV(verbose, "\n"); for (const std::string& tt_line : truth_table_to_string(orig_tt)) { VTR_LOGV(verbose, "\t%s\n", tt_line.c_str()); } VTR_LOGV(verbose, "\n"); + VTR_LOGV(verbose, "Pin rotation map: "); + for (size_t i = 0; i < rotated_pin_map.size(); ++i) { + if (0 < i) { + VTR_LOGV(verbose, ","); + } + if (-1 == rotated_pin_map[i]) { + VTR_LOGV(verbose, "open"); + } else { + VTR_LOGV(verbose, "%lu", rotated_pin_map[i]); + } + } + VTR_LOGV(verbose, "\n"); VTR_LOGV(verbose, "Adapt truth table\n"); - VTR_LOGV(verbose, "-----------------\n"); for (const std::string& tt_line : truth_table_to_string(adapt_tt)) { VTR_LOGV(verbose, "\t%s\n", tt_line.c_str()); } @@ -120,8 +139,15 @@ void rec_adapt_lut_pb_tt(const AtomContext& atom_ctx, /* If we reach a primitive pb_graph node, we return */ if (true == is_primitive_pb_type(pb_graph_node->pb_type)) { if (LUT_CLASS == pb_graph_node->pb_type->class_type) { - /* Do fix-up here */ - fix_up_lut_atom_block_truth_table(atom_ctx, pb, pb_route, vpr_clustering_annotation, verbose); + /* Do fix-up here. + * Note that LUTs have two modes, + * For wire modes, we should skip the truth table adaption + * mode 0 is reserved for wire, see read_xml_arch_file.cpp + * mode 1 is the regular mode + */ + if (1 == pb->mode) { + fix_up_lut_atom_block_truth_table(atom_ctx, pb->child_pbs[0], pb_route, vpr_clustering_annotation, verbose); + } } return; } diff --git a/openfpga/src/utils/lut_utils.cpp b/openfpga/src/utils/lut_utils.cpp index 505502782..e2b01edb5 100644 --- a/openfpga/src/utils/lut_utils.cpp +++ b/openfpga/src/utils/lut_utils.cpp @@ -35,11 +35,11 @@ AtomNetlist::TruthTable lut_truth_table_adaption(const AtomNetlist::TruthTable& AtomNetlist::TruthTable tt; for (auto row : orig_tt) { - VTR_ASSERT(row.size() - 1 == rotated_pin_map.size()); + VTR_ASSERT(row.size() - 1 <= rotated_pin_map.size()); std::vector tt_line; /* We do not care about the last digit, which is the output value */ - for (size_t i = 0; i < row.size() - 1; ++i) { + for (size_t i = 0; i < rotated_pin_map.size(); ++i) { if (-1 == rotated_pin_map[i]) { tt_line.push_back(vtr::LogicValue::DONT_CARE); } else { From 0b6b3bc0290e3bb7df1c2b740aace60df105f91e Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 7 Feb 2020 11:32:33 -0700 Subject: [PATCH 109/645] start adapting rr_gsb related data structure --- openfpga/src/annotation/device_rr_gsb.h | 106 ++++++++++++ openfpga/src/annotation/rr_chan.cpp | 221 ++++++++++++++++++++++++ openfpga/src/annotation/rr_chan.h | 95 ++++++++++ openfpga/src/annotation/rr_gsb.h | 193 +++++++++++++++++++++ 4 files changed, 615 insertions(+) create mode 100644 openfpga/src/annotation/device_rr_gsb.h create mode 100644 openfpga/src/annotation/rr_chan.cpp create mode 100644 openfpga/src/annotation/rr_chan.h create mode 100644 openfpga/src/annotation/rr_gsb.h diff --git a/openfpga/src/annotation/device_rr_gsb.h b/openfpga/src/annotation/device_rr_gsb.h new file mode 100644 index 000000000..e801417bc --- /dev/null +++ b/openfpga/src/annotation/device_rr_gsb.h @@ -0,0 +1,106 @@ +#ifndef DEVICE_RR_GSB_H +#define DEVICE_RR_GSB_H + +/******************************************************************** + * Include header files required by the data structure definition + *******************************************************************/ +#include "rr_gsb.h" + +/******************************************************************** + * Object Device Routing Resource Switch Block + * This includes: + * 1. a collection of RRSwitch blocks, each of which can be used to instance Switch blocks in the top-level netlists + * 2. a collection of unique mirrors of RRGSBs, which can be used to output Verilog / SPICE modules + * 3. a colleciton of unique rotatable of RRGSBs, which can be used to output Verilog / SPICE modules + * The rotatable RRGSBs are more generic mirrors, which allow SwitchBlocks to be wired by rotating the pins, + * further reduce the number of Verilog/SPICE modules outputted. This will lead to rapid layout generation + *******************************************************************/ +class DeviceRRGSB { + public: /* Contructors */ + public: /* Accessors */ + DeviceCoordinator get_gsb_range() const; /* get the max coordinator of the switch block array */ + const RRGSB get_gsb(const DeviceCoordinator& coordinator) const; /* Get a rr switch block in the array with a coordinator */ + const RRGSB get_gsb(size_t x, size_t y) const; /* Get a rr switch block in the array with a coordinator */ + size_t get_num_gsb_unique_module() const; /* get the number of unique mirrors of GSB */ + size_t get_num_sb_unique_submodule(enum e_side side, size_t seg_index) const; /* get the number of unique mirrors of switch blocks */ + size_t get_num_sb_unique_module() const; /* get the number of unique mirrors of switch blocks */ + size_t get_num_cb_unique_module(t_rr_type cb_type) const; /* get the number of unique mirrors of CBs */ + size_t get_sb_unique_submodule_id(DeviceCoordinator& coordinator, enum e_side side, size_t seg_id) const; + const RRGSB get_sb_unique_submodule(size_t index, enum e_side side, size_t seg_id) const; /* Get a rr switch block which a unique mirror */ + const RRGSB get_sb_unique_submodule(DeviceCoordinator& coordinator, enum e_side side, size_t seg_id) const; /* Get a rr switch block which a unique mirror */ + const RRGSB get_sb_unique_module(size_t index) const; /* Get a rr switch block which a unique mirror */ + const RRGSB get_sb_unique_module(const DeviceCoordinator& coordinator) const; /* Get a rr switch block which a unique mirror */ + const RRGSB& get_cb_unique_module(t_rr_type cb_type, size_t index) const; /* Get a rr switch block which a unique mirror */ + const RRGSB& get_cb_unique_module(t_rr_type cb_type, const DeviceCoordinator& coordinator) const; + size_t get_max_num_sides() const; /* Get the maximum number of sides across the switch blocks */ + size_t get_num_segments() const; /* Get the size of segment_ids */ + size_t get_segment_id(size_t index) const; /* Get a segment id */ + bool is_two_sb_share_same_submodules(DeviceCoordinator& src, DeviceCoordinator& des) const; + public: /* Mutators */ + void set_sb_num_reserved_conf_bits(DeviceCoordinator& coordinator, size_t num_reserved_conf_bits); /* TODO: TOBE DEPRECATED!!! conf_bits should be initialized when creating a switch block!!! */ + void set_sb_conf_bits_lsb(DeviceCoordinator& coordinator, size_t conf_bits_lsb); /* TODO: TOBE DEPRECATED!!! conf_bits should be initialized when creating a switch block!!! */ + void set_sb_conf_bits_msb(DeviceCoordinator& coordinator, size_t conf_bits_msb); /* TODO: TOBE DEPRECATED!!! conf_bits should be initialized when creating a switch block!!! */ + void set_cb_num_reserved_conf_bits(DeviceCoordinator& coordinator, t_rr_type cb_type, size_t num_reserved_conf_bits); /* TODO: TOBE DEPRECATED!!! conf_bits should be initialized when creating a switch block!!! */ + void set_cb_conf_bits_lsb(DeviceCoordinator& coordinator, t_rr_type cb_type, size_t conf_bits_lsb); /* TODO: TOBE DEPRECATED!!! conf_bits should be initialized when creating a switch block!!! */ + void set_cb_conf_bits_msb(DeviceCoordinator& coordinator, t_rr_type cb_type, size_t conf_bits_msb); /* TODO: TOBE DEPRECATED!!! conf_bits should be initialized when creating a switch block!!! */ + void reserve(DeviceCoordinator& coordinator); /* Pre-allocate the rr_switch_block array that the device requires */ + void reserve_sb_unique_submodule_id(DeviceCoordinator& coordinator); /* Pre-allocate the rr_sb_unique_module_id matrix that the device requires */ + void resize_upon_need(const DeviceCoordinator& coordinator); /* Resize the rr_switch_block array if needed */ + void add_rr_gsb(const DeviceCoordinator& coordinator, const RRGSB& rr_gsb); /* Add a switch block to the array, which will automatically identify and update the lists of unique mirrors and rotatable mirrors */ + void build_unique_module(); /* Add a switch block to the array, which will automatically identify and update the lists of unique mirrors and rotatable mirrors */ + void clear(); /* clean the content */ + private: /* Internal cleaners */ + void clear_gsb(); /* clean the content */ + void clear_cb_unique_module(t_rr_type cb_type); /* clean the content */ + void clear_cb_unique_module_id(t_rr_type cb_type); /* clean the content */ + void clear_sb_unique_module(); /* clean the content */ + void clear_sb_unique_module_id(); /* clean the content */ + void clear_sb_unique_submodule(); /* clean the content */ + void clear_sb_unique_submodule_id(); /* clean the content */ + void clear_gsb_unique_module(); /* clean the content */ + void clear_gsb_unique_module_id(); /* clean the content */ + void clear_segment_ids(); + private: /* Validators */ + bool validate_coordinator(const DeviceCoordinator& coordinator) const; /* Validate if the (x,y) is the range of this device */ + bool validate_coordinator_edge(DeviceCoordinator& coordinator) const; /* Validate if the (x,y) is the range of this device but takes into consideration the fact that edges are 1 off */ + bool validate_side(enum e_side side) const; /* validate if side is in the range of unique_side_module_ */ + bool validate_sb_unique_module_index(size_t index) const; /* Validate if the index in the range of unique_mirror vector*/ + bool validate_cb_unique_module_index(t_rr_type cb_type, size_t index) const; /* Validate if the index in the range of unique_mirror vector*/ + bool validate_sb_unique_submodule_index(size_t index, enum e_side side, size_t seg_index) const; /* Validate if the index in the range of unique_module vector */ + bool validate_segment_index(size_t index) const; + bool validate_cb_type(t_rr_type cb_type) const; + private: /* Internal builders */ + void build_segment_ids(); /* build a map of segment_ids */ + void add_gsb_unique_module(const DeviceCoordinator& coordinator); + void add_sb_unique_side_submodule(DeviceCoordinator& coordinator, const RRGSB& rr_sb, enum e_side side); + void add_sb_unique_side_segment_submodule(DeviceCoordinator& coordinator, const RRGSB& rr_sb, enum e_side side, size_t seg_id); + void add_cb_unique_module(t_rr_type cb_type, const DeviceCoordinator& coordinator); + void set_cb_unique_module_id(t_rr_type, const DeviceCoordinator& coordinator, size_t id); + void build_sb_unique_submodule(); /* Add a switch block to the array, which will automatically identify and update the lists of unique side module */ + void build_sb_unique_module(); /* Add a switch block to the array, which will automatically identify and update the lists of unique mirrors and rotatable mirrors */ + void build_cb_unique_module(t_rr_type cb_type); /* Add a switch block to the array, which will automatically identify and update the lists of unique side module */ + void build_gsb_unique_module(); /* Add a switch block to the array, which will automatically identify and update the lists of unique mirrors and rotatable mirrors */ + private: /* Internal Data */ + std::vector< std::vector > rr_gsb_; + + std::vector< std::vector > gsb_unique_module_id_; /* A map from rr_gsb to its unique mirror */ + std::vector gsb_unique_module_; + + std::vector< std::vector > sb_unique_module_id_; /* A map from rr_gsb to its unique mirror */ + std::vector sb_unique_module_; + + std::vector< std::vector< std::vector< std::vector > > > sb_unique_submodule_id_; /* A map from rr_switch_block to its unique_side_module [0..x][0..y][0..num_sides][num_seg-1]*/ + std::vector< std::vector > > sb_unique_submodule_; /* For each side of switch block, we identify a list of unique modules based on its connection. This is a matrix [0..num_sides-1][0..num_seg-1][0..num_module], num_sides will the max number of sides of all the rr_switch_blocks */ + + std::vector< std::vector > cbx_unique_module_id_; /* A map from rr_gsb to its unique mirror */ + std::vector cbx_unique_module_; /* For each side of connection block, we identify a list of unique modules based on its connection. This is a matrix [0..num_module] */ + + std::vector< std::vector > cby_unique_module_id_; /* A map from rr_gsb to its unique mirror */ + std::vector cby_unique_module_; /* For each side of connection block, we identify a list of unique modules based on its connection. This is a matrix [0..num_module] */ + + std::vector segment_ids_; +}; + +} /* End namespace openfpga*/ + +#endif diff --git a/openfpga/src/annotation/rr_chan.cpp b/openfpga/src/annotation/rr_chan.cpp new file mode 100644 index 000000000..84540758e --- /dev/null +++ b/openfpga/src/annotation/rr_chan.cpp @@ -0,0 +1,221 @@ +/************************************************************************ + * Member functions for class RRChan + ***********************************************************************/ +#include "vtr_log.h" +#include "vtr_assert.h" +#include "rr_chan.h" + +/* namespace openfpga begins */ +namespace openfpga { + +/************************************************************************ + * Constructors + ***********************************************************************/ +/* Copy Constructor */ +RRChan::RRChan(const RRChan& rr_chan) { + this->set(rr_chan); + return; +} + +/* default constructor */ +RRChan::RRChan() { + type_ = NUM_RR_TYPES; + nodes_.resize(0); + node_segments_.resize(0); +} + +/* Accessors */ +t_rr_type RRChan::get_type() const { + return type_; +} + +/* get the number of tracks in this channel */ +size_t RRChan::get_chan_width() const { + return nodes_.size(); +} + +/* get the track_id of a node */ +int RRChan::get_node_track_id(const RRNodeId& node) const { + /* if the given node is NULL, we return an invalid id */ + if (RRNodeId::INVALID() == node) { + return -1; + } + /* check each member and return if we find a match in content */ + std::vector::const_iterator it = std::find(nodes_.begin(), nodes_.end(), node); + if (nodes_.end() == it) { + return -1; + } + return it - nodes_.begin(); +} + +/* get the rr_node with the track_id */ +RRNodeId RRChan::get_node(const size_t& track_num) const { + if ( false == valid_node_id(track_num) ) { + return RRNodeId::INVALID(); + } + return nodes_[track_num]; +} + +/* get the segment id of a node */ +RRSegmentId RRChan::get_node_segment(const RRNodeId& node) const { + int node_id = get_node_track_id(node); + if ( false == valid_node_id(node_id)) { + return RRSegmentId::INVALID(); + } + return get_node_segment(node_id); +} + +/* get the segment id of a node */ +RRSegmentId RRChan::get_node_segment(const size_t& track_num) const { + if ( false == valid_node_id(track_num)) { + return RRSegmentId::INVALID(); + } + return node_segments_[track_num]; +} + +/* evaluate if two RRChan is mirror to each other */ +bool RRChan::is_mirror(const RRGraph& rr_graph, const RRChan& cand) const { + /* If any following element does not match, it is not mirror */ + /* 1. type */ + if (this->get_type() != cand.get_type()) { + return false; + } + /* 2. track_width */ + if (this->get_chan_width() != cand.get_chan_width()) { + return false; + } + /* 3. for each node */ + for (size_t inode = 0; inode < this->get_chan_width(); ++inode) { + /* 3.1 check node type */ + if (rr_graph.node_type(this->get_node(inode)) != rr_graph.node_type(cand.get_node(inode))) { + return false; + } + /* 3.2 check node directionality */ + if (rr_graph.node_direction(this->get_node(inode)) != rr_graph.node_direction(cand.get_node(inode))) { + return false; + } + /* 3.3 check node segment */ + if (this->get_node_segment(inode) != cand.get_node_segment(inode)) { + return false; + } + } + + return true; +} + +/* Get a list of segments used in this routing channel */ +std::vector RRChan::get_segment_ids() const { + std::vector seg_list; + + /* make sure a clean start */ + seg_list.clear(); + + /* Traverse node_segments */ + for (size_t inode = 0; inode < get_chan_width(); ++inode) { + std::vector::iterator it; + /* Try to find the node_segment id in the list */ + it = find(seg_list.begin(), seg_list.end(), node_segments_[inode]); + if ( it == seg_list.end() ) { + /* Not found, add it to the list */ + seg_list.push_back(node_segments_[inode]); + } + } + + return seg_list; +} + +/* Get a list of nodes whose segment_id is specified */ +std::vector RRChan::get_node_ids_by_segment_ids(const RRSegmentId& seg_id) const { + std::vector node_list; + + /* make sure a clean start */ + node_list.clear(); + + /* Traverse node_segments */ + for (size_t inode = 0; inode < get_chan_width(); ++inode) { + /* Try to find the node_segment id in the list */ + if ( seg_id == node_segments_[inode] ) { + node_list.push_back(nodes_[inode]); + } + } + + return node_list; +} + +/* Mutators */ +void RRChan::set(const RRChan& rr_chan) { + /* Ensure a clean start */ + this->clear(); + /* Assign type of this routing channel */ + this->type_ = rr_chan.get_type(); + /* Copy node and node_segments */ + this->nodes_.resize(rr_chan.get_chan_width()); + this->node_segments_.resize(rr_chan.get_chan_width()); + for (size_t inode = 0; inode < rr_chan.get_chan_width(); ++inode) { + this->nodes_[inode] = rr_chan.get_node(inode); + this->node_segments_[inode] = rr_chan.get_node_segment(inode); + } + return; +} + +/* modify type */ +void RRChan::set_type(const t_rr_type& type) { + VTR_ASSERT(valid_type(type)); + type_ = type; +} + +/* Reserve node list */ +void RRChan::reserve_node(const size_t& node_size) { + nodes_.reserve(node_size); /* reserve to the maximum */ + node_segments_.reserve(node_size); /* reserve to the maximum */ +} + +/* add a node to the array */ +void RRChan::add_node(const RRGraph& rr_graph, const RRNodeId& node, const RRSegmentId& node_segment) { + /* fill the dedicated element in the vector */ + nodes_.push_back(node); + node_segments_.push_back(node_segment); + + VTR_ASSERT(valid_node_type(rr_graph, node)); +} + +/* Clear content */ +void RRChan::clear() { + nodes_.clear(); + node_segments_.clear(); +} + +/************************************************************************ + * Internal functions + ***********************************************************************/ +/* for type, only valid type is CHANX and CHANY */ +bool RRChan::valid_type(const t_rr_type& type) const { + if ((CHANX == type) || (CHANY == type)) { + return true; + } + return false; +} + +/* Check each node, see if the node type is consistent with the type */ +bool RRChan::valid_node_type(const RRGraph& rr_graph, const RRNodeId& node) const { + valid_type(rr_graph.node_type(node)); + if (NUM_RR_TYPES == type_) { + return true; + } + valid_type(type_); + if (type_ != rr_graph.node_type(node)) { + return false; + } + return true; +} + +/* check if the node id is valid */ +bool RRChan::valid_node_id(const size_t& node_id) const { + if (node_id < nodes_.size()) { + return true; + } + + return false; +} + +} /* End namespace openfpga*/ diff --git a/openfpga/src/annotation/rr_chan.h b/openfpga/src/annotation/rr_chan.h new file mode 100644 index 000000000..ab8bb6617 --- /dev/null +++ b/openfpga/src/annotation/rr_chan.h @@ -0,0 +1,95 @@ +#ifndef RR_CHAN_H +#define RR_CHAN_H + +/******************************************************************** + * Include header files required by the data structure definition + *******************************************************************/ +#include + +/* Headers from vtrutil library */ +#include "vtr_geometry.h" + +/* Headers from openfpgautil library */ +#include "openfpga_port.h" + +/* Headers from vpr library */ +#include "rr_graph_obj.h" + +/* Begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * RRChan object aim to describe a routing channel in a routing resource graph + * - What are the nodes in the RRGraph object, for each routing track + * - What are routing segments used by each node in the channel + * - What are the directions of each routing channel + * being either X-direction or Y-direction + * + * Note : + * - This is a collection of rr_nodes from the RRGraph Object + * It does not rebuild or contruct any connects between rr_nodes + * It is just an annotation on an existing RRGraph + * ------------- ------ + * | | | | + * | | | Y | + * | CLB | | Chan | + * | | | | + * | | | | + * ------------- ------ + * ------------- + * | X | + * | Channel | + * ------------- + *******************************************************************/ +class RRChan { + public: /* Constructors */ + RRChan(const RRChan&); /* Copy Constructor */ + RRChan(); + public: /* Accessors */ + t_rr_type get_type() const; + size_t get_chan_width() const; /* get the number of tracks in this channel */ + int get_node_track_id(const RRNodeId& node) const; /* get the track_id of a node */ + RRNodeId get_node(const size_t& track_num) const; /* get the rr_node with the track_id */ + RRSegmentId get_node_segment(const RRNodeId& node) const; + RRSegmentId get_node_segment(const size_t& track_num) const; + bool is_mirror(const RRGraph& rr_graph, const RRChan& cand) const; /* evaluate if two RR_chan is mirror to each other */ + std::vector get_segment_ids() const; /* Get a list of segments used in this routing channel */ + std::vector get_node_ids_by_segment_ids(const RRSegmentId& seg_id) const; /* Get a list of segments used in this routing channel */ + public: /* Mutators */ + /* copy */ + void set(const RRChan&); + + /* modify the type of routing channel */ + void set_type(const t_rr_type& type); + + /* reseve a number of nodes to the array */ + void reserve_node(const size_t& node_size); + + /* add a node to the routing channel */ + void add_node(const RRGraph& rr_graph, const RRNodeId& node, const RRSegmentId& node_segment); + + /* clear the content */ + void clear(); + + private: /* internal functions */ + + /* For the type of a routing channel, only valid type is CHANX and CHANY */ + bool valid_type(const t_rr_type& type) const; + + /* Check each node, see if the node type is consistent with the type of routing channel */ + bool valid_node_type(const RRGraph& rr_graph, const RRNodeId& node) const; + + /* Validate if the track number in the range */ + bool valid_node_id(const size_t& node_id) const; + + private: /* Internal Data */ + t_rr_type type_; /* channel type: CHANX or CHANY */ + std::vector nodes_; /* rr nodes of each track in the channel */ + std::vector node_segments_; /* segment of each track */ +}; + + + +} /* End namespace openfpga*/ + +#endif diff --git a/openfpga/src/annotation/rr_gsb.h b/openfpga/src/annotation/rr_gsb.h new file mode 100644 index 000000000..7febeffc7 --- /dev/null +++ b/openfpga/src/annotation/rr_gsb.h @@ -0,0 +1,193 @@ +#ifndef RR_GSB_H +#define RR_GSB_H + +/******************************************************************** + * Include header files required by the data structure definition + *******************************************************************/ +#include "rr_chan.h" + +/* Begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Object Generic Switch Block + * This block contains + * 1. A switch block + * 2. A X-direction Connection block locates at the left side of the switch block + * 2. A Y-direction Connection block locates at the top side of the switch block + * This is a collection of rr_nodes, which may be replaced with RRNodeId in new RRGraph + * + * +---------------------------------+ + * | Y-direction CB | + * | [x][y + 1] | + * +---------------------------------+ + * + * TOP SIDE + * +-------------+ +---------------------------------+ + * | | | OPIN_NODE CHAN_NODES OPIN_NODES | + * | | | | + * | | | OPIN_NODES OPIN_NODES | + * | X-direction | | | + * | CB | LEFT SIDE | Switch Block | RIGHT SIDE + * | [x][y] | | [x][y] | + * | | | | + * | | | CHAN_NODES CHAN_NODES | + * | | | | + * | | | OPIN_NODES OPIN_NODES | + * | | | | + * | | | OPIN_NODE CHAN_NODES OPIN_NODES | + * +-------------+ +---------------------------------+ + * BOTTOM SIDE + * num_sides: number of sides of this switch block + * chan_rr_node: a collection of rr_nodes as routing tracks locating at each side of the Switch block <0..num_sides-1><0..chan_width-1> + * chan_rr_node_direction: Indicate if this rr_node is an input or an output of the Switch block <0..num_sides-1><0..chan_width-1> + * ipin_rr_node: a collection of rr_nodes as IPIN of a GRID locating at each side of the Switch block <0..num_sides-1><0..num_ipin_rr_nodes-1> + * ipin_rr_node_grid_side: specify the side of the input pins on which side of a GRID <0..num_sides-1><0..num_ipin_rr_nodes-1> + * opin_rr_node: a collection of rr_nodes as OPIN of a GRID locating at each side of the Switch block <0..num_sides-1><0..num_opin_rr_nodes-1> + * opin_rr_node_grid_side: specify the side of the output pins on which side of a GRID <0..num_sides-1><0..num_opin_rr_nodes-1> + * num_reserved_conf_bits: number of reserved configuration bits this switch block requires (mainly due to RRAM-based multiplexers) + * num_conf_bits: number of configuration bits this switch block requires + *******************************************************************/ +class RRGSB { + public: /* Contructors */ + RRGSB(const RRGSB&);/* Copy constructor */ + RRGSB();/* Default constructor */ + public: /* Accessors */ + size_t get_num_sides() const; /* Get the number of sides of this SB */ + size_t get_chan_width(enum e_side side) const; /* Get the number of routing tracks on a side */ + size_t get_cb_chan_width(t_rr_type cb_type) const; /* Get the number of routing tracks of a X/Y-direction CB */ + std::vector get_cb_ipin_sides(t_rr_type cb_type) const; /* Get the sides of CB ipins in the array */ + size_t get_max_chan_width() const; /* Get the maximum number of routing tracks on all sides */ + enum PORTS get_chan_node_direction(enum e_side side, size_t track_id) const; /* Get the direction of a rr_node at a given side and track_id */ + RRChan get_chan(enum e_side side) const; /* get a rr_node at a given side and track_id */ + std::vector get_chan_segment_ids(enum e_side side) const; /* Get a list of segments used in this routing channel */ + std::vector get_chan_node_ids_by_segment_ids(enum e_side side, size_t seg_id) const; /* Get a list of segments used in this routing channel */ + t_rr_node* get_chan_node(enum e_side side, size_t track_id) const; /* get a rr_node at a given side and track_id */ + size_t get_chan_node_segment(enum e_side side, size_t track_id) const; /* get the segment id of a channel rr_node */ + size_t get_num_ipin_nodes(enum e_side side) const; /* Get the number of IPIN rr_nodes on a side */ + t_rr_node* get_ipin_node(enum e_side side, size_t node_id) const; /* get a rr_node at a given side and track_id */ + enum e_side get_ipin_node_grid_side(enum e_side side, size_t node_id) const; /* get a rr_node at a given side and track_id */ + enum e_side get_ipin_node_grid_side(t_rr_node* ipin_node) const; /* get a rr_node at a given side and track_id */ + size_t get_num_opin_nodes(enum e_side side) const; /* Get the number of OPIN rr_nodes on a side */ + t_rr_node* get_opin_node(enum e_side side, size_t node_id) const; /* get a rr_node at a given side and track_id */ + enum e_side get_opin_node_grid_side(enum e_side side, size_t node_id) const; /* get a rr_node at a given side and track_id */ + enum e_side get_opin_node_grid_side(t_rr_node* opin_node) const; /* get a rr_node at a given side and track_id */ + int get_cb_chan_node_index(t_rr_type cb_type, t_rr_node* node) const; + int get_chan_node_index(enum e_side node_side, t_rr_node* node) const; + int get_node_index(t_rr_node* node, enum e_side node_side, enum PORTS node_direction) const; /* Get the node index in the array, return -1 if not found */ + void get_node_side_and_index(t_rr_node* node, enum PORTS node_direction, enum e_side* node_side, int* node_index) const; /* Given a rr_node, try to find its side and index in the Switch block */ + bool is_sb_node_exist_opposite_side(t_rr_node* node, enum e_side node_side) const; /* Check if the node exist in the opposite side of this Switch Block */ + public: /* Accessors: get information about configuration ports */ + size_t get_sb_num_reserved_conf_bits() const; + size_t get_sb_reserved_conf_bits_lsb() const; + size_t get_sb_reserved_conf_bits_msb() const; + size_t get_sb_num_conf_bits() const; + size_t get_sb_conf_bits_lsb() const; + size_t get_sb_conf_bits_msb() const; + size_t get_cb_num_reserved_conf_bits(t_rr_type cb_type) const; + size_t get_cb_reserved_conf_bits_lsb(t_rr_type cb_type) const; + size_t get_cb_reserved_conf_bits_msb(t_rr_type cb_type) const; + size_t get_cb_num_conf_bits(t_rr_type cb_type) const; + size_t get_cb_conf_bits_lsb(t_rr_type cb_type) const; + size_t get_cb_conf_bits_msb(t_rr_type cb_type) const; + bool is_sb_node_passing_wire(const enum e_side node_side, const size_t track_id) const; /* Check if the node imply a short connection inside the SB, which happens to long wires across a FPGA fabric */ + bool is_sb_side_mirror(const RRGSB& cand, enum e_side side) const; /* check if a side of candidate SB is a mirror of the current one */ + bool is_sb_side_segment_mirror(const RRGSB& cand, enum e_side side, size_t seg_id) const; /* check if all the routing segments of a side of candidate SB is a mirror of the current one */ + bool is_sb_mirror(const RRGSB& cand) const; /* check if the candidate SB is a mirror of the current one */ + bool is_sb_mirrorable(const RRGSB& cand) const; /* check if the candidate SB satisfy the basic requirements on being a mirror of the current one */ + bool is_cb_mirror(const RRGSB& cand, t_rr_type cb_type) const; /* check if the candidate SB is a mirror of the current one */ + bool is_cb_exist(t_rr_type cb_type) const; /* check if the candidate SB is a mirror of the current one */ + size_t get_hint_rotate_offset(const RRGSB& cand) const; /* Determine an initial offset in rotating the candidate Switch Block to find a mirror matching*/ + public: /* Cooridinator conversion and output */ + size_t get_x() const; /* get the x coordinator of this switch block */ + size_t get_y() const; /* get the y coordinator of this switch block */ + size_t get_sb_x() const; /* get the x coordinator of this switch block */ + size_t get_sb_y() const; /* get the y coordinator of this switch block */ + DeviceCoordinator get_sb_coordinator() const; /* Get the coordinator of the SB */ + size_t get_cb_x(t_rr_type cb_type) const; /* get the x coordinator of this X/Y-direction block */ + size_t get_cb_y(t_rr_type cb_type) const; /* get the y coordinator of this X/Y-direction block */ + DeviceCoordinator get_cb_coordinator(t_rr_type cb_type) const; /* Get the coordinator of the X/Y-direction CB */ + enum e_side get_cb_chan_side(t_rr_type cb_type) const; /* get the side of a Connection block */ + enum e_side get_cb_chan_side(enum e_side ipin_side) const; /* get the side of a Connection block */ + DeviceCoordinator get_side_block_coordinator(enum e_side side) const; + DeviceCoordinator get_grid_coordinator() const; + public: /* Verilog writer */ + const char* gen_gsb_verilog_module_name() const; + const char* gen_gsb_verilog_instance_name() const; + const char* gen_sb_verilog_module_name() const; + const char* gen_sb_verilog_instance_name() const; + const char* gen_sb_verilog_side_module_name(enum e_side side, size_t seg_id) const; + const char* gen_sb_verilog_side_instance_name(enum e_side side, size_t seg_id) const; + const char* gen_cb_verilog_module_name(t_rr_type cb_type) const; + const char* gen_cb_verilog_instance_name(t_rr_type cb_type) const; + const char* gen_cb_verilog_routing_track_name(t_rr_type cb_type, size_t track_id) const; + public: /* Mutators */ + void set(const RRGSB& src); /* get a copy from a source */ + void set_coordinator(size_t x, size_t y); + void init_num_sides(size_t num_sides); /* Allocate the vectors with the given number of sides */ + void add_chan_node(enum e_side node_side, RRChan& rr_chan, std::vector rr_chan_dir); /* Add a node to the chan_rr_node_ list and also assign its direction in chan_rr_node_direction_ */ + void add_ipin_node(t_rr_node* node, const enum e_side node_side, const enum e_side grid_side); /* Add a node to the chan_rr_node_ list and also assign its direction in chan_rr_node_direction_ */ + void add_opin_node(t_rr_node* node, const enum e_side node_side, const enum e_side grid_side); /* Add a node to the chan_rr_node_ list and also assign its direction in chan_rr_node_direction_ */ + void set_sb_num_reserved_conf_bits(size_t num_reserved_conf_bits); + void set_sb_conf_bits_lsb(size_t conf_bits_lsb); + void set_sb_conf_bits_msb(size_t conf_bits_msb); + void set_cb_num_reserved_conf_bits(t_rr_type cb_type, size_t num_reserved_conf_bits); + void set_cb_conf_bits_lsb(t_rr_type cb_type, size_t conf_bits_lsb); + void set_cb_conf_bits_msb(t_rr_type cb_type, size_t conf_bits_msb); + void rotate_side_chan_node_by_direction(enum e_side side, enum e_direction chan_dir, size_t offset); /* rotate all the channel nodes by a given offset */ + void counter_rotate_side_chan_node_by_direction(enum e_side side, enum e_direction chan_dir, size_t offset); /* rotate all the channel nodes by a given offset */ + void rotate_side_chan_node(enum e_side side, size_t offset); /* rotate all the channel nodes by a given offset */ + void rotate_chan_node(size_t offset); /* rotate all the channel nodes by a given offset */ + void rotate_chan_node_in_group(size_t offset); /* rotate all the channel nodes by a given offset */ + void rotate_side_opin_node_in_group(enum e_side side, size_t offset); /* rotate all the opin nodes by a given offset */ + void rotate_opin_node_in_group(size_t offset); /* rotate all the opin nodes by a given offset */ + void rotate_side(enum e_side side, size_t offset); /* rotate all the channel and opin nodes by a given offset */ + void rotate(size_t offset); /* rotate all the channel and opin nodes by a given offset */ + void swap_chan_node(enum e_side src_side, enum e_side des_side); /* swap the chan rr_nodes on two sides */ + void swap_opin_node(enum e_side src_side, enum e_side des_side); /* swap the OPIN rr_nodes on two sides */ + void swap_ipin_node(enum e_side src_side, enum e_side des_side); /* swap the IPIN rr_nodes on two sides */ + void reverse_opin_node(enum e_side side); /* reverse the OPIN rr_nodes on two sides */ + void reverse_ipin_node(enum e_side side); /* reverse the IPIN rr_nodes on two sides */ + public: /* Mutators: cleaners */ + void clear(); + void clear_chan_nodes(enum e_side node_side); /* Clean the chan_width of a side */ + void clear_ipin_nodes(enum e_side node_side); /* Clean the number of IPINs of a side */ + void clear_opin_nodes(enum e_side node_side); /* Clean the number of OPINs of a side */ + void clear_one_side(enum e_side node_side); /* Clean chan/opin/ipin nodes at one side */ + private: /* Internal Mutators */ + void mirror_side_chan_node_direction(enum e_side side); /* Mirror the node direction and port direction of routing track nodes on a side */ + private: /* internal functions */ + bool is_sb_node_mirror (const RRGSB& cand, enum e_side node_side, size_t track_id) const; + bool is_cb_node_mirror (const RRGSB& cand, t_rr_type cb_type, enum e_side node_side, size_t node_id) const; + size_t get_track_id_first_short_connection(enum e_side node_side) const; + bool validate_num_sides() const; + bool validate_side(enum e_side side) const; + bool validate_track_id(enum e_side side, size_t track_id) const; + bool validate_opin_node_id(enum e_side side, size_t node_id) const; + bool validate_ipin_node_id(enum e_side side, size_t node_id) const; + bool validate_cb_type(t_rr_type cb_type) const; + private: /* Internal Data */ + /* Coordinator */ + DeviceCoordinator coordinator_; + /* Routing channel data */ + std::vector chan_node_; + std::vector< std::vector > chan_node_direction_; + + /* Logic Block Inputs data */ + std::vector< std::vector > ipin_node_; + std::vector< std::vector > ipin_node_grid_side_; + + /* Logic Block Outputs data */ + std::vector< std::vector > opin_node_; + std::vector< std::vector > opin_node_grid_side_; + + /* Configuration bits */ + ConfPorts sb_conf_port_; + ConfPorts cbx_conf_port_; + ConfPorts cby_conf_port_; +}; + + +} /* End namespace openfpga*/ + +#endif From 230c7b709aac11eb0ef9589ea4f4d6de5f6d0fa5 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 9 Feb 2020 00:20:44 -0700 Subject: [PATCH 110/645] put rr_gsb data structure online --- openfpga/src/annotation/rr_gsb.cpp | 1001 +++++++++++++++++ openfpga/src/annotation/rr_gsb.h | 275 +++-- .../src/utils/openfpga_rr_graph_utils.cpp | 66 ++ openfpga/src/utils/openfpga_rr_graph_utils.h | 28 + 4 files changed, 1251 insertions(+), 119 deletions(-) create mode 100644 openfpga/src/annotation/rr_gsb.cpp create mode 100644 openfpga/src/utils/openfpga_rr_graph_utils.cpp create mode 100644 openfpga/src/utils/openfpga_rr_graph_utils.h diff --git a/openfpga/src/annotation/rr_gsb.cpp b/openfpga/src/annotation/rr_gsb.cpp new file mode 100644 index 000000000..54b9fa8f2 --- /dev/null +++ b/openfpga/src/annotation/rr_gsb.cpp @@ -0,0 +1,1001 @@ +/************************************************************************ + * Member functions for class RRGSB + ***********************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_log.h" +#include "vtr_assert.h" + +/* Headers from openfpgautil library */ +#include "openfpga_side_manager.h" + +#include "openfpga_rr_graph_utils.h" + +#include "rr_gsb.h" + +/* namespace openfpga begins */ +namespace openfpga { + +/************************************************************************ + * Constructors + ***********************************************************************/ +/* Constructor for an empty object */ +RRGSB::RRGSB() { + /* Set a clean start! */ + coordinate_.set(0, 0); + chan_node_direction_.clear(); + ipin_node_.clear(); + opin_node_.clear(); +} + +/* Copy constructor */ +RRGSB::RRGSB(const RRGSB& src) { + /* Copy coordinate */ + this->set(src); + return; +} + +/************************************************************************ + * Accessors + ***********************************************************************/ +/* Get the number of sides of this SB */ +size_t RRGSB::get_num_sides() const { + VTR_ASSERT (validate_num_sides()); + return chan_node_direction_.size(); +} + +/* Get the number of routing tracks on a side */ +size_t RRGSB::get_chan_width(const e_side& side) const { + SideManager side_manager(side); + VTR_ASSERT(side_manager.validate()); + return chan_node_[side_manager.to_size_t()].get_chan_width(); +} + +/* Get the maximum number of routing tracks on all sides */ +size_t RRGSB::get_max_chan_width() const { + size_t max_chan_width = 0; + for (size_t side = 0; side < get_num_sides(); ++side) { + SideManager side_manager(side); + max_chan_width = std::max(max_chan_width, get_chan_width(side_manager.get_side())); + } + return max_chan_width; +} + +/* Get the number of routing tracks of a X/Y-direction CB */ +size_t RRGSB::get_cb_chan_width(const t_rr_type& cb_type) const { + return get_chan_width(get_cb_chan_side(cb_type)); +} + +/* Get the sides of ipin_nodes belong to the cb */ +std::vector RRGSB::get_cb_ipin_sides(const t_rr_type& cb_type) const { + VTR_ASSERT (validate_cb_type(cb_type)); + + std::vector ipin_sides; + + /* Make sure a clean start */ + ipin_sides.clear(); + + switch(cb_type) { + case CHANX: + ipin_sides.push_back(TOP); + ipin_sides.push_back(BOTTOM); + break; + case CHANY: + ipin_sides.push_back(RIGHT); + ipin_sides.push_back(LEFT); + break; + default: + VTR_LOG("Invalid type of connection block!\n"); + exit(1); + } + + return ipin_sides; +} + +/* Get the direction of a rr_node at a given side and track_id */ +enum PORTS RRGSB::get_chan_node_direction(const e_side& side, const size_t& track_id) const { + SideManager side_manager(side); + VTR_ASSERT(side_manager.validate()); + + /* Ensure the side is valid in the context of this switch block */ + VTR_ASSERT( validate_side(side) ); + + /* Ensure the track is valid in the context of this switch block at a specific side */ + VTR_ASSERT( validate_track_id(side, track_id) ); + + return chan_node_direction_[side_manager.to_size_t()][track_id]; +} + +/* Get a list of segments used in this routing channel */ +std::vector RRGSB::get_chan_segment_ids(const e_side& side) const { + SideManager side_manager(side); + VTR_ASSERT(side_manager.validate()); + + /* Ensure the side is valid in the context of this switch block */ + VTR_ASSERT( validate_side(side) ); + + return chan_node_[side_manager.to_size_t()].get_segment_ids(); +} + +/* Get a list of rr_nodes whose sed_id is specified */ +std::vector RRGSB::get_chan_node_ids_by_segment_ids(const e_side& side, + const RRSegmentId& seg_id) const { + return chan_node_[size_t(side)].get_node_ids_by_segment_ids(seg_id); +} + +/* get a rr_node at a given side and track_id */ +RRNodeId RRGSB::get_chan_node(const e_side& side, const size_t& track_id) const { + SideManager side_manager(side); + VTR_ASSERT(side_manager.validate()); + + /* Ensure the side is valid in the context of this switch block */ + VTR_ASSERT( validate_side(side) ); + + /* Ensure the track is valid in the context of this switch block at a specific side */ + VTR_ASSERT( validate_track_id(side, track_id) ); + + return chan_node_[side_manager.to_size_t()].get_node(track_id); +} + +/* get the segment id of a channel rr_node */ +RRSegmentId RRGSB::get_chan_node_segment(const e_side& side, const size_t& track_id) const { + SideManager side_manager(side); + VTR_ASSERT(side_manager.validate()); + + /* Ensure the side is valid in the context of this switch block */ + VTR_ASSERT( validate_side(side) ); + + /* Ensure the track is valid in the context of this switch block at a specific side */ + VTR_ASSERT( validate_track_id(side, track_id) ); + + return chan_node_[side_manager.to_size_t()].get_node_segment(track_id); +} + +/* Get the number of IPIN rr_nodes on a side */ +size_t RRGSB::get_num_ipin_nodes(const e_side& side) const { + SideManager side_manager(side); + VTR_ASSERT(side_manager.validate()); + return ipin_node_[side_manager.to_size_t()].size(); +} + +/* get a opin_node at a given side and track_id */ +RRNodeId RRGSB::get_ipin_node(const e_side& side, const size_t& node_id) const { + SideManager side_manager(side); + VTR_ASSERT(side_manager.validate()); + + /* Ensure the side is valid in the context of this switch block */ + VTR_ASSERT( validate_side(side) ); + + /* Ensure the track is valid in the context of this switch block at a specific side */ + VTR_ASSERT( validate_ipin_node_id(side, node_id) ); + + return ipin_node_[side_manager.to_size_t()][node_id]; +} + +/* Get the number of OPIN rr_nodes on a side */ +size_t RRGSB::get_num_opin_nodes(const e_side& side) const { + SideManager side_manager(side); + VTR_ASSERT(side_manager.validate()); + return opin_node_[side_manager.to_size_t()].size(); +} + +/* get a opin_node at a given side and track_id */ +RRNodeId RRGSB::get_opin_node(const e_side& side, const size_t& node_id) const { + SideManager side_manager(side); + VTR_ASSERT(side_manager.validate()); + + /* Ensure the side is valid in the context of this switch block */ + VTR_ASSERT( validate_side(side) ); + + /* Ensure the track is valid in the context of this switch block at a specific side */ + VTR_ASSERT( validate_opin_node_id(side, node_id) ); + + return opin_node_[side_manager.to_size_t()][node_id]; +} + +/* Get the node index of a routing track of a connection block, return -1 if not found */ +int RRGSB::get_cb_chan_node_index(const t_rr_type& cb_type, const RRNodeId& node) const { + enum e_side chan_side = get_cb_chan_side(cb_type); + return get_chan_node_index(chan_side, node); +} + +/* Get the node index in the array, return -1 if not found */ +int RRGSB::get_chan_node_index(const e_side& node_side, const RRNodeId& node) const { + VTR_ASSERT (validate_side(node_side)); + return chan_node_[size_t(node_side)].get_node_track_id(node); +} + +/* Get the node index in the array, return -1 if not found */ +int RRGSB::get_node_index(const RRGraph& rr_graph, + const RRNodeId& node, + const e_side& node_side, + const PORTS& node_direction) const { + size_t cnt; + int ret; + + cnt = 0; + ret = -1; + + /* Depending on the type of rr_node, we search different arrays */ + switch (rr_graph.node_type(node)) { + case CHANX: + case CHANY: + for (size_t inode = 0; inode < get_chan_width(node_side); ++inode){ + if ((node == chan_node_[size_t(node_side)].get_node(inode)) + /* Check if direction meets specification */ + &&(node_direction == chan_node_direction_[size_t(node_side)][inode])) { + cnt++; + ret = inode; + break; + } + } + break; + case IPIN: + for (size_t inode = 0; inode < get_num_ipin_nodes(node_side); ++inode) { + if (node == ipin_node_[size_t(node_side)][inode]) { + cnt++; + ret = inode; + break; + } + } + break; + case OPIN: + for (size_t inode = 0; inode < get_num_opin_nodes(node_side); ++inode) { + if (node == opin_node_[size_t(node_side)][inode]) { + cnt++; + ret = inode; + break; + } + } + break; + default: + VTR_LOG("Invalid cur_rr_node type! Should be [CHANX|CHANY|IPIN|OPIN]\n"); + exit(1); + } + + VTR_ASSERT((0 == cnt)||(1 == cnt)); + + return ret; /* Return an invalid value: nonthing is found*/ +} + +/* Get the side of a node in this SB */ +void RRGSB::get_node_side_and_index(const RRGraph& rr_graph, + const RRNodeId& node, + const PORTS& node_direction, + e_side& node_side, + int& node_index) const { + size_t side; + SideManager side_manager; + + /* Count the number of existence of cur_rr_node in cur_sb_info + * It could happen that same cur_rr_node appears on different sides of a SB + * For example, a routing track go vertically across the SB. + * Then its corresponding rr_node appears on both TOP and BOTTOM sides of this SB. + * We need to ensure that the found rr_node has the same direction as user want. + * By specifying the direction of rr_node, There should be only one rr_node can satisfy! + */ + for (side = 0; side < get_num_sides(); ++side) { + side_manager.set_side(side); + node_index = get_node_index(rr_graph, node, side_manager.get_side(), node_direction); + if (-1 != node_index) { + break; + } + } + + if (side == get_num_sides()) { + /* we find nothing, return NUM_SIDES, and a OPEN node (-1) */ + node_side = NUM_SIDES; + VTR_ASSERT(-1 == node_index); + return; + } + + node_side = side_manager.get_side(); + VTR_ASSERT(-1 != node_index); + + return; +} + +/* Check if the node exist in the opposite side of this Switch Block */ +bool RRGSB::is_sb_node_exist_opposite_side(const RRGraph& rr_graph, + const RRNodeId& node, + const e_side& node_side) const { + SideManager side_manager(node_side); + int index; + + VTR_ASSERT((CHANX == rr_graph.node_type(node)) || (CHANY == rr_graph.node_type(node))); + + /* See if we can find the same src_rr_node in the opposite chan_side + * if there is one, it means a shorted wire across the SB + */ + index = get_node_index(rr_graph, node, side_manager.get_opposite(), IN_PORT); + + return (-1 != index); +} + +/* check if the candidate CB is a mirror of the current one */ +bool RRGSB::is_cb_mirror(const RRGraph& rr_graph, const RRGSB& cand, const t_rr_type& cb_type) const { + /* Check if channel width is the same */ + if ( get_cb_chan_width(cb_type) != cand.get_cb_chan_width(cb_type) ) { + return false; + } + + enum e_side chan_side = get_cb_chan_side(cb_type); + + /* check the numbers/directionality of channel rr_nodes */ + if ( false == chan_node_[size_t(chan_side)].is_mirror(rr_graph, cand.chan_node_[size_t(chan_side)]) ) { + return false; + } + + /* check the equivalence of ipins */ + std::vector ipin_side = get_cb_ipin_sides(cb_type); + for (size_t side = 0; side < ipin_side.size(); ++side) { + /* Ensure we have the same number of IPINs on this side */ + if ( get_num_ipin_nodes(ipin_side[side]) != cand.get_num_ipin_nodes(ipin_side[side]) ) { + return false; + } + for (size_t inode = 0; inode < get_num_ipin_nodes(ipin_side[side]); ++inode) { + if (false == is_cb_node_mirror(rr_graph, cand, cb_type, ipin_side[side], inode)) { + return false; + } + } + } + + return true; +} + +/* check if the CB exist in this GSB */ +bool RRGSB::is_cb_exist(const t_rr_type& cb_type) const { + /* if channel width is zero, there is no CB */ + return (0 != get_cb_chan_width(cb_type)); +} + + +/************************************************************************ + * Check if the node indicates a passing wire across the Switch Block part of the GSB + * Therefore, we actually do the following check + * Check if a track starts from this GSB or not + * For INC_DIRECTION + * (xlow, ylow) should be same as the GSB side coordinate + * For DEC_DIRECTION + * (xhigh, yhigh) should be same as the GSB side coordinate + ***********************************************************************/ +bool RRGSB::is_sb_node_passing_wire(const RRGraph& rr_graph, + const e_side& node_side, + const size_t& track_id) const { + + /* Get the rr_node */ + RRNodeId track_node = get_chan_node(node_side, track_id); + /* Get the coordinates */ + vtr::Point side_coordinate = get_side_block_coordinate(node_side); + + /* Get the coordinate of where the track starts */ + vtr::Point track_start = get_track_rr_node_start_coordinate(rr_graph, track_node); + + /* INC_DIRECTION start_track: (xlow, ylow) should be same as the GSB side coordinate */ + /* DEC_DIRECTION start_track: (xhigh, yhigh) should be same as the GSB side coordinate */ + if ( (track_start.x() == side_coordinate.x()) + && (track_start.y() == side_coordinate.y()) + && (OUT_PORT == get_chan_node_direction(node_side, track_id)) ) { + /* Double check: start track should be an OUTPUT PORT of the GSB */ + return false; /* This is a starting point */ + } + + /* Get the coordinate of where the track ends */ + vtr::Point track_end = get_track_rr_node_end_coordinate(rr_graph, track_node); + + /* INC_DIRECTION end_track: (xhigh, yhigh) should be same as the GSB side coordinate */ + /* DEC_DIRECTION end_track: (xlow, ylow) should be same as the GSB side coordinate */ + if ( (track_end.x() == side_coordinate.x()) + && (track_end.y() == side_coordinate.y()) + && (IN_PORT == get_chan_node_direction(node_side, track_id)) ) { + /* Double check: end track should be an INPUT PORT of the GSB */ + return false; /* This is an ending point */ + } + + /* Reach here it means that this will be a passing wire, + * we should be able to find the node on the opposite side of the GSB! + */ + VTR_ASSERT (true == is_sb_node_exist_opposite_side(rr_graph, track_node, node_side)); + + return true; +} + +/* check if the candidate SB satisfy the basic requirements on being a mirror of the current one */ +/* Idenify mirror Switch blocks + * Check each two switch blocks: + * Number of channel/opin/ipin rr_nodes are same + * If all above are satisfied, the two switch blocks may be mirrors ! + */ +bool RRGSB::is_sb_mirrorable(const RRGraph& rr_graph, const RRGSB& cand) const { + /* check the numbers of sides */ + if (get_num_sides() != cand.get_num_sides()) { + return false; + } + + /* check the numbers/directionality of channel rr_nodes */ + for (size_t side = 0; side < get_num_sides(); ++side) { + SideManager side_manager(side); + + /* Ensure we have the same channel width on this side */ + if (get_chan_width(side_manager.get_side()) != cand.get_chan_width(side_manager.get_side())) { + return false; + } + + if ( ((size_t(-1) == get_track_id_first_short_connection(rr_graph, side_manager.get_side())) + && (size_t(-1) != cand.get_track_id_first_short_connection(rr_graph, side_manager.get_side()))) + || ((size_t(-1) != get_track_id_first_short_connection(rr_graph, side_manager.get_side()) ) + && ( size_t(-1) == cand.get_track_id_first_short_connection(rr_graph, side_manager.get_side()))) ) { + return false; + } + } + + /* check the numbers of opin_rr_nodes */ + for (size_t side = 0; side < get_num_sides(); ++side) { + SideManager side_manager(side); + + if (get_num_opin_nodes(side_manager.get_side()) != cand.get_num_opin_nodes(side_manager.get_side())) { + return false; + } + } + + return true; +} + +/* check if all the routing segments of a side of candidate SB is a mirror of the current one */ +bool RRGSB::is_sb_side_segment_mirror(const RRGraph& rr_graph, const RRGSB& cand, + const e_side& side, const RRSegmentId& seg_id) const { + /* Create a side manager */ + SideManager side_manager(side); + + /* Make sure both Switch blocks has this side!!! */ + VTR_ASSERT ( side_manager.to_size_t() < get_num_sides() ); + VTR_ASSERT ( side_manager.to_size_t() < cand.get_num_sides() ); + + /* check the numbers/directionality of channel rr_nodes */ + /* Ensure we have the same channel width on this side */ + if (get_chan_width(side) != cand.get_chan_width(side)) { + return false; + } + for (size_t itrack = 0; itrack < get_chan_width(side); ++itrack) { + /* Bypass unrelated segments */ + if (seg_id != get_chan_node_segment(side, itrack)) { + continue; + } + /* Check the directionality of each node */ + if (get_chan_node_direction(side, itrack) != cand.get_chan_node_direction(side, itrack)) { + return false; + } + /* Check the track_id of each node + * ptc is not necessary, we care the connectivity! + */ + /* For OUT_PORT rr_node, we need to check fan-in */ + if (OUT_PORT != get_chan_node_direction(side, itrack)) { + continue; /* skip IN_PORT */ + } + + if (false == is_sb_node_mirror(rr_graph, cand, side, itrack)) { + return false; + } + } + + /* check the numbers of opin_rr_nodes */ + if (get_num_opin_nodes(side) != cand.get_num_opin_nodes(side)) { + return false; + } + + /* check the numbers of ipin_rr_nodes */ + if (get_num_ipin_nodes(side) != cand.get_num_ipin_nodes(side)) { + return false; + } + + return true; +} + +/* check if a side of candidate SB is a mirror of the current one */ +bool RRGSB::is_sb_side_mirror(const RRGraph& rr_graph, const RRGSB& cand, const e_side& side) const { + + /* get a list of segments */ + std::vector seg_ids = chan_node_[size_t(side)].get_segment_ids(); + + for (size_t iseg = 0; iseg < seg_ids.size(); ++iseg) { + if (false == is_sb_side_segment_mirror(rr_graph, cand, side, seg_ids[iseg])) { + return false; + } + } + + return true; +} + +/* check if the candidate SB is a mirror of the current one */ +bool RRGSB::is_sb_mirror(const RRGraph& rr_graph, const RRGSB& cand) const { + /* check the numbers of sides */ + if (get_num_sides() != cand.get_num_sides()) { + return false; + } + + /* check the numbers/directionality of channel rr_nodes */ + for (size_t side = 0; side < get_num_sides(); ++side) { + SideManager side_manager(side); + if (false == is_sb_side_mirror(rr_graph, cand, side_manager.get_side())) { + return false; + } + } + + return true; +} + +/* Public Accessors: Cooridinator conversion */ + +/* get the x coordinate of this GSB */ +size_t RRGSB::get_x() const { + return coordinate_.x(); +} + +/* get the y coordinate of this GSB */ +size_t RRGSB::get_y() const { + return coordinate_.y(); +} + + +/* get the x coordinate of this switch block */ +size_t RRGSB::get_sb_x() const { + return coordinate_.x(); +} + +/* get the y coordinate of this switch block */ +size_t RRGSB::get_sb_y() const { + return coordinate_.y(); +} + +/* Get the number of sides of this SB */ +vtr::Point RRGSB::get_sb_coordinate() const { + return coordinate_; +} + +/* get the x coordinate of this X/Y-direction block */ +size_t RRGSB::get_cb_x(const t_rr_type& cb_type) const { + VTR_ASSERT (validate_cb_type(cb_type)); + switch(cb_type) { + case CHANX: + return get_side_block_coordinate(LEFT).x(); + case CHANY: + return get_side_block_coordinate(TOP).x(); + default: + VTR_LOG("Invalid type of connection block!\n"); + exit(1); + } +} + +/* get the y coordinate of this X/Y-direction block */ +size_t RRGSB::get_cb_y(const t_rr_type& cb_type) const { + VTR_ASSERT (validate_cb_type(cb_type)); + switch(cb_type) { + case CHANX: + return get_side_block_coordinate(LEFT).y(); + case CHANY: + return get_side_block_coordinate(TOP).y(); + default: + VTR_LOG("Invalid type of connection block!\n"); + exit(1); + } +} + +/* Get the coordinate of the X/Y-direction CB */ +vtr::Point RRGSB::get_cb_coordinate(const t_rr_type& cb_type) const { + VTR_ASSERT (validate_cb_type(cb_type)); + switch(cb_type) { + case CHANX: + return get_side_block_coordinate(LEFT); + case CHANY: + return get_side_block_coordinate(TOP); + default: + VTR_LOG("Invalid type of connection block!\n"); + exit(1); + } +} + +e_side RRGSB::get_cb_chan_side(const t_rr_type& cb_type) const { + VTR_ASSERT (validate_cb_type(cb_type)); + switch(cb_type) { + case CHANX: + return LEFT; + case CHANY: + return TOP; + default: + VTR_LOG("Invalid type of connection block!\n"); + exit(1); + } +} + +/* Get the side of routing channel in the GSB according to the side of IPIN */ +e_side RRGSB::get_cb_chan_side(const e_side& ipin_side) const { + switch(ipin_side) { + case TOP: + return LEFT; + case RIGHT: + return TOP; + case BOTTOM: + return LEFT; + case LEFT: + return TOP; + default: + VTR_LOG("Invalid type of ipin_side!\n"); + exit(1); + } +} + +vtr::Point RRGSB::get_side_block_coordinate(const e_side& side) const { + SideManager side_manager(side); + VTR_ASSERT(side_manager.validate()); + vtr::Point ret(get_sb_x(), get_sb_y()); + + switch (side_manager.get_side()) { + case TOP: + /* (0 == side) */ + /* 1. Channel Y [x][y+1] inputs */ + ret.set_y(ret.y() + 1); + break; + case RIGHT: + /* 1 == side */ + /* 2. Channel X [x+1][y] inputs */ + ret.set_x(ret.x() + 1); + break; + case BOTTOM: + /* 2 == side */ + /* 3. Channel Y [x][y] inputs */ + break; + case LEFT: + /* 3 == side */ + /* 4. Channel X [x][y] inputs */ + break; + default: + VTR_LOG(" Invalid side!\n"); + exit(1); + } + + return ret; +} + +vtr::Point RRGSB::get_grid_coordinate() const { + vtr::Point ret(get_sb_x(), get_sb_y()); + ret.set_y(ret.y() + 1); + + return ret; +} +/* Public mutators */ + +/* get a copy from a source */ +void RRGSB::set(const RRGSB& src) { + /* Copy coordinate */ + this->set_coordinate(src.get_sb_coordinate().x(), src.get_sb_coordinate().y()); + + /* Initialize sides */ + this->init_num_sides(src.get_num_sides()); + + /* Copy vectors */ + for (size_t side = 0; side < src.get_num_sides(); ++side) { + SideManager side_manager(side); + /* Copy chan_nodes */ + /* skip if there is no channel width */ + if ( 0 < src.get_chan_width(side_manager.get_side()) ) { + this->chan_node_[side_manager.get_side()].set(src.chan_node_[side_manager.get_side()]); + /* Copy chan_node_direction_*/ + this->chan_node_direction_[side_manager.get_side()].clear(); + for (size_t inode = 0; inode < src.get_chan_width(side_manager.get_side()); ++inode) { + this->chan_node_direction_[side_manager.get_side()].push_back(src.get_chan_node_direction(side_manager.get_side(), inode)); + } + } + + /* Copy opin_node and opin_node_grid_side_ */ + this->opin_node_[side_manager.get_side()].clear(); + for (size_t inode = 0; inode < src.get_num_opin_nodes(side_manager.get_side()); ++inode) { + this->opin_node_[side_manager.get_side()].push_back(src.get_opin_node(side_manager.get_side(), inode)); + } + + /* Copy ipin_node and ipin_node_grid_side_ */ + this->ipin_node_[side_manager.get_side()].clear(); + for (size_t inode = 0; inode < src.get_num_ipin_nodes(side_manager.get_side()); ++inode) { + this->ipin_node_[side_manager.get_side()].push_back(src.get_ipin_node(side_manager.get_side(), inode)); + } + } +} + +/* Set the coordinate (x,y) for the switch block */ +void RRGSB::set_coordinate(const size_t& x, const size_t& y) { + coordinate_.set(x, y); +} + +/* Allocate the vectors with the given number of sides */ +void RRGSB::init_num_sides(const size_t& num_sides) { + /* Initialize the vectors */ + chan_node_.resize(num_sides); + chan_node_direction_.resize(num_sides); + ipin_node_.resize(num_sides); + opin_node_.resize(num_sides); +} + +/* Add a node to the chan_node_ list and also assign its direction in chan_node_direction_ */ +void RRGSB::add_chan_node(const e_side& node_side, RRChan& rr_chan, const std::vector& rr_chan_dir) { + /* Validate: 1. side is valid, the type of node is valid */ + VTR_ASSERT(validate_side(node_side)); + + /* fill the dedicated element in the vector */ + chan_node_[size_t(node_side)].set(rr_chan); + chan_node_direction_[size_t(node_side)].resize(rr_chan_dir.size()); + for (size_t inode = 0; inode < rr_chan_dir.size(); ++inode) { + chan_node_direction_[size_t(node_side)][inode] = rr_chan_dir[inode]; + } +} + +/* Add a node to the chan_node_ list and also assign its direction in chan_node_direction_ */ +void RRGSB::add_ipin_node(const RRNodeId& node, const e_side& node_side) { + VTR_ASSERT(validate_side(node_side)); + /* push pack the dedicated element in the vector */ + ipin_node_[size_t(node_side)].push_back(node); +} + +/* Add a node to the chan_node_ list and also assign its direction in chan_node_direction_ */ +void RRGSB::add_opin_node(const RRNodeId& node, const e_side& node_side) { + VTR_ASSERT(validate_side(node_side)); + /* push pack the dedicated element in the vector */ + opin_node_[size_t(node_side)].push_back(node); +} + +/* Reset the RRGSB to pristine state */ +void RRGSB::clear() { + /* Clean all the vectors */ + VTR_ASSERT(validate_num_sides()); + /* Clear the inner vector of each matrix */ + for (size_t side = 0; side < get_num_sides(); ++side) { + chan_node_direction_[side].clear(); + chan_node_[side].clear(); + ipin_node_[side].clear(); + opin_node_[side].clear(); + } + chan_node_direction_.clear(); + chan_node_.clear(); + ipin_node_.clear(); + opin_node_.clear(); +} + +/* Clean the chan_width of a side */ +void RRGSB::clear_chan_nodes(const e_side& node_side) { + VTR_ASSERT(validate_side(node_side)); + + chan_node_[size_t(node_side)].clear(); + chan_node_direction_[size_t(node_side)].clear(); +} + +/* Clean the number of IPINs of a side */ +void RRGSB::clear_ipin_nodes(const e_side& node_side) { + VTR_ASSERT(validate_side(node_side)); + + ipin_node_[size_t(node_side)].clear(); +} + +/* Clean the number of OPINs of a side */ +void RRGSB::clear_opin_nodes(const e_side& node_side) { + VTR_ASSERT(validate_side(node_side)); + + opin_node_[size_t(node_side)].clear(); +} + +/* Clean chan/opin/ipin nodes at one side */ +void RRGSB::clear_one_side(const e_side& node_side) { + clear_chan_nodes(node_side); + clear_ipin_nodes(node_side); + clear_opin_nodes(node_side); +} + +/* Internal functions for validation */ + +/* check if two rr_nodes have a similar set of drive_rr_nodes + * for each drive_rr_node: + * 1. CHANX or CHANY: should have the same side and index + * 2. OPIN or IPIN: should have the same side and index + * 3. each drive_rr_switch should be the same + */ +bool RRGSB::is_sb_node_mirror(const RRGraph& rr_graph, + const RRGSB& cand, + const e_side& node_side, + const size_t& track_id) const { + /* Ensure rr_nodes are either the output of short-connection or multiplexer */ + RRNodeId node = this->get_chan_node(node_side, track_id); + RRNodeId cand_node = cand.get_chan_node(node_side, track_id); + bool is_short_conkt = this->is_sb_node_passing_wire(rr_graph, node_side, track_id); + + if (is_short_conkt != cand.is_sb_node_passing_wire(rr_graph, node_side, track_id)) { + return false; + } + + if (true == is_short_conkt) { + /* Since, both are pass wires, + * The two node should be equivalent + * we can return here + */ + return true; + } + + /* For non-passing wires, check driving rr_nodes */ + if (rr_graph.node_in_edges(node).size() != rr_graph.node_in_edges(cand_node).size()) { + return false; + } + + std::vector node_in_edges; + for (const RREdgeId& edge : rr_graph.node_in_edges(node)) { + node_in_edges.push_back(edge); + } + + std::vector cand_node_in_edges; + for (const RREdgeId& edge : rr_graph.node_in_edges(cand_node)) { + cand_node_in_edges.push_back(edge); + } + VTR_ASSERT(node_in_edges.size() == cand_node_in_edges.size()); + + for (size_t iedge = 0; iedge < node_in_edges.size(); ++iedge) { + RREdgeId src_edge = node_in_edges[iedge]; + RREdgeId src_cand_edge = cand_node_in_edges[iedge]; + RRNodeId src_node = rr_graph.edge_src_node(src_edge); + RRNodeId src_cand_node = rr_graph.edge_src_node(src_cand_edge); + /* node type should be the same */ + if (rr_graph.node_type(src_node) != rr_graph.node_type(cand_node)) { + return false; + } + /* switch type should be the same */ + if (rr_graph.edge_switch(src_edge) != rr_graph.edge_switch(src_cand_edge)) { + return false; + } + int src_node_id, des_node_id; + enum e_side src_node_side, des_node_side; + this->get_node_side_and_index(rr_graph, src_node, OUT_PORT, src_node_side, src_node_id); + cand.get_node_side_and_index(rr_graph, src_cand_node, OUT_PORT, des_node_side, des_node_id); + if (src_node_id != des_node_id) { + return false; + } + if (src_node_side != des_node_side) { + return false; + } + } + + return true; +} + +/* check if two ipin_nodes have a similar set of drive_rr_nodes + * for each drive_rr_node: + * 1. CHANX or CHANY: should have the same side and index + * 2. each drive_rr_switch should be the same + */ +bool RRGSB::is_cb_node_mirror(const RRGraph& rr_graph, + const RRGSB& cand, + const t_rr_type& cb_type, + const e_side& node_side, + const size_t& node_id) const { + /* Ensure rr_nodes are either the output of short-connection or multiplexer */ + RRNodeId node = this->get_ipin_node(node_side, node_id); + RRNodeId cand_node = cand.get_ipin_node(node_side, node_id); + + if ( rr_graph.node_in_edges(node).size() != rr_graph.node_in_edges(cand_node).size() ) { + return false; + } + + std::vector node_in_edges; + for (const RREdgeId& edge : rr_graph.node_in_edges(node)) { + node_in_edges.push_back(edge); + } + + std::vector cand_node_in_edges; + for (const RREdgeId& edge : rr_graph.node_in_edges(cand_node)) { + cand_node_in_edges.push_back(edge); + } + VTR_ASSERT(node_in_edges.size() == cand_node_in_edges.size()); + + for (size_t iedge = 0; iedge < node_in_edges.size(); ++iedge) { + RREdgeId src_edge = node_in_edges[iedge]; + RREdgeId src_cand_edge = cand_node_in_edges[iedge]; + RRNodeId src_node = rr_graph.edge_src_node(src_edge); + RRNodeId src_cand_node = rr_graph.edge_src_node(src_cand_edge); + /* node type should be the same */ + if (rr_graph.node_type(src_node) != rr_graph.node_type(src_cand_node)) { + return false; + } + /* switch type should be the same */ + if (rr_graph.edge_switch(src_edge)!= rr_graph.edge_switch(src_cand_edge)) { + return false; + } + + int src_node_id, des_node_id; + enum e_side src_node_side, des_node_side; + enum e_side chan_side = get_cb_chan_side(cb_type); + switch (rr_graph.node_type(src_node)) { + case CHANX: + case CHANY: + /* if the drive rr_nodes are routing tracks, find index */ + src_node_id = this->get_chan_node_index(chan_side, src_node); + des_node_id = cand.get_chan_node_index(chan_side, src_cand_node); + break; + case OPIN: + this->get_node_side_and_index(rr_graph, src_node, OUT_PORT, src_node_side, src_node_id); + cand.get_node_side_and_index(rr_graph, src_cand_node, OUT_PORT, des_node_side, des_node_id); + if (src_node_side != des_node_side) { + return false; + } + break; + default: + VTR_LOG("Invalid type of drive_rr_nodes for ipin_node!\n"); + exit(1); + } + if (src_node_id != des_node_id) { + return false; + } + } + + return true; +} + +size_t RRGSB::get_track_id_first_short_connection(const RRGraph& rr_graph, const e_side& node_side) const { + VTR_ASSERT(validate_side(node_side)); + + /* Walk through chan_nodes and find the first short connection */ + for (size_t inode = 0; inode < get_chan_width(node_side); ++inode) { + if (true == is_sb_node_passing_wire(rr_graph, node_side, inode)) { + return inode; + } + } + + return size_t(-1); +} + +/* Validate if the number of sides are consistent among internal data arrays ! */ +bool RRGSB::validate_num_sides() const { + size_t num_sides = chan_node_direction_.size(); + + if ( num_sides != chan_node_.size() ) { + return false; + } + + if ( num_sides != ipin_node_.size() ) { + return false; + } + + if ( num_sides != opin_node_.size() ) { + return false; + } + + return true; +} + +/* Check if the side valid in the context: does the switch block have the side? */ +bool RRGSB::validate_side(const e_side& side) const { + return (size_t(side) < get_num_sides()); +} + +/* Check the track_id is valid for chan_node_ and chan_node_direction_ */ +bool RRGSB::validate_track_id(const e_side& side, const size_t& track_id) const { + if (false == validate_side(side)) { + return false; + } + + return ( ( track_id < chan_node_[size_t(side)].get_chan_width()) + && ( track_id < chan_node_direction_[size_t(side)].size()) ); +} + +/* Check the opin_node_id is valid for opin_node_ and opin_node_grid_side_ */ +bool RRGSB::validate_opin_node_id(const e_side& side, const size_t& node_id) const { + if (false == validate_side(side)) { + return false; + } + return (node_id < opin_node_[size_t(side)].size()); +} + +/* Check the ipin_node_id is valid for opin_node_ and opin_node_grid_side_ */ +bool RRGSB::validate_ipin_node_id(const e_side& side, const size_t& node_id) const { + if (false == validate_side(side)) { + return false; + } + return (node_id < ipin_node_[size_t(side)].size()); +} + +bool RRGSB::validate_cb_type(const t_rr_type& cb_type) const { + return ( (CHANX == cb_type) || (CHANY == cb_type) ); +} + +} /* End namespace openfpga*/ diff --git a/openfpga/src/annotation/rr_gsb.h b/openfpga/src/annotation/rr_gsb.h index 7febeffc7..4f3af9552 100644 --- a/openfpga/src/annotation/rr_gsb.h +++ b/openfpga/src/annotation/rr_gsb.h @@ -4,6 +4,9 @@ /******************************************************************** * Include header files required by the data structure definition *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_geometry.h" + #include "rr_chan.h" /* Begin namespace openfpga */ @@ -15,7 +18,6 @@ namespace openfpga { * 1. A switch block * 2. A X-direction Connection block locates at the left side of the switch block * 2. A Y-direction Connection block locates at the top side of the switch block - * This is a collection of rr_nodes, which may be replaced with RRNodeId in new RRGraph * * +---------------------------------+ * | Y-direction CB | @@ -53,141 +55,176 @@ class RRGSB { RRGSB(const RRGSB&);/* Copy constructor */ RRGSB();/* Default constructor */ public: /* Accessors */ - size_t get_num_sides() const; /* Get the number of sides of this SB */ - size_t get_chan_width(enum e_side side) const; /* Get the number of routing tracks on a side */ - size_t get_cb_chan_width(t_rr_type cb_type) const; /* Get the number of routing tracks of a X/Y-direction CB */ - std::vector get_cb_ipin_sides(t_rr_type cb_type) const; /* Get the sides of CB ipins in the array */ - size_t get_max_chan_width() const; /* Get the maximum number of routing tracks on all sides */ - enum PORTS get_chan_node_direction(enum e_side side, size_t track_id) const; /* Get the direction of a rr_node at a given side and track_id */ - RRChan get_chan(enum e_side side) const; /* get a rr_node at a given side and track_id */ - std::vector get_chan_segment_ids(enum e_side side) const; /* Get a list of segments used in this routing channel */ - std::vector get_chan_node_ids_by_segment_ids(enum e_side side, size_t seg_id) const; /* Get a list of segments used in this routing channel */ - t_rr_node* get_chan_node(enum e_side side, size_t track_id) const; /* get a rr_node at a given side and track_id */ - size_t get_chan_node_segment(enum e_side side, size_t track_id) const; /* get the segment id of a channel rr_node */ - size_t get_num_ipin_nodes(enum e_side side) const; /* Get the number of IPIN rr_nodes on a side */ - t_rr_node* get_ipin_node(enum e_side side, size_t node_id) const; /* get a rr_node at a given side and track_id */ - enum e_side get_ipin_node_grid_side(enum e_side side, size_t node_id) const; /* get a rr_node at a given side and track_id */ - enum e_side get_ipin_node_grid_side(t_rr_node* ipin_node) const; /* get a rr_node at a given side and track_id */ - size_t get_num_opin_nodes(enum e_side side) const; /* Get the number of OPIN rr_nodes on a side */ - t_rr_node* get_opin_node(enum e_side side, size_t node_id) const; /* get a rr_node at a given side and track_id */ - enum e_side get_opin_node_grid_side(enum e_side side, size_t node_id) const; /* get a rr_node at a given side and track_id */ - enum e_side get_opin_node_grid_side(t_rr_node* opin_node) const; /* get a rr_node at a given side and track_id */ - int get_cb_chan_node_index(t_rr_type cb_type, t_rr_node* node) const; - int get_chan_node_index(enum e_side node_side, t_rr_node* node) const; - int get_node_index(t_rr_node* node, enum e_side node_side, enum PORTS node_direction) const; /* Get the node index in the array, return -1 if not found */ - void get_node_side_and_index(t_rr_node* node, enum PORTS node_direction, enum e_side* node_side, int* node_index) const; /* Given a rr_node, try to find its side and index in the Switch block */ - bool is_sb_node_exist_opposite_side(t_rr_node* node, enum e_side node_side) const; /* Check if the node exist in the opposite side of this Switch Block */ - public: /* Accessors: get information about configuration ports */ - size_t get_sb_num_reserved_conf_bits() const; - size_t get_sb_reserved_conf_bits_lsb() const; - size_t get_sb_reserved_conf_bits_msb() const; - size_t get_sb_num_conf_bits() const; - size_t get_sb_conf_bits_lsb() const; - size_t get_sb_conf_bits_msb() const; - size_t get_cb_num_reserved_conf_bits(t_rr_type cb_type) const; - size_t get_cb_reserved_conf_bits_lsb(t_rr_type cb_type) const; - size_t get_cb_reserved_conf_bits_msb(t_rr_type cb_type) const; - size_t get_cb_num_conf_bits(t_rr_type cb_type) const; - size_t get_cb_conf_bits_lsb(t_rr_type cb_type) const; - size_t get_cb_conf_bits_msb(t_rr_type cb_type) const; - bool is_sb_node_passing_wire(const enum e_side node_side, const size_t track_id) const; /* Check if the node imply a short connection inside the SB, which happens to long wires across a FPGA fabric */ - bool is_sb_side_mirror(const RRGSB& cand, enum e_side side) const; /* check if a side of candidate SB is a mirror of the current one */ - bool is_sb_side_segment_mirror(const RRGSB& cand, enum e_side side, size_t seg_id) const; /* check if all the routing segments of a side of candidate SB is a mirror of the current one */ - bool is_sb_mirror(const RRGSB& cand) const; /* check if the candidate SB is a mirror of the current one */ - bool is_sb_mirrorable(const RRGSB& cand) const; /* check if the candidate SB satisfy the basic requirements on being a mirror of the current one */ - bool is_cb_mirror(const RRGSB& cand, t_rr_type cb_type) const; /* check if the candidate SB is a mirror of the current one */ - bool is_cb_exist(t_rr_type cb_type) const; /* check if the candidate SB is a mirror of the current one */ - size_t get_hint_rotate_offset(const RRGSB& cand) const; /* Determine an initial offset in rotating the candidate Switch Block to find a mirror matching*/ + /* Get the number of sides of this SB */ + size_t get_num_sides() const; + + /* Get the number of routing tracks on a side */ + size_t get_chan_width(const e_side& side) const; + + /* Get the maximum number of routing tracks on all sides */ + size_t get_max_chan_width() const; + + /* Get the number of routing tracks of a X/Y-direction CB */ + size_t get_cb_chan_width(const t_rr_type& cb_type) const; + + /* Get the sides of CB ipins in the array */ + std::vector get_cb_ipin_sides(const t_rr_type& cb_type) const; + + /* Get the direction of a rr_node at a given side and track_id */ + enum PORTS get_chan_node_direction(const e_side& side, const size_t& track_id) const; + + /* Get a list of segments used in this routing channel */ + std::vector get_chan_segment_ids(const e_side& side) const; + + /* Get a list of segments used in this routing channel */ + std::vector get_chan_node_ids_by_segment_ids(const e_side& side, + const RRSegmentId& seg_id) const; + + /* get a rr_node at a given side and track_id */ + RRNodeId get_chan_node(const e_side& side, const size_t& track_id) const; + + /* get the segment id of a channel rr_node */ + RRSegmentId get_chan_node_segment(const e_side& side, const size_t& track_id) const; + + /* Get the number of IPIN rr_nodes on a side */ + size_t get_num_ipin_nodes(const e_side& side) const; + + /* get a rr_node at a given side and track_id */ + RRNodeId get_ipin_node(const e_side& side, const size_t& node_id) const; + + /* Get the number of OPIN rr_nodes on a side */ + size_t get_num_opin_nodes(const e_side& side) const; + + /* get a rr_node at a given side and track_id */ + RRNodeId get_opin_node(const e_side& side, const size_t& node_id) const; + + int get_cb_chan_node_index(const t_rr_type& cb_type, const RRNodeId& node) const; + + int get_chan_node_index(const e_side& node_side, const RRNodeId& node) const; + + /* Get the node index in the array, return -1 if not found */ + int get_node_index(const RRGraph& rr_graph, const RRNodeId& node, const e_side& node_side, const PORTS& node_direction) const; + + /* Given a rr_node, try to find its side and index in the Switch block */ + void get_node_side_and_index(const RRGraph& rr_graph, const RRNodeId& node, const PORTS& node_direction, e_side& node_side, int& node_index) const; + + /* Check if the node exist in the opposite side of this Switch Block */ + bool is_sb_node_exist_opposite_side(const RRGraph& rr_graph, const RRNodeId& node, const e_side& node_side) const; + public: /* Accessors: to identify mirrors */ + /* check if the candidate SB is a mirror of the current one */ + bool is_cb_mirror(const RRGraph& rr_graph, const RRGSB& cand, const t_rr_type& cb_type) const; + + /* check if the candidate SB is a mirror of the current one */ + bool is_cb_exist(const t_rr_type& cb_type) const; + + /* Check if the node imply a short connection inside the SB, which happens to long wires across a FPGA fabric */ + bool is_sb_node_passing_wire(const RRGraph& rr_graph, const e_side& node_side, const size_t& track_id) const; + + /* check if the candidate SB satisfy the basic requirements + * on being a mirror of the current one + */ + bool is_sb_mirrorable(const RRGraph& rr_graph, const RRGSB& cand) const; + + /* check if all the routing segments of a side of candidate SB is a mirror of the current one */ + bool is_sb_side_segment_mirror(const RRGraph& rr_graph, const RRGSB& cand, + const e_side& side, const RRSegmentId& seg_id) const; + + /* check if a side of candidate SB is a mirror of the current one + * Check the specified side of two switch blocks: + * 1. Number of channel/opin/ipin rr_nodes are same + * For channel rr_nodes + * 2. check if their track_ids (ptc_num) are same + * 3. Check if the switches (ids) are same + * For opin/ipin rr_nodes, + * 4. check if their parent type_descriptors same, + * 5. check if pin class id and pin id are same + * If all above are satisfied, the side of the two switch blocks are mirrors! + */ + bool is_sb_side_mirror(const RRGraph& rr_graph, const RRGSB& cand, const e_side& side) const; + + /* check if the candidate SB is a mirror of the current one + * Idenify mirror Switch blocks + * Check each two switch blocks: + * 1. Number of channel/opin/ipin rr_nodes are same + * For channel rr_nodes + * 2. check if their track_ids (ptc_num) are same + * 3. Check if the switches (ids) are same + * For opin/ipin rr_nodes, + * 4. check if their parent type_descriptors same, + * 5. check if pin class id and pin id are same + * If all above are satisfied, the two switch blocks are mirrors! + */ + bool is_sb_mirror(const RRGraph& rr_graph, const RRGSB& cand) const; + public: /* Cooridinator conversion and output */ - size_t get_x() const; /* get the x coordinator of this switch block */ - size_t get_y() const; /* get the y coordinator of this switch block */ - size_t get_sb_x() const; /* get the x coordinator of this switch block */ - size_t get_sb_y() const; /* get the y coordinator of this switch block */ - DeviceCoordinator get_sb_coordinator() const; /* Get the coordinator of the SB */ - size_t get_cb_x(t_rr_type cb_type) const; /* get the x coordinator of this X/Y-direction block */ - size_t get_cb_y(t_rr_type cb_type) const; /* get the y coordinator of this X/Y-direction block */ - DeviceCoordinator get_cb_coordinator(t_rr_type cb_type) const; /* Get the coordinator of the X/Y-direction CB */ - enum e_side get_cb_chan_side(t_rr_type cb_type) const; /* get the side of a Connection block */ - enum e_side get_cb_chan_side(enum e_side ipin_side) const; /* get the side of a Connection block */ - DeviceCoordinator get_side_block_coordinator(enum e_side side) const; - DeviceCoordinator get_grid_coordinator() const; - public: /* Verilog writer */ - const char* gen_gsb_verilog_module_name() const; - const char* gen_gsb_verilog_instance_name() const; - const char* gen_sb_verilog_module_name() const; - const char* gen_sb_verilog_instance_name() const; - const char* gen_sb_verilog_side_module_name(enum e_side side, size_t seg_id) const; - const char* gen_sb_verilog_side_instance_name(enum e_side side, size_t seg_id) const; - const char* gen_cb_verilog_module_name(t_rr_type cb_type) const; - const char* gen_cb_verilog_instance_name(t_rr_type cb_type) const; - const char* gen_cb_verilog_routing_track_name(t_rr_type cb_type, size_t track_id) const; + size_t get_x() const; /* get the x coordinate of this switch block */ + size_t get_y() const; /* get the y coordinate of this switch block */ + size_t get_sb_x() const; /* get the x coordinate of this switch block */ + size_t get_sb_y() const; /* get the y coordinate of this switch block */ + vtr::Point get_sb_coordinate() const; /* Get the coordinate of the SB */ + size_t get_cb_x(const t_rr_type& cb_type) const; /* get the x coordinate of this X/Y-direction block */ + size_t get_cb_y(const t_rr_type& cb_type) const; /* get the y coordinate of this X/Y-direction block */ + vtr::Point get_cb_coordinate(const t_rr_type& cb_type) const; /* Get the coordinate of the X/Y-direction CB */ + e_side get_cb_chan_side(const t_rr_type& cb_type) const; /* get the side of a Connection block */ + e_side get_cb_chan_side(const e_side& ipin_side) const; /* get the side of a Connection block */ + vtr::Point get_side_block_coordinate(const e_side& side) const; + vtr::Point get_grid_coordinate() const; public: /* Mutators */ void set(const RRGSB& src); /* get a copy from a source */ - void set_coordinator(size_t x, size_t y); - void init_num_sides(size_t num_sides); /* Allocate the vectors with the given number of sides */ - void add_chan_node(enum e_side node_side, RRChan& rr_chan, std::vector rr_chan_dir); /* Add a node to the chan_rr_node_ list and also assign its direction in chan_rr_node_direction_ */ - void add_ipin_node(t_rr_node* node, const enum e_side node_side, const enum e_side grid_side); /* Add a node to the chan_rr_node_ list and also assign its direction in chan_rr_node_direction_ */ - void add_opin_node(t_rr_node* node, const enum e_side node_side, const enum e_side grid_side); /* Add a node to the chan_rr_node_ list and also assign its direction in chan_rr_node_direction_ */ - void set_sb_num_reserved_conf_bits(size_t num_reserved_conf_bits); - void set_sb_conf_bits_lsb(size_t conf_bits_lsb); - void set_sb_conf_bits_msb(size_t conf_bits_msb); - void set_cb_num_reserved_conf_bits(t_rr_type cb_type, size_t num_reserved_conf_bits); - void set_cb_conf_bits_lsb(t_rr_type cb_type, size_t conf_bits_lsb); - void set_cb_conf_bits_msb(t_rr_type cb_type, size_t conf_bits_msb); - void rotate_side_chan_node_by_direction(enum e_side side, enum e_direction chan_dir, size_t offset); /* rotate all the channel nodes by a given offset */ - void counter_rotate_side_chan_node_by_direction(enum e_side side, enum e_direction chan_dir, size_t offset); /* rotate all the channel nodes by a given offset */ - void rotate_side_chan_node(enum e_side side, size_t offset); /* rotate all the channel nodes by a given offset */ - void rotate_chan_node(size_t offset); /* rotate all the channel nodes by a given offset */ - void rotate_chan_node_in_group(size_t offset); /* rotate all the channel nodes by a given offset */ - void rotate_side_opin_node_in_group(enum e_side side, size_t offset); /* rotate all the opin nodes by a given offset */ - void rotate_opin_node_in_group(size_t offset); /* rotate all the opin nodes by a given offset */ - void rotate_side(enum e_side side, size_t offset); /* rotate all the channel and opin nodes by a given offset */ - void rotate(size_t offset); /* rotate all the channel and opin nodes by a given offset */ - void swap_chan_node(enum e_side src_side, enum e_side des_side); /* swap the chan rr_nodes on two sides */ - void swap_opin_node(enum e_side src_side, enum e_side des_side); /* swap the OPIN rr_nodes on two sides */ - void swap_ipin_node(enum e_side src_side, enum e_side des_side); /* swap the IPIN rr_nodes on two sides */ - void reverse_opin_node(enum e_side side); /* reverse the OPIN rr_nodes on two sides */ - void reverse_ipin_node(enum e_side side); /* reverse the IPIN rr_nodes on two sides */ + void set_coordinate(const size_t& x, const size_t& y); + void init_num_sides(const size_t& num_sides); /* Allocate the vectors with the given number of sides */ + void add_chan_node(const e_side& node_side, RRChan& rr_chan, const std::vector& rr_chan_dir); /* Add a node to the chan_rr_node_ list and also assign its direction in chan_rr_node_direction_ */ + void add_ipin_node(const RRNodeId& node, const e_side& node_side); /* Add a node to the chan_rr_node_ list and also assign its direction in chan_rr_node_direction_ */ + void add_opin_node(const RRNodeId& node, const e_side& node_side); /* Add a node to the chan_rr_node_ list and also assign its direction in chan_rr_node_direction_ */ public: /* Mutators: cleaners */ void clear(); - void clear_chan_nodes(enum e_side node_side); /* Clean the chan_width of a side */ - void clear_ipin_nodes(enum e_side node_side); /* Clean the number of IPINs of a side */ - void clear_opin_nodes(enum e_side node_side); /* Clean the number of OPINs of a side */ - void clear_one_side(enum e_side node_side); /* Clean chan/opin/ipin nodes at one side */ - private: /* Internal Mutators */ - void mirror_side_chan_node_direction(enum e_side side); /* Mirror the node direction and port direction of routing track nodes on a side */ + + /* Clean the chan_width of a side */ + void clear_chan_nodes(const e_side& node_side); + + /* Clean the number of IPINs of a side */ + void clear_ipin_nodes(const e_side& node_side); + + /* Clean the number of OPINs of a side */ + void clear_opin_nodes(const e_side& node_side); + + /* Clean chan/opin/ipin nodes at one side */ + void clear_one_side(const e_side& node_side); private: /* internal functions */ - bool is_sb_node_mirror (const RRGSB& cand, enum e_side node_side, size_t track_id) const; - bool is_cb_node_mirror (const RRGSB& cand, t_rr_type cb_type, enum e_side node_side, size_t node_id) const; - size_t get_track_id_first_short_connection(enum e_side node_side) const; + bool is_sb_node_mirror(const RRGraph& rr_graph, + const RRGSB& cand, + const e_side& node_side, + const size_t& track_id) const; + + bool is_cb_node_mirror(const RRGraph& rr_graph, + const RRGSB& cand, + const t_rr_type& cb_type, + const e_side& node_side, + const size_t& node_id) const; + + size_t get_track_id_first_short_connection(const RRGraph& rr_graph, const e_side& node_side) const; + + private: /* internal validators */ bool validate_num_sides() const; - bool validate_side(enum e_side side) const; - bool validate_track_id(enum e_side side, size_t track_id) const; - bool validate_opin_node_id(enum e_side side, size_t node_id) const; - bool validate_ipin_node_id(enum e_side side, size_t node_id) const; - bool validate_cb_type(t_rr_type cb_type) const; + bool validate_side(const e_side& side) const; + bool validate_track_id(const e_side& side, const size_t& track_id) const; + bool validate_opin_node_id(const e_side& side, const size_t& node_id) const; + bool validate_ipin_node_id(const e_side& side, const size_t& node_id) const; + bool validate_cb_type(const t_rr_type& cb_type) const; private: /* Internal Data */ /* Coordinator */ - DeviceCoordinator coordinator_; + vtr::Point coordinate_; /* Routing channel data */ std::vector chan_node_; - std::vector< std::vector > chan_node_direction_; + std::vector> chan_node_direction_; /* Logic Block Inputs data */ - std::vector< std::vector > ipin_node_; - std::vector< std::vector > ipin_node_grid_side_; + std::vector> ipin_node_; /* Logic Block Outputs data */ - std::vector< std::vector > opin_node_; - std::vector< std::vector > opin_node_grid_side_; - - /* Configuration bits */ - ConfPorts sb_conf_port_; - ConfPorts cbx_conf_port_; - ConfPorts cby_conf_port_; + std::vector> opin_node_; }; - } /* End namespace openfpga*/ #endif diff --git a/openfpga/src/utils/openfpga_rr_graph_utils.cpp b/openfpga/src/utils/openfpga_rr_graph_utils.cpp new file mode 100644 index 000000000..cac7284d5 --- /dev/null +++ b/openfpga/src/utils/openfpga_rr_graph_utils.cpp @@ -0,0 +1,66 @@ +/******************************************************************** + * This file includes most utilized functions for the rr_graph + * data structure in the OpenFPGA context + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" + +#include "openfpga_rr_graph_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/************************************************************************ + * Get the coordinator of a starting point of a routing track + * For routing tracks in INC_DIRECTION + * (xlow, ylow) should be the starting point + * + * For routing tracks in DEC_DIRECTION + * (xhigh, yhigh) should be the starting point + ***********************************************************************/ +vtr::Point get_track_rr_node_start_coordinate(const RRGraph& rr_graph, + const RRNodeId& track_rr_node) { + /* Make sure we have CHANX or CHANY */ + VTR_ASSERT( (CHANX == rr_graph.node_type(track_rr_node)) + || (CHANY == rr_graph.node_type(track_rr_node)) ); + + vtr::Point start_coordinator; + + if (INC_DIRECTION == rr_graph.node_direction(track_rr_node)) { + start_coordinator.set(rr_graph.node_xlow(track_rr_node), rr_graph.node_ylow(track_rr_node)); + } else { + VTR_ASSERT(DEC_DIRECTION == rr_graph.node_direction(track_rr_node)); + start_coordinator.set(rr_graph.node_xhigh(track_rr_node), rr_graph.node_yhigh(track_rr_node)); + } + + return start_coordinator; +} + +/************************************************************************ + * Get the coordinator of a end point of a routing track + * For routing tracks in INC_DIRECTION + * (xhigh, yhigh) should be the starting point + * + * For routing tracks in DEC_DIRECTION + * (xlow, ylow) should be the starting point + ***********************************************************************/ +vtr::Point get_track_rr_node_end_coordinate(const RRGraph& rr_graph, + const RRNodeId& track_rr_node) { + /* Make sure we have CHANX or CHANY */ + VTR_ASSERT( (CHANX == rr_graph.node_type(track_rr_node)) + || (CHANY == rr_graph.node_type(track_rr_node)) ); + + vtr::Point end_coordinator; + + if (INC_DIRECTION == rr_graph.node_direction(track_rr_node)) { + end_coordinator.set(rr_graph.node_xhigh(track_rr_node), rr_graph.node_yhigh(track_rr_node)); + } else { + VTR_ASSERT(DEC_DIRECTION == rr_graph.node_direction(track_rr_node)); + end_coordinator.set(rr_graph.node_xlow(track_rr_node), rr_graph.node_ylow(track_rr_node)); + } + + return end_coordinator; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/utils/openfpga_rr_graph_utils.h b/openfpga/src/utils/openfpga_rr_graph_utils.h new file mode 100644 index 000000000..44306664b --- /dev/null +++ b/openfpga/src/utils/openfpga_rr_graph_utils.h @@ -0,0 +1,28 @@ +#ifndef OPENFPGA_RR_GRAPH_UTILS_H +#define OPENFPGA_RR_GRAPH_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_geometry.h" + +/* Headers from vpr library */ +#include "rr_graph_obj.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +vtr::Point get_track_rr_node_start_coordinate(const RRGraph& rr_graph, + const RRNodeId& track_rr_node); + +vtr::Point get_track_rr_node_end_coordinate(const RRGraph& rr_graph, + const RRNodeId& track_rr_node); + +} /* end namespace openfpga */ + +#endif From 85f3826939b058a16c780e2ad42e18f95f37381b Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 9 Feb 2020 14:58:23 -0700 Subject: [PATCH 111/645] put device rr_gsb online. Ready to plug-in --- openfpga/src/annotation/device_rr_gsb.cpp | 452 ++++++++++++++++++++++ openfpga/src/annotation/device_rr_gsb.h | 106 ++--- openfpga/src/annotation/rr_chan.cpp | 10 +- openfpga/src/annotation/rr_gsb.cpp | 15 +- 4 files changed, 515 insertions(+), 68 deletions(-) create mode 100644 openfpga/src/annotation/device_rr_gsb.cpp diff --git a/openfpga/src/annotation/device_rr_gsb.cpp b/openfpga/src/annotation/device_rr_gsb.cpp new file mode 100644 index 000000000..9152f209c --- /dev/null +++ b/openfpga/src/annotation/device_rr_gsb.cpp @@ -0,0 +1,452 @@ +/************************************************************************ + * Member functions for class DeviceRRGSB + ***********************************************************************/ +#include "vtr_log.h" +#include "vtr_assert.h" +#include "device_rr_gsb.h" + +/* namespace openfpga begins */ +namespace openfpga { + +/************************************************************************ + * Constructors + ***********************************************************************/ + +/************************************************************************ + * Public accessors + ***********************************************************************/ +/* get the max coordinate of the switch block array */ +vtr::Point DeviceRRGSB::get_gsb_range() const { + size_t max_y = 0; + /* Get the largest size of sub-arrays */ + for (size_t x = 0; x < rr_gsb_.size(); ++x) { + max_y = std::max(max_y, rr_gsb_[x].size()); + } + + vtr::Point coordinate(rr_gsb_.size(), max_y); + return coordinate; +} + +/* Get a rr switch block in the array with a coordinate */ +const RRGSB DeviceRRGSB::get_gsb(const vtr::Point& coordinate) const { + VTR_ASSERT(validate_coordinate(coordinate)); + return rr_gsb_[coordinate.x()][coordinate.y()]; +} + +/* Get a rr switch block in the array with a coordinate */ +const RRGSB DeviceRRGSB::get_gsb(const size_t& x, const size_t& y) const { + vtr::Point coordinate(x, y); + return get_gsb(coordinate); +} + +/* get the number of unique mirrors of switch blocks */ +size_t DeviceRRGSB::get_num_cb_unique_module(const t_rr_type& cb_type) const { + VTR_ASSERT (validate_cb_type(cb_type)); + switch(cb_type) { + case CHANX: + return cbx_unique_module_.size(); + case CHANY: + return cby_unique_module_.size(); + default: + VTR_LOG_ERROR("Invalid type of connection block!\n"); + exit(1); + } +} + +/* get the number of unique mirrors of switch blocks */ +size_t DeviceRRGSB::get_num_sb_unique_module() const { + return sb_unique_module_.size(); +} + +/* get the number of unique mirrors of switch blocks */ +size_t DeviceRRGSB::get_num_gsb_unique_module() const { + return gsb_unique_module_.size(); +} + +/* Get a rr switch block which a unique mirror */ +const RRGSB DeviceRRGSB::get_sb_unique_module(const size_t& index) const { + VTR_ASSERT (validate_sb_unique_module_index(index)); + + return rr_gsb_[sb_unique_module_[index].x()][sb_unique_module_[index].y()]; +} + +/* Get a rr switch block which a unique mirror */ +const RRGSB& DeviceRRGSB::get_cb_unique_module(const t_rr_type& cb_type, const size_t& index) const { + VTR_ASSERT (validate_cb_unique_module_index(cb_type, index)); + VTR_ASSERT (validate_cb_type(cb_type)); + switch(cb_type) { + case CHANX: + return rr_gsb_[cbx_unique_module_[index].x()][cbx_unique_module_[index].y()]; + case CHANY: + return rr_gsb_[cby_unique_module_[index].x()][cby_unique_module_[index].y()]; + default: + VTR_LOG_ERROR("Invalid type of connection block!\n"); + exit(1); + } +} + +/* Give a coordinate of a rr switch block, and return its unique mirror */ +const RRGSB& DeviceRRGSB::get_cb_unique_module(const t_rr_type& cb_type, const vtr::Point& coordinate) const { + VTR_ASSERT(validate_cb_type(cb_type)); + VTR_ASSERT(validate_coordinate(coordinate)); + size_t cb_unique_module_id; + + switch(cb_type) { + case CHANX: + cb_unique_module_id = cbx_unique_module_id_[coordinate.x()][coordinate.y()]; + break; + case CHANY: + cb_unique_module_id = cby_unique_module_id_[coordinate.x()][coordinate.y()]; + break; + default: + VTR_LOG_ERROR("Invalid type of connection block!\n"); + exit(1); + } + + return get_cb_unique_module(cb_type, cb_unique_module_id); +} + +/* Give a coordinate of a rr switch block, and return its unique mirror */ +const RRGSB DeviceRRGSB::get_sb_unique_module(const vtr::Point& coordinate) const { + VTR_ASSERT(validate_coordinate(coordinate)); + size_t sb_unique_module_id = sb_unique_module_id_[coordinate.x()][coordinate.y()]; + return get_sb_unique_module(sb_unique_module_id); +} + +/************************************************************************ + * Public mutators + ***********************************************************************/ + +/* Pre-allocate the rr_switch_block array that the device requires */ +void DeviceRRGSB::reserve(const vtr::Point& coordinate) { + rr_gsb_.resize(coordinate.x()); + + gsb_unique_module_id_.resize(coordinate.x()); + + sb_unique_module_id_.resize(coordinate.x()); + + cbx_unique_module_id_.resize(coordinate.x()); + cby_unique_module_id_.resize(coordinate.x()); + + for (size_t x = 0; x < coordinate.x(); ++x) { + rr_gsb_[x].resize(coordinate.y()); + + gsb_unique_module_id_[x].resize(coordinate.y()); + + sb_unique_module_id_[x].resize(coordinate.y()); + + cbx_unique_module_id_[x].resize(coordinate.y()); + cby_unique_module_id_[x].resize(coordinate.y()); + } +} + +/* Resize rr_switch_block array is needed*/ +void DeviceRRGSB::resize_upon_need(const vtr::Point& coordinate) { + if (coordinate.x() + 1 > rr_gsb_.size()) { + rr_gsb_.resize(coordinate.x() + 1); + + sb_unique_module_id_.resize(coordinate.x() + 1); + + cbx_unique_module_id_.resize(coordinate.x() + 1); + cby_unique_module_id_.resize(coordinate.x() + 1); + } + + if (coordinate.y() + 1 > rr_gsb_[coordinate.x()].size()) { + rr_gsb_[coordinate.x()].resize(coordinate.y() + 1); + sb_unique_module_id_[coordinate.x()].resize(coordinate.y() + 1); + + cbx_unique_module_id_[coordinate.x()].resize(coordinate.y() + 1); + cby_unique_module_id_[coordinate.x()].resize(coordinate.y() + 1); + } +} + +/* Add a switch block to the array, which will automatically identify and update the lists of unique mirrors and rotatable mirrors */ +void DeviceRRGSB::add_rr_gsb(const vtr::Point& coordinate, + const RRGSB& rr_gsb) { + /* Resize upon needs*/ + resize_upon_need(coordinate); + + /* Add the switch block into array */ + rr_gsb_[coordinate.x()][coordinate.y()] = rr_gsb; +} + +/* Add a switch block to the array, which will automatically identify and update the lists of unique mirrors and rotatable mirrors */ +void DeviceRRGSB::build_cb_unique_module(const RRGraph& rr_graph, const t_rr_type& cb_type) { + /* Make sure a clean start */ + clear_cb_unique_module(cb_type); + + for (size_t ix = 0; ix < rr_gsb_.size(); ++ix) { + for (size_t iy = 0; iy < rr_gsb_[ix].size(); ++iy) { + bool is_unique_module = true; + vtr::Point gsb_coordinate(ix, iy); + + /* Bypass non-exist CB */ + if ( false == rr_gsb_[ix][iy].is_cb_exist(cb_type) ) { + continue; + } + + /* Traverse the unique_mirror list and check it is an mirror of another */ + for (size_t id = 0; id < get_num_cb_unique_module(cb_type); ++id) { + const RRGSB& unique_module = get_cb_unique_module(cb_type, id); + if (true == rr_gsb_[ix][iy].is_cb_mirror(rr_graph, unique_module, cb_type)) { + /* This is a mirror, raise the flag and we finish */ + is_unique_module = false; + /* Record the id of unique mirror */ + set_cb_unique_module_id(cb_type, gsb_coordinate, id); + break; + } + } + /* Add to list if this is a unique mirror*/ + if (true == is_unique_module) { + add_cb_unique_module(cb_type, gsb_coordinate); + /* Record the id of unique mirror */ + set_cb_unique_module_id(cb_type, gsb_coordinate, get_num_cb_unique_module(cb_type) - 1); + } + } + } +} + +/* Add a switch block to the array, which will automatically identify and update the lists of unique mirrors and rotatable mirrors */ +void DeviceRRGSB::build_sb_unique_module(const RRGraph& rr_graph) { + /* Make sure a clean start */ + clear_sb_unique_module(); + + /* Build the unique module */ + for (size_t ix = 0; ix < rr_gsb_.size(); ++ix) { + for (size_t iy = 0; iy < rr_gsb_[ix].size(); ++iy) { + bool is_unique_module = true; + vtr::Point sb_coordinate(ix, iy); + + /* Traverse the unique_mirror list and check it is an mirror of another */ + for (size_t id = 0; id < get_num_sb_unique_module(); ++id) { + /* Check if the two modules have the same submodules, + * if so, these two modules are the same, indicating the sb is not unique. + * else the sb is unique + */ + const RRGSB& unique_module = get_sb_unique_module(id); + if (true == rr_gsb_[ix][iy].is_sb_mirror(rr_graph, unique_module)) { + /* This is a mirror, raise the flag and we finish */ + is_unique_module = false; + /* Record the id of unique mirror */ + sb_unique_module_id_[ix][iy] = id; + break; + } + } + /* Add to list if this is a unique mirror*/ + if (true == is_unique_module) { + sb_unique_module_.push_back(sb_coordinate); + /* Record the id of unique mirror */ + sb_unique_module_id_[ix][iy] = sb_unique_module_.size() - 1; + } + } + } +} + +/* Add a switch block to the array, which will automatically identify and update the lists of unique mirrors and rotatable mirrors */ + +/* Find repeatable GSB block in the array */ +void DeviceRRGSB::build_gsb_unique_module() { + /* Make sure a clean start */ + clear_gsb_unique_module(); + + for (size_t ix = 0; ix < rr_gsb_.size(); ++ix) { + for (size_t iy = 0; iy < rr_gsb_[ix].size(); ++iy) { + bool is_unique_module = true; + vtr::Point gsb_coordinate(ix, iy); + + /* Traverse the unique_mirror list and check it is an mirror of another */ + for (size_t id = 0; id < get_num_gsb_unique_module(); ++id) { + /* We have alreay built sb and cb unique module list + * We just need to check if the unique module id of SBs, CBX and CBY are the same or not + */ + const vtr::Point& gsb_unique_module_coordinate = gsb_unique_module_[id]; + if ((sb_unique_module_id_[ix][iy] == sb_unique_module_id_[gsb_unique_module_coordinate.x()][gsb_unique_module_coordinate.y()]) + && (cbx_unique_module_id_[ix][iy] == cbx_unique_module_id_[gsb_unique_module_coordinate.x()][gsb_unique_module_coordinate.y()]) + && (cby_unique_module_id_[ix][iy] == cby_unique_module_id_[gsb_unique_module_coordinate.x()][gsb_unique_module_coordinate.y()])) { + /* This is a mirror, raise the flag and we finish */ + is_unique_module = false; + /* Record the id of unique mirror */ + gsb_unique_module_id_[ix][iy] = id; + break; + } + } + /* Add to list if this is a unique mirror*/ + if (true == is_unique_module) { + add_gsb_unique_module(gsb_coordinate); + /* Record the id of unique mirror */ + gsb_unique_module_id_[ix][iy] = get_num_gsb_unique_module() - 1; + } + } + } +} + +void DeviceRRGSB::build_unique_module(const RRGraph& rr_graph) { + build_sb_unique_module(rr_graph); + + build_cb_unique_module(rr_graph, CHANX); + build_cb_unique_module(rr_graph, CHANY); + + build_gsb_unique_module(); +} + +void DeviceRRGSB::add_gsb_unique_module(const vtr::Point& coordinate) { + gsb_unique_module_.push_back(coordinate); +} + +void DeviceRRGSB::add_cb_unique_module(const t_rr_type& cb_type, const vtr::Point& coordinate) { + VTR_ASSERT (validate_cb_type(cb_type)); + switch(cb_type) { + case CHANX: + cbx_unique_module_.push_back(coordinate); + return; + case CHANY: + cby_unique_module_.push_back(coordinate); + return; + default: + VTR_LOG_ERROR("Invalid type of connection block!\n"); + exit(1); + } +} + +void DeviceRRGSB::set_cb_unique_module_id(const t_rr_type& cb_type, const vtr::Point& coordinate, size_t id) { + VTR_ASSERT(validate_cb_type(cb_type)); + size_t x = coordinate.x(); + size_t y = coordinate.y(); + switch(cb_type) { + case CHANX: + cbx_unique_module_id_[x][y] = id; + return; + case CHANY: + cby_unique_module_id_[x][y] = id; + return; + default: + VTR_LOG_ERROR("Invalid type of connection block!\n"); + exit(1); + } +} + +/************************************************************************ + * Public clean-up functions: + ***********************************************************************/ +/* clean the content */ +void DeviceRRGSB::clear() { + clear_gsb(); + + clear_gsb_unique_module(); + clear_gsb_unique_module_id(); + + /* clean unique module lists */ + clear_cb_unique_module(CHANX); + clear_cb_unique_module_id(CHANX); + + clear_cb_unique_module(CHANY); + clear_cb_unique_module_id(CHANY); + + clear_sb_unique_module(); + clear_sb_unique_module_id(); +} + +void DeviceRRGSB::clear_gsb() { + /* clean gsb array */ + for (size_t x = 0; x < rr_gsb_.size(); ++x) { + rr_gsb_[x].clear(); + } + rr_gsb_.clear(); +} + +void DeviceRRGSB::clear_gsb_unique_module_id() { + /* clean rr_switch_block array */ + for (size_t x = 0; x < rr_gsb_.size(); ++x) { + gsb_unique_module_id_[x].clear(); + } +} + +void DeviceRRGSB::clear_sb_unique_module_id() { + /* clean rr_switch_block array */ + for (size_t x = 0; x < rr_gsb_.size(); ++x) { + sb_unique_module_id_[x].clear(); + } +} + +void DeviceRRGSB::clear_cb_unique_module_id(const t_rr_type& cb_type) { + VTR_ASSERT (validate_cb_type(cb_type)); + switch(cb_type) { + case CHANX: + for (size_t x = 0; x < rr_gsb_.size(); ++x) { + cbx_unique_module_id_[x].clear(); + } + return; + case CHANY: + for (size_t x = 0; x < rr_gsb_.size(); ++x) { + cby_unique_module_id_[x].clear(); + } + return; + default: + VTR_LOG_ERROR("Invalid type of connection block!\n"); + exit(1); + } +} + +/* clean the content related to unique_mirrors */ +void DeviceRRGSB::clear_gsb_unique_module() { + /* clean unique mirror */ + gsb_unique_module_.clear(); +} + +/* clean the content related to unique_mirrors */ +void DeviceRRGSB::clear_sb_unique_module() { + /* clean unique mirror */ + sb_unique_module_.clear(); +} + +void DeviceRRGSB::clear_cb_unique_module(const t_rr_type& cb_type) { + VTR_ASSERT (validate_cb_type(cb_type)); + switch(cb_type) { + case CHANX: + cbx_unique_module_.clear(); + return; + case CHANY: + cby_unique_module_.clear(); + return; + default: + VTR_LOG_ERROR("Invalid type of connection block!\n"); + exit(1); + } +} + +/************************************************************************ + * Internal validators + ***********************************************************************/ +/* Validate if the (x,y) is the range of this device */ +bool DeviceRRGSB::validate_coordinate(const vtr::Point& coordinate) const { + if (coordinate.x() >= rr_gsb_.capacity()) { + return false; + } + return (coordinate.y() < rr_gsb_[coordinate.x()].capacity()); +} + +/* Validate if the index in the range of unique_mirror vector*/ +bool DeviceRRGSB::validate_sb_unique_module_index(const size_t& index) const { + return (index < sb_unique_module_.size()); +} + +bool DeviceRRGSB::validate_cb_unique_module_index(const t_rr_type& cb_type, const size_t& index) const { + VTR_ASSERT(validate_cb_type(cb_type)); + switch(cb_type) { + case CHANX: + return (index < cbx_unique_module_.size()); + case CHANY: + return (index < cby_unique_module_.size()); + default: + VTR_LOG_ERROR("Invalid type of connection block!\n"); + exit(1); + } + + return false; +} + +bool DeviceRRGSB::validate_cb_type(const t_rr_type& cb_type) const { + return ((CHANX == cb_type) || (CHANY == cb_type)); +} + +} /* End namespace openfpga*/ diff --git a/openfpga/src/annotation/device_rr_gsb.h b/openfpga/src/annotation/device_rr_gsb.h index e801417bc..624492124 100644 --- a/openfpga/src/annotation/device_rr_gsb.h +++ b/openfpga/src/annotation/device_rr_gsb.h @@ -4,8 +4,17 @@ /******************************************************************** * Include header files required by the data structure definition *******************************************************************/ +/* Header files from vtrutil library */ +#include "vtr_geometry.h" + +/* Header files from vpr library */ +#include "rr_graph_obj.h" + #include "rr_gsb.h" +/* namespace openfpga begins */ +namespace openfpga { + /******************************************************************** * Object Device Routing Resource Switch Block * This includes: @@ -18,87 +27,58 @@ class DeviceRRGSB { public: /* Contructors */ public: /* Accessors */ - DeviceCoordinator get_gsb_range() const; /* get the max coordinator of the switch block array */ - const RRGSB get_gsb(const DeviceCoordinator& coordinator) const; /* Get a rr switch block in the array with a coordinator */ - const RRGSB get_gsb(size_t x, size_t y) const; /* Get a rr switch block in the array with a coordinator */ + vtr::Point get_gsb_range() const; /* get the max coordinate of the switch block array */ + const RRGSB get_gsb(const vtr::Point& coordinate) const; /* Get a rr switch block in the array with a coordinate */ + const RRGSB get_gsb(const size_t& x, const size_t& y) const; /* Get a rr switch block in the array with a coordinate */ size_t get_num_gsb_unique_module() const; /* get the number of unique mirrors of GSB */ - size_t get_num_sb_unique_submodule(enum e_side side, size_t seg_index) const; /* get the number of unique mirrors of switch blocks */ size_t get_num_sb_unique_module() const; /* get the number of unique mirrors of switch blocks */ - size_t get_num_cb_unique_module(t_rr_type cb_type) const; /* get the number of unique mirrors of CBs */ - size_t get_sb_unique_submodule_id(DeviceCoordinator& coordinator, enum e_side side, size_t seg_id) const; - const RRGSB get_sb_unique_submodule(size_t index, enum e_side side, size_t seg_id) const; /* Get a rr switch block which a unique mirror */ - const RRGSB get_sb_unique_submodule(DeviceCoordinator& coordinator, enum e_side side, size_t seg_id) const; /* Get a rr switch block which a unique mirror */ - const RRGSB get_sb_unique_module(size_t index) const; /* Get a rr switch block which a unique mirror */ - const RRGSB get_sb_unique_module(const DeviceCoordinator& coordinator) const; /* Get a rr switch block which a unique mirror */ - const RRGSB& get_cb_unique_module(t_rr_type cb_type, size_t index) const; /* Get a rr switch block which a unique mirror */ - const RRGSB& get_cb_unique_module(t_rr_type cb_type, const DeviceCoordinator& coordinator) const; - size_t get_max_num_sides() const; /* Get the maximum number of sides across the switch blocks */ - size_t get_num_segments() const; /* Get the size of segment_ids */ - size_t get_segment_id(size_t index) const; /* Get a segment id */ - bool is_two_sb_share_same_submodules(DeviceCoordinator& src, DeviceCoordinator& des) const; + const RRGSB get_sb_unique_module(const size_t& index) const; /* Get a rr switch block which a unique mirror */ + const RRGSB get_sb_unique_module(const vtr::Point& coordinate) const; /* Get a rr switch block which a unique mirror */ + const RRGSB& get_cb_unique_module(const t_rr_type& cb_type, const size_t& index) const; /* Get a rr switch block which a unique mirror */ + const RRGSB& get_cb_unique_module(const t_rr_type& cb_type, const vtr::Point& coordinate) const; + size_t get_num_cb_unique_module(const t_rr_type& cb_type) const; /* get the number of unique mirrors of CBs */ public: /* Mutators */ - void set_sb_num_reserved_conf_bits(DeviceCoordinator& coordinator, size_t num_reserved_conf_bits); /* TODO: TOBE DEPRECATED!!! conf_bits should be initialized when creating a switch block!!! */ - void set_sb_conf_bits_lsb(DeviceCoordinator& coordinator, size_t conf_bits_lsb); /* TODO: TOBE DEPRECATED!!! conf_bits should be initialized when creating a switch block!!! */ - void set_sb_conf_bits_msb(DeviceCoordinator& coordinator, size_t conf_bits_msb); /* TODO: TOBE DEPRECATED!!! conf_bits should be initialized when creating a switch block!!! */ - void set_cb_num_reserved_conf_bits(DeviceCoordinator& coordinator, t_rr_type cb_type, size_t num_reserved_conf_bits); /* TODO: TOBE DEPRECATED!!! conf_bits should be initialized when creating a switch block!!! */ - void set_cb_conf_bits_lsb(DeviceCoordinator& coordinator, t_rr_type cb_type, size_t conf_bits_lsb); /* TODO: TOBE DEPRECATED!!! conf_bits should be initialized when creating a switch block!!! */ - void set_cb_conf_bits_msb(DeviceCoordinator& coordinator, t_rr_type cb_type, size_t conf_bits_msb); /* TODO: TOBE DEPRECATED!!! conf_bits should be initialized when creating a switch block!!! */ - void reserve(DeviceCoordinator& coordinator); /* Pre-allocate the rr_switch_block array that the device requires */ - void reserve_sb_unique_submodule_id(DeviceCoordinator& coordinator); /* Pre-allocate the rr_sb_unique_module_id matrix that the device requires */ - void resize_upon_need(const DeviceCoordinator& coordinator); /* Resize the rr_switch_block array if needed */ - void add_rr_gsb(const DeviceCoordinator& coordinator, const RRGSB& rr_gsb); /* Add a switch block to the array, which will automatically identify and update the lists of unique mirrors and rotatable mirrors */ - void build_unique_module(); /* Add a switch block to the array, which will automatically identify and update the lists of unique mirrors and rotatable mirrors */ + void reserve(const vtr::Point& coordinate); /* Pre-allocate the rr_switch_block array that the device requires */ + void reserve_sb_unique_submodule_id(const vtr::Point& coordinate); /* Pre-allocate the rr_sb_unique_module_id matrix that the device requires */ + void resize_upon_need(const vtr::Point& coordinate); /* Resize the rr_switch_block array if needed */ + void add_rr_gsb(const vtr::Point& coordinate, const RRGSB& rr_gsb); /* Add a switch block to the array, which will automatically identify and update the lists of unique mirrors and rotatable mirrors */ + void build_unique_module(const RRGraph& rr_graph); /* Add a switch block to the array, which will automatically identify and update the lists of unique mirrors and rotatable mirrors */ void clear(); /* clean the content */ private: /* Internal cleaners */ void clear_gsb(); /* clean the content */ - void clear_cb_unique_module(t_rr_type cb_type); /* clean the content */ - void clear_cb_unique_module_id(t_rr_type cb_type); /* clean the content */ + void clear_cb_unique_module(const t_rr_type& cb_type); /* clean the content */ + void clear_cb_unique_module_id(const t_rr_type& cb_type); /* clean the content */ void clear_sb_unique_module(); /* clean the content */ void clear_sb_unique_module_id(); /* clean the content */ - void clear_sb_unique_submodule(); /* clean the content */ - void clear_sb_unique_submodule_id(); /* clean the content */ void clear_gsb_unique_module(); /* clean the content */ void clear_gsb_unique_module_id(); /* clean the content */ - void clear_segment_ids(); private: /* Validators */ - bool validate_coordinator(const DeviceCoordinator& coordinator) const; /* Validate if the (x,y) is the range of this device */ - bool validate_coordinator_edge(DeviceCoordinator& coordinator) const; /* Validate if the (x,y) is the range of this device but takes into consideration the fact that edges are 1 off */ - bool validate_side(enum e_side side) const; /* validate if side is in the range of unique_side_module_ */ - bool validate_sb_unique_module_index(size_t index) const; /* Validate if the index in the range of unique_mirror vector*/ - bool validate_cb_unique_module_index(t_rr_type cb_type, size_t index) const; /* Validate if the index in the range of unique_mirror vector*/ - bool validate_sb_unique_submodule_index(size_t index, enum e_side side, size_t seg_index) const; /* Validate if the index in the range of unique_module vector */ - bool validate_segment_index(size_t index) const; - bool validate_cb_type(t_rr_type cb_type) const; + bool validate_coordinate(const vtr::Point& coordinate) const; /* Validate if the (x,y) is the range of this device */ + bool validate_side(const e_side& side) const; /* validate if side is in the range of unique_side_module_ */ + bool validate_sb_unique_module_index(const size_t& index) const; /* Validate if the index in the range of unique_mirror vector*/ + bool validate_cb_unique_module_index(const t_rr_type& cb_type, const size_t& index) const; /* Validate if the index in the range of unique_mirror vector*/ + bool validate_cb_type(const t_rr_type& cb_type) const; private: /* Internal builders */ - void build_segment_ids(); /* build a map of segment_ids */ - void add_gsb_unique_module(const DeviceCoordinator& coordinator); - void add_sb_unique_side_submodule(DeviceCoordinator& coordinator, const RRGSB& rr_sb, enum e_side side); - void add_sb_unique_side_segment_submodule(DeviceCoordinator& coordinator, const RRGSB& rr_sb, enum e_side side, size_t seg_id); - void add_cb_unique_module(t_rr_type cb_type, const DeviceCoordinator& coordinator); - void set_cb_unique_module_id(t_rr_type, const DeviceCoordinator& coordinator, size_t id); - void build_sb_unique_submodule(); /* Add a switch block to the array, which will automatically identify and update the lists of unique side module */ - void build_sb_unique_module(); /* Add a switch block to the array, which will automatically identify and update the lists of unique mirrors and rotatable mirrors */ - void build_cb_unique_module(t_rr_type cb_type); /* Add a switch block to the array, which will automatically identify and update the lists of unique side module */ + void add_gsb_unique_module(const vtr::Point& coordinate); + void add_cb_unique_module(const t_rr_type& cb_type, const vtr::Point& coordinate); + void set_cb_unique_module_id(const t_rr_type& cb_type, const vtr::Point& coordinate, size_t id); + void build_sb_unique_module(const RRGraph& rr_graph); /* Add a switch block to the array, which will automatically identify and update the lists of unique mirrors and rotatable mirrors */ + void build_cb_unique_module(const RRGraph& rr_graph, const t_rr_type& cb_type); /* Add a switch block to the array, which will automatically identify and update the lists of unique side module */ void build_gsb_unique_module(); /* Add a switch block to the array, which will automatically identify and update the lists of unique mirrors and rotatable mirrors */ private: /* Internal Data */ - std::vector< std::vector > rr_gsb_; + std::vector> rr_gsb_; - std::vector< std::vector > gsb_unique_module_id_; /* A map from rr_gsb to its unique mirror */ - std::vector gsb_unique_module_; + std::vector> gsb_unique_module_id_; /* A map from rr_gsb to its unique mirror */ + std::vector> gsb_unique_module_; - std::vector< std::vector > sb_unique_module_id_; /* A map from rr_gsb to its unique mirror */ - std::vector sb_unique_module_; + std::vector> sb_unique_module_id_; /* A map from rr_gsb to its unique mirror */ + std::vector> sb_unique_module_; - std::vector< std::vector< std::vector< std::vector > > > sb_unique_submodule_id_; /* A map from rr_switch_block to its unique_side_module [0..x][0..y][0..num_sides][num_seg-1]*/ - std::vector< std::vector > > sb_unique_submodule_; /* For each side of switch block, we identify a list of unique modules based on its connection. This is a matrix [0..num_sides-1][0..num_seg-1][0..num_module], num_sides will the max number of sides of all the rr_switch_blocks */ + std::vector> cbx_unique_module_id_; /* A map from rr_gsb to its unique mirror */ + std::vector> cbx_unique_module_; /* For each side of connection block, we identify a list of unique modules based on its connection. This is a matrix [0..num_module] */ - std::vector< std::vector > cbx_unique_module_id_; /* A map from rr_gsb to its unique mirror */ - std::vector cbx_unique_module_; /* For each side of connection block, we identify a list of unique modules based on its connection. This is a matrix [0..num_module] */ - - std::vector< std::vector > cby_unique_module_id_; /* A map from rr_gsb to its unique mirror */ - std::vector cby_unique_module_; /* For each side of connection block, we identify a list of unique modules based on its connection. This is a matrix [0..num_module] */ - - std::vector segment_ids_; + std::vector> cby_unique_module_id_; /* A map from rr_gsb to its unique mirror */ + std::vector> cby_unique_module_; /* For each side of connection block, we identify a list of unique modules based on its connection. This is a matrix [0..num_module] */ }; } /* End namespace openfpga*/ diff --git a/openfpga/src/annotation/rr_chan.cpp b/openfpga/src/annotation/rr_chan.cpp index 84540758e..b85d1b917 100644 --- a/openfpga/src/annotation/rr_chan.cpp +++ b/openfpga/src/annotation/rr_chan.cpp @@ -24,7 +24,9 @@ RRChan::RRChan() { node_segments_.resize(0); } -/* Accessors */ +/************************************************************************ + * Accessors + ***********************************************************************/ t_rr_type RRChan::get_type() const { return type_; } @@ -142,7 +144,9 @@ std::vector RRChan::get_node_ids_by_segment_ids(const RRSegmentId& seg return node_list; } -/* Mutators */ +/************************************************************************ + * Mutators + ***********************************************************************/ void RRChan::set(const RRChan& rr_chan) { /* Ensure a clean start */ this->clear(); @@ -186,7 +190,7 @@ void RRChan::clear() { } /************************************************************************ - * Internal functions + * Internal validators ***********************************************************************/ /* for type, only valid type is CHANX and CHANY */ bool RRChan::valid_type(const t_rr_type& type) const { diff --git a/openfpga/src/annotation/rr_gsb.cpp b/openfpga/src/annotation/rr_gsb.cpp index 54b9fa8f2..6b8cd5236 100644 --- a/openfpga/src/annotation/rr_gsb.cpp +++ b/openfpga/src/annotation/rr_gsb.cpp @@ -661,8 +661,10 @@ vtr::Point RRGSB::get_grid_coordinate() const { return ret; } -/* Public mutators */ +/************************************************************************ + * Public Mutators + ***********************************************************************/ /* get a copy from a source */ void RRGSB::set(const RRGSB& src) { /* Copy coordinate */ @@ -740,6 +742,9 @@ void RRGSB::add_opin_node(const RRNodeId& node, const e_side& node_side) { opin_node_[size_t(node_side)].push_back(node); } +/************************************************************************ + * Public Mutators: clean-up functions + ***********************************************************************/ /* Reset the RRGSB to pristine state */ void RRGSB::clear() { /* Clean all the vectors */ @@ -786,7 +791,9 @@ void RRGSB::clear_one_side(const e_side& node_side) { clear_opin_nodes(node_side); } -/* Internal functions for validation */ +/************************************************************************ + * Internal Accessors: identify mirrors + ***********************************************************************/ /* check if two rr_nodes have a similar set of drive_rr_nodes * for each drive_rr_node: @@ -944,6 +951,10 @@ size_t RRGSB::get_track_id_first_short_connection(const RRGraph& rr_graph, const return size_t(-1); } + +/************************************************************************ + * Internal validators + ***********************************************************************/ /* Validate if the number of sides are consistent among internal data arrays ! */ bool RRGSB::validate_num_sides() const { size_t num_sides = chan_node_direction_.size(); From e2e115e6f36b075b7db6845fb4004ae815cf6b08 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 11 Feb 2020 11:33:30 -0700 Subject: [PATCH 112/645] improve rr_node fast look-up in rr_graph object so that we have easily find all the channel nodes --- vpr/src/device/rr_graph_obj.cpp | 47 +++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/vpr/src/device/rr_graph_obj.cpp b/vpr/src/device/rr_graph_obj.cpp index da528cf26..9124ffdc7 100644 --- a/vpr/src/device/rr_graph_obj.cpp +++ b/vpr/src/device/rr_graph_obj.cpp @@ -1219,32 +1219,45 @@ void RRGraph::build_fast_node_lookup() const { continue; } RRNodeId node = RRNodeId(id); - /* Special for SOURCE and SINK, we should annotate in the look-up + /* Special for CHANX and CHANY, we should annotate in the look-up * for all the (x,y) upto (xhigh, yhigh) */ - size_t x = node_xlow(node); - size_t y = node_ylow(node); + size_t x_start = std::min(node_xlow(node), node_xhigh(node)); + size_t y_start = std::min(node_ylow(node), node_yhigh(node)); + std::vector node_x(std::abs(node_xlow(node) - node_xhigh(node)) + 1); + std::vector node_y(std::abs(node_ylow(node) - node_yhigh(node)) + 1); + + std::iota(node_x.begin(), node_x.end(), x_start); + std::iota(node_y.begin(), node_y.end(), y_start); + + VTR_ASSERT(size_t(std::max(node_xlow(node), node_xhigh(node))) == node_x.back()); + VTR_ASSERT(size_t(std::max(node_ylow(node), node_yhigh(node))) == node_y.back()); size_t itype = node_type(node); size_t ptc = node_ptc_num(node); - if (ptc >= node_lookup_[x][y][itype].size()) { - node_lookup_[x][y][itype].resize(ptc + 1); - } - size_t iside = -1; - if (node_type(node) == OPIN || node_type(node) == IPIN) { - iside = node_side(node); - } else { - iside = NUM_SIDES; - } + for (const size_t& x : node_x) { + for (const size_t& y : node_y) { + if (ptc >= node_lookup_[x][y][itype].size()) { + node_lookup_[x][y][itype].resize(ptc + 1); + } - if (iside >= node_lookup_[x][y][itype][ptc].size()) { - node_lookup_[x][y][itype][ptc].resize(iside + 1); - } + size_t iside = -1; + if (node_type(node) == OPIN || node_type(node) == IPIN) { + iside = node_side(node); + } else { + iside = NUM_SIDES; + } - //Save node in lookup - node_lookup_[x][y][itype][ptc][iside] = node; + if (iside >= node_lookup_[x][y][itype][ptc].size()) { + node_lookup_[x][y][itype][ptc].resize(iside + 1); + } + + //Save node in lookup + node_lookup_[x][y][itype][ptc][iside] = node; + } + } } } From 1372f748f122f84a54ca6b32f6b1ac3dfa44980e Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 11 Feb 2020 16:37:14 -0700 Subject: [PATCH 113/645] put GSB builder online --- openfpga/src/annotation/annotate_rr_gsb.cpp | 395 ++++++++++++++++++++ openfpga/src/annotation/annotate_rr_gsb.h | 24 ++ openfpga/src/base/openfpga_context.h | 11 + openfpga/src/base/openfpga_link_arch.cpp | 9 + vpr/src/device/rr_graph_obj_util.cpp | 48 ++- vpr/src/device/rr_graph_obj_util.h | 8 + 6 files changed, 494 insertions(+), 1 deletion(-) create mode 100644 openfpga/src/annotation/annotate_rr_gsb.cpp create mode 100644 openfpga/src/annotation/annotate_rr_gsb.h diff --git a/openfpga/src/annotation/annotate_rr_gsb.cpp b/openfpga/src/annotation/annotate_rr_gsb.cpp new file mode 100644 index 000000000..8050110e5 --- /dev/null +++ b/openfpga/src/annotation/annotate_rr_gsb.cpp @@ -0,0 +1,395 @@ +/******************************************************************** + * This file includes functions that are used to annotate device-level + * information, in particular the routing resource graph + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_time.h" +#include "vtr_assert.h" +#include "vtr_log.h" + +/* Headers from openfpgautil library */ +#include "openfpga_side_manager.h" + +/* Headers from vpr library */ +#include "rr_graph_obj_util.h" + +#include "annotate_rr_gsb.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/* Build a RRChan Object with the given channel type and coorindators */ +static +RRChan build_one_rr_chan(const DeviceContext& vpr_device_ctx, + const t_rr_type& chan_type, + vtr::Point& chan_coord) { + std::vector chan_rr_nodes; + + /* Create a rr_chan object and check if it is unique in the graph */ + RRChan rr_chan; + /* Fill the information */ + rr_chan.set_type(chan_type); + + /* Collect rr_nodes for this channel */ + chan_rr_nodes = find_rr_graph_chan_nodes(vpr_device_ctx.rr_graph, + chan_coord.x(), chan_coord.y(), + chan_type); + /* Fill the rr_chan */ + for (const RRNodeId& chan_rr_node : chan_rr_nodes) { + rr_chan.add_node(vpr_device_ctx.rr_graph, chan_rr_node, + vpr_device_ctx.rr_graph.node_segment(chan_rr_node)); + } + + return rr_chan; +} + +/* Build a General Switch Block (GSB) + * which includes: + * [I] A Switch Box subckt consists of following ports: + * 1. Channel Y [x][y] inputs + * 2. Channel X [x+1][y] inputs + * 3. Channel Y [x][y-1] outputs + * 4. Channel X [x][y] outputs + * 5. Grid[x][y+1] Right side outputs pins + * 6. Grid[x+1][y+1] Left side output pins + * 7. Grid[x+1][y+1] Bottom side output pins + * 8. Grid[x+1][y] Top side output pins + * 9. Grid[x+1][y] Left side output pins + * 10. Grid[x][y] Right side output pins + * 11. Grid[x][y] Top side output pins + * 12. Grid[x][y+1] Bottom side output pins + * + * -------------- -------------- + * | | CBY | | + * | Grid | ChanY | Grid | + * | [x][y+1] | [x][y+1] | [x+1][y+1] | + * | | | | + * -------------- -------------- + * ---------- + * ChanX & CBX | Switch | ChanX + * [x][y] | Box | [x+1][y] + * | [x][y] | + * ---------- + * -------------- -------------- + * | | | | + * | Grid | ChanY | Grid | + * | [x][y] | [x][y] | [x+1][y] | + * | | | | + * -------------- -------------- + * For channels chanY with INC_DIRECTION on the top side, they should be marked as outputs + * For channels chanY with DEC_DIRECTION on the top side, they should be marked as inputs + * For channels chanY with INC_DIRECTION on the bottom side, they should be marked as inputs + * For channels chanY with DEC_DIRECTION on the bottom side, they should be marked as outputs + * For channels chanX with INC_DIRECTION on the left side, they should be marked as inputs + * For channels chanX with DEC_DIRECTION on the left side, they should be marked as outputs + * For channels chanX with INC_DIRECTION on the right side, they should be marked as outputs + * For channels chanX with DEC_DIRECTION on the right side, they should be marked as inputs + * + * [II] A X-direction Connection Block [x][y] + * The connection block shares the same routing channel[x][y] with the Switch Block + * We just need to fill the ipin nodes at TOP and BOTTOM sides + * as well as properly fill the ipin_grid_side information + * [III] A Y-direction Connection Block [x][y+1] + * The connection block shares the same routing channel[x][y+1] with the Switch Block + * We just need to fill the ipin nodes at LEFT and RIGHT sides + * as well as properly fill the ipin_grid_side information + */ +static +RRGSB build_rr_gsb(const DeviceContext& vpr_device_ctx, + const vtr::Point& gsb_range, + const vtr::Point& gsb_coord) { + /* Create an object to return */ + RRGSB rr_gsb; + + VTR_ASSERT(gsb_coord.x() <= gsb_range.x()); + VTR_ASSERT(gsb_coord.y() <= gsb_range.y()); + + /* Coordinator initialization */ + rr_gsb.set_coordinate(gsb_coord.x(), gsb_coord.y()); + + /* Basic information*/ + rr_gsb.init_num_sides(4); /* Fixed number of sides */ + + /* Find all rr_nodes of channels */ + /* Side: TOP => 0, RIGHT => 1, BOTTOM => 2, LEFT => 3 */ + for (size_t side = 0; side < rr_gsb.get_num_sides(); ++side) { + /* Local variables inside this for loop */ + SideManager side_manager(side); + vtr::Point coordinate = rr_gsb.get_side_block_coordinate(side_manager.get_side()); + RRChan rr_chan; + std::vector> temp_opin_rr_nodes(2); + enum e_side opin_grid_side[2] = {NUM_SIDES, NUM_SIDES}; + enum PORTS chan_dir_to_port_dir_mapping[2] = {OUT_PORT, IN_PORT}; /* 0: INC_DIRECTION => ?; 1: DEC_DIRECTION => ? */ + + switch (side) { + case TOP: /* TOP = 0 */ + /* For the border, we should take special care */ + if (gsb_coord.y() == gsb_range.y()) { + rr_gsb.clear_one_side(side_manager.get_side()); + break; + } + /* Routing channels*/ + /* Side: TOP => 0, RIGHT => 1, BOTTOM => 2, LEFT => 3 */ + /* Create a rr_chan object and check if it is unique in the graph */ + rr_chan = build_one_rr_chan(vpr_device_ctx, CHANY, coordinate); + chan_dir_to_port_dir_mapping[0] = OUT_PORT; /* INC_DIRECTION => OUT_PORT */ + chan_dir_to_port_dir_mapping[1] = IN_PORT; /* DEC_DIRECTION => IN_PORT */ + + /* Build the Switch block: opin and opin_grid_side */ + /* Assign grid side of OPIN */ + /* Grid[x][y+1] RIGHT side outputs pins */ + opin_grid_side[0] = RIGHT; + /* Grid[x+1][y+1] left side outputs pins */ + opin_grid_side[1] = LEFT; + /* Include Grid[x][y+1] RIGHT side outputs pins */ + temp_opin_rr_nodes[0] = find_rr_graph_grid_nodes(vpr_device_ctx.rr_graph, vpr_device_ctx.grid, + gsb_coord.x(), gsb_coord.y() + 1, OPIN, opin_grid_side[0]); + /* Include Grid[x+1][y+1] Left side output pins */ + temp_opin_rr_nodes[1] = find_rr_graph_grid_nodes(vpr_device_ctx.rr_graph, vpr_device_ctx.grid, + gsb_coord.x() + 1, gsb_coord.y() + 1, OPIN, opin_grid_side[1]); + + break; + case RIGHT: /* RIGHT = 1 */ + /* For the border, we should take special care */ + if (gsb_coord.x() == gsb_range.x()) { + rr_gsb.clear_one_side(side_manager.get_side()); + break; + } + /* Routing channels*/ + /* Side: TOP => 0, RIGHT => 1, BOTTOM => 2, LEFT => 3 */ + /* Collect rr_nodes for Tracks for top: chany[x][y+1] */ + /* Create a rr_chan object and check if it is unique in the graph */ + rr_chan = build_one_rr_chan(vpr_device_ctx, CHANX, coordinate); + chan_dir_to_port_dir_mapping[0] = OUT_PORT; /* INC_DIRECTION => OUT_PORT */ + chan_dir_to_port_dir_mapping[1] = IN_PORT; /* DEC_DIRECTION => IN_PORT */ + + /* Build the Switch block: opin and opin_grid_side */ + /* Assign grid side of OPIN */ + /* Grid[x+1][y+1] BOTTOM side outputs pins */ + opin_grid_side[0] = BOTTOM; + /* Grid[x+1][y] TOP side outputs pins */ + opin_grid_side[1] = TOP; + + /* include Grid[x+1][y+1] Bottom side output pins */ + temp_opin_rr_nodes[0] = find_rr_graph_grid_nodes(vpr_device_ctx.rr_graph, vpr_device_ctx.grid, + gsb_coord.x() + 1, gsb_coord.y() + 1, OPIN, opin_grid_side[0]); + /* include Grid[x+1][y] Top side output pins */ + temp_opin_rr_nodes[1] = find_rr_graph_grid_nodes(vpr_device_ctx.rr_graph, vpr_device_ctx.grid, + gsb_coord.x() + 1, gsb_coord.y(), OPIN, opin_grid_side[1]); + break; + case BOTTOM: /* BOTTOM = 2*/ + /* For the border, we should take special care */ + if (gsb_coord.y() == 0) { + rr_gsb.clear_one_side(side_manager.get_side()); + break; + } + /* Routing channels*/ + /* Side: TOP => 0, RIGHT => 1, BOTTOM => 2, LEFT => 3 */ + /* Collect rr_nodes for Tracks for bottom: chany[x][y] */ + /* Create a rr_chan object and check if it is unique in the graph */ + rr_chan = build_one_rr_chan(vpr_device_ctx, CHANY, coordinate); + chan_dir_to_port_dir_mapping[0] = IN_PORT; /* INC_DIRECTION => IN_PORT */ + chan_dir_to_port_dir_mapping[1] = OUT_PORT; /* DEC_DIRECTION => OUT_PORT */ + + /* Build the Switch block: opin and opin_grid_side */ + /* Assign grid side of OPIN */ + /* Grid[x+1][y] LEFT side outputs pins */ + opin_grid_side[0] = LEFT; + /* Grid[x][y] RIGHT side outputs pins */ + opin_grid_side[1] = RIGHT; + /* include Grid[x+1][y] Left side output pins */ + temp_opin_rr_nodes[0] = find_rr_graph_grid_nodes(vpr_device_ctx.rr_graph, vpr_device_ctx.grid, + gsb_coord.x() + 1, gsb_coord.y(), OPIN, opin_grid_side[0]); + /* include Grid[x][y] Right side output pins */ + temp_opin_rr_nodes[1] = find_rr_graph_grid_nodes(vpr_device_ctx.rr_graph, vpr_device_ctx.grid, + gsb_coord.x(), gsb_coord.y(), OPIN, opin_grid_side[1]); + break; + case LEFT: /* LEFT = 3 */ + /* For the border, we should take special care */ + if (gsb_coord.x() == 0) { + rr_gsb.clear_one_side(side_manager.get_side()); + break; + } + /* Routing channels*/ + /* Side: TOP => 0, RIGHT => 1, BOTTOM => 2, LEFT => 3 */ + /* Collect rr_nodes for Tracks for left: chanx[x][y] */ + /* Create a rr_chan object and check if it is unique in the graph */ + rr_chan = build_one_rr_chan(vpr_device_ctx, CHANX, coordinate); + chan_dir_to_port_dir_mapping[0] = IN_PORT; /* INC_DIRECTION => IN_PORT */ + chan_dir_to_port_dir_mapping[1] = OUT_PORT; /* DEC_DIRECTION => OUT_PORT */ + + /* Build the Switch block: opin and opin_grid_side */ + /* Grid[x][y+1] BOTTOM side outputs pins */ + opin_grid_side[0] = BOTTOM; + /* Grid[x][y] TOP side outputs pins */ + opin_grid_side[1] = TOP; + /* include Grid[x][y+1] Bottom side outputs pins */ + temp_opin_rr_nodes[0] = find_rr_graph_grid_nodes(vpr_device_ctx.rr_graph, vpr_device_ctx.grid, + gsb_coord.x(), gsb_coord.y() + 1, OPIN, opin_grid_side[0]); + /* include Grid[x][y] Top side output pins */ + temp_opin_rr_nodes[1] = find_rr_graph_grid_nodes(vpr_device_ctx.rr_graph, vpr_device_ctx.grid, + gsb_coord.x(), gsb_coord.y(), OPIN, opin_grid_side[1]); + break; + default: + VTR_LOG_ERROR("Invalid side index!\n"); + exit(1); + } + + /* Organize a vector of port direction */ + if (0 < rr_chan.get_chan_width()) { + std::vector rr_chan_dir; + rr_chan_dir.resize(rr_chan.get_chan_width()); + for (size_t itrack = 0; itrack < rr_chan.get_chan_width(); ++itrack) { + /* Identify the directionality, record it in rr_node_direction */ + if (INC_DIRECTION == vpr_device_ctx.rr_graph.node_direction(rr_chan.get_node(itrack))) { + rr_chan_dir[itrack] = chan_dir_to_port_dir_mapping[0]; + } else { + VTR_ASSERT(DEC_DIRECTION == vpr_device_ctx.rr_graph.node_direction(rr_chan.get_node(itrack))); + rr_chan_dir[itrack] = chan_dir_to_port_dir_mapping[1]; + } + } + /* Fill chan_rr_nodes */ + rr_gsb.add_chan_node(side_manager.get_side(), rr_chan, rr_chan_dir); + } + + /* Fill opin_rr_nodes */ + /* Copy from temp_opin_rr_node to opin_rr_node */ + for (const RRNodeId& inode : temp_opin_rr_nodes[0]) { + /* Grid[x+1][y+1] Bottom side outputs pins */ + rr_gsb.add_opin_node(inode, side_manager.get_side()); + } + for (const RRNodeId& inode : temp_opin_rr_nodes[1]) { + /* Grid[x+1][y] TOP side outputs pins */ + rr_gsb.add_opin_node(inode, side_manager.get_side()); + } + + /* Clean ipin_rr_nodes */ + /* We do not have any IPIN for a Switch Block */ + rr_gsb.clear_ipin_nodes(side_manager.get_side()); + + /* Clear the temp data */ + temp_opin_rr_nodes[0].clear(); + temp_opin_rr_nodes[1].clear(); + opin_grid_side[0] = NUM_SIDES; + opin_grid_side[1] = NUM_SIDES; + } + + /* Side: TOP => 0, RIGHT => 1, BOTTOM => 2, LEFT => 3 */ + for (size_t side = 0; side < rr_gsb.get_num_sides(); ++side) { + /* Local variables inside this for loop */ + SideManager side_manager(side); + size_t ix; + size_t iy; + enum e_side chan_side; + std::vector temp_ipin_rr_nodes; + enum e_side ipin_rr_node_grid_side; + + switch (side) { + case TOP: /* TOP = 0 */ + /* For the bording, we should take special care */ + /* Check if left side chan width is 0 or not */ + chan_side = LEFT; + /* Build the connection block: ipin and ipin_grid_side */ + /* BOTTOM side INPUT Pins of Grid[x][y+1] */ + ix = rr_gsb.get_sb_x(); + iy = rr_gsb.get_sb_y() + 1; + ipin_rr_node_grid_side = BOTTOM; + break; + case RIGHT: /* RIGHT = 1 */ + /* For the bording, we should take special care */ + /* Check if TOP side chan width is 0 or not */ + chan_side = TOP; + /* Build the connection block: ipin and ipin_grid_side */ + /* LEFT side INPUT Pins of Grid[x+1][y+1] */ + ix = rr_gsb.get_sb_x() + 1; + iy = rr_gsb.get_sb_y() + 1; + ipin_rr_node_grid_side = LEFT; + break; + case BOTTOM: /* BOTTOM = 2*/ + /* For the bording, we should take special care */ + /* Check if left side chan width is 0 or not */ + chan_side = LEFT; + /* Build the connection block: ipin and ipin_grid_side */ + /* TOP side INPUT Pins of Grid[x][y] */ + ix = rr_gsb.get_sb_x(); + iy = rr_gsb.get_sb_y(); + ipin_rr_node_grid_side = TOP; + break; + case LEFT: /* LEFT = 3 */ + /* For the bording, we should take special care */ + /* Check if left side chan width is 0 or not */ + chan_side = TOP; + /* Build the connection block: ipin and ipin_grid_side */ + /* RIGHT side INPUT Pins of Grid[x][y+1] */ + ix = rr_gsb.get_sb_x(); + iy = rr_gsb.get_sb_y() + 1; + ipin_rr_node_grid_side = RIGHT; + break; + default: + VTR_LOG_ERROR("Invalid side index!\n"); + exit(1); + } + + /* If there is no channel at this side, we skip ipin_node annotation */ + if (0 == rr_gsb.get_chan_width(chan_side)) { + continue; + } + /* Collect IPIN rr_nodes*/ + temp_ipin_rr_nodes = find_rr_graph_grid_nodes(vpr_device_ctx.rr_graph, vpr_device_ctx.grid, + ix, iy, IPIN, ipin_rr_node_grid_side); + /* Fill the ipin nodes of RRGSB */ + for (const RRNodeId& inode : temp_ipin_rr_nodes) { + rr_gsb.add_ipin_node(inode, side_manager.get_side()); + } + /* Clear the temp data */ + temp_ipin_rr_nodes.clear(); + } + + return rr_gsb; +} + +/******************************************************************** + * Build the annotation for the routing resource graph + * by collecting the nodes to the General Switch Block context + *******************************************************************/ +void annotate_device_rr_gsb(const DeviceContext& vpr_device_ctx, + DeviceRRGSB& device_rr_gsb, + const bool& verbose_output) { + + vtr::ScopedStartFinishTimer timer("Build General Switch Block(GSB) annotation on top of routing resource graph"); + + /* Note that the GSB array is smaller than the grids by 1 column and 1 row!!! */ + vtr::Point gsb_range(vpr_device_ctx.grid.width() - 1, vpr_device_ctx.grid.height() - 1); + device_rr_gsb.reserve(gsb_range); + + VTR_LOGV(verbose_output, + "Start annotation GSB up to [%lu][%lu]\n", + gsb_range.x(), gsb_range.y()); + + size_t gsb_cnt = 0; + /* For each switch block, determine the size of array */ + for (size_t ix = 0; ix < gsb_range.x(); ++ix) { + for (size_t iy = 0; iy < gsb_range.y(); ++iy) { + /* Here we give the builder the fringe coordinates so that it can handle the GSBs at the borderside correctly */ + const RRGSB& rr_gsb = build_rr_gsb(vpr_device_ctx, + vtr::Point(vpr_device_ctx.grid.width() - 2, vpr_device_ctx.grid.height() - 2), + vtr::Point(ix, iy)); + /* TODO: sort drive_rr_nodes should be done when building the tileable rr_graph? */ + //sort_rr_gsb_drive_rr_nodes(rr_gsb); + + /* Add to device_rr_gsb */ + vtr::Point gsb_coordinate = rr_gsb.get_sb_coordinate(); + device_rr_gsb.add_rr_gsb(gsb_coordinate, rr_gsb); + gsb_cnt++; /* Update counter */ + /* Print info */ + VTR_LOG("[%lu%] Backannotated GSB[%lu][%lu]\r", + 100 * gsb_cnt / (gsb_range.x() * gsb_range.y()), + ix, iy); + } + } + /* Report number of unique mirrors */ + VTR_LOG("Backannotated %d General Switch Blocks (GSBs).\n", + gsb_range.x() * gsb_range.y()); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/annotation/annotate_rr_gsb.h b/openfpga/src/annotation/annotate_rr_gsb.h new file mode 100644 index 000000000..abf666c4a --- /dev/null +++ b/openfpga/src/annotation/annotate_rr_gsb.h @@ -0,0 +1,24 @@ +#ifndef ANNOTATE_RR_GSB_H +#define ANNOTATE_RR_GSB_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "vpr_context.h" +#include "openfpga_context.h" +#include "device_rr_gsb.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void annotate_device_rr_gsb(const DeviceContext& vpr_device_ctx, + DeviceRRGSB& device_rr_gsb, + const bool& verbose_output); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/base/openfpga_context.h b/openfpga/src/base/openfpga_context.h index 74e01d0de..743847114 100644 --- a/openfpga/src/base/openfpga_context.h +++ b/openfpga/src/base/openfpga_context.h @@ -7,6 +7,7 @@ #include "vpr_pb_type_annotation.h" #include "vpr_clustering_annotation.h" #include "vpr_routing_annotation.h" +#include "device_rr_gsb.h" /******************************************************************** * This file includes the declaration of the date structure @@ -42,22 +43,32 @@ class OpenfpgaContext : public Context { const openfpga::VprNetlistAnnotation& vpr_netlist_annotation() const { return vpr_netlist_annotation_; } const openfpga::VprClusteringAnnotation& vpr_clustering_annotation() const { return vpr_clustering_annotation_; } const openfpga::VprRoutingAnnotation& vpr_routing_annotation() const { return vpr_routing_annotation_; } + const openfpga::DeviceRRGSB& device_rr_gsb() const { return device_rr_gsb_; } public: /* Public mutators */ openfpga::Arch& mutable_arch() { return arch_; } openfpga::VprPbTypeAnnotation& mutable_vpr_pb_type_annotation() { return vpr_pb_type_annotation_; } openfpga::VprNetlistAnnotation& mutable_vpr_netlist_annotation() { return vpr_netlist_annotation_; } openfpga::VprClusteringAnnotation& mutable_vpr_clustering_annotation() { return vpr_clustering_annotation_; } openfpga::VprRoutingAnnotation& mutable_vpr_routing_annotation() { return vpr_routing_annotation_; } + openfpga::DeviceRRGSB& mutable_device_rr_gsb() { return device_rr_gsb_; } private: /* Internal data */ /* Data structure to store information from read_openfpga_arch library */ openfpga::Arch arch_; + /* Annotation to pb_type of VPR */ openfpga::VprPbTypeAnnotation vpr_pb_type_annotation_; + /* Naming fix to netlist */ openfpga::VprNetlistAnnotation vpr_netlist_annotation_; + /* TODO: Pin net fix to cluster results */ openfpga::VprClusteringAnnotation vpr_clustering_annotation_; + + /* Routing results annotation */ openfpga::VprRoutingAnnotation vpr_routing_annotation_; + + /* Device-level annotation */ + openfpga::DeviceRRGSB device_rr_gsb_; }; #endif diff --git a/openfpga/src/base/openfpga_link_arch.cpp b/openfpga/src/base/openfpga_link_arch.cpp index dee7662a6..2519be57a 100644 --- a/openfpga/src/base/openfpga_link_arch.cpp +++ b/openfpga/src/base/openfpga_link_arch.cpp @@ -12,6 +12,7 @@ #include "annotate_pb_types.h" #include "annotate_pb_graph.h" #include "annotate_routing.h" +#include "annotate_rr_gsb.h" #include "openfpga_link_arch.h" /* Include global variables of VPR */ @@ -60,6 +61,14 @@ void link_arch(OpenfpgaContext& openfpga_context, annotate_rr_node_nets(g_vpr_ctx.device(), g_vpr_ctx.clustering(), g_vpr_ctx.routing(), openfpga_context.mutable_vpr_routing_annotation(), cmd_context.option_enable(cmd, opt_verbose)); + + /* Build the routing graph annotation + * - RRGSB + * - DeviceRRGSB + */ + annotate_device_rr_gsb(g_vpr_ctx.device(), + openfpga_context.mutable_device_rr_gsb(), + cmd_context.option_enable(cmd, opt_verbose)); } } /* end namespace openfpga */ diff --git a/vpr/src/device/rr_graph_obj_util.cpp b/vpr/src/device/rr_graph_obj_util.cpp index 373c48fa1..a155eec8f 100644 --- a/vpr/src/device/rr_graph_obj_util.cpp +++ b/vpr/src/device/rr_graph_obj_util.cpp @@ -32,7 +32,7 @@ std::vector find_rr_graph_switches(const RRGraph& rr_graph, /********************************************************************* * Like the RRGraph.find_node() but returns all matching nodes, * rather than just the first. This is particularly useful for getting all instances - * of a specific IPIN/OPIN at a specific gird tile (x,y) location. + * of a specific IPIN/OPIN at a specific grid tile (x,y) location. **********************************************************************/ std::vector find_rr_graph_nodes(const RRGraph& rr_graph, const int& x, @@ -63,6 +63,9 @@ std::vector find_rr_graph_nodes(const RRGraph& rr_graph, return indices; } +/********************************************************************* + * Find a list of rr nodes in a routing channel at (x,y) + **********************************************************************/ std::vector find_rr_graph_chan_nodes(const RRGraph& rr_graph, const int& x, const int& y, @@ -81,3 +84,46 @@ std::vector find_rr_graph_chan_nodes(const RRGraph& rr_graph, return indices; } + +/********************************************************************* + * Find a list of rr_nodes that locate at a side of a grid + **********************************************************************/ +std::vector find_rr_graph_grid_nodes(const RRGraph& rr_graph, + const DeviceGrid& device_grid, + const int& x, + const int& y, + const t_rr_type& rr_type, + const e_side& side) { + std::vector indices; + + VTR_ASSERT(rr_type == IPIN || rr_type == OPIN); + + /* Ensure that (x, y) is a valid location in grids */ + VTR_ASSERT(size_t(x) <= device_grid.width() && size_t(y) <= device_grid.height()); + + /* Ensure we have a valid side */ + VTR_ASSERT(side != NUM_SIDES); + + /* Find all the pins on the side of the grid */ + int width_offset = device_grid[x][y].width_offset; + int height_offset = device_grid[x][y].height_offset; + for (int pin = 0; pin < device_grid[x][y].type->num_pins; ++pin) { + /* Skip those pins have been ignored during rr_graph build-up */ + if (true == device_grid[x][y].type->is_ignored_pin[pin]) { + continue; + } + if (false == device_grid[x][y].type->pinloc[width_offset][height_offset][side][pin]) { + /* Not the pin on this side, we skip */ + continue; + } + /* Try to find the rr node */ + RRNodeId rr_node_index = rr_graph.find_node(x, y, rr_type, pin, side); + if (rr_node_index != RRNodeId::INVALID()) { + indices.push_back(rr_node_index); + } + } + + + return indices; +} + diff --git a/vpr/src/device/rr_graph_obj_util.h b/vpr/src/device/rr_graph_obj_util.h index 71d97dcf9..01f753ddc 100644 --- a/vpr/src/device/rr_graph_obj_util.h +++ b/vpr/src/device/rr_graph_obj_util.h @@ -6,6 +6,7 @@ */ #include #include "rr_graph_obj.h" +#include "device_grid.h" /* Get node-to-node switches in a RRGraph */ std::vector find_rr_graph_switches(const RRGraph& rr_graph, @@ -23,5 +24,12 @@ std::vector find_rr_graph_chan_nodes(const RRGraph& rr_graph, const int& y, const t_rr_type& rr_type); +std::vector find_rr_graph_grid_nodes(const RRGraph& rr_graph, + const DeviceGrid& device_grid, + const int& x, + const int& y, + const t_rr_type& rr_type, + const e_side& side); + #endif From 175bef014a9f8c8850264fef4c23d1e64f3519b3 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 11 Feb 2020 17:40:37 -0700 Subject: [PATCH 114/645] add compact_routing hierarchy command --- openfpga/src/annotation/annotate_rr_gsb.cpp | 2 +- openfpga/src/annotation/device_rr_gsb.cpp | 23 +++++++ openfpga/src/annotation/device_rr_gsb.h | 1 + openfpga/src/annotation/rr_gsb.cpp | 15 ++++ openfpga/src/annotation/rr_gsb.h | 5 +- .../src/base/compact_routing_hierarchy.cpp | 60 ++++++++++++++++ openfpga/src/base/compact_routing_hierarchy.h | 23 +++++++ openfpga/src/base/openfpga_setup_command.cpp | 20 +++++- openfpga/src/utils/device_rr_gsb_utils.cpp | 68 +++++++++++++++++++ openfpga/src/utils/device_rr_gsb_utils.h | 27 ++++++++ openfpga/test_script/s298_k6_frac.openfpga | 3 + 11 files changed, 244 insertions(+), 3 deletions(-) create mode 100644 openfpga/src/base/compact_routing_hierarchy.cpp create mode 100644 openfpga/src/base/compact_routing_hierarchy.h create mode 100644 openfpga/src/utils/device_rr_gsb_utils.cpp create mode 100644 openfpga/src/utils/device_rr_gsb_utils.h diff --git a/openfpga/src/annotation/annotate_rr_gsb.cpp b/openfpga/src/annotation/annotate_rr_gsb.cpp index 8050110e5..dca399abd 100644 --- a/openfpga/src/annotation/annotate_rr_gsb.cpp +++ b/openfpga/src/annotation/annotate_rr_gsb.cpp @@ -374,7 +374,7 @@ void annotate_device_rr_gsb(const DeviceContext& vpr_device_ctx, const RRGSB& rr_gsb = build_rr_gsb(vpr_device_ctx, vtr::Point(vpr_device_ctx.grid.width() - 2, vpr_device_ctx.grid.height() - 2), vtr::Point(ix, iy)); - /* TODO: sort drive_rr_nodes should be done when building the tileable rr_graph? */ + /* TODO: sort drive_rr_nodes should be done when building the tileable rr_graph */ //sort_rr_gsb_drive_rr_nodes(rr_gsb); /* Add to device_rr_gsb */ diff --git a/openfpga/src/annotation/device_rr_gsb.cpp b/openfpga/src/annotation/device_rr_gsb.cpp index 9152f209c..6fa808823 100644 --- a/openfpga/src/annotation/device_rr_gsb.cpp +++ b/openfpga/src/annotation/device_rr_gsb.cpp @@ -53,6 +53,29 @@ size_t DeviceRRGSB::get_num_cb_unique_module(const t_rr_type& cb_type) const { } } +/* Identify if a GSB actually exists at a location */ +bool DeviceRRGSB::is_gsb_exist(const vtr::Point coord) const { + /* Out of range, does not exist */ + if (false == validate_coordinate(coord)) { + return false; + } + + /* If the GSB is empty, it does not exist */ + if (true == get_gsb(coord).is_cb_exist(CHANX)) { + return true; + } + + if (true == get_gsb(coord).is_cb_exist(CHANY)) { + return true; + } + + if (true == get_gsb(coord).is_sb_exist()) { + return true; + } + + return false; +} + /* get the number of unique mirrors of switch blocks */ size_t DeviceRRGSB::get_num_sb_unique_module() const { return sb_unique_module_.size(); diff --git a/openfpga/src/annotation/device_rr_gsb.h b/openfpga/src/annotation/device_rr_gsb.h index 624492124..eae0afa1d 100644 --- a/openfpga/src/annotation/device_rr_gsb.h +++ b/openfpga/src/annotation/device_rr_gsb.h @@ -37,6 +37,7 @@ class DeviceRRGSB { const RRGSB& get_cb_unique_module(const t_rr_type& cb_type, const size_t& index) const; /* Get a rr switch block which a unique mirror */ const RRGSB& get_cb_unique_module(const t_rr_type& cb_type, const vtr::Point& coordinate) const; size_t get_num_cb_unique_module(const t_rr_type& cb_type) const; /* get the number of unique mirrors of CBs */ + bool is_gsb_exist(const vtr::Point coord) const; public: /* Mutators */ void reserve(const vtr::Point& coordinate); /* Pre-allocate the rr_switch_block array that the device requires */ void reserve_sb_unique_submodule_id(const vtr::Point& coordinate); /* Pre-allocate the rr_sb_unique_module_id matrix that the device requires */ diff --git a/openfpga/src/annotation/rr_gsb.cpp b/openfpga/src/annotation/rr_gsb.cpp index 6b8cd5236..cd57d8e6b 100644 --- a/openfpga/src/annotation/rr_gsb.cpp +++ b/openfpga/src/annotation/rr_gsb.cpp @@ -348,6 +348,21 @@ bool RRGSB::is_cb_exist(const t_rr_type& cb_type) const { return (0 != get_cb_chan_width(cb_type)); } +/* check if the SB exist in this GSB */ +bool RRGSB::is_sb_exist() const { + /* if all the channel width is zero and number of OPINs are zero, there is no SB */ + for (size_t side = 0; side < get_num_sides(); ++side) { + SideManager side_manager(side); + if (0 != get_chan_width(side_manager.get_side())) { + return true; + } + if (0 != get_num_opin_nodes(side_manager.get_side())) { + return true; + } + } + + return false; +} /************************************************************************ * Check if the node indicates a passing wire across the Switch Block part of the GSB diff --git a/openfpga/src/annotation/rr_gsb.h b/openfpga/src/annotation/rr_gsb.h index 4f3af9552..4541c88dd 100644 --- a/openfpga/src/annotation/rr_gsb.h +++ b/openfpga/src/annotation/rr_gsb.h @@ -114,9 +114,12 @@ class RRGSB { /* check if the candidate SB is a mirror of the current one */ bool is_cb_mirror(const RRGraph& rr_graph, const RRGSB& cand, const t_rr_type& cb_type) const; - /* check if the candidate SB is a mirror of the current one */ + /* check if the connect block exists in the GSB */ bool is_cb_exist(const t_rr_type& cb_type) const; + /* check if the switch block exists in the GSB */ + bool is_sb_exist() const; + /* Check if the node imply a short connection inside the SB, which happens to long wires across a FPGA fabric */ bool is_sb_node_passing_wire(const RRGraph& rr_graph, const e_side& node_side, const size_t& track_id) const; diff --git a/openfpga/src/base/compact_routing_hierarchy.cpp b/openfpga/src/base/compact_routing_hierarchy.cpp new file mode 100644 index 000000000..e520b5e3f --- /dev/null +++ b/openfpga/src/base/compact_routing_hierarchy.cpp @@ -0,0 +1,60 @@ +/******************************************************************** + * This file includes functions to compress the hierachy of routing architecture + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_time.h" +#include "vtr_log.h" + +#include "device_rr_gsb.h" +#include "device_rr_gsb_utils.h" +#include "compact_routing_hierarchy.h" + +/* Include global variables of VPR */ +#include "globals.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Identify the unique GSBs from the Device RR GSB arrays + * This function should only be called after the GSB builder is done + *******************************************************************/ +void compact_routing_hierarchy(OpenfpgaContext& openfpga_context, + const Command& cmd, const CommandContext& cmd_context) { + vtr::ScopedStartFinishTimer timer("Identify unique General Switch Blocks (GSBs)"); + + CommandOptionId opt_verbose = cmd.option("verbose"); + + /* Build unique module lists */ + openfpga_context.mutable_device_rr_gsb().build_unique_module(g_vpr_ctx.device().rr_graph); + + bool verbose_output = cmd_context.option_enable(cmd, opt_verbose); + + /* Report the stats */ + VTR_LOGV(verbose_output, + "Detected %lu unique X-direction connection blocks from a total of %d (compression rate=%d%)\n", + openfpga_context.device_rr_gsb().get_num_cb_unique_module(CHANX), + find_device_rr_gsb_num_cb_modules(openfpga_context.device_rr_gsb(), CHANX), + 100 * (openfpga_context.device_rr_gsb().get_num_cb_unique_module(CHANX) / find_device_rr_gsb_num_cb_modules(openfpga_context.device_rr_gsb(), CHANX) - 1)); + + VTR_LOGV(verbose_output, + "Detected %lu unique Y-direction connection blocks from a total of %d (compression rate=%d%)\n", + openfpga_context.device_rr_gsb().get_num_cb_unique_module(CHANY), + find_device_rr_gsb_num_cb_modules(openfpga_context.device_rr_gsb(), CHANY), + 100 * (openfpga_context.device_rr_gsb().get_num_cb_unique_module(CHANY) / find_device_rr_gsb_num_cb_modules(openfpga_context.device_rr_gsb(), CHANY) - 1)); + + VTR_LOGV(verbose_output, + "Detected %lu unique switch blocks from a total of %d (compression rate=%d%)\n", + openfpga_context.device_rr_gsb().get_num_sb_unique_module(), + find_device_rr_gsb_num_sb_modules(openfpga_context.device_rr_gsb()), + 100 * (openfpga_context.device_rr_gsb().get_num_sb_unique_module() / find_device_rr_gsb_num_sb_modules(openfpga_context.device_rr_gsb()) - 1)); + + VTR_LOGV(verbose_output, + "Detected %lu unique general switch blocks from a total of %d (compression rate=%d%)\n", + openfpga_context.device_rr_gsb().get_num_gsb_unique_module(), + find_device_rr_gsb_num_gsb_modules(openfpga_context.device_rr_gsb()), + 100 * (openfpga_context.device_rr_gsb().get_num_gsb_unique_module() / find_device_rr_gsb_num_gsb_modules(openfpga_context.device_rr_gsb()) - 1)); + +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/base/compact_routing_hierarchy.h b/openfpga/src/base/compact_routing_hierarchy.h new file mode 100644 index 000000000..bd3eeefe2 --- /dev/null +++ b/openfpga/src/base/compact_routing_hierarchy.h @@ -0,0 +1,23 @@ +#ifndef COMPACT_ROUTING_HIERARCHY_H +#define COMPACT_ROUTING_HIERARCHY_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "command.h" +#include "command_context.h" +#include "openfpga_context.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void compact_routing_hierarchy(OpenfpgaContext& openfpga_context, + const Command& cmd, const CommandContext& cmd_context); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/base/openfpga_setup_command.cpp b/openfpga/src/base/openfpga_setup_command.cpp index 629300bf2..2d08ec38c 100644 --- a/openfpga/src/base/openfpga_setup_command.cpp +++ b/openfpga/src/base/openfpga_setup_command.cpp @@ -8,6 +8,8 @@ #include "openfpga_pb_pin_fixup.h" #include "openfpga_lut_truth_table_fixup.h" #include "check_netlist_naming_conflict.h" +#include "annotate_rr_gsb.h" +#include "compact_routing_hierarchy.h" #include "openfpga_setup_command.h" /* begin namespace openfpga */ @@ -113,11 +115,27 @@ void add_openfpga_setup_commands(openfpga::Shell& shell) { ShellCommandId shell_cmd_lut_truth_table_fixup_id = shell.add_command(shell_cmd_lut_truth_table_fixup, "Fix up the truth table of Look-Up Tables due to pin swapping during packing stage"); shell.set_command_class(shell_cmd_lut_truth_table_fixup_id, openfpga_setup_cmd_class); shell.set_command_execute_function(shell_cmd_lut_truth_table_fixup_id, lut_truth_table_fixup); - /* The 'pb_pin_fixup' command should NOT be executed before 'read_openfpga_arch' and 'vpr' */ + /* The 'lut_truth_table_fixup' command should NOT be executed before 'read_openfpga_arch' and 'vpr' */ std::vector cmd_dependency_lut_truth_table_fixup; cmd_dependency_lut_truth_table_fixup.push_back(shell_cmd_read_arch_id); cmd_dependency_lut_truth_table_fixup.push_back(shell_cmd_vpr_id); shell.set_command_dependency(shell_cmd_lut_truth_table_fixup_id, cmd_dependency_lut_truth_table_fixup); + + /******************************** + * Command 'compact_routing_hierarchy' + */ + Command shell_cmd_compact_routing_hierarchy("compact_routing_hierarchy"); + /* Add an option '--verbose' */ + shell_cmd_compact_routing_hierarchy.add_option("verbose", false, "Show verbose outputs"); + + /* Add command 'compact_routing_hierarchy' to the Shell */ + ShellCommandId shell_cmd_compact_routing_hierarchy_id = shell.add_command(shell_cmd_compact_routing_hierarchy, "Identify the unique GSBs in the routing architecture so that the routing hierarchy of fabric can be compressed"); + shell.set_command_class(shell_cmd_compact_routing_hierarchy_id, openfpga_setup_cmd_class); + shell.set_command_execute_function(shell_cmd_compact_routing_hierarchy_id, compact_routing_hierarchy); + /* The 'compact_routing_hierarchy' command should NOT be executed before 'link_openfpga_arch' */ + std::vector cmd_dependency_compact_routing_hierarchy; + cmd_dependency_lut_truth_table_fixup.push_back(shell_cmd_link_openfpga_arch_id); + shell.set_command_dependency(shell_cmd_compact_routing_hierarchy_id, cmd_dependency_compact_routing_hierarchy); } } /* end namespace openfpga */ diff --git a/openfpga/src/utils/device_rr_gsb_utils.cpp b/openfpga/src/utils/device_rr_gsb_utils.cpp new file mode 100644 index 000000000..dfda5618a --- /dev/null +++ b/openfpga/src/utils/device_rr_gsb_utils.cpp @@ -0,0 +1,68 @@ +/******************************************************************** + * This file includes most utilized functions for data structure + * DeviceRRGSB + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" + +#include "device_rr_gsb_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * This function aims to find out the number of connection block + * modules in the device rr_gsb array + *******************************************************************/ +size_t find_device_rr_gsb_num_cb_modules(const DeviceRRGSB& device_rr_gsb, + const t_rr_type& cb_type) { + size_t counter = 0; + for (size_t x = 0; x < device_rr_gsb.get_gsb_range().x(); ++x) { + for (size_t y = 0; y < device_rr_gsb.get_gsb_range().y(); ++y) { + const RRGSB& rr_gsb = device_rr_gsb.get_gsb(x, y); + if (true == rr_gsb.is_cb_exist(cb_type)) { + counter++; + } + } + } + + return counter; +} + +/******************************************************************** + * This function aims to find out the number of switch block + * modules in the device rr_gsb array + *******************************************************************/ +size_t find_device_rr_gsb_num_sb_modules(const DeviceRRGSB& device_rr_gsb) { + size_t counter = 0; + for (size_t x = 0; x < device_rr_gsb.get_gsb_range().x(); ++x) { + for (size_t y = 0; y < device_rr_gsb.get_gsb_range().y(); ++y) { + const RRGSB& rr_gsb = device_rr_gsb.get_gsb(x, y); + if (true == rr_gsb.is_sb_exist()) { + counter++; + } + } + } + + return counter; +} + +/******************************************************************** + * This function aims to find out the number of GSBs + *******************************************************************/ +size_t find_device_rr_gsb_num_gsb_modules(const DeviceRRGSB& device_rr_gsb) { + size_t counter = 0; + for (size_t x = 0; x < device_rr_gsb.get_gsb_range().x(); ++x) { + for (size_t y = 0; y < device_rr_gsb.get_gsb_range().y(); ++y) { + if (true == device_rr_gsb.is_gsb_exist(vtr::Point(x, y))) { + counter++; + } + } + } + + return counter; + +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/utils/device_rr_gsb_utils.h b/openfpga/src/utils/device_rr_gsb_utils.h new file mode 100644 index 000000000..7b49839a9 --- /dev/null +++ b/openfpga/src/utils/device_rr_gsb_utils.h @@ -0,0 +1,27 @@ +#ifndef DEVICE_RR_GSB_UTILS_H +#define DEVICE_RR_GSB_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "device_rr_gsb.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +size_t find_device_rr_gsb_num_cb_modules(const DeviceRRGSB& device_rr_gsb, + const t_rr_type& cb_type); + +size_t find_device_rr_gsb_num_sb_modules(const DeviceRRGSB& device_rr_gsb); + +size_t find_device_rr_gsb_num_gsb_modules(const DeviceRRGSB& device_rr_gsb); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/test_script/s298_k6_frac.openfpga b/openfpga/test_script/s298_k6_frac.openfpga index f4a5bc979..dd87c77df 100644 --- a/openfpga/test_script/s298_k6_frac.openfpga +++ b/openfpga/test_script/s298_k6_frac.openfpga @@ -16,5 +16,8 @@ pb_pin_fixup --verbose # Apply fix-up to Look-Up Table truth tables based on packing results lut_truth_table_fixup --verbose +# Compress the routing hierarchy +compact_routing_hierarchy --verbose + # Finish and exit OpenFPGA exit From 4367dba9b713b5734aa6641770dd0b8b9d52bd90 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 11 Feb 2020 21:02:58 -0700 Subject: [PATCH 115/645] move mux graph and decoder builders to vpr8 integration; ready to link the rr_switch to circuit models --- openfpga/src/mux_lib/decoder_library.cpp | 109 ++ openfpga/src/mux_lib/decoder_library.h | 95 ++ openfpga/src/mux_lib/decoder_library_fwd.h | 23 + openfpga/src/mux_lib/mux_graph.cpp | 1254 ++++++++++++++++++ openfpga/src/mux_lib/mux_graph.h | 213 +++ openfpga/src/mux_lib/mux_graph_fwd.h | 33 + openfpga/src/mux_lib/mux_library.cpp | 121 ++ openfpga/src/mux_lib/mux_library.h | 64 + openfpga/src/mux_lib/mux_library_fwd.h | 25 + openfpga/src/utils/decoder_library_utils.cpp | 60 + openfpga/src/utils/decoder_library_utils.h | 21 + openfpga/src/utils/mux_utils.cpp | 458 +++++++ openfpga/src/utils/mux_utils.h | 59 + 13 files changed, 2535 insertions(+) create mode 100644 openfpga/src/mux_lib/decoder_library.cpp create mode 100644 openfpga/src/mux_lib/decoder_library.h create mode 100644 openfpga/src/mux_lib/decoder_library_fwd.h create mode 100644 openfpga/src/mux_lib/mux_graph.cpp create mode 100644 openfpga/src/mux_lib/mux_graph.h create mode 100644 openfpga/src/mux_lib/mux_graph_fwd.h create mode 100644 openfpga/src/mux_lib/mux_library.cpp create mode 100644 openfpga/src/mux_lib/mux_library.h create mode 100644 openfpga/src/mux_lib/mux_library_fwd.h create mode 100644 openfpga/src/utils/decoder_library_utils.cpp create mode 100644 openfpga/src/utils/decoder_library_utils.h create mode 100644 openfpga/src/utils/mux_utils.cpp create mode 100644 openfpga/src/utils/mux_utils.h diff --git a/openfpga/src/mux_lib/decoder_library.cpp b/openfpga/src/mux_lib/decoder_library.cpp new file mode 100644 index 000000000..a7c4a8840 --- /dev/null +++ b/openfpga/src/mux_lib/decoder_library.cpp @@ -0,0 +1,109 @@ +/*************************************************************************************** + * This file includes memeber functions for data structure DecoderLibrary + **************************************************************************************/ +#include "vtr_assert.h" +#include "decoder_library.h" + +/* Begin namespace openfpga */ +namespace openfpga { + +/*************************************************************************************** + * Public Accessors: Aggregators + **************************************************************************************/ +DecoderLibrary::decoder_range DecoderLibrary::decoders() const { + return vtr::make_range(decoder_ids_.begin(), decoder_ids_.end()); +} + +/*************************************************************************************** + * Public Accessors: Data query + **************************************************************************************/ +/* Get the size of address input of a decoder */ +size_t DecoderLibrary::addr_size(const DecoderId& decoder) const { + VTR_ASSERT_SAFE(valid_decoder_id(decoder)); + return addr_sizes_[decoder]; +} + +/* Get the size of data output of a decoder */ +size_t DecoderLibrary::data_size(const DecoderId& decoder) const { + VTR_ASSERT_SAFE(valid_decoder_id(decoder)); + return data_sizes_[decoder]; +} + +/* Get the flag if a decoder includes an ENABLE signal */ +bool DecoderLibrary::use_enable(const DecoderId& decoder) const { + VTR_ASSERT_SAFE(valid_decoder_id(decoder)); + return use_enable_[decoder]; +} + +/* Get the flag if a decoder includes an DATA_IN signal */ +bool DecoderLibrary::use_data_in(const DecoderId& decoder) const { + VTR_ASSERT_SAFE(valid_decoder_id(decoder)); + return use_data_in_[decoder]; +} + +/* Get the flag if a decoder includes a data_inv port which is an inversion of the regular data output port */ +bool DecoderLibrary::use_data_inv_port(const DecoderId& decoder) const { + VTR_ASSERT_SAFE(valid_decoder_id(decoder)); + return use_data_inv_port_[decoder]; +} + +/* Find a decoder to the library, with the specification. + * If found, return the id of decoder. + * If not found, return an invalid id of decoder + * To avoid duplicated decoders, this function should be used before adding a decoder + * Example: + * DecoderId decoder_id == decoder_lib.find_decoder(); + * if (DecoderId::INVALID() == decoder_id) { + * // Add decoder + * } + */ +DecoderId DecoderLibrary::find_decoder(const size_t& addr_size, + const size_t& data_size, + const bool& use_enable, + const bool& use_data_in, + const bool& use_data_inv_port) const { + for (auto decoder : decoders()) { + if ( (addr_size == addr_sizes_[decoder]) + && (data_size == data_sizes_[decoder]) + && (use_enable == use_enable_[decoder]) + && (use_data_in == use_data_in_[decoder]) + && (use_data_inv_port == use_data_inv_port_[decoder]) ) { + return decoder; + } + } + + /* Not found, return an invalid id by default */ + return DecoderId::INVALID(); +} + +/*************************************************************************************** + * Public Validators + **************************************************************************************/ +/* Validate ids */ +bool DecoderLibrary::valid_decoder_id(const DecoderId& decoder) const { + return size_t(decoder) < decoder_ids_.size() && decoder_ids_[decoder] == decoder; +} + +/*************************************************************************************** + * Public Mutators : Basic Operations + **************************************************************************************/ +/* Add a decoder to the library */ +DecoderId DecoderLibrary::add_decoder(const size_t& addr_size, + const size_t& data_size, + const bool& use_enable, + const bool& use_data_in, + const bool& use_data_inv_port) { + DecoderId decoder = DecoderId(decoder_ids_.size()); + /* Push to the decoder list */ + decoder_ids_.push_back(decoder); + /* Resize the other related vectors */ + addr_sizes_.push_back(addr_size); + data_sizes_.push_back(data_size); + use_enable_.push_back(use_enable); + use_data_in_.push_back(use_data_in); + use_data_inv_port_.push_back(use_data_inv_port); + + return decoder; +} + +} /* End namespace openfpga*/ diff --git a/openfpga/src/mux_lib/decoder_library.h b/openfpga/src/mux_lib/decoder_library.h new file mode 100644 index 000000000..397308db5 --- /dev/null +++ b/openfpga/src/mux_lib/decoder_library.h @@ -0,0 +1,95 @@ +/*************************************************************************************** + * This file includes key data structures to describe decoders which are used + * in FPGA fabrics + * A decoder is a circuit to convert a binary input to one-hot codes + * The outputs are assumes to be one-hot codes (at most only one '1' exist) + * Therefore, the number of inputs is ceil(log(num_of_outputs)/log(2)) + * All the decoders are assumed to follow the port map : + * + * Inputs + * | | ... | + * v v v + * +-----------+ + * / \ + * / Decoder \ + * +-----------------+ + * | | | ... | | | + * v v v v v v + * Outputs + + ***************************************************************************************/ + +#ifndef DECODER_LIBRARY_H +#define DECODER_LIBRARY_H + +#include "vtr_vector.h" +#include "vtr_range.h" +#include "decoder_library_fwd.h" + +/* Begin namespace openfpga */ +namespace openfpga { + +class DecoderLibrary { + public: /* Types and ranges */ + typedef vtr::vector::const_iterator decoder_iterator; + + typedef vtr::Range decoder_range; + + public: /* Public accessors: Aggregates */ + /* Get all the decoders */ + decoder_range decoders() const; + + public: /* Public accessors: Data query */ + /* Get the size of address input of a decoder */ + size_t addr_size(const DecoderId& decoder) const; + /* Get the size of data output of a decoder */ + size_t data_size(const DecoderId& decoder) const; + /* Get the flag if a decoder includes an ENABLE signal */ + bool use_enable(const DecoderId& decoder) const; + /* Get the flag if a decoder includes an DATA_IN signal */ + bool use_data_in(const DecoderId& decoder) const; + /* Get the flag if a decoder includes a data_inv port which is an inversion of the regular data output port */ + bool use_data_inv_port(const DecoderId& decoder) const; + /* Find a decoder to the library, with the specification. + * If found, return the id of decoder. + * If not found, return an invalid id of decoder + * To avoid duplicated decoders, this function should be used before adding a decoder + * Example: + * DecoderId decoder_id == decoder_lib.find_decoder(); + * if (DecoderId::INVALID() == decoder_id) { + * // Add decoder + * } + */ + DecoderId find_decoder(const size_t& addr_size, + const size_t& data_size, + const bool& use_enable, + const bool& use_data_in, + const bool& use_data_inv_port) const; + + public: /* Public validators */ + /* valid ids */ + bool valid_decoder_id(const DecoderId& decoder) const; + + public: /* Private mutators : basic operations */ + /* Add a decoder to the library */ + DecoderId add_decoder(const size_t& addr_size, + const size_t& data_size, + const bool& use_enable, + const bool& use_data_in, + const bool& use_data_inv_port); + + private: /* Internal Data */ + vtr::vector decoder_ids_; + vtr::vector addr_sizes_; + vtr::vector data_sizes_; + vtr::vector use_enable_; + vtr::vector use_data_in_; + vtr::vector use_data_inv_port_; +}; + +} /* End namespace openfpga*/ + + + +#endif + diff --git a/openfpga/src/mux_lib/decoder_library_fwd.h b/openfpga/src/mux_lib/decoder_library_fwd.h new file mode 100644 index 000000000..e56a7ce82 --- /dev/null +++ b/openfpga/src/mux_lib/decoder_library_fwd.h @@ -0,0 +1,23 @@ +/************************************************** + * This file includes only declarations for + * the data structures to describe decoders + * Please refer to decoder_library.h for more details + *************************************************/ +#ifndef DECODER_LIBRARY_FWD_H +#define DECODER_LIBRARY_FWD_H + +#include "vtr_strong_id.h" + +/* Begin namespace openfpga */ +namespace openfpga { + +/* Strong Ids for MUXes */ +struct decoder_id_tag; + +typedef vtr::StrongId DecoderId; + +class DecoderLibrary; + +} /* End namespace openfpga*/ + +#endif diff --git a/openfpga/src/mux_lib/mux_graph.cpp b/openfpga/src/mux_lib/mux_graph.cpp new file mode 100644 index 000000000..5b0f81828 --- /dev/null +++ b/openfpga/src/mux_lib/mux_graph.cpp @@ -0,0 +1,1254 @@ +/************************************************** + * This file includes member functions for the + * data structures in mux_graph.h + *************************************************/ +#include +#include +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" + +#include "mux_utils.h" +#include "mux_graph.h" + +/* Begin namespace openfpga */ +namespace openfpga { + +/************************************************** + * Member functions for the class MuxGraph + *************************************************/ + +/************************************************** + * Public Constructors + *************************************************/ + +/* Create an object based on a Circuit Model which is MUX */ +MuxGraph::MuxGraph(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const size_t& mux_size) { + /* Build the graph for a given multiplexer model */ + build_mux_graph(circuit_lib, circuit_model, mux_size); +} + +/************************************************** + * Private Constructors + *************************************************/ +/* Create an empty graph */ +MuxGraph::MuxGraph() { + return; +} + +/************************************************** + * Public Accessors : Aggregates + *************************************************/ +//Accessors +MuxGraph::node_range MuxGraph::nodes() const { + return vtr::make_range(node_ids_.begin(), node_ids_.end()); +} + +/* Find the non-input nodes */ +std::vector MuxGraph::non_input_nodes() const { + /* Must be an valid graph */ + VTR_ASSERT_SAFE(valid_mux_graph()); + std::vector node_list; + + /* Build the node list, level by level */ + for (size_t level = 0; level < num_node_levels(); ++level) { + for (size_t node_type = 0; node_type < size_t(NUM_MUX_NODE_TYPES); ++node_type) { + /* Bypass any nodes which are not OUTPUT and INTERNAL */ + if (size_t(MUX_INPUT_NODE) == node_type) { + continue; + } + /* Reach here, this is either an OUTPUT or INTERNAL node */ + for (auto node : node_lookup_[level][node_type]) { + node_list.push_back(node); + } + } + } + + return node_list; +} + +MuxGraph::edge_range MuxGraph::edges() const { + return vtr::make_range(edge_ids_.begin(), edge_ids_.end()); +} + +MuxGraph::mem_range MuxGraph::memories() const { + return vtr::make_range(mem_ids_.begin(), mem_ids_.end()); +} + +std::vector MuxGraph::levels() const { + std::vector graph_levels; + for (size_t lvl = 0; lvl < num_levels(); ++lvl) { + graph_levels.push_back(lvl); + } + return graph_levels; +} + +std::vector MuxGraph::node_levels() const { + std::vector graph_levels; + for (size_t lvl = 0; lvl < num_node_levels(); ++lvl) { + graph_levels.push_back(lvl); + } + return graph_levels; +} + +/************************************************** + * Public Accessors: Data query + *************************************************/ + +/* Find the number of inputs in the MUX graph */ +size_t MuxGraph::num_inputs() const { + /* need to check if the graph is valid or not */ + VTR_ASSERT_SAFE(valid_mux_graph()); + /* Sum up the number of INPUT nodes in each level */ + size_t num_inputs = 0; + for (auto node_per_level : node_lookup_) { + num_inputs += node_per_level[MUX_INPUT_NODE].size(); + } + return num_inputs; +} + +/* Return the node ids of all the inputs of the multiplexer */ +std::vector MuxGraph::inputs() const { + std::vector input_nodes; + /* need to check if the graph is valid or not */ + VTR_ASSERT_SAFE(valid_mux_graph()); + /* Add the input nodes in each level */ + for (auto node_per_level : node_lookup_) { + input_nodes.insert(input_nodes.end(), node_per_level[MUX_INPUT_NODE].begin(), node_per_level[MUX_INPUT_NODE].end()); + } + return input_nodes; +} + +/* Find the number of outputs in the MUX graph */ +size_t MuxGraph::num_outputs() const { + /* need to check if the graph is valid or not */ + VTR_ASSERT_SAFE(valid_mux_graph()); + /* Sum up the number of INPUT nodes in each level */ + size_t num_outputs = 0; + for (auto node_per_level : node_lookup_) { + num_outputs += node_per_level[MUX_OUTPUT_NODE].size(); + } + return num_outputs; +} + +/* Return the node ids of all the outputs of the multiplexer */ +std::vector MuxGraph::outputs() const { + std::vector output_nodes; + /* need to check if the graph is valid or not */ + VTR_ASSERT_SAFE(valid_mux_graph()); + /* Add the output nodes in each level */ + for (auto node_per_level : node_lookup_) { + output_nodes.insert(output_nodes.end(), node_per_level[MUX_OUTPUT_NODE].begin(), node_per_level[MUX_OUTPUT_NODE].end()); + } + return output_nodes; +} + +/* Find the edge between two MUX nodes */ +std::vector MuxGraph::find_edges(const MuxNodeId& from_node, const MuxNodeId& to_node) const { + std::vector edges; + + VTR_ASSERT(valid_node_id(from_node)); + VTR_ASSERT(valid_node_id(to_node)); + + for (const auto& edge : node_out_edges_[from_node]) { + for (const auto& cand : edge_sink_nodes_[edge]) { + if (cand == to_node) { + /* This is the wanted edge, add to list */ + edges.push_back(edge); + } + } + } + + return edges; +} + +/* Find the number of levels in the MUX graph */ +size_t MuxGraph::num_levels() const { + /* need to check if the graph is valid or not */ + VTR_ASSERT_SAFE(valid_mux_graph()); + /* The num_levels by definition excludes the level for outputs, so a deduection is applied */ + return node_lookup_.size() - 1; +} + +/* Find the actual number of levels in the MUX graph */ +size_t MuxGraph::num_node_levels() const { + /* need to check if the graph is valid or not */ + VTR_ASSERT_SAFE(valid_mux_graph()); + return node_lookup_.size(); +} + +/* Find the number of configuration memories in the MUX graph */ +size_t MuxGraph::num_memory_bits() const { + /* need to check if the graph is valid or not */ + VTR_ASSERT_SAFE(valid_mux_graph()); + return mem_ids_.size(); +} + +/* Find the number of SRAMs at a level in the MUX graph */ +size_t MuxGraph::num_memory_bits_at_level(const size_t& level) const { + /* need to check if the graph is valid or not */ + VTR_ASSERT_SAFE(valid_level(level)); + VTR_ASSERT_SAFE(valid_mux_graph()); + return mem_lookup_[level].size(); +} + +/* Return memory id at level */ +std::vector MuxGraph::memories_at_level(const size_t& level) const { + /* need to check if the graph is valid or not */ + VTR_ASSERT_SAFE(valid_level(level)); + VTR_ASSERT_SAFE(valid_mux_graph()); + return mem_lookup_[level]; +} + +/* Find the number of nodes at a given level in the MUX graph */ +size_t MuxGraph::num_nodes_at_level(const size_t& level) const { + /* validate the level numbers */ + VTR_ASSERT_SAFE(valid_level(level)); + VTR_ASSERT_SAFE(valid_mux_graph()); + + size_t num_nodes = 0; + for (size_t node_type = 0; node_type < size_t(NUM_MUX_NODE_TYPES); ++node_type) { + num_nodes += node_lookup_[level][node_type].size(); + } + return num_nodes; +} + +/* Find the level of a node */ +size_t MuxGraph::node_level(const MuxNodeId& node) const { + /* validate the node */ + VTR_ASSERT(valid_node_id(node)); + return node_levels_[node]; +} + +/* Find the index of a node at its level */ +size_t MuxGraph::node_index_at_level(const MuxNodeId& node) const { + /* validate the node */ + VTR_ASSERT(valid_node_id(node)); + return node_ids_at_level_[node]; +} + +/* Find the input edges for a node */ +std::vector MuxGraph::node_in_edges(const MuxNodeId& node) const { + /* validate the node */ + VTR_ASSERT(valid_node_id(node)); + return node_in_edges_[node]; +} + +/* Find the input nodes for a edge */ +std::vector MuxGraph::edge_src_nodes(const MuxEdgeId& edge) const { + /* validate the edge */ + VTR_ASSERT(valid_edge_id(edge)); + return edge_src_nodes_[edge]; +} + +/* Find the mem that control the edge */ +MuxMemId MuxGraph::find_edge_mem(const MuxEdgeId& edge) const { + /* validate the edge */ + VTR_ASSERT(valid_edge_id(edge)); + return edge_mem_ids_[edge]; +} + +/* Identify if the edge is controlled by the inverted output of a mem */ +bool MuxGraph::is_edge_use_inv_mem(const MuxEdgeId& edge) const { + /* validate the edge */ + VTR_ASSERT(valid_edge_id(edge)); + return edge_inv_mem_[edge]; +} + +/* Find the sizes of each branch of a MUX */ +std::vector MuxGraph::branch_sizes() const { + std::vector branch; + /* Visit each internal nodes/output nodes and find the the number of incoming edges */ + for (auto node : node_ids_ ) { + /* Bypass input nodes */ + if ( (MUX_OUTPUT_NODE != node_types_[node]) + && (MUX_INTERNAL_NODE != node_types_[node]) ) { + continue; + } + + size_t branch_size = node_in_edges_[node].size(); + + /* make sure the branch size is valid */ + VTR_ASSERT_SAFE(valid_mux_implementation_num_inputs(branch_size)); + + /* Nodes with the same number of incoming edges, indicate the same size of branch circuit */ + std::vector::iterator it; + it = std::find(branch.begin(), branch.end(), branch_size); + /* if already exists a branch with the same size, skip updating the vector */ + if (it != branch.end()) { + continue; + } + branch.push_back(branch_size); + } + + /* Sort the branch by size */ + std::sort(branch.begin(), branch.end()); + + return branch; +} + +/* Find the sizes of each branch of a MUX at a given level */ +std::vector MuxGraph::branch_sizes(const size_t& level) const { + std::vector branch; + /* Visit each internal nodes/output nodes and find the the number of incoming edges */ + for (auto node : node_ids_ ) { + /* Bypass input nodes */ + if ( (MUX_OUTPUT_NODE != node_types_[node]) + && (MUX_INTERNAL_NODE != node_types_[node]) ) { + continue; + } + /* Bypass nodes that is not at the level */ + if ( level != node_levels_[node]) { + continue; + } + + size_t branch_size = node_in_edges_[node].size(); + + /* make sure the branch size is valid */ + VTR_ASSERT_SAFE(valid_mux_implementation_num_inputs(branch_size)); + + /* Nodes with the same number of incoming edges, indicate the same size of branch circuit */ + std::vector::iterator it; + it = std::find(branch.begin(), branch.end(), branch_size); + /* if already exists a branch with the same size, skip updating the vector */ + if (it != branch.end()) { + continue; + } + branch.push_back(branch_size); + } + + /* Sort the branch by size */ + std::sort(branch.begin(), branch.end()); + + return branch; +} + + +/* Build a subgraph from the given node + * The strategy is very simple, we just + * extract a 1-level graph from here + */ +MuxGraph MuxGraph::subgraph(const MuxNodeId& root_node) const { + /* Validate the node */ + VTR_ASSERT_SAFE(this->valid_node_id(root_node)); + + /* Generate an empty graph */ + MuxGraph mux_graph; + + /* A map to record node-to-node mapping from origin graph to subgraph */ + std::map node2node_map; + + /* A map to record edge-to-edge mapping from origin graph to subgraph */ + std::map edge2edge_map; + + /* Add output nodes to subgraph */ + MuxNodeId to_node_subgraph = mux_graph.add_node(MUX_OUTPUT_NODE); + mux_graph.node_levels_[to_node_subgraph] = 1; + mux_graph.node_ids_at_level_[to_node_subgraph] = 0; + mux_graph.node_output_ids_[to_node_subgraph] = MuxOutputId(0); + /* Update the node-to-node map */ + node2node_map[root_node] = to_node_subgraph; + + /* Add input nodes and edges to subgraph */ + size_t input_cnt = 0; + for (auto edge_origin : this->node_in_edges_[root_node]) { + VTR_ASSERT_SAFE(1 == edge_src_nodes_[edge_origin].size()); + /* Add nodes */ + MuxNodeId from_node_origin = this->edge_src_nodes_[edge_origin][0]; + MuxNodeId from_node_subgraph = mux_graph.add_node(MUX_INPUT_NODE); + /* Configure the nodes */ + mux_graph.node_levels_[from_node_subgraph] = 0; + mux_graph.node_ids_at_level_[from_node_subgraph] = input_cnt; + mux_graph.node_input_ids_[from_node_subgraph] = MuxInputId(input_cnt); + input_cnt++; + /* Update the node-to-node map */ + node2node_map[from_node_origin] = from_node_subgraph; + + /* Add edges */ + MuxEdgeId edge_subgraph = mux_graph.add_edge(node2node_map[from_node_origin], node2node_map[root_node]); + edge2edge_map[edge_origin] = edge_subgraph; + /* Configure edges */ + mux_graph.edge_models_[edge_subgraph] = this->edge_models_[edge_origin]; + mux_graph.edge_inv_mem_[edge_subgraph] = this->edge_inv_mem_[edge_origin]; + } + + /* A map to record mem-to-mem mapping from origin graph to subgraph */ + std::map mem2mem_map; + + /* Add memory bits and configure edges */ + for (auto edge_origin : this->node_in_edges_[root_node]) { + MuxMemId mem_origin = this->edge_mem_ids_[edge_origin]; + /* Try to find if the mem is already in the list */ + std::map::iterator it = mem2mem_map.find(mem_origin); + if (it != mem2mem_map.end()) { + /* Found, we skip mem addition. But make sure we have a valid one */ + VTR_ASSERT_SAFE(MuxMemId::INVALID() != mem2mem_map[mem_origin]); + /* configure the edge */ + mux_graph.edge_mem_ids_[edge2edge_map[edge_origin]] = mem2mem_map[mem_origin]; + continue; + } + /* Not found, we add a memory bit and record in the mem-to-mem map */ + MuxMemId mem_subgraph = mux_graph.add_mem(); + mux_graph.set_mem_level(mem_subgraph, 0); + mem2mem_map[mem_origin] = mem_subgraph; + /* configure the edge */ + mux_graph.edge_mem_ids_[edge2edge_map[edge_origin]] = mem_subgraph; + } + + /* Since the graph is finalized, it is time to build the fast look-up */ + mux_graph.build_node_lookup(); + mux_graph.build_mem_lookup(); + + return mux_graph; +} + +/* Generate MUX graphs for its branches + * Similar to the branch_sizes() method, + * we search all the internal nodes and + * find out what are the input sizes of + * the branches. + * Then we extract unique subgraphs and return + */ +std::vector MuxGraph::build_mux_branch_graphs() const { + std::map branch_done; /* A map showing the status of graph generation */ + + std::vector branch_graphs; + + /* Visit each internal nodes/output nodes and find the the number of incoming edges */ + for (auto node : node_ids_ ) { + /* Bypass input nodes */ + if ( (MUX_OUTPUT_NODE != node_types_[node]) + && (MUX_INTERNAL_NODE != node_types_[node]) ) { + continue; + } + + size_t branch_size = node_in_edges_[node].size(); + + /* make sure the branch size is valid */ + VTR_ASSERT_SAFE(valid_mux_implementation_num_inputs(branch_size)); + + /* check if the branch have been done in sub-graph extraction! */ + std::map::iterator it = branch_done.find(branch_size); + /* if it is done, we can skip */ + if (it != branch_done.end()) { + VTR_ASSERT(branch_done[branch_size]); + continue; + } + + /* Generate a subgraph and push back */ + branch_graphs.push_back(subgraph(node)); + + /* Mark it is done for this branch size */ + branch_done[branch_size] = true; + } + + return branch_graphs; +} + +/* Get the input id of a given node */ +MuxInputId MuxGraph::input_id(const MuxNodeId& node_id) const { + /* Validate node id */ + VTR_ASSERT(valid_node_id(node_id)); + /* Must be an input */ + VTR_ASSERT(MUX_INPUT_NODE == node_types_[node_id]); + return node_input_ids_[node_id]; +} + +/* Identify if the node is an input of the MUX */ +bool MuxGraph::is_node_input(const MuxNodeId& node_id) const { + /* Validate node id */ + VTR_ASSERT(true == valid_node_id(node_id)); + return (MUX_INPUT_NODE == node_types_[node_id]); +} + +/* Get the output id of a given node */ +MuxOutputId MuxGraph::output_id(const MuxNodeId& node_id) const { + /* Validate node id */ + VTR_ASSERT(valid_node_id(node_id)); + /* Must be an output */ + VTR_ASSERT(MUX_OUTPUT_NODE == node_types_[node_id]); + return node_output_ids_[node_id]; +} + +/* Identify if the node is an output of the MUX */ +bool MuxGraph::is_node_output(const MuxNodeId& node_id) const { + /* Validate node id */ + VTR_ASSERT(true == valid_node_id(node_id)); + return (MUX_OUTPUT_NODE == node_types_[node_id]); +} + +/* Get the node id of a given input */ +MuxNodeId MuxGraph::node_id(const MuxInputId& input_id) const { + /* Use the node_lookup to accelerate the search */ + for (const auto& lvl : node_lookup_) { + for (const auto& cand_node : lvl[MUX_INPUT_NODE]) { + if (input_id == node_input_ids_[cand_node]) { + return cand_node; + } + } + } + + return MuxNodeId::INVALID(); +} + +/* Get the node id of a given output */ +MuxNodeId MuxGraph::node_id(const MuxOutputId& output_id) const { + /* Use the node_lookup to accelerate the search */ + for (const auto& lvl : node_lookup_) { + for (const auto& cand_node : lvl[MUX_OUTPUT_NODE]) { + if (output_id == node_output_ids_[cand_node]) { + return cand_node; + } + } + } + + return MuxNodeId::INVALID(); +} + + +/* Get the node id w.r.t. the node level and node_index at the level + * Return an invalid value if not found + */ +MuxNodeId MuxGraph::node_id(const size_t& node_level, const size_t& node_index_at_level) const { + /* Ensure we have a valid node_look-up */ + VTR_ASSERT_SAFE(valid_node_lookup()); + + MuxNodeId ret_node = MuxNodeId::INVALID(); + + /* Search in the fast look up */ + if (node_level >= node_lookup_.size()) { + return ret_node; + } + + size_t node_cnt = 0; + /* Node level is valid, search in the node list */ + for (const auto& nodes_by_type : node_lookup_[node_level]) { + /* Search the node_index_at_level of each node */ + for (const auto& node : nodes_by_type) { + if (node_index_at_level != node_ids_at_level_[node]) { + continue; + } + /* Find the node, assign value and update the counter */ + ret_node = node; + node_cnt++; + } + } + + /* We should either find a node or nothing */ + VTR_ASSERT((0 == node_cnt) || (1 == node_cnt)); + + return ret_node; +} + +/* Decode memory bits based on an input id and an output id */ +vtr::vector MuxGraph::decode_memory_bits(const MuxInputId& input_id, + const MuxOutputId& output_id) const { + /* initialize the memory bits: TODO: support default value */ + vtr::vector mem_bits(mem_ids_.size(), false); + + /* valid the input and output */ + VTR_ASSERT_SAFE(valid_input_id(input_id)); + VTR_ASSERT_SAFE(valid_output_id(output_id)); + + /* Mark all the nodes as not visited */ + vtr::vector visited(nodes().size(), false); + + /* Create a queue for Breadth-First Search */ + std::list queue; + + /* Mark the input node as visited and enqueue it */ + visited[node_id(input_id)] = true; + queue.push_back(node_id(input_id)); + + /* Create a flag to indicate if the route is success or not */ + bool route_success = false; + + while(!queue.empty()) { + /* Dequeue a mux node from queue, + * we will walk through all the fan-in of this node in this loop + */ + MuxNodeId node_to_expand = queue.front(); + queue.pop_front(); + /* Get all fan-in nodes of the dequeued node + * If the node has not been visited, + * then mark it visited and enqueue it + */ + VTR_ASSERT_SAFE (1 == node_out_edges_[node_to_expand].size()); + MuxEdgeId edge = node_out_edges_[node_to_expand][0]; + + /* Configure the mem bits: + * if inv_mem is enabled, it means 0 to enable this edge + * otherwise, it is 1 to enable this edge + */ + MuxMemId mem = edge_mem_ids_[edge]; + VTR_ASSERT_SAFE (valid_mem_id(mem)); + if (true == edge_inv_mem_[edge]) { + mem_bits[mem] = false; + } else { + mem_bits[mem] = true; + } + + /* each edge must have 1 fan-out */ + VTR_ASSERT_SAFE (1 == edge_sink_nodes_[edge].size()); + + /* Get the fan-out node */ + MuxNodeId next_node = edge_sink_nodes_[edge][0]; + + /* If next node is the output node we want, we can finish here */ + if (next_node == node_id(output_id)) { + route_success = true; + break; + } + + /* Add next node to the queue if not visited yet */ + if (false == visited[next_node]) { + visited[next_node] = true; + queue.push_back(next_node); + } + } + + /* Routing must be success! */ + VTR_ASSERT(true == route_success); + + return mem_bits; +} + +/* Find the input node that the memory bits will route an output node to + * This function backward propagate from the output node to an input node + * assuming the memory bits are applied + */ +MuxInputId MuxGraph::find_input_node_driven_by_output_node(const std::map& memory_bits, + const MuxOutputId& output_id) const { + /* Ensure that the memory bits fit the size of memory bits in this MUX */ + VTR_ASSERT(memory_bits.size() == mem_ids_.size()); + + /* valid the output */ + VTR_ASSERT_SAFE(valid_output_id(output_id)); + + /* Start from the output node */ + /* Mark all the nodes as not visited */ + vtr::vector visited(nodes().size(), false); + + /* Create a queue for Breadth-First Search */ + std::list queue; + + /* Mark the output node as visited and enqueue it */ + visited[node_id(output_id)] = true; + queue.push_back(node_id(output_id)); + + /* Record the destination input id */ + MuxInputId des_input_id = MuxInputId::INVALID(); + + while(!queue.empty()) { + /* Dequeue a mux node from queue, + * we will walk through all the fan-in of this node in this loop + */ + MuxNodeId node_to_expand = queue.front(); + queue.pop_front(); + /* Get all fan-in nodes of the dequeued node + * If the node has not been visited, + * then mark it visited and enqueue it + */ + MuxEdgeId next_edge = MuxEdgeId::INVALID(); + for (const MuxEdgeId& edge : node_in_edges_[node_to_expand]) { + /* Configure the mem bits and find the edge that will propagate the signal + * if inv_mem is enabled, it means false to enable this edge + * otherwise, it is true to enable this edge + */ + MuxMemId mem = edge_mem_ids_[edge]; + VTR_ASSERT_SAFE (valid_mem_id(mem)); + if (edge_inv_mem_[edge] == !memory_bits.at(mem)) { + next_edge = edge; + break; + } + } + /* We must have a valid next edge */ + VTR_ASSERT(MuxEdgeId::INVALID() != next_edge); + + /* each edge must have 1 fan-out */ + VTR_ASSERT_SAFE (1 == edge_src_nodes_[next_edge].size()); + + /* Get the fan-in node */ + MuxNodeId next_node = edge_src_nodes_[next_edge][0]; + + /* If next node is an input node, we can finish here */ + if (true == is_node_input(next_node)) { + des_input_id = input_id(next_node); + break; + } + + /* Add next node to the queue if not visited yet */ + if (false == visited[next_node]) { + visited[next_node] = true; + queue.push_back(next_node); + } + } + + /* Routing must be success! */ + VTR_ASSERT(MuxInputId::INVALID() != des_input_id); + + return des_input_id; +} + +/************************************************** + * Private mutators: basic operations + *************************************************/ +/* Add a unconfigured node to the MuxGraph */ +MuxNodeId MuxGraph::add_node(const enum e_mux_graph_node_type& node_type) { + MuxNodeId node = MuxNodeId(node_ids_.size()); + /* Push to the node list */ + node_ids_.push_back(node); + /* Resize the other node-related vectors */ + node_types_.push_back(node_type); + node_input_ids_.push_back(MuxInputId::INVALID()); + node_output_ids_.push_back(MuxOutputId::INVALID()); + node_levels_.push_back(-1); + node_ids_at_level_.push_back(-1); + node_in_edges_.emplace_back(); + node_out_edges_.emplace_back(); + + return node; +} + +/* Add a edge connecting two nodes */ +MuxEdgeId MuxGraph::add_edge(const MuxNodeId& from_node, const MuxNodeId& to_node) { + MuxEdgeId edge = MuxEdgeId(edge_ids_.size()); + /* Push to the node list */ + edge_ids_.push_back(edge); + /* Resize the other node-related vectors */ + edge_models_.push_back(CircuitModelId::INVALID()); + edge_mem_ids_.push_back(MuxMemId::INVALID()); + edge_inv_mem_.push_back(false); + + /* update the edge-node connections */ + VTR_ASSERT(valid_node_id(from_node)); + edge_src_nodes_.emplace_back(); + edge_src_nodes_[edge].push_back(from_node); + node_out_edges_[from_node].push_back(edge); + + VTR_ASSERT(valid_node_id(to_node)); + edge_sink_nodes_.emplace_back(); + edge_sink_nodes_[edge].push_back(to_node); + node_in_edges_[to_node].push_back(edge); + + return edge; +} + +/* Add a memory bit to the MuxGraph */ +MuxMemId MuxGraph::add_mem() { + MuxMemId mem = MuxMemId(mem_ids_.size()); + /* Push to the node list */ + mem_ids_.push_back(mem); + mem_levels_.push_back(size_t(-1)); + /* Resize the other node-related vectors */ + + return mem; +} + +/* Configure the level of a memory */ +void MuxGraph::set_mem_level(const MuxMemId& mem, const size_t& level) { + /* Make sure we have valid edge and mem */ + VTR_ASSERT( valid_mem_id(mem) ); + + mem_levels_[mem] = level; +} + +/* Link an edge to a memory bit */ +void MuxGraph::set_edge_mem_id(const MuxEdgeId& edge, const MuxMemId& mem) { + /* Make sure we have valid edge and mem */ + VTR_ASSERT( valid_edge_id(edge) && valid_mem_id(mem) ); + + edge_mem_ids_[edge] = mem; +} + +/************************************************** + * Private mutators: graph builders + *************************************************/ + +/* Build a graph for a multi-level multiplexer implementation + * support both generic multi-level and tree-like multiplexers + * + * a N:1 multi-level MUX + * ---------------------- + * + * input_node --->+ + * | + * input_node --->| + * |--->+ + * ... | | + * | | + * input_node --->+ |---> ... + * | + * ... --->+ --->+ + * | + * ... ... |---> output_node + * | + * ... --->+ --->+ + * | + * input_node --->+ |---> ... + * | | + * input_node --->| | + * |--->+ + * ... | + * | + * input_node --->+ + * + * tree-like multiplexer graph will look like: + * -------------------------------------------- + * + * input_node --->+ + * |--->+ + * input_node --->+ |---> ... + * | + * --->+ --->+ + * ... ... ... |----> output_node + * ... --->+ --->+ + * |---> ... + * input_node --->+ | + * |--->+ + * input_node --->+ + * + */ +void MuxGraph::build_multilevel_mux_graph(const size_t& mux_size, + const size_t& num_levels, const size_t& num_inputs_per_branch, + const CircuitModelId& pgl_model) { + /* Make sure mux_size for each branch is valid */ + VTR_ASSERT(valid_mux_implementation_num_inputs(num_inputs_per_branch)); + + /* In regular cases, there is 1 mem bit for each input of a branch */ + size_t num_mems_per_level = num_inputs_per_branch; + /* For 2-input branch, only 1 mem bit is needed for each level! */ + if (2 == num_inputs_per_branch) { + num_mems_per_level = 1; + } + /* Number of memory bits is definite, add them */ + for (size_t ilvl = 0; ilvl < num_levels; ++ilvl) { + for (size_t imem = 0; imem < num_mems_per_level; ++imem) { + MuxMemId mem = add_mem(); + mem_levels_[mem] = ilvl; + } + } + + /* Create a fast node lookup locally. + * Only used for building the graph + * it sorts the nodes by levels and ids at each level + */ + std::vector> node_lookup; /* [num_levels][num_nodes_per_level] */ + node_lookup.resize(num_levels + 1); + + /* Number of outputs is definite, add and configure */ + MuxNodeId output_node = add_node(MUX_OUTPUT_NODE); + node_levels_[output_node] = num_levels; + node_ids_at_level_[output_node] = 0; + node_output_ids_[output_node] = MuxOutputId(0); + /* Update node lookup */ + node_lookup[num_levels].push_back(output_node); + + /* keep a list of node ids which can be candidates for input nodes */ + std::vector input_node_ids; + + /* Add internal nodes level by level, + * we start from the last level, following a strategy like tree growing + */ + for (size_t lvl = num_levels - 1; ; --lvl) { + /* Expand from the existing nodes + * Last level should expand from output_node + * Other levels will expand from internal nodes! + */ + size_t node_cnt_per_level = 0; /* A counter to record node indices at each level */ + for (MuxNodeId seed_node : node_lookup[lvl + 1]) { + /* Add a new node and connect to seed_node, until we reach the num_inputs_per_branch */ + for (size_t i = 0; i < num_inputs_per_branch; ++i) { + /* We deposite a type of INTERNAL_NODE, + * later it will be configured to INPUT if it is in the input list + */ + MuxNodeId expand_node = add_node(MUX_INTERNAL_NODE); + + /* Node level is deterministic */ + node_levels_[expand_node] = lvl; + node_ids_at_level_[expand_node] = node_cnt_per_level; + /* update level node counter */ + node_cnt_per_level++; + + /* Create an edge and connect the two nodes */ + MuxEdgeId edge = add_edge(expand_node, seed_node); + /* Configure the edge */ + edge_models_[edge] = pgl_model; + + /* Memory id depends on the level and offset in the current branch + * if number of inputs per branch is 2, it indicates a tree-like multiplexer, + * every two edges will share one memory bit + * otherwise, each edge corresponds to a memory bit + */ + + if ( 2 == num_inputs_per_branch) { + MuxMemId mem_id = MuxMemId(lvl); + set_edge_mem_id(edge, mem_id); + /* If this is a second edge in the branch, we will assign it to an inverted edge */ + if (0 != i % num_inputs_per_branch) { + edge_inv_mem_[edge] = true; + } + } else { + MuxMemId mem_id = MuxMemId( lvl * num_inputs_per_branch + i ); + set_edge_mem_id(edge, mem_id); + } + + /* Update node lookup */ + node_lookup[lvl].push_back(expand_node); + + /* Push the node to input list, and then remove the seed_node from the list */ + input_node_ids.push_back(expand_node); + /* Remove the node if the seed node is the list */ + std::vector::iterator it = find(input_node_ids.begin(), input_node_ids.end(), seed_node); + if (it != input_node_ids.end()) { + input_node_ids.erase(it); + } + + /* Check the number of input nodes, if already meet the demand, we can finish here */ + if (mux_size != input_node_ids.size()) { + continue; /* We need more inputs, keep looping */ + } + + /* The graph is done, we configure the input nodes and then we can return */ + /* We must be in level 0 !*/ + VTR_ASSERT( 0 == lvl ) ; + for (MuxNodeId input_node : input_node_ids) { + node_types_[input_node] = MUX_INPUT_NODE; + } + + /* Sort the nodes by the levels and offset */ + size_t input_cnt = 0; + for (auto lvl_nodes : node_lookup) { + for (MuxNodeId cand_node : lvl_nodes) { + if (MUX_INPUT_NODE != node_types_[cand_node]) { + continue; + } + /* Update the input node ids */ + node_input_ids_[cand_node] = MuxInputId(input_cnt); + /* Update the counter */ + input_cnt++; + } + } + /* Make sure we visited all the inputs in the cache */ + VTR_ASSERT(input_cnt == input_node_ids.size()); + /* Finish building the graph for a multi-level multiplexer */ + return; + } + } + } + /* Finish building the graph for a multi-level multiplexer */ +} + +/* Build the graph for a given one-level multiplexer implementation + * a N:1 one-level MUX + * + * input_node --->+ + * | + * input_node --->| + * |--> output_node + * ... | + * | + * input_node --->+ + */ +void MuxGraph::build_onelevel_mux_graph(const size_t& mux_size, + const CircuitModelId& pgl_model) { + /* Make sure mux_size is valid */ + VTR_ASSERT(valid_mux_implementation_num_inputs(mux_size)); + + /* We definitely know how many nodes we need, + * N inputs, 1 output and 0 internal nodes + */ + MuxNodeId output_node = add_node(MUX_OUTPUT_NODE); + node_levels_[output_node] = 1; + node_ids_at_level_[output_node] = 0; + node_output_ids_[output_node] = MuxOutputId(0); + + for (size_t i = 0; i < mux_size; ++i) { + MuxNodeId input_node = add_node(MUX_INPUT_NODE); + /* All the node belong to level 0 (we have only 1 level) */ + node_input_ids_[input_node] = MuxInputId(i); + node_levels_[input_node] = 0; + node_ids_at_level_[input_node] = i; + + /* We definitely know how many edges we need, + * the same as mux_size, add a edge connecting two nodes + */ + MuxEdgeId edge = add_edge(input_node, output_node); + /* Configure the edge */ + edge_models_[edge] = pgl_model; + + /* Create a memory bit*/ + MuxMemId mem = add_mem(); + mem_levels_[mem] = 0; + /* Link the edge to a memory bit */ + set_edge_mem_id(edge, mem); + } + /* Finish building the graph for a one-level multiplexer */ +} + +/* Convert some internal nodes to be additional outputs + * according to the fracturable LUT port definition + * We will iterate over each output port of a circuit model + * and find the frac_level and output_mask + * Then, the internal nodes at the frac_level will be converted + * to output nodes with a given output_mask + */ +void MuxGraph::add_fracturable_outputs(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model) { + /* Iterate over output ports */ + for (const auto& port : circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_OUTPUT, true)) { + /* Get the fracturable_level */ + size_t frac_level = circuit_lib.port_lut_frac_level(port); + /* Bypass invalid frac_level */ + if (size_t(-1) == frac_level) { + continue; + } + /* Iterate over output masks */ + for (const auto& output_idx : circuit_lib.port_lut_output_mask(port)) { + size_t num_matched_nodes = 0; + /* Iterate over node and find the internal nodes, which match the frac_level and output_idx */ + for (const auto& node : node_lookup_[frac_level][MUX_INTERNAL_NODE]) { + if (node_ids_at_level_[node] != output_idx) { + /* Bypass condition */ + continue; + } + /* Reach here, this is the node we want + * Convert it to output nodes and update the counter + */ + node_types_[node] = MUX_OUTPUT_NODE; + node_output_ids_[node] = MuxOutputId(num_outputs()); + num_matched_nodes++; + } + /* Either find 1 or 0 matched nodes */ + if (0 != num_matched_nodes) { + /* We should find only one node that matches! */ + VTR_ASSERT(1 == num_matched_nodes); + /* Rebuild the node look-up */ + build_node_lookup(); + continue; /* Finish here, go to next */ + } + /* Sometime the wanted node is already an output, do a double check */ + for (const auto& node : node_lookup_[frac_level][MUX_OUTPUT_NODE]) { + if (node_ids_at_level_[node] != output_idx) { + /* Bypass condition */ + continue; + } + /* Reach here, this is the node we want + * Just update the counter + */ + num_matched_nodes++; + } + /* We should find only one node that matches! */ + VTR_ASSERT(1 == num_matched_nodes); + } + } +} + +/* Build the graph for a given multiplexer model */ +void MuxGraph::build_mux_graph(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const size_t& mux_size) { + /* Make sure this model is a MUX */ + VTR_ASSERT((CIRCUIT_MODEL_MUX == circuit_lib.model_type(circuit_model)) + || (CIRCUIT_MODEL_LUT == circuit_lib.model_type(circuit_model)) ); + + /* Make sure mux_size is valid */ + VTR_ASSERT(valid_mux_implementation_num_inputs(mux_size)); + + size_t impl_mux_size = find_mux_implementation_num_inputs(circuit_lib, circuit_model, mux_size); + + /* Depends on the mux size, the implemented multiplexer structure may change! */ + enum e_circuit_model_structure impl_structure = find_mux_implementation_structure(circuit_lib, circuit_model, impl_mux_size); + + /* Branch on multiplexer structures, leading to different building strategies */ + switch (impl_structure) { + case CIRCUIT_MODEL_STRUCTURE_TREE: { + /* Find the number of levels */ + size_t num_levels = find_treelike_mux_num_levels(impl_mux_size); + + /* Find the number of inputs per branch, this is not final */ + size_t num_inputs_per_branch = 2; + + /* Build a multilevel mux graph */ + build_multilevel_mux_graph(impl_mux_size, num_levels, num_inputs_per_branch, circuit_lib.pass_gate_logic_model(circuit_model)); + break; + } + case CIRCUIT_MODEL_STRUCTURE_ONELEVEL: { + build_onelevel_mux_graph(impl_mux_size, circuit_lib.pass_gate_logic_model(circuit_model)); + break; + } + case CIRCUIT_MODEL_STRUCTURE_MULTILEVEL: { + /* Find the number of inputs per branch, this is not final */ + size_t num_inputs_per_branch = find_multilevel_mux_branch_num_inputs(impl_mux_size, circuit_lib.mux_num_levels(circuit_model)); + + /* Build a multilevel mux graph */ + build_multilevel_mux_graph(impl_mux_size, circuit_lib.mux_num_levels(circuit_model), + num_inputs_per_branch, + circuit_lib.pass_gate_logic_model(circuit_model)); + break; + } + default: + VTR_LOG_ERROR("Invalid multiplexer structure for circuit model '%s'!\n", + circuit_lib.model_name(circuit_model).c_str()); + exit(1); + } + + /* Since the graph is finalized, it is time to build the fast look-up */ + build_node_lookup(); + build_mem_lookup(); + + /* For fracturable LUTs, we need to add more outputs to the MUX graph */ + if ( (CIRCUIT_MODEL_LUT == circuit_lib.model_type(circuit_model)) + && (true == circuit_lib.is_lut_fracturable(circuit_model)) ) { + add_fracturable_outputs(circuit_lib, circuit_model); + } +} + +/* Build fast node lookup */ +void MuxGraph::build_node_lookup() { + /* Invalidate the node lookup if necessary */ + invalidate_node_lookup(); + + /* Find the maximum number of levels */ + size_t num_levels = 0; + for (auto node : nodes()) { + num_levels = std::max((int)node_levels_[node], (int)num_levels); + } + + /* Resize node_lookup */ + node_lookup_.resize(num_levels + 1); + for (size_t lvl = 0; lvl < node_lookup_.size(); ++lvl) { + /* Resize by number of node types */ + node_lookup_[lvl].resize(NUM_MUX_NODE_TYPES); + } + + /* Fill the node lookup */ + for (auto node : nodes()) { + node_lookup_[node_levels_[node]][size_t(node_types_[node])].push_back(node); + } +} + +/* Build fast mem lookup */ +void MuxGraph::build_mem_lookup() { + /* Invalidate the mem lookup if necessary */ + invalidate_mem_lookup(); + + /* Find the maximum number of levels */ + size_t num_levels = 0; + for (auto mem : memories()) { + num_levels = std::max((int)mem_levels_[mem], (int)num_levels); + } + + /* Resize mem_lookup */ + mem_lookup_.resize(num_levels + 1); + for (auto mem : memories()) { + /* Categorize mem nodes into mem_lookup */ + mem_lookup_[mem_levels_[mem]].push_back(mem); + } +} + +/* Invalidate (empty) the node fast lookup*/ +void MuxGraph::invalidate_node_lookup() { + node_lookup_.clear(); +} + +/* Invalidate (empty) the mem fast lookup*/ +void MuxGraph::invalidate_mem_lookup() { + mem_lookup_.clear(); +} + +/************************************************** + * Private validators + *************************************************/ + +/* valid ids */ +bool MuxGraph::valid_node_id(const MuxNodeId& node) const { + return size_t(node) < node_ids_.size() && node_ids_[node] == node; +} + +bool MuxGraph::valid_edge_id(const MuxEdgeId& edge) const { + return size_t(edge) < edge_ids_.size() && edge_ids_[edge] == edge; +} + +bool MuxGraph::valid_mem_id(const MuxMemId& mem) const { + return size_t(mem) < mem_ids_.size() && mem_ids_[mem] == mem; +} + +/* validate an input id (from which data path signal will be progagated to the output) */ +bool MuxGraph::valid_input_id(const MuxInputId& input_id) const { + for (const auto& lvl : node_lookup_) { + for (const auto& node : lvl[MUX_INPUT_NODE]) { + if (size_t(input_id) > size_t(node_input_ids_[node])) { + return false; + } + } + } + + return true; +} + +/* validate an output id */ +bool MuxGraph::valid_output_id(const MuxOutputId& output_id) const { + for (const auto& lvl : node_lookup_) { + for (const auto& node : lvl[MUX_OUTPUT_NODE]) { + if (size_t(output_id) > size_t(node_output_ids_[node])) { + return false; + } + } + } + + return true; +} + +bool MuxGraph::valid_level(const size_t& level) const { + return level < num_node_levels(); +} + +bool MuxGraph::valid_node_lookup() const { + return node_lookup_.empty(); +} + +/* validate a mux graph and see if it is valid */ +bool MuxGraph::valid_mux_graph() const { + /* A valid MUX graph should be + * 1. every node has 1 fan-out except output node + * 2. every input can be routed to the output node + */ + for (const auto& node : nodes()) { + /* output node has 0 fan-out*/ + if (MUX_OUTPUT_NODE == node_types_[node]) { + continue; + } + /* other nodes should have 1 fan-out */ + if (1 != node_out_edges_[node].size()) { + return false; + } + } + + /* Try to route to output */ + for (const auto& node : nodes()) { + if (MUX_INPUT_NODE == node_types_[node]) { + MuxNodeId next_node = node; + while ( 0 < node_out_edges_[next_node].size() ) { + MuxEdgeId edge = node_out_edges_[next_node][0]; + /* each edge must have 1 fan-out */ + if (1 != edge_sink_nodes_[edge].size()) { + return false; + } + next_node = edge_sink_nodes_[edge][0]; + } + if (MUX_OUTPUT_NODE != node_types_[next_node]) { + return false; + } + } + } + + return true; +} + +} /* End namespace openfpga*/ diff --git a/openfpga/src/mux_lib/mux_graph.h b/openfpga/src/mux_lib/mux_graph.h new file mode 100644 index 000000000..fd2fbbf6d --- /dev/null +++ b/openfpga/src/mux_lib/mux_graph.h @@ -0,0 +1,213 @@ +#ifndef MUX_GRAPH_H +#define MUX_GRAPH_H + +/******************************************************************** + * Include header files required by the data structure definition + *******************************************************************/ +#include +#include "vtr_vector.h" +#include "vtr_range.h" +#include "mux_graph_fwd.h" +#include "circuit_library.h" + +/* Begin namespace openfpga */ +namespace openfpga { + +/************************************************** + * This file includes a data structure to describe + * the internal structure of a multiplexer + * using a generic graph representation + * A Branch is a N:1 MUX in the part of MUX graph + * + * branch_input --->+ + * | + * branch_input --->| + * |--> branch_out + * ... | + * | + * branch_input --->+ + * + * A short example of how a two-level MUX is organized by branches + * + * +-----------+ +--------+ + * mux_inputs--->| Branch[0] |--->| | + * +-----------+ | | + * ... | Branch |---> mux_out + * +-----------+ | [N+1] | + * mux_inputs--->| Branch[N] |--->| | + * +-----------+ +--------+ + * + *************************************************/ +class MuxGraph { + private: /* data types used only in this class */ + enum e_mux_graph_node_type { + MUX_INPUT_NODE, + MUX_INTERNAL_NODE, + MUX_OUTPUT_NODE, + NUM_MUX_NODE_TYPES + }; + public: /* Types and ranges */ + typedef vtr::vector::const_iterator node_iterator; + typedef vtr::vector::const_iterator edge_iterator; + typedef vtr::vector::const_iterator mem_iterator; + + typedef vtr::Range node_range; + typedef vtr::Range edge_range; + typedef vtr::Range mem_range; + public: /* Public Constructors */ + /* Create an object based on a Circuit Model which is MUX */ + MuxGraph(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const size_t& mux_size); + private: /* Private Constructors*/ + /* Create an empty graph */ + MuxGraph(); + public: /* Public accessors: Aggregates */ + node_range nodes() const; + /* Find the non-input nodes */ + std::vector non_input_nodes() const; + edge_range edges() const; + mem_range memories() const; + /* Find the number of levels in terms of the multiplexer */ + std::vector levels() const; + /* Find the actual number of levels in the graph */ + std::vector node_levels() const; + public: /* Public accessors: Data query */ + /* Find the number of inputs in the MUX graph */ + size_t num_inputs() const; + std::vector inputs() const; + /* Find the number of outputs in the MUX graph */ + size_t num_outputs() const; + std::vector outputs() const; + /* Find the edge between two MUX nodes */ + std::vector find_edges(const MuxNodeId& from_node, const MuxNodeId& to_node) const; + /* Find the number of levels in the MUX graph */ + size_t num_levels() const; + size_t num_node_levels() const; + /* Find the number of SRAMs in the MUX graph */ + size_t num_memory_bits() const; + /* Find the number of SRAMs at a level in the MUX graph */ + size_t num_memory_bits_at_level(const size_t& level) const; + /* Return memory id at level */ + std::vector memories_at_level(const size_t& level) const; + /* Find the number of nodes at a given level in the MUX graph */ + size_t num_nodes_at_level(const size_t& level) const; + /* Find the level of a node */ + size_t node_level(const MuxNodeId& node) const; + /* Find the index of a node at its level */ + size_t node_index_at_level(const MuxNodeId& node) const; + /* Find the input edges for a node */ + std::vector node_in_edges(const MuxNodeId& node) const; + /* Find the input nodes for a edge */ + std::vector edge_src_nodes(const MuxEdgeId& edge) const; + /* Find the mem that control the edge */ + MuxMemId find_edge_mem(const MuxEdgeId& edge) const; + /* Identify if the edge is controlled by the inverted output of a mem */ + bool is_edge_use_inv_mem(const MuxEdgeId& edge) const; + /* Find the sizes of each branch of a MUX */ + std::vector branch_sizes() const; + /* Find the sizes of each branch of a MUX at a given level */ + std::vector branch_sizes(const size_t& level) const; + /* Generate MUX graphs for its branches */ + MuxGraph subgraph(const MuxNodeId& node) const; + std::vector build_mux_branch_graphs() const; + /* Get the node id of a given input */ + MuxNodeId node_id(const MuxInputId& input_id) const; + /* Get the node id of a given output */ + MuxNodeId node_id(const MuxOutputId& output_id) const; + /* Get the node id w.r.t. the node level and node_index at the level */ + MuxNodeId node_id(const size_t& node_level, const size_t& node_index_at_level) const; + /* Get the input id of a given node */ + MuxInputId input_id(const MuxNodeId& node_id) const; + /* Identify if the node is an input of the MUX */ + bool is_node_input(const MuxNodeId& node_id) const; + /* Get the output id of a given node */ + MuxOutputId output_id(const MuxNodeId& node_id) const; + /* Identify if the node is an output of the MUX */ + bool is_node_output(const MuxNodeId& node_id) const; + /* Decode memory bits based on an input id and an output id + * This function will start from the input node + * and do a forward propagation until reaching the output node + */ + vtr::vector decode_memory_bits(const MuxInputId& input_id, + const MuxOutputId& output_id) const; + /* Find the input node that the memory bits will route an output node to + * This function backward propagate from the output node to an input node + * assuming the memory bits are applied + * Note: This function is mainly used for decoding LUT MUXes + */ + MuxInputId find_input_node_driven_by_output_node(const std::map& memory_bits, + const MuxOutputId& output_id) const; + private: /* Private mutators : basic operations */ + /* Add a unconfigured node to the MuxGraph */ + MuxNodeId add_node(const enum e_mux_graph_node_type& node_type); + /* Add a edge connecting two nodes */ + MuxEdgeId add_edge(const MuxNodeId& from_node, const MuxNodeId& to_node); + /* Add a memory bit to the MuxGraph */ + MuxMemId add_mem(); + /* Configure the level of a memory */ + void set_mem_level(const MuxMemId& mem, const size_t& level); + /* Link an edge to a mem */ + void set_edge_mem_id(const MuxEdgeId& edge, const MuxMemId& mem); + private: /* Private mutators : graph builders */ + void build_multilevel_mux_graph(const size_t& mux_size, + const size_t& num_levels, const size_t& num_inputs_per_branch, + const CircuitModelId& pgl_model) ; + /* Build the graph for a given one-level multiplexer implementation */ + void build_onelevel_mux_graph(const size_t& mux_size, + const CircuitModelId& pgl_model) ; + /* Build the graph for a given multiplexer model */ + void build_mux_graph(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const size_t& mux_size); + /* Convert some internal node to outputs according to fracturable LUT circuit design specifications */ + void add_fracturable_outputs(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model); + /* Build fast node lookup */ + void build_node_lookup(); + /* Build fast mem lookup */ + void build_mem_lookup(); + private: /* Private validators */ + /* valid ids */ + bool valid_node_id(const MuxNodeId& node) const; + bool valid_edge_id(const MuxEdgeId& edge) const; + bool valid_mem_id(const MuxMemId& mem) const; + bool valid_input_id(const MuxInputId& input_id) const; + bool valid_output_id(const MuxOutputId& output_id) const; + bool valid_level(const size_t& level) const; + /* validate/invalidate node lookup */ + bool valid_node_lookup() const; + void invalidate_node_lookup(); + void invalidate_mem_lookup(); + /* validate graph */ + bool valid_mux_graph() const; + private: /* Internal data */ + vtr::vector node_ids_; /* Unique ids for each node */ + vtr::vector node_types_; /* type of each node, input/output/internal */ + vtr::vector node_input_ids_; /* Unique ids for each node as an input of the MUX */ + vtr::vector node_output_ids_; /* Unique ids for each node as an input of the MUX */ + vtr::vector node_levels_; /* at which level, each node belongs to */ + vtr::vector node_ids_at_level_; /* the index at the level that each node belongs to */ + vtr::vector> node_in_edges_; /* ids of incoming edges to each node */ + vtr::vector> node_out_edges_; /* ids of outgoing edges from each node */ + + vtr::vector edge_ids_; /* Unique ids for each edge */ + vtr::vector> edge_src_nodes_; /* source nodes drive this edge */ + vtr::vector> edge_sink_nodes_; /* sink nodes this edge drives */ + vtr::vector edge_models_; /* type of each edge: tgate/pass-gate */ + vtr::vector edge_mem_ids_; /* ids of memory bit that control the edge */ + vtr::vector edge_inv_mem_; /* if the edge is controlled by an inverted output of a memory bit */ + + vtr::vector mem_ids_; /* ids of configuration memories */ + vtr::vector mem_levels_; /* ids of configuration memories */ + + /* fast look-up */ + typedef std::vector>> NodeLookup; + mutable NodeLookup node_lookup_; /* [num_levels][num_types][num_nodes_per_level] */ + typedef std::vector> MemLookup; + mutable MemLookup mem_lookup_; /* [num_levels][num_mems_per_level] */ +}; + +} /* End namespace openfpga*/ + +#endif diff --git a/openfpga/src/mux_lib/mux_graph_fwd.h b/openfpga/src/mux_lib/mux_graph_fwd.h new file mode 100644 index 000000000..75fd5b1e1 --- /dev/null +++ b/openfpga/src/mux_lib/mux_graph_fwd.h @@ -0,0 +1,33 @@ +/************************************************** + * This file includes only declarations for + * the data structures to describe multiplexer structures + * Please refer to mux_graph.h for more details + *************************************************/ +#ifndef MUX_GRAPH_FWD_H +#define MUX_GRAPH_FWD_H + +#include "vtr_strong_id.h" + +/* begin namespace openfpga */ +namespace openfpga { + + +/* Strong Ids for MUXes */ +struct mux_node_id_tag; +struct mux_edge_id_tag; +struct mux_mem_id_tag; +struct mux_input_id_tag; +struct mux_output_id_tag; + +typedef vtr::StrongId MuxNodeId; +typedef vtr::StrongId MuxEdgeId; +typedef vtr::StrongId MuxMemId; +typedef vtr::StrongId MuxInputId; +typedef vtr::StrongId MuxOutputId; + +class MuxGraph; + +} /* end namespace openfpga */ + + +#endif diff --git a/openfpga/src/mux_lib/mux_library.cpp b/openfpga/src/mux_lib/mux_library.cpp new file mode 100644 index 000000000..20e590fdf --- /dev/null +++ b/openfpga/src/mux_lib/mux_library.cpp @@ -0,0 +1,121 @@ +/************************************************** + * This file includes member functions for the + * data structures in mux_library.h + *************************************************/ + +#include "vtr_assert.h" + +#include "mux_library.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/************************************************** + * Member functions for the class MuxLibrary + *************************************************/ + +/************************************************** + * Public accessors: aggregates + *************************************************/ +MuxLibrary::mux_range MuxLibrary::muxes() const { + return vtr::make_range(mux_ids_.begin(), mux_ids_.end()); +} + +/************************************************** + * Public accessors: data query + *************************************************/ +/* Get a MUX graph (read-only) */ +MuxId MuxLibrary::mux_graph(const CircuitModelId& circuit_model, + const size_t& mux_size) const { + /* Make sure we have a valid mux look-up */ + VTR_ASSERT_SAFE(valid_mux_lookup()); + /* Validate circuit model id and mux_size */ + VTR_ASSERT_SAFE(valid_mux_size(circuit_model, mux_size)); + + return mux_lookup_[circuit_model][mux_size]; +} + +const MuxGraph& MuxLibrary::mux_graph(const MuxId& mux_id) const { + VTR_ASSERT_SAFE(valid_mux_id(mux_id)); + return mux_graphs_[mux_id]; +} + +/* Get a mux circuit model id */ +CircuitModelId MuxLibrary::mux_circuit_model(const MuxId& mux_id) const { + VTR_ASSERT_SAFE(valid_mux_id(mux_id)); + return mux_circuit_models_[mux_id]; +} + +/* Find the maximum mux size among the mux graphs */ +size_t MuxLibrary::max_mux_size() const { + /* Iterate over all the mux graphs and find their sizes */ + size_t max_mux_size = 0; + for (const auto& mux : mux_ids_) { + max_mux_size = std::max(max_mux_size, mux_graphs_[mux].num_inputs()); + } + return max_mux_size; +} + +/************************************************** + * Private mutators: + *************************************************/ +/* Add a mux to the library */ +void MuxLibrary::add_mux(const CircuitLibrary& circuit_lib, const CircuitModelId& circuit_model, const size_t& mux_size) { + /* First, check if there is already an existing graph */ + if (valid_mux_size(circuit_model, mux_size)) { + return; + } + + /* create a new id for the mux */ + MuxId mux = MuxId(mux_ids_.size()); + /* Push to the node list */ + mux_ids_.push_back(mux); + /* Add a mux graph */ + mux_graphs_.push_back(MuxGraph(circuit_lib, circuit_model, mux_size)); + /* Recorde mux cirucit model id */ + mux_circuit_models_.push_back(circuit_model); + + /* update mux_lookup*/ + mux_lookup_[circuit_model][mux_size] = mux; +} + +/************************************************** + * Private accessors: validator and invalidators + *************************************************/ +bool MuxLibrary::valid_mux_id(const MuxId& mux) const { + return size_t(mux) < mux_ids_.size() && mux_ids_[mux] == mux; +} + +bool MuxLibrary::valid_mux_lookup() const { + return mux_lookup_.empty(); +} + +bool MuxLibrary::valid_mux_circuit_model_id(const CircuitModelId& circuit_model) const { + MuxLookup::iterator it = mux_lookup_.find(circuit_model); + return (it != mux_lookup_.end()); +} + +bool MuxLibrary::valid_mux_size(const CircuitModelId& circuit_model, const size_t& mux_size) const { + if (false == valid_mux_circuit_model_id(circuit_model)) { + return false; + } + std::map::iterator it = mux_lookup_[circuit_model].find(mux_size); + return (it != mux_lookup_[circuit_model].end()); +} + +/************************************************** + * Private mutators: validator and invalidators + *************************************************/ + +/* Build fast node lookup */ +void MuxLibrary::build_mux_lookup() { + /* Invalidate the mux lookup if necessary */ + invalidate_mux_lookup(); +} + +/* Invalidate (empty) the mux fast lookup*/ +void MuxLibrary::invalidate_mux_lookup() { + mux_lookup_.clear(); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/mux_lib/mux_library.h b/openfpga/src/mux_lib/mux_library.h new file mode 100644 index 000000000..03f406738 --- /dev/null +++ b/openfpga/src/mux_lib/mux_library.h @@ -0,0 +1,64 @@ +/************************************************** + * This file includes a data structure to describe + * the multiplexer implementations in FPGA architectures + * MuxLibrary is a collection of multiplexers + * with various circuit-level description (related to + * the information available in CircuitLibrary + * and the input size of multiplexers) + *************************************************/ + +#ifndef MUX_LIBRARY_H +#define MUX_LIBRARY_H + +#include +#include "mux_graph.h" +#include "mux_library_fwd.h" + +/* begin namespace openfpga */ +namespace openfpga { + +class MuxLibrary { + public: /* Types and ranges */ + typedef vtr::vector::const_iterator mux_iterator; + + typedef vtr::Range mux_range; + public: /* Public accessors: Aggregates */ + mux_range muxes() const; + public: /* Public accessors */ + /* Get a MUX graph (read-only) */ + MuxId mux_graph(const CircuitModelId& circuit_model, const size_t& mux_size) const; + const MuxGraph& mux_graph(const MuxId& mux_id) const; + /* Get a mux circuit model id */ + CircuitModelId mux_circuit_model(const MuxId& mux_id) const; + /* Find the mux sizes */ + size_t max_mux_size() const; + public: /* Public mutators */ + /* Add a mux to the library */ + void add_mux(const CircuitLibrary& circuit_lib, const CircuitModelId& circuit_model, const size_t& mux_size); + public: /* Public validators */ + bool valid_mux_id(const MuxId& mux) const; + private: /* Private accessors */ + bool valid_mux_lookup() const; + bool valid_mux_circuit_model_id(const CircuitModelId& circuit_model) const; + bool valid_mux_size(const CircuitModelId& circuit_model, const size_t& mux_size) const; + private: /* Private mutators: mux_lookup */ + void build_mux_lookup(); + /* Invalidate (empty) the mux fast lookup*/ + void invalidate_mux_lookup(); + private: /* Internal data */ + /* MUX graph-based desription */ + vtr::vector mux_ids_; /* Unique identifier for each mux graph */ + vtr::vector mux_graphs_; /* Graphs describing MUX internal structures */ + vtr::vector mux_circuit_models_; /* circuit model id in circuit library */ + + /* Local encoder description */ + //vtr::vector mux_local_encoders_; /* Graphs describing MUX internal structures */ + + /* a fast look-up to search mux_graphs with given circuit model and mux size */ + typedef std::map> MuxLookup; + mutable MuxLookup mux_lookup_; +}; + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/mux_lib/mux_library_fwd.h b/openfpga/src/mux_lib/mux_library_fwd.h new file mode 100644 index 000000000..f9a8697d6 --- /dev/null +++ b/openfpga/src/mux_lib/mux_library_fwd.h @@ -0,0 +1,25 @@ +/************************************************** + * This file includes only declarations for + * the data structures to describe multiplexer structures + * Please refer to mux_library.h for more details + *************************************************/ +#ifndef MUX_LIBRARY_FWD_H +#define MUX_LIBRARY_FWD_H + +#include "vtr_strong_id.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/* Strong Ids for MUXes */ +struct mux_id_tag; +struct mux_local_decoder_id_tag; + +typedef vtr::StrongId MuxId; +typedef vtr::StrongId MuxLocalDecoderId; + +class MuxLibrary; + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/utils/decoder_library_utils.cpp b/openfpga/src/utils/decoder_library_utils.cpp new file mode 100644 index 000000000..a41e439ef --- /dev/null +++ b/openfpga/src/utils/decoder_library_utils.cpp @@ -0,0 +1,60 @@ +/*************************************************************************************** + * This file includes most utilized functions for the DecoderLibrary data structure + ***************************************************************************************/ +#include + +#include "vtr_assert.h" + +#include "decoder_library_utils.h" + +/* Begin namespace openfpga */ +namespace openfpga { + +/*************************************************************************************** + * NOTE: This function is mainly designed for local decoders inside multiplexers + * Find the size of address lines for a decoder with a given data output size + * Addr lines + * | | ... | + * v v v + * +-----------+ + * / Local \ + * / Decoder \ + * +-----------------+ + * | | | ... | | | + * v v v v v v + * Data outputs + * + * The outputs are assumes to be one-hot codes (at most only one '1' exist) + * Considering this fact, there are only num_of_outputs + 1 conditions to be encoded. + * Therefore, the number of inputs is ceil(log(num_of_outputs+1)/log(2)) + * We plus 1, which is all-zero condition for outputs + ***************************************************************************************/ +size_t find_mux_local_decoder_addr_size(const size_t& data_size) { + /* if data size is 1, it is an corner case for the decoder (addr = 1) */ + if (1 == data_size) { + return 1; + } + VTR_ASSERT (2 <= data_size); + return ceil(log(data_size) / log(2)); +} + +/*************************************************************************************** + * Try to find if the decoder already exists in the library, + * If there is no such decoder, add it to the library + ***************************************************************************************/ +DecoderId add_mux_local_decoder_to_library(DecoderLibrary& decoder_lib, + const size_t data_size) { + size_t addr_size = find_mux_local_decoder_addr_size(data_size); + + DecoderId decoder_id = decoder_lib.find_decoder(addr_size, data_size, false, false, true); + + if (DecoderId::INVALID() == decoder_id) { + /* Add the decoder */ + return decoder_lib.add_decoder(addr_size, data_size, false, false, true); + } + + /* There is already a decoder in the library, return the decoder id */ + return decoder_id; +} + +} /* End namespace openfpga*/ diff --git a/openfpga/src/utils/decoder_library_utils.h b/openfpga/src/utils/decoder_library_utils.h new file mode 100644 index 000000000..d55a0750a --- /dev/null +++ b/openfpga/src/utils/decoder_library_utils.h @@ -0,0 +1,21 @@ +/*************************************************************************************** + * Header file for most utilized functions for the DecoderLibrary data structure + ***************************************************************************************/ +#ifndef DECODER_LIBRARY_UTILS_H +#define DECODER_LIBRARY_UTILS_H + +#include "decoder_library.h" + +/* Begin namespace openfpga */ +namespace openfpga { + +bool need_mux_local_decoder(const size_t& data_size); + +size_t find_mux_local_decoder_addr_size(const size_t& data_size); + +DecoderId add_mux_local_decoder_to_library(DecoderLibrary& decoder_lib, + const size_t data_size); + +} /* End namespace openfpga*/ + +#endif diff --git a/openfpga/src/utils/mux_utils.cpp b/openfpga/src/utils/mux_utils.cpp new file mode 100644 index 000000000..b921f0589 --- /dev/null +++ b/openfpga/src/utils/mux_utils.cpp @@ -0,0 +1,458 @@ +/************************************************** + * This file includes a series of most utilized functions + * that are used to implement a multiplexer + *************************************************/ +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_log.h" +#include "vtr_assert.h" + +/* Headers from readarchopenfpga library */ +#include "circuit_types.h" + +#include "decoder_library_utils.h" +#include "mux_utils.h" + +/* Begin namespace openfpga */ +namespace openfpga { + +/* Validate the number of inputs for a multiplexer implementation, + * the minimum supported size is 2 + * otherwise, there is no need for a MUX + */ +bool valid_mux_implementation_num_inputs(const size_t& mux_size) { + return (2 <= mux_size); +} + +/************************************************** + * Find the actual number of datapath inputs for a multiplexer implementation + * 1. if there are no requirements on constant inputs, mux_size is the actual one + * 2. if there exist constant inputs, mux_size should minus 1 + * This function is mainly used to recover the number of datapath inputs + * for MUXGraphs which is a generic representation without labelling datapath inputs + *************************************************/ +size_t find_mux_num_datapath_inputs(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const size_t& mux_size) { + /* Should be either MUX or LUT + * LUTs do have an tree-like MUX, but there is no need for a constant input! + */ + VTR_ASSERT ((CIRCUIT_MODEL_MUX == circuit_lib.model_type(circuit_model)) + || (CIRCUIT_MODEL_LUT == circuit_lib.model_type(circuit_model)) ); + + if (CIRCUIT_MODEL_LUT == circuit_lib.model_type(circuit_model)) { + return mux_size; + } + + if (true == circuit_lib.mux_add_const_input(circuit_model)) { + return mux_size - 1; + } + return mux_size; +} + +/************************************************** + * Find the actual number of inputs for a multiplexer implementation + * 1. if there are no requirements on constant inputs, mux_size is the actual one + * 2. if there exist constant inputs, mux_size should plus 1 + *************************************************/ +size_t find_mux_implementation_num_inputs(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const size_t& mux_size) { + /* Should be either MUX or LUT + * LUTs do have an tree-like MUX, but there is no need for a constant input! + */ + VTR_ASSERT ((CIRCUIT_MODEL_MUX == circuit_lib.model_type(circuit_model)) + || (CIRCUIT_MODEL_LUT == circuit_lib.model_type(circuit_model)) ); + + if (CIRCUIT_MODEL_LUT == circuit_lib.model_type(circuit_model)) { + return mux_size; + } + + if (true == circuit_lib.mux_add_const_input(circuit_model)) { + return mux_size + 1; + } + return mux_size; +} + +/************************************************** + * Find the structure for a multiplexer implementation + * 1. In most cases, the structure should follow the + * mux_structure defined by users in the CircuitLibrary + * 2. However, a special case may apply when mux_size is 2 + * In such case, we will force a TREE structure + * regardless of users' specification as this is the + * most efficient structure + *************************************************/ +enum e_circuit_model_structure find_mux_implementation_structure(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const size_t& mux_size) { + /* Ensure the mux size is valid ! */ + VTR_ASSERT(valid_mux_implementation_num_inputs(mux_size)); + + /* Branch on the mux sizes */ + if (2 == mux_size) { + /* Tree-like is the best structure of CMOS MUX2 */ + if (CIRCUIT_MODEL_DESIGN_CMOS == circuit_lib.design_tech_type(circuit_model)) { + return CIRCUIT_MODEL_STRUCTURE_TREE; + } + VTR_ASSERT_SAFE(CIRCUIT_MODEL_DESIGN_RRAM == circuit_lib.design_tech_type(circuit_model)); + /* One-level is the best structure of RRAM MUX2 */ + return CIRCUIT_MODEL_STRUCTURE_ONELEVEL; + } + + return circuit_lib.mux_structure(circuit_model); +} + +/************************************************** + * Find the number of levels for a tree-like multiplexer implementation + *************************************************/ +size_t find_treelike_mux_num_levels(const size_t& mux_size) { + /* Do log2(mux_size), have a basic number */ + size_t level = (size_t)(log((double)mux_size)/log(2.)); + /* Fix the error, i.e. mux_size=5, level = 2, we have to complete */ + while (mux_size > pow(2.,(double)level)) { + level++; + } + + return level; +} + +/************************************************** + * Find the number of inputs for majority of branches + * in a multi-level multiplexer implementation + *************************************************/ +size_t find_multilevel_mux_branch_num_inputs(const size_t& mux_size, + const size_t& mux_level) { + /* Special Case: mux_size = 2 */ + if (2 == mux_size) { + return mux_size; + } + + if (1 == mux_level) { + return mux_size; + } + + if (2 == mux_level) { + size_t num_input_per_unit = (size_t)sqrt(mux_size); + while ( num_input_per_unit * num_input_per_unit < mux_size) { + num_input_per_unit++; + } + return num_input_per_unit; + } + + VTR_ASSERT_SAFE(2 < mux_level); + + size_t num_input_per_unit = 2; + while (pow((double)num_input_per_unit, (double)mux_level) < mux_size) { + num_input_per_unit++; + } + + if (!valid_mux_implementation_num_inputs(num_input_per_unit)) { + VTR_LOG_ERROR("Number of inputs of each basis should be at least 2!\n"); + exit(1); + } + + return num_input_per_unit; +} + +/************************************************** + * Build a location map for intermediate buffers + * that may appear at the multiplexing structure of a LUT + * Here is a tricky thing: + * By default, the first and last stage should not exist any intermediate buffers + * For example: + * There are 5 stages in a 4-stage multiplexer is available for buffering + * but only 3 stages [1,2,3] are intermedate buffers + * and these are users' specification + * + * +-------+ +-------+ +-------+ +-------+ + * location | stage | location | stage | location | stage | location | stage | location + * [0] | [0] | [1] | [1] | [2] | [2] | [3] | [3] | [5] + * +-------+ +-------+ +-------+ +-------+ + * + * We will check if the length of location map matches the number of + * multiplexer levels. And then complete a location map + * for the given multiplexers + *************************************************/ +std::vector build_mux_intermediate_buffer_location_map(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const size_t& num_mux_levels) { + /* Deposite a default location map */ + std::vector location_map(num_mux_levels, false); + std::string location_map_str; + + /* ONLY for LUTs: intermediate buffers may exist if specified */ + if (CIRCUIT_MODEL_LUT != circuit_lib.model_type(circuit_model)) { + return location_map; + } + + /* Get location map when the flag of intermediate buffer is on */ + if (true == circuit_lib.is_lut_intermediate_buffered(circuit_model)) { + location_map_str = circuit_lib.lut_intermediate_buffer_location_map(circuit_model); + } + + /* If no location map is specified, we can return here */ + if (location_map_str.empty()) { + return location_map; + } + + /* Check if the user-defined location map matches the number of mux levels*/ + VTR_ASSERT(num_mux_levels - 2 == location_map_str.length()); + + /* Apply the location_map string to the intermediate stages of multiplexers */ + for (size_t i = 0; i < location_map_str.length(); ++i) { + /* '1' indicates that an intermediate buffer is needed at the location */ + if ('1' == location_map_str[i]) { + location_map[i + 1] = true; + } + } + + return location_map; +} + +/************************************************** + * Find the number of reserved configuration bits for a multiplexer + * The reserved configuration bits is only used by ReRAM-based multiplexers + * It is actually the shared BL/WLs among ReRAMs + *************************************************/ +size_t find_mux_num_reserved_config_bits(const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const MuxGraph& mux_graph) { + if (CIRCUIT_MODEL_DESIGN_RRAM != circuit_lib.design_tech_type(mux_model)) { + return 0; + } + + std::vector mux_branch_sizes = mux_graph.branch_sizes(); + /* For tree-like multiplexers: they have two shared configuration bits */ + if ( (1 == mux_branch_sizes.size()) + && (2 == mux_branch_sizes[0]) ) { + return mux_branch_sizes[0]; + } + /* One-level multiplexer */ + if ( 1 == mux_graph.num_levels() ) { + return mux_graph.num_inputs(); + } + /* Multi-level multiplexers: TODO: This should be better tested and clarified + * Now the multi-level multiplexers are treated as cascaded one-level multiplexers + * Use the maximum branch sizes and multiply it by the number of levels + */ + std::vector::iterator max_mux_branch_size = std::max_element(mux_branch_sizes.begin(), mux_branch_sizes.end()); + return mux_graph.num_levels() * (*max_mux_branch_size); +} + +/************************************************** + * Find the number of configuration bits for a CMOS multiplexer + * In general, the number of configuration bits is + * the number of memory bits for a mux_graph + * However, when local decoders are used, + * the number of configuration bits are reduced to log2(X) + *************************************************/ +static +size_t find_cmos_mux_num_config_bits(const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const MuxGraph& mux_graph, + const e_config_protocol_type& sram_orgz_type) { + size_t num_config_bits = 0; + + switch (sram_orgz_type) { + case CONFIG_MEM_MEMORY_BANK: + case CONFIG_MEM_SCAN_CHAIN: + case CONFIG_MEM_STANDALONE: + num_config_bits = mux_graph.num_memory_bits(); + break; + default: + VTR_LOG_ERROR("Invalid type of SRAM organization!\n"); + exit(1); + } + + if (false == circuit_lib.mux_use_local_encoder(mux_model)) { + return num_config_bits; + } + + num_config_bits = 0; + /* Multiplexer local encoders are applied to memory bits at each stage */ + for (const auto& lvl : mux_graph.levels()) { + num_config_bits += find_mux_local_decoder_addr_size(mux_graph.num_memory_bits_at_level(lvl)); + } + + return num_config_bits; +} + +/************************************************** + * Find the number of configuration bits for a RRAM multiplexer + * In general, the number of configuration bits is + * the number of levels for a mux_graph + * This is due to only the last BL/WL of the multiplexer is + * independent from each other + * However, when local decoders are used, + * the number of configuration bits should be consider all the + * shared(reserved) configuration bits and independent bits + *************************************************/ +static +size_t find_rram_mux_num_config_bits(const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const MuxGraph& mux_graph, + const e_config_protocol_type& sram_orgz_type) { + size_t num_config_bits = 0; + switch (sram_orgz_type) { + case CONFIG_MEM_MEMORY_BANK: + /* In memory bank, by intensively share the Bit/Word Lines, + * we only need 1 additional BL and WL for each MUX level. + */ + num_config_bits = mux_graph.num_levels(); + break; + case CONFIG_MEM_SCAN_CHAIN: + case CONFIG_MEM_STANDALONE: + /* Currently we DO NOT SUPPORT THESE, given an invalid number */ + num_config_bits = size_t(-1); + break; + default: + VTR_LOG_ERROR("Invalid type of SRAM organization!\n"); + exit(1); + } + + if (true == circuit_lib.mux_use_local_encoder(mux_model)) { + /* TODO: this is a to-do work for ReRAM-based multiplexers and FPGAs + * The number of states of a local decoder only depends on how many + * memory bits that the multiplexer will have + * This may NOT be correct!!! + */ + return find_mux_local_decoder_addr_size(mux_graph.num_memory_bits()); + } + + return num_config_bits; +} + +/************************************************** + * Find the number of configuration bits for + * a routing multiplexer + * Two cases are considered here. + * They are placed in different branches (sub-functions) + * in order to be easy in extending to new technology! + *************************************************/ +size_t find_mux_num_config_bits(const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const MuxGraph& mux_graph, + const e_config_protocol_type& sram_orgz_type) { + size_t num_config_bits = size_t(-1); + + switch (circuit_lib.design_tech_type(mux_model)) { + case CIRCUIT_MODEL_DESIGN_CMOS: + num_config_bits = find_cmos_mux_num_config_bits(circuit_lib, mux_model, mux_graph, sram_orgz_type); + break; + case CIRCUIT_MODEL_DESIGN_RRAM: + num_config_bits = find_rram_mux_num_config_bits(circuit_lib, mux_model, mux_graph, sram_orgz_type); + break; + default: + VTR_LOG_ERROR("Invalid design_technology of MUX(name: %s)\n", + circuit_lib.model_name(mux_model).c_str()); + exit(1); + } + + return num_config_bits; +} + +/************************************************** + * Find the number of shared configuration bits for a CMOS multiplexer + * Currently, all the supported CMOS multiplexers + * do NOT require any shared configuration bits + *************************************************/ +static +size_t find_cmos_mux_num_shared_config_bits(const e_config_protocol_type& sram_orgz_type) { + size_t num_shared_config_bits = 0; + + switch (sram_orgz_type) { + case CONFIG_MEM_MEMORY_BANK: + case CONFIG_MEM_SCAN_CHAIN: + case CONFIG_MEM_STANDALONE: + num_shared_config_bits = 0; + break; + default: + VTR_LOG_ERROR("Invalid type of SRAM organization!\n"); + exit(1); + } + + return num_shared_config_bits; +} + +/************************************************** + * Find the number of shared configuration bits for a ReRAM multiplexer + *************************************************/ +static +size_t find_rram_mux_num_shared_config_bits(const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const MuxGraph& mux_graph, + const e_config_protocol_type& sram_orgz_type) { + size_t num_shared_config_bits = 0; + switch (sram_orgz_type) { + case CONFIG_MEM_MEMORY_BANK: { + /* In memory bank, the number of shared configuration bits is + * the sum of largest branch size at each level + */ + for (auto lvl : mux_graph.node_levels()) { + /* Find the maximum branch size: + * Note that branch_sizes() returns a sorted vector + * The last one is the maximum + */ + num_shared_config_bits += mux_graph.branch_sizes(lvl).back(); + } + break; + } + case CONFIG_MEM_SCAN_CHAIN: + case CONFIG_MEM_STANDALONE: + /* Currently we DO NOT SUPPORT THESE, given an invalid number */ + num_shared_config_bits = size_t(-1); + break; + default: + VTR_LOG_ERROR("Invalid type of SRAM organization!\n"); + exit(1); + } + + if (true == circuit_lib.mux_use_local_encoder(mux_model)) { + /* TODO: this is a to-do work for ReRAM-based multiplexers and FPGAs + * The number of states of a local decoder only depends on how many + * memory bits that the multiplexer will have + * This may NOT be correct!!! + * If local encoders are introduced, zero shared configuration bits are required + */ + return 0; + } + + return num_shared_config_bits; +} + +/************************************************** + * Find the number of shared configuration bits for + * a routing multiplexer + * Two cases are considered here. + * They are placed in different branches (sub-functions) + * in order to be easy in extending to new technology! + * + * Note: currently, shared configuration bits are demanded + * by ReRAM-based multiplexers only + *************************************************/ +size_t find_mux_num_shared_config_bits(const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const MuxGraph& mux_graph, + const e_config_protocol_type& sram_orgz_type) { + size_t num_shared_config_bits = size_t(-1); + + switch (circuit_lib.design_tech_type(mux_model)) { + case CIRCUIT_MODEL_DESIGN_CMOS: + num_shared_config_bits = find_cmos_mux_num_shared_config_bits(sram_orgz_type); + break; + case CIRCUIT_MODEL_DESIGN_RRAM: + num_shared_config_bits = find_rram_mux_num_shared_config_bits(circuit_lib, mux_model, mux_graph, sram_orgz_type); + break; + default: + VTR_LOG_ERROR("Invalid design_technology of MUX(name: %s)\n", + circuit_lib.model_name(mux_model).c_str()); + exit(1); + } + + return num_shared_config_bits; +} + +} /* End namespace openfpga*/ diff --git a/openfpga/src/utils/mux_utils.h b/openfpga/src/utils/mux_utils.h new file mode 100644 index 000000000..cf2ce95ad --- /dev/null +++ b/openfpga/src/utils/mux_utils.h @@ -0,0 +1,59 @@ +#ifndef MUX_UTILS_H +#define MUX_UTILS_H + +/******************************************************************** + * Include header files required by the data structure definition + *******************************************************************/ + +#include + +#include "circuit_library.h" +#include "mux_library.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* Begin namespace openfpga */ +namespace openfpga { + +bool valid_mux_implementation_num_inputs(const size_t& mux_size); + +size_t find_mux_num_datapath_inputs(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const size_t& mux_size); + +size_t find_mux_implementation_num_inputs(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const size_t& mux_size); + +enum e_circuit_model_structure find_mux_implementation_structure(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const size_t& mux_size); + +size_t find_treelike_mux_num_levels(const size_t& mux_size); + +size_t find_multilevel_mux_branch_num_inputs(const size_t& mux_size, + const size_t& mux_level); + +std::vector build_mux_intermediate_buffer_location_map(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const size_t& num_mux_levels); + +size_t find_mux_num_reserved_config_bits(const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const MuxGraph& mux_graph); + +size_t find_mux_num_config_bits(const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const MuxGraph& mux_graph, + const e_config_protocol_type& sram_orgz_type); + +size_t find_mux_num_shared_config_bits(const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const MuxGraph& mux_graph, + const e_config_protocol_type& sram_orgz_type); + +} /* End namespace openfpga*/ + +#endif From a31d6c6d1e05597574c93defcd379c285ccb8f52 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 12 Feb 2020 09:52:18 -0700 Subject: [PATCH 116/645] rename pb_type annotation to device annotation --- openfpga/src/annotation/annotate_pb_graph.cpp | 98 +++++------ openfpga/src/annotation/annotate_pb_graph.h | 6 +- openfpga/src/annotation/annotate_pb_types.cpp | 162 +++++++++--------- openfpga/src/annotation/annotate_pb_types.h | 4 +- .../annotation/check_pb_graph_annotation.cpp | 20 +-- .../annotation/check_pb_graph_annotation.h | 4 +- .../annotation/check_pb_type_annotation.cpp | 70 ++++---- .../src/annotation/check_pb_type_annotation.h | 10 +- ...notation.cpp => vpr_device_annotation.cpp} | 72 ++++---- ...e_annotation.h => vpr_device_annotation.h} | 8 +- openfpga/src/base/openfpga_context.h | 8 +- openfpga/src/base/openfpga_link_arch.cpp | 6 +- 12 files changed, 234 insertions(+), 234 deletions(-) rename openfpga/src/annotation/{vpr_pb_type_annotation.cpp => vpr_device_annotation.cpp} (87%) rename openfpga/src/annotation/{vpr_pb_type_annotation.h => vpr_device_annotation.h} (98%) diff --git a/openfpga/src/annotation/annotate_pb_graph.cpp b/openfpga/src/annotation/annotate_pb_graph.cpp index 2043c2098..bf93cf28e 100644 --- a/openfpga/src/annotation/annotate_pb_graph.cpp +++ b/openfpga/src/annotation/annotate_pb_graph.cpp @@ -28,7 +28,7 @@ namespace openfpga { *******************************************************************/ static void rec_build_vpr_pb_graph_interconnect_physical_type_annotation(t_pb_graph_node* pb_graph_node, - VprPbTypeAnnotation& vpr_pb_type_annotation, + VprDeviceAnnotation& vpr_device_annotation, const bool& verbose_output) { /* Skip the root node because we start from the inputs of child pb_graph node * @@ -45,7 +45,7 @@ void rec_build_vpr_pb_graph_interconnect_physical_type_annotation(t_pb_graph_nod */ if (false == pb_graph_node->is_root()) { /* We only care the physical modes! But we have to find it through the parent node */ - t_mode* child_physical_mode = vpr_pb_type_annotation.physical_mode(pb_graph_node->parent_pb_graph_node->pb_type); + t_mode* child_physical_mode = vpr_device_annotation.physical_mode(pb_graph_node->parent_pb_graph_node->pb_type); VTR_ASSERT(nullptr != child_physical_mode); std::map interc_num_inputs; @@ -83,14 +83,14 @@ void rec_build_vpr_pb_graph_interconnect_physical_type_annotation(t_pb_graph_nod } e_interconnect interc_physical_type = pb_interconnect_physical_type(interc, interc_num_inputs[interc]); - if (interc_physical_type == vpr_pb_type_annotation.interconnect_physical_type(interc)) { + if (interc_physical_type == vpr_device_annotation.interconnect_physical_type(interc)) { /* Skip annotation if we have already done! */ continue; } VTR_LOGV(verbose_output, "Infer physical type '%s' of interconnect '%s' (was '%s')\n", INTERCONNECT_TYPE_STRING[interc_physical_type], interc->name, INTERCONNECT_TYPE_STRING[interc->type]); - vpr_pb_type_annotation.add_interconnect_physical_type(interc, interc_physical_type); + vpr_device_annotation.add_interconnect_physical_type(interc, interc_physical_type); } } @@ -100,13 +100,13 @@ void rec_build_vpr_pb_graph_interconnect_physical_type_annotation(t_pb_graph_nod } /* Recursively visit all the child pb_graph_nodes */ - t_mode* physical_mode = vpr_pb_type_annotation.physical_mode(pb_graph_node->pb_type); + t_mode* physical_mode = vpr_device_annotation.physical_mode(pb_graph_node->pb_type); VTR_ASSERT(nullptr != physical_mode); for (int ipb = 0; ipb < physical_mode->num_pb_type_children; ++ipb) { /* Each child may exist multiple times in the hierarchy*/ for (int jpb = 0; jpb < physical_mode->pb_type_children[ipb].num_pb; ++jpb) { rec_build_vpr_pb_graph_interconnect_physical_type_annotation(&(pb_graph_node->child_pb_graph_nodes[physical_mode->index][ipb][jpb]), - vpr_pb_type_annotation, + vpr_device_annotation, verbose_output); } } @@ -122,14 +122,14 @@ void rec_build_vpr_pb_graph_interconnect_physical_type_annotation(t_pb_graph_nod * build_vpr_physical_pb_mode_implicit_annotation() *******************************************************************/ void annotate_pb_graph_interconnect_physical_type(const DeviceContext& vpr_device_ctx, - VprPbTypeAnnotation& vpr_pb_type_annotation, + VprDeviceAnnotation& vpr_device_annotation, const bool& verbose_output) { for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { /* By pass nullptr for pb_graph head */ if (nullptr == lb_type.pb_graph_head) { continue; } - rec_build_vpr_pb_graph_interconnect_physical_type_annotation(lb_type.pb_graph_head, vpr_pb_type_annotation, verbose_output); + rec_build_vpr_pb_graph_interconnect_physical_type_annotation(lb_type.pb_graph_head, vpr_device_annotation, verbose_output); } } @@ -143,7 +143,7 @@ void annotate_pb_graph_interconnect_physical_type(const DeviceContext& vpr_devic *******************************************************************/ static void rec_build_vpr_primitive_pb_graph_node_unique_index(t_pb_graph_node* pb_graph_node, - VprPbTypeAnnotation& vpr_pb_type_annotation) { + VprDeviceAnnotation& vpr_device_annotation) { /* Go recursive first until we touch the primitive node */ if (false == is_primitive_pb_type(pb_graph_node->pb_type)) { for (int imode = 0; imode < pb_graph_node->pb_type->num_modes; ++imode) { @@ -151,7 +151,7 @@ void rec_build_vpr_primitive_pb_graph_node_unique_index(t_pb_graph_node* pb_grap /* Each child may exist multiple times in the hierarchy*/ for (int jpb = 0; jpb < pb_graph_node->pb_type->modes[imode].pb_type_children[ipb].num_pb; ++jpb) { rec_build_vpr_primitive_pb_graph_node_unique_index(&(pb_graph_node->child_pb_graph_nodes[imode][ipb][jpb]), - vpr_pb_type_annotation); + vpr_device_annotation); } } } @@ -159,7 +159,7 @@ void rec_build_vpr_primitive_pb_graph_node_unique_index(t_pb_graph_node* pb_grap } /* Give a unique index to the pb_graph_node */ - vpr_pb_type_annotation.add_pb_graph_node_unique_index(pb_graph_node); + vpr_device_annotation.add_pb_graph_node_unique_index(pb_graph_node); } /******************************************************************** @@ -178,13 +178,13 @@ void rec_build_vpr_primitive_pb_graph_node_unique_index(t_pb_graph_node* pb_grap *******************************************************************/ static void annotate_primitive_pb_graph_node_unique_index(const DeviceContext& vpr_device_ctx, - VprPbTypeAnnotation& vpr_pb_type_annotation) { + VprDeviceAnnotation& vpr_device_annotation) { for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { /* By pass nullptr for pb_graph head */ if (nullptr == lb_type.pb_graph_head) { continue; } - rec_build_vpr_primitive_pb_graph_node_unique_index(lb_type.pb_graph_head, vpr_pb_type_annotation); + rec_build_vpr_primitive_pb_graph_node_unique_index(lb_type.pb_graph_head, vpr_device_annotation); } } @@ -196,9 +196,9 @@ void annotate_primitive_pb_graph_node_unique_index(const DeviceContext& vpr_devi static bool try_match_pb_graph_pin(t_pb_graph_pin* operating_pb_graph_pin, t_pb_graph_pin* physical_pb_graph_pin, - const VprPbTypeAnnotation& vpr_pb_type_annotation) { + const VprDeviceAnnotation& vpr_device_annotation) { /* If the parent ports of the two pins are not paired, fail */ - if (physical_pb_graph_pin->port != vpr_pb_type_annotation.physical_pb_port(operating_pb_graph_pin->port)) { + if (physical_pb_graph_pin->port != vpr_device_annotation.physical_pb_port(operating_pb_graph_pin->port)) { return false; } /* Check the pin number of physical pb_graph_pin matches the pin number of @@ -217,8 +217,8 @@ bool try_match_pb_graph_pin(t_pb_graph_pin* operating_pb_graph_pin, * by the pin rotate offset value * The accumulated offset will be reset to 0 when it exceeds the msb() of the physical port */ - int acc_offset = vpr_pb_type_annotation.physical_pb_pin_offset(operating_pb_graph_pin->port); - const BasicPort& physical_port_range = vpr_pb_type_annotation.physical_pb_port_range(operating_pb_graph_pin->port); + int acc_offset = vpr_device_annotation.physical_pb_pin_offset(operating_pb_graph_pin->port); + const BasicPort& physical_port_range = vpr_device_annotation.physical_pb_port_range(operating_pb_graph_pin->port); if (physical_pb_graph_pin->pin_number != operating_pb_graph_pin->pin_number + (int)physical_port_range.get_lsb() + acc_offset) { @@ -250,12 +250,12 @@ void print_success_bind_pb_graph_pin(t_pb_graph_pin* operating_pb_graph_pin, /******************************************************************** * Bind a pb_graph_pin from an operating pb_graph_node to * a pb_graph_pin from a physical pb_graph_node - * - the name matching rules are already defined in the vpr_pb_type_annotation + * - the name matching rules are already defined in the vpr_device_annotation *******************************************************************/ static void annotate_physical_pb_graph_pin(t_pb_graph_pin* operating_pb_graph_pin, t_pb_graph_node* physical_pb_graph_node, - VprPbTypeAnnotation& vpr_pb_type_annotation, + VprDeviceAnnotation& vpr_device_annotation, const bool& verbose_output) { /* Iterate over every port and pin of the operating pb_graph_node * and find the physical pins @@ -264,15 +264,15 @@ void annotate_physical_pb_graph_pin(t_pb_graph_pin* operating_pb_graph_pin, for (int ipin = 0; ipin < physical_pb_graph_node->num_input_pins[iport]; ++ipin) { if (false == try_match_pb_graph_pin(operating_pb_graph_pin, &(physical_pb_graph_node->input_pins[iport][ipin]), - vpr_pb_type_annotation)) { + vpr_device_annotation)) { continue; } /* Reach here, it means the pins are matched by the annotation requirements * We can pair the pin and return */ - vpr_pb_type_annotation.add_physical_pb_graph_pin(operating_pb_graph_pin, &(physical_pb_graph_node->input_pins[iport][ipin])); + vpr_device_annotation.add_physical_pb_graph_pin(operating_pb_graph_pin, &(physical_pb_graph_node->input_pins[iport][ipin])); if (true == verbose_output) { - print_success_bind_pb_graph_pin(operating_pb_graph_pin, vpr_pb_type_annotation.physical_pb_graph_pin(operating_pb_graph_pin)); + print_success_bind_pb_graph_pin(operating_pb_graph_pin, vpr_device_annotation.physical_pb_graph_pin(operating_pb_graph_pin)); } return; } @@ -282,15 +282,15 @@ void annotate_physical_pb_graph_pin(t_pb_graph_pin* operating_pb_graph_pin, for (int ipin = 0; ipin < physical_pb_graph_node->num_output_pins[iport]; ++ipin) { if (false == try_match_pb_graph_pin(operating_pb_graph_pin, &(physical_pb_graph_node->output_pins[iport][ipin]), - vpr_pb_type_annotation)) { + vpr_device_annotation)) { continue; } /* Reach here, it means the pins are matched by the annotation requirements * We can pair the pin and return */ - vpr_pb_type_annotation.add_physical_pb_graph_pin(operating_pb_graph_pin, &(physical_pb_graph_node->output_pins[iport][ipin])); + vpr_device_annotation.add_physical_pb_graph_pin(operating_pb_graph_pin, &(physical_pb_graph_node->output_pins[iport][ipin])); if (true == verbose_output) { - print_success_bind_pb_graph_pin(operating_pb_graph_pin, vpr_pb_type_annotation.physical_pb_graph_pin(operating_pb_graph_pin)); + print_success_bind_pb_graph_pin(operating_pb_graph_pin, vpr_device_annotation.physical_pb_graph_pin(operating_pb_graph_pin)); } return; } @@ -300,15 +300,15 @@ void annotate_physical_pb_graph_pin(t_pb_graph_pin* operating_pb_graph_pin, for (int ipin = 0; ipin < physical_pb_graph_node->num_clock_pins[iport]; ++ipin) { if (false == try_match_pb_graph_pin(operating_pb_graph_pin, &(physical_pb_graph_node->clock_pins[iport][ipin]), - vpr_pb_type_annotation)) { + vpr_device_annotation)) { continue; } /* Reach here, it means the pins are matched by the annotation requirements * We can pair the pin and return */ - vpr_pb_type_annotation.add_physical_pb_graph_pin(operating_pb_graph_pin, &(physical_pb_graph_node->clock_pins[iport][ipin])); + vpr_device_annotation.add_physical_pb_graph_pin(operating_pb_graph_pin, &(physical_pb_graph_node->clock_pins[iport][ipin])); if (true == verbose_output) { - print_success_bind_pb_graph_pin(operating_pb_graph_pin, vpr_pb_type_annotation.physical_pb_graph_pin(operating_pb_graph_pin)); + print_success_bind_pb_graph_pin(operating_pb_graph_pin, vpr_device_annotation.physical_pb_graph_pin(operating_pb_graph_pin)); } return; } @@ -323,13 +323,13 @@ void annotate_physical_pb_graph_pin(t_pb_graph_pin* operating_pb_graph_pin, /******************************************************************** * This function will try bind each pin of the operating pb_graph_node * to a pin of the physical pb_graph_node by following the annotation - * available in vpr_pb_type_annotation - * It will add the pin bindings to the vpr_pb_type_annotation + * available in vpr_device_annotation + * It will add the pin bindings to the vpr_device_annotation *******************************************************************/ static void annotate_physical_pb_graph_node_pins(t_pb_graph_node* operating_pb_graph_node, t_pb_graph_node* physical_pb_graph_node, - VprPbTypeAnnotation& vpr_pb_type_annotation, + VprDeviceAnnotation& vpr_device_annotation, const bool& verbose_output) { /* Iterate over every port and pin of the operating pb_graph_node * and find the physical pins @@ -337,7 +337,7 @@ void annotate_physical_pb_graph_node_pins(t_pb_graph_node* operating_pb_graph_no for (int iport = 0; iport < operating_pb_graph_node->num_input_ports; ++iport) { for (int ipin = 0; ipin < operating_pb_graph_node->num_input_pins[iport]; ++ipin) { annotate_physical_pb_graph_pin(&(operating_pb_graph_node->input_pins[iport][ipin]), - physical_pb_graph_node, vpr_pb_type_annotation, + physical_pb_graph_node, vpr_device_annotation, verbose_output); } } @@ -345,7 +345,7 @@ void annotate_physical_pb_graph_node_pins(t_pb_graph_node* operating_pb_graph_no for (int iport = 0; iport < operating_pb_graph_node->num_output_ports; ++iport) { for (int ipin = 0; ipin < operating_pb_graph_node->num_output_pins[iport]; ++ipin) { annotate_physical_pb_graph_pin(&(operating_pb_graph_node->output_pins[iport][ipin]), - physical_pb_graph_node, vpr_pb_type_annotation, + physical_pb_graph_node, vpr_device_annotation, verbose_output); } } @@ -353,7 +353,7 @@ void annotate_physical_pb_graph_node_pins(t_pb_graph_node* operating_pb_graph_no for (int iport = 0; iport < operating_pb_graph_node->num_clock_ports; ++iport) { for (int ipin = 0; ipin < operating_pb_graph_node->num_clock_pins[iport]; ++ipin) { annotate_physical_pb_graph_pin(&(operating_pb_graph_node->clock_pins[iport][ipin]), - physical_pb_graph_node, vpr_pb_type_annotation, + physical_pb_graph_node, vpr_device_annotation, verbose_output); } } @@ -369,7 +369,7 @@ void annotate_physical_pb_graph_node_pins(t_pb_graph_node* operating_pb_graph_no *******************************************************************/ static void rec_build_vpr_physical_pb_graph_node_annotation(t_pb_graph_node* pb_graph_node, - VprPbTypeAnnotation& vpr_pb_type_annotation, + VprDeviceAnnotation& vpr_device_annotation, const bool& verbose_output) { /* Go recursive first until we touch the primitive node */ if (false == is_primitive_pb_type(pb_graph_node->pb_type)) { @@ -378,7 +378,7 @@ void rec_build_vpr_physical_pb_graph_node_annotation(t_pb_graph_node* pb_graph_n /* Each child may exist multiple times in the hierarchy*/ for (int jpb = 0; jpb < pb_graph_node->pb_type->modes[imode].pb_type_children[ipb].num_pb; ++jpb) { rec_build_vpr_physical_pb_graph_node_annotation(&(pb_graph_node->child_pb_graph_nodes[imode][ipb][jpb]), - vpr_pb_type_annotation, + vpr_device_annotation, verbose_output); } } @@ -393,7 +393,7 @@ void rec_build_vpr_physical_pb_graph_node_annotation(t_pb_graph_node* pb_graph_n * - Find the physical pb_graph_node with the given index * To bind pins from operating pb_graph_node to their physical pb_graph_node pins */ - t_pb_type* physical_pb_type = vpr_pb_type_annotation.physical_pb_type(pb_graph_node->pb_type); + t_pb_type* physical_pb_type = vpr_device_annotation.physical_pb_type(pb_graph_node->pb_type); VTR_ASSERT(nullptr != physical_pb_type); /* Index inference: @@ -401,13 +401,13 @@ void rec_build_vpr_physical_pb_graph_node_annotation(t_pb_graph_node* pb_graph_n * where factor and offset are provided by users */ PbGraphNodeId physical_pb_graph_node_id = PbGraphNodeId( - vpr_pb_type_annotation.physical_pb_type_index_factor(pb_graph_node->pb_type) - * (size_t)vpr_pb_type_annotation.pb_graph_node_unique_index(pb_graph_node) - + vpr_pb_type_annotation.physical_pb_type_index_offset(pb_graph_node->pb_type) + vpr_device_annotation.physical_pb_type_index_factor(pb_graph_node->pb_type) + * (size_t)vpr_device_annotation.pb_graph_node_unique_index(pb_graph_node) + + vpr_device_annotation.physical_pb_type_index_offset(pb_graph_node->pb_type) ); - t_pb_graph_node* physical_pb_graph_node = vpr_pb_type_annotation.pb_graph_node(physical_pb_type, physical_pb_graph_node_id); + t_pb_graph_node* physical_pb_graph_node = vpr_device_annotation.pb_graph_node(physical_pb_type, physical_pb_graph_node_id); VTR_ASSERT(nullptr != physical_pb_graph_node); - vpr_pb_type_annotation.add_physical_pb_graph_node(pb_graph_node, physical_pb_graph_node); + vpr_device_annotation.add_physical_pb_graph_node(pb_graph_node, physical_pb_graph_node); VTR_LOGV(verbose_output, "Bind operating pb_graph_node '%s' to physical pb_graph_node '%s'\n", @@ -415,7 +415,7 @@ void rec_build_vpr_physical_pb_graph_node_annotation(t_pb_graph_node* pb_graph_n physical_pb_graph_node->hierarchical_type_name().c_str()); /* Try to bind each pins under this pb_graph_node to physical_pb_graph_node */ - annotate_physical_pb_graph_node_pins(pb_graph_node, physical_pb_graph_node, vpr_pb_type_annotation, verbose_output); + annotate_physical_pb_graph_node_pins(pb_graph_node, physical_pb_graph_node, vpr_device_annotation, verbose_output); } /******************************************************************** @@ -425,14 +425,14 @@ void rec_build_vpr_physical_pb_graph_node_annotation(t_pb_graph_node* pb_graph_n *******************************************************************/ static void annotate_physical_pb_graph_node(const DeviceContext& vpr_device_ctx, - VprPbTypeAnnotation& vpr_pb_type_annotation, + VprDeviceAnnotation& vpr_device_annotation, const bool& verbose_output) { for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { /* By pass nullptr for pb_graph head */ if (nullptr == lb_type.pb_graph_head) { continue; } - rec_build_vpr_physical_pb_graph_node_annotation(lb_type.pb_graph_head, vpr_pb_type_annotation, verbose_output); + rec_build_vpr_physical_pb_graph_node_annotation(lb_type.pb_graph_head, vpr_device_annotation, verbose_output); } } @@ -443,21 +443,21 @@ void annotate_physical_pb_graph_node(const DeviceContext& vpr_device_ctx, * - Bind pins from operating pb_graph_node to their physical pb_graph_node pins *******************************************************************/ void annotate_pb_graph(const DeviceContext& vpr_device_ctx, - VprPbTypeAnnotation& vpr_pb_type_annotation, + VprDeviceAnnotation& vpr_device_annotation, const bool& verbose_output) { VTR_LOG("Assigning unique indices for primitive pb_graph nodes..."); VTR_LOGV(verbose_output, "\n"); - annotate_primitive_pb_graph_node_unique_index(vpr_device_ctx, vpr_pb_type_annotation); + annotate_primitive_pb_graph_node_unique_index(vpr_device_ctx, vpr_device_annotation); VTR_LOG("Done\n"); VTR_LOG("Binding operating pb_graph nodes/pins to physical pb_graph nodes/pins..."); VTR_LOGV(verbose_output, "\n"); - annotate_physical_pb_graph_node(vpr_device_ctx, vpr_pb_type_annotation, verbose_output); + annotate_physical_pb_graph_node(vpr_device_ctx, vpr_device_annotation, verbose_output); VTR_LOG("Done\n"); /* Check each primitive pb_graph_node and pin has been binded to a physical node and pin */ - check_physical_pb_graph_node_annotation(vpr_device_ctx, const_cast(vpr_pb_type_annotation)); + check_physical_pb_graph_node_annotation(vpr_device_ctx, const_cast(vpr_device_annotation)); } } /* end namespace openfpga */ diff --git a/openfpga/src/annotation/annotate_pb_graph.h b/openfpga/src/annotation/annotate_pb_graph.h index 64eced256..5dd140da8 100644 --- a/openfpga/src/annotation/annotate_pb_graph.h +++ b/openfpga/src/annotation/annotate_pb_graph.h @@ -6,7 +6,7 @@ *******************************************************************/ #include "vpr_context.h" #include "openfpga_context.h" -#include "vpr_pb_type_annotation.h" +#include "vpr_device_annotation.h" /******************************************************************** * Function declaration @@ -16,11 +16,11 @@ namespace openfpga { void annotate_pb_graph_interconnect_physical_type(const DeviceContext& vpr_device_ctx, - VprPbTypeAnnotation& vpr_pb_type_annotation, + VprDeviceAnnotation& vpr_pb_type_annotation, const bool& verbose_output); void annotate_pb_graph(const DeviceContext& vpr_device_ctx, - VprPbTypeAnnotation& vpr_pb_type_annotation, + VprDeviceAnnotation& vpr_pb_type_annotation, const bool& verbose_output); } /* end namespace openfpga */ diff --git a/openfpga/src/annotation/annotate_pb_types.cpp b/openfpga/src/annotation/annotate_pb_types.cpp index d70ab7427..d8701be78 100644 --- a/openfpga/src/annotation/annotate_pb_types.cpp +++ b/openfpga/src/annotation/annotate_pb_types.cpp @@ -7,7 +7,7 @@ #include "vtr_assert.h" #include "vtr_log.h" -#include "vpr_pb_type_annotation.h" +#include "vpr_device_annotation.h" #include "pb_type_utils.h" #include "annotate_pb_graph.h" #include "check_pb_type_annotation.h" @@ -24,7 +24,7 @@ namespace openfpga { static void build_vpr_physical_pb_mode_explicit_annotation(const DeviceContext& vpr_device_ctx, const Arch& openfpga_arch, - VprPbTypeAnnotation& vpr_pb_type_annotation, + VprDeviceAnnotation& vpr_device_annotation, const bool& verbose_output) { /* Walk through the pb_type annotation stored in the openfpga arch */ for (const PbTypeAnnotation& pb_type_annotation : openfpga_arch.pb_type_annotations) { @@ -80,7 +80,7 @@ void build_vpr_physical_pb_mode_explicit_annotation(const DeviceContext& vpr_dev /* Found, we update the annotation by assigning the physical mode */ t_mode* physical_mode = find_pb_type_mode(target_pb_type, pb_type_annotation.physical_mode_name().c_str()); - vpr_pb_type_annotation.add_pb_type_physical_mode(target_pb_type, physical_mode); + vpr_device_annotation.add_pb_type_physical_mode(target_pb_type, physical_mode); /* Give a message */ VTR_LOGV(verbose_output, @@ -112,7 +112,7 @@ void build_vpr_physical_pb_mode_explicit_annotation(const DeviceContext& vpr_dev *******************************************************************/ static void rec_infer_vpr_physical_pb_mode_annotation(t_pb_type* cur_pb_type, - VprPbTypeAnnotation& vpr_pb_type_annotation, + VprDeviceAnnotation& vpr_device_annotation, const bool& verbose_output) { /* We do not check any primitive pb_type */ if (true == is_primitive_pb_type(cur_pb_type)) { @@ -128,18 +128,18 @@ void rec_infer_vpr_physical_pb_mode_annotation(t_pb_type* cur_pb_type, t_mode* physical_mode = nullptr; if (1 == cur_pb_type->num_modes) { - if (nullptr == vpr_pb_type_annotation.physical_mode(cur_pb_type)) { + if (nullptr == vpr_device_annotation.physical_mode(cur_pb_type)) { /* Not assigned by explicit annotation, we should infer here */ - vpr_pb_type_annotation.add_pb_type_physical_mode(cur_pb_type, &(cur_pb_type->modes[0])); + vpr_device_annotation.add_pb_type_physical_mode(cur_pb_type, &(cur_pb_type->modes[0])); VTR_LOGV(verbose_output, "Implicitly infer physical mode '%s' for pb_type '%s'\n", cur_pb_type->modes[0].name, cur_pb_type->name); } } else { VTR_ASSERT(1 < cur_pb_type->num_modes); - if (nullptr == vpr_pb_type_annotation.physical_mode(cur_pb_type)) { + if (nullptr == vpr_device_annotation.physical_mode(cur_pb_type)) { /* Not assigned by explicit annotation, we should infer here */ - vpr_pb_type_annotation.add_pb_type_physical_mode(cur_pb_type, &(cur_pb_type->modes[0])); + vpr_device_annotation.add_pb_type_physical_mode(cur_pb_type, &(cur_pb_type->modes[0])); VTR_LOG_ERROR("Unable to find a physical mode for a multi-mode pb_type '%s'!\n", cur_pb_type->name); VTR_LOG_ERROR("Please specify in the OpenFPGA architecture\n"); @@ -148,14 +148,14 @@ void rec_infer_vpr_physical_pb_mode_annotation(t_pb_type* cur_pb_type, } /* Get the physical mode from annotation */ - physical_mode = vpr_pb_type_annotation.physical_mode(cur_pb_type); + physical_mode = vpr_device_annotation.physical_mode(cur_pb_type); VTR_ASSERT(nullptr != physical_mode); /* Traverse the pb_type children under the physical mode */ for (int ichild = 0; ichild < physical_mode->num_pb_type_children; ++ichild) { rec_infer_vpr_physical_pb_mode_annotation(&(physical_mode->pb_type_children[ichild]), - vpr_pb_type_annotation, + vpr_device_annotation, verbose_output); } } @@ -174,14 +174,14 @@ void rec_infer_vpr_physical_pb_mode_annotation(t_pb_type* cur_pb_type, *******************************************************************/ static void build_vpr_physical_pb_mode_implicit_annotation(const DeviceContext& vpr_device_ctx, - VprPbTypeAnnotation& vpr_pb_type_annotation, + VprDeviceAnnotation& vpr_device_annotation, const bool& verbose_output) { for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { /* By pass nullptr for pb_type head */ if (nullptr == lb_type.pb_type) { continue; } - rec_infer_vpr_physical_pb_mode_annotation(lb_type.pb_type, vpr_pb_type_annotation, verbose_output); + rec_infer_vpr_physical_pb_mode_annotation(lb_type.pb_type, vpr_device_annotation, verbose_output); } } @@ -196,13 +196,13 @@ void build_vpr_physical_pb_mode_implicit_annotation(const DeviceContext& vpr_dev * we assume their physical ports share the same as the operating ports * We will try to find a port in the physical pb_type and check the port range * If found, we will create a pair - * - All the pairs will be updated in vpr_pb_type_annotation + * - All the pairs will be updated in vpr_device_annotation *******************************************************************/ static bool pair_operating_and_physical_pb_types(t_pb_type* operating_pb_type, t_pb_type* physical_pb_type, const PbTypeAnnotation& pb_type_annotation, - VprPbTypeAnnotation& vpr_pb_type_annotation) { + VprDeviceAnnotation& vpr_device_annotation) { /* Reach here, we should have valid operating and physical pb_types */ VTR_ASSERT((nullptr != operating_pb_type) && (nullptr != physical_pb_type)); @@ -229,24 +229,24 @@ bool pair_operating_and_physical_pb_types(t_pb_type* operating_pb_type, if (false == BasicPort(physical_pb_port->name, physical_pb_port->num_pins).contained(expected_physical_pb_port)) { return false; } - /* Now, port mapping should succeed, we update the vpr_pb_type_annotation + /* Now, port mapping should succeed, we update the vpr_device_annotation * - port binding * - port range * - port pin rotate offset */ - vpr_pb_type_annotation.add_physical_pb_port(operating_pb_port, physical_pb_port); - vpr_pb_type_annotation.add_physical_pb_port_range(operating_pb_port, expected_physical_pb_port); - vpr_pb_type_annotation.add_physical_pb_pin_rotate_offset(operating_pb_port, pb_type_annotation.physical_pin_rotate_offset(std::string(operating_pb_port->name))); + vpr_device_annotation.add_physical_pb_port(operating_pb_port, physical_pb_port); + vpr_device_annotation.add_physical_pb_port_range(operating_pb_port, expected_physical_pb_port); + vpr_device_annotation.add_physical_pb_pin_rotate_offset(operating_pb_port, pb_type_annotation.physical_pin_rotate_offset(std::string(operating_pb_port->name))); } - /* Now, pb_type mapping should succeed, we update the vpr_pb_type_annotation + /* Now, pb_type mapping should succeed, we update the vpr_device_annotation * - pb_type binding * - physical_pb_type_index_factor * - physical_pb_type_index_offset */ - vpr_pb_type_annotation.add_physical_pb_type(operating_pb_type, physical_pb_type); - vpr_pb_type_annotation.add_physical_pb_type_index_factor(operating_pb_type, pb_type_annotation.physical_pb_type_index_factor()); - vpr_pb_type_annotation.add_physical_pb_type_index_offset(operating_pb_type, pb_type_annotation.physical_pb_type_index_offset()); + vpr_device_annotation.add_physical_pb_type(operating_pb_type, physical_pb_type); + vpr_device_annotation.add_physical_pb_type_index_factor(operating_pb_type, pb_type_annotation.physical_pb_type_index_factor()); + vpr_device_annotation.add_physical_pb_type_index_offset(operating_pb_type, pb_type_annotation.physical_pb_type_index_offset()); return true; } @@ -263,7 +263,7 @@ bool pair_operating_and_physical_pb_types(t_pb_type* operating_pb_type, static void build_vpr_physical_pb_type_explicit_annotation(const DeviceContext& vpr_device_ctx, const Arch& openfpga_arch, - VprPbTypeAnnotation& vpr_pb_type_annotation, + VprDeviceAnnotation& vpr_device_annotation, const bool& verbose_output) { /* Walk through the pb_type annotation stored in the openfpga arch */ for (const PbTypeAnnotation& pb_type_annotation : openfpga_arch.pb_type_annotations) { @@ -328,7 +328,7 @@ void build_vpr_physical_pb_type_explicit_annotation(const DeviceContext& vpr_dev * we update the annotation by assigning the physical mode */ if (true == pair_operating_and_physical_pb_types(target_op_pb_type, target_phy_pb_type, - pb_type_annotation, vpr_pb_type_annotation)) { + pb_type_annotation, vpr_device_annotation)) { /* Give a message */ VTR_LOGV(verbose_output, @@ -354,7 +354,7 @@ void build_vpr_physical_pb_type_explicit_annotation(const DeviceContext& vpr_dev *******************************************************************/ static bool self_pair_physical_pb_types(t_pb_type* physical_pb_type, - VprPbTypeAnnotation& vpr_pb_type_annotation) { + VprDeviceAnnotation& vpr_device_annotation) { /* Reach here, we should have valid physical pb_types */ VTR_ASSERT(nullptr != physical_pb_type); @@ -364,12 +364,12 @@ bool self_pair_physical_pb_types(t_pb_type* physical_pb_type, */ for (t_port* physical_pb_port : pb_type_ports(physical_pb_type)) { BasicPort physical_port_range(physical_pb_port->name, physical_pb_port->num_pins); - vpr_pb_type_annotation.add_physical_pb_port(physical_pb_port, physical_pb_port); - vpr_pb_type_annotation.add_physical_pb_port_range(physical_pb_port, physical_port_range); + vpr_device_annotation.add_physical_pb_port(physical_pb_port, physical_pb_port); + vpr_device_annotation.add_physical_pb_port_range(physical_pb_port, physical_port_range); } - /* Now, pb_type mapping should succeed, we update the vpr_pb_type_annotation */ - vpr_pb_type_annotation.add_physical_pb_type(physical_pb_type, physical_pb_type); + /* Now, pb_type mapping should succeed, we update the vpr_device_annotation */ + vpr_device_annotation.add_physical_pb_type(physical_pb_type, physical_pb_type); return true; } @@ -384,16 +384,16 @@ bool self_pair_physical_pb_types(t_pb_type* physical_pb_type, *******************************************************************/ static void rec_infer_vpr_physical_pb_type_annotation(t_pb_type* cur_pb_type, - VprPbTypeAnnotation& vpr_pb_type_annotation, + VprDeviceAnnotation& vpr_device_annotation, const bool& verbose_output) { /* Physical pb_type is mainly for the primitive pb_type */ if (true == is_primitive_pb_type(cur_pb_type)) { /* If the physical pb_type has been mapped, we can skip it */ - if (nullptr != vpr_pb_type_annotation.physical_pb_type(cur_pb_type)) { + if (nullptr != vpr_device_annotation.physical_pb_type(cur_pb_type)) { return; } /* Create the pair here */ - if (true == self_pair_physical_pb_types(cur_pb_type, vpr_pb_type_annotation)) { + if (true == self_pair_physical_pb_types(cur_pb_type, vpr_device_annotation)) { /* Give a message */ VTR_LOGV(verbose_output, "Implicitly infer the physical pb_type for pb_type '%s' itself\n", @@ -406,14 +406,14 @@ void rec_infer_vpr_physical_pb_type_annotation(t_pb_type* cur_pb_type, } /* Get the physical mode from annotation */ - t_mode* physical_mode = vpr_pb_type_annotation.physical_mode(cur_pb_type); + t_mode* physical_mode = vpr_device_annotation.physical_mode(cur_pb_type); VTR_ASSERT(nullptr != physical_mode); /* Traverse the pb_type children under the physical mode */ for (int ichild = 0; ichild < physical_mode->num_pb_type_children; ++ichild) { rec_infer_vpr_physical_pb_type_annotation(&(physical_mode->pb_type_children[ichild]), - vpr_pb_type_annotation, + vpr_device_annotation, verbose_output); } } @@ -429,14 +429,14 @@ void rec_infer_vpr_physical_pb_type_annotation(t_pb_type* cur_pb_type, *******************************************************************/ static void build_vpr_physical_pb_type_implicit_annotation(const DeviceContext& vpr_device_ctx, - VprPbTypeAnnotation& vpr_pb_type_annotation, + VprDeviceAnnotation& vpr_device_annotation, const bool& verbose_output) { for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { /* By pass nullptr for pb_type head */ if (nullptr == lb_type.pb_type) { continue; } - rec_infer_vpr_physical_pb_type_annotation(lb_type.pb_type, vpr_pb_type_annotation, verbose_output); + rec_infer_vpr_physical_pb_type_annotation(lb_type.pb_type, vpr_device_annotation, verbose_output); } } @@ -450,7 +450,7 @@ static bool link_physical_pb_port_to_circuit_port(t_pb_type* physical_pb_type, const CircuitLibrary& circuit_lib, const CircuitModelId& circuit_model, - VprPbTypeAnnotation& vpr_pb_type_annotation, + VprDeviceAnnotation& vpr_device_annotation, const bool& verbose_output) { bool link_success = true; /* Iterate over the pb_ports @@ -492,8 +492,8 @@ bool link_physical_pb_port_to_circuit_port(t_pb_type* physical_pb_type, continue; } - /* Reach here, it means that mapping should be ok, update the vpr_pb_type_annotation */ - vpr_pb_type_annotation.add_pb_circuit_port(pb_port, circuit_port); + /* Reach here, it means that mapping should be ok, update the vpr_device_annotation */ + vpr_device_annotation.add_pb_circuit_port(pb_port, circuit_port); VTR_LOGV(verbose_output, "Bind pb type '%s' port '%s' to circuit model '%s' port '%s'\n", physical_pb_type->name, @@ -513,13 +513,13 @@ static bool link_physical_pb_type_to_circuit_model(t_pb_type* physical_pb_type, const CircuitLibrary& circuit_lib, const PbTypeAnnotation& pb_type_annotation, - VprPbTypeAnnotation& vpr_pb_type_annotation, + VprDeviceAnnotation& vpr_device_annotation, const bool& verbose_output) { /* Reach here, we should have valid operating and physical pb_types */ VTR_ASSERT(nullptr != physical_pb_type); /* This must be a physical pb_type according to our annotation! */ - if (false == vpr_pb_type_annotation.is_physical_pb_type(physical_pb_type)) { + if (false == vpr_device_annotation.is_physical_pb_type(physical_pb_type)) { VTR_LOG_ERROR("An operating pb_type '%s' is not allowed to be linked to any circuit model!\n", physical_pb_type->name); return false; @@ -537,12 +537,12 @@ bool link_physical_pb_type_to_circuit_model(t_pb_type* physical_pb_type, /* Ensure that the pb_type ports can be matched in the circuit model ports */ if (false == link_physical_pb_port_to_circuit_port(physical_pb_type, circuit_lib, circuit_model_id, - vpr_pb_type_annotation, verbose_output)) { + vpr_device_annotation, verbose_output)) { return false; } - /* Now the circuit model is valid, update the vpr_pb_type_annotation */ - vpr_pb_type_annotation.add_pb_type_circuit_model(physical_pb_type, circuit_model_id); + /* Now the circuit model is valid, update the vpr_device_annotation */ + vpr_device_annotation.add_pb_type_circuit_model(physical_pb_type, circuit_model_id); return true; } @@ -555,7 +555,7 @@ bool link_physical_pb_interconnect_to_circuit_model(t_pb_type* physical_pb_type, const std::string& interconnect_name, const CircuitLibrary& circuit_lib, const PbTypeAnnotation& pb_type_annotation, - VprPbTypeAnnotation& vpr_pb_type_annotation, + VprDeviceAnnotation& vpr_device_annotation, const bool& verbose_output) { /* The physical pb_type should NOT be a primitive, otherwise it should never contain any interconnect */ if (true == is_primitive_pb_type(physical_pb_type)) { @@ -565,7 +565,7 @@ bool link_physical_pb_interconnect_to_circuit_model(t_pb_type* physical_pb_type, } /* Get the physical mode from annotation */ - t_mode* physical_mode = vpr_pb_type_annotation.physical_mode(physical_pb_type); + t_mode* physical_mode = vpr_device_annotation.physical_mode(physical_pb_type); VTR_ASSERT(nullptr != physical_mode); @@ -594,7 +594,7 @@ bool link_physical_pb_interconnect_to_circuit_model(t_pb_type* physical_pb_type, } /* Double check the type of circuit model, it should be the same as required physical type */ - e_circuit_model_type required_circuit_model_type = pb_interconnect_require_circuit_model_type(vpr_pb_type_annotation.interconnect_physical_type(pb_interc)); + e_circuit_model_type required_circuit_model_type = pb_interconnect_require_circuit_model_type(vpr_device_annotation.interconnect_physical_type(pb_interc)); if (circuit_lib.model_type(circuit_model_id) != required_circuit_model_type) { VTR_LOG_ERROR("Circuit model '%s' type '%s' does not match required type '%s' for interconnect '%s' under physical mode '%s' of pb_type '%s'!\n", circuit_lib.model_name(circuit_model_id).c_str(), @@ -606,8 +606,8 @@ bool link_physical_pb_interconnect_to_circuit_model(t_pb_type* physical_pb_type, return false; } - /* Now the circuit model is valid, update the vpr_pb_type_annotation */ - vpr_pb_type_annotation.add_interconnect_circuit_model(pb_interc, circuit_model_id); + /* Now the circuit model is valid, update the vpr_device_annotation */ + vpr_device_annotation.add_interconnect_circuit_model(pb_interc, circuit_model_id); VTR_LOGV(verbose_output, "Bind pb_type '%s' physical mode '%s' interconnect '%s' to circuit model '%s'\n", @@ -631,7 +631,7 @@ bool link_physical_pb_interconnect_to_circuit_model(t_pb_type* physical_pb_type, static void link_vpr_pb_type_to_circuit_model_explicit_annotation(const DeviceContext& vpr_device_ctx, const Arch& openfpga_arch, - VprPbTypeAnnotation& vpr_pb_type_annotation, + VprDeviceAnnotation& vpr_device_annotation, const bool& verbose_output) { /* Walk through the pb_type annotation stored in the openfpga arch */ for (const PbTypeAnnotation& pb_type_annotation : openfpga_arch.pb_type_annotations) { @@ -683,13 +683,13 @@ void link_vpr_pb_type_to_circuit_model_explicit_annotation(const DeviceContext& /* Only try to bind pb_type to circuit model when it is defined by users */ if (true == link_physical_pb_type_to_circuit_model(target_phy_pb_type, openfpga_arch.circuit_lib, - pb_type_annotation, vpr_pb_type_annotation, + pb_type_annotation, vpr_device_annotation, verbose_output)) { /* Give a message */ VTR_LOGV(verbose_output, "Bind physical pb_type '%s' to its circuit model '%s'\n", target_phy_pb_type->name, - openfpga_arch.circuit_lib.model_name(vpr_pb_type_annotation.pb_type_circuit_model(target_phy_pb_type)).c_str()); + openfpga_arch.circuit_lib.model_name(vpr_device_annotation.pb_type_circuit_model(target_phy_pb_type)).c_str()); link_success = true; break; @@ -718,7 +718,7 @@ void link_vpr_pb_type_to_circuit_model_explicit_annotation(const DeviceContext& static void link_vpr_pb_interconnect_to_circuit_model_explicit_annotation(const DeviceContext& vpr_device_ctx, const Arch& openfpga_arch, - VprPbTypeAnnotation& vpr_pb_type_annotation, + VprDeviceAnnotation& vpr_device_annotation, const bool& verbose_output) { /* Walk through the pb_type annotation stored in the openfpga arch */ for (const PbTypeAnnotation& pb_type_annotation : openfpga_arch.pb_type_annotations) { @@ -771,7 +771,7 @@ void link_vpr_pb_interconnect_to_circuit_model_explicit_annotation(const DeviceC /* Only try to bind interconnect to circuit model when it is defined by users */ for (const std::string& interc_name : pb_type_annotation.interconnect_names()) { if (false == link_physical_pb_interconnect_to_circuit_model(target_phy_pb_type, interc_name, openfpga_arch.circuit_lib, - pb_type_annotation, vpr_pb_type_annotation, + pb_type_annotation, vpr_device_annotation, verbose_output)) { VTR_LOG_ERROR("Unable to bind pb_type '%s' interconnect '%s' to circuit model '%s'!\n", target_phy_pb_type_names.back().c_str(), @@ -812,7 +812,7 @@ void link_vpr_pb_interconnect_to_circuit_model_explicit_annotation(const DeviceC static void rec_infer_vpr_pb_interconnect_circuit_model_annotation(t_pb_type* cur_pb_type, const CircuitLibrary& circuit_lib, - VprPbTypeAnnotation& vpr_pb_type_annotation, + VprDeviceAnnotation& vpr_device_annotation, const bool& verbose_output) { /* We do not check any primitive pb_type */ if (true == is_primitive_pb_type(cur_pb_type)) { @@ -820,18 +820,18 @@ void rec_infer_vpr_pb_interconnect_circuit_model_annotation(t_pb_type* cur_pb_ty } /* Get the physical mode from annotation */ - t_mode* physical_mode = vpr_pb_type_annotation.physical_mode(cur_pb_type); + t_mode* physical_mode = vpr_device_annotation.physical_mode(cur_pb_type); VTR_ASSERT(nullptr != physical_mode); /* Annotate the circuit model for each interconnect under this physical mode */ for (t_interconnect* pb_interc : pb_mode_interconnects(physical_mode)) { /* If the interconnect has been annotated, we skip it */ - if (CircuitModelId::INVALID() != vpr_pb_type_annotation.interconnect_circuit_model(pb_interc)) { + if (CircuitModelId::INVALID() != vpr_device_annotation.interconnect_circuit_model(pb_interc)) { continue; } /* Infer the circuit model type for a given interconnect */ - e_circuit_model_type circuit_model_type = pb_interconnect_require_circuit_model_type(vpr_pb_type_annotation.interconnect_physical_type(pb_interc)); + e_circuit_model_type circuit_model_type = pb_interconnect_require_circuit_model_type(vpr_device_annotation.interconnect_physical_type(pb_interc)); /* Try to find a default circuit model from the circuit library */ CircuitModelId default_circuit_model = circuit_lib.default_model(circuit_model_type); /* Update the annotation if the model id is valid */ @@ -841,7 +841,7 @@ void rec_infer_vpr_pb_interconnect_circuit_model_annotation(t_pb_type* cur_pb_ty physical_mode->name, cur_pb_type->name); } - vpr_pb_type_annotation.add_interconnect_circuit_model(pb_interc, default_circuit_model); + vpr_device_annotation.add_interconnect_circuit_model(pb_interc, default_circuit_model); VTR_LOGV(verbose_output, "Implicitly infer a circuit model '%s' for interconnect '%s' under physical mode '%s' of pb_type '%s'\n", circuit_lib.model_name(default_circuit_model).c_str(), @@ -853,7 +853,7 @@ void rec_infer_vpr_pb_interconnect_circuit_model_annotation(t_pb_type* cur_pb_ty /* Traverse the pb_type children under the physical mode */ for (int ichild = 0; ichild < physical_mode->num_pb_type_children; ++ichild) { rec_infer_vpr_pb_interconnect_circuit_model_annotation(&(physical_mode->pb_type_children[ichild]), - circuit_lib, vpr_pb_type_annotation, + circuit_lib, vpr_device_annotation, verbose_output); } } @@ -871,25 +871,25 @@ void rec_infer_vpr_pb_interconnect_circuit_model_annotation(t_pb_type* cur_pb_ty static void link_vpr_pb_interconnect_to_circuit_model_implicit_annotation(const DeviceContext& vpr_device_ctx, const CircuitLibrary& circuit_lib, - VprPbTypeAnnotation& vpr_pb_type_annotation, + VprDeviceAnnotation& vpr_device_annotation, const bool& verbose_output) { for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { /* By pass nullptr for pb_type head */ if (nullptr == lb_type.pb_type) { continue; } - rec_infer_vpr_pb_interconnect_circuit_model_annotation(lb_type.pb_type, circuit_lib, vpr_pb_type_annotation, verbose_output); + rec_infer_vpr_pb_interconnect_circuit_model_annotation(lb_type.pb_type, circuit_lib, vpr_device_annotation, verbose_output); } } /******************************************************************** * This function will bind mode selection bits to a primitive pb_type - * in the vpr_pb_type_annotation + * in the vpr_device_annotation *******************************************************************/ static bool link_primitive_pb_type_to_mode_bits(t_pb_type* primitive_pb_type, const PbTypeAnnotation& pb_type_annotation, - VprPbTypeAnnotation& vpr_pb_type_annotation) { + VprDeviceAnnotation& vpr_device_annotation) { /* Error out if this is not a primitive pb_type */ if (false == is_primitive_pb_type(primitive_pb_type)) { VTR_LOG_ERROR("Mode selection is only applicable to primitive pb_type while pb_type '%s' is not primitve !\n", @@ -898,7 +898,7 @@ bool link_primitive_pb_type_to_mode_bits(t_pb_type* primitive_pb_type, } /* Update the annotation */ - vpr_pb_type_annotation.add_pb_type_mode_bits(primitive_pb_type, pb_type_annotation.mode_bits()); + vpr_device_annotation.add_pb_type_mode_bits(primitive_pb_type, pb_type_annotation.mode_bits()); return true; } @@ -916,7 +916,7 @@ bool link_primitive_pb_type_to_mode_bits(t_pb_type* primitive_pb_type, static void link_vpr_pb_type_to_mode_bits_explicit_annotation(const DeviceContext& vpr_device_ctx, const Arch& openfpga_arch, - VprPbTypeAnnotation& vpr_pb_type_annotation, + VprDeviceAnnotation& vpr_device_annotation, const bool& verbose_output) { /* Walk through the pb_type annotation stored in the openfpga arch */ for (const PbTypeAnnotation& pb_type_annotation : openfpga_arch.pb_type_annotations) { @@ -973,7 +973,7 @@ void link_vpr_pb_type_to_mode_bits_explicit_annotation(const DeviceContext& vpr_ /* Only try to bind pb_type to circuit model when it is defined by users */ if (true == link_primitive_pb_type_to_mode_bits(target_pb_type, - pb_type_annotation, vpr_pb_type_annotation)) { + pb_type_annotation, vpr_device_annotation)) { /* Give a message */ VTR_LOGV(verbose_output, "Bind physical pb_type '%s' to mode selection bits '%s'\n", @@ -1002,7 +1002,7 @@ void link_vpr_pb_type_to_mode_bits_explicit_annotation(const DeviceContext& vpr_ *******************************************************************/ void annotate_pb_types(const DeviceContext& vpr_device_ctx, const Arch& openfpga_arch, - VprPbTypeAnnotation& vpr_pb_type_annotation, + VprDeviceAnnotation& vpr_device_annotation, const bool& verbose_output) { /* Annotate physical mode to pb_type in the VPR pb_type graph */ @@ -1010,16 +1010,16 @@ void annotate_pb_types(const DeviceContext& vpr_device_ctx, VTR_LOG("Building annotation for physical modes in pb_type..."); VTR_LOGV(verbose_output, "\n"); build_vpr_physical_pb_mode_explicit_annotation(vpr_device_ctx, openfpga_arch, - vpr_pb_type_annotation, + vpr_device_annotation, verbose_output); build_vpr_physical_pb_mode_implicit_annotation(vpr_device_ctx, - vpr_pb_type_annotation, + vpr_device_annotation, verbose_output); VTR_LOG("Done\n"); check_vpr_physical_pb_mode_annotation(vpr_device_ctx, - const_cast(vpr_pb_type_annotation)); + const_cast(vpr_device_annotation)); /* Annotate the physical type for each interconnect under physical modes * Must run AFTER physical mode annotation is done and @@ -1029,7 +1029,7 @@ void annotate_pb_types(const DeviceContext& vpr_device_ctx, VTR_LOG("Building annotation about physical types for pb_type interconnection..."); VTR_LOGV(verbose_output, "\n"); annotate_pb_graph_interconnect_physical_type(vpr_device_ctx, - vpr_pb_type_annotation, + vpr_device_annotation, verbose_output); VTR_LOG("Done\n"); @@ -1043,16 +1043,16 @@ void annotate_pb_types(const DeviceContext& vpr_device_ctx, VTR_LOG("Building annotation between operating and physical pb_types..."); VTR_LOGV(verbose_output, "\n"); build_vpr_physical_pb_type_explicit_annotation(vpr_device_ctx, openfpga_arch, - vpr_pb_type_annotation, + vpr_device_annotation, verbose_output); build_vpr_physical_pb_type_implicit_annotation(vpr_device_ctx, - vpr_pb_type_annotation, + vpr_device_annotation, verbose_output); VTR_LOG("Done\n"); check_vpr_physical_pb_type_annotation(vpr_device_ctx, - const_cast(vpr_pb_type_annotation)); + const_cast(vpr_device_annotation)); /* Link * - physical pb_type to circuit model @@ -1062,32 +1062,32 @@ void annotate_pb_types(const DeviceContext& vpr_device_ctx, VTR_LOG("Building annotation between physical pb_types and circuit models..."); VTR_LOGV(verbose_output, "\n"); link_vpr_pb_type_to_circuit_model_explicit_annotation(vpr_device_ctx, openfpga_arch, - vpr_pb_type_annotation, + vpr_device_annotation, verbose_output); link_vpr_pb_interconnect_to_circuit_model_explicit_annotation(vpr_device_ctx, openfpga_arch, - vpr_pb_type_annotation, + vpr_device_annotation, verbose_output); link_vpr_pb_interconnect_to_circuit_model_implicit_annotation(vpr_device_ctx, openfpga_arch.circuit_lib, - vpr_pb_type_annotation, + vpr_device_annotation, verbose_output); VTR_LOG("Done\n"); check_vpr_pb_type_circuit_model_annotation(vpr_device_ctx, openfpga_arch.circuit_lib, - const_cast(vpr_pb_type_annotation)); + const_cast(vpr_device_annotation)); /* Link physical pb_type to mode_bits */ VTR_LOG("\n"); VTR_LOG("Building annotation between physical pb_types and mode selection bits..."); VTR_LOGV(verbose_output, "\n"); link_vpr_pb_type_to_mode_bits_explicit_annotation(vpr_device_ctx, openfpga_arch, - vpr_pb_type_annotation, + vpr_device_annotation, verbose_output); VTR_LOG("Done\n"); check_vpr_pb_type_mode_bits_annotation(vpr_device_ctx, openfpga_arch.circuit_lib, - const_cast(vpr_pb_type_annotation)); + const_cast(vpr_device_annotation)); } diff --git a/openfpga/src/annotation/annotate_pb_types.h b/openfpga/src/annotation/annotate_pb_types.h index 4be50aed6..d2c1b4cf5 100644 --- a/openfpga/src/annotation/annotate_pb_types.h +++ b/openfpga/src/annotation/annotate_pb_types.h @@ -6,7 +6,7 @@ *******************************************************************/ #include "vpr_context.h" #include "openfpga_context.h" -#include "vpr_pb_type_annotation.h" +#include "vpr_device_annotation.h" /******************************************************************** * Function declaration @@ -17,7 +17,7 @@ namespace openfpga { void annotate_pb_types(const DeviceContext& vpr_device_ctx, const Arch& openfpga_arch, - VprPbTypeAnnotation& vpr_pb_type_annotation, + VprDeviceAnnotation& vpr_device_annotation, const bool& verbose_output); } /* end namespace openfpga */ diff --git a/openfpga/src/annotation/check_pb_graph_annotation.cpp b/openfpga/src/annotation/check_pb_graph_annotation.cpp index 92cabefb4..fdfa1394c 100644 --- a/openfpga/src/annotation/check_pb_graph_annotation.cpp +++ b/openfpga/src/annotation/check_pb_graph_annotation.cpp @@ -19,8 +19,8 @@ namespace openfpga { *******************************************************************/ static bool check_physical_pb_graph_pin(t_pb_graph_pin* pb_graph_pin, - const VprPbTypeAnnotation& vpr_pb_type_annotation) { - if (nullptr == vpr_pb_type_annotation.physical_pb_graph_pin(pb_graph_pin)) { + const VprDeviceAnnotation& vpr_device_annotation) { + if (nullptr == vpr_device_annotation.physical_pb_graph_pin(pb_graph_pin)) { VTR_LOG_ERROR("Found a pb_graph_pin '%s' missing physical pb_graph_pin binding!\n", pb_graph_pin->port->name); return false; @@ -40,7 +40,7 @@ bool check_physical_pb_graph_pin(t_pb_graph_pin* pb_graph_pin, *******************************************************************/ static void rec_check_vpr_physical_pb_graph_node_annotation(t_pb_graph_node* pb_graph_node, - const VprPbTypeAnnotation& vpr_pb_type_annotation, + const VprDeviceAnnotation& vpr_device_annotation, size_t& num_err) { /* Go recursive first until we touch the primitive node */ if (false == is_primitive_pb_type(pb_graph_node->pb_type)) { @@ -49,7 +49,7 @@ void rec_check_vpr_physical_pb_graph_node_annotation(t_pb_graph_node* pb_graph_n /* Each child may exist multiple times in the hierarchy*/ for (int jpb = 0; jpb < pb_graph_node->pb_type->modes[imode].pb_type_children[ipb].num_pb; ++jpb) { rec_check_vpr_physical_pb_graph_node_annotation(&(pb_graph_node->child_pb_graph_nodes[imode][ipb][jpb]), - vpr_pb_type_annotation, num_err); + vpr_device_annotation, num_err); } } } @@ -57,7 +57,7 @@ void rec_check_vpr_physical_pb_graph_node_annotation(t_pb_graph_node* pb_graph_n } /* Ensure that the pb_graph_node has been mapped to a physical node */ - t_pb_graph_node* physical_pb_graph_node = vpr_pb_type_annotation.physical_pb_graph_node(pb_graph_node); + t_pb_graph_node* physical_pb_graph_node = vpr_device_annotation.physical_pb_graph_node(pb_graph_node); if (nullptr == physical_pb_graph_node) { VTR_LOG_ERROR("Found a pb_graph_node '%s' missing physical pb_graph_node binding!\n", physical_pb_graph_node->pb_type->name); @@ -71,7 +71,7 @@ void rec_check_vpr_physical_pb_graph_node_annotation(t_pb_graph_node* pb_graph_n for (int iport = 0; iport < physical_pb_graph_node->num_input_ports; ++iport) { for (int ipin = 0; ipin < physical_pb_graph_node->num_input_pins[iport]; ++ipin) { if (false == check_physical_pb_graph_pin(&(physical_pb_graph_node->input_pins[iport][ipin]), - vpr_pb_type_annotation)) { + vpr_device_annotation)) { num_err++; } } @@ -80,7 +80,7 @@ void rec_check_vpr_physical_pb_graph_node_annotation(t_pb_graph_node* pb_graph_n for (int iport = 0; iport < physical_pb_graph_node->num_output_ports; ++iport) { for (int ipin = 0; ipin < physical_pb_graph_node->num_output_pins[iport]; ++ipin) { if (false == check_physical_pb_graph_pin(&(physical_pb_graph_node->output_pins[iport][ipin]), - vpr_pb_type_annotation)) { + vpr_device_annotation)) { num_err++; } } @@ -89,7 +89,7 @@ void rec_check_vpr_physical_pb_graph_node_annotation(t_pb_graph_node* pb_graph_n for (int iport = 0; iport < physical_pb_graph_node->num_clock_ports; ++iport) { for (int ipin = 0; ipin < physical_pb_graph_node->num_clock_pins[iport]; ++ipin) { if (false == check_physical_pb_graph_pin(&(physical_pb_graph_node->clock_pins[iport][ipin]), - vpr_pb_type_annotation)) { + vpr_device_annotation)) { num_err++; } } @@ -102,7 +102,7 @@ void rec_check_vpr_physical_pb_graph_node_annotation(t_pb_graph_node* pb_graph_n * - Each pin has been binded to a physical pb_graph_node pin *******************************************************************/ void check_physical_pb_graph_node_annotation(const DeviceContext& vpr_device_ctx, - const VprPbTypeAnnotation& vpr_pb_type_annotation) { + const VprDeviceAnnotation& vpr_device_annotation) { size_t num_err = 0; for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { @@ -110,7 +110,7 @@ void check_physical_pb_graph_node_annotation(const DeviceContext& vpr_device_ctx if (nullptr == lb_type.pb_graph_head) { continue; } - rec_check_vpr_physical_pb_graph_node_annotation(lb_type.pb_graph_head, vpr_pb_type_annotation, num_err); + rec_check_vpr_physical_pb_graph_node_annotation(lb_type.pb_graph_head, vpr_device_annotation, num_err); } if (0 == num_err) { diff --git a/openfpga/src/annotation/check_pb_graph_annotation.h b/openfpga/src/annotation/check_pb_graph_annotation.h index c482943c5..8416800c0 100644 --- a/openfpga/src/annotation/check_pb_graph_annotation.h +++ b/openfpga/src/annotation/check_pb_graph_annotation.h @@ -6,7 +6,7 @@ *******************************************************************/ #include "vpr_context.h" #include "openfpga_context.h" -#include "vpr_pb_type_annotation.h" +#include "vpr_device_annotation.h" /******************************************************************** * Function declaration @@ -16,7 +16,7 @@ namespace openfpga { void check_physical_pb_graph_node_annotation(const DeviceContext& vpr_device_ctx, - const VprPbTypeAnnotation& vpr_pb_type_annotation); + const VprDeviceAnnotation& vpr_device_annotation); } /* end namespace openfpga */ diff --git a/openfpga/src/annotation/check_pb_type_annotation.cpp b/openfpga/src/annotation/check_pb_type_annotation.cpp index fdebba00c..5159ec064 100644 --- a/openfpga/src/annotation/check_pb_type_annotation.cpp +++ b/openfpga/src/annotation/check_pb_type_annotation.cpp @@ -22,7 +22,7 @@ namespace openfpga { static void rec_check_vpr_physical_pb_mode_annotation(t_pb_type* cur_pb_type, const bool& expect_physical_mode, - const VprPbTypeAnnotation& vpr_pb_type_annotation, + const VprDeviceAnnotation& vpr_device_annotation, size_t& num_err) { /* We do not check any primitive pb_type */ if (true == is_primitive_pb_type(cur_pb_type)) { @@ -36,7 +36,7 @@ void rec_check_vpr_physical_pb_mode_annotation(t_pb_type* cur_pb_type, * nothing in the annotation */ if (true == expect_physical_mode) { - if (nullptr == vpr_pb_type_annotation.physical_mode(cur_pb_type)) { + if (nullptr == vpr_device_annotation.physical_mode(cur_pb_type)) { VTR_LOG_ERROR("Unable to find a physical mode for a multi-mode pb_type '%s'!\n", cur_pb_type->name); VTR_LOG_ERROR("Please specify in the OpenFPGA architecture\n"); @@ -45,9 +45,9 @@ void rec_check_vpr_physical_pb_mode_annotation(t_pb_type* cur_pb_type, } } else { VTR_ASSERT_SAFE(false == expect_physical_mode); - if (nullptr != vpr_pb_type_annotation.physical_mode(cur_pb_type)) { + if (nullptr != vpr_device_annotation.physical_mode(cur_pb_type)) { VTR_LOG_ERROR("Find a physical mode '%s' for pb_type '%s' which is not under any physical mode!\n", - vpr_pb_type_annotation.physical_mode(cur_pb_type)->name, + vpr_device_annotation.physical_mode(cur_pb_type)->name, cur_pb_type->name); num_err++; return; @@ -60,12 +60,12 @@ void rec_check_vpr_physical_pb_mode_annotation(t_pb_type* cur_pb_type, */ for (int imode = 0; imode < cur_pb_type->num_modes; ++imode) { bool expect_child_physical_mode = false; - if (&(cur_pb_type->modes[imode]) == vpr_pb_type_annotation.physical_mode(cur_pb_type)) { + if (&(cur_pb_type->modes[imode]) == vpr_device_annotation.physical_mode(cur_pb_type)) { expect_child_physical_mode = true && expect_physical_mode; } for (int ichild = 0; ichild < cur_pb_type->modes[imode].num_pb_type_children; ++ichild) { rec_check_vpr_physical_pb_mode_annotation(&(cur_pb_type->modes[imode].pb_type_children[ichild]), - expect_child_physical_mode, vpr_pb_type_annotation, + expect_child_physical_mode, vpr_device_annotation, num_err); } } @@ -76,7 +76,7 @@ void rec_check_vpr_physical_pb_mode_annotation(t_pb_type* cur_pb_type, * each pb_type in the device *******************************************************************/ void check_vpr_physical_pb_mode_annotation(const DeviceContext& vpr_device_ctx, - const VprPbTypeAnnotation& vpr_pb_type_annotation) { + const VprDeviceAnnotation& vpr_device_annotation) { size_t num_err = 0; for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { @@ -85,7 +85,7 @@ void check_vpr_physical_pb_mode_annotation(const DeviceContext& vpr_device_ctx, continue; } /* Top pb_type should always has a physical mode! */ - rec_check_vpr_physical_pb_mode_annotation(lb_type.pb_type, true, vpr_pb_type_annotation, num_err); + rec_check_vpr_physical_pb_mode_annotation(lb_type.pb_type, true, vpr_device_annotation, num_err); } if (0 == num_err) { VTR_LOG("Check physical mode annotation for pb_types passed.\n"); @@ -102,9 +102,9 @@ void check_vpr_physical_pb_mode_annotation(const DeviceContext& vpr_device_ctx, *******************************************************************/ static void check_vpr_physical_primitive_pb_type_annotation(t_pb_type* cur_pb_type, - const VprPbTypeAnnotation& vpr_pb_type_annotation, + const VprDeviceAnnotation& vpr_device_annotation, size_t& num_err) { - if (nullptr == vpr_pb_type_annotation.physical_pb_type(cur_pb_type)) { + if (nullptr == vpr_device_annotation.physical_pb_type(cur_pb_type)) { VTR_LOG_ERROR("Find a pb_type '%s' which has not been mapped to any physical pb_type!\n", cur_pb_type->name); VTR_LOG_ERROR("Please specify in the OpenFPGA architecture\n"); @@ -114,7 +114,7 @@ void check_vpr_physical_primitive_pb_type_annotation(t_pb_type* cur_pb_type, /* Now we need to check each port of the pb_type */ for (t_port* pb_port : pb_type_ports(cur_pb_type)) { - if (nullptr == vpr_pb_type_annotation.physical_pb_port(pb_port)) { + if (nullptr == vpr_device_annotation.physical_pb_port(pb_port)) { VTR_LOG_ERROR("Find a port '%s' of pb_type '%s' which has not been mapped to any physical port!\n", pb_port->name, cur_pb_type->name); VTR_LOG_ERROR("Please specify in the OpenFPGA architecture\n"); @@ -132,11 +132,11 @@ void check_vpr_physical_primitive_pb_type_annotation(t_pb_type* cur_pb_type, *******************************************************************/ static void rec_check_vpr_physical_pb_type_annotation(t_pb_type* cur_pb_type, - const VprPbTypeAnnotation& vpr_pb_type_annotation, + const VprDeviceAnnotation& vpr_device_annotation, size_t& num_err) { /* Primitive pb_type should always been binded to a physical pb_type */ if (true == is_primitive_pb_type(cur_pb_type)) { - check_vpr_physical_primitive_pb_type_annotation(cur_pb_type, vpr_pb_type_annotation, num_err); + check_vpr_physical_primitive_pb_type_annotation(cur_pb_type, vpr_device_annotation, num_err); return; } @@ -144,7 +144,7 @@ void rec_check_vpr_physical_pb_type_annotation(t_pb_type* cur_pb_type, for (int imode = 0; imode < cur_pb_type->num_modes; ++imode) { for (int ichild = 0; ichild < cur_pb_type->modes[imode].num_pb_type_children; ++ichild) { rec_check_vpr_physical_pb_type_annotation(&(cur_pb_type->modes[imode].pb_type_children[ichild]), - vpr_pb_type_annotation, + vpr_device_annotation, num_err); } } @@ -157,7 +157,7 @@ void rec_check_vpr_physical_pb_type_annotation(t_pb_type* cur_pb_type, * and every port of the pb_type have been linked a port of a physical pb_type *******************************************************************/ void check_vpr_physical_pb_type_annotation(const DeviceContext& vpr_device_ctx, - const VprPbTypeAnnotation& vpr_pb_type_annotation) { + const VprDeviceAnnotation& vpr_device_annotation) { size_t num_err = 0; for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { @@ -166,7 +166,7 @@ void check_vpr_physical_pb_type_annotation(const DeviceContext& vpr_device_ctx, continue; } /* Top pb_type should always has a physical mode! */ - rec_check_vpr_physical_pb_type_annotation(lb_type.pb_type, vpr_pb_type_annotation, num_err); + rec_check_vpr_physical_pb_type_annotation(lb_type.pb_type, vpr_device_annotation, num_err); } if (0 == num_err) { VTR_LOG("Check physical pb_type annotation for pb_types passed.\n"); @@ -187,12 +187,12 @@ void check_vpr_physical_pb_type_annotation(const DeviceContext& vpr_device_ctx, static void rec_check_vpr_pb_type_circuit_model_annotation(t_pb_type* cur_pb_type, const CircuitLibrary& circuit_lib, - const VprPbTypeAnnotation& vpr_pb_type_annotation, + const VprDeviceAnnotation& vpr_device_annotation, size_t& num_err) { /* Primitive pb_type should always been binded to a physical pb_type */ if (true == is_primitive_pb_type(cur_pb_type)) { /* Every physical pb_type should be linked to a valid circuit model */ - if (CircuitModelId::INVALID() == vpr_pb_type_annotation.pb_type_circuit_model(cur_pb_type)) { + if (CircuitModelId::INVALID() == vpr_device_annotation.pb_type_circuit_model(cur_pb_type)) { VTR_LOG_ERROR("Found a physical pb_type '%s' missing circuit model binding!\n", cur_pb_type->name); num_err++; @@ -200,7 +200,7 @@ void rec_check_vpr_pb_type_circuit_model_annotation(t_pb_type* cur_pb_type, } /* Every port of the pb_type have been linked to a valid port of a circuit model */ for (t_port* port : pb_type_ports(cur_pb_type)) { - if (CircuitPortId::INVALID() == vpr_pb_type_annotation.pb_circuit_port(port)) { + if (CircuitPortId::INVALID() == vpr_device_annotation.pb_circuit_port(port)) { VTR_LOG_ERROR("Found a port '%s' of physical pb_type '%s' missing circuit port binding!\n", port->name, cur_pb_type->name); num_err++; @@ -210,9 +210,9 @@ void rec_check_vpr_pb_type_circuit_model_annotation(t_pb_type* cur_pb_type, } /* Every interconnect in the physical mode has been linked to a valid circuit model in a correct type */ - t_mode* physical_mode = vpr_pb_type_annotation.physical_mode(cur_pb_type); + t_mode* physical_mode = vpr_device_annotation.physical_mode(cur_pb_type); for (t_interconnect* interc : pb_mode_interconnects(physical_mode)) { - CircuitModelId interc_circuit_model = vpr_pb_type_annotation.interconnect_circuit_model(interc); + CircuitModelId interc_circuit_model = vpr_device_annotation.interconnect_circuit_model(interc); if (CircuitModelId::INVALID() == interc_circuit_model) { VTR_LOG_ERROR("Found an interconnect '%s' under physical mode '%s' of pb_type '%s' missing circuit model binding!\n", interc->name, @@ -221,7 +221,7 @@ void rec_check_vpr_pb_type_circuit_model_annotation(t_pb_type* cur_pb_type, num_err++; continue; } - e_circuit_model_type required_circuit_model_type = pb_interconnect_require_circuit_model_type(vpr_pb_type_annotation.interconnect_physical_type(interc)); + e_circuit_model_type required_circuit_model_type = pb_interconnect_require_circuit_model_type(vpr_device_annotation.interconnect_physical_type(interc)); if (circuit_lib.model_type(interc_circuit_model) != required_circuit_model_type) { VTR_LOG_ERROR("Found an interconnect '%s' under physical mode '%s' of pb_type '%s' linked to a circuit model '%s' with a wrong type!\nExpect: '%s' Linked: '%s'\n", interc->name, @@ -238,7 +238,7 @@ void rec_check_vpr_pb_type_circuit_model_annotation(t_pb_type* cur_pb_type, for (int ichild = 0; ichild < physical_mode->num_pb_type_children; ++ichild) { rec_check_vpr_pb_type_circuit_model_annotation(&(physical_mode->pb_type_children[ichild]), circuit_lib, - vpr_pb_type_annotation, + vpr_device_annotation, num_err); } } @@ -253,7 +253,7 @@ void rec_check_vpr_pb_type_circuit_model_annotation(t_pb_type* cur_pb_type, *******************************************************************/ void check_vpr_pb_type_circuit_model_annotation(const DeviceContext& vpr_device_ctx, const CircuitLibrary& circuit_lib, - const VprPbTypeAnnotation& vpr_pb_type_annotation) { + const VprDeviceAnnotation& vpr_device_annotation) { size_t num_err = 0; for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { @@ -262,7 +262,7 @@ void check_vpr_pb_type_circuit_model_annotation(const DeviceContext& vpr_device_ continue; } /* Top pb_type should always has a physical mode! */ - rec_check_vpr_pb_type_circuit_model_annotation(lb_type.pb_type, circuit_lib, vpr_pb_type_annotation, num_err); + rec_check_vpr_pb_type_circuit_model_annotation(lb_type.pb_type, circuit_lib, vpr_device_annotation, num_err); } if (0 == num_err) { VTR_LOG("Check physical pb_type annotation for circuit model passed.\n"); @@ -282,14 +282,14 @@ void check_vpr_pb_type_circuit_model_annotation(const DeviceContext& vpr_device_ static void rec_check_vpr_pb_type_mode_bits_annotation(t_pb_type* cur_pb_type, const CircuitLibrary& circuit_lib, - const VprPbTypeAnnotation& vpr_pb_type_annotation, + const VprDeviceAnnotation& vpr_device_annotation, size_t& num_err) { /* Primitive pb_type should always been binded to a physical pb_type */ if (true == is_primitive_pb_type(cur_pb_type)) { /* Find the physical pb_type * If the physical pb_type has mode selection bits, this pb_type must have as well! */ - t_pb_type* physical_pb_type = vpr_pb_type_annotation.physical_pb_type(cur_pb_type); + t_pb_type* physical_pb_type = vpr_device_annotation.physical_pb_type(cur_pb_type); if (nullptr == physical_pb_type) { VTR_LOG_ERROR("Find a pb_type '%s' which has not been mapped to any physical pb_type!\n", @@ -299,7 +299,7 @@ void rec_check_vpr_pb_type_mode_bits_annotation(t_pb_type* cur_pb_type, return; } - if (vpr_pb_type_annotation.pb_type_mode_bits(cur_pb_type).size() != vpr_pb_type_annotation.pb_type_mode_bits(physical_pb_type).size()) { + if (vpr_device_annotation.pb_type_mode_bits(cur_pb_type).size() != vpr_device_annotation.pb_type_mode_bits(physical_pb_type).size()) { VTR_LOG_ERROR("Found different sizes of mode_bits for pb_type '%s' and its physical pb_type '%s'\n", cur_pb_type->name, physical_pb_type->name); @@ -308,15 +308,15 @@ void rec_check_vpr_pb_type_mode_bits_annotation(t_pb_type* cur_pb_type, } /* Try to find a mode selection port for the circuit model linked to the circuit model */ - CircuitModelId circuit_model = vpr_pb_type_annotation.pb_type_circuit_model(physical_pb_type); - if (CircuitModelId::INVALID() == vpr_pb_type_annotation.pb_type_circuit_model(physical_pb_type)) { + CircuitModelId circuit_model = vpr_device_annotation.pb_type_circuit_model(physical_pb_type); + if (CircuitModelId::INVALID() == vpr_device_annotation.pb_type_circuit_model(physical_pb_type)) { VTR_LOG_ERROR("Found a physical pb_type '%s' missing circuit model binding!\n", physical_pb_type->name); num_err++; return; /* Invalid id already, further check is not applicable */ } - if (0 == vpr_pb_type_annotation.pb_type_mode_bits(cur_pb_type).size()) { + if (0 == vpr_device_annotation.pb_type_mode_bits(cur_pb_type).size()) { /* No mode bits to be checked! */ return; } @@ -326,7 +326,7 @@ void rec_check_vpr_pb_type_mode_bits_annotation(t_pb_type* cur_pb_type, for (const CircuitPortId& mode_select_port : mode_select_ports) { port_num_mode_bits += circuit_lib.port_size(mode_select_port); } - if (port_num_mode_bits != vpr_pb_type_annotation.pb_type_mode_bits(cur_pb_type).size()) { + if (port_num_mode_bits != vpr_device_annotation.pb_type_mode_bits(cur_pb_type).size()) { VTR_LOG_ERROR("Length of mode bits of pb_type '%s' does not match the size(%ld) of mode selection ports of circuit model '%s'!\n", cur_pb_type->name, port_num_mode_bits, @@ -341,7 +341,7 @@ void rec_check_vpr_pb_type_mode_bits_annotation(t_pb_type* cur_pb_type, for (int imode = 0; imode < cur_pb_type->num_modes; ++imode) { for (int ichild = 0; ichild < cur_pb_type->modes[imode].num_pb_type_children; ++ichild) { rec_check_vpr_pb_type_mode_bits_annotation(&(cur_pb_type->modes[imode].pb_type_children[ichild]), - circuit_lib, vpr_pb_type_annotation, + circuit_lib, vpr_device_annotation, num_err); } } @@ -359,7 +359,7 @@ void rec_check_vpr_pb_type_mode_bits_annotation(t_pb_type* cur_pb_type, *******************************************************************/ void check_vpr_pb_type_mode_bits_annotation(const DeviceContext& vpr_device_ctx, const CircuitLibrary& circuit_lib, - const VprPbTypeAnnotation& vpr_pb_type_annotation) { + const VprDeviceAnnotation& vpr_device_annotation) { size_t num_err = 0; for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { @@ -368,7 +368,7 @@ void check_vpr_pb_type_mode_bits_annotation(const DeviceContext& vpr_device_ctx, continue; } /* Top pb_type should always has a physical mode! */ - rec_check_vpr_pb_type_mode_bits_annotation(lb_type.pb_type, circuit_lib, vpr_pb_type_annotation, num_err); + rec_check_vpr_pb_type_mode_bits_annotation(lb_type.pb_type, circuit_lib, vpr_device_annotation, num_err); } if (0 == num_err) { VTR_LOG("Check pb_type annotation for mode selection bits passed.\n"); diff --git a/openfpga/src/annotation/check_pb_type_annotation.h b/openfpga/src/annotation/check_pb_type_annotation.h index d3d2799aa..5183c5e10 100644 --- a/openfpga/src/annotation/check_pb_type_annotation.h +++ b/openfpga/src/annotation/check_pb_type_annotation.h @@ -6,7 +6,7 @@ *******************************************************************/ #include "vpr_context.h" #include "openfpga_context.h" -#include "vpr_pb_type_annotation.h" +#include "vpr_device_annotation.h" /******************************************************************** * Function declaration @@ -16,18 +16,18 @@ namespace openfpga { void check_vpr_physical_pb_mode_annotation(const DeviceContext& vpr_device_ctx, - const VprPbTypeAnnotation& vpr_pb_type_annotation); + const VprDeviceAnnotation& vpr_device_annotation); void check_vpr_physical_pb_type_annotation(const DeviceContext& vpr_device_ctx, - const VprPbTypeAnnotation& vpr_pb_type_annotation); + const VprDeviceAnnotation& vpr_device_annotation); void check_vpr_pb_type_circuit_model_annotation(const DeviceContext& vpr_device_ctx, const CircuitLibrary& circuit_lib, - const VprPbTypeAnnotation& vpr_pb_type_annotation); + const VprDeviceAnnotation& vpr_device_annotation); void check_vpr_pb_type_mode_bits_annotation(const DeviceContext& vpr_device_ctx, const CircuitLibrary& circuit_lib, - const VprPbTypeAnnotation& vpr_pb_type_annotation); + const VprDeviceAnnotation& vpr_device_annotation); } /* end namespace openfpga */ diff --git a/openfpga/src/annotation/vpr_pb_type_annotation.cpp b/openfpga/src/annotation/vpr_device_annotation.cpp similarity index 87% rename from openfpga/src/annotation/vpr_pb_type_annotation.cpp rename to openfpga/src/annotation/vpr_device_annotation.cpp index f75a91059..8ae8079d5 100644 --- a/openfpga/src/annotation/vpr_pb_type_annotation.cpp +++ b/openfpga/src/annotation/vpr_device_annotation.cpp @@ -1,11 +1,11 @@ /************************************************************************ - * Member functions for class VprPbTypeAnnotation + * Member functions for class VprDeviceAnnotation ***********************************************************************/ #include #include "vtr_log.h" #include "vtr_assert.h" -#include "vpr_pb_type_annotation.h" +#include "vpr_device_annotation.h" /* namespace openfpga begins */ namespace openfpga { @@ -13,14 +13,14 @@ namespace openfpga { /************************************************************************ * Constructors ***********************************************************************/ -VprPbTypeAnnotation::VprPbTypeAnnotation() { +VprDeviceAnnotation::VprDeviceAnnotation() { return; } /************************************************************************ * Public accessors ***********************************************************************/ -bool VprPbTypeAnnotation::is_physical_pb_type(t_pb_type* pb_type) const { +bool VprDeviceAnnotation::is_physical_pb_type(t_pb_type* pb_type) const { /* Ensure that the pb_type is in the list */ std::map::const_iterator it = physical_pb_types_.find(pb_type); if (it == physical_pb_types_.end()) { @@ -30,7 +30,7 @@ bool VprPbTypeAnnotation::is_physical_pb_type(t_pb_type* pb_type) const { return pb_type == physical_pb_types_.at(pb_type); } -t_mode* VprPbTypeAnnotation::physical_mode(t_pb_type* pb_type) const { +t_mode* VprDeviceAnnotation::physical_mode(t_pb_type* pb_type) const { /* Ensure that the pb_type is in the list */ std::map::const_iterator it = physical_pb_modes_.find(pb_type); if (it == physical_pb_modes_.end()) { @@ -39,7 +39,7 @@ t_mode* VprPbTypeAnnotation::physical_mode(t_pb_type* pb_type) const { return physical_pb_modes_.at(pb_type); } -t_pb_type* VprPbTypeAnnotation::physical_pb_type(t_pb_type* pb_type) const { +t_pb_type* VprDeviceAnnotation::physical_pb_type(t_pb_type* pb_type) const { /* Ensure that the pb_type is in the list */ std::map::const_iterator it = physical_pb_types_.find(pb_type); if (it == physical_pb_types_.end()) { @@ -48,7 +48,7 @@ t_pb_type* VprPbTypeAnnotation::physical_pb_type(t_pb_type* pb_type) const { return physical_pb_types_.at(pb_type); } -t_port* VprPbTypeAnnotation::physical_pb_port(t_port* pb_port) const { +t_port* VprDeviceAnnotation::physical_pb_port(t_port* pb_port) const { /* Ensure that the pb_type is in the list */ std::map::const_iterator it = physical_pb_ports_.find(pb_port); if (it == physical_pb_ports_.end()) { @@ -57,7 +57,7 @@ t_port* VprPbTypeAnnotation::physical_pb_port(t_port* pb_port) const { return physical_pb_ports_.at(pb_port); } -BasicPort VprPbTypeAnnotation::physical_pb_port_range(t_port* pb_port) const { +BasicPort VprDeviceAnnotation::physical_pb_port_range(t_port* pb_port) const { /* Ensure that the pb_type is in the list */ std::map::const_iterator it = physical_pb_port_ranges_.find(pb_port); if (it == physical_pb_port_ranges_.end()) { @@ -67,7 +67,7 @@ BasicPort VprPbTypeAnnotation::physical_pb_port_range(t_port* pb_port) const { return physical_pb_port_ranges_.at(pb_port); } -CircuitModelId VprPbTypeAnnotation::pb_type_circuit_model(t_pb_type* physical_pb_type) const { +CircuitModelId VprDeviceAnnotation::pb_type_circuit_model(t_pb_type* physical_pb_type) const { /* Ensure that the pb_type is in the list */ std::map::const_iterator it = pb_type_circuit_models_.find(physical_pb_type); if (it == pb_type_circuit_models_.end()) { @@ -77,7 +77,7 @@ CircuitModelId VprPbTypeAnnotation::pb_type_circuit_model(t_pb_type* physical_pb return pb_type_circuit_models_.at(physical_pb_type); } -CircuitModelId VprPbTypeAnnotation::interconnect_circuit_model(t_interconnect* pb_interconnect) const { +CircuitModelId VprDeviceAnnotation::interconnect_circuit_model(t_interconnect* pb_interconnect) const { /* Ensure that the pb_type is in the list */ std::map::const_iterator it = interconnect_circuit_models_.find(pb_interconnect); if (it == interconnect_circuit_models_.end()) { @@ -87,7 +87,7 @@ CircuitModelId VprPbTypeAnnotation::interconnect_circuit_model(t_interconnect* p return interconnect_circuit_models_.at(pb_interconnect); } -e_interconnect VprPbTypeAnnotation::interconnect_physical_type(t_interconnect* pb_interconnect) const { +e_interconnect VprDeviceAnnotation::interconnect_physical_type(t_interconnect* pb_interconnect) const { /* Ensure that the pb_type is in the list */ std::map::const_iterator it = interconnect_physical_types_.find(pb_interconnect); if (it == interconnect_physical_types_.end()) { @@ -97,7 +97,7 @@ e_interconnect VprPbTypeAnnotation::interconnect_physical_type(t_interconnect* p return interconnect_physical_types_.at(pb_interconnect); } -CircuitPortId VprPbTypeAnnotation::pb_circuit_port(t_port* pb_port) const { +CircuitPortId VprDeviceAnnotation::pb_circuit_port(t_port* pb_port) const { /* Ensure that the pb_type is in the list */ std::map::const_iterator it = pb_circuit_ports_.find(pb_port); if (it == pb_circuit_ports_.end()) { @@ -107,7 +107,7 @@ CircuitPortId VprPbTypeAnnotation::pb_circuit_port(t_port* pb_port) const { return pb_circuit_ports_.at(pb_port); } -std::vector VprPbTypeAnnotation::pb_type_mode_bits(t_pb_type* pb_type) const { +std::vector VprDeviceAnnotation::pb_type_mode_bits(t_pb_type* pb_type) const { /* Ensure that the pb_type is in the list */ std::map>::const_iterator it = pb_type_mode_bits_.find(pb_type); if (it == pb_type_mode_bits_.end()) { @@ -117,7 +117,7 @@ std::vector VprPbTypeAnnotation::pb_type_mode_bits(t_pb_type* pb_type) c return pb_type_mode_bits_.at(pb_type); } -PbGraphNodeId VprPbTypeAnnotation::pb_graph_node_unique_index(t_pb_graph_node* pb_graph_node) const { +PbGraphNodeId VprDeviceAnnotation::pb_graph_node_unique_index(t_pb_graph_node* pb_graph_node) const { /* Ensure that the pb_type is in the list */ std::map>::const_iterator it = pb_graph_node_unique_index_.find(pb_graph_node->pb_type); if (it == pb_graph_node_unique_index_.end()) { @@ -138,7 +138,7 @@ PbGraphNodeId VprPbTypeAnnotation::pb_graph_node_unique_index(t_pb_graph_node* p return PbGraphNodeId(it_node - pb_graph_node_unique_index_.at(pb_graph_node->pb_type).begin()); } -t_pb_graph_node* VprPbTypeAnnotation::pb_graph_node(t_pb_type* pb_type, const PbGraphNodeId& unique_index) const { +t_pb_graph_node* VprDeviceAnnotation::pb_graph_node(t_pb_type* pb_type, const PbGraphNodeId& unique_index) const { /* Ensure that the pb_type is in the list */ std::map>::const_iterator it = pb_graph_node_unique_index_.find(pb_type); if (it == pb_graph_node_unique_index_.end()) { @@ -156,7 +156,7 @@ t_pb_graph_node* VprPbTypeAnnotation::pb_graph_node(t_pb_type* pb_type, const Pb return pb_graph_node_unique_index_.at(pb_type)[size_t(unique_index)]; } -t_pb_graph_node* VprPbTypeAnnotation::physical_pb_graph_node(t_pb_graph_node* pb_graph_node) const { +t_pb_graph_node* VprDeviceAnnotation::physical_pb_graph_node(t_pb_graph_node* pb_graph_node) const { /* Ensure that the pb_graph_node is in the list */ std::map::const_iterator it = physical_pb_graph_nodes_.find(pb_graph_node); if (it == physical_pb_graph_nodes_.end()) { @@ -165,7 +165,7 @@ t_pb_graph_node* VprPbTypeAnnotation::physical_pb_graph_node(t_pb_graph_node* pb return physical_pb_graph_nodes_.at(pb_graph_node); } -int VprPbTypeAnnotation::physical_pb_type_index_factor(t_pb_type* pb_type) const { +int VprDeviceAnnotation::physical_pb_type_index_factor(t_pb_type* pb_type) const { /* Ensure that the pb_type is in the list */ std::map::const_iterator it = physical_pb_type_index_factors_.find(pb_type); if (it == physical_pb_type_index_factors_.end()) { @@ -175,7 +175,7 @@ int VprPbTypeAnnotation::physical_pb_type_index_factor(t_pb_type* pb_type) const return physical_pb_type_index_factors_.at(pb_type); } -int VprPbTypeAnnotation::physical_pb_type_index_offset(t_pb_type* pb_type) const { +int VprDeviceAnnotation::physical_pb_type_index_offset(t_pb_type* pb_type) const { /* Ensure that the pb_type is in the list */ std::map::const_iterator it = physical_pb_type_index_offsets_.find(pb_type); if (it == physical_pb_type_index_offsets_.end()) { @@ -185,7 +185,7 @@ int VprPbTypeAnnotation::physical_pb_type_index_offset(t_pb_type* pb_type) const return physical_pb_type_index_offsets_.at(pb_type); } -int VprPbTypeAnnotation::physical_pb_pin_rotate_offset(t_port* pb_port) const { +int VprDeviceAnnotation::physical_pb_pin_rotate_offset(t_port* pb_port) const { /* Ensure that the pb_type is in the list */ std::map::const_iterator it = physical_pb_pin_rotate_offsets_.find(pb_port); if (it == physical_pb_pin_rotate_offsets_.end()) { @@ -195,7 +195,7 @@ int VprPbTypeAnnotation::physical_pb_pin_rotate_offset(t_port* pb_port) const { return physical_pb_pin_rotate_offsets_.at(pb_port); } -int VprPbTypeAnnotation::physical_pb_pin_offset(t_port* pb_port) const { +int VprDeviceAnnotation::physical_pb_pin_offset(t_port* pb_port) const { /* Ensure that the pb_type is in the list */ std::map::const_iterator it = physical_pb_pin_offsets_.find(pb_port); if (it == physical_pb_pin_offsets_.end()) { @@ -206,7 +206,7 @@ int VprPbTypeAnnotation::physical_pb_pin_offset(t_port* pb_port) const { } -t_pb_graph_pin* VprPbTypeAnnotation::physical_pb_graph_pin(t_pb_graph_pin* pb_graph_pin) const { +t_pb_graph_pin* VprDeviceAnnotation::physical_pb_graph_pin(t_pb_graph_pin* pb_graph_pin) const { /* Ensure that the pb_type is in the list */ std::map::const_iterator it = physical_pb_graph_pins_.find(pb_graph_pin); if (it == physical_pb_graph_pins_.end()) { @@ -218,7 +218,7 @@ t_pb_graph_pin* VprPbTypeAnnotation::physical_pb_graph_pin(t_pb_graph_pin* pb_gr /************************************************************************ * Public mutators ***********************************************************************/ -void VprPbTypeAnnotation::add_pb_type_physical_mode(t_pb_type* pb_type, t_mode* physical_mode) { +void VprDeviceAnnotation::add_pb_type_physical_mode(t_pb_type* pb_type, t_mode* physical_mode) { /* Warn any override attempt */ std::map::const_iterator it = physical_pb_modes_.find(pb_type); if (it != physical_pb_modes_.end()) { @@ -229,7 +229,7 @@ void VprPbTypeAnnotation::add_pb_type_physical_mode(t_pb_type* pb_type, t_mode* physical_pb_modes_[pb_type] = physical_mode; } -void VprPbTypeAnnotation::add_physical_pb_type(t_pb_type* operating_pb_type, t_pb_type* physical_pb_type) { +void VprDeviceAnnotation::add_physical_pb_type(t_pb_type* operating_pb_type, t_pb_type* physical_pb_type) { /* Warn any override attempt */ std::map::const_iterator it = physical_pb_types_.find(operating_pb_type); if (it != physical_pb_types_.end()) { @@ -240,7 +240,7 @@ void VprPbTypeAnnotation::add_physical_pb_type(t_pb_type* operating_pb_type, t_p physical_pb_types_[operating_pb_type] = physical_pb_type; } -void VprPbTypeAnnotation::add_physical_pb_port(t_port* operating_pb_port, t_port* physical_pb_port) { +void VprDeviceAnnotation::add_physical_pb_port(t_port* operating_pb_port, t_port* physical_pb_port) { /* Warn any override attempt */ std::map::const_iterator it = physical_pb_ports_.find(operating_pb_port); if (it != physical_pb_ports_.end()) { @@ -251,7 +251,7 @@ void VprPbTypeAnnotation::add_physical_pb_port(t_port* operating_pb_port, t_port physical_pb_ports_[operating_pb_port] = physical_pb_port; } -void VprPbTypeAnnotation::add_physical_pb_port_range(t_port* operating_pb_port, const BasicPort& port_range) { +void VprDeviceAnnotation::add_physical_pb_port_range(t_port* operating_pb_port, const BasicPort& port_range) { /* The port range must satify the port width*/ VTR_ASSERT((size_t)operating_pb_port->num_pins == port_range.get_width()); @@ -265,7 +265,7 @@ void VprPbTypeAnnotation::add_physical_pb_port_range(t_port* operating_pb_port, physical_pb_port_ranges_[operating_pb_port] = port_range; } -void VprPbTypeAnnotation::add_pb_type_circuit_model(t_pb_type* physical_pb_type, const CircuitModelId& circuit_model) { +void VprDeviceAnnotation::add_pb_type_circuit_model(t_pb_type* physical_pb_type, const CircuitModelId& circuit_model) { /* Warn any override attempt */ std::map::const_iterator it = pb_type_circuit_models_.find(physical_pb_type); if (it != pb_type_circuit_models_.end()) { @@ -276,7 +276,7 @@ void VprPbTypeAnnotation::add_pb_type_circuit_model(t_pb_type* physical_pb_type, pb_type_circuit_models_[physical_pb_type] = circuit_model; } -void VprPbTypeAnnotation::add_interconnect_circuit_model(t_interconnect* pb_interconnect, const CircuitModelId& circuit_model) { +void VprDeviceAnnotation::add_interconnect_circuit_model(t_interconnect* pb_interconnect, const CircuitModelId& circuit_model) { /* Warn any override attempt */ std::map::const_iterator it = interconnect_circuit_models_.find(pb_interconnect); if (it != interconnect_circuit_models_.end()) { @@ -287,7 +287,7 @@ void VprPbTypeAnnotation::add_interconnect_circuit_model(t_interconnect* pb_inte interconnect_circuit_models_[pb_interconnect] = circuit_model; } -void VprPbTypeAnnotation::add_interconnect_physical_type(t_interconnect* pb_interconnect, +void VprDeviceAnnotation::add_interconnect_physical_type(t_interconnect* pb_interconnect, const e_interconnect& physical_type) { /* Warn any override attempt */ std::map::const_iterator it = interconnect_physical_types_.find(pb_interconnect); @@ -299,7 +299,7 @@ void VprPbTypeAnnotation::add_interconnect_physical_type(t_interconnect* pb_inte interconnect_physical_types_[pb_interconnect] = physical_type; } -void VprPbTypeAnnotation::add_pb_circuit_port(t_port* pb_port, const CircuitPortId& circuit_port) { +void VprDeviceAnnotation::add_pb_circuit_port(t_port* pb_port, const CircuitPortId& circuit_port) { /* Warn any override attempt */ std::map::const_iterator it = pb_circuit_ports_.find(pb_port); if (it != pb_circuit_ports_.end()) { @@ -310,7 +310,7 @@ void VprPbTypeAnnotation::add_pb_circuit_port(t_port* pb_port, const CircuitPort pb_circuit_ports_[pb_port] = circuit_port; } -void VprPbTypeAnnotation::add_pb_type_mode_bits(t_pb_type* pb_type, const std::vector& mode_bits) { +void VprDeviceAnnotation::add_pb_type_mode_bits(t_pb_type* pb_type, const std::vector& mode_bits) { /* Warn any override attempt */ std::map>::const_iterator it = pb_type_mode_bits_.find(pb_type); if (it != pb_type_mode_bits_.end()) { @@ -321,11 +321,11 @@ void VprPbTypeAnnotation::add_pb_type_mode_bits(t_pb_type* pb_type, const std::v pb_type_mode_bits_[pb_type] = mode_bits; } -void VprPbTypeAnnotation::add_pb_graph_node_unique_index(t_pb_graph_node* pb_graph_node) { +void VprDeviceAnnotation::add_pb_graph_node_unique_index(t_pb_graph_node* pb_graph_node) { pb_graph_node_unique_index_[pb_graph_node->pb_type].push_back(pb_graph_node); } -void VprPbTypeAnnotation::add_physical_pb_graph_node(t_pb_graph_node* operating_pb_graph_node, +void VprDeviceAnnotation::add_physical_pb_graph_node(t_pb_graph_node* operating_pb_graph_node, t_pb_graph_node* physical_pb_graph_node) { /* Warn any override attempt */ std::map::const_iterator it = physical_pb_graph_nodes_.find(operating_pb_graph_node); @@ -340,7 +340,7 @@ void VprPbTypeAnnotation::add_physical_pb_graph_node(t_pb_graph_node* operating_ physical_pb_graph_nodes_[operating_pb_graph_node] = physical_pb_graph_node; } -void VprPbTypeAnnotation::add_physical_pb_type_index_factor(t_pb_type* pb_type, const int& factor) { +void VprDeviceAnnotation::add_physical_pb_type_index_factor(t_pb_type* pb_type, const int& factor) { /* Warn any override attempt */ std::map::const_iterator it = physical_pb_type_index_factors_.find(pb_type); if (it != physical_pb_type_index_factors_.end()) { @@ -351,7 +351,7 @@ void VprPbTypeAnnotation::add_physical_pb_type_index_factor(t_pb_type* pb_type, physical_pb_type_index_factors_[pb_type] = factor; } -void VprPbTypeAnnotation::add_physical_pb_type_index_offset(t_pb_type* pb_type, const int& offset) { +void VprDeviceAnnotation::add_physical_pb_type_index_offset(t_pb_type* pb_type, const int& offset) { /* Warn any override attempt */ std::map::const_iterator it = physical_pb_type_index_offsets_.find(pb_type); if (it != physical_pb_type_index_offsets_.end()) { @@ -362,7 +362,7 @@ void VprPbTypeAnnotation::add_physical_pb_type_index_offset(t_pb_type* pb_type, physical_pb_type_index_offsets_[pb_type] = offset; } -void VprPbTypeAnnotation::add_physical_pb_pin_rotate_offset(t_port* pb_port, const int& offset) { +void VprDeviceAnnotation::add_physical_pb_pin_rotate_offset(t_port* pb_port, const int& offset) { /* Warn any override attempt */ std::map::const_iterator it = physical_pb_pin_rotate_offsets_.find(pb_port); if (it != physical_pb_pin_rotate_offsets_.end()) { @@ -375,7 +375,7 @@ void VprPbTypeAnnotation::add_physical_pb_pin_rotate_offset(t_port* pb_port, con physical_pb_pin_offsets_[pb_port] = 0; } -void VprPbTypeAnnotation::add_physical_pb_graph_pin(t_pb_graph_pin* operating_pb_graph_pin, +void VprDeviceAnnotation::add_physical_pb_graph_pin(t_pb_graph_pin* operating_pb_graph_pin, t_pb_graph_pin* physical_pb_graph_pin) { /* Warn any override attempt */ std::map::const_iterator it = physical_pb_graph_pins_.find(operating_pb_graph_pin); diff --git a/openfpga/src/annotation/vpr_pb_type_annotation.h b/openfpga/src/annotation/vpr_device_annotation.h similarity index 98% rename from openfpga/src/annotation/vpr_pb_type_annotation.h rename to openfpga/src/annotation/vpr_device_annotation.h index add45e42d..0cd9ac63f 100644 --- a/openfpga/src/annotation/vpr_pb_type_annotation.h +++ b/openfpga/src/annotation/vpr_device_annotation.h @@ -1,5 +1,5 @@ -#ifndef VPR_PB_TYPE_ANNOTATION_H -#define VPR_PB_TYPE_ANNOTATION_H +#ifndef VPR_DEVICE_ANNOTATION_H +#define VPR_DEVICE_ANNOTATION_H /******************************************************************** * Include header files required by the data structure definition @@ -33,9 +33,9 @@ typedef vtr::StrongId PbGraphNodeId; * 3. what is the physical pb_type for an operating pb_type * 4. what is the mode pointer that represents the physical mode for a pb_type *******************************************************************/ -class VprPbTypeAnnotation { +class VprDeviceAnnotation { public: /* Constructor */ - VprPbTypeAnnotation(); + VprDeviceAnnotation(); public: /* Public accessors */ bool is_physical_pb_type(t_pb_type* pb_type) const; t_mode* physical_mode(t_pb_type* pb_type) const; diff --git a/openfpga/src/base/openfpga_context.h b/openfpga/src/base/openfpga_context.h index 743847114..aed6b8014 100644 --- a/openfpga/src/base/openfpga_context.h +++ b/openfpga/src/base/openfpga_context.h @@ -4,7 +4,7 @@ #include "vpr_context.h" #include "openfpga_arch.h" #include "vpr_netlist_annotation.h" -#include "vpr_pb_type_annotation.h" +#include "vpr_device_annotation.h" #include "vpr_clustering_annotation.h" #include "vpr_routing_annotation.h" #include "device_rr_gsb.h" @@ -39,14 +39,14 @@ class OpenfpgaContext : public Context { public: /* Public accessors */ const openfpga::Arch& arch() const { return arch_; } - const openfpga::VprPbTypeAnnotation& vpr_pb_type_annotation() const { return vpr_pb_type_annotation_; } + const openfpga::VprDeviceAnnotation& vpr_device_annotation() const { return vpr_device_annotation_; } const openfpga::VprNetlistAnnotation& vpr_netlist_annotation() const { return vpr_netlist_annotation_; } const openfpga::VprClusteringAnnotation& vpr_clustering_annotation() const { return vpr_clustering_annotation_; } const openfpga::VprRoutingAnnotation& vpr_routing_annotation() const { return vpr_routing_annotation_; } const openfpga::DeviceRRGSB& device_rr_gsb() const { return device_rr_gsb_; } public: /* Public mutators */ openfpga::Arch& mutable_arch() { return arch_; } - openfpga::VprPbTypeAnnotation& mutable_vpr_pb_type_annotation() { return vpr_pb_type_annotation_; } + openfpga::VprDeviceAnnotation& mutable_vpr_device_annotation() { return vpr_device_annotation_; } openfpga::VprNetlistAnnotation& mutable_vpr_netlist_annotation() { return vpr_netlist_annotation_; } openfpga::VprClusteringAnnotation& mutable_vpr_clustering_annotation() { return vpr_clustering_annotation_; } openfpga::VprRoutingAnnotation& mutable_vpr_routing_annotation() { return vpr_routing_annotation_; } @@ -56,7 +56,7 @@ class OpenfpgaContext : public Context { openfpga::Arch arch_; /* Annotation to pb_type of VPR */ - openfpga::VprPbTypeAnnotation vpr_pb_type_annotation_; + openfpga::VprDeviceAnnotation vpr_device_annotation_; /* Naming fix to netlist */ openfpga::VprNetlistAnnotation vpr_netlist_annotation_; diff --git a/openfpga/src/base/openfpga_link_arch.cpp b/openfpga/src/base/openfpga_link_arch.cpp index 2519be57a..88f46571a 100644 --- a/openfpga/src/base/openfpga_link_arch.cpp +++ b/openfpga/src/base/openfpga_link_arch.cpp @@ -7,7 +7,7 @@ #include "vtr_assert.h" #include "vtr_log.h" -#include "vpr_pb_type_annotation.h" +#include "vpr_device_annotation.h" #include "pb_type_utils.h" #include "annotate_pb_types.h" #include "annotate_pb_graph.h" @@ -42,7 +42,7 @@ void link_arch(OpenfpgaContext& openfpga_context, * - circuit models for pb_type and pb interconnect */ annotate_pb_types(g_vpr_ctx.device(), openfpga_context.arch(), - openfpga_context.mutable_vpr_pb_type_annotation(), + openfpga_context.mutable_vpr_device_annotation(), cmd_context.option_enable(cmd, opt_verbose)); /* Annotate pb_graph_nodes @@ -51,7 +51,7 @@ void link_arch(OpenfpgaContext& openfpga_context, * - Bind pins from operating pb_graph_node to their physical pb_graph_node pins */ annotate_pb_graph(g_vpr_ctx.device(), - openfpga_context.mutable_vpr_pb_type_annotation(), + openfpga_context.mutable_vpr_device_annotation(), cmd_context.option_enable(cmd, opt_verbose)); /* Annotate net mapping to each rr_node From feccbc5780e8202fc3edd965d9d9fbbe9a0f8924 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 12 Feb 2020 10:08:54 -0700 Subject: [PATCH 117/645] add more methods to link routing to circuit models in device annotation --- .../src/annotation/vpr_device_annotation.cpp | 40 +++++++++++++++++++ .../src/annotation/vpr_device_annotation.h | 13 ++++++ 2 files changed, 53 insertions(+) diff --git a/openfpga/src/annotation/vpr_device_annotation.cpp b/openfpga/src/annotation/vpr_device_annotation.cpp index 8ae8079d5..280e4ca38 100644 --- a/openfpga/src/annotation/vpr_device_annotation.cpp +++ b/openfpga/src/annotation/vpr_device_annotation.cpp @@ -215,6 +215,24 @@ t_pb_graph_pin* VprDeviceAnnotation::physical_pb_graph_pin(t_pb_graph_pin* pb_gr return physical_pb_graph_pins_.at(pb_graph_pin); } +CircuitModelId VprDeviceAnnotation::rr_switch_circuit_model(const RRSwitchId& rr_switch) const { + /* Ensure that the rr_switch is in the list */ + std::map::const_iterator it = rr_switch_circuit_models_.find(rr_switch); + if (it == rr_switch_circuit_models_.end()) { + return CircuitModelId::INVALID(); + } + return rr_switch_circuit_models_.at(rr_switch); +} + +CircuitModelId VprDeviceAnnotation::rr_segment_circuit_model(const RRSegmentId& rr_segment) const { + /* Ensure that the rr_switch is in the list */ + std::map::const_iterator it = rr_segment_circuit_models_.find(rr_segment); + if (it == rr_segment_circuit_models_.end()) { + return CircuitModelId::INVALID(); + } + return rr_segment_circuit_models_.at(rr_segment); +} + /************************************************************************ * Public mutators ***********************************************************************/ @@ -412,4 +430,26 @@ void VprDeviceAnnotation::add_physical_pb_graph_pin(t_pb_graph_pin* operating_pb } } +void VprDeviceAnnotation::add_rr_switch_circuit_model(const RRSwitchId& rr_switch, const CircuitModelId& circuit_model) { + /* Warn any override attempt */ + std::map::const_iterator it = rr_switch_circuit_models_.find(rr_switch); + if (it != rr_switch_circuit_models_.end()) { + VTR_LOG_WARN("Override the annotation between rr_switch '%ld' and its circuit_model '%ld'!\n", + size_t(rr_switch), size_t(circuit_model)); + } + + rr_switch_circuit_models_[rr_switch] = circuit_model; +} + +void VprDeviceAnnotation::add_rr_segment_circuit_model(const RRSegmentId& rr_segment, const CircuitModelId& circuit_model) { + /* Warn any override attempt */ + std::map::const_iterator it = rr_segment_circuit_models_.find(rr_segment); + if (it != rr_segment_circuit_models_.end()) { + VTR_LOG_WARN("Override the annotation between rr_segment '%ld' and its circuit_model '%ld'!\n", + size_t(rr_segment), size_t(circuit_model)); + } + + rr_segment_circuit_models_[rr_segment] = circuit_model; +} + } /* End namespace openfpga*/ diff --git a/openfpga/src/annotation/vpr_device_annotation.h b/openfpga/src/annotation/vpr_device_annotation.h index 0cd9ac63f..211676264 100644 --- a/openfpga/src/annotation/vpr_device_annotation.h +++ b/openfpga/src/annotation/vpr_device_annotation.h @@ -12,6 +12,9 @@ /* Header from archfpga library */ #include "physical_types.h" +/* Header from vpr library */ +#include "rr_graph_obj.h" + /* Header from openfpgautil library */ #include "openfpga_port.h" #include "circuit_library.h" @@ -66,6 +69,8 @@ class VprDeviceAnnotation { */ int physical_pb_pin_offset(t_port* pb_port) const; t_pb_graph_pin* physical_pb_graph_pin(t_pb_graph_pin* pb_graph_pin) const; + CircuitModelId rr_switch_circuit_model(const RRSwitchId& rr_switch) const; + CircuitModelId rr_segment_circuit_model(const RRSegmentId& rr_segment) const; public: /* Public mutators */ void add_pb_type_physical_mode(t_pb_type* pb_type, t_mode* physical_mode); void add_physical_pb_type(t_pb_type* operating_pb_type, t_pb_type* physical_pb_type); @@ -83,6 +88,8 @@ class VprDeviceAnnotation { void add_physical_pb_type_index_offset(t_pb_type* pb_type, const int& offset); void add_physical_pb_pin_rotate_offset(t_port* pb_port, const int& offset); void add_physical_pb_graph_pin(t_pb_graph_pin* operating_pb_graph_pin, t_pb_graph_pin* physical_pb_graph_pin); + void add_rr_switch_circuit_model(const RRSwitchId& rr_switch, const CircuitModelId& circuit_model); + void add_rr_segment_circuit_model(const RRSegmentId& rr_segment, const CircuitModelId& circuit_model); private: /* Internal data */ /* Pair a regular pb_type to its physical pb_type */ std::map physical_pb_types_; @@ -157,6 +164,12 @@ class VprDeviceAnnotation { /* Pair a pb_graph_pin to a physical pb_graph_pin */ std::map physical_pb_graph_pins_; + + /* Pair a Routing Resource Switch (rr_switch) to a circuit model */ + std::map rr_switch_circuit_models_; + + /* Pair a Routing Segment (rr_segment) to a circuit model */ + std::map rr_segment_circuit_models_; }; } /* End namespace openfpga*/ From a736e09c29a4d85e84451b28797308744494efce Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 12 Feb 2020 10:52:20 -0700 Subject: [PATCH 118/645] add rr_switch binding in link openfpga arch command --- ...otate_rr_gsb.cpp => annotate_rr_graph.cpp} | 75 ++++++++++++++++++- ...{annotate_rr_gsb.h => annotate_rr_graph.h} | 9 ++- openfpga/src/base/openfpga_link_arch.cpp | 8 +- openfpga/src/base/openfpga_setup_command.cpp | 1 - 4 files changed, 88 insertions(+), 5 deletions(-) rename openfpga/src/annotation/{annotate_rr_gsb.cpp => annotate_rr_graph.cpp} (82%) rename openfpga/src/annotation/{annotate_rr_gsb.h => annotate_rr_graph.h} (67%) diff --git a/openfpga/src/annotation/annotate_rr_gsb.cpp b/openfpga/src/annotation/annotate_rr_graph.cpp similarity index 82% rename from openfpga/src/annotation/annotate_rr_gsb.cpp rename to openfpga/src/annotation/annotate_rr_graph.cpp index dca399abd..efb7bd3a7 100644 --- a/openfpga/src/annotation/annotate_rr_gsb.cpp +++ b/openfpga/src/annotation/annotate_rr_graph.cpp @@ -13,11 +13,13 @@ /* Headers from vpr library */ #include "rr_graph_obj_util.h" -#include "annotate_rr_gsb.h" +#include "annotate_rr_graph.h" /* begin namespace openfpga */ namespace openfpga { +constexpr char* VPR_DELAYLESS_SWITCH_NAME = "__vpr_delayless_switch__"; + /* Build a RRChan Object with the given channel type and coorindators */ static RRChan build_one_rr_chan(const DeviceContext& vpr_device_ctx, @@ -392,4 +394,75 @@ void annotate_device_rr_gsb(const DeviceContext& vpr_device_ctx, gsb_range.x() * gsb_range.y()); } +/******************************************************************** + * Build the link between rr_graph switches to their physical circuit models + * The binding is done based on the name of rr_switches defined in the + * OpenFPGA arch XML + *******************************************************************/ +static +void annotate_rr_switch_circuit_models(const DeviceContext& vpr_device_ctx, + const Arch& openfpga_arch, + VprDeviceAnnotation& vpr_device_annotation, + const bool& verbose_output) { + size_t count = 0; + + for (size_t iswitch = 0; iswitch < vpr_device_ctx.rr_switch_inf.size(); iswitch++) { + std::string switch_name(vpr_device_ctx.rr_switch_inf[iswitch].name); + /* Skip the delayless switch, which is only used by the edges between + * - SOURCE and OPIN + * - IPIN and SINK + */ + if (switch_name == std::string(VPR_DELAYLESS_SWITCH_NAME)) { + continue; + } + + CircuitModelId circuit_model = CircuitModelId::INVALID(); + /* The name-to-circuit mapping is stored in either cb_switch-to-circuit or sb_switch-to-circuit, + * Try to find one and update the device annotation + */ + if (0 < openfpga_arch.cb_switch2circuit.count(switch_name)) { + circuit_model = openfpga_arch.cb_switch2circuit.at(switch_name); + } + if (0 < openfpga_arch.sb_switch2circuit.count(switch_name)) { + if (CircuitModelId::INVALID() != circuit_model) { + VTR_LOG_WARN("Found a connection block and a switch block switch share the same name '%s' and binded to different circuit models '%s' and '%s'!\nWill use the switch block switch binding!\n", + switch_name.c_str(), + openfpga_arch.circuit_lib.model_name(circuit_model).c_str(), + openfpga_arch.circuit_lib.model_name(openfpga_arch.sb_switch2circuit.at(switch_name)).c_str()); + } + circuit_model = openfpga_arch.sb_switch2circuit.at(switch_name); + } + + /* Cannot find a circuit model, error out! */ + if (CircuitModelId::INVALID() == circuit_model) { + VTR_LOG_ERROR("Fail to find a circuit model for a routing resource graph switch '%s'!\nPlease check your OpenFPGA architecture XML!\n", + switch_name.c_str()); + exit(1); + } + + /* Now update the device annotation */ + vpr_device_annotation.add_rr_switch_circuit_model(RRSwitchId(iswitch), circuit_model); + VTR_LOGV(verbose_output, + "Binded a routing resource graph switch '%s' to circuit model '%s'\n", + switch_name.c_str(), + openfpga_arch.circuit_lib.model_name(circuit_model).c_str()); + count++; + } + + VTR_LOG("Binded %lu routing resource graph switches to circuit models\n", + count); +} + +/******************************************************************** + * Build the link between rr_graph switches and segments to their + * physical circuit models + *******************************************************************/ +void annotate_rr_graph_circuit_models(const DeviceContext& vpr_device_ctx, + const Arch& openfpga_arch, + VprDeviceAnnotation& vpr_device_annotation, + const bool& verbose_output) { + /* Iterate over each rr_switch in the device context and bind with names */ + annotate_rr_switch_circuit_models(vpr_device_ctx, openfpga_arch, vpr_device_annotation, verbose_output); +} + } /* end namespace openfpga */ diff --git a/openfpga/src/annotation/annotate_rr_gsb.h b/openfpga/src/annotation/annotate_rr_graph.h similarity index 67% rename from openfpga/src/annotation/annotate_rr_gsb.h rename to openfpga/src/annotation/annotate_rr_graph.h index abf666c4a..d4550639f 100644 --- a/openfpga/src/annotation/annotate_rr_gsb.h +++ b/openfpga/src/annotation/annotate_rr_graph.h @@ -1,5 +1,5 @@ -#ifndef ANNOTATE_RR_GSB_H -#define ANNOTATE_RR_GSB_H +#ifndef ANNOTATE_RR_GRAPH_H +#define ANNOTATE_RR_GRAPH_H /******************************************************************** * Include header files that are required by function declaration @@ -19,6 +19,11 @@ void annotate_device_rr_gsb(const DeviceContext& vpr_device_ctx, DeviceRRGSB& device_rr_gsb, const bool& verbose_output); +void annotate_rr_graph_circuit_models(const DeviceContext& vpr_device_ctx, + const Arch& openfpga_arch, + VprDeviceAnnotation& vpr_device_annotation, + const bool& verbose_output); + } /* end namespace openfpga */ #endif diff --git a/openfpga/src/base/openfpga_link_arch.cpp b/openfpga/src/base/openfpga_link_arch.cpp index 88f46571a..bffca3682 100644 --- a/openfpga/src/base/openfpga_link_arch.cpp +++ b/openfpga/src/base/openfpga_link_arch.cpp @@ -12,7 +12,7 @@ #include "annotate_pb_types.h" #include "annotate_pb_graph.h" #include "annotate_routing.h" -#include "annotate_rr_gsb.h" +#include "annotate_rr_graph.h" #include "openfpga_link_arch.h" /* Include global variables of VPR */ @@ -54,6 +54,12 @@ void link_arch(OpenfpgaContext& openfpga_context, openfpga_context.mutable_vpr_device_annotation(), cmd_context.option_enable(cmd, opt_verbose)); + /* Annotate routing architecture to circuit library */ + annotate_rr_graph_circuit_models(g_vpr_ctx.device(), + openfpga_context.arch(), + openfpga_context.mutable_vpr_device_annotation(), + cmd_context.option_enable(cmd, opt_verbose)); + /* Annotate net mapping to each rr_node */ openfpga_context.mutable_vpr_routing_annotation().init(g_vpr_ctx.device().rr_graph); diff --git a/openfpga/src/base/openfpga_setup_command.cpp b/openfpga/src/base/openfpga_setup_command.cpp index 2d08ec38c..e992875bd 100644 --- a/openfpga/src/base/openfpga_setup_command.cpp +++ b/openfpga/src/base/openfpga_setup_command.cpp @@ -8,7 +8,6 @@ #include "openfpga_pb_pin_fixup.h" #include "openfpga_lut_truth_table_fixup.h" #include "check_netlist_naming_conflict.h" -#include "annotate_rr_gsb.h" #include "compact_routing_hierarchy.h" #include "openfpga_setup_command.h" From 4a05cec037d90edb083c5c6c501c349809e4f9c0 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 12 Feb 2020 11:21:40 -0700 Subject: [PATCH 119/645] add rr_segment binding to circuit model --- openfpga/src/annotation/annotate_rr_graph.cpp | 46 ++++++++++++++++++- openfpga/src/vpr_wrapper/vpr_main.cpp | 7 +-- openfpga/test_vpr_arch/k6_frac_N10_40nm.xml | 2 +- 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/openfpga/src/annotation/annotate_rr_graph.cpp b/openfpga/src/annotation/annotate_rr_graph.cpp index efb7bd3a7..ebca2d398 100644 --- a/openfpga/src/annotation/annotate_rr_graph.cpp +++ b/openfpga/src/annotation/annotate_rr_graph.cpp @@ -406,7 +406,7 @@ void annotate_rr_switch_circuit_models(const DeviceContext& vpr_device_ctx, const bool& verbose_output) { size_t count = 0; - for (size_t iswitch = 0; iswitch < vpr_device_ctx.rr_switch_inf.size(); iswitch++) { + for (size_t iswitch = 0; iswitch < vpr_device_ctx.rr_switch_inf.size(); ++iswitch) { std::string switch_name(vpr_device_ctx.rr_switch_inf[iswitch].name); /* Skip the delayless switch, which is only used by the edges between * - SOURCE and OPIN @@ -453,6 +453,47 @@ void annotate_rr_switch_circuit_models(const DeviceContext& vpr_device_ctx, count); } +/******************************************************************** + * Build the link between rr_graph routing segments to their physical circuit models + * The binding is done based on the name of rr_segment defined in the + * OpenFPGA arch XML + *******************************************************************/ +static +void annotate_rr_segment_circuit_models(const DeviceContext& vpr_device_ctx, + const Arch& openfpga_arch, + VprDeviceAnnotation& vpr_device_annotation, + const bool& verbose_output) { + size_t count = 0; + + for (size_t iseg = 0; iseg < vpr_device_ctx.arch->Segments.size(); ++iseg) { + std::string segment_name = vpr_device_ctx.arch->Segments[iseg].name; + CircuitModelId circuit_model = CircuitModelId::INVALID(); + /* The name-to-circuit mapping is stored in either cb_switch-to-circuit or sb_switch-to-circuit, + * Try to find one and update the device annotation + */ + if (0 < openfpga_arch.routing_seg2circuit.count(segment_name)) { + circuit_model = openfpga_arch.routing_seg2circuit.at(segment_name); + } + /* Cannot find a circuit model, error out! */ + if (CircuitModelId::INVALID() == circuit_model) { + VTR_LOG_ERROR("Fail to find a circuit model for a routing segment '%s'!\nPlease check your OpenFPGA architecture XML!\n", + segment_name.c_str()); + exit(1); + } + + /* Now update the device annotation */ + vpr_device_annotation.add_rr_segment_circuit_model(RRSegmentId(iseg), circuit_model); + VTR_LOGV(verbose_output, + "Binded a routing segment '%s' to circuit model '%s'\n", + segment_name.c_str(), + openfpga_arch.circuit_lib.model_name(circuit_model).c_str()); + count++; + } + + VTR_LOG("Binded %lu routing segments to circuit models\n", + count); +} + /******************************************************************** * Build the link between rr_graph switches and segments to their * physical circuit models @@ -463,6 +504,9 @@ void annotate_rr_graph_circuit_models(const DeviceContext& vpr_device_ctx, const bool& verbose_output) { /* Iterate over each rr_switch in the device context and bind with names */ annotate_rr_switch_circuit_models(vpr_device_ctx, openfpga_arch, vpr_device_annotation, verbose_output); + + /* Iterate over each rr_segment in the device context and bind with names */ + annotate_rr_segment_circuit_models(vpr_device_ctx, openfpga_arch, vpr_device_annotation, verbose_output); } } /* end namespace openfpga */ diff --git a/openfpga/src/vpr_wrapper/vpr_main.cpp b/openfpga/src/vpr_wrapper/vpr_main.cpp index bf0f6de91..37560b288 100644 --- a/openfpga/src/vpr_wrapper/vpr_main.cpp +++ b/openfpga/src/vpr_wrapper/vpr_main.cpp @@ -41,20 +41,21 @@ int vpr(int argc, char** argv) { vtr::ScopedFinishTimer t("The entire flow of VPR"); t_options Options = t_options(); - t_arch Arch = t_arch(); + /* Arch should NOT be freed once this function is done */ + t_arch* Arch = new t_arch; t_vpr_setup vpr_setup = t_vpr_setup(); try { vpr_install_signal_handler(); /* Read options, architecture, and circuit netlist */ - vpr_init(argc, const_cast(argv), &Options, &vpr_setup, &Arch); + vpr_init(argc, const_cast(argv), &Options, &vpr_setup, Arch); if (Options.show_version) { return SUCCESS_EXIT_CODE; } - bool flow_succeeded = vpr_flow(vpr_setup, Arch); + bool flow_succeeded = vpr_flow(vpr_setup, *Arch); if (!flow_succeeded) { VTR_LOG("VPR failed to implement circuit\n"); return UNIMPLEMENTABLE_EXIT_CODE; diff --git a/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml index 269384cbc..a090fd45b 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml @@ -132,7 +132,7 @@ - + 1 1 1 1 1 1 1 1 1 From ce63b1cc6288c034f1a85a38229efd94ce7f99dc Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 12 Feb 2020 11:40:20 -0700 Subject: [PATCH 120/645] add circuit model binding for direct connections and enhance model type checking --- openfpga/src/annotation/annotate_rr_graph.cpp | 75 ++++++++++++++++++- .../src/annotation/vpr_device_annotation.cpp | 20 +++++ .../src/annotation/vpr_device_annotation.h | 5 ++ 3 files changed, 98 insertions(+), 2 deletions(-) diff --git a/openfpga/src/annotation/annotate_rr_graph.cpp b/openfpga/src/annotation/annotate_rr_graph.cpp index ebca2d398..351c248bb 100644 --- a/openfpga/src/annotation/annotate_rr_graph.cpp +++ b/openfpga/src/annotation/annotate_rr_graph.cpp @@ -439,6 +439,14 @@ void annotate_rr_switch_circuit_models(const DeviceContext& vpr_device_ctx, switch_name.c_str()); exit(1); } + + /* Check the circuit model type */ + if (CIRCUIT_MODEL_MUX != openfpga_arch.circuit_lib.model_type(circuit_model)) { + VTR_LOG_ERROR("Require circuit model type '%s' for a routing resource graph switch '%s'!\nPlease check your OpenFPGA architecture XML!\n", + CIRCUIT_MODEL_TYPE_STRING[CIRCUIT_MODEL_MUX], + switch_name.c_str()); + exit(1); + } /* Now update the device annotation */ vpr_device_annotation.add_rr_switch_circuit_model(RRSwitchId(iswitch), circuit_model); @@ -480,6 +488,14 @@ void annotate_rr_segment_circuit_models(const DeviceContext& vpr_device_ctx, segment_name.c_str()); exit(1); } + + /* Check the circuit model type */ + if (CIRCUIT_MODEL_CHAN_WIRE != openfpga_arch.circuit_lib.model_type(circuit_model)) { + VTR_LOG_ERROR("Require circuit model type '%s' for a routing segment '%s'!\nPlease check your OpenFPGA architecture XML!\n", + CIRCUIT_MODEL_TYPE_STRING[CIRCUIT_MODEL_CHAN_WIRE], + segment_name.c_str()); + exit(1); + } /* Now update the device annotation */ vpr_device_annotation.add_rr_segment_circuit_model(RRSegmentId(iseg), circuit_model); @@ -495,8 +511,60 @@ void annotate_rr_segment_circuit_models(const DeviceContext& vpr_device_ctx, } /******************************************************************** - * Build the link between rr_graph switches and segments to their - * physical circuit models + * Build the link between rr_graph direct connection to their physical circuit models + * The binding is done based on the name of directs defined in the + * OpenFPGA arch XML + *******************************************************************/ +static +void annotate_direct_circuit_models(const DeviceContext& vpr_device_ctx, + const Arch& openfpga_arch, + VprDeviceAnnotation& vpr_device_annotation, + const bool& verbose_output) { + size_t count = 0; + + for (int idirect = 0; idirect < vpr_device_ctx.arch->num_directs; ++idirect) { + std::string direct_name = vpr_device_ctx.arch->Directs[idirect].name; + CircuitModelId circuit_model = CircuitModelId::INVALID(); + /* The name-to-circuit mapping is stored in either cb_switch-to-circuit or sb_switch-to-circuit, + * Try to find one and update the device annotation + */ + if (0 < openfpga_arch.direct2circuit.count(direct_name)) { + circuit_model = openfpga_arch.direct2circuit.at(direct_name); + } + /* Cannot find a circuit model, error out! */ + if (CircuitModelId::INVALID() == circuit_model) { + VTR_LOG_ERROR("Fail to find a circuit model for a direct connection '%s'!\nPlease check your OpenFPGA architecture XML!\n", + direct_name.c_str()); + exit(1); + } + + /* Check the circuit model type */ + if (CIRCUIT_MODEL_WIRE != openfpga_arch.circuit_lib.model_type(circuit_model)) { + VTR_LOG_ERROR("Require circuit model type '%s' for a direct connection '%s'!\nPlease check your OpenFPGA architecture XML!\n", + CIRCUIT_MODEL_TYPE_STRING[CIRCUIT_MODEL_WIRE], + direct_name.c_str()); + exit(1); + } + + /* Now update the device annotation */ + vpr_device_annotation.add_direct_circuit_model(idirect, circuit_model); + VTR_LOGV(verbose_output, + "Binded a direct connection '%s' to circuit model '%s'\n", + direct_name.c_str(), + openfpga_arch.circuit_lib.model_name(circuit_model).c_str()); + count++; + } + + VTR_LOG("Binded %lu direct connections to circuit models\n", + count); +} + +/******************************************************************** + * Build the link between + * - rr_graph switches + * - rr_graph segments + * - directlist + * to their physical circuit models *******************************************************************/ void annotate_rr_graph_circuit_models(const DeviceContext& vpr_device_ctx, const Arch& openfpga_arch, @@ -507,6 +575,9 @@ void annotate_rr_graph_circuit_models(const DeviceContext& vpr_device_ctx, /* Iterate over each rr_segment in the device context and bind with names */ annotate_rr_segment_circuit_models(vpr_device_ctx, openfpga_arch, vpr_device_annotation, verbose_output); + + /* Iterate over each direct connection in the device context and bind with names */ + annotate_direct_circuit_models(vpr_device_ctx, openfpga_arch, vpr_device_annotation, verbose_output); } } /* end namespace openfpga */ diff --git a/openfpga/src/annotation/vpr_device_annotation.cpp b/openfpga/src/annotation/vpr_device_annotation.cpp index 280e4ca38..aa2e6fce9 100644 --- a/openfpga/src/annotation/vpr_device_annotation.cpp +++ b/openfpga/src/annotation/vpr_device_annotation.cpp @@ -233,6 +233,15 @@ CircuitModelId VprDeviceAnnotation::rr_segment_circuit_model(const RRSegmentId& return rr_segment_circuit_models_.at(rr_segment); } +CircuitModelId VprDeviceAnnotation::direct_circuit_model(const size_t& direct) const { + /* Ensure that the rr_switch is in the list */ + std::map::const_iterator it = direct_circuit_models_.find(direct); + if (it == direct_circuit_models_.end()) { + return CircuitModelId::INVALID(); + } + return direct_circuit_models_.at(direct); +} + /************************************************************************ * Public mutators ***********************************************************************/ @@ -452,4 +461,15 @@ void VprDeviceAnnotation::add_rr_segment_circuit_model(const RRSegmentId& rr_seg rr_segment_circuit_models_[rr_segment] = circuit_model; } +void VprDeviceAnnotation::add_direct_circuit_model(const size_t& direct, const CircuitModelId& circuit_model) { + /* Warn any override attempt */ + std::map::const_iterator it = direct_circuit_models_.find(direct); + if (it != direct_circuit_models_.end()) { + VTR_LOG_WARN("Override the annotation between direct '%ld' and its circuit_model '%ld'!\n", + size_t(direct), size_t(circuit_model)); + } + + direct_circuit_models_[direct] = circuit_model; +} + } /* End namespace openfpga*/ diff --git a/openfpga/src/annotation/vpr_device_annotation.h b/openfpga/src/annotation/vpr_device_annotation.h index 211676264..74868ae61 100644 --- a/openfpga/src/annotation/vpr_device_annotation.h +++ b/openfpga/src/annotation/vpr_device_annotation.h @@ -71,6 +71,7 @@ class VprDeviceAnnotation { t_pb_graph_pin* physical_pb_graph_pin(t_pb_graph_pin* pb_graph_pin) const; CircuitModelId rr_switch_circuit_model(const RRSwitchId& rr_switch) const; CircuitModelId rr_segment_circuit_model(const RRSegmentId& rr_segment) const; + CircuitModelId direct_circuit_model(const size_t& direct) const; public: /* Public mutators */ void add_pb_type_physical_mode(t_pb_type* pb_type, t_mode* physical_mode); void add_physical_pb_type(t_pb_type* operating_pb_type, t_pb_type* physical_pb_type); @@ -90,6 +91,7 @@ class VprDeviceAnnotation { void add_physical_pb_graph_pin(t_pb_graph_pin* operating_pb_graph_pin, t_pb_graph_pin* physical_pb_graph_pin); void add_rr_switch_circuit_model(const RRSwitchId& rr_switch, const CircuitModelId& circuit_model); void add_rr_segment_circuit_model(const RRSegmentId& rr_segment, const CircuitModelId& circuit_model); + void add_direct_circuit_model(const size_t& direct, const CircuitModelId& circuit_model); private: /* Internal data */ /* Pair a regular pb_type to its physical pb_type */ std::map physical_pb_types_; @@ -170,6 +172,9 @@ class VprDeviceAnnotation { /* Pair a Routing Segment (rr_segment) to a circuit model */ std::map rr_segment_circuit_models_; + + /* Pair a direct connection (direct) to a circuit model */ + std::map direct_circuit_models_; }; } /* End namespace openfpga*/ From c78d3e9af175441d32d1dbee4aac9ef91e924f7c Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 12 Feb 2020 14:58:23 -0700 Subject: [PATCH 121/645] add mux library builder --- openfpga/src/base/openfpga_context.h | 6 + openfpga/src/base/openfpga_link_arch.cpp | 31 +++ openfpga/src/mux_lib/mux_library_builder.cpp | 228 ++++++++++++++++++ openfpga/src/mux_lib/mux_library_builder.h | 23 ++ .../src/utils/openfpga_rr_graph_utils.cpp | 18 ++ openfpga/src/utils/openfpga_rr_graph_utils.h | 3 + openfpga/src/utils/pb_graph_utils.cpp | 25 ++ openfpga/src/utils/pb_graph_utils.h | 3 + 8 files changed, 337 insertions(+) create mode 100644 openfpga/src/mux_lib/mux_library_builder.cpp create mode 100644 openfpga/src/mux_lib/mux_library_builder.h diff --git a/openfpga/src/base/openfpga_context.h b/openfpga/src/base/openfpga_context.h index aed6b8014..4234de5b4 100644 --- a/openfpga/src/base/openfpga_context.h +++ b/openfpga/src/base/openfpga_context.h @@ -7,6 +7,7 @@ #include "vpr_device_annotation.h" #include "vpr_clustering_annotation.h" #include "vpr_routing_annotation.h" +#include "mux_library.h" #include "device_rr_gsb.h" /******************************************************************** @@ -44,6 +45,7 @@ class OpenfpgaContext : public Context { const openfpga::VprClusteringAnnotation& vpr_clustering_annotation() const { return vpr_clustering_annotation_; } const openfpga::VprRoutingAnnotation& vpr_routing_annotation() const { return vpr_routing_annotation_; } const openfpga::DeviceRRGSB& device_rr_gsb() const { return device_rr_gsb_; } + const openfpga::MuxLibrary& mux_lib() const { return mux_lib_; } public: /* Public mutators */ openfpga::Arch& mutable_arch() { return arch_; } openfpga::VprDeviceAnnotation& mutable_vpr_device_annotation() { return vpr_device_annotation_; } @@ -51,6 +53,7 @@ class OpenfpgaContext : public Context { openfpga::VprClusteringAnnotation& mutable_vpr_clustering_annotation() { return vpr_clustering_annotation_; } openfpga::VprRoutingAnnotation& mutable_vpr_routing_annotation() { return vpr_routing_annotation_; } openfpga::DeviceRRGSB& mutable_device_rr_gsb() { return device_rr_gsb_; } + openfpga::MuxLibrary& mutable_mux_lib() { return mux_lib_; } private: /* Internal data */ /* Data structure to store information from read_openfpga_arch library */ openfpga::Arch arch_; @@ -69,6 +72,9 @@ class OpenfpgaContext : public Context { /* Device-level annotation */ openfpga::DeviceRRGSB device_rr_gsb_; + + /* Library of physical implmentation of routing multiplexers */ + openfpga::MuxLibrary mux_lib_; }; #endif diff --git a/openfpga/src/base/openfpga_link_arch.cpp b/openfpga/src/base/openfpga_link_arch.cpp index bffca3682..78afc93a2 100644 --- a/openfpga/src/base/openfpga_link_arch.cpp +++ b/openfpga/src/base/openfpga_link_arch.cpp @@ -13,6 +13,7 @@ #include "annotate_pb_graph.h" #include "annotate_routing.h" #include "annotate_rr_graph.h" +#include "mux_library_builder.h" #include "openfpga_link_arch.h" /* Include global variables of VPR */ @@ -21,6 +22,28 @@ /* begin namespace openfpga */ namespace openfpga { +/******************************************************************** + * A function to identify if the routing resource graph generated by + * VPR is support by OpenFPGA + * - Currently we only support uni-directional + * It means every routing tracks must have a direction + *******************************************************************/ +static +bool is_vpr_rr_graph_supported(const RRGraph& rr_graph) { + /* Check if the rr_graph is uni-directional*/ + for (const RRNodeId& node : rr_graph.nodes()) { + if (CHANX != rr_graph.node_type(node) && CHANY != rr_graph.node_type(node)) { + continue; + } + if (BI_DIRECTION == rr_graph.node_direction(node)) { + VTR_LOG_ERROR("Routing resource graph is bi-directional. OpenFPGA currently supports uni-directional routing architecture only.\n"); + return false; + } + } + + return true; +} + /******************************************************************** * Top-level function to link openfpga architecture to VPR, including: * - physical pb_type @@ -72,9 +95,17 @@ void link_arch(OpenfpgaContext& openfpga_context, * - RRGSB * - DeviceRRGSB */ + if (false == is_vpr_rr_graph_supported(g_vpr_ctx.device().rr_graph)) { + return; + } + annotate_device_rr_gsb(g_vpr_ctx.device(), openfpga_context.mutable_device_rr_gsb(), cmd_context.option_enable(cmd, opt_verbose)); + + /* Build multiplexer library */ + openfpga_context.mutable_mux_lib() = build_device_mux_library(g_vpr_ctx.device(), + const_cast(openfpga_context)); } } /* end namespace openfpga */ diff --git a/openfpga/src/mux_lib/mux_library_builder.cpp b/openfpga/src/mux_lib/mux_library_builder.cpp new file mode 100644 index 000000000..953dbb308 --- /dev/null +++ b/openfpga/src/mux_lib/mux_library_builder.cpp @@ -0,0 +1,228 @@ +/******************************************************************** + * This file includes the functions of builders for MuxLibrary. + *******************************************************************/ +#include + +/* Headers from vtrutil library */ +#include "vtr_time.h" +#include "vtr_assert.h" +#include "vtr_log.h" + +/* Headers from readarchopenfpga library */ +#include "circuit_types.h" +#include "circuit_library.h" + +#include "mux_utils.h" +#include "pb_type_utils.h" +#include "pb_graph_utils.h" +#include "openfpga_rr_graph_utils.h" + +#include "mux_library.h" +#include "mux_library_builder.h" + +/* Begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Update MuxLibrary with the unique multiplexer structures + * found in the global routing architecture + *******************************************************************/ +static +void build_routing_arch_mux_library(const DeviceContext& vpr_device_ctx, + const CircuitLibrary& circuit_lib, + const VprDeviceAnnotation& vpr_device_annotation, + MuxLibrary& mux_lib) { + /* The routing path is. + * OPIN ----> CHAN ----> ... ----> CHAN ----> IPIN + * Each edge is a switch, for IPIN, the switch is a connection block, + * for the rest is a switch box + */ + /* Count the sizes of muliplexers in routing architecture */ + for (const RRNodeId& node : vpr_device_ctx.rr_graph.nodes()) { + switch (vpr_device_ctx.rr_graph.node_type(node)) { + case IPIN: + case CHANX: + case CHANY: { + /* Have to consider the fan_in only, it is a connection block (multiplexer)*/ + if ( (0 == vpr_device_ctx.rr_graph.node_in_edges(node).size()) + || (1 == vpr_device_ctx.rr_graph.node_in_edges(node).size()) ) { + break; + } + /* Find the circuit_model for multiplexers in connection blocks */ + std::vector driver_switches = get_rr_graph_driver_switches(vpr_device_ctx.rr_graph, node); + VTR_ASSERT(1 == driver_switches.size()); + const CircuitModelId& rr_switch_circuit_model = vpr_device_annotation.rr_switch_circuit_model(driver_switches[0]); + /* we should select a circuit model for the routing resource switch */ + VTR_ASSERT(CircuitModelId::INVALID() != rr_switch_circuit_model); + /* Add the mux to mux_library */ + mux_lib.add_mux(circuit_lib, rr_switch_circuit_model, vpr_device_ctx.rr_graph.node_in_edges(node).size()); + break; + } + default: + /* We do not care other types of rr_node */ + break; + } + } +} + + +/******************************************************************** + * For a given pin of a pb_graph_node + * - Identify the interconnect implementation + * - Find the number of inputs for the interconnect implementation + * - Update the mux_library is the implementation is multiplexers + ********************************************************************/ +static +void build_pb_graph_pin_interconnect_mux_library(t_pb_graph_pin* pb_graph_pin, + t_mode* interconnect_mode, + const CircuitLibrary& circuit_lib, + const VprDeviceAnnotation& vpr_device_annotation, + MuxLibrary& mux_lib) { + /* Find the interconnect in the physical mode that drives this pin */ + t_interconnect* physical_interc = pb_graph_pin_interc(pb_graph_pin, interconnect_mode); + /* Bypass if the interc does not indicate multiplexers */ + if (MUX_INTERC != vpr_device_annotation.interconnect_physical_type(physical_interc)) { + return; + } + size_t mux_size = pb_graph_pin_inputs(pb_graph_pin, physical_interc).size(); + VTR_ASSERT(true == valid_mux_implementation_num_inputs(mux_size)); + const CircuitModelId& interc_circuit_model = vpr_device_annotation.interconnect_circuit_model(physical_interc); + VTR_ASSERT(CircuitModelId::INVALID() != interc_circuit_model); + /* Add the mux model to library */ + mux_lib.add_mux(circuit_lib, interc_circuit_model, mux_size); +} + +/******************************************************************** + * Update MuxLibrary with the unique multiplexer structures + * found in programmable logic blocks + ********************************************************************/ +static +void rec_build_vpr_physical_pb_graph_node_mux_library(t_pb_graph_node* pb_graph_node, + const CircuitLibrary& circuit_lib, + const VprDeviceAnnotation& vpr_device_annotation, + MuxLibrary& mux_lib) { + /* Find the number of inputs for each interconnect of this pb_graph_node + * This is only applicable to each interconnect which will be implemented with multiplexers + */ + t_mode* parent_physical_mode = nullptr; + + if (false == pb_graph_node->is_root()) { + parent_physical_mode = vpr_device_annotation.physical_mode(pb_graph_node->parent_pb_graph_node->pb_type); + } + + for (int iport = 0; iport < pb_graph_node->num_input_ports; ++iport) { + for (int ipin = 0; ipin < pb_graph_node->num_input_pins[iport]; ++ipin) { + build_pb_graph_pin_interconnect_mux_library(&(pb_graph_node->input_pins[iport][ipin]), + parent_physical_mode, + circuit_lib, + vpr_device_annotation, + mux_lib); + } + } + + for (int iport = 0; iport < pb_graph_node->num_clock_ports; ++iport) { + for (int ipin = 0; ipin < pb_graph_node->num_clock_pins[iport]; ++ipin) { + build_pb_graph_pin_interconnect_mux_library(&(pb_graph_node->clock_pins[iport][ipin]), + parent_physical_mode, + circuit_lib, + vpr_device_annotation, + mux_lib); + } + } + + /* Return if this is a primitive node */ + if (true == is_primitive_pb_type(pb_graph_node->pb_type)) { + return; + } + + /* Get the physical mode, primitive node does not have physical mode */ + t_mode* physical_mode = vpr_device_annotation.physical_mode(pb_graph_node->pb_type); + VTR_ASSERT(nullptr != physical_mode); + + /* Note that the output port interconnect should be considered for non-primitive nodes */ + for (int iport = 0; iport < pb_graph_node->num_output_ports; ++iport) { + for (int ipin = 0; ipin < pb_graph_node->num_output_pins[iport]; ++ipin) { + build_pb_graph_pin_interconnect_mux_library(&(pb_graph_node->output_pins[iport][ipin]), + physical_mode, + circuit_lib, + vpr_device_annotation, + mux_lib); + } + } + + /* Visit all the child nodes in the physical mode */ + for (int ipb = 0; ipb < physical_mode->num_pb_type_children; ++ipb) { + for (int jpb = 0; jpb < physical_mode->pb_type_children[ipb].num_pb; ++jpb) { + rec_build_vpr_physical_pb_graph_node_mux_library(&(pb_graph_node->child_pb_graph_nodes[physical_mode->index][ipb][jpb]), + circuit_lib, vpr_device_annotation, + mux_lib); + } + } +} + +/******************************************************************** + * Update MuxLibrary with the unique multiplexers required by + * LUTs in the circuit library + ********************************************************************/ +static +void build_lut_mux_library(MuxLibrary& mux_lib, + const CircuitLibrary& circuit_lib) { + /* Find all the circuit models which are LUTs in the circuit library */ + for (const auto& circuit_model : circuit_lib.models()) { + /* Bypass non-LUT circuit models */ + if (CIRCUIT_MODEL_LUT != circuit_lib.model_type(circuit_model)) { + continue; + } + /* Find the MUX size required by the LUT */ + /* Get input ports which are not global ports! */ + std::vector input_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_INPUT, true); + VTR_ASSERT(1 == input_ports.size()); + /* MUX size = 2^lut_size */ + size_t lut_mux_size = (size_t)pow(2., (double)(circuit_lib.port_size(input_ports[0]))); + /* Add mux to the mux library */ + mux_lib.add_mux(circuit_lib, circuit_model, lut_mux_size); + } +} + +/* Statistic for all the multiplexers in FPGA + * We determine the sizes and its structure (according to spice_model) for each type of multiplexers + * We search multiplexers in Switch Blocks, Connection blocks and Configurable Logic Blocks + * In additional to multiplexers, this function also consider crossbars. + * All the statistics are stored in a linked list, as a return value + */ +MuxLibrary build_device_mux_library(const DeviceContext& vpr_device_ctx, + const OpenfpgaContext& openfpga_ctx) { + vtr::ScopedStartFinishTimer timer("Build a library of physical multiplexers"); + + /* MuxLibrary to store the information of Multiplexers*/ + MuxLibrary mux_lib; + + /* Step 1: We should check the multiplexer spice models defined in routing architecture.*/ + build_routing_arch_mux_library(vpr_device_ctx, openfpga_ctx.arch().circuit_lib, + openfpga_ctx.vpr_device_annotation(), + mux_lib); + + /* Step 2: Count the sizes of multiplexers in complex logic blocks */ + for (const t_logical_block_type& lb_type : vpr_device_ctx.logical_block_types) { + /* By pass nullptr for pb_graph head */ + if (nullptr == lb_type.pb_graph_head) { + continue; + } + rec_build_vpr_physical_pb_graph_node_mux_library(lb_type.pb_graph_head, + openfpga_ctx.arch().circuit_lib, + openfpga_ctx.vpr_device_annotation(), + mux_lib); + } + + /* Step 3: count the size of multiplexer that will be used in LUTs*/ + build_lut_mux_library(mux_lib, openfpga_ctx.arch().circuit_lib); + + VTR_LOG("Built a multiplexer library of %lu physical multiplexers.\n", + mux_lib.muxes().size()); + VTR_LOG("Maximum multiplexer size is %lu.\n", + mux_lib.max_mux_size()); + + return mux_lib; +} + +} /* End namespace openfpga*/ diff --git a/openfpga/src/mux_lib/mux_library_builder.h b/openfpga/src/mux_lib/mux_library_builder.h new file mode 100644 index 000000000..d303e7e76 --- /dev/null +++ b/openfpga/src/mux_lib/mux_library_builder.h @@ -0,0 +1,23 @@ +#ifndef MUX_LIBRARY_BUILDER_H +#define MUX_LIBRARY_BUILDER_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "vpr_context.h" +#include "openfpga_context.h" +#include "mux_library.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +MuxLibrary build_device_mux_library(const DeviceContext& vpr_device_ctx, + const OpenfpgaContext& openfpga_ctx); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/utils/openfpga_rr_graph_utils.cpp b/openfpga/src/utils/openfpga_rr_graph_utils.cpp index cac7284d5..21ec1b640 100644 --- a/openfpga/src/utils/openfpga_rr_graph_utils.cpp +++ b/openfpga/src/utils/openfpga_rr_graph_utils.cpp @@ -2,6 +2,8 @@ * This file includes most utilized functions for the rr_graph * data structure in the OpenFPGA context *******************************************************************/ +#include + /* Headers from vtrutil library */ #include "vtr_assert.h" #include "vtr_log.h" @@ -63,4 +65,20 @@ vtr::Point get_track_rr_node_end_coordinate(const RRGraph& rr_graph, return end_coordinator; } +/************************************************************************ + * Find the driver switches for a node in the rr_graph + * This function only return unique driver switches + ***********************************************************************/ +std::vector get_rr_graph_driver_switches(const RRGraph& rr_graph, + const RRNodeId& node) { + std::vector driver_switches; + for (const RREdgeId& edge : rr_graph.node_in_edges(node)) { + if (driver_switches.end() == std::find(driver_switches.begin(), driver_switches.end(), rr_graph.edge_switch(edge))) { + driver_switches.push_back(rr_graph.edge_switch(edge)); + } + } + + return driver_switches; +} + } /* end namespace openfpga */ diff --git a/openfpga/src/utils/openfpga_rr_graph_utils.h b/openfpga/src/utils/openfpga_rr_graph_utils.h index 44306664b..0c4d174c6 100644 --- a/openfpga/src/utils/openfpga_rr_graph_utils.h +++ b/openfpga/src/utils/openfpga_rr_graph_utils.h @@ -23,6 +23,9 @@ vtr::Point get_track_rr_node_start_coordinate(const RRGraph& rr_graph, vtr::Point get_track_rr_node_end_coordinate(const RRGraph& rr_graph, const RRNodeId& track_rr_node); +std::vector get_rr_graph_driver_switches(const RRGraph& rr_graph, + const RRNodeId& node); + } /* end namespace openfpga */ #endif diff --git a/openfpga/src/utils/pb_graph_utils.cpp b/openfpga/src/utils/pb_graph_utils.cpp index 84e642bde..a3d6a999e 100644 --- a/openfpga/src/utils/pb_graph_utils.cpp +++ b/openfpga/src/utils/pb_graph_utils.cpp @@ -38,4 +38,29 @@ std::vector pb_graph_pin_inputs(t_pb_graph_pin* pb_graph_pin, return inputs; } +/******************************************************************** + * This function aims to find out the interconnect that drives + * a given pb_graph pin when operating in a select mode + *******************************************************************/ +t_interconnect* pb_graph_pin_interc(t_pb_graph_pin* pb_graph_pin, + t_mode* selected_mode) { + t_interconnect* interc = nullptr; + + /* Search the input edges only, stats on the size of MUX we may need (fan-in) */ + for (int iedge = 0; iedge < pb_graph_pin->num_input_edges; ++iedge) { + /* We care the only edges in the selected mode */ + if (selected_mode != pb_graph_pin->input_edges[iedge]->interconnect->parent_mode) { + continue; + } + /* There should be one unique interconnect to be found! */ + if (nullptr != interc) { + VTR_ASSERT(interc == pb_graph_pin->input_edges[iedge]->interconnect); + } else { + interc = pb_graph_pin->input_edges[iedge]->interconnect; + } + } + + return interc; +} + } /* end namespace openfpga */ diff --git a/openfpga/src/utils/pb_graph_utils.h b/openfpga/src/utils/pb_graph_utils.h index d46457b7d..8c1c3064a 100644 --- a/openfpga/src/utils/pb_graph_utils.h +++ b/openfpga/src/utils/pb_graph_utils.h @@ -18,6 +18,9 @@ namespace openfpga { std::vector pb_graph_pin_inputs(t_pb_graph_pin* pb_graph_pin, t_interconnect* selected_interconnect); +t_interconnect* pb_graph_pin_interc(t_pb_graph_pin* pb_graph_pin, + t_mode* selected_mode); + } /* end namespace openfpga */ #endif From df3ae60954849c7bf83d799b0c37abd3f23a75cb Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 12 Feb 2020 15:19:40 -0700 Subject: [PATCH 122/645] add default configurable memory model set-up when reading openfpga architecture XML --- .../src/openfpga_arch_linker.cpp | 47 +++++++++++++++++++ .../src/openfpga_arch_linker.h | 3 ++ .../src/read_xml_openfpga_arch.cpp | 7 +++ 3 files changed, 57 insertions(+) diff --git a/libopenfpga/libarchopenfpga/src/openfpga_arch_linker.cpp b/libopenfpga/libarchopenfpga/src/openfpga_arch_linker.cpp index 27b065053..90ae1aad3 100644 --- a/libopenfpga/libarchopenfpga/src/openfpga_arch_linker.cpp +++ b/libopenfpga/libarchopenfpga/src/openfpga_arch_linker.cpp @@ -3,6 +3,8 @@ * data structures inside the openfpga arch data structure *******************************************************************/ #include "vtr_log.h" +#include "vtr_assert.h" + #include "openfpga_arch_linker.h" /******************************************************************** @@ -21,3 +23,48 @@ void link_config_protocol_to_circuit_library(openfpga::Arch& openfpga_arch) { openfpga_arch.config_protocol.set_memory_model(config_memory_model); } + +/******************************************************************** + * Link the circuit model of SRAM ports of each circuit model + * to a default SRAM circuit model. + * This function aims to ease the XML writing, allowing users to skip + * the circuit model definition for SRAM ports that are used by default + * TODO: Maybe deprecated as we prefer strict definition + *******************************************************************/ +void config_circuit_models_sram_port_to_default_sram_model(CircuitLibrary& circuit_lib, + const CircuitModelId& default_sram_model) { + VTR_ASSERT(CircuitModelId::INVALID() != default_sram_model); + + for (const auto& model : circuit_lib.models()) { + for (const auto& port : circuit_lib.model_ports(model)) { + /* Bypass non SRAM ports */ + if (CIRCUIT_MODEL_PORT_SRAM != circuit_lib.port_type(port)) { + continue; + } + + /* Write for the default SRAM SPICE model! */ + circuit_lib.set_port_tri_state_model_id(port, default_sram_model); + + /* Only show warning when we try to override the given spice_model_name ! */ + if (true == circuit_lib.port_tri_state_model_name(port).empty()) { + VTR_LOG("Use the default configurable memory model '%s' for circuit model '%s' port '%s')\n", + circuit_lib.model_name(default_sram_model).c_str(), + circuit_lib.model_name(model).c_str(), + circuit_lib.port_prefix(port).c_str()); + continue; + } + + /* Give a warning !!! */ + if (circuit_lib.model_name(default_sram_model) != circuit_lib.port_tri_state_model_name(port)) { + VTR_LOG_WARN("Overwrite SRAM circuit model for circuit model port (name:%s, port:%s) to be the correct one (name:%s)!\n", + circuit_lib.model_name(model).c_str(), + circuit_lib.port_prefix(port).c_str(), + circuit_lib.model_name(default_sram_model).c_str()); + } + } + } + /* Rebuild the submodels for circuit_library + * because we have created links for ports + */ + circuit_lib.build_model_links(); +} diff --git a/libopenfpga/libarchopenfpga/src/openfpga_arch_linker.h b/libopenfpga/libarchopenfpga/src/openfpga_arch_linker.h index fea97330a..52e49cb66 100644 --- a/libopenfpga/libarchopenfpga/src/openfpga_arch_linker.h +++ b/libopenfpga/libarchopenfpga/src/openfpga_arch_linker.h @@ -5,4 +5,7 @@ void link_config_protocol_to_circuit_library(openfpga::Arch& openfpga_arch); +void config_circuit_models_sram_port_to_default_sram_model(CircuitLibrary& circuit_lib, + const CircuitModelId& default_sram_model); + #endif diff --git a/libopenfpga/libarchopenfpga/src/read_xml_openfpga_arch.cpp b/libopenfpga/libarchopenfpga/src/read_xml_openfpga_arch.cpp index f30ff4f1e..b35955f97 100644 --- a/libopenfpga/libarchopenfpga/src/read_xml_openfpga_arch.cpp +++ b/libopenfpga/libarchopenfpga/src/read_xml_openfpga_arch.cpp @@ -70,6 +70,13 @@ openfpga::Arch read_xml_openfpga_arch(const char* arch_file_name) { /* Build the internal link between configuration protocol and circuit library */ link_config_protocol_to_circuit_library(openfpga_arch); + /* Now, we can know the default configurable memory model + * Apply it to all the SRAM ports of circuit models + */ + config_circuit_models_sram_port_to_default_sram_model(openfpga_arch.circuit_lib, + openfpga_arch.config_protocol.memory_model()); + + /* Parse the connection block circuit definition */ openfpga_arch.cb_switch2circuit = read_xml_cb_switch_circuit(xml_openfpga_arch, loc_data, openfpga_arch.circuit_lib); From 13fadd0f910adf89bc99be90fcbfcce72260035b Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 12 Feb 2020 15:49:47 -0700 Subject: [PATCH 123/645] move compact routing hierarchy to build_fabric command --- ...ierarchy.cpp => openfpga_build_fabric.cpp} | 29 ++++++++++++++----- ...ng_hierarchy.h => openfpga_build_fabric.h} | 8 ++--- openfpga/src/base/openfpga_setup_command.cpp | 22 +++++++------- openfpga/test_script/s298_k6_frac.openfpga | 10 ++++--- 4 files changed, 44 insertions(+), 25 deletions(-) rename openfpga/src/base/{compact_routing_hierarchy.cpp => openfpga_build_fabric.cpp} (77%) rename openfpga/src/base/{compact_routing_hierarchy.h => openfpga_build_fabric.h} (70%) diff --git a/openfpga/src/base/compact_routing_hierarchy.cpp b/openfpga/src/base/openfpga_build_fabric.cpp similarity index 77% rename from openfpga/src/base/compact_routing_hierarchy.cpp rename to openfpga/src/base/openfpga_build_fabric.cpp index e520b5e3f..4671f3169 100644 --- a/openfpga/src/base/compact_routing_hierarchy.cpp +++ b/openfpga/src/base/openfpga_build_fabric.cpp @@ -7,7 +7,8 @@ #include "device_rr_gsb.h" #include "device_rr_gsb_utils.h" -#include "compact_routing_hierarchy.h" +//#include "build_device_module.h" +#include "openfpga_build_fabric.h" /* Include global variables of VPR */ #include "globals.h" @@ -19,17 +20,14 @@ namespace openfpga { * Identify the unique GSBs from the Device RR GSB arrays * This function should only be called after the GSB builder is done *******************************************************************/ -void compact_routing_hierarchy(OpenfpgaContext& openfpga_context, - const Command& cmd, const CommandContext& cmd_context) { +static +void compress_routing_hierarchy(OpenfpgaContext& openfpga_context, + const bool& verbose_output) { vtr::ScopedStartFinishTimer timer("Identify unique General Switch Blocks (GSBs)"); - CommandOptionId opt_verbose = cmd.option("verbose"); - /* Build unique module lists */ openfpga_context.mutable_device_rr_gsb().build_unique_module(g_vpr_ctx.device().rr_graph); - bool verbose_output = cmd_context.option_enable(cmd, opt_verbose); - /* Report the stats */ VTR_LOGV(verbose_output, "Detected %lu unique X-direction connection blocks from a total of %d (compression rate=%d%)\n", @@ -54,7 +52,24 @@ void compact_routing_hierarchy(OpenfpgaContext& openfpga_context, openfpga_context.device_rr_gsb().get_num_gsb_unique_module(), find_device_rr_gsb_num_gsb_modules(openfpga_context.device_rr_gsb()), 100 * (openfpga_context.device_rr_gsb().get_num_gsb_unique_module() / find_device_rr_gsb_num_gsb_modules(openfpga_context.device_rr_gsb()) - 1)); +} +/******************************************************************** + * Build the module graph for FPGA device + *******************************************************************/ +void build_fabric(OpenfpgaContext& openfpga_context, + const Command& cmd, const CommandContext& cmd_context) { + + CommandOptionId opt_compress_routing = cmd.option("compress_routing"); + CommandOptionId opt_verbose = cmd.option("verbose"); + + if (true == cmd_context.option_enable(cmd, opt_compress_routing)) { + compress_routing_hierarchy(openfpga_context, cmd_context.option_enable(cmd, opt_verbose)); + } + + /* + openfpga_context.mutable_module_graph() = build_device_module_graph(); + */ } } /* end namespace openfpga */ diff --git a/openfpga/src/base/compact_routing_hierarchy.h b/openfpga/src/base/openfpga_build_fabric.h similarity index 70% rename from openfpga/src/base/compact_routing_hierarchy.h rename to openfpga/src/base/openfpga_build_fabric.h index bd3eeefe2..44aa3632c 100644 --- a/openfpga/src/base/compact_routing_hierarchy.h +++ b/openfpga/src/base/openfpga_build_fabric.h @@ -1,5 +1,5 @@ -#ifndef COMPACT_ROUTING_HIERARCHY_H -#define COMPACT_ROUTING_HIERARCHY_H +#ifndef OPENFPGA_BUILD_FABRIC_H +#define OPENFPGA_BUILD_FABRIC_H /******************************************************************** * Include header files that are required by function declaration @@ -15,8 +15,8 @@ /* begin namespace openfpga */ namespace openfpga { -void compact_routing_hierarchy(OpenfpgaContext& openfpga_context, - const Command& cmd, const CommandContext& cmd_context); +void build_fabric(OpenfpgaContext& openfpga_context, + const Command& cmd, const CommandContext& cmd_context); } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_setup_command.cpp b/openfpga/src/base/openfpga_setup_command.cpp index e992875bd..1b6971167 100644 --- a/openfpga/src/base/openfpga_setup_command.cpp +++ b/openfpga/src/base/openfpga_setup_command.cpp @@ -8,7 +8,7 @@ #include "openfpga_pb_pin_fixup.h" #include "openfpga_lut_truth_table_fixup.h" #include "check_netlist_naming_conflict.h" -#include "compact_routing_hierarchy.h" +#include "openfpga_build_fabric.h" #include "openfpga_setup_command.h" /* begin namespace openfpga */ @@ -121,20 +121,22 @@ void add_openfpga_setup_commands(openfpga::Shell& shell) { shell.set_command_dependency(shell_cmd_lut_truth_table_fixup_id, cmd_dependency_lut_truth_table_fixup); /******************************** - * Command 'compact_routing_hierarchy' + * Command 'build_fabric' */ - Command shell_cmd_compact_routing_hierarchy("compact_routing_hierarchy"); + Command shell_cmd_build_fabric("build_fabric"); /* Add an option '--verbose' */ - shell_cmd_compact_routing_hierarchy.add_option("verbose", false, "Show verbose outputs"); + shell_cmd_build_fabric.add_option("compress_routing", false, "Compress the number of unique routing modules by identifying the unique GSBs"); + shell_cmd_build_fabric.add_option("duplicate_grid_pin", false, "Duplicate the pins on the same side of a grid"); + shell_cmd_build_fabric.add_option("verbose", false, "Show verbose outputs"); /* Add command 'compact_routing_hierarchy' to the Shell */ - ShellCommandId shell_cmd_compact_routing_hierarchy_id = shell.add_command(shell_cmd_compact_routing_hierarchy, "Identify the unique GSBs in the routing architecture so that the routing hierarchy of fabric can be compressed"); - shell.set_command_class(shell_cmd_compact_routing_hierarchy_id, openfpga_setup_cmd_class); - shell.set_command_execute_function(shell_cmd_compact_routing_hierarchy_id, compact_routing_hierarchy); - /* The 'compact_routing_hierarchy' command should NOT be executed before 'link_openfpga_arch' */ - std::vector cmd_dependency_compact_routing_hierarchy; + ShellCommandId shell_cmd_build_fabric_id = shell.add_command(shell_cmd_build_fabric, "Build the FPGA fabric in a graph of modules"); + shell.set_command_class(shell_cmd_build_fabric_id, openfpga_setup_cmd_class); + shell.set_command_execute_function(shell_cmd_build_fabric_id, build_fabric); + /* The 'build_fabric' command should NOT be executed before 'link_openfpga_arch' */ + std::vector cmd_dependency_build_fabric; cmd_dependency_lut_truth_table_fixup.push_back(shell_cmd_link_openfpga_arch_id); - shell.set_command_dependency(shell_cmd_compact_routing_hierarchy_id, cmd_dependency_compact_routing_hierarchy); + shell.set_command_dependency(shell_cmd_build_fabric_id, cmd_dependency_build_fabric); } } /* end namespace openfpga */ diff --git a/openfpga/test_script/s298_k6_frac.openfpga b/openfpga/test_script/s298_k6_frac.openfpga index dd87c77df..9d4edcced 100644 --- a/openfpga/test_script/s298_k6_frac.openfpga +++ b/openfpga/test_script/s298_k6_frac.openfpga @@ -11,13 +11,15 @@ link_openfpga_arch check_netlist_naming_conflict --fix --report ./netlist_renaming.xml # Apply fix-up to clustering nets based on routing results -pb_pin_fixup --verbose +pb_pin_fixup #--verbose # Apply fix-up to Look-Up Table truth tables based on packing results -lut_truth_table_fixup --verbose +lut_truth_table_fixup #--verbose -# Compress the routing hierarchy -compact_routing_hierarchy --verbose +# Build the module graph +# - Enabled compression on routing architecture modules +# - Enable pin duplication on grid modules +build_fabric --compress_routing --duplicate_grid_pin --verbose # Finish and exit OpenFPGA exit From f11832b8cf8c8faa1fc8fd6f93244a92a57d12c6 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 12 Feb 2020 17:53:23 -0700 Subject: [PATCH 124/645] start integrating module graph builder --- openfpga/src/base/openfpga_build_fabric.cpp | 9 +- openfpga/src/base/openfpga_context.h | 6 + openfpga/src/base/openfpga_naming.cpp | 1386 +++++++++++++++++ openfpga/src/base/openfpga_naming.h | 262 ++++ openfpga/src/fabric/build_device_module.cpp | 104 ++ openfpga/src/fabric/build_device_module.h | 22 + .../src/fabric/build_essential_modules.cpp | 271 ++++ openfpga/src/fabric/build_essential_modules.h | 30 + openfpga/src/fabric/module_manager.cpp | 777 +++++++++ openfpga/src/fabric/module_manager.h | 238 +++ openfpga/src/fabric/module_manager_fwd.h | 35 + openfpga/src/utils/circuit_library_utils.cpp | 16 +- openfpga/src/utils/circuit_library_utils.h | 1 - openfpga/src/utils/memory_utils.cpp | 385 +++++ openfpga/src/utils/memory_utils.h | 42 + openfpga/src/utils/module_manager_utils.cpp | 1147 ++++++++++++++ openfpga/src/utils/module_manager_utils.h | 128 ++ openfpga/src/utils/pb_type_utils.cpp | 48 + openfpga/src/utils/pb_type_utils.h | 5 + 19 files changed, 4899 insertions(+), 13 deletions(-) create mode 100644 openfpga/src/base/openfpga_naming.cpp create mode 100644 openfpga/src/base/openfpga_naming.h create mode 100644 openfpga/src/fabric/build_device_module.cpp create mode 100644 openfpga/src/fabric/build_device_module.h create mode 100644 openfpga/src/fabric/build_essential_modules.cpp create mode 100644 openfpga/src/fabric/build_essential_modules.h create mode 100644 openfpga/src/fabric/module_manager.cpp create mode 100644 openfpga/src/fabric/module_manager.h create mode 100644 openfpga/src/fabric/module_manager_fwd.h create mode 100644 openfpga/src/utils/memory_utils.cpp create mode 100644 openfpga/src/utils/memory_utils.h create mode 100644 openfpga/src/utils/module_manager_utils.cpp create mode 100644 openfpga/src/utils/module_manager_utils.h diff --git a/openfpga/src/base/openfpga_build_fabric.cpp b/openfpga/src/base/openfpga_build_fabric.cpp index 4671f3169..f8fde4a31 100644 --- a/openfpga/src/base/openfpga_build_fabric.cpp +++ b/openfpga/src/base/openfpga_build_fabric.cpp @@ -7,7 +7,7 @@ #include "device_rr_gsb.h" #include "device_rr_gsb_utils.h" -//#include "build_device_module.h" +#include "build_device_module.h" #include "openfpga_build_fabric.h" /* Include global variables of VPR */ @@ -67,9 +67,10 @@ void build_fabric(OpenfpgaContext& openfpga_context, compress_routing_hierarchy(openfpga_context, cmd_context.option_enable(cmd, opt_verbose)); } - /* - openfpga_context.mutable_module_graph() = build_device_module_graph(); - */ + VTR_LOG("\n"); + + openfpga_context.mutable_module_graph() = build_device_module_graph(g_vpr_ctx.device(), + const_cast(openfpga_context)); } } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_context.h b/openfpga/src/base/openfpga_context.h index 4234de5b4..6f9ee0910 100644 --- a/openfpga/src/base/openfpga_context.h +++ b/openfpga/src/base/openfpga_context.h @@ -8,6 +8,7 @@ #include "vpr_clustering_annotation.h" #include "vpr_routing_annotation.h" #include "mux_library.h" +#include "module_manager.h" #include "device_rr_gsb.h" /******************************************************************** @@ -46,6 +47,7 @@ class OpenfpgaContext : public Context { const openfpga::VprRoutingAnnotation& vpr_routing_annotation() const { return vpr_routing_annotation_; } const openfpga::DeviceRRGSB& device_rr_gsb() const { return device_rr_gsb_; } const openfpga::MuxLibrary& mux_lib() const { return mux_lib_; } + const openfpga::ModuleManager& module_graph() const { return module_graph_; } public: /* Public mutators */ openfpga::Arch& mutable_arch() { return arch_; } openfpga::VprDeviceAnnotation& mutable_vpr_device_annotation() { return vpr_device_annotation_; } @@ -54,6 +56,7 @@ class OpenfpgaContext : public Context { openfpga::VprRoutingAnnotation& mutable_vpr_routing_annotation() { return vpr_routing_annotation_; } openfpga::DeviceRRGSB& mutable_device_rr_gsb() { return device_rr_gsb_; } openfpga::MuxLibrary& mutable_mux_lib() { return mux_lib_; } + openfpga::ModuleManager& mutable_module_graph() { return module_graph_; } private: /* Internal data */ /* Data structure to store information from read_openfpga_arch library */ openfpga::Arch arch_; @@ -75,6 +78,9 @@ class OpenfpgaContext : public Context { /* Library of physical implmentation of routing multiplexers */ openfpga::MuxLibrary mux_lib_; + + /* Fabric module graph */ + openfpga::ModuleManager module_graph_; }; #endif diff --git a/openfpga/src/base/openfpga_naming.cpp b/openfpga/src/base/openfpga_naming.cpp new file mode 100644 index 000000000..19c58a1bc --- /dev/null +++ b/openfpga/src/base/openfpga_naming.cpp @@ -0,0 +1,1386 @@ +/******************************************************************** + * This file includes functions to generate module/port names for + * Verilog and SPICE netlists + * + * IMPORTANT: keep all the naming functions in this file to be + * generic for both Verilog and SPICE generators + ********************************************************************/ +#include "vtr_assert.h" +#include "vtr_log.h" + +#include "openfpga_side_manager.h" +#include "pb_type_utils.h" +#include "circuit_library_utils.h" +#include "openfpga_naming.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/************************************************ + * Generate the node name for a multiplexing structure + * Case 1 : If there is an intermediate buffer followed by, + * the node name will be mux_l_in_buf + * Case 1 : If there is NO intermediate buffer followed by, + * the node name will be mux_l_in + ***********************************************/ +std::string generate_mux_node_name(const size_t& node_level, + const bool& add_buffer_postfix) { + /* Generate the basic node_name */ + std::string node_name = "mux_l" + std::to_string(node_level) + "_in"; + + /* Add a postfix upon requests */ + if (true == add_buffer_postfix) { + /* '1' indicates that the location is needed */ + node_name += "_buf"; + } + + return node_name; +} + + /************************************************ + * Generate the instance name for a branch circuit in multiplexing structure + * Case 1 : If there is an intermediate buffer followed by, + * the node name will be mux_l_in_buf + * Case 1 : If there is NO intermediate buffer followed by, + * the node name will be mux_l_in + ***********************************************/ +std::string generate_mux_branch_instance_name(const size_t& node_level, + const size_t& node_index_at_level, + const bool& add_buffer_postfix) { + return std::string(generate_mux_node_name(node_level, add_buffer_postfix) + "_" + std::to_string(node_index_at_level) + "_"); +} + +/************************************************ + * Generate the module name for a multiplexer in Verilog format + * Different circuit model requires different names: + * 1. LUTs are named as _mux + * 2. MUXes are named as _size + ***********************************************/ +std::string generate_mux_subckt_name(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const size_t& mux_size, + const std::string& postfix) { + std::string module_name = circuit_lib.model_name(circuit_model); + /* Check the model type and give different names */ + if (CIRCUIT_MODEL_MUX == circuit_lib.model_type(circuit_model)) { + module_name += "_size"; + module_name += std::to_string(mux_size); + } else { + VTR_ASSERT(CIRCUIT_MODEL_LUT == circuit_lib.model_type(circuit_model)); + module_name += "_mux"; + } + + /* Add postfix if it is not empty */ + if (!postfix.empty()) { + module_name += postfix; + } + + return module_name; +} + +/************************************************ + * Generate the module name of a branch for a + * multiplexer in Verilog format + ***********************************************/ +std::string generate_mux_branch_subckt_name(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const size_t& mux_size, + const size_t& branch_mux_size, + const std::string& postfix) { + /* If the tgate circuit model of this MUX is a MUX2 standard cell, + * the mux_subckt name will be the name of the standard cell + */ + CircuitModelId subckt_model = circuit_lib.pass_gate_logic_model(circuit_model); + if (CIRCUIT_MODEL_GATE == circuit_lib.model_type(subckt_model)) { + VTR_ASSERT (CIRCUIT_MODEL_GATE_MUX2 == circuit_lib.gate_type(subckt_model)); + return circuit_lib.model_name(subckt_model); + } + std::string branch_postfix = postfix + "_size" + std::to_string(branch_mux_size); + + return generate_mux_subckt_name(circuit_lib, circuit_model, mux_size, branch_postfix); +} + +/************************************************ + * Generate the module name of a local decoder + * for multiplexer + ***********************************************/ +std::string generate_mux_local_decoder_subckt_name(const size_t& addr_size, + const size_t& data_size) { + std::string subckt_name = "decoder"; + subckt_name += std::to_string(addr_size); + subckt_name += "to"; + subckt_name += std::to_string(data_size); + + return subckt_name; +} + +/************************************************ + * Generate the module name of a routing track wire + ***********************************************/ +std::string generate_segment_wire_subckt_name(const std::string& wire_model_name, + const size_t& segment_id) { + std::string segment_wire_subckt_name = wire_model_name + "_seg" + std::to_string(segment_id); + + return segment_wire_subckt_name; +} + +/********************************************************************* + * Generate the port name for the mid-output of a routing track wire + * Mid-output is the output that is wired to a Connection block multiplexer. + * + * | CLB | + * +------------+ + * ^ + * | + * +------------------------------+ + * | Connection block multiplexer | + * +------------------------------+ + * ^ + * | mid-output +-------------- + * +--------------------+ | + * input --->| Routing track wire |--------->| Switch Block + * +--------------------+ output | + * +-------------- + * + ********************************************************************/ +std::string generate_segment_wire_mid_output_name(const std::string& regular_output_name) { + /* TODO: maybe have a postfix? */ + return std::string("mid_" + regular_output_name); +} + +/********************************************************************* + * Generate the module name for a memory sub-circuit + ********************************************************************/ +std::string generate_memory_module_name(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const CircuitModelId& sram_model, + const std::string& postfix) { + return std::string( circuit_lib.model_name(circuit_model) + "_" + circuit_lib.model_name(sram_model) + postfix ); +} + +/********************************************************************* + * Generate the netlist name for a unique routing block + * It could be + * 1. Routing channel + * 2. Connection block + * 3. Switch block + * A unique block id should be given + *********************************************************************/ +std::string generate_routing_block_netlist_name(const std::string& prefix, + const size_t& block_id, + const std::string& postfix) { + return std::string( prefix + std::to_string(block_id) + postfix ); +} + +/********************************************************************* + * Generate the netlist name for a routing block with a given coordinate + * It could be + * 1. Routing channel + * 2. Connection block + * 3. Switch block + *********************************************************************/ +std::string generate_routing_block_netlist_name(const std::string& prefix, + const vtr::Point& coordinate, + const std::string& postfix) { + return std::string( prefix + std::to_string(coordinate.x()) + std::string("_") + std::to_string(coordinate.y()) + postfix ); +} + +/********************************************************************* + * Generate the netlist name for a connection block with a given coordinate + *********************************************************************/ +std::string generate_connection_block_netlist_name(const t_rr_type& cb_type, + const vtr::Point& coordinate, + const std::string& postfix) { + std::string prefix("cb"); + switch (cb_type) { + case CHANX: + prefix += std::string("x_"); + break; + case CHANY: + prefix += std::string("y_"); + break; + default: + VTR_LOG_ERROR("Invalid type of connection block!\n"); + exit(1); + } + + return generate_routing_block_netlist_name(prefix, coordinate, postfix); +} + +/********************************************************************* + * Generate the module name for a unique routing channel + *********************************************************************/ +std::string generate_routing_channel_module_name(const t_rr_type& chan_type, + const size_t& block_id) { + /* Channel must be either CHANX or CHANY */ + VTR_ASSERT( (CHANX == chan_type) || (CHANY == chan_type) ); + + /* Create a map between chan_type and module_prefix */ + std::map module_prefix_map; + /* TODO: use a constexpr string to replace the fixed name? */ + module_prefix_map[CHANX] = std::string("chanx"); + module_prefix_map[CHANY] = std::string("chany"); + + return std::string( module_prefix_map[chan_type] + std::string("_") + std::to_string(block_id) + std::string("_") ); +} + +/********************************************************************* + * Generate the module name for a routing channel with a given coordinate + *********************************************************************/ +std::string generate_routing_channel_module_name(const t_rr_type& chan_type, + const vtr::Point& coordinate) { + /* Channel must be either CHANX or CHANY */ + VTR_ASSERT( (CHANX == chan_type) || (CHANY == chan_type) ); + + /* Create a map between chan_type and module_prefix */ + std::map module_prefix_map; + /* TODO: use a constexpr string to replace the fixed name? */ + module_prefix_map[CHANX] = std::string("chanx"); + module_prefix_map[CHANY] = std::string("chany"); + + return std::string( module_prefix_map[chan_type] + std::to_string(coordinate.x()) + std::string("_") + std::to_string(coordinate.y()) + std::string("_") ); +} + +/********************************************************************* + * Generate the port name for a routing track with a given coordinate + * and port direction + * This function is mainly used in naming routing tracks in the top-level netlists + * where we do need unique names (with coordinates) for each routing tracks + *********************************************************************/ +std::string generate_routing_track_port_name(const t_rr_type& chan_type, + const vtr::Point& coordinate, + const size_t& track_id, + const PORTS& port_direction) { + /* Channel must be either CHANX or CHANY */ + VTR_ASSERT( (CHANX == chan_type) || (CHANY == chan_type) ); + + /* Create a map between chan_type and module_prefix */ + std::map module_prefix_map; + /* TODO: use a constexpr string to replace the fixed name? */ + module_prefix_map[CHANX] = std::string("chanx"); + module_prefix_map[CHANY] = std::string("chany"); + + std::string port_name = module_prefix_map[chan_type]; + port_name += std::string("_" + std::to_string(coordinate.x()) + std::string("__") + std::to_string(coordinate.y()) + std::string("__")); + + switch (port_direction) { + case OUT_PORT: + port_name += std::string("out_"); + break; + case IN_PORT: + port_name += std::string("in_"); + break; + default: + VTR_LOG_ERROR("Invalid direction of chan_rr_node!\n"); + exit(1); + } + + /* Add the track id to the port name */ + port_name += std::to_string(track_id) + std::string("_"); + + return port_name; +} + +/********************************************************************* + * Generate the port name for a routing track in a Switch Block module + * This function is created to ease the PnR for each unique routing module + * So it is mainly used when creating non-top-level modules! + * Note that this function does not include any port coordinate + * Instead, we use the relative location of the pins in the context of routing modules + * so that each module can be instanciated across the fabric + * Even though, port direction must be provided! + *********************************************************************/ +std::string generate_sb_module_track_port_name(const t_rr_type& chan_type, + const e_side& module_side, + const size_t& track_id, + const PORTS& port_direction) { + /* Channel must be either CHANX or CHANY */ + VTR_ASSERT( (CHANX == chan_type) || (CHANY == chan_type) ); + + /* Create a map between chan_type and module_prefix */ + std::map module_prefix_map; + /* TODO: use a constexpr string to replace the fixed name? */ + module_prefix_map[CHANX] = std::string("chanx"); + module_prefix_map[CHANY] = std::string("chany"); + + std::string port_name = module_prefix_map[chan_type]; + port_name += std::string("_"); + + SideManager side_manager(module_side); + port_name += std::string(side_manager.to_string()); + port_name += std::string("_"); + + switch (port_direction) { + case OUT_PORT: + port_name += std::string("out_"); + break; + case IN_PORT: + port_name += std::string("in_"); + break; + default: + VTR_LOG_ERROR("Invalid direction of chan_rr_node!\n"); + exit(1); + } + + /* Add the track id to the port name */ + port_name += std::to_string(track_id) + std::string("_"); + + return port_name; +} + +/********************************************************************* + * Generate the port name for a routing track in a Connection Block module + * This function is created to ease the PnR for each unique routing module + * So it is mainly used when creating non-top-level modules! + * Note that this function does not include any port coordinate + * Instead, we use the relative location of the pins in the context of routing modules + * so that each module can be instanciated across the fabric + * Even though, port direction must be provided! + *********************************************************************/ +std::string generate_cb_module_track_port_name(const t_rr_type& chan_type, + const size_t& track_id, + const PORTS& port_direction) { + /* Channel must be either CHANX or CHANY */ + VTR_ASSERT( (CHANX == chan_type) || (CHANY == chan_type) ); + + /* Create a map between chan_type and module_prefix */ + std::map module_prefix_map; + /* TODO: use a constexpr string to replace the fixed name? */ + module_prefix_map[CHANX] = std::string("chanx"); + module_prefix_map[CHANY] = std::string("chany"); + + std::string port_name = module_prefix_map[chan_type]; + port_name += std::string("_"); + + switch (port_direction) { + case OUT_PORT: + port_name += std::string("out_"); + break; + case IN_PORT: + port_name += std::string("in_"); + break; + default: + VTR_LOG_ERROR("Invalid direction of chan_rr_node!\n"); + exit(1); + } + + /* Add the track id to the port name */ + port_name += std::to_string(track_id) + std::string("_"); + + return port_name; +} + +/********************************************************************* + * Generate the middle output port name for a routing track + * with a given coordinate + *********************************************************************/ +std::string generate_routing_track_middle_output_port_name(const t_rr_type& chan_type, + const vtr::Point& coordinate, + const size_t& track_id) { + /* Channel must be either CHANX or CHANY */ + VTR_ASSERT( (CHANX == chan_type) || (CHANY == chan_type) ); + + /* Create a map between chan_type and module_prefix */ + std::map module_prefix_map; + /* TODO: use a constexpr string to replace the fixed name? */ + module_prefix_map[CHANX] = std::string("chanx"); + module_prefix_map[CHANY] = std::string("chany"); + + std::string port_name = module_prefix_map[chan_type]; + port_name += std::string("_" + std::to_string(coordinate.x()) + std::string("__") + std::to_string(coordinate.y()) + std::string("__")); + + port_name += std::string("midout_"); + + /* Add the track id to the port name */ + port_name += std::to_string(track_id) + std::string("_"); + + return port_name; +} + +/********************************************************************* + * Generate the module name for a switch block with a given coordinate + *********************************************************************/ +std::string generate_switch_block_module_name(const vtr::Point& coordinate) { + return std::string( "sb_" + std::to_string(coordinate.x()) + std::string("__") + std::to_string(coordinate.y()) + std::string("_") ); +} + +/********************************************************************* + * Generate the module name for a connection block with a given coordinate + *********************************************************************/ +std::string generate_connection_block_module_name(const t_rr_type& cb_type, + const vtr::Point& coordinate) { + std::string prefix("cb"); + switch (cb_type) { + case CHANX: + prefix += std::string("x_"); + break; + case CHANY: + prefix += std::string("y_"); + break; + default: + VTR_LOG_ERROR("Invalid type of connection block!\n"); + exit(1); + } + + return std::string( prefix + std::to_string(coordinate.x()) + std::string("__") + std::to_string(coordinate.y()) + std::string("_") ); +} + +/********************************************************************* + * Generate the port name for a grid in top-level netlists, i.e., full FPGA fabric + * This function will generate a full port name including coordinates + * so that each pin in top-level netlists is unique! + *********************************************************************/ +std::string generate_grid_port_name(const vtr::Point& coordinate, + const size_t& width, + const size_t& height, + const e_side& side, + const size_t& pin_id, + const bool& for_top_netlist) { + if (true == for_top_netlist) { + std::string port_name = std::string("grid_"); + port_name += std::to_string(coordinate.x()); + port_name += std::string("__"); + port_name += std::to_string(coordinate.y()); + port_name += std::string("__pin_"); + port_name += std::to_string(height); + port_name += std::string("__"); + port_name += std::to_string(size_t(side)); + port_name += std::string("__"); + port_name += std::to_string(pin_id); + port_name += std::string("_"); + return port_name; + } + /* For non-top netlist */ + VTR_ASSERT( false == for_top_netlist ); + SideManager side_manager(side); + std::string port_name = std::string(side_manager.to_string()); + port_name += std::string("_width_"); + port_name += std::to_string(width); + port_name += std::string("_height_"); + port_name += std::to_string(height); + port_name += std::string("__pin_"); + port_name += std::to_string(pin_id); + port_name += std::string("_"); + return port_name; +} + +/********************************************************************* + * Generate the port name for a grid with duplication + * This function will generate two types of port names. + * One with a postfix of "upper" + * The other with a postfix of "lower" + *********************************************************************/ +std::string generate_grid_duplicated_port_name(const size_t& width, + const size_t& height, + const e_side& side, + const size_t& pin_id, + const bool& upper_port) { + /* For non-top netlist */ + SideManager side_manager(side); + std::string port_name = std::string(side_manager.to_string()); + port_name += std::string("_width_"); + port_name += std::to_string(width); + port_name += std::string("_height_"); + port_name += std::to_string(height); + port_name += std::string("_pin_"); + port_name += std::to_string(pin_id); + port_name += std::string("_"); + + if (true == upper_port) { + port_name += std::string("upper"); + } else { + VTR_ASSERT_SAFE(false == upper_port); + port_name += std::string("lower"); + } + + return port_name; +} + + +/********************************************************************* + * Generate the port name for a grid in the context of a module + * To keep a short and simple name, this function will not + * include any grid coorindate information! + *********************************************************************/ +std::string generate_grid_module_port_name(const size_t& pin_id) { + /* For non-top netlist */ + std::string port_name = std::string("grid_"); + port_name += std::string("pin_"); + port_name += std::to_string(pin_id); + port_name += std::string("_"); + return port_name; +} + +/********************************************************************* + * Generate the port name for a Grid + * This is a wrapper function for generate_port_name() + * which can automatically decode the port name by the pin side and height + *********************************************************************/ +std::string generate_grid_side_port_name(const DeviceGrid& grids, + const vtr::Point& coordinate, + const e_side& side, + const size_t& pin_id) { + /* Output the pins on the side*/ + size_t width = grids[coordinate.x()][coordinate.y()].type->pin_width_offset[pin_id]; + size_t height = grids[coordinate.x()][coordinate.y()].type->pin_height_offset[pin_id]; + if (true != grids[coordinate.x()][coordinate.y()].type->pinloc[width][height][side][pin_id]) { + SideManager side_manager(side); + VTR_LOG_ERROR("Fail to generate a grid pin (x=%lu, y=%lu, width=%lu, height=%lu, side=%s, index=%d)\n", + coordinate.x(), coordinate.y(), width, height, side_manager.c_str(), pin_id); + exit(1); + } + return generate_grid_port_name(coordinate, width, height, side, pin_id, true); +} + +/********************************************************************* + * Generate the port name of a grid pin for a routing module, + * which could be a switch block or a connection block + * Note that to ensure unique grid port name in the context of a routing module, + * we need a prefix which denotes the relative location of the port in the routing module + * + * The prefix is created by considering the the grid coordinate + * and switch block coordinate + * Detailed rules in conversion is as follows: + * + * top_left top_right + * +------------------------+ + * left_top | | right_top + * | Switch Block | + * | [x][y] | + * | | + * | | + * left_right | | right_bottom + * +------------------------+ + * bottom_left bottom_right + * + * +-------------------------------------------------------- + * | Grid Coordinate | Pin side of grid | module side + * +-------------------------------------------------------- + * | [x][y+1] | right | top_left + * +-------------------------------------------------------- + * | [x][y+1] | bottom | left_top + * +-------------------------------------------------------- + * | [x+1][y+1] | left | top_right + * +-------------------------------------------------------- + * | [x+1][y+1] | bottom | right_top + * +-------------------------------------------------------- + * | [x][y] | top | left_right + * +-------------------------------------------------------- + * | [x][y] | right | bottom_left + * +-------------------------------------------------------- + * | [x+1][y] | top | right_bottom + * +-------------------------------------------------------- + * | [x+1][y] | left | bottom_right + * +-------------------------------------------------------- + * + *********************************************************************/ +std::string generate_sb_module_grid_port_name(const e_side& sb_side, + const e_side& grid_side, + const size_t& pin_id) { + SideManager sb_side_manager(sb_side); + SideManager grid_side_manager(grid_side); + /* Relative location is opposite to the side in grid context */ + grid_side_manager.set_opposite(); + std::string prefix = sb_side_manager.to_string() + std::string("_") + grid_side_manager.to_string(); + return prefix + std::string("_") + generate_grid_module_port_name(pin_id); +} + +/********************************************************************* + * Generate the port name of a grid pin for a routing module, + * which could be a switch block or a connection block + * Note that to ensure unique grid port name in the context of a routing module, + * we need a prefix which denotes the relative location of the port in the routing module + *********************************************************************/ +std::string generate_cb_module_grid_port_name(const e_side& cb_side, + const size_t& pin_id) { + SideManager side_manager(cb_side); + std::string prefix = side_manager.to_string(); + return prefix + std::string("_") + generate_grid_module_port_name(pin_id); +} + +/********************************************************************* + * Generate the port name for a reserved sram port, i.e., BLB/WL port + * When port_type is BLB, a string denoting to the reserved BLB port is generated + * When port_type is WL, a string denoting to the reserved WL port is generated + * + * DO NOT put any SRAM organization check codes HERE!!! + * Even though the reserved BLB/WL ports are used by RRAM-based FPGA only, + * try to keep this function does simple job. + * Check codes should be added outside, when print the ports to files!!! + *********************************************************************/ +std::string generate_reserved_sram_port_name(const e_circuit_model_port_type& port_type) { + VTR_ASSERT( (port_type == CIRCUIT_MODEL_PORT_BLB) || (port_type == CIRCUIT_MODEL_PORT_WL) ); + + if (CIRCUIT_MODEL_PORT_BLB == port_type) { + return std::string("reserved_blb"); + } + return std::string("reserved_wl"); +} + +/********************************************************************* + * Generate the port name for a sram port, used for formal verification + * The port name is named after the cell name of SRAM in circuit library + *********************************************************************/ +std::string generate_formal_verification_sram_port_name(const CircuitLibrary& circuit_lib, + const CircuitModelId& sram_model) { + std::string port_name = circuit_lib.model_name(sram_model) + std::string("_out_fm"); + + return port_name; +} + +/********************************************************************* + * Generate the head port name of a configuration chain + * TODO: This could be replaced as a constexpr string + *********************************************************************/ +std::string generate_configuration_chain_head_name() { + return std::string("ccff_head"); +} + +/********************************************************************* + * Generate the tail port name of a configuration chain + * TODO: This could be replaced as a constexpr string + *********************************************************************/ +std::string generate_configuration_chain_tail_name() { + return std::string("ccff_tail"); +} + +/********************************************************************* + * Generate the memory output port name of a configuration chain + * TODO: This could be replaced as a constexpr string + *********************************************************************/ +std::string generate_configuration_chain_data_out_name() { + return std::string("mem_out"); +} + +/********************************************************************* + * Generate the inverted memory output port name of a configuration chain + * TODO: This could be replaced as a constexpr string + *********************************************************************/ +std::string generate_configuration_chain_inverted_data_out_name() { + return std::string("mem_outb"); +} + +/********************************************************************* + * Generate the addr port (input) for a local decoder of a multiplexer + * TODO: This could be replaced as a constexpr string + *********************************************************************/ +std::string generate_mux_local_decoder_addr_port_name() { + return std::string("addr"); +} + +/********************************************************************* + * Generate the data port (output) for a local decoder of a multiplexer + * TODO: This could be replaced as a constexpr string + *********************************************************************/ +std::string generate_mux_local_decoder_data_port_name() { + return std::string("data"); +} + +/********************************************************************* + * Generate the inverted data port (output) for a local decoder of a multiplexer + * TODO: This could be replaced as a constexpr string + *********************************************************************/ +std::string generate_mux_local_decoder_data_inv_port_name() { + return std::string("data_inv"); +} + +/********************************************************************* + * Generate the port name of a local configuration bus + * TODO: This could be replaced as a constexpr string + *********************************************************************/ +std::string generate_local_config_bus_port_name() { + return std::string("config_bus"); +} + +/********************************************************************* + * Generate the port name for a regular sram port which appears in the + * port list of a module + * The port name is named after the cell name of SRAM in circuit library + *********************************************************************/ +std::string generate_sram_port_name(const e_config_protocol_type& sram_orgz_type, + const e_circuit_model_port_type& port_type) { + std::string port_name; + + switch (sram_orgz_type) { + case CONFIG_MEM_STANDALONE: { + /* Two types of ports are available: + * (1) Regular output of a SRAM, enabled by port type of INPUT + * (2) Inverted output of a SRAM, enabled by port type of OUTPUT + */ + if (CIRCUIT_MODEL_PORT_INPUT == port_type) { + port_name = std::string("mem_out"); + } else { + VTR_ASSERT( CIRCUIT_MODEL_PORT_OUTPUT == port_type ); + port_name = std::string("mem_outb"); + } + break; + } + case CONFIG_MEM_SCAN_CHAIN: + /* Two types of ports are available: + * (1) Head of a chain of Configuration-chain Flip-Flops (CCFFs), enabled by port type of INPUT + * (2) Tail of a chian of Configuration-chain Flip-flops (CCFFs), enabled by port type of OUTPUT + * +------+ +------+ +------+ + * Head --->| CCFF |--->| CCFF |--->| CCFF |---> Tail + * +------+ +------+ +------+ + */ + if (CIRCUIT_MODEL_PORT_INPUT == port_type) { + port_name = std::string("ccff_head"); + } else { + VTR_ASSERT( CIRCUIT_MODEL_PORT_OUTPUT == port_type ); + port_name = std::string("ccff_tail"); + } + break; + case CONFIG_MEM_MEMORY_BANK: + /* Four types of ports are available: + * (1) Bit Lines (BLs) of a SRAM cell, enabled by port type of BL + * (2) Word Lines (WLs) of a SRAM cell, enabled by port type of WL + * (3) Inverted Bit Lines (BLBs) of a SRAM cell, enabled by port type of BLB + * (4) Inverted Word Lines (WLBs) of a SRAM cell, enabled by port type of WLB + * + * BL BLB WL WLB BL BLB WL WLB BL BLB WL WLB + * [0] [0] [0] [0] [1] [1] [1] [1] [i] [i] [i] [i] + * ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ + * | | | | | | | | | | | | + * +----------+ +----------+ +----------+ + * | SRAM | | SRAM | ... | SRAM | + * +----------+ +----------+ +----------+ + */ + if (CIRCUIT_MODEL_PORT_BL == port_type) { + port_name = std::string("bl"); + } else if (CIRCUIT_MODEL_PORT_WL == port_type) { + port_name = std::string("wl"); + } else if (CIRCUIT_MODEL_PORT_BLB == port_type) { + port_name = std::string("blb"); + } else { + VTR_ASSERT( CIRCUIT_MODEL_PORT_WLB == port_type ); + port_name = std::string("wlb"); + } + break; + default: + VTR_LOG_ERROR("Invalid type of SRAM organization !\n"); + exit(1); + } + + return port_name; +} + +/********************************************************************* + * Generate the port name for a regular sram port which is an internal + * wire of a module + * The port name is named after the cell name of SRAM in circuit library + *********************************************************************/ +std::string generate_sram_local_port_name(const CircuitLibrary& circuit_lib, + const CircuitModelId& sram_model, + const e_config_protocol_type& sram_orgz_type, + const e_circuit_model_port_type& port_type) { + std::string port_name = circuit_lib.model_name(sram_model) + std::string("_"); + + switch (sram_orgz_type) { + case CONFIG_MEM_STANDALONE: { + /* Two types of ports are available: + * (1) Regular output of a SRAM, enabled by port type of INPUT + * (2) Inverted output of a SRAM, enabled by port type of OUTPUT + */ + if (CIRCUIT_MODEL_PORT_INPUT == port_type) { + port_name += std::string("out_local_bus"); + } else { + VTR_ASSERT( CIRCUIT_MODEL_PORT_OUTPUT == port_type ); + port_name += std::string("outb_local_bus"); + } + break; + } + case CONFIG_MEM_SCAN_CHAIN: + /* Three types of ports are available: + * (1) Input of Configuration-chain Flip-Flops (CCFFs), enabled by port type of INPUT + * (2) Output of a chian of Configuration-chain Flip-flops (CCFFs), enabled by port type of OUTPUT + * (2) Inverted output of a chian of Configuration-chain Flip-flops (CCFFs), enabled by port type of INOUT + * +------+ +------+ +------+ + * Head --->| CCFF |--->| CCFF |--->| CCFF |---> Tail + * +------+ +------+ +------+ + */ + if (CIRCUIT_MODEL_PORT_INPUT == port_type) { + port_name += std::string("ccff_in_local_bus"); + } else if ( CIRCUIT_MODEL_PORT_OUTPUT == port_type ) { + port_name += std::string("ccff_out_local_bus"); + } else { + VTR_ASSERT( CIRCUIT_MODEL_PORT_INOUT == port_type ); + port_name += std::string("ccff_outb_local_bus"); + } + break; + case CONFIG_MEM_MEMORY_BANK: { + /* Two types of ports are available: + * (1) Regular output of a SRAM, enabled by port type of INPUT + * (2) Inverted output of a SRAM, enabled by port type of OUTPUT + */ + if (CIRCUIT_MODEL_PORT_INPUT == port_type) { + port_name += std::string("out_local_bus"); + } else { + VTR_ASSERT( CIRCUIT_MODEL_PORT_OUTPUT == port_type ); + port_name += std::string("outb_local_bus"); + } + break; + } + default: + VTR_LOG_ERROR("Invalid type of SRAM organization !\n"); + exit(1); + } + + return port_name; +} + +/********************************************************************* + * Generate the port name for the input bus of a routing multiplexer + * This is very useful in Verilog code generation where the inputs of + * a routing multiplexer may come from different ports. + * On the other side, the datapath input of a routing multiplexer + * is defined as a bus port. + * Therefore, to interface, a bus port is required, and this function + * give a name to the bus port + * To keep the bus port name unique to each multiplexer we will instance, + * a mux_instance_id should be provided by user + *********************************************************************/ +std::string generate_mux_input_bus_port_name(const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const size_t& mux_size, + const size_t& mux_instance_id) { + std::string postfix = std::string("_") + std::to_string(mux_instance_id) + std::string("_inbus"); + return generate_mux_subckt_name(circuit_lib, mux_model, mux_size, postfix); +} + +/********************************************************************* + * Generate the name of a bus port which is wired to the configuration + * ports of a routing multiplexer + * This port is supposed to be used locally inside a Verilog/SPICE module + *********************************************************************/ +std::string generate_mux_config_bus_port_name(const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const size_t& mux_size, + const size_t& bus_id, + const bool& inverted) { + std::string postfix = std::string("_configbus") + std::to_string(bus_id); + /* Add a bar to the end of the name for inverted bus ports */ + if (true == inverted) { + postfix += std::string("_b"); + } + + return generate_mux_subckt_name(circuit_lib, mux_model, mux_size, postfix); +} + +/********************************************************************* + * Generate the port name for a SRAM port of a circuit + * This name is used for local wires that connecting SRAM ports + * of a circuit model inside a Verilog/SPICE module + * Note that the SRAM ports share the same naming + * convention regardless of their configuration style + *********************************************************************/ +std::string generate_local_sram_port_name(const std::string& port_prefix, + const size_t& instance_id, + const e_circuit_model_port_type& port_type) { + std::string port_name = port_prefix + std::string("_") + std::to_string(instance_id) + std::string("_"); + + if (CIRCUIT_MODEL_PORT_INPUT == port_type) { + port_name += std::string("out"); + } else { + VTR_ASSERT( CIRCUIT_MODEL_PORT_OUTPUT == port_type ); + port_name += std::string("outb"); + } + + return port_name; +} + +/********************************************************************* + * Generate the port name for a SRAM port of a routing multiplexer + * This name is used for local wires that connecting SRAM ports + * of routing multiplexers inside a Verilog/SPICE module + * Note that the SRAM ports of routing multiplexers share the same naming + * convention regardless of their configuration style + **********************************************************************/ +std::string generate_mux_sram_port_name(const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const size_t& mux_size, + const size_t& mux_instance_id, + const e_circuit_model_port_type& port_type) { + std::string prefix = generate_mux_subckt_name(circuit_lib, mux_model, mux_size, std::string()); + return generate_local_sram_port_name(prefix, mux_instance_id, port_type); +} + +/********************************************************************* + * Generate the prefix for naming a grid block netlist or a grid module + * This function will consider the io side and add it to the prefix + **********************************************************************/ +std::string generate_grid_block_prefix(const std::string& prefix, + const e_side& io_side) { + std::string block_prefix(prefix); + + if (NUM_SIDES != io_side) { + SideManager side_manager(io_side); + block_prefix += std::string(side_manager.to_string()); + block_prefix += std::string("_"); + } + + return block_prefix; +} + +/********************************************************************* + * Generate the netlist name of a grid block + **********************************************************************/ +std::string generate_grid_block_netlist_name(const std::string& block_name, + const bool& is_block_io, + const e_side& io_side, + const std::string& postfix) { + /* Add the name of physical block */ + std::string module_name(block_name); + + if (true == is_block_io) { + SideManager side_manager(io_side); + module_name += std::string("_"); + module_name += std::string(side_manager.to_string()); + } + + module_name += postfix; + + return module_name; +} + +/********************************************************************* + * Generate the module name of a grid block + **********************************************************************/ +std::string generate_grid_block_module_name(const std::string& prefix, + const std::string& block_name, + const bool& is_block_io, + const e_side& io_side) { + std::string module_name(prefix); + + module_name += generate_grid_block_netlist_name(block_name, is_block_io, io_side, std::string()); + + return module_name; +} + +/********************************************************************* + * Generate the instance name for a programmable routing multiplexer module + * in a Switch Block + * To keep a unique name in each module and also consider unique routing modules, + * please do NOT include any coordinates in the naming!!! + * Consider only relative coordinate, such as side! + ********************************************************************/ +std::string generate_sb_mux_instance_name(const std::string& prefix, + const e_side& sb_side, + const size_t& track_id, + const std::string& postfix) { + std::string instance_name(prefix); + instance_name += SideManager(sb_side).to_string(); + instance_name += std::string("_track_") + std::to_string(track_id); + instance_name += postfix; + + return instance_name; +} + +/********************************************************************* + * Generate the instance name for a configurable memory module in a Switch Block + * To keep a unique name in each module and also consider unique routing modules, + * please do NOT include any coordinates in the naming!!! + * Consider only relative coordinate, such as side! + ********************************************************************/ +std::string generate_sb_memory_instance_name(const std::string& prefix, + const e_side& sb_side, + const size_t& track_id, + const std::string& postfix) { + std::string instance_name(prefix); + instance_name += SideManager(sb_side).to_string(); + instance_name += std::string("_track_") + std::to_string(track_id); + instance_name += postfix; + + return instance_name; +} + +/********************************************************************* + * Generate the instance name for a programmable routing multiplexer module + * in a Connection Block + * To keep a unique name in each module and also consider unique routing modules, + * please do NOT include any coordinates in the naming!!! + * Consider only relative coordinate, such as side! + ********************************************************************/ +std::string generate_cb_mux_instance_name(const std::string& prefix, + const e_side& cb_side, + const size_t& pin_id, + const std::string& postfix) { + std::string instance_name(prefix); + + instance_name += SideManager(cb_side).to_string(); + instance_name += std::string("_ipin_") + std::to_string(pin_id); + instance_name += postfix; + + return instance_name; +} + +/********************************************************************* + * Generate the instance name for a configurable memory module in a Connection Block + * To keep a unique name in each module and also consider unique routing modules, + * please do NOT include any coordinates in the naming!!! + * Consider only relative coordinate, such as side! + ********************************************************************/ +std::string generate_cb_memory_instance_name(const std::string& prefix, + const e_side& cb_side, + const size_t& pin_id, + const std::string& postfix) { + std::string instance_name(prefix); + + instance_name += SideManager(cb_side).to_string(); + instance_name += std::string("_ipin_") + std::to_string(pin_id); + instance_name += postfix; + + return instance_name; +} + +/********************************************************************* + * Generate the instance name for a programmable routing multiplexer + * module in a physical block of a grid + * To guarentee a unique name for pb_graph pin, + * the instance name includes the index of parent node + * as well as the port name and pin index of this pin + * + * Exceptions: + * For OUTPUT ports, due to hierarchical module organization, + * their parent nodes will be uniquified + * So, we should not add any index here + ********************************************************************/ +std::string generate_pb_mux_instance_name(const std::string& prefix, + t_pb_graph_pin* pb_graph_pin, + const std::string& postfix) { + std::string instance_name(prefix); + instance_name += std::string(pb_graph_pin->parent_node->pb_type->name); + + if (IN_PORT == pb_graph_pin->port->type) { + instance_name += std::string("_"); + instance_name += std::to_string(pb_graph_pin->parent_node->placement_index); + } + + instance_name += std::string("_"); + instance_name += std::string(pb_graph_pin->port->name); + instance_name += std::string("_"); + instance_name += std::to_string(pb_graph_pin->pin_number); + instance_name += postfix; + + return instance_name; +} + +/********************************************************************* + * Generate the instance name for a configurable memory module in a + * physical block of a grid + * To guarentee a unique name for pb_graph pin, + * the instance name includes the index of parent node + * as well as the port name and pin index of this pin + * + * Exceptions: + * For OUTPUT ports, due to hierarchical module organization, + * their parent nodes will be uniquified + * So, we should not add any index here + ********************************************************************/ +std::string generate_pb_memory_instance_name(const std::string& prefix, + t_pb_graph_pin* pb_graph_pin, + const std::string& postfix) { + std::string instance_name(prefix); + instance_name += std::string(pb_graph_pin->parent_node->pb_type->name); + + if (IN_PORT == pb_graph_pin->port->type) { + instance_name += std::string("_"); + instance_name += std::to_string(pb_graph_pin->parent_node->placement_index); + } + + instance_name += std::string("_"); + instance_name += std::string(pb_graph_pin->port->name); + instance_name += std::string("_"); + instance_name += std::to_string(pb_graph_pin->pin_number); + instance_name += postfix; + + return instance_name; +} + +/********************************************************************* + * Generate the instance name of a grid block + **********************************************************************/ +std::string generate_grid_block_instance_name(const std::string& prefix, + const std::string& block_name, + const bool& is_block_io, + const e_side& io_side, + const vtr::Point& grid_coord) { + std::string module_name(prefix); + + module_name += generate_grid_block_netlist_name(block_name, is_block_io, io_side, std::string()); + module_name += std::string("_"); + module_name += std::to_string(grid_coord.x()); + module_name += std::string("_"); + module_name += std::to_string(grid_coord.y()); + + return module_name; +} + +/********************************************************************* + * Generate the module name of a physical block + * To ensure a unique name for each physical block inside the graph of complex blocks + * (pb_graph_nodes), this function trace backward to the top-level node + * in the graph and add the name of these parents + * The final name will be in the following format: + * __ ... + * + * TODO: to make sure the length of this name does not exceed the size of + * chars in a line of a file!!! + **********************************************************************/ +std::string generate_physical_block_module_name(const std::string& prefix, + t_pb_type* physical_pb_type) { + std::string module_name(physical_pb_type->name); + + t_pb_type* parent_pb_type = physical_pb_type; + + /* Backward trace until we meet the top-level pb_type */ + while (1) { + /* If there is no parent mode, this is a top-level pb_type, quit the loop here */ + t_mode* parent_mode = parent_pb_type->parent_mode; + if (NULL == parent_mode) { + break; + } + + /* Add the mode name to the module name */ + module_name = std::string("mode_") + std::string(parent_mode->name) + std::string("__") + module_name; + + /* Backtrace to the upper level */ + parent_pb_type = parent_mode->parent_pb_type; + + /* If there is no parent pb_type, this is a top-level pb_type, quit the loop here */ + if (NULL == parent_pb_type) { + break; + } + + /* Add the current pb_type name to the module name */ + module_name = std::string(parent_pb_type->name) + std::string("_") + module_name; + } + + /* Exception for top-level pb_type: add an virtual mode name (same name as the pb_type) + * This is to follow the naming convention as non top-level pb_types + * In addition, the name can be really unique, being different than the grid blocks + */ + if (NULL == physical_pb_type->parent_mode) { + module_name += std::string("_mode_") + std::string(physical_pb_type->name) + std::string("_"); + } + + /* Add the prefix */ + module_name = prefix + module_name; + + return module_name; +} + + +/********************************************************************* + * Generate the instance name for physical block with a given index + **********************************************************************/ +std::string generate_physical_block_instance_name(const std::string& prefix, + t_pb_type* pb_type, + const size_t& index) { + std::string instance_name = generate_physical_block_module_name(prefix, pb_type); + /* Add index to the name */ + instance_name += std::string("_"); + instance_name += std::to_string(index); + + return instance_name; +} + +/********************************************************************* + * This function is a wrapper for the function generate_physical_block_module_name() + * which can automatically decode the io_side and add a prefix + **********************************************************************/ +std::string generate_grid_physical_block_module_name(const std::string& prefix, + t_pb_type* pb_type, + const e_side& border_side) { + std::string module_name_prefix = generate_grid_block_prefix(prefix, border_side); + return generate_physical_block_module_name(module_name_prefix, pb_type); +} + +/********************************************************************* + * Generate the instance name for physical block in Grid with a given index + **********************************************************************/ +std::string generate_grid_physical_block_instance_name(const std::string& prefix, + t_pb_type* pb_type, + const e_side& border_side, + const size_t& index) { + std::string module_name_prefix = generate_grid_block_prefix(prefix, border_side); + std::string instance_name = generate_physical_block_module_name(module_name_prefix, pb_type); + /* Add index to the name */ + instance_name += std::string("_"); + instance_name += std::to_string(index); + + return instance_name; +} + +/******************************************************************** + * This function try to infer if a grid locates at the border of a + * FPGA fabric, i.e., TOP/RIGHT/BOTTOM/LEFT sides + * 1. if this grid is on the border, it will return the side it locates, + * 2. if this grid is in the center, it will return an valid value NUM_SIDES + * + * In this function, we assume that the corner grids are actually empty! + * + * +-------+ +----------------------------+ +-------+ + * | EMPTY | | TOP side I/O | | EMPTY | + * +-------+ +----------------------------+ +-------+ + * + * +-------+ +----------------------------+ +-------+ + * | | | | | | + * | | | | | | + * | | | | | | + * | LEFT | | | | RIGHT | + * | side | | Core grids | | side | + * | I/O | | | | I/O | + * | | | | | | + * | | | | | | + * | | | | | | + * | | | | | | + * +-------+ +----------------------------+ +-------+ + * + * +-------+ +----------------------------+ +-------+ + * | EMPTY | | BOTTOM side I/O | | EMPTY | + * +-------+ +----------------------------+ +-------+ + *******************************************************************/ +e_side find_grid_border_side(const vtr::Point& device_size, + const vtr::Point& grid_coordinate) { + e_side grid_side = NUM_SIDES; + + if (device_size.y() - 1 == grid_coordinate.y()) { + return TOP; + } + if (device_size.x() - 1 == grid_coordinate.x()) { + return RIGHT; + } + if (0 == grid_coordinate.y()) { + return BOTTOM; + } + if (0 == grid_coordinate.x()) { + return LEFT; + } + + return grid_side; +} + +/******************************************************************** + * This function try to infer if a grid locates at the border of the + * core FPGA fabric, i.e., TOP/RIGHT/BOTTOM/LEFT sides + * 1. if this grid is on the border and it matches the given side, return true, + * 2. if this grid is in the center, return false + * + * In this function, we assume that the corner grids are actually empty! + * + * +-------+ +----------------------------+ +-------+ + * | EMPTY | | TOP side I/O | | EMPTY | + * +-------+ +----------------------------+ +-------+ + * + * +-------+ +----------------------------+ +-------+ + * | | | TOP | | | + * | | |----------------------------| | | + * | | | | | | | | + * | LEFT | | | | | | RIGHT | + * | side | | LEFT | Core grids | RIGHT| | side | + * | I/O | | | | | | I/O | + * | | | | | | | | + * | | | | | | | | + * | | |---------------------| | | | + * | | | BOTTOM | | | | + * +-------+ +----------------------------+ +-------+ + * + * +-------+ +----------------------------+ +-------+ + * | EMPTY | | BOTTOM side I/O | | EMPTY | + * +-------+ +----------------------------+ +-------+ + * + * Note: for the blocks on the four corners of the core grids + * Please refer to the figure above to infer its border_side + *******************************************************************/ +bool is_core_grid_on_given_border_side(const vtr::Point& device_size, + const vtr::Point& grid_coordinate, + const e_side& border_side) { + + if ( (device_size.y() - 2 == grid_coordinate.y()) + && (TOP == border_side) ) { + return true; + } + if ( (device_size.x() - 2 == grid_coordinate.x()) + && (RIGHT == border_side) ) { + return true; + } + if ( (1 == grid_coordinate.y()) + && (BOTTOM == border_side) ) { + return true; + } + if ( (1 == grid_coordinate.x()) + && (LEFT == border_side) ) { + return true; + } + + return false; +} + + +/********************************************************************* + * Generate the port name of a Verilog module describing a pb_type + * The name convention is + * _ + ********************************************************************/ +std::string generate_pb_type_port_name(t_port* pb_type_port) { + std::string port_name; + + port_name = std::string(pb_type_port->parent_pb_type->name) + std::string("_") + std::string(pb_type_port->name); + + return port_name; +} + +/********************************************************************* + * Generate the global I/O port name of a Verilog module + * This is mainly used by I/O circuit models + ********************************************************************/ +std::string generate_fpga_global_io_port_name(const std::string& prefix, + const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model) { + std::string port_name(prefix); + + port_name += circuit_lib.model_name(circuit_model); + + return port_name; +} + +/********************************************************************* + * Generate the module name for the top-level module + * The top-level module is actually the FPGA fabric + * We give a fixed name here, because it is independent from benchmark file + ********************************************************************/ +std::string generate_fpga_top_module_name() { + return std::string("fpga_top"); +} + +/********************************************************************* + * Generate the netlist name for the top-level module + * The top-level module is actually the FPGA fabric + * We give a fixed name here, because it is independent from benchmark file + ********************************************************************/ +std::string generate_fpga_top_netlist_name(const std::string& postfix) { + return std::string("fpga_top" + postfix); +} + +/********************************************************************* + * Generate the module name for a constant generator + * either VDD or GND, depending on the input argument + ********************************************************************/ +std::string generate_const_value_module_name(const size_t& const_val) { + if (0 == const_val) { + return std::string("const0"); + } + + VTR_ASSERT (1 == const_val); + return std::string("const1"); +} + +/********************************************************************* + * Generate the output port name for a constant generator module + * either VDD or GND, depending on the input argument + ********************************************************************/ +std::string generate_const_value_module_output_port_name(const size_t& const_val) { + return generate_const_value_module_name(const_val); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_naming.h b/openfpga/src/base/openfpga_naming.h new file mode 100644 index 000000000..0c0c640ca --- /dev/null +++ b/openfpga/src/base/openfpga_naming.h @@ -0,0 +1,262 @@ +/************************************************ + * Header file for fpga_x2p_naming.cpp + * Include functions to generate module/port names + * for Verilog and SPICE netlists + ***********************************************/ + +#ifndef OPENFPGA_NAMING_H +#define OPENFPGA_NAMING_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include + +#include "vtr_geometry.h" +#include "circuit_library.h" +#include "device_grid.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +std::string generate_mux_node_name(const size_t& node_level, + const bool& add_buffer_postfix); + +std::string generate_mux_branch_instance_name(const size_t& node_level, + const size_t& node_index_at_level, + const bool& add_buffer_postfix); + +std::string generate_mux_subckt_name(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const size_t& mux_size, + const std::string& posfix) ; + +std::string generate_mux_branch_subckt_name(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const size_t& mux_size, + const size_t& branch_mux_size, + const std::string& posfix); + +std::string generate_mux_local_decoder_subckt_name(const size_t& addr_size, + const size_t& data_size); + +std::string generate_segment_wire_subckt_name(const std::string& wire_model_name, + const size_t& segment_id); + +std::string generate_segment_wire_mid_output_name(const std::string& regular_output_name); + +std::string generate_memory_module_name(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const CircuitModelId& sram_model, + const std::string& postfix); + +std::string generate_routing_block_netlist_name(const std::string& prefix, + const size_t& block_id, + const std::string& postfix); + +std::string generate_routing_block_netlist_name(const std::string& prefix, + const vtr::Point& block_id, + const std::string& postfix); + +std::string generate_connection_block_netlist_name(const t_rr_type& cb_type, + const vtr::Point& coordinate, + const std::string& postfix); + +std::string generate_routing_channel_module_name(const t_rr_type& chan_type, + const size_t& block_id); + +std::string generate_routing_channel_module_name(const t_rr_type& chan_type, + const vtr::Point& coordinate); + +std::string generate_routing_track_port_name(const t_rr_type& chan_type, + const vtr::Point& coordinate, + const size_t& track_id, + const PORTS& port_direction); + +std::string generate_sb_module_track_port_name(const t_rr_type& chan_type, + const e_side& module_side, + const size_t& track_id, + const PORTS& port_direction); + +std::string generate_cb_module_track_port_name(const t_rr_type& chan_type, + const size_t& track_id, + const PORTS& port_direction); + +std::string generate_routing_track_middle_output_port_name(const t_rr_type& chan_type, + const vtr::Point& coordinate, + const size_t& track_id); + +std::string generate_switch_block_module_name(const vtr::Point& coordinate); + +std::string generate_connection_block_module_name(const t_rr_type& cb_type, + const vtr::Point& coordinate); + +std::string generate_sb_mux_instance_name(const std::string& prefix, + const e_side& sb_side, + const size_t& track_id, + const std::string& postfix); + +std::string generate_sb_memory_instance_name(const std::string& prefix, + const e_side& sb_side, + const size_t& track_id, + const std::string& postfix); + +std::string generate_cb_mux_instance_name(const std::string& prefix, + const e_side& cb_side, + const size_t& pin_id, + const std::string& postfix); + +std::string generate_cb_memory_instance_name(const std::string& prefix, + const e_side& cb_side, + const size_t& pin_id, + const std::string& postfix); + +std::string generate_pb_mux_instance_name(const std::string& prefix, + t_pb_graph_pin* pb_graph_pin, + const std::string& postfix); + +std::string generate_pb_memory_instance_name(const std::string& prefix, + t_pb_graph_pin* pb_graph_pin, + const std::string& postfix); + +std::string generate_grid_port_name(const vtr::Point& coordinate, + const size_t& width, + const size_t& height, + const e_side& side, + const size_t& pin_id, + const bool& for_top_netlist); + +std::string generate_grid_duplicated_port_name(const size_t& width, + const size_t& height, + const e_side& side, + const size_t& pin_id, + const bool& upper_port); + +std::string generate_grid_module_port_name(const size_t& pin_id); + +std::string generate_grid_side_port_name(const DeviceGrid& grids, + const vtr::Point& coordinate, + const e_side& side, + const size_t& pin_id); + +std::string generate_sb_module_grid_port_name(const e_side& sb_side, + const e_side& grid_side, + const size_t& pin_id); + +std::string generate_cb_module_grid_port_name(const e_side& cb_side, + const size_t& pin_id); + +std::string generate_reserved_sram_port_name(const e_circuit_model_port_type& port_type); + +std::string generate_formal_verification_sram_port_name(const CircuitLibrary& circuit_lib, + const CircuitModelId& sram_model); + +std::string generate_configuration_chain_head_name(); + +std::string generate_configuration_chain_tail_name(); + +std::string generate_configuration_chain_data_out_name(); + +std::string generate_configuration_chain_inverted_data_out_name(); + +std::string generate_mux_local_decoder_addr_port_name(); + +std::string generate_mux_local_decoder_data_port_name(); + +std::string generate_mux_local_decoder_data_inv_port_name(); + +std::string generate_local_config_bus_port_name(); + +std::string generate_sram_port_name(const e_config_protocol_type& sram_orgz_type, + const e_circuit_model_port_type& port_type); + +std::string generate_sram_local_port_name(const CircuitLibrary& circuit_lib, + const CircuitModelId& sram_model, + const e_config_protocol_type& sram_orgz_type, + const e_circuit_model_port_type& port_type); + +std::string generate_mux_input_bus_port_name(const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const size_t& mux_size, + const size_t& mux_instance_id); + +std::string generate_mux_config_bus_port_name(const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const size_t& mux_size, + const size_t& bus_id, + const bool& inverted); + +std::string generate_local_sram_port_name(const std::string& port_prefix, + const size_t& instance_id, + const e_circuit_model_port_type& port_type); + +std::string generate_mux_sram_port_name(const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const size_t& mux_size, + const size_t& mux_instance_id, + const e_circuit_model_port_type& port_type); + +std::string generate_grid_block_prefix(const std::string& prefix, + const e_side& io_side); + +std::string generate_grid_block_netlist_name(const std::string& block_name, + const bool& is_block_io, + const e_side& io_side, + const std::string& postfix); + +std::string generate_grid_block_module_name(const std::string& prefix, + const std::string& block_name, + const bool& is_block_io, + const e_side& io_side); + +std::string generate_grid_block_instance_name(const std::string& prefix, + const std::string& block_name, + const bool& is_block_io, + const e_side& io_side, + const vtr::Point& grid_coord); + +std::string generate_physical_block_module_name(const std::string& prefix, + t_pb_type* physical_pb_type); + +std::string generate_physical_block_instance_name(const std::string& prefix, + t_pb_type* pb_type, + const size_t& index); + +std::string generate_grid_physical_block_module_name(const std::string& prefix, + t_pb_type* pb_type, + const e_side& border_side); + +std::string generate_grid_physical_block_instance_name(const std::string& prefix, + t_pb_type* pb_type, + const e_side& border_side, + const size_t& index); + + +e_side find_grid_border_side(const vtr::Point& device_size, + const vtr::Point& grid_coordinate); + +bool is_core_grid_on_given_border_side(const vtr::Point& device_size, + const vtr::Point& grid_coordinate, + const e_side& border_side); + +std::string generate_pb_type_port_name(t_port* pb_type_port); + +std::string generate_fpga_global_io_port_name(const std::string& prefix, + const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model); + +std::string generate_fpga_top_module_name(); + +std::string generate_fpga_top_netlist_name(const std::string& postfix); + +std::string generate_const_value_module_name(const size_t& const_val); + +std::string generate_const_value_module_output_port_name(const size_t& const_val); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fabric/build_device_module.cpp b/openfpga/src/fabric/build_device_module.cpp new file mode 100644 index 000000000..bf664048f --- /dev/null +++ b/openfpga/src/fabric/build_device_module.cpp @@ -0,0 +1,104 @@ +/******************************************************************** + * This file includes the main function to build module graphs + * for the FPGA fabric + *******************************************************************/ + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" +#include "vtr_time.h" + +#include "build_essential_modules.h" +//#include "build_decoder_modules.h" +//#include "build_mux_modules.h" +//#include "build_lut_modules.h" +//#include "build_wire_modules.h" +//#include "build_memory_modules.h" +//#include "build_grid_modules.h" +//#include "build_routing_modules.h" +//#include "build_top_module.h" +#include "build_device_module.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * The main function to be called for building module graphs + * for a FPGA fabric + *******************************************************************/ +ModuleManager build_device_module_graph(const DeviceContext& vpr_device_ctx, + const OpenfpgaContext& openfpga_ctx) { + vtr::ScopedStartFinishTimer timer("Build fabric module graph"); + + /* Module manager to be built */ + ModuleManager module_manager; + + CircuitModelId sram_model = openfpga_ctx.arch().config_protocol.memory_model(); + VTR_ASSERT(true == openfpga_ctx.arch().circuit_lib.valid_model_id(sram_model)); + + /* Add constant generator modules: VDD and GND */ + build_constant_generator_modules(module_manager); + + /* Register all the user-defined modules in the module manager + * This should be done prior to other steps in this function, + * because they will be instanciated by other primitive modules + */ + build_user_defined_modules(module_manager, openfpga_ctx.arch().circuit_lib); + + /* Build elmentary modules */ + build_essential_modules(module_manager, openfpga_ctx.arch().circuit_lib); + + /* Build local encoders for multiplexers, this MUST be called before multiplexer building */ + //build_mux_local_decoder_modules(module_manager, mux_lib, arch.spice->circuit_lib); + + /* Build multiplexer modules */ + //build_mux_modules(module_manager, mux_lib, arch.spice->circuit_lib); + + /* Build LUT modules */ + //build_lut_modules(module_manager, arch.spice->circuit_lib); + + /* Build wire modules */ + //build_wire_modules(module_manager, arch.spice->circuit_lib); + + /* Build memory modules */ + //build_memory_modules(module_manager, mux_lib, arch.spice->circuit_lib, + // arch.sram_inf.verilog_sram_inf_orgz->type); + + /* Build grid and programmable block modules */ + //build_grid_modules(module_manager, arch.spice->circuit_lib, mux_lib, + // arch.sram_inf.verilog_sram_inf_orgz->type, sram_model, + // TRUE == vpr_setup.FPGA_SPICE_Opts.duplicate_grid_pin); + + //if (TRUE == vpr_setup.FPGA_SPICE_Opts.compact_routing_hierarchy) { + // build_unique_routing_modules(module_manager, L_device_rr_gsb, arch.spice->circuit_lib, + // arch.sram_inf.verilog_sram_inf_orgz->type, sram_model, + // vpr_setup.RoutingArch, rr_switches); + //} else { + // VTR_ASSERT(FALSE == vpr_setup.FPGA_SPICE_Opts.compact_routing_hierarchy); + // build_flatten_routing_modules(module_manager, L_device_rr_gsb, arch.spice->circuit_lib, + // arch.sram_inf.verilog_sram_inf_orgz->type, sram_model, + // vpr_setup.RoutingArch, rr_switches); + //} + + + /* Build FPGA fabric top-level module */ + //build_top_module(module_manager, arch.spice->circuit_lib, + // device_size, grids, L_device_rr_gsb, + // clb2clb_directs, + // arch.sram_inf.verilog_sram_inf_orgz->type, sram_model, + // TRUE == vpr_setup.FPGA_SPICE_Opts.compact_routing_hierarchy, + // TRUE == vpr_setup.FPGA_SPICE_Opts.duplicate_grid_pin); + + /* Now a critical correction has to be done! + * In the module construction, we always use prefix of ports because they are binded + * to the ports in architecture description (logic blocks etc.) + * To interface with standard cell, we should + * rename the ports of primitive modules using lib_name instead of prefix + * (which have no children and are probably linked to a standard cell!) + */ + //rename_primitive_module_port_names(module_manager, arch.spice->circuit_lib); + + return module_manager; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fabric/build_device_module.h b/openfpga/src/fabric/build_device_module.h new file mode 100644 index 000000000..1e6bb2226 --- /dev/null +++ b/openfpga/src/fabric/build_device_module.h @@ -0,0 +1,22 @@ +#ifndef BUILD_DEVICE_MODULE_H +#define BUILD_DEVICE_MODULE_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "vpr_context.h" +#include "openfpga_context.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +ModuleManager build_device_module_graph(const DeviceContext& vpr_device_ctx, + const OpenfpgaContext& openfpga_ctx); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fabric/build_essential_modules.cpp b/openfpga/src/fabric/build_essential_modules.cpp new file mode 100644 index 000000000..07a00a788 --- /dev/null +++ b/openfpga/src/fabric/build_essential_modules.cpp @@ -0,0 +1,271 @@ +/******************************************************************** + * This function includes the module builders for essential logic gates + * which are the leaf circuit model in the circuit library + *******************************************************************/ + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" +#include "vtr_time.h" + +#include "openfpga_naming.h" +#include "module_manager_utils.h" +#include "build_essential_modules.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/************************************************ + * Build a module of inverter or buffer + * or tapered buffer to a file + ***********************************************/ +static +void build_invbuf_module(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model) { + /* Find the input port, output port and global inputs*/ + std::vector input_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_INPUT, true); + std::vector output_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_OUTPUT, true); + std::vector global_ports = circuit_lib.model_global_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_INPUT, true, true); + + /* Make sure: + * There is only 1 input port and 1 output port, + * each size of which is 1 + */ + VTR_ASSERT( (1 == input_ports.size()) && (1 == circuit_lib.port_size(input_ports[0])) ); + VTR_ASSERT( (1 == output_ports.size()) && (1 == circuit_lib.port_size(output_ports[0])) ); + + /* TODO: move the check codes to check_circuit_library.h */ + /* If the circuit model is power-gated, we need to find at least one global config_enable signals */ + if (true == circuit_lib.is_power_gated(circuit_model)) { + /* Check all the ports we have are good for a power-gated circuit model */ + size_t num_err = 0; + /* We need at least one global port */ + if (0 == global_ports.size()) { + num_err++; + } + /* All the global ports should be config_enable */ + for (const auto& port : global_ports) { + if (false == circuit_lib.port_is_config_enable(port)) { + num_err++; + } + } + /* Report errors if there are any */ + if (0 < num_err) { + VTR_LOG_ERROR("Inverter/buffer circuit model '%s' is power-gated. At least one config-enable global port is required!\n", + circuit_lib.model_name(circuit_model).c_str()); + exit(1); + } + } + + /* Create a Verilog Module based on the circuit model, and add to module manager */ + ModuleId module_id = add_circuit_model_to_module_manager(module_manager, circuit_lib, circuit_model); + VTR_ASSERT(true == module_manager.valid_module_id(module_id)); +} + +/************************************************ + * Build a module of a pass-gate, + * either transmission-gate or pass-transistor + ***********************************************/ +static +void build_passgate_module(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model) { + /* Find the input port, output port*/ + std::vector input_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_INPUT, true); + std::vector output_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_OUTPUT, true); + std::vector global_ports = circuit_lib.model_global_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_INPUT, true, true); + + switch (circuit_lib.pass_gate_logic_type(circuit_model)) { + case CIRCUIT_MODEL_PASS_GATE_TRANSMISSION: + /* Make sure: + * There is only 3 input port (in, sel, selb), + * each size of which is 1 + */ + VTR_ASSERT( 3 == input_ports.size() ); + for (const auto& input_port : input_ports) { + VTR_ASSERT(1 == circuit_lib.port_size(input_port)); + } + break; + case CIRCUIT_MODEL_PASS_GATE_TRANSISTOR: + /* Make sure: + * There is only 2 input port (in, sel), + * each size of which is 1 + */ + VTR_ASSERT( 2 == input_ports.size() ); + for (const auto& input_port : input_ports) { + VTR_ASSERT(1 == circuit_lib.port_size(input_port)); + } + break; + default: + VTR_LOG_ERROR("Invalid topology for circuit model '%s'!\n", + circuit_lib.model_name(circuit_model).c_str()); + exit(1); + } + + /* Make sure: + * There is only 1 output port, + * each size of which is 1 + */ + VTR_ASSERT( (1 == output_ports.size()) && (1 == circuit_lib.port_size(output_ports[0])) ); + + /* Create a Verilog Module based on the circuit model, and add to module manager */ + ModuleId module_id = add_circuit_model_to_module_manager(module_manager, circuit_lib, circuit_model); + VTR_ASSERT(true == module_manager.valid_module_id(module_id)); +} + +/************************************************ + * Build a module of a logic gate + * which are standard cells + * Supported gate types: + * 1. N-input AND + * 2. N-input OR + * 3. 2-input MUX + ***********************************************/ +static +void build_gate_module(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model) { + /* Find the input port, output port*/ + std::vector input_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_INPUT, true); + std::vector output_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_OUTPUT, true); + std::vector global_ports = circuit_lib.model_global_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_INPUT, true, true); + + /* Make sure: + * There is only 1 output port, + * each size of which is 1 + */ + VTR_ASSERT( (1 == output_ports.size()) && (1 == circuit_lib.port_size(output_ports[0])) ); + + /* Create a Verilog Module based on the circuit model, and add to module manager */ + ModuleId module_id = add_circuit_model_to_module_manager(module_manager, circuit_lib, circuit_model); + VTR_ASSERT(true == module_manager.valid_module_id(module_id)); +} + + +/************************************************ + * Generate the modules for essential gates + * include inverters, buffers, transmission-gates, + * etc. + ***********************************************/ +void build_essential_modules(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib) { + vtr::ScopedStartFinishTimer timer("Build essential (inverter/buffer/logic gate) modules..."); + + for (const auto& circuit_model : circuit_lib.models()) { + /* Add essential modules upon on demand: only when it is not yet in the module library */ + ModuleId module = module_manager.find_module(circuit_lib.model_name(circuit_model)); + if (true == module_manager.valid_module_id(module)) { + continue; + } + + if (CIRCUIT_MODEL_INVBUF == circuit_lib.model_type(circuit_model)) { + build_invbuf_module(module_manager, circuit_lib, circuit_model); + continue; + } + if (CIRCUIT_MODEL_PASSGATE == circuit_lib.model_type(circuit_model)) { + build_passgate_module(module_manager, circuit_lib, circuit_model); + continue; + } + if (CIRCUIT_MODEL_GATE == circuit_lib.model_type(circuit_model)) { + build_gate_module(module_manager, circuit_lib, circuit_model); + continue; + } + } +} + +/********************************************************************* + * Register all the user-defined modules in the module manager + * Walk through the circuit library and add user-defined circuit models + * to the module_manager + ********************************************************************/ +void build_user_defined_modules(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib) { + vtr::ScopedStartFinishTimer timer("Build user-defined modules"); + + /* Iterate over Verilog modules */ + for (const auto& model : circuit_lib.models()) { + /* We only care about user-defined models */ + if ( (true == circuit_lib.model_verilog_netlist(model).empty()) + && (true == circuit_lib.model_circuit_netlist(model).empty()) ) { + continue; + } + /* Skip Routing channel wire models because they need a different name. Do it later */ + if (CIRCUIT_MODEL_CHAN_WIRE == circuit_lib.model_type(model)) { + continue; + } + /* Reach here, the model requires a user-defined Verilog netlist, + * Register it in the module_manager + */ + add_circuit_model_to_module_manager(module_manager, circuit_lib, model); + } +} + +/********************************************************************* + * This function will build a constant generator modules + * and add it to the module manager + * It could be either + * 1. VDD or 2. GND + * Each module will have only one output port + ********************************************************************/ +static +void build_constant_generator_module(ModuleManager& module_manager, + const size_t& const_value) { + ModuleId const_module = module_manager.add_module(generate_const_value_module_name(const_value)); + /* Add one output port */ + BasicPort const_output_port(generate_const_value_module_output_port_name(const_value), 1); + module_manager.add_port(const_module, const_output_port, ModuleManager::MODULE_OUTPUT_PORT); +} + +/********************************************************************* + * This function will add two constant generator modules + * to the module manager + * 1. VDD + * 2. GND + ********************************************************************/ +void build_constant_generator_modules(ModuleManager& module_manager) { + vtr::ScopedStartFinishTimer timer("Build constant generator modules"); + + /* VDD */ + build_constant_generator_module(module_manager, 1); + + /* GND */ + build_constant_generator_module(module_manager, 0); +} + +/********************************************************************* + * This function will rename the ports of primitive modules + * using lib_name instead of prefix + * Primitive modules are defined as those modules in the module manager + * which have user defined netlists + ********************************************************************/ +void rename_primitive_module_port_names(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib) { + for (const CircuitModelId& model : circuit_lib.models()) { + /* We only care about user-defined models */ + if ( (true == circuit_lib.model_verilog_netlist(model).empty()) + && (true == circuit_lib.model_circuit_netlist(model).empty()) ) { + continue; + } + /* Skip Routing channel wire models because they need a different name. Do it later */ + if (CIRCUIT_MODEL_CHAN_WIRE == circuit_lib.model_type(model)) { + continue; + } + /* Find the module in module manager */ + ModuleId module = module_manager.find_module(circuit_lib.model_name(model)); + /* We must find one! */ + VTR_ASSERT(true == module_manager.valid_module_id(module)); + + /* Rename all the ports to use lib_name! */ + for (const CircuitPortId& model_port : circuit_lib.model_ports(model)) { + /* Find the module port in module manager. We used prefix when creating the ports */ + ModulePortId module_port = module_manager.find_module_port(module, circuit_lib.port_prefix(model_port)); + /* We must find one! */ + VTR_ASSERT(true == module_manager.valid_module_port_id(module, module_port)); + /* Name it with lib_name */ + module_manager.set_module_port_name(module, module_port, circuit_lib.port_lib_name(model_port)); + } + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fabric/build_essential_modules.h b/openfpga/src/fabric/build_essential_modules.h new file mode 100644 index 000000000..e7d348bc6 --- /dev/null +++ b/openfpga/src/fabric/build_essential_modules.h @@ -0,0 +1,30 @@ +#ifndef BUILD_ESSENTIAL_MODULES_H +#define BUILD_ESSENTIAL_MODULES_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "circuit_library.h" +#include "module_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void build_essential_modules(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib); + +void build_user_defined_modules(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib); + +void build_constant_generator_modules(ModuleManager& module_manager); + +void rename_primitive_module_port_names(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fabric/module_manager.cpp b/openfpga/src/fabric/module_manager.cpp new file mode 100644 index 000000000..40f672194 --- /dev/null +++ b/openfpga/src/fabric/module_manager.cpp @@ -0,0 +1,777 @@ +/****************************************************************************** + * Memember functions for data structure ModuleManager + ******************************************************************************/ +#include +#include +#include +#include "vtr_assert.h" + +#include "circuit_library.h" +#include "module_manager.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/****************************************************************************** + * Public Constructors + ******************************************************************************/ + +/************************************************** + * Public Accessors : Aggregates + *************************************************/ +/* Find all the modules */ +ModuleManager::module_range ModuleManager::modules() const { + return vtr::make_range(ids_.begin(), ids_.end()); +} + +/* Find all the ports belonging to a module */ +ModuleManager::module_port_range ModuleManager::module_ports(const ModuleId& module) const { + /* Validate the module_id */ + VTR_ASSERT(valid_module_id(module)); + return vtr::make_range(port_ids_[module].begin(), port_ids_[module].end()); +} + +/* Find all the nets belonging to a module */ +ModuleManager::module_net_range ModuleManager::module_nets(const ModuleId& module) const { + /* Validate the module_id */ + VTR_ASSERT(valid_module_id(module)); + return vtr::make_range(net_ids_[module].begin(), net_ids_[module].end()); +} + +/* Find all the child modules under a parent module */ +std::vector ModuleManager::child_modules(const ModuleId& parent_module) const { + /* Validate the module_id */ + VTR_ASSERT(valid_module_id(parent_module)); + return children_[parent_module]; +} + +/* Find all the instances under a parent module */ +std::vector ModuleManager::child_module_instances(const ModuleId& parent_module, const ModuleId& child_module) const { + /* Validate the module_id */ + VTR_ASSERT(valid_module_id(parent_module)); + /* Ensure that the child module is in the child list of parent module */ + size_t child_index = children_[parent_module].size(); + for (size_t i = 0; i < children_[parent_module].size(); ++i) { + if (child_module == children_[parent_module][i]) { + child_index = i; + break; + } + } + VTR_ASSERT(child_index != children_[parent_module].size()); + + /* Create a vector, with sequentially increasing numbers */ + std::vector instance_range(num_child_instances_[parent_module][child_index], 0); + std::iota(instance_range.begin(), instance_range.end(), 0); + + return instance_range; +} + +/* Find all the configurable child modules under a parent module */ +std::vector ModuleManager::configurable_children(const ModuleId& parent_module) const { + /* Validate the module_id */ + VTR_ASSERT(valid_module_id(parent_module)); + + return configurable_children_[parent_module]; +} + +/* Find all the instances of configurable child modules under a parent module */ +std::vector ModuleManager::configurable_child_instances(const ModuleId& parent_module) const { + /* Validate the module_id */ + VTR_ASSERT(valid_module_id(parent_module)); + + return configurable_child_instances_[parent_module]; +} + +/* Find the source ids of modules */ +ModuleManager::module_net_src_range ModuleManager::module_net_sources(const ModuleId& module, const ModuleNetId& net) const { + /* Validate the module_id */ + VTR_ASSERT(valid_module_net_id(module, net)); + return vtr::make_range(net_src_ids_[module][net].begin(), net_src_ids_[module][net].end()); +} + +/* Find the sink ids of modules */ +ModuleManager::module_net_sink_range ModuleManager::module_net_sinks(const ModuleId& module, const ModuleNetId& net) const { + /* Validate the module_id */ + VTR_ASSERT(valid_module_net_id(module, net)); + return vtr::make_range(net_sink_ids_[module][net].begin(), net_sink_ids_[module][net].end()); +} + +/****************************************************************************** + * Public Accessors + ******************************************************************************/ +/* Return number of modules */ +size_t ModuleManager::num_modules() const { + return ids_.size(); +} + +/* Return number of net of a module */ +size_t ModuleManager::num_nets(const ModuleId& module) const { + /* Validate the module_id */ + VTR_ASSERT(valid_module_id(module)); + return net_ids_[module].size(); +} + +/* Find the name of a module */ +std::string ModuleManager::module_name(const ModuleId& module_id) const { + /* Validate the module_id */ + VTR_ASSERT(valid_module_id(module_id)); + return names_[module_id]; +} + +/* Get the string of a module port type */ +std::string ModuleManager::module_port_type_str(const enum e_module_port_type& port_type) const { + std::array MODULE_PORT_TYPE_STRING = {{"GLOBAL PORTS", "GPIO PORTS", "INOUT PORTS", "INPUT PORTS", "OUTPUT PORTS", "CLOCK PORTS"}}; + return MODULE_PORT_TYPE_STRING[port_type]; +} + +/* Find a list of ports of a module by a given types */ +std::vector ModuleManager::module_ports_by_type(const ModuleId& module_id, const enum e_module_port_type& port_type) const { + /* Validate the module_id */ + VTR_ASSERT(valid_module_id(module_id)); + + std::vector ports; + for (const auto& port : port_ids_[module_id]) { + /* Skip unmatched ports */ + if (port_type != port_types_[module_id][port]) { + continue; + } + ports.push_back(ports_[module_id][port]); + } + + return ports; +} + +/* Find a list of port ids of a module by a given types */ +std::vector ModuleManager::module_port_ids_by_type(const ModuleId& module_id, const enum e_module_port_type& port_type) const { + /* Validate the module_id */ + VTR_ASSERT(valid_module_id(module_id)); + + std::vector port_ids; + for (const auto& port : port_ids_[module_id]) { + /* Skip unmatched ports */ + if (port_type != port_types_[module_id][port]) { + continue; + } + port_ids.push_back(port_ids_[module_id][port]); + } + + return port_ids; +} + + +/* Find a port of a module by a given name */ +ModulePortId ModuleManager::find_module_port(const ModuleId& module_id, const std::string& port_name) const { + /* Validate the module id */ + VTR_ASSERT(valid_module_id(module_id)); + + /* Iterate over the ports of the module */ + for (const auto& port : port_ids_[module_id]) { + if (0 == port_name.compare(ports_[module_id][port].get_name())) { + /* Find it, return the id */ + return port; + } + } + /* Not found, return an invalid id */ + return ModulePortId::INVALID(); +} + +/* Find the Port information with a given port id */ +BasicPort ModuleManager::module_port(const ModuleId& module_id, const ModulePortId& port_id) const { + /* Validate the module and port id */ + VTR_ASSERT(valid_module_port_id(module_id, port_id)); + return ports_[module_id][port_id]; +} + +/* Find the module id by a given name, return invalid if not found */ +ModuleId ModuleManager::find_module(const std::string& name) const { + if (name_id_map_.find(name) != name_id_map_.end()) { + /* Find it, return the id */ + return name_id_map_.at(name); + } + /* Not found, return an invalid id */ + return ModuleId::INVALID(); +} + +/* Find the number of instances of a child module in the parent module */ +size_t ModuleManager::num_instance(const ModuleId& parent_module, const ModuleId& child_module) const { + size_t child_index = find_child_module_index_in_parent_module(parent_module, child_module); + if (size_t(-1) == child_index) { + /* Not found, return a zero */ + return 0; + } + + return num_child_instances_[parent_module][child_index]; +} + +/* Find the instance name of a child module */ +std::string ModuleManager::instance_name(const ModuleId& parent_module, const ModuleId& child_module, + const size_t& instance_id) const { + /* Validate the id of both parent and child modules */ + VTR_ASSERT ( valid_module_id(parent_module) ); + VTR_ASSERT ( valid_module_id(child_module) ); + + /* Find the index of child module in the child list of parent module */ + size_t child_index = find_child_module_index_in_parent_module(parent_module, child_module); + VTR_ASSERT (child_index < children_[parent_module].size()); + /* Ensure that instance id is valid */ + VTR_ASSERT (instance_id < num_instance(parent_module, child_module)); + return child_instance_names_[parent_module][child_index][instance_id]; +} + +/* Find the instance id of a given instance name */ +size_t ModuleManager::instance_id(const ModuleId& parent_module, const ModuleId& child_module, + const std::string& instance_name) const { + /* Validate the id of both parent and child modules */ + VTR_ASSERT ( valid_module_id(parent_module) ); + VTR_ASSERT ( valid_module_id(child_module) ); + + /* Find the index of child module in the child list of parent module */ + size_t child_index = find_child_module_index_in_parent_module(parent_module, child_module); + VTR_ASSERT (child_index < children_[parent_module].size()); + + /* Search the instance name list and try to find a match */ + for (size_t name_id = 0; name_id < child_instance_names_[parent_module][child_index].size(); ++name_id) { + const std::string& name = child_instance_names_[parent_module][child_index][name_id]; + if (0 == name.compare(instance_name)) { + return name_id; + } + } + + /* Not found, return an invalid name */ + return size_t(-1); +} + +/* Find if a port is a wire connection */ +bool ModuleManager::port_is_wire(const ModuleId& module, const ModulePortId& port) const { + /* validate both module id and port id*/ + VTR_ASSERT(valid_module_port_id(module, port)); + return port_is_wire_[module][port]; +} + +/* Find if a port is register */ +bool ModuleManager::port_is_register(const ModuleId& module, const ModulePortId& port) const { + /* validate both module id and port id*/ + VTR_ASSERT(valid_module_port_id(module, port)); + return port_is_register_[module][port]; +} + +/* Return the pre-processing flag of a port */ +std::string ModuleManager::port_preproc_flag(const ModuleId& module, const ModulePortId& port) const { + /* validate both module id and port id*/ + VTR_ASSERT(valid_module_port_id(module, port)); + return port_preproc_flags_[module][port]; +} + +/* Find a net from an instance of a module */ +ModuleNetId ModuleManager::module_instance_port_net(const ModuleId& parent_module, + const ModuleId& child_module, const size_t& child_instance, + const ModulePortId& child_port, const size_t& child_pin) const { + /* Validate parent_module */ + VTR_ASSERT(valid_module_id(parent_module)); + + /* Validate child_module */ + VTR_ASSERT(valid_module_id(child_module)); + + /* Validate instance id */ + if (child_module == parent_module) { + /* Assume a default instance id as zero */ + VTR_ASSERT(0 == child_instance); + } else { + VTR_ASSERT(child_instance < num_instance(parent_module, child_module)); + } + + /* Validate child_port */ + VTR_ASSERT(valid_module_port_id(child_module, child_port)); + + /* Validate child_pin */ + VTR_ASSERT(child_pin < module_port(child_module, child_port).get_width()); + + return net_lookup_[parent_module][child_module][child_instance][child_port][child_pin]; +} + +/* Find the name of net */ +std::string ModuleManager::net_name(const ModuleId& module, const ModuleNetId& net) const { + /* Validate module net */ + VTR_ASSERT(valid_module_net_id(module, net)); + + return net_names_[module][net]; +} + +/* Find the source modules of a net */ +vtr::vector ModuleManager::net_source_modules(const ModuleId& module, const ModuleNetId& net) const { + /* Validate module net */ + VTR_ASSERT(valid_module_net_id(module, net)); + + return net_src_module_ids_[module][net]; +} + +/* Find the ids of source instances of a net */ +vtr::vector ModuleManager::net_source_instances(const ModuleId& module, const ModuleNetId& net) const { + /* Validate module net */ + VTR_ASSERT(valid_module_net_id(module, net)); + + return net_src_instance_ids_[module][net]; +} + +/* Find the source ports of a net */ +vtr::vector ModuleManager::net_source_ports(const ModuleId& module, const ModuleNetId& net) const { + /* Validate module net */ + VTR_ASSERT(valid_module_net_id(module, net)); + + return net_src_port_ids_[module][net]; +} + +/* Find the source pin indices of a net */ +vtr::vector ModuleManager::net_source_pins(const ModuleId& module, const ModuleNetId& net) const { + /* Validate module net */ + VTR_ASSERT(valid_module_net_id(module, net)); + + return net_src_pin_ids_[module][net]; +} + +/* Identify if a pin of a port in a module already exists in the net source list*/ +bool ModuleManager::net_source_exist(const ModuleId& module, const ModuleNetId& net, + const ModuleId& src_module, const size_t& instance_id, + const ModulePortId& src_port, const size_t& src_pin) { + /* Validate module net */ + VTR_ASSERT(valid_module_net_id(module, net)); + + /* Iterate over each source of the net. + * If a net source has the same src_module, instance_id, src_port and src_pin, + * we can say that the source has already been added to this net! + */ + for (const ModuleNetSrcId& net_src : module_net_sources(module, net)) { + if ( (src_module == net_source_modules(module, net)[net_src]) + && (instance_id == net_source_instances(module, net)[net_src]) + && (src_port == net_source_ports(module, net)[net_src]) + && (src_pin == net_source_pins(module, net)[net_src]) ) { + return true; + } + } + + /* Reach here, it means nothing has been found. Return false */ + return false; +} + +/* Find the sink modules of a net */ +vtr::vector ModuleManager::net_sink_modules(const ModuleId& module, const ModuleNetId& net) const { + /* Validate module net */ + VTR_ASSERT(valid_module_net_id(module, net)); + + return net_sink_module_ids_[module][net]; +} + +/* Find the ids of sink instances of a net */ +vtr::vector ModuleManager::net_sink_instances(const ModuleId& module, const ModuleNetId& net) const { + /* Validate module net */ + VTR_ASSERT(valid_module_net_id(module, net)); + + return net_sink_instance_ids_[module][net]; +} + +/* Find the sink ports of a net */ +vtr::vector ModuleManager::net_sink_ports(const ModuleId& module, const ModuleNetId& net) const { + /* Validate module net */ + VTR_ASSERT(valid_module_net_id(module, net)); + + return net_sink_port_ids_[module][net]; +} + +/* Find the sink pin indices of a net */ +vtr::vector ModuleManager::net_sink_pins(const ModuleId& module, const ModuleNetId& net) const { + /* Validate module net */ + VTR_ASSERT(valid_module_net_id(module, net)); + + return net_sink_pin_ids_[module][net]; +} + +/* Identify if a pin of a port in a module already exists in the net sink list*/ +bool ModuleManager::net_sink_exist(const ModuleId& module, const ModuleNetId& net, + const ModuleId& sink_module, const size_t& instance_id, + const ModulePortId& sink_port, const size_t& sink_pin) { + /* Validate module net */ + VTR_ASSERT(valid_module_net_id(module, net)); + + /* Iterate over each sink of the net. + * If a net sink has the same sink_module, instance_id, sink_port and sink_pin, + * we can say that the sink has already been added to this net! + */ + for (const ModuleNetSinkId& net_sink : module_net_sinks(module, net)) { + if ( (sink_module == net_sink_modules(module, net)[net_sink]) + && (instance_id == net_sink_instances(module, net)[net_sink]) + && (sink_port == net_sink_ports(module, net)[net_sink]) + && (sink_pin == net_sink_pins(module, net)[net_sink]) ) { + return true; + } + } + + /* Reach here, it means nothing has been found. Return false */ + return false; +} + +/****************************************************************************** + * Private Accessors + ******************************************************************************/ +size_t ModuleManager::find_child_module_index_in_parent_module(const ModuleId& parent_module, const ModuleId& child_module) const { + /* validate both module ids */ + VTR_ASSERT(valid_module_id(parent_module)); + VTR_ASSERT(valid_module_id(child_module)); + /* Try to find the child_module in the children list of parent_module*/ + for (size_t i = 0; i < children_[parent_module].size(); ++i) { + if (child_module == children_[parent_module][i]) { + /* Found, return the number of instances */ + return i; + } + } + /* Not found: return an valid value */ + return size_t(-1); +} + +/****************************************************************************** + * Public Mutators + ******************************************************************************/ +/* Add a module */ +ModuleId ModuleManager::add_module(const std::string& name) { + /* Find if the name has been used. If used, return an invalid Id and report error! */ + std::map::iterator it = name_id_map_.find(name); + if (it != name_id_map_.end()) { + return ModuleId::INVALID(); + } + + /* Create an new id */ + ModuleId module = ModuleId(ids_.size()); + ids_.push_back(module); + + /* Allocate other attributes */ + names_.push_back(name); + parents_.emplace_back(); + children_.emplace_back(); + num_child_instances_.emplace_back(); + child_instance_names_.emplace_back(); + configurable_children_.emplace_back(); + configurable_child_instances_.emplace_back(); + + port_ids_.emplace_back(); + ports_.emplace_back(); + port_types_.emplace_back(); + port_is_wire_.emplace_back(); + port_is_register_.emplace_back(); + port_preproc_flags_.emplace_back(); + + net_ids_.emplace_back(); + net_names_.emplace_back(); + net_src_ids_.emplace_back(); + net_src_module_ids_.emplace_back(); + net_src_instance_ids_.emplace_back(); + net_src_port_ids_.emplace_back(); + net_src_pin_ids_.emplace_back(); + + net_sink_ids_.emplace_back(); + net_sink_module_ids_.emplace_back(); + net_sink_instance_ids_.emplace_back(); + net_sink_port_ids_.emplace_back(); + net_sink_pin_ids_.emplace_back(); + + /* Register in the name-to-id map */ + name_id_map_[name] = module; + + /* Build port lookup */ + port_lookup_.emplace_back(); + port_lookup_[module].resize(NUM_MODULE_PORT_TYPES); + + /* Build fast look-up for nets */ + net_lookup_.emplace_back(); + /* Reserve the instance 0 for the module */ + net_lookup_[module][module].emplace_back(); + + /* Return the new id */ + return module; +} + +/* Add a port to a module */ +ModulePortId ModuleManager::add_port(const ModuleId& module, + const BasicPort& port_info, const enum e_module_port_type& port_type) { + /* Validate the id of module */ + VTR_ASSERT( valid_module_id(module) ); + + /* Add port and fill port attributes */ + ModulePortId port = ModulePortId(port_ids_[module].size()); + port_ids_[module].push_back(port); + ports_[module].push_back(port_info); + port_types_[module].push_back(port_type); + port_is_wire_[module].push_back(false); + port_is_register_[module].push_back(false); + port_preproc_flags_[module].emplace_back(); /* Create an empty string for the pre-processing flags */ + + /* Update fast look-up for port */ + port_lookup_[module][port_type].push_back(port); + + /* Update fast look-up for nets */ + VTR_ASSERT_SAFE(1 == net_lookup_[module][module].size()); + net_lookup_[module][module][0][port].resize(port_info.get_width(), ModuleNetId::INVALID()); + + return port; +} + +/* Set a name for a module port */ +void ModuleManager::set_module_port_name(const ModuleId& module, const ModulePortId& module_port, + const std::string& port_name) { + /* Validate the id of module port */ + VTR_ASSERT( valid_module_port_id(module, module_port) ); + + ports_[module][module_port].set_name(port_name); +} + +/* Set a name for a module */ +void ModuleManager::set_module_name(const ModuleId& module, const std::string& name) { + /* Validate the id of module */ + VTR_ASSERT( valid_module_id(module) ); + names_[module] = name; +} + +/* Set a port to be a wire */ +void ModuleManager::set_port_is_wire(const ModuleId& module, const std::string& port_name, const bool& is_wire) { + /* Find the port */ + ModulePortId port = find_module_port(module, port_name); + /* Must find something, otherwise drop an error */ + VTR_ASSERT(ModulePortId::INVALID() != port); + port_is_wire_[module][port] = is_wire; +} + +/* Set a port to be a register */ +void ModuleManager::set_port_is_register(const ModuleId& module, const std::string& port_name, const bool& is_register) { + /* Find the port */ + ModulePortId port = find_module_port(module, port_name); + /* Must find something, otherwise drop an error */ + VTR_ASSERT(ModulePortId::INVALID() != port); + port_is_register_[module][port] = is_register; +} + +/* Set the preprocessing flag for a port */ +void ModuleManager::set_port_preproc_flag(const ModuleId& module, const ModulePortId& port, const std::string& preproc_flag) { + /* Must find something, otherwise drop an error */ + VTR_ASSERT(valid_module_port_id(module, port)); + port_preproc_flags_[module][port] = preproc_flag; +} + +/* Add a child module to a parent module */ +void ModuleManager::add_child_module(const ModuleId& parent_module, const ModuleId& child_module) { + /* Validate the id of both parent and child modules */ + VTR_ASSERT ( valid_module_id(parent_module) ); + VTR_ASSERT ( valid_module_id(child_module) ); + + /* Try to find if the parent module is already in the list */ + std::vector::iterator parent_it = std::find(parents_[child_module].begin(), parents_[child_module].end(), parent_module); + if (parent_it == parents_[child_module].end()) { + /* Update the parent module of child module */ + parents_[child_module].push_back(parent_module); + } + + std::vector::iterator child_it = std::find(children_[parent_module].begin(), children_[parent_module].end(), child_module); + if (child_it == children_[parent_module].end()) { + /* Update the child module of parent module */ + children_[parent_module].push_back(child_module); + num_child_instances_[parent_module].push_back(1); /* By default give one */ + /* Update the instance name list */ + child_instance_names_[parent_module].emplace_back(); + child_instance_names_[parent_module].back().emplace_back(); + } else { + /* Increase the counter of instances */ + num_child_instances_[parent_module][child_it - children_[parent_module].begin()]++; + child_instance_names_[parent_module][child_it - children_[parent_module].begin()].emplace_back(); + } + + /* Update fast look-up for nets */ + size_t instance_id = net_lookup_[parent_module][child_module].size(); + net_lookup_[parent_module][child_module].emplace_back(); + /* Find the ports for the child module and update the fast look-up */ + for (ModulePortId child_port : port_ids_[child_module]) { + net_lookup_[parent_module][child_module][instance_id][child_port].resize(ports_[child_module][child_port].get_width(), ModuleNetId::INVALID()); + } +} + +/* Set the instance name of a child module */ +void ModuleManager::set_child_instance_name(const ModuleId& parent_module, + const ModuleId& child_module, + const size_t& instance_id, + const std::string& instance_name) { + /* Validate the id of both parent and child modules */ + VTR_ASSERT ( valid_module_id(parent_module) ); + VTR_ASSERT ( valid_module_id(child_module) ); + /* Ensure that the instance id is in range */ + VTR_ASSERT ( instance_id < num_instance(parent_module, child_module)); + /* Try to find the child_module in the children list of parent_module*/ + size_t child_index = find_child_module_index_in_parent_module(parent_module, child_module); + /* We must find something! */ + VTR_ASSERT(size_t(-1) != child_index); + /* Set the name */ + child_instance_names_[parent_module][child_index][instance_id] = instance_name; +} + +/* Add a configurable child module to module + * Note: this function should be called after add_child_module! + * It will check if the child module does exist in the parent module + * And the instance id is in range or not + */ +void ModuleManager::add_configurable_child(const ModuleId& parent_module, + const ModuleId& child_module, + const size_t& child_instance) { + /* Validate the id of both parent and child modules */ + VTR_ASSERT ( valid_module_id(parent_module) ); + VTR_ASSERT ( valid_module_id(child_module) ); + /* Ensure that the instance id is in range */ + VTR_ASSERT ( child_instance < num_instance(parent_module, child_module)); + + configurable_children_[parent_module].push_back(child_module); + configurable_child_instances_[parent_module].push_back(child_instance); +} + +/* Add a net to the connection graph of the module */ +ModuleNetId ModuleManager::create_module_net(const ModuleId& module) { + /* Validate the module id */ + VTR_ASSERT ( valid_module_id(module) ); + + /* Create an new id */ + ModuleNetId net = ModuleNetId(net_ids_[module].size()); + net_ids_[module].push_back(net); + + /* Allocate net-related data structures */ + net_names_[module].emplace_back(); + net_src_ids_[module].emplace_back(); + net_src_module_ids_[module].emplace_back(); + net_src_instance_ids_[module].emplace_back(); + net_src_port_ids_[module].emplace_back(); + net_src_pin_ids_[module].emplace_back(); + + net_sink_ids_[module].emplace_back(); + net_sink_module_ids_[module].emplace_back(); + net_sink_instance_ids_[module].emplace_back(); + net_sink_port_ids_[module].emplace_back(); + net_sink_pin_ids_[module].emplace_back(); + + return net; +} + +/* Set the name of net */ +void ModuleManager::set_net_name(const ModuleId& module, const ModuleNetId& net, + const std::string& name) { + /* Validate module net */ + VTR_ASSERT(valid_module_net_id(module, net)); + + net_names_[module][net] = name; +} + +/* Add a source to a net in the connection graph */ +ModuleNetSrcId ModuleManager::add_module_net_source(const ModuleId& module, const ModuleNetId& net, + const ModuleId& src_module, const size_t& instance_id, + const ModulePortId& src_port, const size_t& src_pin) { + /* Validate the module and net id */ + VTR_ASSERT(valid_module_net_id(module, net)); + + /* Create a new id for src node */ + ModuleNetSrcId net_src = ModuleNetSrcId(net_src_ids_[module][net].size()); + net_src_ids_[module][net].push_back(net_src); + + /* Validate the source module */ + VTR_ASSERT(valid_module_id(src_module)); + net_src_module_ids_[module][net].push_back(src_module); + + /* if it has the same id as module, our instance id will be by default 0 */ + size_t src_instance_id = instance_id; + if (src_module == module) { + src_instance_id = 0; + net_src_instance_ids_[module][net].push_back(src_instance_id); + } else { + /* Check the instance id of the src module */ + VTR_ASSERT (src_instance_id < num_instance(module, src_module)); + net_src_instance_ids_[module][net].push_back(src_instance_id); + } + + /* Validate the port exists in the src module */ + VTR_ASSERT(valid_module_port_id(src_module, src_port)); + net_src_port_ids_[module][net].push_back(src_port); + + /* Validate the pin id is in the range of the port width */ + VTR_ASSERT(src_pin < module_port(src_module, src_port).get_width()); + net_src_pin_ids_[module][net].push_back(src_pin); + + /* Update fast look-up for nets */ + net_lookup_[module][src_module][src_instance_id][src_port][src_pin] = net; + + return net_src; +} + +/* Add a sink to a net in the connection graph */ +ModuleNetSinkId ModuleManager::add_module_net_sink(const ModuleId& module, const ModuleNetId& net, + const ModuleId& sink_module, const size_t& instance_id, + const ModulePortId& sink_port, const size_t& sink_pin) { + /* Validate the module and net id */ + VTR_ASSERT(valid_module_net_id(module, net)); + + /* Create a new id for sink node */ + ModuleNetSinkId net_sink = ModuleNetSinkId(net_sink_ids_[module][net].size()); + net_sink_ids_[module][net].push_back(net_sink); + + /* Validate the source module */ + VTR_ASSERT(valid_module_id(sink_module)); + net_sink_module_ids_[module][net].push_back(sink_module); + + /* if it has the same id as module, our instance id will be by default 0 */ + size_t sink_instance_id = instance_id; + if (sink_module == module) { + sink_instance_id = 0; + net_sink_instance_ids_[module][net].push_back(sink_instance_id); + } else { + /* Check the instance id of the src module */ + VTR_ASSERT (sink_instance_id < num_instance(module, sink_module)); + net_sink_instance_ids_[module][net].push_back(sink_instance_id); + } + + /* Validate the port exists in the sink module */ + VTR_ASSERT(valid_module_port_id(sink_module, sink_port)); + net_sink_port_ids_[module][net].push_back(sink_port); + + /* Validate the pin id is in the range of the port width */ + VTR_ASSERT(sink_pin < module_port(sink_module, sink_port).get_width()); + net_sink_pin_ids_[module][net].push_back(sink_pin); + + /* Update fast look-up for nets */ + net_lookup_[module][sink_module][sink_instance_id][sink_port][sink_pin] = net; + + return net_sink; +} + +/****************************************************************************** + * Private validators/invalidators + ******************************************************************************/ +bool ModuleManager::valid_module_id(const ModuleId& module) const { + return ( size_t(module) < ids_.size() ) && ( module == ids_[module] ); +} + +bool ModuleManager::valid_module_port_id(const ModuleId& module, const ModulePortId& port) const { + if (false == valid_module_id(module)) { + return false; + } + return ( size_t(port) < port_ids_[module].size() ) && ( port == port_ids_[module][port] ); +} + +bool ModuleManager::valid_module_net_id(const ModuleId& module, const ModuleNetId& net) const { + if (false == valid_module_id(module)) { + return false; + } + return ( size_t(net) < net_ids_[module].size() ) && ( net == net_ids_[module][net] ); +} + +void ModuleManager::invalidate_name2id_map() { + name_id_map_.clear(); +} + +void ModuleManager::invalidate_port_lookup() { + port_lookup_.clear(); +} + +void ModuleManager::invalidate_net_lookup() { + net_lookup_.clear(); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fabric/module_manager.h b/openfpga/src/fabric/module_manager.h new file mode 100644 index 000000000..425b7a072 --- /dev/null +++ b/openfpga/src/fabric/module_manager.h @@ -0,0 +1,238 @@ +#ifndef MODULE_MANAGER_H +#define MODULE_MANAGER_H + +#include +#include +#include "vtr_vector.h" +#include "module_manager_fwd.h" +#include "openfpga_port.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/****************************************************************************** + * This files includes data structures for module management. + * It keeps a list of modules that have been generated, the port map of the modules, + * parents and children of each modules. This will ease instanciation of modules + * with explicit port map and outputting a hierarchy of modules + * + * Module includes the basic information: + * 1. unique identifier + * 2. module name: which should be unique + * 3. port list: basic information of all the ports belonging to the module + * 4. port types: types of each port, which will matter how we output the ports + * 5. parent modules: ids of parent modules + * 6. children modules: ids of child modules + ******************************************************************************/ +class ModuleManager { + public: /* Private data structures */ + enum e_module_port_type { + MODULE_GLOBAL_PORT, /* Global inputs */ + MODULE_GPIO_PORT, /* General-purpose IOs, which are data IOs of the fabric */ + MODULE_INOUT_PORT, /* Normal (non-global) inout ports */ + MODULE_INPUT_PORT, /* Normal (non-global) input ports */ + MODULE_OUTPUT_PORT, /* Normal (non-global) output ports */ + MODULE_CLOCK_PORT, /* Nromal (non-global) clock ports*/ + NUM_MODULE_PORT_TYPES + }; + + public: /* Public Constructors */ + + public: /* Types and ranges */ + typedef vtr::vector::const_iterator module_iterator; + typedef vtr::vector::const_iterator module_port_iterator; + typedef vtr::vector::const_iterator module_net_iterator; + typedef vtr::vector::const_iterator module_net_src_iterator; + typedef vtr::vector::const_iterator module_net_sink_iterator; + + typedef vtr::Range module_range; + typedef vtr::Range module_port_range; + typedef vtr::Range module_net_range; + typedef vtr::Range module_net_src_range; + typedef vtr::Range module_net_sink_range; + + public: /* Public aggregators */ + /* Find all the modules */ + module_range modules() const; + /* Find all the ports belonging to a module */ + module_port_range module_ports(const ModuleId& module) const; + /* Find all the nets belonging to a module */ + module_net_range module_nets(const ModuleId& module) const; + /* Find all the child modules under a parent module */ + std::vector child_modules(const ModuleId& parent_module) const; + /* Find all the instances under a parent module */ + std::vector child_module_instances(const ModuleId& parent_module, const ModuleId& child_module) const; + /* Find all the configurable child modules under a parent module */ + std::vector configurable_children(const ModuleId& parent_module) const; + /* Find all the instances of configurable child modules under a parent module */ + std::vector configurable_child_instances(const ModuleId& parent_module) const; + /* Find the source ids of modules */ + module_net_src_range module_net_sources(const ModuleId& module, const ModuleNetId& net) const; + /* Find the sink ids of modules */ + module_net_sink_range module_net_sinks(const ModuleId& module, const ModuleNetId& net) const; + + public: /* Public accessors */ + size_t num_modules() const; + size_t num_nets(const ModuleId& module) const; + std::string module_name(const ModuleId& module_id) const; + std::string module_port_type_str(const enum e_module_port_type& port_type) const; + std::vector module_ports_by_type(const ModuleId& module_id, const enum e_module_port_type& port_type) const; + std::vector module_port_ids_by_type(const ModuleId& module_id, const enum e_module_port_type& port_type) const; + /* Find a port of a module by a given name */ + ModulePortId find_module_port(const ModuleId& module_id, const std::string& port_name) const; + /* Find the Port information with a given port id */ + BasicPort module_port(const ModuleId& module_id, const ModulePortId& port_id) const; + /* Find a module by a given name */ + ModuleId find_module(const std::string& name) const; + /* Find the number of instances of a child module in the parent module */ + size_t num_instance(const ModuleId& parent_module, const ModuleId& child_module) const; + /* Find the instance name of a child module */ + std::string instance_name(const ModuleId& parent_module, const ModuleId& child_module, + const size_t& instance_id) const; + /* Find the instance id of a given instance name */ + size_t instance_id(const ModuleId& parent_module, const ModuleId& child_module, + const std::string& instance_name) const; + /* Find if a port is a wire connection */ + bool port_is_wire(const ModuleId& module, const ModulePortId& port) const; + /* Find if a port is register */ + bool port_is_register(const ModuleId& module, const ModulePortId& port) const; + /* Return the pre-processing flag of a port */ + std::string port_preproc_flag(const ModuleId& module, const ModulePortId& port) const; + /* Find a net from an instance of a module */ + ModuleNetId module_instance_port_net(const ModuleId& parent_module, + const ModuleId& child_module, const size_t& child_instance, + const ModulePortId& child_port, const size_t& child_pin) const; + /* Find the name of net */ + std::string net_name(const ModuleId& module, const ModuleNetId& net) const; + /* Find the source modules of a net */ + vtr::vector net_source_modules(const ModuleId& module, const ModuleNetId& net) const; + /* Find the ids of source instances of a net */ + vtr::vector net_source_instances(const ModuleId& module, const ModuleNetId& net) const; + /* Find the source ports of a net */ + vtr::vector net_source_ports(const ModuleId& module, const ModuleNetId& net) const; + /* Find the source pin indices of a net */ + vtr::vector net_source_pins(const ModuleId& module, const ModuleNetId& net) const; + /* Identify if a pin of a port in a module already exists in the net source list*/ + bool net_source_exist(const ModuleId& module, const ModuleNetId& net, + const ModuleId& src_module, const size_t& instance_id, + const ModulePortId& src_port, const size_t& src_pin); + + /* Find the sink modules of a net */ + vtr::vector net_sink_modules(const ModuleId& module, const ModuleNetId& net) const; + /* Find the ids of sink instances of a net */ + vtr::vector net_sink_instances(const ModuleId& module, const ModuleNetId& net) const; + /* Find the sink ports of a net */ + vtr::vector net_sink_ports(const ModuleId& module, const ModuleNetId& net) const; + /* Find the sink pin indices of a net */ + vtr::vector net_sink_pins(const ModuleId& module, const ModuleNetId& net) const; + /* Identify if a pin of a port in a module already exists in the net sink list*/ + bool net_sink_exist(const ModuleId& module, const ModuleNetId& net, + const ModuleId& sink_module, const size_t& instance_id, + const ModulePortId& sink_port, const size_t& sink_pin); + + private: /* Private accessors */ + size_t find_child_module_index_in_parent_module(const ModuleId& parent_module, const ModuleId& child_module) const; + public: /* Public mutators */ + /* Add a module */ + ModuleId add_module(const std::string& name); + /* Add a port to a module */ + ModulePortId add_port(const ModuleId& module, + const BasicPort& port_info, const enum e_module_port_type& port_type); + /* Set a name for a module port */ + void set_module_port_name(const ModuleId& module, const ModulePortId& module_port, const std::string& port_name); + /* Set a name for a module */ + void set_module_name(const ModuleId& module, const std::string& name); + /* Set a port to be a wire */ + void set_port_is_wire(const ModuleId& module, const std::string& port_name, const bool& is_wire); + /* Set a port to be a register */ + void set_port_is_register(const ModuleId& module, const std::string& port_name, const bool& is_register); + /* Set the preprocessing flag for a port */ + void set_port_preproc_flag(const ModuleId& module, const ModulePortId& port, const std::string& preproc_flag); + /* Add a child module to a parent module */ + void add_child_module(const ModuleId& parent_module, const ModuleId& child_module); + /* Set the instance name of a child module */ + void set_child_instance_name(const ModuleId& parent_module, const ModuleId& child_module, const size_t& instance_id, const std::string& instance_name); + /* Add a configurable child module to module */ + void add_configurable_child(const ModuleId& module, const ModuleId& child_module, const size_t& child_instance); + /* Add a net to the connection graph of the module */ + ModuleNetId create_module_net(const ModuleId& module); + /* Set the name of net */ + void set_net_name(const ModuleId& module, const ModuleNetId& net, + const std::string& name); + /* Add a source to a net in the connection graph */ + ModuleNetSrcId add_module_net_source(const ModuleId& module, const ModuleNetId& net, + const ModuleId& src_module, const size_t& instance_id, + const ModulePortId& src_port, const size_t& src_pin); + /* Add a sink to a net in the connection graph */ + ModuleNetSinkId add_module_net_sink(const ModuleId& module, const ModuleNetId& net, + const ModuleId& sink_module, const size_t& instance_id, + const ModulePortId& sink_port, const size_t& sink_pin); + public: /* Public validators/invalidators */ + bool valid_module_id(const ModuleId& module) const; + bool valid_module_port_id(const ModuleId& module, const ModulePortId& port) const; + bool valid_module_net_id(const ModuleId& module, const ModuleNetId& net) const; + private: /* Private validators/invalidators */ + void invalidate_name2id_map(); + void invalidate_port_lookup(); + void invalidate_net_lookup(); + private: /* Internal data */ + /* Module-level data */ + vtr::vector ids_; /* Unique identifier for each Module */ + vtr::vector names_; /* Unique identifier for each Module */ + vtr::vector> parents_; /* Parent modules that include the module */ + vtr::vector> children_; /* Child modules that this module contain */ + vtr::vector> num_child_instances_; /* Number of children instance in each child module */ + vtr::vector>> child_instance_names_; /* Number of children instance in each child module */ + + /* Configurable child modules are used to record the position of configurable modules in bitstream + * The sequence of children in the list denotes which one is configured first, etc. + * Note that the sequence can be totally different from the children_ list + * This is really dependent how the configuration protocol is organized + * which should be made by users/designers + */ + vtr::vector> configurable_children_; /* Child modules with configurable memory bits that this module contain */ + vtr::vector> configurable_child_instances_; /* Instances of child modules with configurable memory bits that this module contain */ + + /* Port-level data */ + vtr::vector> port_ids_; /* List of ports for each Module */ + vtr::vector> ports_; /* List of ports for each Module */ + vtr::vector> port_types_; /* Type of ports */ + vtr::vector> port_is_wire_; /* If the port is a wire, use for Verilog port definition. If enabled: reg */ + vtr::vector> port_is_register_; /* If the port is a register, use for Verilog port definition. If enabled: reg */ + vtr::vector> port_preproc_flags_; /* If a port is available only when a pre-processing flag is enabled. This is to record the pre-processing flags */ + + /* Graph-level data: + * We use nets to model the connection between pins of modules and instances. + * To avoid large memory footprint, we do NOT create pins, + * To enable fast look-up on pins, we create a fast look-up + */ + vtr::vector> net_ids_; /* List of nets for each Module */ + vtr::vector> net_names_; /* Name of net */ + + vtr::vector>> net_src_ids_; /* Unique id of the source that drive the net */ + vtr::vector>> net_src_module_ids_; /* Pin ids that drive the net */ + vtr::vector>> net_src_instance_ids_; /* Pin ids that drive the net */ + vtr::vector>> net_src_port_ids_; /* Pin ids that drive the net */ + vtr::vector>> net_src_pin_ids_; /* Pin ids that drive the net */ + + + vtr::vector>> net_sink_ids_; /* Unique ids of the sink that the net drives */ + vtr::vector>> net_sink_module_ids_; /* Pin ids that the net drives */ + vtr::vector>> net_sink_instance_ids_; /* Pin ids that drive the net */ + vtr::vector>> net_sink_port_ids_; /* Pin ids that drive the net */ + vtr::vector>> net_sink_pin_ids_; /* Pin ids that drive the net */ + + /* fast look-up for module */ + std::map name_id_map_; + /* fast look-up for ports */ + typedef vtr::vector>> PortLookup; + mutable PortLookup port_lookup_; /* [module_ids][port_types][port_ids] */ + + /* fast look-up for nets */ + typedef vtr::vector>>>> NetLookup; + mutable NetLookup net_lookup_; /* [module_ids][module_ids][instance_ids][port_ids][pin_ids] */ +}; + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fabric/module_manager_fwd.h b/openfpga/src/fabric/module_manager_fwd.h new file mode 100644 index 000000000..695ce1024 --- /dev/null +++ b/openfpga/src/fabric/module_manager_fwd.h @@ -0,0 +1,35 @@ +/************************************************** + * This file includes only declarations for + * the data structures for module managers + * Please refer to module_manager.h for more details + *************************************************/ +#ifndef MODULE_MANAGER_FWD_H +#define MODULE_MANAGER_FWD_H + +#include "vtr_strong_id.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/* Strong Ids for ModuleManager */ +struct module_id_tag; +struct instance_id_tag; /* TODO: use instance id in module_manager */ +struct module_port_id_tag; +struct module_pin_id_tag; +struct module_net_id_tag; +struct module_net_src_id_tag; +struct module_net_sink_id_tag; + +typedef vtr::StrongId ModuleId; +typedef vtr::StrongId InstanceId; +typedef vtr::StrongId ModulePortId; +typedef vtr::StrongId ModulePinId; +typedef vtr::StrongId ModuleNetId; +typedef vtr::StrongId ModuleNetSrcId; +typedef vtr::StrongId ModuleNetSinkId; + +class ModuleManager; + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/utils/circuit_library_utils.cpp b/openfpga/src/utils/circuit_library_utils.cpp index 720a0e7c1..cecbcafed 100644 --- a/openfpga/src/utils/circuit_library_utils.cpp +++ b/openfpga/src/utils/circuit_library_utils.cpp @@ -4,9 +4,10 @@ * They are made to ease the development in some specific purposes * Please classify such functions in this file ***********************************************************************/ + #include -/* Headers from vtr util library */ +/* Headers from vtrutil library */ #include "vtr_assert.h" #include "vtr_log.h" @@ -83,11 +84,11 @@ std::vector find_circuit_mode_select_sram_ports(const CircuitLibr static size_t find_rram_circuit_num_shared_config_bits(const CircuitLibrary& circuit_lib, const CircuitModelId& rram_model, - const e_config_protocol_type& config_protocol_type) { + const e_config_protocol_type& sram_orgz_type) { size_t num_shared_config_bits = 0; /* Branch on the organization of configuration protocol */ - switch (config_protocol_type) { + switch (sram_orgz_type) { case CONFIG_MEM_STANDALONE: case CONFIG_MEM_SCAN_CHAIN: break; @@ -100,7 +101,7 @@ size_t find_rram_circuit_num_shared_config_bits(const CircuitLibrary& circuit_li break; } default: - VTR_LOG_ERROR("Invalid type of configuration protocol!\n"); + VTR_LOG_ERROR("Invalid type of SRAM organization!\n"); exit(1); } @@ -123,7 +124,7 @@ size_t find_rram_circuit_num_shared_config_bits(const CircuitLibrary& circuit_li *******************************************************************/ size_t find_circuit_num_shared_config_bits(const CircuitLibrary& circuit_lib, const CircuitModelId& circuit_model, - const e_config_protocol_type& config_protocol_type) { + const e_config_protocol_type& sram_orgz_type) { size_t num_shared_config_bits = 0; std::vector sram_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_SRAM); @@ -138,11 +139,10 @@ size_t find_circuit_num_shared_config_bits(const CircuitLibrary& circuit_lib, break; case CIRCUIT_MODEL_DESIGN_RRAM: /* RRAM circuit do need shared configuration bits, but it is subjected to the largest one among different SRAM models */ - num_shared_config_bits = std::max((int)num_shared_config_bits, (int)find_rram_circuit_num_shared_config_bits(circuit_lib, sram_model, config_protocol_type)); + num_shared_config_bits = std::max((int)num_shared_config_bits, (int)find_rram_circuit_num_shared_config_bits(circuit_lib, sram_model, sram_orgz_type)); break; default: - VTR_LOG_ERROR("Invalid design technology for SRAM circuit model!\n", - circuit_lib.model_name(circuit_model).c_str()); + VTR_LOG_ERROR("Invalid design technology for SRAM model!\n"); exit(1); } } diff --git a/openfpga/src/utils/circuit_library_utils.h b/openfpga/src/utils/circuit_library_utils.h index aad080b72..7c5552304 100644 --- a/openfpga/src/utils/circuit_library_utils.h +++ b/openfpga/src/utils/circuit_library_utils.h @@ -7,7 +7,6 @@ /******************************************************************** * Include header files that are required by function declaration *******************************************************************/ - #include #include "circuit_types.h" #include "circuit_library.h" diff --git a/openfpga/src/utils/memory_utils.cpp b/openfpga/src/utils/memory_utils.cpp new file mode 100644 index 000000000..8452626d8 --- /dev/null +++ b/openfpga/src/utils/memory_utils.cpp @@ -0,0 +1,385 @@ +/********************************************************************* + * This file includes functions that are used for + * generating ports for memory modules + *********************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" + +#include "openfpga_naming.h" +#include "memory_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/********************************************************************* + * Create a port-to-port map for a CMOS memory module + * + * Configuration Chain + * ------------------- + * + * config_bus (head) config_bus (tail) + * | ^ + * v | + * +-------------------------------------+ + * | CMOS-based Memory Module | + * +-------------------------------------+ + * | | + * v v + * sram_out sram_outb + * + * + * Memory bank + * ----------- + * + * config_bus (BL) config_bus (WL) + * | | + * v v + * +-------------------------------------+ + * | CMOS-based Memory Module | + * +-------------------------------------+ + * | | + * v v + * sram_out sram_outb + * + **********************************************************************/ +static +std::map generate_cmos_mem_module_port2port_map(const BasicPort& config_bus, + const std::vector& mem_output_bus_ports, + const e_config_protocol_type& sram_orgz_type) { + std::map port2port_name_map; + + switch (sram_orgz_type) { + case CONFIG_MEM_STANDALONE: + /* Nothing to do */ + break; + case CONFIG_MEM_SCAN_CHAIN: { + /* Link the head port of the memory module: + * the LSB of config bus port is the head port index + */ + std::vector config_bus_ports; + config_bus_ports.push_back(BasicPort(generate_local_config_bus_port_name(), config_bus.get_msb(), config_bus.get_msb() + 1)); + BasicPort head_port(config_bus_ports[0].get_name(), config_bus_ports[0].get_lsb(), config_bus_ports[0].get_lsb()); + port2port_name_map[generate_configuration_chain_head_name()] = head_port; + + /* Link the tail port of the memory module: + * the MSB of config bus port is the tail port index + */ + BasicPort tail_port(config_bus_ports[0].get_name(), config_bus_ports[0].get_msb(), config_bus_ports[0].get_msb()); + port2port_name_map[generate_configuration_chain_tail_name()] = tail_port; + + /* Link the SRAM output ports of the memory module */ + VTR_ASSERT( 2 == mem_output_bus_ports.size() ); + port2port_name_map[generate_configuration_chain_data_out_name()] = mem_output_bus_ports[0]; + port2port_name_map[generate_configuration_chain_inverted_data_out_name()] = mem_output_bus_ports[1]; + break; + } + case CONFIG_MEM_MEMORY_BANK: + /* TODO: */ + break; + default: + VTR_LOG_ERROR("Invalid type of SRAM organization!\n"); + exit(1); + } + + return port2port_name_map; +} + +/********************************************************************* + * Create a port-to-port map for a ReRAM-based memory module + * Memory bank + * ----------- + * + * config_bus (BL) config_bus (WL) + * | | + * v v + * +-------------------------------------+ + * | ReRAM-based Memory Module | + * +-------------------------------------+ + * | | + * v v + * Mem_out Mem_outb + **********************************************************************/ +static +std::map generate_rram_mem_module_port2port_map(const BasicPort& config_bus, + const std::vector& mem_output_bus_ports, + const e_config_protocol_type& sram_orgz_type) { + std::map port2port_name_map; + + switch (sram_orgz_type) { + case CONFIG_MEM_STANDALONE: + /* Not supported */ + break; + case CONFIG_MEM_SCAN_CHAIN: { + /* Link the head port of the memory module: + * the LSB of config bus port is the head port index + */ + std::vector config_bus_ports; + config_bus_ports.push_back(BasicPort(generate_local_config_bus_port_name(), config_bus.get_msb(), config_bus.get_msb() + 1)); + BasicPort head_port(config_bus_ports[0].get_name(), config_bus_ports[0].get_lsb(), config_bus_ports[0].get_lsb()); + port2port_name_map[generate_configuration_chain_head_name()] = head_port; + + /* Link the tail port of the memory module: + * the MSB of config bus port is the tail port index + */ + BasicPort tail_port(config_bus_ports[0].get_name(), config_bus_ports[0].get_msb(), config_bus_ports[0].get_msb()); + port2port_name_map[generate_configuration_chain_tail_name()] = tail_port; + + /* Link the SRAM output ports of the memory module */ + VTR_ASSERT( 2 == mem_output_bus_ports.size() ); + port2port_name_map[generate_configuration_chain_data_out_name()] = mem_output_bus_ports[0]; + port2port_name_map[generate_configuration_chain_inverted_data_out_name()] = mem_output_bus_ports[1]; + break; + } + case CONFIG_MEM_MEMORY_BANK: + /* TODO: link BL/WL/Reserved Ports to the inputs of a memory module */ + break; + default: + VTR_LOG_ERROR("Invalid type of SRAM organization!\n"); + exit(1); + } + + return port2port_name_map; +} + +/********************************************************************* + * Create a port-to-port map for a memory module + * The content of the port-to-port map will depend not only + * the design technology of the memory cells but also the + * configuration styles of FPGA fabric. + * Here we will branch on the design technology + **********************************************************************/ +std::map generate_mem_module_port2port_map(const BasicPort& config_bus, + const std::vector& mem_output_bus_ports, + const e_circuit_model_design_tech& mem_design_tech, + const e_config_protocol_type& sram_orgz_type) { + std::map port2port_name_map; + + switch (mem_design_tech) { + case CIRCUIT_MODEL_DESIGN_CMOS: + port2port_name_map = generate_cmos_mem_module_port2port_map(config_bus, mem_output_bus_ports, sram_orgz_type); + break; + case CIRCUIT_MODEL_DESIGN_RRAM: + port2port_name_map = generate_rram_mem_module_port2port_map(config_bus, mem_output_bus_ports, sram_orgz_type); + break; + default: + VTR_LOG_ERROR("Invalid type of memory design technology!\n"); + exit(1); + } + + return port2port_name_map; +} + +/********************************************************************* + * Update the LSB and MSB of a configuration bus based on the number of + * memory bits of a CMOS memory module. + **********************************************************************/ +static +void update_cmos_mem_module_config_bus(const e_config_protocol_type& sram_orgz_type, + const size_t& num_config_bits, + BasicPort& config_bus) { + switch (sram_orgz_type) { + case CONFIG_MEM_STANDALONE: + /* Not supported */ + break; + case CONFIG_MEM_SCAN_CHAIN: + /* Scan-chain of a memory module only has a head and a tail. + * LSB and MSB of configuration bus will be shifted to the next head. + */ + VTR_ASSERT(true == config_bus.rotate(1)); + break; + case CONFIG_MEM_MEMORY_BANK: + /* In this case, a memory module has a number of BL/WL and BLB/WLB (possibly). + * LSB and MSB of configuration bus will be shifted by the number of BL/WL/BLB/WLB. + */ + VTR_ASSERT(true == config_bus.rotate(num_config_bits)); + break; + default: + VTR_LOG_ERROR("Invalid type of SRAM organization!\n"); + exit(1); + } +} + +/********************************************************************* + * Update the LSB and MSB of a configuration bus based on the number of + * memory bits of a ReRAM memory module. + **********************************************************************/ +static +void update_rram_mem_module_config_bus(const e_config_protocol_type& sram_orgz_type, + BasicPort& config_bus) { + switch (sram_orgz_type) { + case CONFIG_MEM_STANDALONE: + /* Not supported */ + break; + case CONFIG_MEM_SCAN_CHAIN: + /* Scan-chain of a memory module only has a head and a tail. + * LSB and MSB of configuration bus will be shifted to the next head. + * TODO: this may be changed later!!! + */ + VTR_ASSERT(true == config_bus.rotate(1)); + break; + case CONFIG_MEM_MEMORY_BANK: + /* In this case, a memory module contains unique BL/WL or BLB/WLB, + * which are not shared with other modules + * TODO: this may be changed later!!! + */ + VTR_ASSERT(true == config_bus.rotate(1)); + break; + default: + VTR_LOG_ERROR("Invalid type of SRAM organization!\n"); + exit(1); + } +} + +/********************************************************************* + * Update the LSB and MSB of a configuration bus based on the number of + * memory bits of a module. + * Note that this function is designed to do such simple job, in purpose of + * being independent from adding ports or printing ports. + * As such, this function can be re-used in bitstream generation + * when Verilog generation is not needed. + * DO NOT update the configuration bus in the function of adding/printing ports + **********************************************************************/ +void update_mem_module_config_bus(const e_config_protocol_type& sram_orgz_type, + const e_circuit_model_design_tech& mem_design_tech, + const size_t& num_config_bits, + BasicPort& config_bus) { + switch (mem_design_tech) { + case CIRCUIT_MODEL_DESIGN_CMOS: + update_cmos_mem_module_config_bus(sram_orgz_type, num_config_bits, config_bus); + break; + case CIRCUIT_MODEL_DESIGN_RRAM: + update_rram_mem_module_config_bus(sram_orgz_type, config_bus); + break; + default: + VTR_LOG_ERROR("Invalid type of memory design technology!\n"); + exit(1); + } +} + +/******************************************************************** + * Check if the MSB of a configuration bus of a switch block + * matches the expected value + ********************************************************************/ +bool check_mem_config_bus(const e_config_protocol_type& sram_orgz_type, + const BasicPort& config_bus, + const size_t& local_expected_msb) { + switch (sram_orgz_type) { + case CONFIG_MEM_STANDALONE: + /* Not supported */ + return false; + break; + case CONFIG_MEM_SCAN_CHAIN: + /* TODO: comment on why + */ + return (local_expected_msb == config_bus.get_msb()); + break; + case CONFIG_MEM_MEMORY_BANK: + /* TODO: comment on why + */ + return (local_expected_msb == config_bus.get_msb()); + break; + default: + VTR_LOG_ERROR("Invalid type of SRAM organization!\n"); + exit(1); + } + /* Reach here, it means something goes wrong, return a false value */ + return false; +} + +/******************************************************************** + * Generate a list of ports that are used for SRAM configuration to a module + * The type and names of added ports strongly depend on the + * organization of SRAMs. + * 1. Standalone SRAMs: + * two ports will be added, which are regular output and inverted output + * 2. Scan-chain Flip-flops: + * two ports will be added, which are the head of scan-chain + * and the tail of scan-chain + * IMPORTANT: the port size will be forced to 1 in this case + * because the head and tail are both 1-bit ports!!! + * 3. Memory decoders: + * 2-4 ports will be added, depending on the ports available in the SRAM + * Among these, two ports are mandatory: BL and WL + * The other two ports are optional: BLB and WLB + * Note that the constraints are correletated to the checking rules + * in check_circuit_library() + ********************************************************************/ +std::vector generate_sram_port_names(const CircuitLibrary& circuit_lib, + const CircuitModelId& sram_model, + const e_config_protocol_type sram_orgz_type) { + std::vector sram_port_names; + /* Prepare a list of port types to be added, the port type will be used to create port names */ + std::vector model_port_types; + + switch (sram_orgz_type) { + case CONFIG_MEM_STANDALONE: + model_port_types.push_back(CIRCUIT_MODEL_PORT_INPUT); + model_port_types.push_back(CIRCUIT_MODEL_PORT_OUTPUT); + break; + case CONFIG_MEM_SCAN_CHAIN: + model_port_types.push_back(CIRCUIT_MODEL_PORT_INPUT); + model_port_types.push_back(CIRCUIT_MODEL_PORT_OUTPUT); + break; + case CONFIG_MEM_MEMORY_BANK: { + std::vector ports_to_search; + ports_to_search.push_back(CIRCUIT_MODEL_PORT_BL); + ports_to_search.push_back(CIRCUIT_MODEL_PORT_WL); + ports_to_search.push_back(CIRCUIT_MODEL_PORT_BLB); + ports_to_search.push_back(CIRCUIT_MODEL_PORT_WLB); + /* Try to find a BL/WL/BLB/WLB port and update the port types/module port types to be added */ + for (const auto& port_to_search : ports_to_search) { + std::vector found_port = circuit_lib.model_ports_by_type(sram_model, port_to_search); + if (0 == found_port.size()) { + continue; + } + model_port_types.push_back(port_to_search); + } + break; + } + default: + VTR_LOG_ERROR("Invalid type of SRAM organization !\n"); + exit(1); + } + + /* Add ports to the module manager */ + for (size_t iport = 0; iport < model_port_types.size(); ++iport) { + /* Create a port */ + std::string port_name = generate_sram_port_name(sram_orgz_type, model_port_types[iport]); + sram_port_names.push_back(port_name); + } + + return sram_port_names; +} + +/******************************************************************** + * Generate a list of ports that are used for SRAM configuration to a module + * 1. Standalone SRAMs: + * use the suggested port_size + * 2. Scan-chain Flip-flops: + * IMPORTANT: the port size will be forced to 1 in this case + * 3. Memory decoders: + * use the suggested port_size + ********************************************************************/ +size_t generate_sram_port_size(const e_config_protocol_type sram_orgz_type, + const size_t& num_config_bits) { + size_t sram_port_size = num_config_bits; + + switch (sram_orgz_type) { + case CONFIG_MEM_STANDALONE: + break; + case CONFIG_MEM_SCAN_CHAIN: + /* CCFF head/tail are single-bit ports */ + sram_port_size = 1; + break; + case CONFIG_MEM_MEMORY_BANK: + break; + default: + VTR_LOG_ERROR("Invalid type of SRAM organization !\n"); + exit(1); + } + + return sram_port_size; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/utils/memory_utils.h b/openfpga/src/utils/memory_utils.h new file mode 100644 index 000000000..1c1eed257 --- /dev/null +++ b/openfpga/src/utils/memory_utils.h @@ -0,0 +1,42 @@ +#ifndef MEMORY_UTILS_H +#define MEMORY_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include "openfpga_port.h" +#include "circuit_types.h" +#include "module_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +std::map generate_mem_module_port2port_map(const BasicPort& config_bus, + const std::vector& mem_output_bus_ports, + const e_circuit_model_design_tech& mem_design_tech, + const e_config_protocol_type& sram_orgz_type); + +void update_mem_module_config_bus(const e_config_protocol_type& sram_orgz_type, + const e_circuit_model_design_tech& mem_design_tech, + const size_t& num_config_bits, + BasicPort& config_bus); + +bool check_mem_config_bus(const e_config_protocol_type& sram_orgz_type, + const BasicPort& config_bus, + const size_t& local_expected_msb); + +std::vector generate_sram_port_names(const CircuitLibrary& circuit_lib, + const CircuitModelId& sram_model, + const e_config_protocol_type sram_orgz_type); + +size_t generate_sram_port_size(const e_config_protocol_type sram_orgz_type, + const size_t& num_config_bits); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/utils/module_manager_utils.cpp b/openfpga/src/utils/module_manager_utils.cpp new file mode 100644 index 000000000..25a77a752 --- /dev/null +++ b/openfpga/src/utils/module_manager_utils.cpp @@ -0,0 +1,1147 @@ +/****************************************************************************** + * This files includes most utilized functions + * for data structures for module management. + ******************************************************************************/ + +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_log.h" +#include "vtr_assert.h" + +#include "openfpga_naming.h" +#include "memory_utils.h" +#include "pb_type_utils.h" + +#include "circuit_library_utils.h" +#include "module_manager_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/****************************************************************************** + * Add a module to the module manager based on the circuit-level + * description of a circuit model + * This function add a module with a given customized name + * as well as add the ports of circuit model to the module manager + ******************************************************************************/ +ModuleId add_circuit_model_to_module_manager(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, const CircuitModelId& circuit_model, + const std::string& module_name) { + ModuleId module = module_manager.add_module(module_name); + VTR_ASSERT(ModuleId::INVALID() != module); + + /* Add ports */ + /* Find global ports and add one by one */ + for (const auto& port : circuit_lib.model_global_ports(circuit_model, false)) { + BasicPort port_info(circuit_lib.port_prefix(port), circuit_lib.port_size(port)); + module_manager.add_port(module, port_info, ModuleManager::MODULE_GLOBAL_PORT); + } + + /* Find other ports and add one by one */ + /* Create a type-to-type map for ports */ + std::map port_type2type_map; + port_type2type_map[CIRCUIT_MODEL_PORT_INOUT] = ModuleManager::MODULE_INOUT_PORT; + port_type2type_map[CIRCUIT_MODEL_PORT_INPUT] = ModuleManager::MODULE_INPUT_PORT; + port_type2type_map[CIRCUIT_MODEL_PORT_CLOCK] = ModuleManager::MODULE_INPUT_PORT; + port_type2type_map[CIRCUIT_MODEL_PORT_SRAM] = ModuleManager::MODULE_INPUT_PORT; + port_type2type_map[CIRCUIT_MODEL_PORT_BL] = ModuleManager::MODULE_INPUT_PORT; + port_type2type_map[CIRCUIT_MODEL_PORT_BLB] = ModuleManager::MODULE_INPUT_PORT; + port_type2type_map[CIRCUIT_MODEL_PORT_WL] = ModuleManager::MODULE_INPUT_PORT; + port_type2type_map[CIRCUIT_MODEL_PORT_WLB] = ModuleManager::MODULE_INPUT_PORT; + port_type2type_map[CIRCUIT_MODEL_PORT_OUTPUT] = ModuleManager::MODULE_OUTPUT_PORT; + + /* Input ports (ignore all the global ports when searching the circuit_lib */ + for (const auto& kv : port_type2type_map) { + for (const auto& port : circuit_lib.model_ports_by_type(circuit_model, kv.first, true)) { + BasicPort port_info(circuit_lib.port_prefix(port), circuit_lib.port_size(port)); + module_manager.add_port(module, port_info, kv.second); + } + } + + /* Return the new id */ + return module; +} + +/****************************************************************************** + * Add a module to the module manager based on the circuit-level + * description of a circuit model + * This function add a module in the name of the circuit model + * as well as add the ports of circuit model to the module manager + * + * This function is a wrapper of a more customizable function in the same name + ******************************************************************************/ +ModuleId add_circuit_model_to_module_manager(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, const CircuitModelId& circuit_model) { + + return add_circuit_model_to_module_manager(module_manager, circuit_lib, circuit_model, circuit_lib.model_name(circuit_model)); +} + +/******************************************************************** + * Add a list of ports that are used for reserved SRAM ports to a module + * in the module manager + * The reserved SRAM ports are mainly designed for RRAM-based FPGA, + * which are shared across modules. + * Note that different modules may require different size of reserved + * SRAM ports but their LSB must all start from 0 + * +---------+ + * reserved_sram_port[0:X] --->| ModuleA | + * +---------+ + * + * +---------+ + * reserved_sram_port[0:Y] --->| ModuleB | + * +---------+ + * + ********************************************************************/ +void add_reserved_sram_ports_to_module_manager(ModuleManager& module_manager, + const ModuleId& module_id, + const size_t& port_size) { + /* Add a reserved BLB port to the module */ + std::string blb_port_name = generate_reserved_sram_port_name(CIRCUIT_MODEL_PORT_BLB); + BasicPort blb_module_port(blb_port_name, port_size); + /* Add generated ports to the ModuleManager */ + module_manager.add_port(module_id, blb_module_port, ModuleManager::MODULE_INPUT_PORT); + + /* Add a reserved BLB port to the module */ + std::string wl_port_name = generate_reserved_sram_port_name(CIRCUIT_MODEL_PORT_WL); + BasicPort wl_module_port(wl_port_name, port_size); + /* Add generated ports to the ModuleManager */ + module_manager.add_port(module_id, wl_module_port, ModuleManager::MODULE_INPUT_PORT); +} + +/******************************************************************** + * Add a list of ports that are used for formal verification to a module + * in the module manager + * + * The formal verification port will appear only when a pre-processing flag is defined + * This function will add the pre-processing flag along with the port + ********************************************************************/ +void add_formal_verification_sram_ports_to_module_manager(ModuleManager& module_manager, + const ModuleId& module_id, + const CircuitLibrary& circuit_lib, + const CircuitModelId& sram_model, + const std::string& preproc_flag, + const size_t& port_size) { + /* Create a port */ + std::string port_name = generate_formal_verification_sram_port_name(circuit_lib, sram_model); + BasicPort module_port(port_name, port_size); + /* Add generated ports to the ModuleManager */ + ModulePortId port_id = module_manager.add_port(module_id, module_port, ModuleManager::MODULE_INPUT_PORT); + /* Add pre-processing flag if defined */ + module_manager.set_port_preproc_flag(module_id, port_id, preproc_flag); +} + + +/******************************************************************** + * Add a list of ports that are used for SRAM configuration to a module + * in the module manager + * The type and names of added ports strongly depend on the + * organization of SRAMs. + * 1. Standalone SRAMs: + * two ports will be added, which are regular output and inverted output + * 2. Scan-chain Flip-flops: + * two ports will be added, which are the head of scan-chain + * and the tail of scan-chain + * IMPORTANT: the port size will be forced to 1 in this case + * because the head and tail are both 1-bit ports!!! + * 3. Memory decoders: + * 2-4 ports will be added, depending on the ports available in the SRAM + * Among these, two ports are mandatory: BL and WL + * The other two ports are optional: BLB and WLB + * Note that the constraints are correletated to the checking rules + * in check_circuit_library() + ********************************************************************/ +void add_sram_ports_to_module_manager(ModuleManager& module_manager, + const ModuleId& module_id, + const CircuitLibrary& circuit_lib, + const CircuitModelId& sram_model, + const e_config_protocol_type sram_orgz_type, + const size_t& num_config_bits) { + std::vector sram_port_names = generate_sram_port_names(circuit_lib, sram_model, sram_orgz_type); + size_t sram_port_size = generate_sram_port_size(sram_orgz_type, num_config_bits); + + /* Add ports to the module manager */ + switch (sram_orgz_type) { + case CONFIG_MEM_STANDALONE: + case CONFIG_MEM_MEMORY_BANK: { + for (const std::string& sram_port_name : sram_port_names) { + /* Add generated ports to the ModuleManager */ + BasicPort sram_port(sram_port_name, sram_port_size); + module_manager.add_port(module_id, sram_port, ModuleManager::MODULE_INPUT_PORT); + } + break; + } + case CONFIG_MEM_SCAN_CHAIN: { + /* Note that configuration chain tail is an output while head is an input + * IMPORTANT: this is co-designed with function generate_sram_port_names() + * If the return vector is changed, the following codes MUST be adapted! + */ + VTR_ASSERT(2 == sram_port_names.size()); + size_t port_counter = 0; + for (const std::string& sram_port_name : sram_port_names) { + /* Add generated ports to the ModuleManager */ + BasicPort sram_port(sram_port_name, sram_port_size); + if (0 == port_counter) { + module_manager.add_port(module_id, sram_port, ModuleManager::MODULE_INPUT_PORT); + } else { + VTR_ASSERT(1 == port_counter); + module_manager.add_port(module_id, sram_port, ModuleManager::MODULE_OUTPUT_PORT); + } + port_counter++; + } + break; + } + default: + VTR_LOG_ERROR("Invalid type of SRAM organization !\n"); + exit(1); + } +} + +/******************************************************************** + * Add ports of a pb_type block to module manager + * Port addition will follow the sequence: inout, input, output, clock + * This will help use to keep a clean module definition when printing out + * To avoid port mismatch between the pb_type and its linked circuit model + * This function will also check that each pb_type port is actually exist + * in the linked circuit model + *******************************************************************/ +void add_primitive_pb_type_ports_to_module_manager(ModuleManager& module_manager, + const ModuleId& module_id, + t_pb_type* cur_pb_type, + const VprDeviceAnnotation& vpr_device_annotation) { + + /* Find the inout ports required by the primitive pb_type, and add them to the module */ + std::vector pb_type_inout_ports = find_pb_type_ports_match_circuit_model_port_type(cur_pb_type, CIRCUIT_MODEL_PORT_INOUT, vpr_device_annotation); + for (auto port : pb_type_inout_ports) { + BasicPort module_port(generate_pb_type_port_name(port), port->num_pins); + module_manager.add_port(module_id, module_port, ModuleManager::MODULE_INOUT_PORT); + /* Set the port to be wire-connection */ + module_manager.set_port_is_wire(module_id, module_port.get_name(), true); + } + + /* Find the input ports required by the primitive pb_type, and add them to the module */ + std::vector pb_type_input_ports = find_pb_type_ports_match_circuit_model_port_type(cur_pb_type, CIRCUIT_MODEL_PORT_INPUT, vpr_device_annotation); + for (auto port : pb_type_input_ports) { + BasicPort module_port(generate_pb_type_port_name(port), port->num_pins); + module_manager.add_port(module_id, module_port, ModuleManager::MODULE_INPUT_PORT); + /* Set the port to be wire-connection */ + module_manager.set_port_is_wire(module_id, module_port.get_name(), true); + } + + /* Find the output ports required by the primitive pb_type, and add them to the module */ + std::vector pb_type_output_ports = find_pb_type_ports_match_circuit_model_port_type(cur_pb_type, CIRCUIT_MODEL_PORT_OUTPUT, vpr_device_annotation); + for (auto port : pb_type_output_ports) { + BasicPort module_port(generate_pb_type_port_name(port), port->num_pins); + module_manager.add_port(module_id, module_port, ModuleManager::MODULE_OUTPUT_PORT); + /* Set the port to be wire-connection */ + module_manager.set_port_is_wire(module_id, module_port.get_name(), true); + } + + /* Find the clock ports required by the primitive pb_type, and add them to the module */ + std::vector pb_type_clock_ports = find_pb_type_ports_match_circuit_model_port_type(cur_pb_type, CIRCUIT_MODEL_PORT_CLOCK, vpr_device_annotation); + for (auto port : pb_type_clock_ports) { + BasicPort module_port(generate_pb_type_port_name(port), port->num_pins); + module_manager.add_port(module_id, module_port, ModuleManager::MODULE_CLOCK_PORT); + /* Set the port to be wire-connection */ + module_manager.set_port_is_wire(module_id, module_port.get_name(), true); + } +} + +/******************************************************************** + * Add ports of a pb_type block to module manager + * This function is designed for non-primitive pb_types, which are + * NOT linked to any circuit model. + * Actually, this makes things much simpler. + * We just iterate over all the ports and add it to the module + * with the naming convention + *******************************************************************/ +void add_pb_type_ports_to_module_manager(ModuleManager& module_manager, + const ModuleId& module_id, + t_pb_type* cur_pb_type) { + /* Create a type-to-type mapping between module ports and pb_type ports */ + std::map port_type2type_map; + port_type2type_map[IN_PORT] = ModuleManager::MODULE_INPUT_PORT; + port_type2type_map[OUT_PORT] = ModuleManager::MODULE_OUTPUT_PORT; + port_type2type_map[INOUT_PORT] = ModuleManager::MODULE_INOUT_PORT; + + for (int port = 0; port < cur_pb_type->num_ports; ++port) { + t_port* pb_type_port = &(cur_pb_type->ports[port]); + BasicPort module_port(generate_pb_type_port_name(pb_type_port), pb_type_port->num_pins); + module_manager.add_port(module_id, module_port, port_type2type_map[pb_type_port->type]); + /* Set the port to be wire-connection */ + module_manager.set_port_is_wire(module_id, module_port.get_name(), true); + } +} + +/******************************************************************** + * Identify if a net is a local wire inside a module: + * A net is a local wire if it connects between two instances, + * It means that any of its source and sink modules should not include current module_id + *******************************************************************/ +bool module_net_is_local_wire(const ModuleManager& module_manager, + const ModuleId& module_id, const ModuleNetId& module_net) { + /* Check all the sink modules of the net, + * if we have a source module is the current module, this is not local wire + */ + for (ModuleId src_module : module_manager.net_source_modules(module_id, module_net)) { + if (module_id == src_module) { + /* Here, this is not a local wire */ + return false; + } + } + + /* Check all the sink modules of the net */ + for (ModuleId sink_module : module_manager.net_sink_modules(module_id, module_net)) { + if (module_id == sink_module) { + /* Here, this is not a local wire */ + return false; + } + } + + return true; +} + +/******************************************************************** + * Identify if a net is an output short connection inside a module: + * The short connection is defined as the direct connection + * between two outputs port of the module + * + * module + * +-----------------------------+ + * | + * src------>+--------------->|--->outputA + * | | + * | | + * +--------------->|--->outputB + * +-----------------------------+ + + *******************************************************************/ +bool module_net_include_output_short_connection(const ModuleManager& module_manager, + const ModuleId& module_id, const ModuleNetId& module_net) { + /* Check all the sink modules of the net */ + size_t contain_num_module_output = 0; + for (ModuleId sink_module : module_manager.net_sink_modules(module_id, module_net)) { + if (module_id == sink_module) { + contain_num_module_output++; + } + } + + /* If we have found more than 1 module outputs, it indicated output short connection! */ + return (1 < contain_num_module_output); +} + +/******************************************************************** + * Identify if a net is a local short connection inside a module: + * The short connection is defined as the direct connection + * between an input port of the module and an output port of the module + * + * module + * +-----------------------------+ + * | | + * inputA--->|---------------------------->|--->outputB + * | | + * | | + * | | + * +-----------------------------+ + *******************************************************************/ +bool module_net_include_local_short_connection(const ModuleManager& module_manager, + const ModuleId& module_id, const ModuleNetId& module_net) { + /* Check all the sink modules of the net, + * if we have a source module is the current module, this is not local wire + */ + bool contain_module_input = false; + for (ModuleId src_module : module_manager.net_source_modules(module_id, module_net)) { + if (module_id == src_module) { + contain_module_input = true; + break; + } + } + + /* Check all the sink modules of the net */ + bool contain_module_output = false; + for (ModuleId sink_module : module_manager.net_sink_modules(module_id, module_net)) { + if (module_id == sink_module) { + contain_module_output = true; + break; + } + } + + return contain_module_input & contain_module_output; +} + +/******************************************************************** + * Add the port-to-port connection between a pb_type and its linked circuit model + * This function is mainly used to create instance of the module for a pb_type + * + * Note: this function SHOULD be called after the pb_type_module is created + * and its child module is created! + *******************************************************************/ +void add_primitive_pb_type_module_nets(ModuleManager& module_manager, + const ModuleId& pb_type_module, + const ModuleId& child_module, + const size_t& child_instance_id, + const CircuitLibrary& circuit_lib, + t_pb_type* cur_pb_type, + const VprDeviceAnnotation& vpr_device_annotation) { + for (int iport = 0; iport < cur_pb_type->num_ports; ++iport) { + t_port* pb_type_port = &(cur_pb_type->ports[iport]); + /* Must have a linked circuit model port */ + VTR_ASSERT( CircuitPortId::INVALID() != vpr_device_annotation.pb_circuit_port(pb_type_port)); + + /* Find the source port in pb_type module */ + /* Get the src module port id */ + ModulePortId src_module_port_id = module_manager.find_module_port(pb_type_module, generate_pb_type_port_name(pb_type_port)); + VTR_ASSERT(ModulePortId::INVALID() != src_module_port_id); + BasicPort src_port = module_manager.module_port(pb_type_module, src_module_port_id); + + /* Get the des module port id */ + std::string des_module_port_name = circuit_lib.port_prefix(vpr_device_annotation.pb_circuit_port(pb_type_port)); + ModulePortId des_module_port_id = module_manager.find_module_port(child_module, des_module_port_name); + VTR_ASSERT(ModulePortId::INVALID() != des_module_port_id); + BasicPort des_port = module_manager.module_port(child_module, des_module_port_id); + + /* Port size must match */ + if (src_port.get_width() != des_port.get_width()) + VTR_ASSERT(src_port.get_width() == des_port.get_width()); + + /* For each pin, generate the nets. + * For non-output ports (input ports, inout ports and clock ports), + * src_port is the source of the net + * For output ports + * src_port is the sink of the net + */ + switch (pb_type_port->type) { + case IN_PORT: + case INOUT_PORT: + for (size_t pin_id = 0; pin_id < src_port.pins().size(); ++pin_id) { + ModuleNetId net = module_manager.create_module_net(pb_type_module); + /* Add net source */ + module_manager.add_module_net_source(pb_type_module, net, pb_type_module, 0, src_module_port_id, src_port.pins()[pin_id]); + /* Add net sink */ + module_manager.add_module_net_sink(pb_type_module, net, child_module, child_instance_id, des_module_port_id, des_port.pins()[pin_id]); + } + break; + case OUT_PORT: + for (size_t pin_id = 0; pin_id < src_port.pins().size(); ++pin_id) { + ModuleNetId net = module_manager.create_module_net(pb_type_module); + /* Add net source */ + module_manager.add_module_net_sink(pb_type_module, net, pb_type_module, 0, src_module_port_id, src_port.pins()[pin_id]); + /* Add net sink */ + module_manager.add_module_net_source(pb_type_module, net, child_module, child_instance_id, des_module_port_id, des_port.pins()[pin_id]); + } + break; + default: + VTR_LOG_ERROR("Invalid port of pb_type!\n"); + exit(1); + } + } +} + +/******************************************************************** + * Add the port-to-port connection between a logic module + * and a memory module + * Create nets to wire SRAM ports between logic module and memory module + * + * The information about SRAM ports of logic module are stored in the + * mem_output_bus_ports, where element [0] denotes the SRAM port while + * element [1] denotes the SRAMb port + * + * +---------+ +--------+ + * | | regular SRAM port | | + * | Logic |-----------------------+ | Memory | + * | Module | mode-select SRAM port |->| Module | + * | |-----------------------+ | | + * +---------+ +--------+ + * + * There could be multiple SRAM ports of logic module, which are wired to + * the SRAM ports of memory module + * + * Note: this function SHOULD be called after the pb_type_module is created + * and its child module (logic_module and memory_module) is created! + * + * Note: this function only handle either SRAM or SRAMb ports. + * So, this function may be called twice to complete the wiring + *******************************************************************/ +static +void add_module_nets_between_logic_and_memory_sram_ports(ModuleManager& module_manager, + const ModuleId& parent_module, + const ModuleId& logic_module, + const size_t& logic_instance_id, + const ModuleId& memory_module, + const size_t& memory_instance_id, + const std::vector& logic_module_sram_port_ids, + const ModulePortId& mem_module_sram_port_id) { + /* Find mem_output_bus ports in logic module */ + std::vector logic_module_sram_ports; + for (const ModulePortId& logic_module_sram_port_id : logic_module_sram_port_ids) { + logic_module_sram_ports.push_back(module_manager.module_port(logic_module, logic_module_sram_port_id)); + } + + /* Create a list of virtual ports to align with the SRAM port of logic module + * Physical ports: + * + * logic_module_sram_port[0] logic_module_sram_port[1] + * + * LSB[0]------------>MSB[0] LSB------------------>MSB + * + * memory_sram_port + * LSBY---------------------------------------------->MSBY + * + * Virtual ports: + * mem_module_sram_port[0] mem_module_sram_port[1] + * LSBY--------------->MSBX MSBX+1------------------>MSBY + * + */ + BasicPort mem_module_port = module_manager.module_port(memory_module, mem_module_sram_port_id); + std::vector virtual_mem_module_ports; + + /* Create a counter for the LSB of virtual ports */ + size_t port_lsb = 0; + for (const BasicPort& logic_module_sram_port : logic_module_sram_ports) { + BasicPort virtual_port; + virtual_port.set_name(mem_module_port.get_name()); + virtual_port.set_width(port_lsb, port_lsb + logic_module_sram_port.get_width() - 1); + virtual_mem_module_ports.push_back(virtual_port); + port_lsb = virtual_port.get_msb() + 1; + } + /* port_lsb should be aligned with the MSB of memory_sram_port */ + VTR_ASSERT(port_lsb == mem_module_port.get_msb() + 1); + + /* Wire port to port */ + for (size_t port_index = 0; port_index < logic_module_sram_ports.size(); ++port_index) { + /* Create a net for each pin */ + for (size_t pin_id = 0; pin_id < logic_module_sram_ports[port_index].pins().size(); ++pin_id) { + ModuleNetId net = module_manager.create_module_net(parent_module); + /* TODO: Give a name to make it clear */ + std::string net_name = module_manager.module_name(logic_module) + std::string("_") + std::to_string(logic_instance_id) + std::string("_") + logic_module_sram_ports[port_index].get_name(); + module_manager.set_net_name(parent_module, net, net_name); + /* Add net source */ + module_manager.add_module_net_source(parent_module, net, logic_module, logic_instance_id, logic_module_sram_port_ids[port_index], logic_module_sram_ports[port_index].pins()[pin_id]); + /* Add net sink */ + module_manager.add_module_net_sink(parent_module, net, memory_module, memory_instance_id, mem_module_sram_port_id, virtual_mem_module_ports[port_index].pins()[pin_id]); + } + } +} + +/******************************************************************** + * Add the port-to-port connection between a logic module + * and a memory module + * Create nets to wire SRAM ports between logic module and memory module + * + * + * +---------+ +--------+ + * | | SRAM ports | | + * | Logic |----------------------->| Memory | + * | Module | SRAMb ports | Module | + * | |----------------------->| | + * +---------+ +--------+ + * + * Note: this function SHOULD be called after the pb_type_module is created + * and its child module (logic_module and memory_module) is created! + * + *******************************************************************/ +void add_module_nets_between_logic_and_memory_sram_bus(ModuleManager& module_manager, + const ModuleId& parent_module, + const ModuleId& logic_module, + const size_t& logic_instance_id, + const ModuleId& memory_module, + const size_t& memory_instance_id, + const CircuitLibrary& circuit_lib, + const CircuitModelId& logic_model) { + + /* Connect SRAM port */ + /* Find SRAM ports in the circuit model for logic module */ + std::vector logic_model_sram_port_names; + /* Regular sram port goes first */ + for (CircuitPortId regular_sram_port : find_circuit_regular_sram_ports(circuit_lib, logic_model)) { + logic_model_sram_port_names.push_back(circuit_lib.port_prefix(regular_sram_port)); + } + /* Mode-select sram port goes first */ + for (CircuitPortId mode_select_sram_port : find_circuit_mode_select_sram_ports(circuit_lib, logic_model)) { + logic_model_sram_port_names.push_back(circuit_lib.port_prefix(mode_select_sram_port)); + } + /* Find the port ids in the memory */ + std::vector logic_module_sram_port_ids; + for (const std::string& logic_model_sram_port_name : logic_model_sram_port_names) { + /* Skip non-exist ports */ + if (ModulePortId::INVALID() == module_manager.find_module_port(logic_module, logic_model_sram_port_name)) { + continue; + } + logic_module_sram_port_ids.push_back(module_manager.find_module_port(logic_module, logic_model_sram_port_name)); + } + + /* Get the SRAM port name of memory model */ + /* TODO: this should be a constant expression and it should be the same for all the memory module! */ + std::string memory_model_sram_port_name = generate_configuration_chain_data_out_name(); + /* Find the corresponding ports in memory module */ + ModulePortId mem_module_sram_port_id = module_manager.find_module_port(memory_module, memory_model_sram_port_name); + + /* Do wiring only when we have sram ports */ + if ( (false == logic_module_sram_port_ids.empty()) + || (ModulePortId::INVALID() == mem_module_sram_port_id) ) { + add_module_nets_between_logic_and_memory_sram_ports(module_manager, parent_module, + logic_module, logic_instance_id, + memory_module, memory_instance_id, + logic_module_sram_port_ids, mem_module_sram_port_id); + } + + /* Connect SRAMb port */ + /* Find SRAM ports in the circuit model for logic module */ + std::vector logic_model_sramb_port_names; + /* Regular sram port goes first */ + for (CircuitPortId regular_sram_port : find_circuit_regular_sram_ports(circuit_lib, logic_model)) { + logic_model_sramb_port_names.push_back(circuit_lib.port_prefix(regular_sram_port) + std::string("_inv")); + } + /* Mode-select sram port goes first */ + for (CircuitPortId mode_select_sram_port : find_circuit_mode_select_sram_ports(circuit_lib, logic_model)) { + logic_model_sramb_port_names.push_back(circuit_lib.port_prefix(mode_select_sram_port) + std::string("_inv")); + } + /* Find the port ids in the memory */ + std::vector logic_module_sramb_port_ids; + for (const std::string& logic_model_sramb_port_name : logic_model_sramb_port_names) { + /* Skip non-exist ports */ + if (ModulePortId::INVALID() == module_manager.find_module_port(logic_module, logic_model_sramb_port_name)) { + continue; + } + logic_module_sramb_port_ids.push_back(module_manager.find_module_port(logic_module, logic_model_sramb_port_name)); + } + + /* Get the SRAM port name of memory model */ + std::string memory_model_sramb_port_name = generate_configuration_chain_inverted_data_out_name(); + /* Find the corresponding ports in memory module */ + ModulePortId mem_module_sramb_port_id = module_manager.find_module_port(memory_module, memory_model_sramb_port_name); + + /* Do wiring only when we have sramb ports */ + if ( (false == logic_module_sramb_port_ids.empty()) + || (ModulePortId::INVALID() == mem_module_sramb_port_id) ) { + add_module_nets_between_logic_and_memory_sram_ports(module_manager, parent_module, + logic_module, logic_instance_id, + memory_module, memory_instance_id, + logic_module_sramb_port_ids, mem_module_sramb_port_id); + } +} + +/******************************************************************** + * Connect all the memory modules under the parent module in a chain + * + * +--------+ +--------+ +--------+ + * ccff_head --->| Memory |--->| Memory |--->... --->| Memory |----> ccff_tail + * | Module | | Module | | Module | + * | [0] | | [1] | | [N-1] | + * +--------+ +--------+ +--------+ + * For the 1st memory module: + * net source is the configuration chain head of the primitive module + * net sink is the configuration chain head of the next memory module + * + * For the rest of memory modules: + * net source is the configuration chain tail of the previous memory module + * net sink is the configuration chain head of the next memory module + *********************************************************************/ +void add_module_nets_cmos_memory_chain_config_bus(ModuleManager& module_manager, + const ModuleId& parent_module, + const e_config_protocol_type& sram_orgz_type) { + for (size_t mem_index = 0; mem_index < module_manager.configurable_children(parent_module).size(); ++mem_index) { + ModuleId net_src_module_id; + size_t net_src_instance_id; + ModulePortId net_src_port_id; + + ModuleId net_sink_module_id; + size_t net_sink_instance_id; + ModulePortId net_sink_port_id; + + if (0 == mem_index) { + /* Find the port name of configuration chain head */ + std::string src_port_name = generate_sram_port_name(sram_orgz_type, CIRCUIT_MODEL_PORT_INPUT); + net_src_module_id = parent_module; + net_src_instance_id = 0; + net_src_port_id = module_manager.find_module_port(net_src_module_id, src_port_name); + + /* Find the port name of next memory module */ + std::string sink_port_name = generate_configuration_chain_head_name(); + net_sink_module_id = module_manager.configurable_children(parent_module)[mem_index]; + net_sink_instance_id = module_manager.configurable_child_instances(parent_module)[mem_index]; + net_sink_port_id = module_manager.find_module_port(net_sink_module_id, sink_port_name); + } else { + /* Find the port name of previous memory module */ + std::string src_port_name = generate_configuration_chain_tail_name(); + net_src_module_id = module_manager.configurable_children(parent_module)[mem_index - 1]; + net_src_instance_id = module_manager.configurable_child_instances(parent_module)[mem_index - 1]; + net_src_port_id = module_manager.find_module_port(net_src_module_id, src_port_name); + + /* Find the port name of next memory module */ + std::string sink_port_name = generate_configuration_chain_head_name(); + net_sink_module_id = module_manager.configurable_children(parent_module)[mem_index]; + net_sink_instance_id = module_manager.configurable_child_instances(parent_module)[mem_index]; + net_sink_port_id = module_manager.find_module_port(net_sink_module_id, sink_port_name); + } + + /* Get the pin id for source port */ + BasicPort net_src_port = module_manager.module_port(net_src_module_id, net_src_port_id); + /* Get the pin id for sink port */ + BasicPort net_sink_port = module_manager.module_port(net_sink_module_id, net_sink_port_id); + /* Port sizes of source and sink should match */ + VTR_ASSERT(net_src_port.get_width() == net_sink_port.get_width()); + + /* Create a net for each pin */ + for (size_t pin_id = 0; pin_id < net_src_port.pins().size(); ++pin_id) { + /* Create a net and add source and sink to it */ + ModuleNetId net = module_manager.create_module_net(parent_module); + /* Add net source */ + module_manager.add_module_net_source(parent_module, net, net_src_module_id, net_src_instance_id, net_src_port_id, net_src_port.pins()[pin_id]); + /* Add net sink */ + module_manager.add_module_net_sink(parent_module, net, net_sink_module_id, net_sink_instance_id, net_sink_port_id, net_sink_port.pins()[pin_id]); + } + } + + /* For the last memory module: + * net source is the configuration chain tail of the previous memory module + * net sink is the configuration chain tail of the primitive module + */ + /* Find the port name of previous memory module */ + std::string src_port_name = generate_configuration_chain_tail_name(); + ModuleId net_src_module_id = module_manager.configurable_children(parent_module).back(); + size_t net_src_instance_id = module_manager.configurable_child_instances(parent_module).back(); + ModulePortId net_src_port_id = module_manager.find_module_port(net_src_module_id, src_port_name); + + /* Find the port name of next memory module */ + std::string sink_port_name = generate_sram_port_name(sram_orgz_type, CIRCUIT_MODEL_PORT_OUTPUT); + ModuleId net_sink_module_id = parent_module; + size_t net_sink_instance_id = 0; + ModulePortId net_sink_port_id = module_manager.find_module_port(net_sink_module_id, sink_port_name); + + /* Get the pin id for source port */ + BasicPort net_src_port = module_manager.module_port(net_src_module_id, net_src_port_id); + /* Get the pin id for sink port */ + BasicPort net_sink_port = module_manager.module_port(net_sink_module_id, net_sink_port_id); + /* Port sizes of source and sink should match */ + VTR_ASSERT(net_src_port.get_width() == net_sink_port.get_width()); + + /* Create a net for each pin */ + for (size_t pin_id = 0; pin_id < net_src_port.pins().size(); ++pin_id) { + /* Create a net and add source and sink to it */ + ModuleNetId net = module_manager.create_module_net(parent_module); + /* Add net source */ + module_manager.add_module_net_source(parent_module, net, net_src_module_id, net_src_instance_id, net_src_port_id, net_src_port.pins()[pin_id]); + /* Add net sink */ + module_manager.add_module_net_sink(parent_module, net, net_sink_module_id, net_sink_instance_id, net_sink_port_id, net_sink_port.pins()[pin_id]); + } +} + +/********************************************************************* + * Add the port-to-port connection between all the memory modules + * and their parent module + * + * Create nets to wire the control signals of memory module to + * the configuration ports of primitive module + * + * Configuration Chain + * ------------------- + * + * config_bus (head) config_bus (tail) + * | ^ + * primitive | | + * +---------------------------------------------+ + * | | | | + * | v | | + * | +-------------------------------------+ | + * | | CMOS-based Memory Modules | | + * | +-------------------------------------+ | + * | | | | + * | v v | + * | sram_out sram_outb | + * | | + * +---------------------------------------------+ + * + * Memory bank + * ----------- + * + * config_bus (BL) config_bus (WL) + * | | + * primitive | | + * +---------------------------------------------+ + * | | | | + * | v v | + * | +-------------------------------------+ | + * | | CMOS-based Memory Modules | | + * | +-------------------------------------+ | + * | | | | + * | v v | + * | sram_out sram_outb | + * | | + * +---------------------------------------------+ + * + **********************************************************************/ +static +void add_module_nets_cmos_memory_config_bus(ModuleManager& module_manager, + const ModuleId& parent_module, + const e_config_protocol_type& sram_orgz_type) { + switch (sram_orgz_type) { + case CONFIG_MEM_STANDALONE: + /* Nothing to do */ + break; + case CONFIG_MEM_SCAN_CHAIN: { + add_module_nets_cmos_memory_chain_config_bus(module_manager, parent_module, sram_orgz_type); + break; + } + case CONFIG_MEM_MEMORY_BANK: + /* TODO: */ + break; + default: + VTR_LOG_ERROR("Invalid type of SRAM organization!\n"); + exit(1); + } +} + +/********************************************************************* + * TODO: + * Add the port-to-port connection between a logic module + * and a memory module inside a primitive module + * + * Memory bank + * ----------- + * config_bus (BL) config_bus (WL) shared_config_bugs(shared_BL/WLs) + * | | | | + * primitive | | | | + * +------------------------------------------------------------+ + * | | | | | | + * | v v v v | + * | +----------------------------------------------------+ | + * | | ReRAM-based Memory Module | | + * | +----------------------------------------------------+ | + * | | | | + * | v v | + * | mem_out mem_outb | + * | | + * +------------------------------------------------------------+ + * + **********************************************************************/ + +/******************************************************************** + * TODO: + * Add the port-to-port connection between a memory module + * and the configuration bus of a primitive module + * + * Create nets to wire the control signals of memory module to + * the configuration ports of primitive module + * + * Primitive module + * +----------------------------+ + * | +--------+ | + * config | | | | + * ports --->|--------------->| Memory | | + * | | Module | | + * | | | | + * | +--------+ | + * +----------------------------+ + * The detailed config ports really depend on the type + * of SRAM organization. + * + * The config_bus in the argument is the reserved address of configuration + * bus in the parent_module for this memory module + * + * The configuration bus connection will depend not only + * the design technology of the memory cells but also the + * configuration styles of FPGA fabric. + * Here we will branch on the design technology + * + * Note: this function SHOULD be called after the pb_type_module is created + * and its child module (logic_module and memory_module) is created! + *******************************************************************/ +void add_module_nets_memory_config_bus(ModuleManager& module_manager, + const ModuleId& parent_module, + const e_config_protocol_type& sram_orgz_type, + const e_circuit_model_design_tech& mem_tech) { + switch (mem_tech) { + case CIRCUIT_MODEL_DESIGN_CMOS: + add_module_nets_cmos_memory_config_bus(module_manager, parent_module, + sram_orgz_type); + break; + case CIRCUIT_MODEL_DESIGN_RRAM: + /* TODO: */ + break; + default: + VTR_LOG_ERROR("Invalid type of memory design technology !\n"); + exit(1); + } +} + +/******************************************************************** + * Find the size of shared(reserved) configuration ports for module + *******************************************************************/ +size_t find_module_num_shared_config_bits(const ModuleManager& module_manager, + const ModuleId& module_id) { + std::vector shared_config_port_names; + shared_config_port_names.push_back(generate_reserved_sram_port_name(CIRCUIT_MODEL_PORT_BLB)); + shared_config_port_names.push_back(generate_reserved_sram_port_name(CIRCUIT_MODEL_PORT_WL)); + size_t num_shared_config_bits = 0; /* By default it has zero configuration bits*/ + + /* Try to find these ports in the module manager */ + for (const std::string& shared_config_port_name : shared_config_port_names) { + ModulePortId module_port_id = module_manager.find_module_port(module_id, shared_config_port_name); + /* If the port does not exist, go to the next */ + if (false == module_manager.valid_module_port_id(module_id, module_port_id)) { + continue; + } + /* The port exist, find the port size and update the num_config_bits if the size is larger */ + BasicPort module_port = module_manager.module_port(module_id, module_port_id); + num_shared_config_bits = std::max((int)num_shared_config_bits, (int)module_port.get_width()); + } + + return num_shared_config_bits; +} + +/******************************************************************** + * Find the size of configuration ports for module + *******************************************************************/ +size_t find_module_num_config_bits(const ModuleManager& module_manager, + const ModuleId& module_id, + const CircuitLibrary& circuit_lib, + const CircuitModelId& sram_model, + const e_config_protocol_type& sram_orgz_type) { + std::vector config_port_names = generate_sram_port_names(circuit_lib, sram_model, sram_orgz_type); + size_t num_config_bits = 0; /* By default it has zero configuration bits*/ + + /* Try to find these ports in the module manager */ + for (const std::string& config_port_name : config_port_names) { + ModulePortId module_port_id = module_manager.find_module_port(module_id, config_port_name); + /* If the port does not exist, go to the next */ + if (false == module_manager.valid_module_port_id(module_id, module_port_id)) { + continue; + } + /* The port exist, find the port size and update the num_config_bits if the size is larger */ + BasicPort module_port = module_manager.module_port(module_id, module_port_id); + num_config_bits = std::max((int)num_config_bits, (int)module_port.get_width()); + } + + return num_config_bits; +} + +/******************************************************************** + * Add GPIO ports to the module: + * In this function, the following tasks are done: + * 1. find all the GPIO ports from the child modules and build a list of it, + * 2. Merge all the GPIO ports with the same name + * 3. add the ports to the pb_module + * 4. add module nets to connect to the GPIO ports of each sub module + * + * Note: This function should be call ONLY after all the sub modules (instances) + * have been added to the pb_module! + * Otherwise, some GPIO ports of the sub modules may be missed! + *******************************************************************/ +void add_module_gpio_ports_from_child_modules(ModuleManager& module_manager, + const ModuleId& module_id) { + std::vector gpio_ports_to_add; + + /* Iterate over the child modules */ + for (const ModuleId& child : module_manager.child_modules(module_id)) { + /* Iterate over the child instances */ + for (size_t i = 0; i < module_manager.num_instance(module_id, child); ++i) { + /* Find all the global ports, whose port type is special */ + for (BasicPort gpio_port : module_manager.module_ports_by_type(child, ModuleManager::MODULE_GPIO_PORT)) { + /* If this port is not mergeable, we update the list */ + bool is_mergeable = false; + for (BasicPort& gpio_port_to_add : gpio_ports_to_add) { + if (false == gpio_port_to_add.mergeable(gpio_port)) { + continue; + } + is_mergeable = true; + /* For mergeable ports, we combine the port + * Note: do NOT use the merge() method! + * the GPIO ports should be accumulated by the sizes of ports + * not by the LSB/MSB range !!! + */ + gpio_port_to_add.combine(gpio_port); + break; + } + if (false == is_mergeable) { + /* Reach here, this is an unique gpio port, update the list */ + gpio_ports_to_add.push_back(gpio_port); + } + } + } + } + + /* Record the port id for each type of GPIO port */ + std::vector gpio_port_ids; + /* Add the gpio ports for the module */ + for (const BasicPort& gpio_port_to_add : gpio_ports_to_add) { + ModulePortId port_id = module_manager.add_port(module_id, gpio_port_to_add, ModuleManager::MODULE_GPIO_PORT); + gpio_port_ids.push_back(port_id); + } + + /* Set up a counter for each type of GPIO port */ + std::vector gpio_port_lsb(gpio_ports_to_add.size(), 0); + /* Add module nets to connect the GPIOs of the module to the GPIOs of the sub module */ + for (const ModuleId& child : module_manager.child_modules(module_id)) { + /* Iterate over the child instances */ + for (const size_t& child_instance : module_manager.child_module_instances(module_id, child)) { + /* Find all the global ports, whose port type is special */ + for (ModulePortId child_gpio_port_id : module_manager.module_port_ids_by_type(child, ModuleManager::MODULE_GPIO_PORT)) { + BasicPort child_gpio_port = module_manager.module_port(child, child_gpio_port_id); + /* Find the port with the same name! */ + for (size_t iport = 0; iport < gpio_ports_to_add.size(); ++iport) { + if (false == gpio_ports_to_add[iport].mergeable(child_gpio_port)) { + continue; + } + /* For each pin of the child port, create a net and do wiring */ + for (const size_t& pin_id : child_gpio_port.pins()) { + /* Reach here, it means this is the port we want, create a net and configure its source and sink */ + ModuleNetId net = module_manager.create_module_net(module_id); + module_manager.add_module_net_source(module_id, net, module_id, 0, gpio_port_ids[iport], gpio_port_lsb[iport]); + module_manager.add_module_net_sink(module_id, net, child, child_instance, child_gpio_port_id, pin_id); + /* Update the LSB counter */ + gpio_port_lsb[iport]++; + } + /* We finish for this child gpio port */ + break; + } + } + } + } + + /* Check: all the lsb should now match the size of each GPIO port */ + for (size_t iport = 0; iport < gpio_ports_to_add.size(); ++iport) { + if (gpio_ports_to_add[iport].get_width() != gpio_port_lsb[iport]) + VTR_ASSERT(gpio_ports_to_add[iport].get_width() == gpio_port_lsb[iport]); + } +} + +/******************************************************************** + * Add global ports to the module: + * In this function, the following tasks are done: + * 1. find all the global ports from the child modules and build a list of it, + * 2. add the ports to the pb_module + * 3. add the module nets to connect the pb_module global ports to those of child modules + * + * Note: This function should be call ONLY after all the sub modules (instances) + * have been added to the pb_module! + * Otherwise, some global ports of the sub modules may be missed! + *******************************************************************/ +void add_module_global_ports_from_child_modules(ModuleManager& module_manager, + const ModuleId& module_id) { + std::vector global_ports_to_add; + + /* Iterate over the child modules */ + for (const ModuleId& child : module_manager.child_modules(module_id)) { + /* Iterate over the child instances */ + for (size_t i = 0; i < module_manager.num_instance(module_id, child); ++i) { + /* Find all the global ports, whose port type is special */ + for (BasicPort global_port : module_manager.module_ports_by_type(child, ModuleManager::MODULE_GLOBAL_PORT)) { + /* Search in the global port list to be added, if this is unique, we update the list */ + std::vector::iterator it = std::find(global_ports_to_add.begin(), global_ports_to_add.end(), global_port); + if (it != global_ports_to_add.end()) { + continue; + } + /* Reach here, this is an unique global port, update the list */ + global_ports_to_add.push_back(global_port); + } + } + } + + /* Record the port id for each type of global port */ + std::vector global_port_ids; + /* Add the global ports for the module */ + for (const BasicPort& global_port_to_add : global_ports_to_add) { + ModulePortId port_id = module_manager.add_port(module_id, global_port_to_add, ModuleManager::MODULE_GLOBAL_PORT); + global_port_ids.push_back(port_id); + } + + /* Add module nets to connect the global ports of the module to the global ports of the sub module */ + /* Iterate over the child modules */ + for (const ModuleId& child : module_manager.child_modules(module_id)) { + /* Iterate over the child instances */ + for (const size_t& child_instance : module_manager.child_module_instances(module_id, child)) { + /* Find all the global ports, whose port type is special */ + for (ModulePortId child_global_port_id : module_manager.module_port_ids_by_type(child, ModuleManager::MODULE_GLOBAL_PORT)) { + BasicPort child_global_port = module_manager.module_port(child, child_global_port_id); + /* Search in the global port list to be added, find the port id */ + std::vector::iterator it = std::find(global_ports_to_add.begin(), global_ports_to_add.end(), child_global_port); + VTR_ASSERT(it != global_ports_to_add.end()); + ModulePortId module_global_port_id = global_port_ids[it - global_ports_to_add.begin()]; + BasicPort module_global_port = module_manager.module_port(module_id, module_global_port_id); + /* The global ports should match in size */ + VTR_ASSERT(module_global_port.get_width() == child_global_port.get_width()); + /* For each pin of the child port, create a net and do wiring */ + for (size_t pin_id = 0; pin_id < child_global_port.pins().size(); ++pin_id) { + /* Reach here, it means this is the port we want, create a net and configure its source and sink */ + ModuleNetId net = module_manager.create_module_net(module_id); + module_manager.add_module_net_source(module_id, net, module_id, 0, module_global_port_id, module_global_port.pins()[pin_id]); + module_manager.add_module_net_sink(module_id, net, child, child_instance, child_global_port_id, child_global_port.pins()[pin_id]); + /* We finish for this child gpio port */ + } + } + } + } +} + +/******************************************************************** + * Find the number of shared configuration bits for a module + * by selected the maximum number of shared configuration bits of child modules + * + * Note: This function should be call ONLY after all the sub modules (instances) + * have been added to the pb_module! + * Otherwise, some global ports of the sub modules may be missed! + *******************************************************************/ +size_t find_module_num_shared_config_bits_from_child_modules(ModuleManager& module_manager, + const ModuleId& module_id) { + size_t num_shared_config_bits = 0; + + /* Iterate over the child modules */ + for (const ModuleId& child : module_manager.child_modules(module_id)) { + num_shared_config_bits = std::max((int)num_shared_config_bits, (int)find_module_num_shared_config_bits(module_manager, child)); + } + + return num_shared_config_bits; +} + +/******************************************************************** + * Find the number of configuration bits for a module + * by summing up the number of configuration bits of child modules + * + * Note: This function should be call ONLY after all the sub modules (instances) + * have been added to the pb_module! + * Otherwise, some global ports of the sub modules may be missed! + *******************************************************************/ +size_t find_module_num_config_bits_from_child_modules(ModuleManager& module_manager, + const ModuleId& module_id, + const CircuitLibrary& circuit_lib, + const CircuitModelId& sram_model, + const e_config_protocol_type& sram_orgz_type) { + size_t num_config_bits = 0; + + /* Iterate over the child modules */ + for (const ModuleId& child : module_manager.child_modules(module_id)) { + num_config_bits += find_module_num_config_bits(module_manager, child, circuit_lib, sram_model, sram_orgz_type); + } + + return num_config_bits; +} + + +/******************************************************************** + * TODO: + * Add the port-to-port connection between a logic module + * and a memory module inside a primitive module + * + * Create nets to wire the formal verification ports of + * primitive module to SRAM ports of logic module + * + * Primitive module + * + * formal_port_sram + * +-----------------------------------------------+ + * | ^ | + * | +---------+ | +--------+ | + * | | | SRAM | | | | + * | | Logic |--------+--->| Memory | | + * | | Module | SRAMb | Module | | + * | | |--------+--->| | | + * | +---------+ | +--------+ | + * | v | + * +-----------------------------------------------+ + * formal_port_sramb + * + *******************************************************************/ + +} /* end namespace openfpga */ diff --git a/openfpga/src/utils/module_manager_utils.h b/openfpga/src/utils/module_manager_utils.h new file mode 100644 index 000000000..6198701ad --- /dev/null +++ b/openfpga/src/utils/module_manager_utils.h @@ -0,0 +1,128 @@ +/****************************************************************************** + * This files includes declarations for most utilized functions + * for data structures for module management. + ******************************************************************************/ +#ifndef MODULE_MANAGER_UTILS_H +#define MODULE_MANAGER_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include + +/* Headers from readarch library */ +#include "physical_types.h" + +/* Headers from openfpgautil library */ +#include "openfpga_port.h" + +/* Headers from readarchopenfpga library */ +#include "circuit_types.h" +#include "circuit_library.h" + +#include "module_manager.h" +#include "vpr_device_annotation.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +ModuleId add_circuit_model_to_module_manager(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, const CircuitModelId& circuit_model, + const std::string& module_name); + +ModuleId add_circuit_model_to_module_manager(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, const CircuitModelId& circuit_model); + +void add_reserved_sram_ports_to_module_manager(ModuleManager& module_manager, + const ModuleId& module_id, + const size_t& port_size); + +void add_formal_verification_sram_ports_to_module_manager(ModuleManager& module_manager, + const ModuleId& module_id, + const CircuitLibrary& circuit_lib, + const CircuitModelId& sram_model, + const std::string& preproc_flag, + const size_t& port_size); + +void add_sram_ports_to_module_manager(ModuleManager& module_manager, + const ModuleId& module_id, + const CircuitLibrary& circuit_lib, + const CircuitModelId& sram_model, + const e_config_protocol_type sram_orgz_type, + const size_t& num_config_bits); + +void add_primitive_pb_type_ports_to_module_manager(ModuleManager& module_manager, + const ModuleId& module_id, + t_pb_type* cur_pb_type, + const VprDeviceAnnotation& vpr_device_annotation); + +void add_pb_type_ports_to_module_manager(ModuleManager& module_manager, + const ModuleId& module_id, + t_pb_type* cur_pb_type); + +bool module_net_is_local_wire(const ModuleManager& module_manager, + const ModuleId& module_id, const ModuleNetId& module_net); + +bool module_net_include_output_short_connection(const ModuleManager& module_manager, + const ModuleId& module_id, const ModuleNetId& module_net); + +bool module_net_include_local_short_connection(const ModuleManager& module_manager, + const ModuleId& module_id, const ModuleNetId& module_net); + +void add_primitive_pb_type_module_nets(ModuleManager& module_manager, + const ModuleId& pb_type_module, + const ModuleId& child_module, + const size_t& child_instance_id, + const CircuitLibrary& circuit_lib, + t_pb_type* cur_pb_type, + const VprDeviceAnnotation& vpr_device_annotation); + +void add_module_nets_between_logic_and_memory_sram_bus(ModuleManager& module_manager, + const ModuleId& parent_module, + const ModuleId& logic_module, + const size_t& logic_instance_id, + const ModuleId& memory_module, + const size_t& memory_instance_id, + const CircuitLibrary& circuit_lib, + const CircuitModelId& logic_model); + +void add_module_nets_cmos_memory_chain_config_bus(ModuleManager& module_manager, + const ModuleId& parent_module, + const e_config_protocol_type& sram_orgz_type); + +void add_module_nets_memory_config_bus(ModuleManager& module_manager, + const ModuleId& parent_module, + const e_config_protocol_type& sram_orgz_type, + const e_circuit_model_design_tech& mem_tech); + +size_t find_module_num_shared_config_bits(const ModuleManager& module_manager, + const ModuleId& module_id); + +size_t find_module_num_config_bits(const ModuleManager& module_manager, + const ModuleId& module_id, + const CircuitLibrary& circuit_lib, + const CircuitModelId& sram_model, + const e_config_protocol_type& sram_orgz_type); + +void add_module_global_ports_from_child_modules(ModuleManager& module_manager, + const ModuleId& module_id); + +void add_module_gpio_ports_from_child_modules(ModuleManager& module_manager, + const ModuleId& module_id); + +size_t find_module_num_shared_config_bits_from_child_modules(ModuleManager& module_manager, + const ModuleId& module_id); + +size_t find_module_num_config_bits_from_child_modules(ModuleManager& module_manager, + const ModuleId& module_id, + const CircuitLibrary& circuit_lib, + const CircuitModelId& sram_model, + const e_config_protocol_type& sram_orgz_type); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/utils/pb_type_utils.cpp b/openfpga/src/utils/pb_type_utils.cpp index b791485a2..9f52c01da 100644 --- a/openfpga/src/utils/pb_type_utils.cpp +++ b/openfpga/src/utils/pb_type_utils.cpp @@ -254,4 +254,52 @@ enum PORTS circuit_port_require_pb_port_type(const e_circuit_model_port_type& ci return type_mapping.at(circuit_port_type); } +/******************************************************************** + * Return a list of ports of a pb_type which matches the ports defined + * in its linked circuit model + * This function will only care if the port type matches + *******************************************************************/ +std::vector find_pb_type_ports_match_circuit_model_port_type(t_pb_type* pb_type, + const e_circuit_model_port_type& port_type, + const VprDeviceAnnotation& vpr_device_annotation) { + std::vector ports; + + for (int iport = 0; iport < pb_type->num_ports; ++iport) { + /* Check the circuit_port id of the port ? */ + VTR_ASSERT(CircuitPortId::INVALID() != vpr_device_annotation.pb_circuit_port(&(pb_type->ports[iport]))); + switch (port_type) { + case CIRCUIT_MODEL_PORT_INPUT: + if ( (IN_PORT == pb_type->ports[iport].type) + && (0 == pb_type->ports[iport].is_clock) ) { + ports.push_back(&pb_type->ports[iport]); + } + break; + case CIRCUIT_MODEL_PORT_OUTPUT: + if ( (OUT_PORT == pb_type->ports[iport].type) + && (0 == pb_type->ports[iport].is_clock) ) { + ports.push_back(&pb_type->ports[iport]); + } + break; + case CIRCUIT_MODEL_PORT_INOUT: + if ( (INOUT_PORT == pb_type->ports[iport].type) + && (0 == pb_type->ports[iport].is_clock) ) { + ports.push_back(&pb_type->ports[iport]); + } + break; + case CIRCUIT_MODEL_PORT_CLOCK: + if ( (IN_PORT == pb_type->ports[iport].type) + && (1 == pb_type->ports[iport].is_clock) ) { + ports.push_back(&pb_type->ports[iport]); + } + break; + /* Configuration ports are not in pb_type definition */ + default: + VTR_LOG_ERROR("Invalid type for port!\n"); + exit(1); + } + } + + return ports; +} + } /* end namespace openfpga */ diff --git a/openfpga/src/utils/pb_type_utils.h b/openfpga/src/utils/pb_type_utils.h index 17ec7f78e..6a4dd91be 100644 --- a/openfpga/src/utils/pb_type_utils.h +++ b/openfpga/src/utils/pb_type_utils.h @@ -7,6 +7,7 @@ #include #include #include "physical_types.h" +#include "vpr_device_annotation.h" #include "circuit_library.h" /******************************************************************** @@ -43,6 +44,10 @@ e_circuit_model_type pb_interconnect_require_circuit_model_type(const e_intercon enum PORTS circuit_port_require_pb_port_type(const e_circuit_model_port_type& circuit_port_type); +std::vector find_pb_type_ports_match_circuit_model_port_type(t_pb_type* pb_type, + const e_circuit_model_port_type& port_type, + const VprDeviceAnnotation& vpr_device_annotation); + } /* end namespace openfpga */ #endif From ea7d879b4f397be5632da8510defb2708140c34d Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 12 Feb 2020 18:28:50 -0700 Subject: [PATCH 125/645] add decoder module builder --- openfpga/src/fabric/build_decoder_modules.cpp | 140 ++++++++++++++++++ openfpga/src/fabric/build_decoder_modules.h | 24 +++ openfpga/src/fabric/build_device_module.cpp | 5 +- .../src/fabric/build_essential_modules.cpp | 2 +- 4 files changed, 168 insertions(+), 3 deletions(-) create mode 100644 openfpga/src/fabric/build_decoder_modules.cpp create mode 100644 openfpga/src/fabric/build_decoder_modules.h diff --git a/openfpga/src/fabric/build_decoder_modules.cpp b/openfpga/src/fabric/build_decoder_modules.cpp new file mode 100644 index 000000000..06defc007 --- /dev/null +++ b/openfpga/src/fabric/build_decoder_modules.cpp @@ -0,0 +1,140 @@ +/*************************************************************************************** + * This file includes functions that are used to build modules for decoders, including: + * 1. Local decoders used by multiplexers ONLY + * 2. Decoders used by grid/routing/top-level module for memory address decoding + ***************************************************************************************/ + +/* Headers from vtrutil library */ +#include "vtr_log.h" +#include "vtr_assert.h" +#include "vtr_time.h" + +#include "openfpga_naming.h" +#include "decoder_library_utils.h" +#include "module_manager_utils.h" + +#include "build_decoder_modules.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/*************************************************************************************** + * Create a module for a decoder with a given output size + * + * Inputs + * | | ... | + * v v v + * +-----------+ + * / \ + * / Decoder \ + * +-----------------+ + * | | | ... | | | + * v v v v v v + * Outputs + * + * The outputs are assumes to be one-hot codes (at most only one '1' exist) + * Considering this fact, there are only num_of_outputs conditions to be encoded. + * Therefore, the number of inputs is ceil(log(num_of_outputs)/log(2)) + ***************************************************************************************/ +static +void build_mux_local_decoder_module(ModuleManager& module_manager, + const DecoderLibrary& decoder_lib, + const DecoderId& decoder) { + /* Get the number of inputs */ + size_t addr_size = decoder_lib.addr_size(decoder); + size_t data_size = decoder_lib.data_size(decoder); + + /* TODO: create a name for the local encoder */ + std::string module_name = generate_mux_local_decoder_subckt_name(addr_size, data_size); + + /* Create a Verilog Module based on the circuit model, and add to module manager */ + ModuleId module_id = module_manager.add_module(module_name); + VTR_ASSERT(true == module_manager.valid_module_id(module_id)); + /* Add module ports */ + /* Add each input port */ + BasicPort addr_port(generate_mux_local_decoder_addr_port_name(), addr_size); + module_manager.add_port(module_id, addr_port, ModuleManager::MODULE_INPUT_PORT); + /* Add each output port */ + BasicPort data_port(generate_mux_local_decoder_data_port_name(), data_size); + module_manager.add_port(module_id, data_port, ModuleManager::MODULE_OUTPUT_PORT); + /* Data port is registered. It should be outputted as + * output reg [lsb:msb] data + */ + module_manager.set_port_is_register(module_id, data_port.get_name(), true); + /* Add data_in port */ + BasicPort data_inv_port(generate_mux_local_decoder_data_inv_port_name(), data_size); + VTR_ASSERT(true == decoder_lib.use_data_inv_port(decoder)); + module_manager.add_port(module_id, data_inv_port, ModuleManager::MODULE_OUTPUT_PORT); +} + + +/*************************************************************************************** + * This function will generate all the unique Verilog modules of local decoders for + * the multiplexers used in a FPGA fabric + * It will reach the goal in two steps: + * 1. Find the unique local decoders w.r.t. the number of inputs/outputs + * We will generate the subgraphs from the multiplexing graph of each multiplexers + * The number of memory bits is the number of outputs. + * From that we can infer the number of inputs of each local decoders. + * Here is an illustrative example of how local decoders are interfaced with multi-level MUXes + * + * +---------+ +---------+ + * | Local | | Local | + * | Decoder | | Decoder | + * | A | | B | + * +---------+ +---------+ + * | ... | | ... | + * v v v v + * +--------------+ +--------------+ + * | MUX Level 0 |--->| MUX Level 1 | + * +--------------+ +--------------+ + * 2. Generate local decoder Verilog modules using behavioral description. + * Note that the implementation of local decoders can be dependent on the technology + * and standard cell libraries. + * Therefore, behavioral Verilog is used and the local decoders should be synthesized + * before running the back-end flow for FPGA fabric + * See more details in the function print_verilog_mux_local_decoder() for more details + ***************************************************************************************/ +void build_mux_local_decoder_modules(ModuleManager& module_manager, + const MuxLibrary& mux_lib, + const CircuitLibrary& circuit_lib) { + vtr::ScopedStartFinishTimer timer("Build local encoder (for multiplexers) modules"); + + /* Create a library for local encoders with different sizes */ + DecoderLibrary decoder_lib; + + /* Find unique local decoders for unique branches shared by the multiplexers */ + for (auto mux : mux_lib.muxes()) { + /* Local decoders are need only when users specify them */ + CircuitModelId mux_circuit_model = mux_lib.mux_circuit_model(mux); + /* If this MUX does not need local decoder, we skip it */ + if (false == circuit_lib.mux_use_local_encoder(mux_circuit_model)) { + continue; + } + + const MuxGraph& mux_graph = mux_lib.mux_graph(mux); + /* Create a mux graph for the branch circuit */ + std::vector branch_mux_graphs = mux_graph.build_mux_branch_graphs(); + /* Add the decoder to the decoder library */ + for (auto branch_mux_graph : branch_mux_graphs) { + /* The decoder size depends on the number of memories of a branch MUX. + * Note that only when there are >=2 memories, a decoder is needed + */ + size_t decoder_data_size = branch_mux_graph.num_memory_bits(); + if (0 == decoder_data_size) { + continue; + } + /* Try to find if the decoder already exists in the library, + * If there is no such decoder, add it to the library + */ + add_mux_local_decoder_to_library(decoder_lib, decoder_data_size); + } + } + + /* Generate Verilog modules for the found unique local encoders */ + for (const auto& decoder : decoder_lib.decoders()) { + build_mux_local_decoder_module(module_manager, decoder_lib, decoder); + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fabric/build_decoder_modules.h b/openfpga/src/fabric/build_decoder_modules.h new file mode 100644 index 000000000..842aef8c4 --- /dev/null +++ b/openfpga/src/fabric/build_decoder_modules.h @@ -0,0 +1,24 @@ +#ifndef BUILD_DECODER_MODULES_H +#define BUILD_DECODER_MODULES_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "module_manager.h" +#include "mux_library.h" +#include "circuit_library.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void build_mux_local_decoder_modules(ModuleManager& module_manager, + const MuxLibrary& mux_lib, + const CircuitLibrary& circuit_lib); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fabric/build_device_module.cpp b/openfpga/src/fabric/build_device_module.cpp index bf664048f..71966937a 100644 --- a/openfpga/src/fabric/build_device_module.cpp +++ b/openfpga/src/fabric/build_device_module.cpp @@ -9,7 +9,7 @@ #include "vtr_time.h" #include "build_essential_modules.h" -//#include "build_decoder_modules.h" +#include "build_decoder_modules.h" //#include "build_mux_modules.h" //#include "build_lut_modules.h" //#include "build_wire_modules.h" @@ -49,7 +49,8 @@ ModuleManager build_device_module_graph(const DeviceContext& vpr_device_ctx, build_essential_modules(module_manager, openfpga_ctx.arch().circuit_lib); /* Build local encoders for multiplexers, this MUST be called before multiplexer building */ - //build_mux_local_decoder_modules(module_manager, mux_lib, arch.spice->circuit_lib); + build_mux_local_decoder_modules(module_manager, openfpga_ctx.mux_lib(), + openfpga_ctx.arch().circuit_lib); /* Build multiplexer modules */ //build_mux_modules(module_manager, mux_lib, arch.spice->circuit_lib); diff --git a/openfpga/src/fabric/build_essential_modules.cpp b/openfpga/src/fabric/build_essential_modules.cpp index 07a00a788..a66a52979 100644 --- a/openfpga/src/fabric/build_essential_modules.cpp +++ b/openfpga/src/fabric/build_essential_modules.cpp @@ -150,7 +150,7 @@ void build_gate_module(ModuleManager& module_manager, ***********************************************/ void build_essential_modules(ModuleManager& module_manager, const CircuitLibrary& circuit_lib) { - vtr::ScopedStartFinishTimer timer("Build essential (inverter/buffer/logic gate) modules..."); + vtr::ScopedStartFinishTimer timer("Build essential (inverter/buffer/logic gate) modules"); for (const auto& circuit_model : circuit_lib.models()) { /* Add essential modules upon on demand: only when it is not yet in the module library */ From fddd3c94637e0be39e54be03f49396ad9c790b3c Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 12 Feb 2020 19:45:14 -0700 Subject: [PATCH 126/645] add mux module builder --- openfpga/src/base/openfpga_reserved_words.h | 28 + openfpga/src/fabric/build_device_module.cpp | 4 +- .../src/fabric/build_module_graph_utils.cpp | 89 ++ .../src/fabric/build_module_graph_utils.h | 38 + openfpga/src/fabric/build_mux_modules.cpp | 1408 +++++++++++++++++ openfpga/src/fabric/build_mux_modules.h | 24 + .../k6_frac_N10_40nm_openfpga.xml | 2 +- 7 files changed, 1590 insertions(+), 3 deletions(-) create mode 100644 openfpga/src/base/openfpga_reserved_words.h create mode 100644 openfpga/src/fabric/build_module_graph_utils.cpp create mode 100644 openfpga/src/fabric/build_module_graph_utils.h create mode 100644 openfpga/src/fabric/build_mux_modules.cpp create mode 100644 openfpga/src/fabric/build_mux_modules.h diff --git a/openfpga/src/base/openfpga_reserved_words.h b/openfpga/src/base/openfpga_reserved_words.h new file mode 100644 index 000000000..6e761de87 --- /dev/null +++ b/openfpga/src/base/openfpga_reserved_words.h @@ -0,0 +1,28 @@ +/******************************************************************** + * This file includes all the reserved words that are used in + * naming module, blocks, instances and cells in FPGA X2P support, + * including: + * Verilog generation, SPICE generation and bitstream generation + *******************************************************************/ +#ifndef OPENFPGA_RESERVED_WORDS_H +#define OPENFPGA_RESERVED_WORDS_H + +/* Grid naming constant strings */ +constexpr char* GRID_MODULE_NAME_PREFIX = "grid_"; + +/* Memory naming constant strings */ +constexpr char* GRID_MEM_INSTANCE_PREFIX = "mem_"; +constexpr char* SWITCH_BLOCK_MEM_INSTANCE_PREFIX = "mem_"; +constexpr char* CONNECTION_BLOCK_MEM_INSTANCE_PREFIX = "mem_"; +constexpr char* MEMORY_MODULE_POSTFIX = "_mem"; + +/* Multiplexer naming constant strings */ +constexpr char* MUX_BASIS_MODULE_POSTFIX = "_basis"; +constexpr char* GRID_MUX_INSTANCE_PREFIX = "mux_"; +constexpr char* SWITCH_BLOCK_MUX_INSTANCE_PREFIX = "mux_"; +constexpr char* CONNECTION_BLOCK_MUX_INSTANCE_PREFIX = "mux_"; + +/* Bitstream file strings */ +constexpr char* BITSTREAM_XML_FILE_NAME_POSTFIX = "_bitstream.xml"; + +#endif diff --git a/openfpga/src/fabric/build_device_module.cpp b/openfpga/src/fabric/build_device_module.cpp index 71966937a..a322422df 100644 --- a/openfpga/src/fabric/build_device_module.cpp +++ b/openfpga/src/fabric/build_device_module.cpp @@ -10,7 +10,7 @@ #include "build_essential_modules.h" #include "build_decoder_modules.h" -//#include "build_mux_modules.h" +#include "build_mux_modules.h" //#include "build_lut_modules.h" //#include "build_wire_modules.h" //#include "build_memory_modules.h" @@ -53,7 +53,7 @@ ModuleManager build_device_module_graph(const DeviceContext& vpr_device_ctx, openfpga_ctx.arch().circuit_lib); /* Build multiplexer modules */ - //build_mux_modules(module_manager, mux_lib, arch.spice->circuit_lib); + build_mux_modules(module_manager, openfpga_ctx.mux_lib(), openfpga_ctx.arch().circuit_lib); /* Build LUT modules */ //build_lut_modules(module_manager, arch.spice->circuit_lib); diff --git a/openfpga/src/fabric/build_module_graph_utils.cpp b/openfpga/src/fabric/build_module_graph_utils.cpp new file mode 100644 index 000000000..617a156a6 --- /dev/null +++ b/openfpga/src/fabric/build_module_graph_utils.cpp @@ -0,0 +1,89 @@ +/******************************************************************** + * This file includes most utilized functions that are used to + * build module graphs + ********************************************************************/ +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" + +#include "openfpga_naming.h" +#include "build_module_graph_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Find input port of a buffer/inverter module + ********************************************************************/ +ModulePortId find_inverter_buffer_module_port(const ModuleManager& module_manager, + const ModuleId& module_id, + const CircuitLibrary& circuit_lib, + const CircuitModelId& model_id, + const e_circuit_model_port_type& port_type) { + /* We must have a valid module id */ + VTR_ASSERT(true == module_manager.valid_module_id(module_id)); + /* Check the type of model */ + VTR_ASSERT(CIRCUIT_MODEL_INVBUF == circuit_lib.model_type(model_id)); + + /* Add module nets to wire to the buffer module */ + /* To match the context, Buffer should have only 2 non-global ports: 1 input port and 1 output port */ + std::vector model_ports = circuit_lib.model_ports_by_type(model_id, port_type, true); + VTR_ASSERT(1 == model_ports.size()); + + /* Find the input and output module ports */ + ModulePortId module_port_id = module_manager.find_module_port(module_id, circuit_lib.port_prefix(model_ports[0])); + VTR_ASSERT(true == module_manager.valid_module_port_id(module_id, module_port_id)); + + return module_port_id; +} + +/******************************************************************** + * Add inverter/buffer module to a parent module + * and complete the wiring to the input port of inverter/buffer + * This function will return the wire created for the output port of inverter/buffer + * + * parent_module + * +----------------------------------------------------------------- + * | + * | input_net output_net + * | | | + * | v +---------------+ v + * | src_module_port --------->| child_module |--------> + * | +---------------+ + * + ********************************************************************/ +ModuleNetId add_inverter_buffer_child_module_and_nets(ModuleManager& module_manager, + const ModuleId& parent_module, + const CircuitLibrary& circuit_lib, + const CircuitModelId& model_id, + const ModuleNetId& input_net) { + /* We must have a valid module id */ + VTR_ASSERT(true == module_manager.valid_module_id(parent_module)); + + std::string module_name = circuit_lib.model_name(model_id); + ModuleId child_module = module_manager.find_module(module_name); + VTR_ASSERT(true == module_manager.valid_module_id(child_module)); + + ModulePortId module_input_port_id = find_inverter_buffer_module_port(module_manager, child_module, circuit_lib, model_id, CIRCUIT_MODEL_PORT_INPUT); + ModulePortId module_output_port_id = find_inverter_buffer_module_port(module_manager, child_module, circuit_lib, model_id, CIRCUIT_MODEL_PORT_OUTPUT); + + /* Port size should be 1 ! */ + VTR_ASSERT(1 == module_manager.module_port(child_module, module_input_port_id).get_width()); + VTR_ASSERT(1 == module_manager.module_port(child_module, module_output_port_id).get_width()); + + /* Instanciate a child module */ + size_t child_instance = module_manager.num_instance(parent_module, child_module); + module_manager.add_child_module(parent_module, child_module); + + /* Use the net to connect to the input net of buffer */ + module_manager.add_module_net_sink(parent_module, input_net, child_module, child_instance, module_input_port_id, 0); + + /* Create a net to bridge the input inverter and LUT MUX */ + ModuleNetId output_net = module_manager.create_module_net(parent_module); + module_manager.add_module_net_source(parent_module, output_net, child_module, child_instance, module_output_port_id, 0); + + return output_net; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fabric/build_module_graph_utils.h b/openfpga/src/fabric/build_module_graph_utils.h new file mode 100644 index 000000000..dee44140a --- /dev/null +++ b/openfpga/src/fabric/build_module_graph_utils.h @@ -0,0 +1,38 @@ +#ifndef BUILD_MODULE_GRAPH_UTILS_H +#define BUILD_MODULE_GRAPH_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "vtr_geometry.h" + +#include "circuit_library.h" +#include "openfpga_side_manager.h" + +#include "vpr_types.h" +#include "module_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +ModulePortId find_inverter_buffer_module_port(const ModuleManager& module_manager, + const ModuleId& module_id, + const CircuitLibrary& circuit_lib, + const CircuitModelId& model_id, + const e_circuit_model_port_type& port_type); + +ModuleNetId add_inverter_buffer_child_module_and_nets(ModuleManager& module_manager, + const ModuleId& parent_module, + const CircuitLibrary& circuit_lib, + const CircuitModelId& model_id, + const ModuleNetId& input_net); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fabric/build_mux_modules.cpp b/openfpga/src/fabric/build_mux_modules.cpp new file mode 100644 index 000000000..0f430c4d3 --- /dev/null +++ b/openfpga/src/fabric/build_mux_modules.cpp @@ -0,0 +1,1408 @@ +/*********************************************** + * This file includes functions to generate + * Verilog submodules for multiplexers. + * including both fundamental submodules + * such as a branch in a multiplexer + * and the full multiplexer + **********************************************/ +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_log.h" +#include "vtr_assert.h" +#include "vtr_time.h" + +#include "mux_graph.h" +#include "module_manager.h" +#include "mux_utils.h" +#include "circuit_library_utils.h" +#include "decoder_library_utils.h" +#include "module_manager_utils.h" +#include "build_module_graph_utils.h" +#include "openfpga_reserved_words.h" +#include "openfpga_naming.h" + +#include "build_mux_modules.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/********************************************************************* + * Generate structural Verilog codes (consist of transmission-gates or + * pass-transistor) modeling an branch circuit + * for a multiplexer with the given size + * + * +----------+ + * input[0] --->| tgate[0] |-+ + * +----------+ | + * | + * +----------+ | + * input[1] --->| tgate[1] |-+--->output[0] + * +----------+ | + * | + * ... ... | + * | + * +----------+ | + * input[i] --->| tgate[i] |-+ + * +----------+ + *********************************************************************/ +static +void build_cmos_mux_branch_body(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const CircuitModelId& tgate_model, + const ModuleId& mux_module, + const ModulePortId& module_input_port, + const ModulePortId& module_output_port, + const ModulePortId& module_mem_port, + const ModulePortId& module_mem_inv_port, + const MuxGraph& mux_graph) { + /* Get the module id of tgate in Module manager */ + ModuleId tgate_module_id = module_manager.find_module(circuit_lib.model_name(tgate_model)); + VTR_ASSERT(ModuleId::INVALID() != tgate_module_id); + + /* Get model ports of tgate */ + std::vector tgate_input_ports = circuit_lib.model_ports_by_type(tgate_model, CIRCUIT_MODEL_PORT_INPUT, true); + std::vector tgate_output_ports = circuit_lib.model_ports_by_type(tgate_model, CIRCUIT_MODEL_PORT_OUTPUT, true); + VTR_ASSERT(3 == tgate_input_ports.size()); + VTR_ASSERT(1 == tgate_output_ports.size()); + + /* Find the module ports of tgate module */ + /* Input port is the data path input of the tgate, whose size must be 1 ! */ + ModulePortId tgate_module_input = module_manager.find_module_port(tgate_module_id, circuit_lib.port_prefix(tgate_input_ports[0])); + VTR_ASSERT(true == module_manager.valid_module_port_id(tgate_module_id, tgate_module_input)); + BasicPort tgate_module_input_port = module_manager.module_port(tgate_module_id, tgate_module_input); + VTR_ASSERT(1 == tgate_module_input_port.get_width()); + + /* Mem port is the memory of the tgate, whose size must be 1 ! */ + ModulePortId tgate_module_mem = module_manager.find_module_port(tgate_module_id, circuit_lib.port_prefix(tgate_input_ports[1])); + VTR_ASSERT(true == module_manager.valid_module_port_id(tgate_module_id, tgate_module_mem)); + BasicPort tgate_module_mem_port = module_manager.module_port(tgate_module_id, tgate_module_mem); + VTR_ASSERT(1 == tgate_module_mem_port.get_width()); + + /* Mem inv port is the inverted memory of the tgate, whose size must be 1 ! */ + ModulePortId tgate_module_mem_inv = module_manager.find_module_port(tgate_module_id, circuit_lib.port_prefix(tgate_input_ports[2])); + VTR_ASSERT(true == module_manager.valid_module_port_id(tgate_module_id, tgate_module_mem_inv)); + BasicPort tgate_module_mem_inv_port = module_manager.module_port(tgate_module_id, tgate_module_mem_inv); + VTR_ASSERT(1 == tgate_module_mem_inv_port.get_width()); + + /* Output port is the data path output of the tgate, whose size must be 1 ! */ + ModulePortId tgate_module_output = module_manager.find_module_port(tgate_module_id, circuit_lib.port_prefix(tgate_output_ports[0])); + VTR_ASSERT(true == module_manager.valid_module_port_id(tgate_module_id, tgate_module_output)); + BasicPort tgate_module_output_port = module_manager.module_port(tgate_module_id, tgate_module_output); + VTR_ASSERT(1 == tgate_module_output_port.get_width()); + + /* Ensure that input port size does match mux inputs */ + BasicPort input_port = module_manager.module_port(mux_module, module_input_port); + VTR_ASSERT(input_port.get_width() == mux_graph.num_inputs()); + + /* Add module nets for each mux inputs */ + std::vector mux_input_nets; + for (const size_t& pin : input_port.pins()) { + ModuleNetId input_net = module_manager.create_module_net(mux_module); + mux_input_nets.push_back(input_net); + /* Configure the source for each net */ + module_manager.add_module_net_source(mux_module, input_net, mux_module, 0, module_input_port, pin); + } + + /* Ensure that output port size does match mux outputs */ + BasicPort output_port = module_manager.module_port(mux_module, module_output_port); + VTR_ASSERT(output_port.get_width() == mux_graph.num_outputs()); + + /* Add module nets for each mux outputs */ + std::vector mux_output_nets; + for (const size_t& pin : output_port.pins()) { + ModuleNetId output_net = module_manager.create_module_net(mux_module); + mux_output_nets.push_back(output_net); + /* Configure the sink for each net */ + module_manager.add_module_net_sink(mux_module, output_net, mux_module, 0, module_output_port, pin); + } + + /* Ensure that mem port size does match mux outputs */ + BasicPort mem_port = module_manager.module_port(mux_module, module_mem_port); + VTR_ASSERT(mem_port.get_width() == mux_graph.num_memory_bits()); + + /* Add module nets for each mem inputs */ + std::vector mux_mem_nets; + for (const size_t& pin : mem_port.pins()) { + ModuleNetId mem_net = module_manager.create_module_net(mux_module); + mux_mem_nets.push_back(mem_net); + /* Configure the source for each net */ + module_manager.add_module_net_source(mux_module, mem_net, mux_module, 0, module_mem_port, pin); + } + + /* Ensure that mem_inv port size does match mux outputs */ + BasicPort mem_inv_port = module_manager.module_port(mux_module, module_mem_inv_port); + VTR_ASSERT(mem_inv_port.get_width() == mux_graph.num_memory_bits()); + + /* Add module nets for each mem inverted inputs */ + std::vector mux_mem_inv_nets; + for (const size_t& pin : mem_inv_port.pins()) { + ModuleNetId mem_net = module_manager.create_module_net(mux_module); + mux_mem_inv_nets.push_back(mem_net); + /* Configure the source for each net */ + module_manager.add_module_net_source(mux_module, mem_net, mux_module, 0, module_mem_inv_port, pin); + } + + /* Build a module following the connections in mux_graph */ + /* Iterate over the inputs */ + for (const auto& mux_input : mux_graph.inputs()) { + /* Iterate over the outputs */ + for (const auto& mux_output : mux_graph.outputs()) { + /* Add the a tgate to bridge the mux input and output */ + size_t tgate_instance = module_manager.num_instance(mux_module, tgate_module_id); + module_manager.add_child_module(mux_module, tgate_module_id); + + /* Add module nets to connect the mux input and tgate input */ + module_manager.add_module_net_sink(mux_module, mux_input_nets[size_t(mux_graph.input_id(mux_input))], tgate_module_id, tgate_instance, tgate_module_input, tgate_module_input_port.get_lsb()); + + /* if there is a connection between the input and output, a tgate will be outputted */ + std::vector edges = mux_graph.find_edges(mux_input, mux_output); + /* There should be only one edge or no edge*/ + VTR_ASSERT((1 == edges.size()) || (0 == edges.size())); + /* No need to output tgates if there are no edges between two nodes */ + if (0 == edges.size()) { + continue; + } + + /* Add module nets to connect the mux output and tgate output */ + module_manager.add_module_net_source(mux_module, mux_output_nets[size_t(mux_graph.output_id(mux_output))], tgate_module_id, tgate_instance, tgate_module_output, tgate_module_output_port.get_lsb()); + + MuxMemId mux_mem = mux_graph.find_edge_mem(edges[0]); + /* Add module nets to connect the mem input and tgate mem input */ + if (false == mux_graph.is_edge_use_inv_mem(edges[0])) { + /* wire mem to mem of module, and wire mem_inv to mem_inv of module */ + module_manager.add_module_net_sink(mux_module, mux_mem_nets[size_t(mux_mem)], tgate_module_id, tgate_instance, tgate_module_mem, tgate_module_mem_port.get_lsb()); + module_manager.add_module_net_sink(mux_module, mux_mem_inv_nets[size_t(mux_mem)], tgate_module_id, tgate_instance, tgate_module_mem_inv, tgate_module_mem_inv_port.get_lsb()); + } else { + /* wire mem_inv to mem of module, wire mem to mem_inv of module */ + module_manager.add_module_net_sink(mux_module, mux_mem_inv_nets[size_t(mux_mem)], tgate_module_id, tgate_instance, tgate_module_mem, tgate_module_mem_port.get_lsb()); + module_manager.add_module_net_sink(mux_module, mux_mem_nets[size_t(mux_mem)], tgate_module_id, tgate_instance, tgate_module_mem_inv, tgate_module_mem_inv_port.get_lsb()); + } + } + } +} + +/********************************************************************* + * Generate Verilog codes modeling an branch circuit + * for a CMOS multiplexer with the given size + * Support structural and behavioral Verilog codes + *********************************************************************/ +static +void build_cmos_mux_branch_module(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const std::string& module_name, + const MuxGraph& mux_graph) { + /* Get the tgate model */ + CircuitModelId tgate_model = circuit_lib.pass_gate_logic_model(mux_model); + + /* Skip output if the tgate model is a MUX2, it is handled by essential-gate generator */ + if (CIRCUIT_MODEL_GATE == circuit_lib.model_type(tgate_model)) { + VTR_ASSERT(CIRCUIT_MODEL_GATE_MUX2 == circuit_lib.gate_type(tgate_model)); + return; + } + + std::vector tgate_global_ports = circuit_lib.model_global_ports_by_type(tgate_model, CIRCUIT_MODEL_PORT_INPUT, true, true); + + /* Generate the Verilog netlist according to the mux_graph */ + /* Find out the number of inputs */ + size_t num_inputs = mux_graph.num_inputs(); + /* Find out the number of outputs */ + size_t num_outputs = mux_graph.num_outputs(); + /* Find out the number of memory bits */ + size_t num_mems = mux_graph.num_memory_bits(); + + /* Check codes to ensure the port of Verilog netlists will match */ + /* MUX graph must have only 1 output */ + VTR_ASSERT(1 == num_outputs); + /* MUX graph must have only 1 level*/ + VTR_ASSERT(1 == mux_graph.num_levels()); + + /* Create a Verilog Module based on the circuit model, and add to module manager */ + ModuleId mux_module = module_manager.add_module(module_name); + VTR_ASSERT(true == module_manager.valid_module_id(mux_module)); + /* Add module ports */ + /* Add each input port */ + BasicPort input_port("in", num_inputs); + ModulePortId module_input_port = module_manager.add_port(mux_module, input_port, ModuleManager::MODULE_INPUT_PORT); + /* Add each output port */ + BasicPort output_port("out", num_outputs); + ModulePortId module_output_port = module_manager.add_port(mux_module, output_port, ModuleManager::MODULE_OUTPUT_PORT); + /* Add each memory port */ + BasicPort mem_port("mem", num_mems); + ModulePortId module_mem_port = module_manager.add_port(mux_module, mem_port, ModuleManager::MODULE_INPUT_PORT); + BasicPort mem_inv_port("mem_inv", num_mems); + ModulePortId module_mem_inv_port = module_manager.add_port(mux_module, mem_inv_port, ModuleManager::MODULE_INPUT_PORT); + + /* By default we give a structural description, + * Writers can freely write the module in their styles + * For instance, Verilog writer can ignore the internal structure and write in behavioral codes + */ + build_cmos_mux_branch_body(module_manager, circuit_lib, tgate_model, mux_module, module_input_port, module_output_port, module_mem_port, module_mem_inv_port, mux_graph); + + /* Add global ports to the mux module: + * This is a much easier job after adding sub modules (instances), + * we just need to find all the global ports from the child modules and build a list of it + */ + add_module_global_ports_from_child_modules(module_manager, mux_module); +} + +/********************************************************************* + * Generate Verilog codes modeling an branch circuit + * for a RRAM-based multiplexer with the given size + * Support structural and behavioral Verilog codes + *********************************************************************/ +static +void build_rram_mux_branch_module(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const std::string& module_name, + const MuxGraph& mux_graph) { + /* Get the input ports from the mux */ + std::vector mux_input_ports = circuit_lib.model_ports_by_type(mux_model, CIRCUIT_MODEL_PORT_INPUT, true); + /* Get the output ports from the mux */ + std::vector mux_output_ports = circuit_lib.model_ports_by_type(mux_model, CIRCUIT_MODEL_PORT_OUTPUT, true); + /* Get the BL and WL ports from the mux */ + std::vector mux_blb_ports = circuit_lib.model_ports_by_type(mux_model, CIRCUIT_MODEL_PORT_BLB, true); + std::vector mux_wl_ports = circuit_lib.model_ports_by_type(mux_model, CIRCUIT_MODEL_PORT_WL, true); + + /* Generate the Verilog netlist according to the mux_graph */ + /* Find out the number of inputs */ + size_t num_inputs = mux_graph.num_inputs(); + /* Find out the number of outputs */ + size_t num_outputs = mux_graph.num_outputs(); + /* Find out the number of memory bits */ + size_t num_mems = mux_graph.num_memory_bits(); + + /* Check codes to ensure the port of Verilog netlists will match */ + /* MUX graph must have only 1 output */ + VTR_ASSERT(1 == num_outputs); + /* MUX graph must have only 1 level*/ + VTR_ASSERT(1 == mux_graph.num_levels()); + /* MUX graph must have only 1 input and 1 BLB and 1 WL port */ + VTR_ASSERT(1 == mux_input_ports.size()); + VTR_ASSERT(1 == mux_output_ports.size()); + VTR_ASSERT(1 == mux_blb_ports.size()); + VTR_ASSERT(1 == mux_wl_ports.size()); + + /* Create a Verilog Module based on the circuit model, and add to module manager */ + ModuleId mux_module = module_manager.add_module(module_name); + VTR_ASSERT(ModuleId::INVALID() != mux_module); + + /* Add module ports */ + /* Add each global programming enable/disable ports */ + std::vector prog_enable_ports = circuit_lib.model_global_ports_by_type(mux_model, CIRCUIT_MODEL_PORT_INPUT, true, true); + for (const auto& port : prog_enable_ports) { + /* Configure each global port */ + BasicPort global_port(circuit_lib.port_prefix(port), circuit_lib.port_size(port)); + module_manager.add_port(mux_module, global_port, ModuleManager::MODULE_GLOBAL_PORT); + } + + /* Add each input port */ + BasicPort input_port(circuit_lib.port_prefix(mux_input_ports[0]), num_inputs); + module_manager.add_port(mux_module, input_port, ModuleManager::MODULE_INPUT_PORT); + + /* Add each output port */ + BasicPort output_port(circuit_lib.port_prefix(mux_output_ports[0]), num_outputs); + module_manager.add_port(mux_module, output_port, ModuleManager::MODULE_OUTPUT_PORT); + + /* Add RRAM programming ports, + * RRAM MUXes require one more pair of BLB and WL + * to configure the memories. See schematic for details + */ + BasicPort blb_port(circuit_lib.port_prefix(mux_blb_ports[0]), num_mems + 1); + module_manager.add_port(mux_module, blb_port, ModuleManager::MODULE_INPUT_PORT); + + BasicPort wl_port(circuit_lib.port_prefix(mux_wl_ports[0]), num_mems + 1); + module_manager.add_port(mux_module, wl_port, ModuleManager::MODULE_INPUT_PORT); + + /* Note: we do not generate the internal structure of the ReRAM-based MUX + * circuit as a module graph! + * This is mainly due to that the internal structure could be different + * in Verilog or SPICE netlists + * Leave the writers to customize this + */ +} + +/*********************************************** + * Generate Verilog codes modeling an branch circuit + * for a multiplexer with the given size + **********************************************/ +static +void build_mux_branch_module(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const size_t& mux_size, + const MuxGraph& mux_graph) { + std::string module_name = generate_mux_branch_subckt_name(circuit_lib, mux_model, mux_size, mux_graph.num_inputs(), MUX_BASIS_MODULE_POSTFIX); + + /* Multiplexers built with different technology is in different organization */ + switch (circuit_lib.design_tech_type(mux_model)) { + case CIRCUIT_MODEL_DESIGN_CMOS: + build_cmos_mux_branch_module(module_manager, circuit_lib, mux_model, module_name, mux_graph); + break; + case CIRCUIT_MODEL_DESIGN_RRAM: + build_rram_mux_branch_module(module_manager, circuit_lib, mux_model, module_name, mux_graph); + break; + default: + VTR_LOG_ERROR("Invalid design technology of multiplexer '%s'\n", + circuit_lib.model_name(mux_model).c_str()); + exit(1); + } +} + +/******************************************************************** + * Generate the standard-cell-based internal logic (multiplexing structure) + * for a multiplexer or LUT in Verilog codes + * This function will : + * 1. build a multiplexing structure by instanciating standard cells MUX2 + * 2. add intermediate buffers between multiplexing stages if specified. + *******************************************************************/ +static +void build_cmos_mux_module_mux2_multiplexing_structure(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const ModuleId& mux_module, + const CircuitModelId& mux_model, + const CircuitModelId& std_cell_model, + const vtr::vector& mux_module_input_nets, + const vtr::vector& mux_module_output_nets, + const vtr::vector& mux_module_mem_nets, + const MuxGraph& mux_graph) { + /* Get the regular (non-mode-select) sram ports from the mux */ + std::vector mux_regular_sram_ports = find_circuit_regular_sram_ports(circuit_lib, mux_model); + VTR_ASSERT(1 == mux_regular_sram_ports.size()); + + /* Find the input ports and output ports of the standard cell */ + std::vector std_cell_input_ports = circuit_lib.model_ports_by_type(std_cell_model, CIRCUIT_MODEL_PORT_INPUT, true); + std::vector std_cell_output_ports = circuit_lib.model_ports_by_type(std_cell_model, CIRCUIT_MODEL_PORT_OUTPUT, true); + /* Quick check the requirements on port map */ + VTR_ASSERT(3 == std_cell_input_ports.size()); + VTR_ASSERT(1 == std_cell_output_ports.size()); + + /* Find module information of the standard cell MUX2 */ + std::string std_cell_module_name = circuit_lib.model_name(std_cell_model); + /* Get the moduleId for the submodule */ + ModuleId std_cell_module_id = module_manager.find_module(std_cell_module_name); + /* We must have one */ + VTR_ASSERT(ModuleId::INVALID() != std_cell_module_id); + + /* Find the module ports of the standard cell MUX2 module */ + std::vector std_cell_module_inputs; + std::vector std_cell_module_input_ports; + /* Input 0 port is the first data path input of the tgate, whose size must be 1 ! */ + for (size_t port_id = 0; port_id < 2; ++port_id) { + std_cell_module_inputs.push_back(module_manager.find_module_port(std_cell_module_id, circuit_lib.port_prefix(std_cell_input_ports[port_id]))); + VTR_ASSERT(true == module_manager.valid_module_port_id(std_cell_module_id, std_cell_module_inputs[port_id])); + std_cell_module_input_ports.push_back(module_manager.module_port(std_cell_module_id, std_cell_module_inputs[port_id])); + VTR_ASSERT(1 == std_cell_module_input_ports[port_id].get_width()); + } + + /* Mem port is the memory of the standard cell MUX2, whose size must be 1 ! */ + ModulePortId std_cell_module_mem = module_manager.find_module_port(std_cell_module_id, circuit_lib.port_prefix(std_cell_input_ports[2])); + VTR_ASSERT(true == module_manager.valid_module_port_id(std_cell_module_id, std_cell_module_mem)); + BasicPort std_cell_module_mem_port = module_manager.module_port(std_cell_module_id, std_cell_module_mem); + VTR_ASSERT(1 == std_cell_module_mem_port.get_width()); + + /* Output port is the data path output of the standard cell MUX2, whose size must be 1 ! */ + ModulePortId std_cell_module_output = module_manager.find_module_port(std_cell_module_id, circuit_lib.port_prefix(std_cell_output_ports[0])); + VTR_ASSERT(true == module_manager.valid_module_port_id(std_cell_module_id, std_cell_module_output)); + BasicPort std_cell_module_output_port = module_manager.module_port(std_cell_module_id, std_cell_module_output); + VTR_ASSERT(1 == std_cell_module_output_port.get_width()); + + /* Cache Net ids for each level of the multiplexer */ + std::vector> module_nets_by_level; + module_nets_by_level.resize(mux_graph.num_node_levels()); + for (size_t level = 0; level < mux_graph.num_node_levels(); ++level) { + /* Print the internal wires located at this level */ + module_nets_by_level[level].resize(mux_graph.num_nodes_at_level(level)); + } + + /* Build the location map of intermediate buffers */ + std::vector inter_buffer_location_map = build_mux_intermediate_buffer_location_map(circuit_lib, mux_model, mux_graph.num_node_levels()); + + /* Add all the branch modules and intermediate buffers */ + for (const auto& node : mux_graph.non_input_nodes()) { + /* Get the size of branch circuit + * Instanciate an branch circuit by the size (fan-in) of the node + */ + size_t branch_size = mux_graph.node_in_edges(node).size(); + /* To match the standard cell MUX2: We should have only 2 input_nodes */ + VTR_ASSERT(2 == branch_size); + + /* Find the instance id */ + size_t std_cell_instance_id = module_manager.num_instance(mux_module, std_cell_module_id); + /* Add the module to mux_module */ + module_manager.add_child_module(mux_module, std_cell_module_id); + + /* Get the node level and index in the current level */ + size_t output_node_level = mux_graph.node_level(node); + size_t output_node_index_at_level = mux_graph.node_index_at_level(node); + /* Set a name for the instance */ + std::string std_cell_instance_name = generate_mux_branch_instance_name(output_node_level, output_node_index_at_level, false); + module_manager.set_child_instance_name(mux_module, std_cell_module_id, std_cell_instance_id, std_cell_instance_name); + + /* Add module nets to wire to next stage modules */ + ModuleNetId branch_net; + if (true == mux_graph.is_node_output(node)) { + /* This is an output node, we should use existing output nets */ + MuxOutputId output_id = mux_graph.output_id(node); + branch_net = mux_module_output_nets[output_id]; + } else { + VTR_ASSERT(false == mux_graph.is_node_output(node)); + branch_net = module_manager.create_module_net(mux_module); + } + module_manager.add_module_net_source(mux_module, branch_net, std_cell_module_id, std_cell_instance_id, std_cell_module_output, std_cell_module_output_port.get_lsb()); + + /* Record the module net id in the cache */ + module_nets_by_level[output_node_level][output_node_index_at_level] = branch_net; + + /* Wire the branch module memory ports to the nets of MUX memory ports */ + /* Get the mems in the branch circuits */ + std::vector mems; + for (const auto& edge : mux_graph.node_in_edges(node)) { + /* Get the mem control the edge */ + MuxMemId mem = mux_graph.find_edge_mem(edge); + /* Add the mem if it is not in the list */ + if (mems.end() == std::find(mems.begin(), mems.end(), mem)) { + mems.push_back(mem); + } + } + /* Connect mem to mem net one by one + * Note that standard cell MUX2 only needs mem but NOT mem_inv + */ + for (const MuxMemId& mem : mems) { + module_manager.add_module_net_sink(mux_module, mux_module_mem_nets[mem], std_cell_module_id, std_cell_instance_id, std_cell_module_mem, std_cell_module_mem_port.get_lsb()); + } + + /* Wire the branch module inputs to the nets in previous stage */ + /* Get the nodes which drive the root_node */ + std::vector input_nodes; + for (const auto& edge : mux_graph.node_in_edges(node)) { + /* Get the nodes drive the edge */ + for (const auto& src_node : mux_graph.edge_src_nodes(edge)) { + input_nodes.push_back(src_node); + } + } + /* Number of inputs should match the branch_input_size!!! */ + VTR_ASSERT(input_nodes.size() == branch_size); + /* To match the standard cell MUX2: We should have only 2 input_nodes */ + VTR_ASSERT(2 == input_nodes.size()); + /* build the link between input_node[0] and std_cell_input_port[0] + * build the link between input_node[1] and std_cell_input_port[1] + */ + for (size_t node_id = 0; node_id < input_nodes.size(); ++node_id) { + /* Find the port info of each input node */ + size_t input_node_level = mux_graph.node_level(input_nodes[node_id]); + size_t input_node_index_at_level = mux_graph.node_index_at_level(input_nodes[node_id]); + /* For inputs of mux, the net id is reserved */ + if (true == mux_graph.is_node_input(input_nodes[node_id])) { + /* Get node input id */ + MuxInputId input_id = mux_graph.input_id(input_nodes[node_id]); + module_manager.add_module_net_sink(mux_module, mux_module_input_nets[input_id], std_cell_module_id, std_cell_instance_id, std_cell_module_inputs[node_id], std_cell_module_input_ports[node_id].get_lsb()); + } else { + VTR_ASSERT (false == mux_graph.is_node_input(input_nodes[node_id])); + /* Find the input port of standard cell */ + module_manager.add_module_net_sink(mux_module, module_nets_by_level[input_node_level][input_node_index_at_level], std_cell_module_id, std_cell_instance_id, std_cell_module_inputs[node_id], std_cell_module_input_ports[node_id].get_lsb()); + } + } + + /* Identify if an intermediate buffer is needed */ + if (false == inter_buffer_location_map[output_node_level]) { + continue; + } + /* Add an intermediate buffer to mux_module if needed */ + if (true == mux_graph.is_node_output(node)) { + /* Output node does not need buffer addition here, it is handled outside this function */ + continue; + } + /* Now we need to add intermediate buffers by instanciating the modules */ + CircuitModelId buffer_model = circuit_lib.lut_intermediate_buffer_model(mux_model); + /* We must have a valid model id */ + VTR_ASSERT(CircuitModelId::INVALID() != buffer_model); + + /* Create a module net which sources from buffer output */ + ModuleNetId buffer_net = add_inverter_buffer_child_module_and_nets(module_manager, mux_module, circuit_lib, buffer_model, branch_net); + + /* Record the module net id in the cache */ + module_nets_by_level[output_node_level][output_node_index_at_level] = buffer_net; + } +} + +/******************************************************************** + * Generate the pass-transistor/transmission-gate -based internal logic + * (multiplexing structure) for a multiplexer or LUT in Verilog codes + * This function will : + * 1. build a multiplexing structure by instanciating the branch circuits + * generated before + * 2. add intermediate buffers between multiplexing stages if specified. + *******************************************************************/ +static +void build_cmos_mux_module_tgate_multiplexing_structure(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const ModuleId& mux_module, + const CircuitModelId& circuit_model, + const vtr::vector& mux_module_input_nets, + const vtr::vector& mux_module_output_nets, + const vtr::vector& mux_module_mem_nets, + const vtr::vector& mux_module_mem_inv_nets, + const MuxGraph& mux_graph) { + /* Find the actual mux size */ + size_t mux_size = find_mux_num_datapath_inputs(circuit_lib, circuit_model, mux_graph.num_inputs()); + + /* Get the regular (non-mode-select) sram ports from the mux */ + std::vector mux_regular_sram_ports = find_circuit_regular_sram_ports(circuit_lib, circuit_model); + VTR_ASSERT(1 == mux_regular_sram_ports.size()); + + /* Cache Net ids for each level of the multiplexer */ + std::vector> module_nets_by_level; + module_nets_by_level.resize(mux_graph.num_node_levels()); + for (size_t level = 0; level < mux_graph.num_node_levels(); ++level) { + /* Print the internal wires located at this level */ + module_nets_by_level[level].resize(mux_graph.num_nodes_at_level(level)); + } + + /* Build the location map of intermediate buffers */ + std::vector inter_buffer_location_map = build_mux_intermediate_buffer_location_map(circuit_lib, circuit_model, mux_graph.num_node_levels()); + + /* Add all the branch modules and intermediate buffers */ + for (const auto& node : mux_graph.non_input_nodes()) { + /* Get the size of branch circuit + * Instanciate an branch circuit by the size (fan-in) of the node + */ + size_t branch_size = mux_graph.node_in_edges(node).size(); + + /* Instanciate the branch module which is a tgate-based module + */ + std::string branch_module_name= generate_mux_branch_subckt_name(circuit_lib, circuit_model, mux_size, branch_size, MUX_BASIS_MODULE_POSTFIX); + /* Get the moduleId for the submodule */ + ModuleId branch_module_id = module_manager.find_module(branch_module_name); + /* We must have one */ + VTR_ASSERT(ModuleId::INVALID() != branch_module_id); + + /* Find the instance id */ + size_t branch_instance_id = module_manager.num_instance(mux_module, branch_module_id); + /* Add the module to mux_module */ + module_manager.add_child_module(mux_module, branch_module_id); + + /* Get the node level and index in the current level */ + size_t output_node_level = mux_graph.node_level(node); + size_t output_node_index_at_level = mux_graph.node_index_at_level(node); + /* Set a name for the instance */ + std::string branch_instance_name = generate_mux_branch_instance_name(output_node_level, output_node_index_at_level, false); + module_manager.set_child_instance_name(mux_module, branch_module_id, branch_instance_id, branch_instance_name); + + /* Get the output port id of branch module */ + ModulePortId branch_module_output_port_id = module_manager.find_module_port(branch_module_id, std::string("out")); + BasicPort branch_module_output_port = module_manager.module_port(branch_module_id, branch_module_output_port_id); + + /* Add module nets to wire to next stage modules */ + ModuleNetId branch_net; + if (true == mux_graph.is_node_output(node)) { + /* This is an output node, we should use existing output nets */ + MuxOutputId output_id = mux_graph.output_id(node); + branch_net = mux_module_output_nets[output_id]; + } else { + VTR_ASSERT(false == mux_graph.is_node_output(node)); + branch_net = module_manager.create_module_net(mux_module); + } + module_manager.add_module_net_source(mux_module, branch_net, branch_module_id, branch_instance_id, branch_module_output_port_id, branch_module_output_port.get_lsb()); + + /* Record the module net id in the cache */ + module_nets_by_level[output_node_level][output_node_index_at_level] = branch_net; + + /* Wire the branch module memory ports to the nets of MUX memory ports */ + /* Get the mems in the branch circuits */ + std::vector mems; + for (const auto& edge : mux_graph.node_in_edges(node)) { + /* Get the mem control the edge */ + MuxMemId mem = mux_graph.find_edge_mem(edge); + /* Add the mem if it is not in the list */ + if (mems.end() == std::find(mems.begin(), mems.end(), mem)) { + mems.push_back(mem); + } + } + + /* Get mem/mem_inv ports of branch module */ + ModulePortId branch_module_mem_port_id = module_manager.find_module_port(branch_module_id, std::string("mem")); + BasicPort branch_module_mem_port = module_manager.module_port(branch_module_id, branch_module_mem_port_id); + ModulePortId branch_module_mem_inv_port_id = module_manager.find_module_port(branch_module_id, std::string("mem_inv")); + BasicPort branch_module_mem_inv_port = module_manager.module_port(branch_module_id, branch_module_mem_inv_port_id); + + /* Note that we do NOT care inverted edge-to-mem connection. + * It is handled in branch module generation!!! + */ + /* Connect mem/mem_inv to mem/mem_inv net one by one */ + for (size_t mem_id = 0; mem_id < mems.size(); ++mem_id) { + module_manager.add_module_net_sink(mux_module, mux_module_mem_nets[mems[mem_id]], branch_module_id, branch_instance_id, branch_module_mem_port_id, branch_module_mem_port.pins()[mem_id]); + module_manager.add_module_net_sink(mux_module, mux_module_mem_inv_nets[mems[mem_id]], branch_module_id, branch_instance_id, branch_module_mem_inv_port_id, branch_module_mem_inv_port.pins()[mem_id]); + } + + /* Wire the branch module inputs to the nets in previous stage */ + /* Get the input port id of branch module */ + ModulePortId branch_module_input_port_id = module_manager.find_module_port(branch_module_id, std::string("in")); + BasicPort branch_module_input_port = module_manager.module_port(branch_module_id, branch_module_input_port_id); + + /* Get the nodes which drive the root_node */ + std::vector input_nodes; + for (const auto& edge : mux_graph.node_in_edges(node)) { + /* Get the nodes drive the edge */ + for (const auto& src_node : mux_graph.edge_src_nodes(edge)) { + input_nodes.push_back(src_node); + } + } + /* Number of inputs should match the branch_input_size!!! */ + VTR_ASSERT(input_nodes.size() == branch_size); + /* build the link between input_node and branch circuit input_port[0] + */ + for (size_t node_id = 0; node_id < input_nodes.size(); ++node_id) { + /* Find the port info of each input node */ + size_t input_node_level = mux_graph.node_level(input_nodes[node_id]); + size_t input_node_index_at_level = mux_graph.node_index_at_level(input_nodes[node_id]); + /* For inputs of mux, the net id is reserved */ + if (true == mux_graph.is_node_input(input_nodes[node_id])) { + /* Get node input id */ + MuxInputId input_id = mux_graph.input_id(input_nodes[node_id]); + module_manager.add_module_net_sink(mux_module, mux_module_input_nets[input_id], branch_module_id, branch_instance_id, branch_module_input_port_id, branch_module_input_port.pins()[node_id]); + } else { + VTR_ASSERT (false == mux_graph.is_node_input(input_nodes[node_id])); + module_manager.add_module_net_sink(mux_module, module_nets_by_level[input_node_level][input_node_index_at_level], branch_module_id, branch_instance_id, branch_module_input_port_id, branch_module_input_port.pins()[node_id]); + } + } + + /* Identify if an intermediate buffer is needed */ + if (false == inter_buffer_location_map[output_node_level]) { + continue; + } + /* Add an intermediate buffer to mux_module if needed */ + if (true == mux_graph.is_node_output(node)) { + /* Output node does not need buffer addition here, it is handled outside this function */ + continue; + } + /* Now we need to add intermediate buffers by instanciating the modules */ + CircuitModelId buffer_model = circuit_lib.lut_intermediate_buffer_model(circuit_model); + /* We must have a valid model id */ + VTR_ASSERT(CircuitModelId::INVALID() != buffer_model); + + ModuleNetId buffer_net = add_inverter_buffer_child_module_and_nets(module_manager, mux_module, circuit_lib, buffer_model, branch_net); + + /* Record the module net id in the cache */ + module_nets_by_level[output_node_level][output_node_index_at_level] = buffer_net; + } +} + +/********************************************************************* + * This function will add nets and input buffers (if needed) + * to a mux module + * Module net represents the connections when there are no input buffers + * mux_input_net[0] + * | + * v +------------ + * mux_in[0] ----------->| + * | + * | + * | + * | Multiplexing + * mux_input_net[i] | Structure + * | | + * v | + * mux_in[0] ----------->| + * | + * + * + * Module net represents the connections when there are input buffers + * mux_input_net[0] + * | + * +-----------------+ v +------------ + * mux_in[0] ----->| input_buffer[0] |-----> | + * +-----------------+ | + * | + * ... | + * | Multiplexing + * mux_input_net[i] | Structure + * | | + * +-----------------+ v | + * mux_in[0] ----->| input_buffer[0] |-----> | + * +-----------------+ | + *********************************************************************/ +static +vtr::vector build_mux_module_input_buffers(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const ModuleId& mux_module, + const CircuitModelId& mux_model, + const MuxGraph& mux_graph) { + vtr::vector mux_input_nets(mux_graph.num_inputs(), ModuleNetId::INVALID()); + + /* Get the input ports from the mux */ + std::vector mux_input_ports = circuit_lib.model_ports_by_type(mux_model, CIRCUIT_MODEL_PORT_INPUT, true); + /* We should have only 1 input port! */ + VTR_ASSERT(1 == mux_input_ports.size()); + + /* Get the input port from MUX module */ + ModulePortId module_input_port_id = module_manager.find_module_port(mux_module, circuit_lib.port_prefix(mux_input_ports[0])); + VTR_ASSERT(ModulePortId::INVALID() != module_input_port_id); + /* Get the port from module */ + BasicPort module_input_port = module_manager.module_port(mux_module, module_input_port_id); + + /* Iterate over all the inputs in the MUX graph */ + for (const auto& input_node : mux_graph.inputs()) { + /* Fetch fundamental information from MUX graph w.r.t. the input node */ + MuxInputId input_index = mux_graph.input_id(input_node); + VTR_ASSERT(MuxInputId::INVALID() != input_index); + + /* For last input: + * Add a constant value to the last input, if this MUX needs a constant input + */ + if ( (MuxInputId(mux_graph.num_inputs() - 1) == mux_graph.input_id(input_node)) + && (true == circuit_lib.mux_add_const_input(mux_model)) ) { + /* Get the constant input value */ + size_t const_value = circuit_lib.mux_const_input_value(mux_model); + VTR_ASSERT( (0 == const_value) || (1 == const_value) ); + /* Instanciate a VDD module (default module) + * and build a net between VDD and the MUX input + */ + /* Get the moduleId for the buffer module */ + ModuleId const_val_module_id = module_manager.find_module(generate_const_value_module_name(const_value)); + /* We must have one */ + VTR_ASSERT(ModuleId::INVALID() != const_val_module_id); + size_t const_val_instance = module_manager.num_instance(mux_module, const_val_module_id); + module_manager.add_child_module(mux_module, const_val_module_id); + ModulePortId const_port_id = module_manager.find_module_port(const_val_module_id, generate_const_value_module_output_port_name(const_value)); + + ModuleNetId input_net = module_manager.create_module_net(mux_module); + module_manager.add_module_net_source(mux_module, input_net, const_val_module_id, const_val_instance, const_port_id, 0); + mux_input_nets[input_index] = input_net; + continue; + } + + /* When we do not need any buffer, create a net for the input directly */ + if (false == circuit_lib.is_input_buffered(mux_model)) { + ModuleNetId input_net = module_manager.create_module_net(mux_module); + module_manager.add_module_net_source(mux_module, input_net, mux_module, 0, module_input_port_id, size_t(input_index)); + mux_input_nets[input_index] = input_net; + continue; + } + + /* Now we need to add intermediate buffers by instanciating the modules */ + CircuitModelId buffer_model = circuit_lib.input_buffer_model(mux_model); + /* We must have a valid model id */ + VTR_ASSERT(CircuitModelId::INVALID() != buffer_model); + + /* Connect the module net from branch output to buffer input */ + ModuleNetId buffer_net = module_manager.create_module_net(mux_module); + module_manager.add_module_net_source(mux_module, buffer_net, mux_module, 0, module_input_port_id, size_t(input_index)); + + /* Create a module net which sources from buffer output */ + ModuleNetId input_net = add_inverter_buffer_child_module_and_nets(module_manager, mux_module, circuit_lib, buffer_model, buffer_net); + mux_input_nets[input_index] = input_net; + } + + return mux_input_nets; +} + +/********************************************************************* + * This function will add nets and input buffers (if needed) + * to a mux module + * Module net represents the connections when there are no output buffers + * + * mux_output_net[0] + * ------------+ | + * | v + * |--------> mux_output[0] + * | + * | + * Multiplexer | ... + * Strcuture | + * |--------> mux_output[i] + * | ^ + * | | + * ------------+ mux_output_net[i] + * + * Module net represents the connections when there are output buffers + * + * mux_output_net[0] + * ------------+ | + * | | + * | v +------------------+ + * |------->| output_buffer[0] |------> mux_output[0] + * | +------------------+ + * | + * Multiplexer | ... + * Strcuture | + * | +------------------+ + * |------->| output_buffer[i] |------> mux_output[i] + * | ^ +------------------+ + * | | + * | | + * ------------+ mux_output_net[i] + + * + *********************************************************************/ +static +vtr::vector build_mux_module_output_buffers(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const ModuleId& mux_module, + const CircuitModelId& mux_model, + const MuxGraph& mux_graph) { + + /* Create module nets for output ports */ + vtr::vector mux_output_nets(mux_graph.num_outputs(), ModuleNetId::INVALID()); + + /* Get the output ports from the mux */ + std::vector mux_output_ports = circuit_lib.model_ports_by_type(mux_model, CIRCUIT_MODEL_PORT_OUTPUT, true); + + /* Iterate over all the outputs in the MUX module */ + for (const auto& output_port : mux_output_ports) { + /* Get the output port from MUX module */ + ModulePortId module_output_port_id = module_manager.find_module_port(mux_module, circuit_lib.port_prefix(output_port)); + VTR_ASSERT(ModulePortId::INVALID() != module_output_port_id); + /* Get the port from module */ + BasicPort module_output_port = module_manager.module_port(mux_module, module_output_port_id); + + /* Iterate over each pin of the output port */ + for (const size_t& pin : circuit_lib.pins(output_port)) { + /* Fetch fundamental information from MUX graph w.r.t. the input node */ + /* Deposite the last level of the graph, which is a default value */ + size_t output_node_level = mux_graph.num_node_levels() - 1; + /* If there is a fracturable level specified for the output, we find the exact level */ + if (size_t(-1) != circuit_lib.port_lut_frac_level(output_port)) { + output_node_level = circuit_lib.port_lut_frac_level(output_port); + } + /* Deposite a zero, which is a default value */ + size_t output_node_index_at_level = 0; + /* If there are output masks, we find the node_index */ + if (!circuit_lib.port_lut_output_mask(output_port).empty()) { + output_node_index_at_level = circuit_lib.port_lut_output_mask(output_port).at(pin); + } + /* Double check the node exists in the Mux Graph */ + MuxNodeId node_id = mux_graph.node_id(output_node_level, output_node_index_at_level); + VTR_ASSERT(MuxNodeId::INVALID() != node_id); + MuxOutputId output_index = mux_graph.output_id(node_id); + + /* Create the port information of the module output at the given pin range, which is the output of buffer instance */ + BasicPort instance_output_port(module_output_port.get_name(), pin, pin); + + /* If the output is not supposed to be buffered, create a net for the input directly */ + if (false == circuit_lib.is_output_buffered(mux_model)) { + ModuleNetId output_net = module_manager.create_module_net(mux_module); + module_manager.add_module_net_sink(mux_module, output_net, mux_module, 0, module_output_port_id, pin); + mux_output_nets[output_index] = output_net; + continue; /* Finish here */ + } + + /* Reach here, we need a buffer, create a port-to-port map and output the buffer instance */ + /* Now we need to add intermediate buffers by instanciating the modules */ + CircuitModelId buffer_model = circuit_lib.output_buffer_model(mux_model); + /* We must have a valid model id */ + VTR_ASSERT(CircuitModelId::INVALID() != buffer_model); + + /* Create a module net which sinks at buffer input */ + ModuleNetId input_net = module_manager.create_module_net(mux_module); + ModuleNetId output_net = add_inverter_buffer_child_module_and_nets(module_manager, mux_module, circuit_lib, buffer_model, input_net); + module_manager.add_module_net_sink(mux_module, output_net, mux_module, 0, module_output_port_id, pin); + mux_output_nets[output_index] = input_net; + } + } + + return mux_output_nets; +} + +/********************************************************************* + * This function will + * 1. Build local encoders for a MUX module (if specified) + * 2. Build nets between memory ports of a MUX module and branch circuits + * This happens when local encoders are not needed + * + * MUX module + * +--------------------- + * | mux_mem_nets/mux_mem_inv_nets + * | | + * | v +--------- + * mem-+-------->| + * | | Branch Module + * | | + * + * 3. Build nets between local encoders and memory ports of a MUX module + * This happens when local encoders are needed + * 4. Build nets between local encoders and branch circuits + * This happens when local encoders are needed + * + * MUX module + * +--------------------- + * | + * | +-------+ mux_mem_nets/mux_mem_inv_nets + * | | | | + * mem--+------>| | v +--------- + * | | Local |-------->| + * | |Encoder| | Branch + * | | | | Module + * | | | | + * | | | | + * + *********************************************************************/ +static +void build_mux_module_local_encoders_and_memory_nets(ModuleManager& module_manager, + const ModuleId& mux_module, + const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const std::vector& mux_sram_ports, + const MuxGraph& mux_graph, + vtr::vector& mux_mem_nets, + vtr::vector& mux_mem_inv_nets) { + + /* Create nets here, and we will configure the net source later */ + for (size_t mem = 0; mem < mux_graph.num_memory_bits(); ++mem) { + ModuleNetId mem_net = module_manager.create_module_net(mux_module); + mux_mem_nets.push_back(mem_net); + ModuleNetId mem_inv_net = module_manager.create_module_net(mux_module); + mux_mem_inv_nets.push_back(mem_inv_net); + } + + if (false == circuit_lib.mux_use_local_encoder(mux_model)) { + /* Add mem and mem_inv nets here */ + size_t mem_net_cnt = 0; + for (const auto& port : mux_sram_ports) { + ModulePortId mem_port_id = module_manager.find_module_port(mux_module, circuit_lib.port_prefix(port)); + BasicPort mem_port = module_manager.module_port(mux_module, mem_port_id); + for (const size_t& pin : mem_port.pins()) { + MuxMemId mem_id = MuxMemId(mem_net_cnt); + /* Set the module net source */ + module_manager.add_module_net_source(mux_module, mux_mem_nets[mem_id], mux_module, 0, mem_port_id, pin); + /* Update counter */ + mem_net_cnt++; + } + } + VTR_ASSERT(mem_net_cnt == mux_graph.num_memory_bits()); + + /* Add mem and mem_inv nets here */ + size_t mem_inv_net_cnt = 0; + for (const auto& port : mux_sram_ports) { + ModulePortId mem_inv_port_id = module_manager.find_module_port(mux_module, std::string(circuit_lib.port_prefix(port) + "_inv")); + BasicPort mem_inv_port = module_manager.module_port(mux_module, mem_inv_port_id); + for (const size_t& pin : mem_inv_port.pins()) { + MuxMemId mem_id = MuxMemId(mem_inv_net_cnt); + /* Set the module net source */ + module_manager.add_module_net_source(mux_module, mux_mem_inv_nets[mem_id], mux_module, 0, mem_inv_port_id, pin); + /* Update counter */ + mem_inv_net_cnt++; + } + } + VTR_ASSERT(mem_inv_net_cnt == mux_graph.num_memory_bits()); + return; /* Finish here if local encoders are not required */ + } + + /* Add local decoder instance here */ + VTR_ASSERT(true == circuit_lib.mux_use_local_encoder(mux_model)); + BasicPort decoder_data_port(generate_mux_local_decoder_data_port_name(), mux_graph.num_memory_bits()); + BasicPort decoder_data_inv_port(generate_mux_local_decoder_data_inv_port_name(), mux_graph.num_memory_bits()); + + /* Local port to record the LSB and MSB of each level, here, we deposite (0, 0) */ + ModulePortId mux_module_sram_port_id = module_manager.find_module_port(mux_module, circuit_lib.port_prefix(mux_sram_ports[0])); + ModulePortId mux_module_sram_inv_port_id = module_manager.find_module_port(mux_module, circuit_lib.port_prefix(mux_sram_ports[0]) + "_inv"); + BasicPort lvl_addr_port(circuit_lib.port_prefix(mux_sram_ports[0]), 0); + BasicPort lvl_data_port(decoder_data_port.get_name(), 0); + BasicPort lvl_data_inv_port(decoder_data_inv_port.get_name(), 0); + + /* Counter for mem index */ + size_t mem_net_cnt = 0; + size_t mem_inv_net_cnt = 0; + + for (const auto& lvl : mux_graph.levels()) { + size_t addr_size = find_mux_local_decoder_addr_size(mux_graph.num_memory_bits_at_level(lvl)); + size_t data_size = mux_graph.num_memory_bits_at_level(lvl); + /* Update the LSB and MSB of addr and data port for the current level */ + lvl_addr_port.rotate(addr_size); + lvl_data_port.rotate(data_size); + lvl_data_inv_port.rotate(data_size); + + /* Exception: if the data size is one, we just need wires! */ + if (1 == data_size) { + for (size_t pin_id = 0; pin_id < lvl_addr_port.pins().size(); ++pin_id) { + MuxMemId mem_id = MuxMemId(mem_net_cnt); + /* Set the module net source */ + module_manager.add_module_net_source(mux_module, mux_mem_nets[mem_id], mux_module, 0, mux_module_sram_port_id, lvl_addr_port.pins()[pin_id]); + /* Update counter */ + mem_net_cnt++; + + MuxMemId mem_inv_id = MuxMemId(mem_inv_net_cnt); + /* Set the module net source */ + module_manager.add_module_net_source(mux_module, mux_mem_inv_nets[mem_inv_id], mux_module, 0, mux_module_sram_inv_port_id, lvl_addr_port.pins()[pin_id]); + /* Update counter */ + mem_inv_net_cnt++; + } + continue; + } + + std::string decoder_module_name = generate_mux_local_decoder_subckt_name(addr_size, data_size); + ModuleId decoder_module = module_manager.find_module(decoder_module_name); + VTR_ASSERT(ModuleId::INVALID() != decoder_module); + + size_t decoder_instance = module_manager.num_instance(mux_module, decoder_module); + module_manager.add_child_module(mux_module, decoder_module); + + /* Add module nets to connect sram ports of MUX to address port */ + ModulePortId decoder_module_addr_port_id = module_manager.find_module_port(decoder_module, generate_mux_local_decoder_addr_port_name()); + BasicPort decoder_module_addr_port = module_manager.module_port(decoder_module, decoder_module_addr_port_id); + VTR_ASSERT(decoder_module_addr_port.get_width() == lvl_addr_port.get_width()); + + /* Build pin-to-pin net connection */ + for (size_t pin_id = 0; pin_id < lvl_addr_port.pins().size(); ++pin_id) { + ModuleNetId net = module_manager.create_module_net(mux_module); + module_manager.add_module_net_source(mux_module, net, mux_module, 0, mux_module_sram_port_id, lvl_addr_port.pins()[pin_id]); + module_manager.add_module_net_sink(mux_module, net, decoder_module, decoder_instance, decoder_module_addr_port_id, decoder_module_addr_port.pins()[pin_id]); + } + + /* Add module nets to connect data port to MUX mem ports */ + ModulePortId decoder_module_data_port_id = module_manager.find_module_port(decoder_module, generate_mux_local_decoder_data_port_name()); + BasicPort decoder_module_data_port = module_manager.module_port(decoder_module, decoder_module_data_port_id); + + /* Build pin-to-pin net connection */ + for (const size_t& pin : decoder_module_data_port.pins()) { + ModuleNetId net = mux_mem_nets[MuxMemId(mem_net_cnt)]; + module_manager.add_module_net_source(mux_module, net, decoder_module, decoder_instance, decoder_module_data_port_id, pin); + /* Add the module nets to mux_mem_nets cache */ + mem_net_cnt++; + } + + ModulePortId decoder_module_data_inv_port_id = module_manager.find_module_port(decoder_module, generate_mux_local_decoder_data_inv_port_name()); + BasicPort decoder_module_data_inv_port = module_manager.module_port(decoder_module, decoder_module_data_inv_port_id); + + /* Build pin-to-pin net connection */ + for (const size_t& pin : decoder_module_data_inv_port.pins()) { + ModuleNetId net = mux_mem_inv_nets[MuxMemId(mem_inv_net_cnt)]; + module_manager.add_module_net_source(mux_module, net, decoder_module, decoder_instance, decoder_module_data_inv_port_id, pin); + /* Add the module nets to mux_mem_inv_nets cache */ + mem_inv_net_cnt++; + } + } + VTR_ASSERT(mem_net_cnt == mux_graph.num_memory_bits()); + VTR_ASSERT(mem_inv_net_cnt == mux_graph.num_memory_bits()); +} + +/********************************************************************* + * Generate module of a CMOS multiplexer with the given size + * The module will consist of three parts: + * 1. instances of the branch circuits of multiplexers which are generated before + * This builds up the multiplexing structure + * 2. Input buffers/inverters + * 3. Output buffers/inverters + *********************************************************************/ +static +void build_cmos_mux_module(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const std::string& module_name, + const MuxGraph& mux_graph) { + /* Get the global ports required by MUX (and any submodules) */ + std::vector mux_global_ports = circuit_lib.model_global_ports_by_type(mux_model, CIRCUIT_MODEL_PORT_INPUT, true, true); + /* Get the input ports from the mux */ + std::vector mux_input_ports = circuit_lib.model_ports_by_type(mux_model, CIRCUIT_MODEL_PORT_INPUT, true); + /* Get the output ports from the mux */ + std::vector mux_output_ports = circuit_lib.model_ports_by_type(mux_model, CIRCUIT_MODEL_PORT_OUTPUT, true); + /* Get the sram ports from the mux + * Multiplexing structure does not mode_sram_ports, they are handled in LUT modules + * Here we just bypass it. + */ + std::vector mux_sram_ports = find_circuit_regular_sram_ports(circuit_lib, mux_model); + + /* Generate the Verilog netlist according to the mux_graph */ + /* Find out the number of data-path inputs */ + size_t num_inputs = find_mux_num_datapath_inputs(circuit_lib, mux_model, mux_graph.num_inputs()); + /* Find out the number of outputs */ + size_t num_outputs = mux_graph.num_outputs(); + /* Find out the number of memory bits */ + size_t num_mems = mux_graph.num_memory_bits(); + + /* The size of of memory ports depend on + * if a local encoder is used for the mux or not + * Multiplexer local encoders are applied to memory bits at each stage + */ + if (true == circuit_lib.mux_use_local_encoder(mux_model)) { + num_mems = 0; + for (const auto& lvl : mux_graph.levels()) { + size_t data_size = mux_graph.num_memory_bits_at_level(lvl); + num_mems += find_mux_local_decoder_addr_size(data_size); + } + } + + /* Check codes to ensure the port of Verilog netlists will match */ + /* MUX graph must have only 1 output */ + VTR_ASSERT(1 == mux_input_ports.size()); + /* A quick check on the model ports */ + if ((CIRCUIT_MODEL_MUX == circuit_lib.model_type(mux_model)) + || ((CIRCUIT_MODEL_LUT == circuit_lib.model_type(mux_model)) + && (false == circuit_lib.is_lut_fracturable(mux_model))) ) { + VTR_ASSERT(1 == mux_output_ports.size()); + VTR_ASSERT(1 == circuit_lib.port_size(mux_output_ports[0])); + } else { + VTR_ASSERT_SAFE( (CIRCUIT_MODEL_LUT == circuit_lib.model_type(mux_model)) + && (true == circuit_lib.is_lut_fracturable(mux_model)) ); + for (const auto& port : mux_output_ports) { + VTR_ASSERT(0 < circuit_lib.port_size(port)); + } + } + + /* Create a Verilog Module based on the circuit model, and add to module manager */ + ModuleId mux_module = module_manager.add_module(module_name); + VTR_ASSERT(ModuleId::INVALID() != mux_module); + /* Add module ports */ + /* Add each input port + * Treat MUX and LUT differently + * 1. MUXes: we do not have a specific input/output sizes, it is inferred by architecture + * 2. LUTes: we do have specific input/output sizes, + * but the inputs of MUXes are the SRAM ports of LUTs + * and the SRAM ports of MUXes are the inputs of LUTs + */ + size_t input_port_cnt = 0; + for (const auto& port : mux_input_ports) { + BasicPort input_port(circuit_lib.port_prefix(port), num_inputs); + module_manager.add_port(mux_module, input_port, ModuleManager::MODULE_INPUT_PORT); + /* Update counter */ + input_port_cnt++; + } + /* Double check: We should have only 1 input port generated here! */ + VTR_ASSERT(1 == input_port_cnt); + + /* Add input buffers and update module nets for inputs */ + vtr::vector mux_input_nets = build_mux_module_input_buffers(module_manager, circuit_lib, mux_module, mux_model, mux_graph); + + for (const auto& port : mux_output_ports) { + BasicPort output_port(circuit_lib.port_prefix(port), num_outputs); + if (CIRCUIT_MODEL_LUT == circuit_lib.model_type(mux_model)) { + output_port.set_width(circuit_lib.port_size(port)); + } + module_manager.add_port(mux_module, output_port, ModuleManager::MODULE_OUTPUT_PORT); + } + + /* TODO: Add output buffers and update module nets for outputs */ + vtr::vector mux_output_nets = build_mux_module_output_buffers(module_manager, circuit_lib, mux_module, mux_model, mux_graph); + + size_t sram_port_cnt = 0; + for (const auto& port : mux_sram_ports) { + BasicPort mem_port(circuit_lib.port_prefix(port), num_mems); + module_manager.add_port(mux_module, mem_port, ModuleManager::MODULE_INPUT_PORT); + BasicPort mem_inv_port(std::string(circuit_lib.port_prefix(port) + "_inv"), num_mems); + module_manager.add_port(mux_module, mem_inv_port, ModuleManager::MODULE_INPUT_PORT); + /* Update counter */ + sram_port_cnt++; + } + VTR_ASSERT(1 == sram_port_cnt); + + /* Create module nets for mem and mem_inv ports */ + vtr::vector mux_mem_nets; + vtr::vector mux_mem_inv_nets; + + build_mux_module_local_encoders_and_memory_nets(module_manager, mux_module, + circuit_lib, mux_model, mux_sram_ports, + mux_graph, + mux_mem_nets, mux_mem_inv_nets); + + /* Print the internal logic in Verilog codes */ + /* Print the Multiplexing structure in Verilog codes + * Separated generation strategy on using standard cell MUX2 or TGATE, + * 1. MUX2 has a fixed port map: input_port[0] and input_port[1] is the data_path input + * 2. Branch TGATE-based module has a fixed port name + * TODO: the naming could be more flexible? + */ + /* Get the tgate model */ + CircuitModelId tgate_model = circuit_lib.pass_gate_logic_model(mux_model); + /* Instanciate the branch module: + * Case 1: the branch module is a standard cell MUX2 + * Case 2: the branch module is a tgate-based module + */ + std::string branch_module_name; + if (CIRCUIT_MODEL_GATE == circuit_lib.model_type(tgate_model)) { + VTR_ASSERT(CIRCUIT_MODEL_GATE_MUX2 == circuit_lib.gate_type(tgate_model)); + build_cmos_mux_module_mux2_multiplexing_structure(module_manager, circuit_lib, mux_module, mux_model, tgate_model, mux_input_nets, mux_output_nets, mux_mem_nets, mux_graph); + } else { + VTR_ASSERT(CIRCUIT_MODEL_PASSGATE == circuit_lib.model_type(tgate_model)); + build_cmos_mux_module_tgate_multiplexing_structure(module_manager, circuit_lib, mux_module, mux_model, mux_input_nets, mux_output_nets, mux_mem_nets, mux_mem_inv_nets, mux_graph); + } + + /* Add global ports to the pb_module: + * This is a much easier job after adding sub modules (instances), + * we just need to find all the global ports from the child modules and build a list of it + */ + add_module_global_ports_from_child_modules(module_manager, mux_module); +} + +/********************************************************************* + * Generate a module of a RRAM-based multiplexer with the given size + * The module will consist of three parts: + * 1. instances of the branch circuits of multiplexers which are generated before + * This builds up the 4T1R-based multiplexing structure + * + * BLB WL + * | | ... + * v v + * +--------+ + * in[0]-->| | BLB WL + * ...| Branch |-----+ | | + * in -->| 0 | | v v + * [N-1] +--------+ | +--------+ + * ... -->| | + * BLBs WLs ...| Branch | + * | | ... -->| X | + * v v +--------+ + * +--------+ | + * -->| | | + * ...| Branch |----+ + * -->| i | + * +--------+ + * + * 2. Input buffers/inverters + * 3. Output buffers/inverters + *********************************************************************/ +static +void build_rram_mux_module(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const std::string& module_name, + const MuxGraph& mux_graph) { + /* Error out for the conditions where we are not yet supported! */ + if (CIRCUIT_MODEL_LUT == circuit_lib.model_type(circuit_model)) { + /* RRAM LUT is not supported now... */ + VTR_LOG_ERROR("RRAM-based LUT is not supported for circuit model '%s')!\n", + circuit_lib.model_name(circuit_model).c_str()); + exit(1); + } + + /* Get the global ports required by MUX (and any submodules) */ + std::vector mux_global_ports = circuit_lib.model_global_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_INPUT, true, true); + /* Get the input ports from the mux */ + std::vector mux_input_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_INPUT, true); + /* Get the output ports from the mux */ + std::vector mux_output_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_OUTPUT, true); + /* Get the BL and WL ports from the mux */ + std::vector mux_blb_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_BLB, true); + std::vector mux_wl_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_WL, true); + + /* Generate the Verilog netlist according to the mux_graph */ + /* Find out the number of data-path inputs */ + size_t num_inputs = find_mux_num_datapath_inputs(circuit_lib, circuit_model, mux_graph.num_inputs()); + /* Find out the number of outputs */ + size_t num_outputs = mux_graph.num_outputs(); + /* Find out the number of memory bits */ + size_t num_mems = mux_graph.num_memory_bits(); + + /* Check codes to ensure the port of Verilog netlists will match */ + /* MUX graph must have only 1 input and 1 BLB and 1 WL port */ + VTR_ASSERT(1 == mux_input_ports.size()); + VTR_ASSERT(1 == mux_blb_ports.size()); + VTR_ASSERT(1 == mux_wl_ports.size()); + + /* Create a Verilog Module based on the circuit model, and add to module manager */ + ModuleId module_id = module_manager.add_module(module_name); + VTR_ASSERT(ModuleId::INVALID() != module_id); + /* Add module ports */ + /* Add each global port */ + for (const auto& port : mux_global_ports) { + /* Configure each global port */ + BasicPort global_port(circuit_lib.port_prefix(port), circuit_lib.port_size(port)); + module_manager.add_port(module_id, global_port, ModuleManager::MODULE_GLOBAL_PORT); + } + /* Add each input port */ + size_t input_port_cnt = 0; + for (const auto& port : mux_input_ports) { + BasicPort input_port(circuit_lib.port_prefix(port), num_inputs); + module_manager.add_port(module_id, input_port, ModuleManager::MODULE_INPUT_PORT); + /* Update counter */ + input_port_cnt++; + } + /* Double check: We should have only 1 input port generated here! */ + VTR_ASSERT(1 == input_port_cnt); + + for (const auto& port : mux_output_ports) { + BasicPort output_port(circuit_lib.port_prefix(port), num_outputs); + if (CIRCUIT_MODEL_LUT == circuit_lib.model_type(circuit_model)) { + output_port.set_width(circuit_lib.port_size(port)); + } + module_manager.add_port(module_id, output_port, ModuleManager::MODULE_OUTPUT_PORT); + } + + /* BLB port */ + for (const auto& port : mux_blb_ports) { + /* IMPORTANT: RRAM-based MUX has an additional BLB pin per level + * So, the actual port width of BLB should be added by the number of levels of the MUX graph + */ + BasicPort blb_port(circuit_lib.port_prefix(port), num_mems + mux_graph.num_levels()); + module_manager.add_port(module_id, blb_port, ModuleManager::MODULE_INPUT_PORT); + } + + /* WL port */ + for (const auto& port : mux_wl_ports) { + /* IMPORTANT: RRAM-based MUX has an additional WL pin per level + * So, the actual port width of WL should be added by the number of levels of the MUX graph + */ + BasicPort wl_port(circuit_lib.port_prefix(port), num_mems + mux_graph.num_levels()); + module_manager.add_port(module_id, wl_port, ModuleManager::MODULE_INPUT_PORT); + } + + /* TODO: Add the input and output buffers in Verilog codes */ + + /* TODO: Print the internal logic in Verilog codes */ +} + +/*********************************************** + * Generate Verilog codes modeling a multiplexer + * with the given graph-level description + **********************************************/ +static +void build_mux_module(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const MuxGraph& mux_graph) { + std::string module_name = generate_mux_subckt_name(circuit_lib, circuit_model, + find_mux_num_datapath_inputs(circuit_lib, circuit_model, mux_graph.num_inputs()), + std::string("")); + + /* Multiplexers built with different technology is in different organization */ + switch (circuit_lib.design_tech_type(circuit_model)) { + case CIRCUIT_MODEL_DESIGN_CMOS: + /* SRAM-based Multiplexer Verilog module generation */ + build_cmos_mux_module(module_manager, circuit_lib, circuit_model, module_name, mux_graph); + break; + case CIRCUIT_MODEL_DESIGN_RRAM: + /* TODO: RRAM-based Multiplexer Verilog module generation */ + build_rram_mux_module(module_manager, circuit_lib, circuit_model, module_name, mux_graph); + break; + default: + VTR_LOG_ERROR("Invalid design technology of multiplexer '%s'\n", + circuit_lib.model_name(circuit_model).c_str()); + exit(1); + } +} + +/*********************************************** + * Generate Verilog modules for all the unique + * multiplexers in the FPGA device + **********************************************/ +void build_mux_modules(ModuleManager& module_manager, + const MuxLibrary& mux_lib, + const CircuitLibrary& circuit_lib) { + vtr::ScopedStartFinishTimer timer("Building multiplexer modules"); + + /* Generate basis sub-circuit for unique branches shared by the multiplexers */ + for (auto mux : mux_lib.muxes()) { + const MuxGraph& mux_graph = mux_lib.mux_graph(mux); + CircuitModelId mux_circuit_model = mux_lib.mux_circuit_model(mux); + /* Create a mux graph for the branch circuit */ + std::vector branch_mux_graphs = mux_graph.build_mux_branch_graphs(); + /* Create branch circuits, which are N:1 one-level or 2:1 tree-like MUXes */ + for (auto branch_mux_graph : branch_mux_graphs) { + build_mux_branch_module(module_manager, circuit_lib, mux_circuit_model, + find_mux_num_datapath_inputs(circuit_lib, mux_circuit_model, mux_graph.num_inputs()), + branch_mux_graph); + } + } + + /* Generate unique Verilog modules for the multiplexers */ + for (auto mux : mux_lib.muxes()) { + const MuxGraph& mux_graph = mux_lib.mux_graph(mux); + CircuitModelId mux_circuit_model = mux_lib.mux_circuit_model(mux); + /* Create MUX circuits */ + build_mux_module(module_manager, circuit_lib, mux_circuit_model, mux_graph); + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fabric/build_mux_modules.h b/openfpga/src/fabric/build_mux_modules.h new file mode 100644 index 000000000..8326eeb74 --- /dev/null +++ b/openfpga/src/fabric/build_mux_modules.h @@ -0,0 +1,24 @@ +#ifndef BUILD_MUX_MODULES_H +#define BUILD_MUX_MODULES_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "circuit_library.h" +#include "mux_library.h" +#include "module_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void build_mux_modules(ModuleManager& module_manager, + const MuxLibrary& mux_lib, + const CircuitLibrary& circuit_lib); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml index b517fa24b..2a4bb1b02 100644 --- a/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml @@ -149,7 +149,7 @@ - + From e842150cc5dfdf929d1c2bc5be404d62ef0556fd Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 12 Feb 2020 19:52:41 -0700 Subject: [PATCH 127/645] add lut module builder --- openfpga/src/fabric/build_device_module.cpp | 4 +- openfpga/src/fabric/build_lut_modules.cpp | 406 ++++++++++++++++++ openfpga/src/fabric/build_lut_modules.h | 22 + .../k6_frac_N10_40nm_openfpga.xml | 2 +- 4 files changed, 431 insertions(+), 3 deletions(-) create mode 100644 openfpga/src/fabric/build_lut_modules.cpp create mode 100644 openfpga/src/fabric/build_lut_modules.h diff --git a/openfpga/src/fabric/build_device_module.cpp b/openfpga/src/fabric/build_device_module.cpp index a322422df..5e8b090f2 100644 --- a/openfpga/src/fabric/build_device_module.cpp +++ b/openfpga/src/fabric/build_device_module.cpp @@ -11,7 +11,7 @@ #include "build_essential_modules.h" #include "build_decoder_modules.h" #include "build_mux_modules.h" -//#include "build_lut_modules.h" +#include "build_lut_modules.h" //#include "build_wire_modules.h" //#include "build_memory_modules.h" //#include "build_grid_modules.h" @@ -56,7 +56,7 @@ ModuleManager build_device_module_graph(const DeviceContext& vpr_device_ctx, build_mux_modules(module_manager, openfpga_ctx.mux_lib(), openfpga_ctx.arch().circuit_lib); /* Build LUT modules */ - //build_lut_modules(module_manager, arch.spice->circuit_lib); + build_lut_modules(module_manager, openfpga_ctx.arch().circuit_lib); /* Build wire modules */ //build_wire_modules(module_manager, arch.spice->circuit_lib); diff --git a/openfpga/src/fabric/build_lut_modules.cpp b/openfpga/src/fabric/build_lut_modules.cpp new file mode 100644 index 000000000..c799fe75a --- /dev/null +++ b/openfpga/src/fabric/build_lut_modules.cpp @@ -0,0 +1,406 @@ +/******************************************************************** + * This file include functions that create modules for + * the Look-Up Tables (LUTs) + ********************************************************************/ +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" +#include "vtr_time.h" + +#include "openfpga_naming.h" +#include "circuit_library_utils.h" +#include "module_manager.h" +#include "module_manager_utils.h" + +#include "build_module_graph_utils.h" +#include "build_lut_modules.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Build a module for a LUT circuit model + * This function supports both single-output and fracturable LUTs + * The module will be organized in a connected graph of the following instances: + * 1. Multiplexer used inside LUT + * 2. Input buffers + * 3. Input inverters + * 4. Output buffers. + * 6. AND/OR gates to tri-state LUT inputs + ********************************************************************/ +static +void build_lut_module(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const CircuitModelId& lut_model) { + /* Get the global ports required by LUT + * Note that this function will only add global ports from LUT circuit model definition itself + * We should NOT go recursively here. + * The global ports of sub module will be handled by another function !!! + * add_module_global_ports_from_child_modules(module_manager, lut_module); + */ + std::vector lut_global_ports = circuit_lib.model_global_ports_by_type(lut_model, CIRCUIT_MODEL_PORT_INPUT, false, true); + /* Get the input ports from the mux */ + std::vector lut_input_ports = circuit_lib.model_ports_by_type(lut_model, CIRCUIT_MODEL_PORT_INPUT, true); + /* Get the output ports from the mux */ + std::vector lut_output_ports = circuit_lib.model_ports_by_type(lut_model, CIRCUIT_MODEL_PORT_OUTPUT, true); + + /* Classify SRAM ports into two categories: regular (not for mode select) and mode-select */ + std::vector lut_regular_sram_ports = find_circuit_regular_sram_ports(circuit_lib, lut_model); + std::vector lut_mode_select_sram_ports = find_circuit_mode_select_sram_ports(circuit_lib, lut_model); + + /*********************************************** + * Model Port Sanity Check + ***********************************************/ + /* Make sure that the number of ports and sizes of ports are what we want */ + if (false == circuit_lib.is_lut_fracturable(lut_model)) { + /* Single-output LUTs: + * We should have only 1 input port, 1 output port and 1 SRAM port + */ + VTR_ASSERT (1 == lut_input_ports.size()); + VTR_ASSERT (1 == lut_output_ports.size()); + VTR_ASSERT (1 == lut_regular_sram_ports.size()); + VTR_ASSERT (0 == lut_mode_select_sram_ports.size()); + } else { + VTR_ASSERT (true == circuit_lib.is_lut_fracturable(lut_model)); + /* Fracturable LUT: + * We should have only 1 input port, a few output ports (fracturable outputs) + * and two SRAM ports + */ + VTR_ASSERT (1 == lut_input_ports.size()); + VTR_ASSERT (1 <= lut_output_ports.size()); + VTR_ASSERT (1 == lut_regular_sram_ports.size()); + VTR_ASSERT (1 == lut_mode_select_sram_ports.size()); + } + + /*********************************************** + * Module Port addition + ***********************************************/ + /* Create a Verilog Module based on the circuit model, and add to module manager */ + ModuleId lut_module = module_manager.add_module(circuit_lib.model_name(lut_model)); + VTR_ASSERT(true == module_manager.valid_module_id(lut_module)); + /* Add module ports */ + /* Add each global port */ + for (const auto& port : lut_global_ports) { + /* Configure each global port */ + BasicPort global_port(circuit_lib.port_prefix(port), circuit_lib.port_size(port)); + module_manager.add_port(lut_module, global_port, ModuleManager::MODULE_GLOBAL_PORT); + } + /* Add each input port */ + for (const auto& port : lut_input_ports) { + BasicPort input_port(circuit_lib.port_prefix(port), circuit_lib.port_size(port)); + module_manager.add_port(lut_module, input_port, ModuleManager::MODULE_INPUT_PORT); + /* Set the port to be wire-connection */ + module_manager.set_port_is_wire(lut_module, input_port.get_name(), true); + } + /* Add each output port */ + for (const auto& port : lut_output_ports) { + BasicPort output_port(circuit_lib.port_prefix(port), circuit_lib.port_size(port)); + module_manager.add_port(lut_module, output_port, ModuleManager::MODULE_OUTPUT_PORT); + /* Set the port to be wire-connection */ + module_manager.set_port_is_wire(lut_module, output_port.get_name(), true); + } + /* Add each regular (not mode select) SRAM port */ + for (const auto& port : lut_regular_sram_ports) { + BasicPort mem_port(circuit_lib.port_prefix(port), circuit_lib.port_size(port)); + module_manager.add_port(lut_module, mem_port, ModuleManager::MODULE_INPUT_PORT); + BasicPort mem_inv_port(std::string(circuit_lib.port_prefix(port) + "_inv"), circuit_lib.port_size(port)); + module_manager.add_port(lut_module, mem_inv_port, ModuleManager::MODULE_INPUT_PORT); + } + + /* Add each mode-select SRAM port */ + for (const auto& port : lut_mode_select_sram_ports) { + BasicPort mem_port(circuit_lib.port_prefix(port), circuit_lib.port_size(port)); + module_manager.add_port(lut_module, mem_port, ModuleManager::MODULE_INPUT_PORT); + BasicPort mem_inv_port(std::string(circuit_lib.port_prefix(port) + "_inv"), circuit_lib.port_size(port)); + module_manager.add_port(lut_module, mem_inv_port, ModuleManager::MODULE_INPUT_PORT); + } + + /*********************************************** + * Child module addition: Model-select gates + ***********************************************/ + /* Module nets after the mode-selection circuit, this could include LUT inputs */ + std::vector mode_selected_nets; + /* Instanciate mode selecting circuit: AND/OR gate + * By following the tri-state map of LUT input port + * The wiring of input ports will be organized as follows + * + * LUT input + * | + * v + * +----------+ + * | mode | + * | selector | + * +----------+ + * | mode_selected_nets + * v + * +-----------------+------------+ + * | | + * v v + * +----------+ +---------+ + * | Inverter | | Buffer | + * +----------+ +---------+ + * | inverter_output_net | buffered_output_net + * v v + * +--------------------------------------+ + * | LUT Multiplexing Structure | + * +--------------------------------------+ + */ + /* Get the tri-state port map for the input ports*/ + std::string tri_state_map = circuit_lib.port_tri_state_map(lut_input_ports[0]); + size_t mode_select_port_lsb = 0; + for (const auto& pin : circuit_lib.pins(lut_input_ports[0])) { + ModulePortId lut_module_input_port_id = module_manager.find_module_port(lut_module, circuit_lib.port_prefix(lut_input_ports[0])); + VTR_ASSERT(true == module_manager.valid_module_port_id(lut_module, lut_module_input_port_id)); + + /* Create a module net for the connection */ + ModuleNetId net = module_manager.create_module_net(lut_module); + /* Set the source of the net to an lut input port */ + module_manager.add_module_net_source(lut_module, net, lut_module, 0, lut_module_input_port_id, pin); + + /* For an empty tri-state map or a '-' sign in tri-state map, we can short-wire mode select_output_ports */ + if (tri_state_map.empty() || ('-' == tri_state_map[pin]) ) { + /* Update the output nets of the mode-select layer */ + mode_selected_nets.push_back(net); + continue; /* Finish here */ + } + + e_circuit_model_gate_type required_gate_type = NUM_CIRCUIT_MODEL_GATE_TYPES; + /* Reach here, it means that we need a circuit for mode selection */ + if ('0' == tri_state_map[pin]) { + /* We need a 2-input AND gate, in order to tri-state the input + * Detailed circuit is as follow: + * +---------+ + * SRAM --->| 2-input |----> mode_select_output_port + * LUT input--->| AND | + * +---------+ + * When SRAM is set to logic 0, the LUT input is tri-stated + * When SRAM is set to logic 1, the LUT input is effective to the downstream circuits + */ + required_gate_type = CIRCUIT_MODEL_GATE_AND; + } else { + VTR_ASSERT ('1' == tri_state_map[pin]); + /* We need a 2-input OR gate, in order to tri-state the input + * Detailed circuit is as follow: + * +---------+ + * SRAM --->| 2-input |----> mode_select_output_port + * LUT input--->| OR | + * +---------+ + * When SRAM is set to logic 1, the LUT input is tri-stated + * When SRAM is set to logic 0, the LUT input is effective to the downstream circuits + */ + required_gate_type = CIRCUIT_MODEL_GATE_OR; + } + /* Get the circuit model of the gate */ + CircuitModelId gate_model = circuit_lib.port_tri_state_model(lut_input_ports[0]); + /* Check this is the gate we want ! */ + VTR_ASSERT (required_gate_type == circuit_lib.gate_type(gate_model)); + + /* Prepare for the gate instanciation */ + /* Get the input ports from the gate */ + std::vector gate_input_ports = circuit_lib.model_ports_by_type(gate_model, CIRCUIT_MODEL_PORT_INPUT, true); + /* Get the output ports from the gate */ + std::vector gate_output_ports = circuit_lib.model_ports_by_type(gate_model, CIRCUIT_MODEL_PORT_OUTPUT, true); + /* Check the port sizes and width: + * we should have only 2 input ports, each of which has a size of 1 + * we should have only 1 output port, each of which has a size of 1 + */ + VTR_ASSERT (2 == gate_input_ports.size()); + VTR_ASSERT (1 == gate_output_ports.size()); + /* Find the module id of gate_model in the module manager */ + ModuleId gate_module = module_manager.find_module(circuit_lib.model_name(gate_model)); + /* We must have a valid id */ + VTR_ASSERT (true == module_manager.valid_module_id(gate_module)); + size_t gate_instance = module_manager.num_instance(lut_module, gate_module); + module_manager.add_child_module(lut_module, gate_module); + + /* Create a port-to-port net connection: + * Input[0] of the gate is wired to a SRAM mode-select port + * Input[1] of the gate is wired to the input port of LUT + * Output[0] of the gate is wired to the mode_select_output_port + */ + /* Create a module net for the connection */ + ModuleNetId gate_sram_net = module_manager.create_module_net(lut_module); + + /* Find the module port id of the SRAM port of LUT module */ + ModulePortId lut_module_mode_select_port_id = module_manager.find_module_port(lut_module, circuit_lib.port_prefix(lut_mode_select_sram_ports[0])); + VTR_ASSERT(true == module_manager.valid_module_port_id(lut_module, lut_module_mode_select_port_id)); + /* Set the source of the net to an mode-select SRAM port of the LUT module */ + module_manager.add_module_net_source(lut_module, gate_sram_net, lut_module, 0, lut_module_mode_select_port_id, mode_select_port_lsb); + + /* Find the module port id of the SRAM port of LUT module */ + ModulePortId gate_module_input0_port_id = module_manager.find_module_port(gate_module, circuit_lib.port_prefix(gate_input_ports[0])); + VTR_ASSERT(true == module_manager.valid_module_port_id(gate_module, gate_module_input0_port_id)); + /* Set the sink of the net to an input[0] port of the gate module */ + VTR_ASSERT(1 == module_manager.module_port(gate_module, gate_module_input0_port_id).get_width()); + for (const size_t& gate_pin : module_manager.module_port(gate_module, gate_module_input0_port_id).pins()) { + module_manager.add_module_net_sink(lut_module, gate_sram_net, gate_module, gate_instance, gate_module_input0_port_id, gate_pin); + } + + /* Use the existing net to connect to the input[1] port of the gate module */ + ModulePortId gate_module_input1_port_id = module_manager.find_module_port(gate_module, circuit_lib.port_prefix(gate_input_ports[1])); + VTR_ASSERT(true == module_manager.valid_module_port_id(gate_module, gate_module_input1_port_id)); + VTR_ASSERT(1 == module_manager.module_port(gate_module, gate_module_input1_port_id).get_width()); + for (const size_t& gate_pin : module_manager.module_port(gate_module, gate_module_input1_port_id).pins()) { + module_manager.add_module_net_sink(lut_module, net, gate_module, gate_instance, gate_module_input1_port_id, gate_pin); + } + + /* Create a module net for the output connection */ + ModuleNetId gate_output_net = module_manager.create_module_net(lut_module); + ModulePortId gate_module_output_port_id = module_manager.find_module_port(gate_module, circuit_lib.port_prefix(gate_output_ports[0])); + VTR_ASSERT(true == module_manager.valid_module_port_id(gate_module, gate_module_output_port_id)); + BasicPort gate_module_output_port = module_manager.module_port(gate_module, gate_module_output_port_id); + VTR_ASSERT(1 == gate_module_output_port.get_width()); + module_manager.add_module_net_source(lut_module, gate_output_net, gate_module, gate_instance, gate_module_output_port_id, gate_module_output_port.get_lsb()); + + /* Update the output nets of the mode-select layer */ + mode_selected_nets.push_back(gate_output_net); + + /* update the lsb of mode select port size */ + mode_select_port_lsb++; + } + + /* Sanitity check */ + if ( true == circuit_lib.is_lut_fracturable(lut_model) ) { + if (mode_select_port_lsb != circuit_lib.port_size(lut_mode_select_sram_ports[0])) { + VTR_LOG_ERROR("(ircuit model '%s' has a unmatched tri-state map '%s' implied by mode_port size='%d'!\n", + circuit_lib.model_name(lut_model).c_str(), + tri_state_map.c_str(), + circuit_lib.port_size(lut_mode_select_sram_ports[0])); + exit(1); + } + } + + /*********************************************** + * Child module addition: Input inverters + ***********************************************/ + /* Find the circuit model of the input inverter */ + CircuitModelId input_inverter_model = circuit_lib.lut_input_inverter_model(lut_model); + VTR_ASSERT( CircuitModelId::INVALID() != input_inverter_model ); + + std::vector lut_mux_sram_inv_nets; + /* Now we need to add inverters by instanciating the modules */ + for (size_t pin = 0; pin < circuit_lib.port_size(lut_input_ports[0]); ++pin) { + ModuleNetId lut_mux_sram_inv_net = add_inverter_buffer_child_module_and_nets(module_manager, lut_module, + circuit_lib, input_inverter_model, + mode_selected_nets[pin]); + /* Update the net vector */ + lut_mux_sram_inv_nets.push_back(lut_mux_sram_inv_net); + } + + /*********************************************** + * Child module addition: Input buffers + ***********************************************/ + /* Add buffers to mode_select output ports */ + /* Find the circuit model of the input inverter */ + CircuitModelId input_buffer_model = circuit_lib.lut_input_buffer_model(lut_model); + VTR_ASSERT( CircuitModelId::INVALID() != input_buffer_model ); + + std::vector lut_mux_sram_nets; + /* Now we need to add inverters by instanciating the modules and add module nets */ + for (size_t pin = 0; pin < circuit_lib.port_size(lut_input_ports[0]); ++pin) { + ModuleNetId lut_mux_sram_net = add_inverter_buffer_child_module_and_nets(module_manager, lut_module, + circuit_lib, input_buffer_model, + mode_selected_nets[pin]); + /* Update the net vector */ + lut_mux_sram_nets.push_back(lut_mux_sram_net); + } + + /*********************************************** + * Child module addition: LUT MUX + ***********************************************/ + /* Find the name of LUT MUX: no need to provide a mux size, just give an invalid number (=-1) */ + std::string lut_mux_module_name = generate_mux_subckt_name(circuit_lib, lut_model, size_t(-1), std::string("")); + /* Find the module id of LUT MUX in the module manager */ + ModuleId lut_mux_module = module_manager.find_module(lut_mux_module_name); + /* We must have a valid id */ + VTR_ASSERT (ModuleId::INVALID() != lut_mux_module); + /* Instanciate a LUT MUX as child module */ + size_t lut_mux_instance = module_manager.num_instance(lut_module, lut_mux_module); + module_manager.add_child_module(lut_module, lut_mux_module); + + /* TODO: Build module nets to connect + * 1. SRAM ports of LUT MUX module to output ports of input buffer + * 2. Inverted SRAM ports of LUT MUX module to output ports of input inverters + * 3. Data input of LUT MUX module to SRAM port of LUT + * 4. Data output of LUT MUX module to output ports of LUT + */ + ModulePortId lut_mux_sram_port_id = module_manager.find_module_port(lut_mux_module, circuit_lib.port_prefix(lut_regular_sram_ports[0])); + BasicPort lut_mux_sram_port = module_manager.module_port(lut_mux_module, lut_mux_sram_port_id); + VTR_ASSERT(lut_mux_sram_port.get_width() == lut_mux_sram_nets.size()); + /* Wire the port to lut_mux_sram_net */ + for (const size_t& pin : lut_mux_sram_port.pins()) { + module_manager.add_module_net_sink(lut_module, lut_mux_sram_nets[pin], lut_mux_module, lut_mux_instance, lut_mux_sram_port_id, pin); + } + + ModulePortId lut_mux_sram_inv_port_id = module_manager.find_module_port(lut_mux_module, std::string(circuit_lib.port_prefix(lut_regular_sram_ports[0]) + "_inv")); + BasicPort lut_mux_sram_inv_port = module_manager.module_port(lut_mux_module, lut_mux_sram_inv_port_id); + VTR_ASSERT(lut_mux_sram_inv_port.get_width() == lut_mux_sram_inv_nets.size()); + /* Wire the port to lut_mux_sram_net */ + for (const size_t& pin : lut_mux_sram_inv_port.pins()) { + module_manager.add_module_net_sink(lut_module, lut_mux_sram_inv_nets[pin], lut_mux_module, lut_mux_instance, lut_mux_sram_inv_port_id, pin); + } + + /* lut_module + * +------------ + * | +------ + * sram -->|---->| (lut_mux_input_port) + * | ^ | LUT MUX + * | | | + * | + * net + */ + ModulePortId lut_sram_port_id = module_manager.find_module_port(lut_module, circuit_lib.port_prefix(lut_regular_sram_ports[0])); + BasicPort lut_sram_port = module_manager.module_port(lut_module, lut_sram_port_id); + ModulePortId lut_mux_input_port_id = module_manager.find_module_port(lut_mux_module, circuit_lib.port_prefix(lut_input_ports[0])); + BasicPort lut_mux_input_port = module_manager.module_port(lut_mux_module, lut_mux_input_port_id); + VTR_ASSERT(lut_mux_input_port.get_width() == lut_sram_port.get_width()); + /* Wire the port to lut_mux_sram_net */ + for (size_t pin_id = 0; pin_id < lut_mux_input_port.pins().size(); ++pin_id) { + ModuleNetId net = module_manager.create_module_net(lut_module); + module_manager.add_module_net_source(lut_module, net, lut_module, 0, lut_sram_port_id, lut_sram_port.pins()[pin_id]); + module_manager.add_module_net_sink(lut_module, net, lut_mux_module, lut_mux_instance, lut_mux_input_port_id, lut_mux_input_port.pins()[pin_id]); + } + + for (const auto& port : lut_output_ports) { + ModulePortId lut_output_port_id = module_manager.find_module_port(lut_module, circuit_lib.port_prefix(port)); + BasicPort lut_output_port = module_manager.module_port(lut_module, lut_output_port_id); + ModulePortId lut_mux_output_port_id = module_manager.find_module_port(lut_mux_module, circuit_lib.port_prefix(port)); + BasicPort lut_mux_output_port = module_manager.module_port(lut_mux_module, lut_mux_output_port_id); + VTR_ASSERT(lut_mux_output_port.get_width() == lut_output_port.get_width()); + /* Wire the port to lut_mux_sram_net */ + for (size_t pin_id = 0; pin_id < lut_output_port.pins().size(); ++pin_id) { + ModuleNetId net = module_manager.create_module_net(lut_module); + module_manager.add_module_net_source(lut_module, net, lut_mux_module, lut_mux_instance, lut_mux_output_port_id, lut_mux_output_port.pins()[pin_id]); + module_manager.add_module_net_sink(lut_module, net, lut_module, 0, lut_output_port_id, lut_output_port.pins()[pin_id]); + } + } + + /* Add global ports to the pb_module: + * This is a much easier job after adding sub modules (instances), + * we just need to find all the global ports from the child modules and build a list of it + */ + add_module_global_ports_from_child_modules(module_manager, lut_module); +} + +/******************************************************************** + * Print Verilog modules for the Look-Up Tables (LUTs) + * in the circuit library + ********************************************************************/ +void build_lut_modules(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib) { + vtr::ScopedStartFinishTimer timer("Build Look-Up Table (LUT) modules"); + + /* Search for each LUT circuit model */ + for (const auto& lut_model : circuit_lib.models()) { + /* Bypas non-LUT modules */ + if (CIRCUIT_MODEL_LUT != circuit_lib.model_type(lut_model)) { + continue; + } + build_lut_module(module_manager, circuit_lib, lut_model); + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fabric/build_lut_modules.h b/openfpga/src/fabric/build_lut_modules.h new file mode 100644 index 000000000..5b43a742b --- /dev/null +++ b/openfpga/src/fabric/build_lut_modules.h @@ -0,0 +1,22 @@ +#ifndef BUILD_LUT_MODULES_H +#define BUILD_LUT_MODULES_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "circuit_library.h" +#include "module_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void build_lut_modules(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml index 2a4bb1b02..829deb1a9 100644 --- a/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml @@ -156,7 +156,7 @@ - + From 8e381f05815a9917ded35b172398a2f16310ec35 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 12 Feb 2020 19:57:15 -0700 Subject: [PATCH 128/645] add wire module builder --- openfpga/src/fabric/build_device_module.cpp | 4 +- openfpga/src/fabric/build_wire_modules.cpp | 73 +++++++++++++++++++++ openfpga/src/fabric/build_wire_modules.h | 22 +++++++ 3 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 openfpga/src/fabric/build_wire_modules.cpp create mode 100644 openfpga/src/fabric/build_wire_modules.h diff --git a/openfpga/src/fabric/build_device_module.cpp b/openfpga/src/fabric/build_device_module.cpp index 5e8b090f2..a7c82f351 100644 --- a/openfpga/src/fabric/build_device_module.cpp +++ b/openfpga/src/fabric/build_device_module.cpp @@ -12,7 +12,7 @@ #include "build_decoder_modules.h" #include "build_mux_modules.h" #include "build_lut_modules.h" -//#include "build_wire_modules.h" +#include "build_wire_modules.h" //#include "build_memory_modules.h" //#include "build_grid_modules.h" //#include "build_routing_modules.h" @@ -59,7 +59,7 @@ ModuleManager build_device_module_graph(const DeviceContext& vpr_device_ctx, build_lut_modules(module_manager, openfpga_ctx.arch().circuit_lib); /* Build wire modules */ - //build_wire_modules(module_manager, arch.spice->circuit_lib); + build_wire_modules(module_manager, openfpga_ctx.arch().circuit_lib); /* Build memory modules */ //build_memory_modules(module_manager, mux_lib, arch.spice->circuit_lib, diff --git a/openfpga/src/fabric/build_wire_modules.cpp b/openfpga/src/fabric/build_wire_modules.cpp new file mode 100644 index 000000000..da3b03e23 --- /dev/null +++ b/openfpga/src/fabric/build_wire_modules.cpp @@ -0,0 +1,73 @@ +/*********************************************** + * This file includes functions to generate + * Verilog submodules for wires. + **********************************************/ +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_log.h" +#include "vtr_assert.h" +#include "vtr_time.h" + +/* Device-level header files */ +#include "module_manager.h" +#include "module_manager_utils.h" + +#include "openfpga_naming.h" + +#include "build_wire_modules.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Print a Verilog module of a regular wire segment + * Regular wire, which is 1-input and 1-output + * This type of wires are used in the local routing architecture + * +------+ + * input --->| wire |---> output + * +------+ + * + *******************************************************************/ +static +void build_wire_module(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const CircuitModelId& wire_model) { + /* Find the input port, output port*/ + std::vector input_ports = circuit_lib.model_ports_by_type(wire_model, CIRCUIT_MODEL_PORT_INPUT, true); + std::vector output_ports = circuit_lib.model_ports_by_type(wire_model, CIRCUIT_MODEL_PORT_OUTPUT, true); + std::vector global_ports = circuit_lib.model_global_ports_by_type(wire_model, CIRCUIT_MODEL_PORT_INPUT, true, true); + + /* Makre sure the port size is what we want */ + VTR_ASSERT (1 == input_ports.size()); + VTR_ASSERT (1 == output_ports.size()); + VTR_ASSERT (1 == circuit_lib.port_size(input_ports[0])); + VTR_ASSERT (1 == circuit_lib.port_size(output_ports[0])); + + /* Create a Verilog Module based on the circuit model, and add to module manager */ + add_circuit_model_to_module_manager(module_manager, circuit_lib, wire_model); +} + +/******************************************************************** + * This function will only create wire modules with a number of + * ports that are defined by users. + * It will NOT insert any internal logic, which should be handled + * by Verilog/SPICE writers + *******************************************************************/ +void build_wire_modules(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib) { + vtr::ScopedStartFinishTimer timer("Build wire modules"); + + /* Print Verilog models for regular wires*/ + for (const auto& wire_model : circuit_lib.models_by_type(CIRCUIT_MODEL_WIRE)) { + /* Bypass user-defined circuit models */ + if ( (!circuit_lib.model_circuit_netlist(wire_model).empty()) + && (!circuit_lib.model_verilog_netlist(wire_model).empty()) ) { + continue; + } + build_wire_module(module_manager, circuit_lib, wire_model); + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fabric/build_wire_modules.h b/openfpga/src/fabric/build_wire_modules.h new file mode 100644 index 000000000..845f5f532 --- /dev/null +++ b/openfpga/src/fabric/build_wire_modules.h @@ -0,0 +1,22 @@ +#ifndef BUILD_WIRE_MODULES_H +#define BUILD_WIRE_MODULES_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "circuit_library.h" +#include "module_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void build_wire_modules(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib); + +} /* end namespace openfpga */ + +#endif From 002c2795fe505f2fb25d1f4c8d96fdd163b0d556 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 12 Feb 2020 20:06:38 -0700 Subject: [PATCH 129/645] add memory module builder --- openfpga/src/fabric/build_device_module.cpp | 7 +- openfpga/src/fabric/build_memory_modules.cpp | 715 +++++++++++++++++++ openfpga/src/fabric/build_memory_modules.h | 25 + 3 files changed, 744 insertions(+), 3 deletions(-) create mode 100644 openfpga/src/fabric/build_memory_modules.cpp create mode 100644 openfpga/src/fabric/build_memory_modules.h diff --git a/openfpga/src/fabric/build_device_module.cpp b/openfpga/src/fabric/build_device_module.cpp index a7c82f351..40d856022 100644 --- a/openfpga/src/fabric/build_device_module.cpp +++ b/openfpga/src/fabric/build_device_module.cpp @@ -13,7 +13,7 @@ #include "build_mux_modules.h" #include "build_lut_modules.h" #include "build_wire_modules.h" -//#include "build_memory_modules.h" +#include "build_memory_modules.h" //#include "build_grid_modules.h" //#include "build_routing_modules.h" //#include "build_top_module.h" @@ -62,8 +62,9 @@ ModuleManager build_device_module_graph(const DeviceContext& vpr_device_ctx, build_wire_modules(module_manager, openfpga_ctx.arch().circuit_lib); /* Build memory modules */ - //build_memory_modules(module_manager, mux_lib, arch.spice->circuit_lib, - // arch.sram_inf.verilog_sram_inf_orgz->type); + build_memory_modules(module_manager, openfpga_ctx.mux_lib(), + openfpga_ctx.arch().circuit_lib, + openfpga_ctx.arch().config_protocol.type()); /* Build grid and programmable block modules */ //build_grid_modules(module_manager, arch.spice->circuit_lib, mux_lib, diff --git a/openfpga/src/fabric/build_memory_modules.cpp b/openfpga/src/fabric/build_memory_modules.cpp new file mode 100644 index 000000000..26b463377 --- /dev/null +++ b/openfpga/src/fabric/build_memory_modules.cpp @@ -0,0 +1,715 @@ +/********************************************************************* + * This file includes functions to generate Verilog submodules for + * the memories that are affiliated to multiplexers and other programmable + * circuit models, such as IOPADs, LUTs, etc. + ********************************************************************/ +#include +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_log.h" +#include "vtr_time.h" +#include "vtr_assert.h" + +#include "mux_graph.h" +#include "module_manager.h" +#include "circuit_library_utils.h" +#include "module_manager_utils.h" +#include "mux_utils.h" + +#include "openfpga_reserved_words.h" +#include "openfpga_naming.h" + +#include "build_memory_modules.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/********************************************************************* + * Add module nets to connect an input port of a memory module to + * an input port of its child module + * Restriction: this function is really designed for memory modules + * 1. It assumes that input port name of child module is the same as memory module + * 2. It assumes exact pin-to-pin mapping: + * j-th pin of input port of the i-th child module is wired to the j + i*W -th + * pin of input port of the memory module, where W is the size of port + ********************************************************************/ +static +void add_module_input_nets_to_mem_modules(ModuleManager& module_manager, + const ModuleId& mem_module, + const CircuitLibrary& circuit_lib, + const std::vector& circuit_ports, + const ModuleId& child_module, + const size_t& child_index, + const size_t& child_instance) { + /* Wire inputs of parent module to inputs of child modules */ + for (const auto& port : circuit_ports) { + ModulePortId src_port_id = module_manager.find_module_port(mem_module, circuit_lib.port_prefix(port)); + ModulePortId sink_port_id = module_manager.find_module_port(child_module, circuit_lib.port_prefix(port)); + for (size_t pin_id = 0; pin_id < module_manager.module_port(mem_module, sink_port_id).pins().size(); ++pin_id) { + ModuleNetId net = module_manager.create_module_net(mem_module); + /* Source pin is shifted by the number of memories */ + size_t src_pin_id = child_index * circuit_lib.port_size(port) + module_manager.module_port(mem_module, src_port_id).pins()[pin_id]; + /* Source node of the input net is the input of memory module */ + module_manager.add_module_net_source(mem_module, net, mem_module, 0, src_port_id, src_pin_id); + /* Sink node of the input net is the input of sram module */ + size_t sink_pin_id = module_manager.module_port(child_module, sink_port_id).pins()[pin_id]; + module_manager.add_module_net_sink(mem_module, net, child_module, child_instance, sink_port_id, sink_pin_id); + } + } +} + +/********************************************************************* + * Add module nets to connect an output port of a memory module to + * an output port of its child module + * Restriction: this function is really designed for memory modules + * 1. It assumes that output port name of child module is the same as memory module + * 2. It assumes exact pin-to-pin mapping: + * j-th pin of output port of the i-th child module is wired to the j + i*W -th + * pin of output port of the memory module, where W is the size of port + ********************************************************************/ +static +void add_module_output_nets_to_mem_modules(ModuleManager& module_manager, + const ModuleId& mem_module, + const CircuitLibrary& circuit_lib, + const std::vector& circuit_ports, + const ModuleId& child_module, + const size_t& child_index, + const size_t& child_instance) { + /* Wire inputs of parent module to inputs of child modules */ + for (const auto& port : circuit_ports) { + ModulePortId src_port_id = module_manager.find_module_port(child_module, circuit_lib.port_prefix(port)); + ModulePortId sink_port_id = module_manager.find_module_port(mem_module, circuit_lib.port_prefix(port)); + for (size_t pin_id = 0; pin_id < module_manager.module_port(child_module, src_port_id).pins().size(); ++pin_id) { + ModuleNetId net = module_manager.create_module_net(mem_module); + /* Source pin is shifted by the number of memories */ + size_t src_pin_id = module_manager.module_port(child_module, src_port_id).pins()[pin_id]; + /* Source node of the input net is the input of memory module */ + module_manager.add_module_net_source(mem_module, net, child_module, child_instance, src_port_id, src_pin_id); + /* Sink node of the input net is the input of sram module */ + size_t sink_pin_id = child_index * circuit_lib.port_size(port) + module_manager.module_port(mem_module, sink_port_id).pins()[pin_id]; + module_manager.add_module_net_sink(mem_module, net, mem_module, 0, sink_port_id, sink_pin_id); + } + } +} + +/********************************************************************* + * Add module nets to connect an output port of a configuration-chain + * memory module to an output port of its child module + * Restriction: this function is really designed for memory modules + * 1. It assumes that output port name of child module is the same as memory module + * 2. It assumes exact pin-to-pin mapping: + * j-th pin of output port of the i-th child module is wired to the j + i*W -th + * pin of output port of the memory module, where W is the size of port + * 3. It assumes fixed port name for output ports + * + * We cache the module nets that have been created because they will be used later + ********************************************************************/ +static +std::vector add_module_output_nets_to_chain_mem_modules(ModuleManager& module_manager, + const ModuleId& mem_module, + const std::string& mem_module_output_name, + const CircuitLibrary& circuit_lib, + const CircuitPortId& circuit_port, + const ModuleId& child_module, + const size_t& child_index, + const size_t& child_instance) { + std::vector module_nets; + + /* Wire inputs of parent module to inputs of child modules */ + ModulePortId src_port_id = module_manager.find_module_port(child_module, circuit_lib.port_prefix(circuit_port)); + ModulePortId sink_port_id = module_manager.find_module_port(mem_module, mem_module_output_name); + for (size_t pin_id = 0; pin_id < module_manager.module_port(child_module, src_port_id).pins().size(); ++pin_id) { + ModuleNetId net = module_manager.create_module_net(mem_module); + /* Source pin is shifted by the number of memories */ + size_t src_pin_id = module_manager.module_port(child_module, src_port_id).pins()[pin_id]; + /* Source node of the input net is the input of memory module */ + module_manager.add_module_net_source(mem_module, net, child_module, child_instance, src_port_id, src_pin_id); + /* Sink node of the input net is the input of sram module */ + size_t sink_pin_id = child_index * circuit_lib.port_size(circuit_port) + module_manager.module_port(mem_module, sink_port_id).pins()[pin_id]; + module_manager.add_module_net_sink(mem_module, net, mem_module, 0, sink_port_id, sink_pin_id); + + /* Cache the nets */ + module_nets.push_back(net); + } + + return module_nets; +} + +/******************************************************************** + * Connect all the memory modules under the parent module in a chain + * + * +--------+ +--------+ +--------+ + * ccff_head --->| Memory |--->| Memory |--->... --->| Memory |----> ccff_tail + * | Module | | Module | | Module | + * | [0] | | [1] | | [N-1] | + * +--------+ +--------+ +--------+ + * For the 1st memory module: + * net source is the configuration chain head of the primitive module + * net sink is the configuration chain head of the next memory module + * + * For the rest of memory modules: + * net source is the configuration chain tail of the previous memory module + * net sink is the configuration chain head of the next memory module + * + * Note that: + * This function is designed for memory modules ONLY! + * Do not use it to replace the + * add_module_nets_cmos_memory_chain_config_bus() !!! + *********************************************************************/ +static +void add_module_nets_to_cmos_memory_chain_module(ModuleManager& module_manager, + const ModuleId& parent_module, + const std::vector& output_nets, + const CircuitLibrary& circuit_lib, + const CircuitPortId& model_input_port, + const CircuitPortId& model_output_port) { + /* Counter for the nets */ + size_t net_counter = 0; + + for (size_t mem_index = 0; mem_index < module_manager.configurable_children(parent_module).size(); ++mem_index) { + ModuleId net_src_module_id; + size_t net_src_instance_id; + ModulePortId net_src_port_id; + + ModuleId net_sink_module_id; + size_t net_sink_instance_id; + ModulePortId net_sink_port_id; + + if (0 == mem_index) { + /* Find the port name of configuration chain head */ + std::string src_port_name = generate_configuration_chain_head_name(); + net_src_module_id = parent_module; + net_src_instance_id = 0; + net_src_port_id = module_manager.find_module_port(net_src_module_id, src_port_name); + + /* Find the port name of next memory module */ + std::string sink_port_name = circuit_lib.port_prefix(model_input_port); + net_sink_module_id = module_manager.configurable_children(parent_module)[mem_index]; + net_sink_instance_id = module_manager.configurable_child_instances(parent_module)[mem_index]; + net_sink_port_id = module_manager.find_module_port(net_sink_module_id, sink_port_name); + } else { + /* Find the port name of previous memory module */ + std::string src_port_name = circuit_lib.port_prefix(model_output_port); + net_src_module_id = module_manager.configurable_children(parent_module)[mem_index - 1]; + net_src_instance_id = module_manager.configurable_child_instances(parent_module)[mem_index - 1]; + net_src_port_id = module_manager.find_module_port(net_src_module_id, src_port_name); + + /* Find the port name of next memory module */ + std::string sink_port_name = circuit_lib.port_prefix(model_input_port); + net_sink_module_id = module_manager.configurable_children(parent_module)[mem_index]; + net_sink_instance_id = module_manager.configurable_child_instances(parent_module)[mem_index]; + net_sink_port_id = module_manager.find_module_port(net_sink_module_id, sink_port_name); + } + + /* Get the pin id for source port */ + BasicPort net_src_port = module_manager.module_port(net_src_module_id, net_src_port_id); + /* Get the pin id for sink port */ + BasicPort net_sink_port = module_manager.module_port(net_sink_module_id, net_sink_port_id); + /* Port sizes of source and sink should match */ + VTR_ASSERT(net_src_port.get_width() == net_sink_port.get_width()); + + /* Create a net for each pin */ + for (size_t pin_id = 0; pin_id < net_src_port.pins().size(); ++pin_id) { + /* Create a net and add source and sink to it */ + ModuleNetId net; + if (0 == mem_index) { + net = module_manager.create_module_net(parent_module); + } else { + net = output_nets[net_counter]; + } + /* Add net source */ + module_manager.add_module_net_source(parent_module, net, net_src_module_id, net_src_instance_id, net_src_port_id, net_src_port.pins()[pin_id]); + /* Add net sink */ + module_manager.add_module_net_sink(parent_module, net, net_sink_module_id, net_sink_instance_id, net_sink_port_id, net_sink_port.pins()[pin_id]); + + /* Update net counter */ + if (0 < mem_index) { + net_counter++; + } + } + } + + /* For the last memory module: + * net source is the configuration chain tail of the previous memory module + * net sink is the configuration chain tail of the primitive module + */ + /* Find the port name of previous memory module */ + std::string src_port_name = circuit_lib.port_prefix(model_output_port); + ModuleId net_src_module_id = module_manager.configurable_children(parent_module).back(); + size_t net_src_instance_id = module_manager.configurable_child_instances(parent_module).back(); + ModulePortId net_src_port_id = module_manager.find_module_port(net_src_module_id, src_port_name); + + /* Find the port name of next memory module */ + std::string sink_port_name = generate_configuration_chain_tail_name(); + ModuleId net_sink_module_id = parent_module; + size_t net_sink_instance_id = 0; + ModulePortId net_sink_port_id = module_manager.find_module_port(net_sink_module_id, sink_port_name); + + /* Get the pin id for source port */ + BasicPort net_src_port = module_manager.module_port(net_src_module_id, net_src_port_id); + /* Get the pin id for sink port */ + BasicPort net_sink_port = module_manager.module_port(net_sink_module_id, net_sink_port_id); + /* Port sizes of source and sink should match */ + VTR_ASSERT(net_src_port.get_width() == net_sink_port.get_width()); + + /* Create a net for each pin */ + for (size_t pin_id = 0; pin_id < net_src_port.pins().size(); ++pin_id) { + /* Create a net and add source and sink to it */ + ModuleNetId net = output_nets[net_counter]; + /* Add net source */ + module_manager.add_module_net_source(parent_module, net, net_src_module_id, net_src_instance_id, net_src_port_id, net_src_port.pins()[pin_id]); + /* Add net sink */ + module_manager.add_module_net_sink(parent_module, net, net_sink_module_id, net_sink_instance_id, net_sink_port_id, net_sink_port.pins()[pin_id]); + + /* Update net counter */ + net_counter++; + } + + VTR_ASSERT(net_counter == output_nets.size()); +} + +/********************************************************************* + * Flat memory modules + * + * in[0] in[1] in[N] + * | | | + * v v v + * +-------+ +-------+ +-------+ + * | SRAM | | SRAM | ... | SRAM | + * | [0] | | [1] | | [N-1] | + * +-------+ +-------+ +-------+ + * | | ... | + * v v v + * +------------------------------------+ + * | Multiplexer Configuration port | + * + ********************************************************************/ +static +void build_memory_standalone_module(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const std::string& module_name, + const CircuitModelId& sram_model, + const size_t& num_mems) { + /* Get the global ports required by the SRAM */ + std::vector global_port_types; + global_port_types.push_back(CIRCUIT_MODEL_PORT_CLOCK); + global_port_types.push_back(CIRCUIT_MODEL_PORT_INPUT); + std::vector sram_global_ports = circuit_lib.model_global_ports_by_type(sram_model, global_port_types, true, false); + /* Get the input ports from the SRAM */ + std::vector sram_input_ports = circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_INPUT, true); + /* Get the output ports from the SRAM */ + std::vector sram_output_ports = circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_OUTPUT, true); + + /* Create a module and add to the module manager */ + ModuleId mem_module = module_manager.add_module(module_name); + VTR_ASSERT(true == module_manager.valid_module_id(mem_module)); + + /* Add each input port */ + for (const auto& port : sram_input_ports) { + BasicPort input_port(circuit_lib.port_prefix(port), num_mems); + module_manager.add_port(mem_module, input_port, ModuleManager::MODULE_INPUT_PORT); + } + /* Add each output port: port width should match the number of memories */ + for (const auto& port : sram_output_ports) { + BasicPort output_port(circuit_lib.port_prefix(port), num_mems); + module_manager.add_port(mem_module, output_port, ModuleManager::MODULE_OUTPUT_PORT); + } + + /* Find the sram module in the module manager */ + ModuleId sram_mem_module = module_manager.find_module(circuit_lib.model_name(sram_model)); + + /* Instanciate each submodule */ + for (size_t i = 0; i < num_mems; ++i) { + size_t sram_mem_instance = module_manager.num_instance(mem_module, sram_mem_module); + module_manager.add_child_module(mem_module, sram_mem_module); + module_manager.add_configurable_child(mem_module, sram_mem_module, sram_mem_instance); + + /* Build module nets */ + /* Wire inputs of parent module to inputs of child modules */ + add_module_input_nets_to_mem_modules(module_manager, mem_module, circuit_lib, sram_input_ports, sram_mem_module, i, sram_mem_instance); + /* Wire inputs of parent module to outputs of child modules */ + add_module_output_nets_to_mem_modules(module_manager, mem_module, circuit_lib, sram_output_ports, sram_mem_module, i, sram_mem_instance); + } + + /* Add global ports to the pb_module: + * This is a much easier job after adding sub modules (instances), + * we just need to find all the global ports from the child modules and build a list of it + */ + add_module_global_ports_from_child_modules(module_manager, mem_module); +} + +/********************************************************************* + * Scan-chain organization + * + * +-------+ +-------+ +-------+ + * scan-chain--->| CCFF |--->| CCFF |--->... --->| CCFF |---->scan-chain + * input&clock | [0] | | [1] | | [N-1] | output + * +-------+ +-------+ +-------+ + * | | ... | + * v v v + * +-----------------------------------------+ + * | Multiplexer Configuration port | + * + ********************************************************************/ +static +void build_memory_chain_module(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const std::string& module_name, + const CircuitModelId& sram_model, + const size_t& num_mems) { + + /* Get the input ports from the SRAM */ + std::vector sram_input_ports = circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_INPUT, true); + /* Should have only 1 input port */ + VTR_ASSERT( 1 == sram_input_ports.size() ); + /* Get the output ports from the SRAM */ + std::vector sram_output_ports = circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_OUTPUT, true); + /* Should have only 1 or 2 output port */ + VTR_ASSERT( (1 == sram_output_ports.size()) || ( 2 == sram_output_ports.size()) ); + + /* Create a module and add to the module manager */ + ModuleId mem_module = module_manager.add_module(module_name); + VTR_ASSERT(true == module_manager.valid_module_id(mem_module)); + + /* Add an input port, which is the head of configuration chain in the module */ + /* TODO: restriction!!! + * consider only the first input of the CCFF model as the D port, + * which will be connected to the head of the chain + */ + BasicPort chain_head_port(generate_configuration_chain_head_name(), + circuit_lib.port_size(sram_input_ports[0])); + module_manager.add_port(mem_module, chain_head_port, ModuleManager::MODULE_INPUT_PORT); + /* Add an output port, which is the tail of configuration chain in the module */ + /* TODO: restriction!!! + * consider only the first output of the CCFF model as the Q port, + * which will be connected to the tail of the chain + */ + BasicPort chain_tail_port(generate_configuration_chain_tail_name(), + circuit_lib.port_size(sram_output_ports[0])); + module_manager.add_port(mem_module, chain_tail_port, ModuleManager::MODULE_OUTPUT_PORT); + + /* Add each output port: port width should match the number of memories */ + for (size_t iport = 0; iport < sram_output_ports.size(); ++iport) { + std::string port_name; + if (0 == iport) { + port_name = generate_configuration_chain_data_out_name(); + } else { + VTR_ASSERT( 1 == iport); + port_name = generate_configuration_chain_inverted_data_out_name(); + } + BasicPort output_port(port_name, num_mems); + module_manager.add_port(mem_module, output_port, ModuleManager::MODULE_OUTPUT_PORT); + } + + /* Find the sram module in the module manager */ + ModuleId sram_mem_module = module_manager.find_module(circuit_lib.model_name(sram_model)); + + /* Cache the output nets for non-inverted data output */ + std::vector mem_output_nets; + + /* Instanciate each submodule */ + for (size_t i = 0; i < num_mems; ++i) { + size_t sram_mem_instance = module_manager.num_instance(mem_module, sram_mem_module); + module_manager.add_child_module(mem_module, sram_mem_module); + module_manager.add_configurable_child(mem_module, sram_mem_module, sram_mem_instance); + + /* Build module nets to wire outputs of sram modules to outputs of memory module */ + for (size_t iport = 0; iport < sram_output_ports.size(); ++iport) { + std::string port_name; + if (0 == iport) { + port_name = generate_configuration_chain_data_out_name(); + } else { + VTR_ASSERT( 1 == iport); + port_name = generate_configuration_chain_inverted_data_out_name(); + } + std::vector output_nets = add_module_output_nets_to_chain_mem_modules(module_manager, mem_module, + port_name, circuit_lib, sram_output_ports[iport], + sram_mem_module, i, sram_mem_instance); + /* Cache only for regular data outputs */ + if (0 == iport) { + mem_output_nets.insert(mem_output_nets.end(), output_nets.begin(), output_nets.end()); + } + } + } + + /* Build module nets to wire the configuration chain */ + add_module_nets_to_cmos_memory_chain_module(module_manager, mem_module, mem_output_nets, + circuit_lib, sram_input_ports[0], sram_output_ports[0]); + + + /* Add global ports to the pb_module: + * This is a much easier job after adding sub modules (instances), + * we just need to find all the global ports from the child modules and build a list of it + */ + add_module_global_ports_from_child_modules(module_manager, mem_module); +} + +/********************************************************************* + * Memory bank organization + * + * Bit lines(BL/BLB) Word lines (WL/WLB) + * | | + * v v + * +------------------------------------+ + * | Memory Module Configuration port | + * +------------------------------------+ + * | | | + * v v v + * +-------+ +-------+ +-------+ + * | SRAM | | SRAM | ... | SRAM | + * | [0] | | [1] | | [N-1] | + * +-------+ +-------+ +-------+ + * | | ... | + * v v v + * +------------------------------------+ + * | Multiplexer Configuration port | + * + ********************************************************************/ +static +void build_memory_bank_module(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const std::string& module_name, + const CircuitModelId& sram_model, + const size_t& num_mems) { + /* Get the global ports required by the SRAM */ + std::vector global_port_types; + global_port_types.push_back(CIRCUIT_MODEL_PORT_CLOCK); + global_port_types.push_back(CIRCUIT_MODEL_PORT_INPUT); + std::vector sram_global_ports = circuit_lib.model_global_ports_by_type(sram_model, global_port_types, true, false); + /* Get the input ports from the SRAM */ + std::vector sram_input_ports = circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_INPUT, true); + /* A SRAM cell with BL/WL should not have any input */ + VTR_ASSERT( 0 == sram_input_ports.size() ); + /* Get the output ports from the SRAM */ + std::vector sram_output_ports = circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_OUTPUT, true); + /* Get the BL/WL ports from the SRAM */ + std::vector sram_bl_ports = circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_BL, true); + std::vector sram_blb_ports = circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_BLB, true); + std::vector sram_wl_ports = circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_WL, true); + std::vector sram_wlb_ports = circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_WLB, true); + + /* Create a module and add to the module manager */ + ModuleId mem_module = module_manager.add_module(module_name); + VTR_ASSERT(true == module_manager.valid_module_id(mem_module)); + + /* Add module ports: the ports come from the SRAM modules */ + /* Add each input port */ + for (const auto& port : sram_input_ports) { + BasicPort input_port(circuit_lib.port_prefix(port), num_mems * circuit_lib.port_size(port)); + module_manager.add_port(mem_module, input_port, ModuleManager::MODULE_INPUT_PORT); + } + /* Add each output port: port width should match the number of memories */ + for (const auto& port : sram_output_ports) { + BasicPort output_port(circuit_lib.port_prefix(port), num_mems * circuit_lib.port_size(port)); + module_manager.add_port(mem_module, output_port, ModuleManager::MODULE_OUTPUT_PORT); + } + /* Add each output port: port width should match the number of memories */ + for (const auto& port : sram_bl_ports) { + BasicPort bl_port(circuit_lib.port_prefix(port), num_mems * circuit_lib.port_size(port)); + module_manager.add_port(mem_module, bl_port, ModuleManager::MODULE_INPUT_PORT); + } + for (const auto& port : sram_blb_ports) { + BasicPort blb_port(circuit_lib.port_prefix(port), num_mems * circuit_lib.port_size(port)); + module_manager.add_port(mem_module, blb_port, ModuleManager::MODULE_INPUT_PORT); + } + for (const auto& port : sram_wl_ports) { + BasicPort wl_port(circuit_lib.port_prefix(port), num_mems * circuit_lib.port_size(port)); + module_manager.add_port(mem_module, wl_port, ModuleManager::MODULE_INPUT_PORT); + } + for (const auto& port : sram_wlb_ports) { + BasicPort wlb_port(circuit_lib.port_prefix(port), num_mems * circuit_lib.port_size(port)); + module_manager.add_port(mem_module, wlb_port, ModuleManager::MODULE_INPUT_PORT); + } + + /* Find the sram module in the module manager */ + ModuleId sram_mem_module = module_manager.find_module(circuit_lib.model_name(sram_model)); + + /* Instanciate each submodule */ + for (size_t i = 0; i < num_mems; ++i) { + /* Memory seed module instanciation */ + size_t sram_instance = module_manager.num_instance(mem_module, sram_mem_module); + module_manager.add_child_module(mem_module, sram_mem_module); + + /* Build module nets */ + /* Wire inputs of parent module to inputs of child modules */ + add_module_input_nets_to_mem_modules(module_manager, mem_module, circuit_lib, sram_input_ports, sram_mem_module, i, sram_instance); + /* Wire inputs of parent module to outputs of child modules */ + add_module_output_nets_to_mem_modules(module_manager, mem_module, circuit_lib, sram_output_ports, sram_mem_module, i, sram_instance); + /* Wire BL/WLs of parent module to BL/WLs of child modules */ + add_module_input_nets_to_mem_modules(module_manager, mem_module, circuit_lib, sram_bl_ports, sram_mem_module, i, sram_instance); + add_module_input_nets_to_mem_modules(module_manager, mem_module, circuit_lib, sram_blb_ports, sram_mem_module, i, sram_instance); + add_module_input_nets_to_mem_modules(module_manager, mem_module, circuit_lib, sram_wl_ports, sram_mem_module, i, sram_instance); + add_module_input_nets_to_mem_modules(module_manager, mem_module, circuit_lib, sram_wlb_ports, sram_mem_module, i, sram_instance); + } + + /* TODO: if a local memory decoder is required, instanciate it here */ + + /* Add global ports to the pb_module: + * This is a much easier job after adding sub modules (instances), + * we just need to find all the global ports from the child modules and build a list of it + */ + add_module_global_ports_from_child_modules(module_manager, mem_module); +} + + +/********************************************************************* + * Generate Verilog modules for the memories that are used + * by a circuit model + * The organization of memory circuit will depend on the style of + * configuration protocols + * Currently, we support + * 1. Flat SRAM organization + * 2. Configuration chain + * 3. Memory bank (memory decoders) + ********************************************************************/ +static +void build_memory_module(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const e_config_protocol_type& sram_orgz_type, + const std::string& module_name, + const CircuitModelId& sram_model, + const size_t& num_mems) { + switch (sram_orgz_type) { + case CONFIG_MEM_STANDALONE: + build_memory_standalone_module(module_manager, circuit_lib, + module_name, sram_model, num_mems); + break; + case CONFIG_MEM_SCAN_CHAIN: + build_memory_chain_module(module_manager, circuit_lib, + module_name, sram_model, num_mems); + break; + case CONFIG_MEM_MEMORY_BANK: + build_memory_bank_module(module_manager, circuit_lib, + module_name, sram_model, num_mems); + break; + default: + VTR_LOG_ERROR("Invalid SRAM organization!\n"); + exit(1); + } +} + + +/********************************************************************* + * Generate Verilog modules for the memories that are used + * by multiplexers + * + * +----------------+ + * mem_in --->| Memory Module |---> mem_out + * +----------------+ + * | | ... | | + * v v v v SRAM ports of multiplexer + * +---------------------+ + * in--->| Multiplexer Module |---> out + * +---------------------+ + ********************************************************************/ +static +void build_mux_memory_module(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const e_config_protocol_type& sram_orgz_type, + const CircuitModelId& mux_model, + const MuxGraph& mux_graph) { + /* Find the actual number of configuration bits, based on the mux graph + * Due to the use of local decoders inside mux, this may be + */ + size_t num_config_bits = find_mux_num_config_bits(circuit_lib, mux_model, mux_graph, sram_orgz_type); + /* Multiplexers built with different technology is in different organization */ + switch (circuit_lib.design_tech_type(mux_model)) { + case CIRCUIT_MODEL_DESIGN_CMOS: { + /* Generate module name */ + std::string module_name = generate_mux_subckt_name(circuit_lib, mux_model, + find_mux_num_datapath_inputs(circuit_lib, mux_model, mux_graph.num_inputs()), + std::string(MEMORY_MODULE_POSTFIX)); + + /* Get the sram ports from the mux */ + std::vector sram_models = find_circuit_sram_models(circuit_lib, mux_model); + VTR_ASSERT( 1 == sram_models.size() ); + + build_memory_module(module_manager, circuit_lib, sram_orgz_type, module_name, sram_models[0], num_config_bits); + break; + } + case CIRCUIT_MODEL_DESIGN_RRAM: + /* We do not need a memory submodule for RRAM MUX, + * RRAM are embedded in the datapath + * TODO: generate local encoders for RRAM-based multiplexers here!!! + */ + break; + default: + VTR_LOG_ERROR("Invalid design technology of multiplexer '%s'\n", + circuit_lib.model_name(mux_model).c_str()); + exit(1); + } +} + +/********************************************************************* + * Build modules for + * the memories that are affiliated to multiplexers and other programmable + * circuit models, such as IOPADs, LUTs, etc. + * + * We keep the memory modules separated from the multiplexers and other + * programmable circuit models, for the sake of supporting + * various configuration schemes. + * By following such organiztion, the Verilog modules of the circuit models + * implements the functionality (circuit logic) only, while the memory Verilog + * modules implements the memory circuits as well as configuration protocols. + * For example, the local decoders of multiplexers are implemented in the + * memory modules. + * Take another example, the memory circuit can implement the scan-chain or + * memory-bank organization for the memories. + ********************************************************************/ +void build_memory_modules(ModuleManager& module_manager, + const MuxLibrary& mux_lib, + const CircuitLibrary& circuit_lib, + const e_config_protocol_type& sram_orgz_type) { + vtr::ScopedStartFinishTimer timer("Build memory modules"); + + /* Create the memory circuits for the multiplexer */ + for (auto mux : mux_lib.muxes()) { + const MuxGraph& mux_graph = mux_lib.mux_graph(mux); + CircuitModelId mux_model = mux_lib.mux_circuit_model(mux); + /* Bypass the non-MUX circuit models (i.e., LUTs). + * They should be handled in a different way + * Memory circuits of LUT includes both regular and mode-select ports + */ + if (CIRCUIT_MODEL_MUX != circuit_lib.model_type(mux_model)) { + continue; + } + /* Create a Verilog module for the memories used by the multiplexer */ + build_mux_memory_module(module_manager, circuit_lib, sram_orgz_type, mux_model, mux_graph); + } + + /* Create the memory circuits for non-MUX circuit models. + * In this case, the memory modules are designed to interface + * the mode-select ports + */ + for (const auto& model : circuit_lib.models()) { + /* Bypass MUXes, they have already been considered */ + if (CIRCUIT_MODEL_MUX == circuit_lib.model_type(model)) { + continue; + } + /* Bypass those modules without any SRAM ports */ + std::vector sram_ports = circuit_lib.model_ports_by_type(model, CIRCUIT_MODEL_PORT_SRAM, true); + if (0 == sram_ports.size()) { + continue; + } + /* Find the name of memory module */ + /* Get the total number of SRAMs */ + size_t num_mems = 0; + for (const auto& port : sram_ports) { + num_mems += circuit_lib.port_size(port); + } + /* Get the circuit model for the memory circuit used by the multiplexer */ + std::vector sram_models = find_circuit_sram_models(circuit_lib, model); + /* Should have only 1 SRAM model */ + VTR_ASSERT( 1 == sram_models.size() ); + + /* Create the module name for the memory block */ + std::string module_name = generate_memory_module_name(circuit_lib, model, sram_models[0], std::string(MEMORY_MODULE_POSTFIX)); + + /* Create a Verilog module for the memories used by the circuit model */ + build_memory_module(module_manager, circuit_lib, sram_orgz_type, module_name, sram_models[0], num_mems); + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fabric/build_memory_modules.h b/openfpga/src/fabric/build_memory_modules.h new file mode 100644 index 000000000..2728d1d81 --- /dev/null +++ b/openfpga/src/fabric/build_memory_modules.h @@ -0,0 +1,25 @@ +#ifndef BUILD_MEMORY_MODULES_H +#define BUILD_MEMORY_MODULES_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "circuit_library.h" +#include "mux_library.h" +#include "module_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void build_memory_modules(ModuleManager& module_manager, + const MuxLibrary& mux_lib, + const CircuitLibrary& circuit_lib, + const e_config_protocol_type& sram_orgz_type); + +} /* end namespace openfpga */ + +#endif From 895d5b5a0a756bc1b297a982df03ebf9e8f44168 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 12 Feb 2020 20:25:05 -0700 Subject: [PATCH 130/645] add utils for grid module builder --- .../src/base/openfpga_interconnect_types.h | 15 +++ openfpga/src/base/openfpga_reserved_words.h | 6 + .../src/fabric/build_grid_module_utils.cpp | 111 ++++++++++++++++++ openfpga/src/fabric/build_grid_module_utils.h | 34 ++++++ 4 files changed, 166 insertions(+) create mode 100644 openfpga/src/base/openfpga_interconnect_types.h create mode 100644 openfpga/src/fabric/build_grid_module_utils.cpp create mode 100644 openfpga/src/fabric/build_grid_module_utils.h diff --git a/openfpga/src/base/openfpga_interconnect_types.h b/openfpga/src/base/openfpga_interconnect_types.h new file mode 100644 index 000000000..cbdd1535e --- /dev/null +++ b/openfpga/src/base/openfpga_interconnect_types.h @@ -0,0 +1,15 @@ +#ifndef OPENFPGA_INTERCONNECT_TYPES_H +#define OPENFPGA_INTERCONNECT_TYPES_H + +/* begin namespace openfpga */ +namespace openfpga { + +enum e_pin2pin_interc_type { + INPUT2INPUT_INTERC, + OUTPUT2OUTPUT_INTERC, + NUM_PIN2PIN_INTERC_TYPES +}; + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/base/openfpga_reserved_words.h b/openfpga/src/base/openfpga_reserved_words.h index 6e761de87..345c1183b 100644 --- a/openfpga/src/base/openfpga_reserved_words.h +++ b/openfpga/src/base/openfpga_reserved_words.h @@ -7,6 +7,10 @@ #ifndef OPENFPGA_RESERVED_WORDS_H #define OPENFPGA_RESERVED_WORDS_H +/* begin namespace openfpga */ +namespace openfpga { + + /* Grid naming constant strings */ constexpr char* GRID_MODULE_NAME_PREFIX = "grid_"; @@ -25,4 +29,6 @@ constexpr char* CONNECTION_BLOCK_MUX_INSTANCE_PREFIX = "mux_"; /* Bitstream file strings */ constexpr char* BITSTREAM_XML_FILE_NAME_POSTFIX = "_bitstream.xml"; +} /* end namespace openfpga */ + #endif diff --git a/openfpga/src/fabric/build_grid_module_utils.cpp b/openfpga/src/fabric/build_grid_module_utils.cpp new file mode 100644 index 000000000..1386e93fe --- /dev/null +++ b/openfpga/src/fabric/build_grid_module_utils.cpp @@ -0,0 +1,111 @@ +/******************************************************************** + * This file includes most utilized functions for grid module builders + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_assert.h" + +/* Headers from openfpgautil library */ +#include "openfpga_side_manager.h" + +/* Headers from vpr library */ +#include "vpr_utils.h" + +#include "openfpga_naming.h" + +#include "build_grid_module_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Find the side where I/O pins locate on a grid I/O block + * 1. I/O grids on the top side of FPGA only have ports on its bottom side + * 2. I/O grids on the right side of FPGA only have ports on its left side + * 3. I/O grids on the bottom side of FPGA only have ports on its top side + * 4. I/O grids on the left side of FPGA only have ports on its right side + *******************************************************************/ +e_side find_grid_module_pin_side(t_physical_tile_type_ptr grid_type_descriptor, + const e_side& border_side) { + /* We must have an regular (non-I/O) type here */ + VTR_ASSERT(true == is_io_type(grid_type_descriptor)); + SideManager side_manager(border_side); + return side_manager.get_opposite(); +} + +/******************************************************************** + * Add module nets to connect a port of child pb_module + * to the grid module + *******************************************************************/ +void add_grid_module_net_connect_pb_graph_pin(ModuleManager& module_manager, + const ModuleId& grid_module, + const ModuleId& child_module, + const size_t& child_instance, + t_physical_tile_type_ptr grid_type_descriptor, + t_pb_graph_pin* pb_graph_pin, + const e_side& border_side, + const e_pin2pin_interc_type& pin2pin_interc_type) { + /* Find the pin side for I/O grids*/ + std::vector grid_pin_sides; + /* For I/O grids, we care only one side + * Otherwise, we will iterate all the 4 sides + */ + if (true == is_io_type(grid_type_descriptor)) { + grid_pin_sides.push_back(find_grid_module_pin_side(grid_type_descriptor, border_side)); + } else { + grid_pin_sides.push_back(TOP); + grid_pin_sides.push_back(RIGHT); + grid_pin_sides.push_back(BOTTOM); + grid_pin_sides.push_back(LEFT); + } + + /* num_pins/capacity = the number of pins that each type_descriptor has. + * Capacity defines the number of type_descriptors in each grid + * so the pin index at grid level = pin_index_in_type_descriptor + * + type_descriptor_index_in_capacity * num_pins_per_type_descriptor + */ + size_t grid_pin_index = pb_graph_pin->pin_count_in_cluster + + child_instance * grid_type_descriptor->num_pins / grid_type_descriptor->capacity; + int pin_height = grid_type_descriptor->pin_height_offset[grid_pin_index]; + int pin_width = grid_type_descriptor->pin_width_offset[grid_pin_index]; + for (const e_side& side : grid_pin_sides) { + if (true != grid_type_descriptor->pinloc[pin_width][pin_height][side][grid_pin_index]) { + continue; + } + /* Reach here, it means this pin is on this side */ + /* Create a net to connect the grid pin to child module pin */ + ModuleNetId net = module_manager.create_module_net(grid_module); + /* Find the port in grid_module */ + vtr::Point dummy_coordinate; + std::string grid_port_name = generate_grid_port_name(dummy_coordinate, pin_width, pin_height, side, grid_pin_index, false); + ModulePortId grid_module_port_id = module_manager.find_module_port(grid_module, grid_port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(grid_module, grid_module_port_id)); + /* Grid port always has only 1 pin, it is assumed when adding these ports to the module + * if you need a change, please also change the port adding codes + */ + size_t grid_module_pin_id = 0; + /* Find the port in child module */ + std::string child_module_port_name = generate_pb_type_port_name(pb_graph_pin->port); + ModulePortId child_module_port_id = module_manager.find_module_port(child_module, child_module_port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(child_module, child_module_port_id)); + size_t child_module_pin_id = pb_graph_pin->pin_number; + /* Add net sources and sinks: + * For input-to-input connection, net_source is grid pin, while net_sink is pb_graph_pin + * For output-to-output connection, net_source is pb_graph_pin, while net_sink is grid pin + */ + switch (pin2pin_interc_type) { + case INPUT2INPUT_INTERC: + module_manager.add_module_net_source(grid_module, net, grid_module, 0, grid_module_port_id, grid_module_pin_id); + module_manager.add_module_net_sink(grid_module, net, child_module, child_instance, child_module_port_id, child_module_pin_id); + break; + case OUTPUT2OUTPUT_INTERC: + module_manager.add_module_net_source(grid_module, net, child_module, child_instance, child_module_port_id, child_module_pin_id); + module_manager.add_module_net_sink(grid_module, net, grid_module, 0, grid_module_port_id, grid_module_pin_id); + break; + default: + VTR_LOG_ERROR("Invalid pin-to-pin interconnection type!\n"); + exit(1); + } + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fabric/build_grid_module_utils.h b/openfpga/src/fabric/build_grid_module_utils.h new file mode 100644 index 000000000..654551fc5 --- /dev/null +++ b/openfpga/src/fabric/build_grid_module_utils.h @@ -0,0 +1,34 @@ +#ifndef BUILD_GRID_MODULE_UTILS_H +#define BUILD_GRID_MODULE_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +/* Headers from readarch library */ +#include "physical_types.h" + +#include "openfpga_interconnect_types.h" +#include "module_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +e_side find_grid_module_pin_side(t_physical_tile_type_ptr grid_type_descriptor, + const e_side& border_side); + +void add_grid_module_net_connect_pb_graph_pin(ModuleManager& module_manager, + const ModuleId& grid_module, + const ModuleId& child_module, + const size_t& child_instance, + t_physical_tile_type_ptr grid_type_descriptor, + t_pb_graph_pin* pb_graph_pin, + const e_side& border_side, + const enum e_pin2pin_interc_type& pin2pin_interc_type); + +} /* end namespace openfpga */ + +#endif From 59d579425efb511d1d31c7050765660524d56534 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 12 Feb 2020 20:48:07 -0700 Subject: [PATCH 131/645] add utils for duplicate pins in grid module builder --- .../build_grid_module_duplicated_pins.cpp | 283 ++++++++++++++++++ .../build_grid_module_duplicated_pins.h | 32 ++ 2 files changed, 315 insertions(+) create mode 100644 openfpga/src/fabric/build_grid_module_duplicated_pins.cpp create mode 100644 openfpga/src/fabric/build_grid_module_duplicated_pins.h diff --git a/openfpga/src/fabric/build_grid_module_duplicated_pins.cpp b/openfpga/src/fabric/build_grid_module_duplicated_pins.cpp new file mode 100644 index 000000000..fc1f754df --- /dev/null +++ b/openfpga/src/fabric/build_grid_module_duplicated_pins.cpp @@ -0,0 +1,283 @@ +/******************************************************************** + * This file includes functions that are used to add duplicated + * pins to each side of a grid + * + * These functions are located in this file, being separated from + * the default functions in build_grid_module.cpp + * This allows us to keep new features easy to be maintained. + * + * Please follow this rules when creating new features! + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_assert.h" + +/* Headers from vpr library */ +#include "vpr_utils.h" + +#include "openfpga_naming.h" +#include "openfpga_interconnect_types.h" + +#include "build_grid_module_utils.h" +#include "build_grid_module_duplicated_pins.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * This function adds pb_type ports to top-level grid module with duplication + * For each pin at each side, we create two pins which are short-wired + * They are driven by the same pin, e.g., pinA in the child module + * But in this top module, we will create two pins, each of which indicates + * the physical location of pin. + * Take the following example: + * One is called pinA_upper which is located close to the top side of this grid + * The other is called pinA_lower which is located close to the bottom side of this grid + * + * Similarly, we duplicate pins at TOP, RIGHT, BOTTOM and LEFT sides. + * For LEFT side, upper and lower pins carry the indication in physical location as RIGHT side. + * For TOP and BOTTOM side, upper pin is located close to the left side of a grid, while lower + * pin is located close to the right side of a grid + * + * pinB_upper pinB_lower + * ^ ^ + * | | + * ---------------+ + * |--->pinA_upper + * | + * Grid | + * | + * |--->pinA_lower + * ---------------+ + *******************************************************************/ +void add_grid_module_duplicated_pb_type_ports(ModuleManager& module_manager, + const ModuleId& grid_module, + t_physical_tile_type_ptr grid_type_descriptor, + const e_side& border_side) { + /* Ensure that we have a valid grid_type_descriptor */ + VTR_ASSERT(false == is_empty_type(grid_type_descriptor)); + + /* Find the pin side for I/O grids*/ + std::vector grid_pin_sides; + /* For I/O grids, we care only one side + * Otherwise, we will iterate all the 4 sides + */ + if (true == is_io_type(grid_type_descriptor)) { + grid_pin_sides.push_back(find_grid_module_pin_side(grid_type_descriptor, border_side)); + } else { + grid_pin_sides = {TOP, RIGHT, BOTTOM, LEFT}; + } + + /* Create a map between pin class type and grid pin direction */ + std::map pin_type2type_map; + pin_type2type_map[RECEIVER] = ModuleManager::MODULE_INPUT_PORT; + pin_type2type_map[DRIVER] = ModuleManager::MODULE_OUTPUT_PORT; + + /* Iterate over sides, height and pins */ + for (const e_side& side : grid_pin_sides) { + for (int iwidth = 0; iwidth < grid_type_descriptor->width; ++iwidth) { + for (int iheight = 0; iheight < grid_type_descriptor->height; ++iheight) { + for (int ipin = 0; ipin < grid_type_descriptor->num_pins; ++ipin) { + if (true != grid_type_descriptor->pinloc[iwidth][iheight][side][ipin]) { + continue; + } + /* Reach here, it means this pin is on this side */ + int class_id = grid_type_descriptor->pin_class[ipin]; + e_pin_type pin_class_type = grid_type_descriptor->class_inf[class_id].type; + /* Generate the pin name + * For each RECEIVER PIN or DRIVER PIN for direct connection, + * we do not duplicate in these cases */ + if ( (RECEIVER == pin_class_type) + /* Xifan: I assume that each direct connection pin must have Fc=0. */ + || ( (DRIVER == pin_class_type) && (0. == grid_type_descriptor->fc_specs[ipin].fc_value) ) ) { + vtr::Point dummy_coordinate; + std::string port_name = generate_grid_port_name(dummy_coordinate, iwidth, iheight, side, ipin, false); + BasicPort grid_port(port_name, 0, 0); + /* Add the port to the module */ + module_manager.add_port(grid_module, grid_port, pin_type2type_map[pin_class_type]); + } else { + /* For each DRIVER pin, we create two copies. + * One with a postfix of upper, indicating it is located on the upper part of a side + * The other with a postfix of lower, indicating it is located on the lower part of a side + */ + VTR_ASSERT(DRIVER == pin_class_type); + std::string upper_port_name = generate_grid_duplicated_port_name(iwidth, iheight, side, ipin, true); + BasicPort grid_upper_port(upper_port_name, 0, 0); + /* Add the port to the module */ + module_manager.add_port(grid_module, grid_upper_port, pin_type2type_map[pin_class_type]); + + std::string lower_port_name = generate_grid_duplicated_port_name(iwidth, iheight, side, ipin, false); + BasicPort grid_lower_port(lower_port_name, 0, 0); + /* Add the port to the module */ + module_manager.add_port(grid_module, grid_lower_port, pin_type2type_map[pin_class_type]); + } + } + } + } + } +} + +/******************************************************************** + * Add module nets to connect a port of child pb_module + * to the duplicated pins of grid module + * Note: This function SHOULD be ONLY applied to pb_graph output pins + * of the child module. + * For each such pin, we connect it to two outputs of the grid module + * one is named after "upper", and the other is named after "lower" + *******************************************************************/ +static +void add_grid_module_net_connect_duplicated_pb_graph_pin(ModuleManager& module_manager, + const ModuleId& grid_module, + const ModuleId& child_module, + const size_t& child_instance, + t_physical_tile_type_ptr grid_type_descriptor, + t_pb_graph_pin* pb_graph_pin, + const e_side& border_side, + const e_pin2pin_interc_type& pin2pin_interc_type) { + /* Make sure this is ONLY applied to output pins */ + VTR_ASSERT(OUTPUT2OUTPUT_INTERC == pin2pin_interc_type); + + /* Find the pin side for I/O grids*/ + std::vector grid_pin_sides; + /* For I/O grids, we care only one side + * Otherwise, we will iterate all the 4 sides + */ + if (true == is_io_type(grid_type_descriptor)) { + grid_pin_sides.push_back(find_grid_module_pin_side(grid_type_descriptor, border_side)); + } else { + grid_pin_sides.push_back(TOP); + grid_pin_sides.push_back(RIGHT); + grid_pin_sides.push_back(BOTTOM); + grid_pin_sides.push_back(LEFT); + } + + /* num_pins/capacity = the number of pins that each type_descriptor has. + * Capacity defines the number of type_descriptors in each grid + * so the pin index at grid level = pin_index_in_type_descriptor + * + type_descriptor_index_in_capacity * num_pins_per_type_descriptor + */ + size_t grid_pin_index = pb_graph_pin->pin_count_in_cluster + + child_instance * grid_type_descriptor->num_pins / grid_type_descriptor->capacity; + int pin_width = grid_type_descriptor->pin_height_offset[grid_pin_index]; + int pin_height = grid_type_descriptor->pin_height_offset[grid_pin_index]; + for (const e_side& side : grid_pin_sides) { + if (true != grid_type_descriptor->pinloc[pin_width][pin_height][side][grid_pin_index]) { + continue; + } + + /* Pins for direct connection are NOT duplicated. + * Follow the traditional recipe when adding nets! + * Xifan: I assume that each direct connection pin must have Fc=0. + */ + if (0. == grid_type_descriptor->fc_specs[grid_pin_index].fc_value) { + /* Create a net to connect the grid pin to child module pin */ + ModuleNetId net = module_manager.create_module_net(grid_module); + /* Find the port in grid_module */ + vtr::Point dummy_coordinate; + std::string grid_port_name = generate_grid_port_name(dummy_coordinate, pin_width, pin_height, side, grid_pin_index, false); + ModulePortId grid_module_port_id = module_manager.find_module_port(grid_module, grid_port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(grid_module, grid_module_port_id)); + + /* Grid port always has only 1 pin, it is assumed when adding these ports to the module + * if you need a change, please also change the port adding codes + */ + size_t grid_module_pin_id = 0; + /* Find the port in child module */ + std::string child_module_port_name = generate_pb_type_port_name(pb_graph_pin->port); + ModulePortId child_module_port_id = module_manager.find_module_port(child_module, child_module_port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(child_module, child_module_port_id)); + size_t child_module_pin_id = pb_graph_pin->pin_number; + /* Add net sources and sinks: + * For output-to-output connection, net_source is pb_graph_pin, while net_sink is grid pin + */ + module_manager.add_module_net_source(grid_module, net, child_module, child_instance, child_module_port_id, child_module_pin_id); + module_manager.add_module_net_sink(grid_module, net, grid_module, 0, grid_module_port_id, grid_module_pin_id); + continue; + } + /* Reach here, it means this pin is on this side */ + /* Create a net to connect the grid pin to child module pin */ + ModuleNetId net = module_manager.create_module_net(grid_module); + /* Find the upper port in grid_module */ + std::string grid_upper_port_name = generate_grid_duplicated_port_name(pin_width, pin_height, side, grid_pin_index, true); + ModulePortId grid_module_upper_port_id = module_manager.find_module_port(grid_module, grid_upper_port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(grid_module, grid_module_upper_port_id)); + + /* Find the lower port in grid_module */ + std::string grid_lower_port_name = generate_grid_duplicated_port_name(pin_width, pin_height, side, grid_pin_index, false); + ModulePortId grid_module_lower_port_id = module_manager.find_module_port(grid_module, grid_lower_port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(grid_module, grid_module_lower_port_id)); + + /* Grid port always has only 1 pin, it is assumed when adding these ports to the module + * if you need a change, please also change the port adding codes + */ + size_t grid_module_pin_id = 0; + /* Find the port in child module */ + std::string child_module_port_name = generate_pb_type_port_name(pb_graph_pin->port); + ModulePortId child_module_port_id = module_manager.find_module_port(child_module, child_module_port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(child_module, child_module_port_id)); + size_t child_module_pin_id = pb_graph_pin->pin_number; + + /* Add net sources and sinks: + * For output-to-output connection, + * net_source is pb_graph_pin, + * while net_sinks are grid upper pin and grid lower pin + */ + module_manager.add_module_net_source(grid_module, net, child_module, child_instance, child_module_port_id, child_module_pin_id); + module_manager.add_module_net_sink(grid_module, net, grid_module, 0, grid_module_upper_port_id, grid_module_pin_id); + module_manager.add_module_net_sink(grid_module, net, grid_module, 0, grid_module_lower_port_id, grid_module_pin_id); + } +} + +/******************************************************************** + * Add module nets to connect a port of child pb_module + * to the duplicated ports of grid module + *******************************************************************/ +void add_grid_module_nets_connect_duplicated_pb_type_ports(ModuleManager& module_manager, + const ModuleId& grid_module, + const ModuleId& child_module, + const size_t& child_instance, + t_physical_tile_type_ptr grid_type_descriptor, + const e_side& border_side) { + /* Ensure that we have a valid grid_type_descriptor */ + VTR_ASSERT(false == is_empty_type(grid_type_descriptor)); + + for (t_logical_block_type_ptr lb_type : grid_type_descriptor->equivalent_sites) { + t_pb_graph_node* top_pb_graph_node = lb_type->pb_graph_head; + VTR_ASSERT(nullptr != top_pb_graph_node); + + for (int iport = 0; iport < top_pb_graph_node->num_input_ports; ++iport) { + for (int ipin = 0; ipin < top_pb_graph_node->num_input_pins[iport]; ++ipin) { + add_grid_module_net_connect_pb_graph_pin(module_manager, grid_module, + child_module, child_instance, + grid_type_descriptor, + &(top_pb_graph_node->input_pins[iport][ipin]), + border_side, + INPUT2INPUT_INTERC); + + } + } + + for (int iport = 0; iport < top_pb_graph_node->num_output_ports; ++iport) { + for (int ipin = 0; ipin < top_pb_graph_node->num_output_pins[iport]; ++ipin) { + add_grid_module_net_connect_duplicated_pb_graph_pin(module_manager, grid_module, + child_module, child_instance, + grid_type_descriptor, + &(top_pb_graph_node->output_pins[iport][ipin]), + border_side, + OUTPUT2OUTPUT_INTERC); + } + } + + for (int iport = 0; iport < top_pb_graph_node->num_clock_ports; ++iport) { + for (int ipin = 0; ipin < top_pb_graph_node->num_clock_pins[iport]; ++ipin) { + add_grid_module_net_connect_pb_graph_pin(module_manager, grid_module, + child_module, child_instance, + grid_type_descriptor, + &(top_pb_graph_node->clock_pins[iport][ipin]), + border_side, + INPUT2INPUT_INTERC); + } + } + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fabric/build_grid_module_duplicated_pins.h b/openfpga/src/fabric/build_grid_module_duplicated_pins.h new file mode 100644 index 000000000..2bdd56dea --- /dev/null +++ b/openfpga/src/fabric/build_grid_module_duplicated_pins.h @@ -0,0 +1,32 @@ +#ifndef BUILD_GRID_MODULE_DUPLICATED_PINS_H +#define BUILD_GRID_MODULE_DUPLICATED_PINS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "physical_types.h" +#include "module_manager.h" +#include "openfpga_side_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void add_grid_module_duplicated_pb_type_ports(ModuleManager& module_manager, + const ModuleId& grid_module, + t_physical_tile_type_ptr grid_type_descriptor, + const e_side& border_side); + +void add_grid_module_nets_connect_duplicated_pb_type_ports(ModuleManager& module_manager, + const ModuleId& grid_module, + const ModuleId& child_module, + const size_t& child_instance, + t_physical_tile_type_ptr grid_type_descriptor, + const e_side& border_side); + +} /* end namespace openfpga */ + +#endif From 072965cd64ffa237fada1210bdfe1471e49c6dcd Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 13 Feb 2020 15:27:16 -0700 Subject: [PATCH 132/645] make grid module builder online; basic support on physical tiles --- openfpga/src/base/openfpga_build_fabric.cpp | 4 +- .../src/base/openfpga_interconnect_types.h | 7 + openfpga/src/base/openfpga_naming.cpp | 42 +- openfpga/src/base/openfpga_naming.h | 16 +- openfpga/src/base/openfpga_reserved_words.h | 4 + openfpga/src/fabric/build_device_module.cpp | 14 +- openfpga/src/fabric/build_device_module.h | 3 +- .../build_grid_module_duplicated_pins.cpp | 1 + openfpga/src/fabric/build_grid_modules.cpp | 1107 +++++++++++++++++ openfpga/src/fabric/build_grid_modules.h | 30 + 10 files changed, 1173 insertions(+), 55 deletions(-) create mode 100644 openfpga/src/fabric/build_grid_modules.cpp create mode 100644 openfpga/src/fabric/build_grid_modules.h diff --git a/openfpga/src/base/openfpga_build_fabric.cpp b/openfpga/src/base/openfpga_build_fabric.cpp index f8fde4a31..c15cf548f 100644 --- a/openfpga/src/base/openfpga_build_fabric.cpp +++ b/openfpga/src/base/openfpga_build_fabric.cpp @@ -61,6 +61,7 @@ void build_fabric(OpenfpgaContext& openfpga_context, const Command& cmd, const CommandContext& cmd_context) { CommandOptionId opt_compress_routing = cmd.option("compress_routing"); + CommandOptionId opt_duplicate_grid_pin = cmd.option("duplicate_grid_pin"); CommandOptionId opt_verbose = cmd.option("verbose"); if (true == cmd_context.option_enable(cmd, opt_compress_routing)) { @@ -70,7 +71,8 @@ void build_fabric(OpenfpgaContext& openfpga_context, VTR_LOG("\n"); openfpga_context.mutable_module_graph() = build_device_module_graph(g_vpr_ctx.device(), - const_cast(openfpga_context)); + const_cast(openfpga_context), + cmd_context.option_enable(cmd, opt_duplicate_grid_pin)); } } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_interconnect_types.h b/openfpga/src/base/openfpga_interconnect_types.h index cbdd1535e..a402323f0 100644 --- a/openfpga/src/base/openfpga_interconnect_types.h +++ b/openfpga/src/base/openfpga_interconnect_types.h @@ -10,6 +10,13 @@ enum e_pin2pin_interc_type { NUM_PIN2PIN_INTERC_TYPES }; +enum e_circuit_pb_port_type { + CIRCUIT_PB_PORT_INPUT, + CIRCUIT_PB_PORT_OUTPUT, + CIRCUIT_PB_PORT_CLOCK, + NUM_CIRCUIT_PB_PORT_TYPES +}; + } /* end namespace openfpga */ #endif diff --git a/openfpga/src/base/openfpga_naming.cpp b/openfpga/src/base/openfpga_naming.cpp index 19c58a1bc..7aaafff5d 100644 --- a/openfpga/src/base/openfpga_naming.cpp +++ b/openfpga/src/base/openfpga_naming.cpp @@ -11,6 +11,7 @@ #include "openfpga_side_manager.h" #include "pb_type_utils.h" #include "circuit_library_utils.h" +#include "openfpga_reserved_words.h" #include "openfpga_naming.h" /* begin namespace openfpga */ @@ -1116,7 +1117,9 @@ std::string generate_grid_block_instance_name(const std::string& prefix, } /********************************************************************* - * Generate the module name of a physical block + * Generate the module name of a logical block type (pb_type) + * Since the logical block does not carry any physical attributes, + * this logical block will have a common prefix 'logical_type' * To ensure a unique name for each physical block inside the graph of complex blocks * (pb_graph_nodes), this function trace backward to the top-level node * in the graph and add the name of these parents @@ -1126,8 +1129,7 @@ std::string generate_grid_block_instance_name(const std::string& prefix, * TODO: to make sure the length of this name does not exceed the size of * chars in a line of a file!!! **********************************************************************/ -std::string generate_physical_block_module_name(const std::string& prefix, - t_pb_type* physical_pb_type) { +std::string generate_physical_block_module_name(t_pb_type* physical_pb_type) { std::string module_name(physical_pb_type->name); t_pb_type* parent_pb_type = physical_pb_type; @@ -1164,7 +1166,7 @@ std::string generate_physical_block_module_name(const std::string& prefix, } /* Add the prefix */ - module_name = prefix + module_name; + module_name = LOGICAL_MODULE_NAME_PREFIX + module_name; return module_name; } @@ -1173,37 +1175,9 @@ std::string generate_physical_block_module_name(const std::string& prefix, /********************************************************************* * Generate the instance name for physical block with a given index **********************************************************************/ -std::string generate_physical_block_instance_name(const std::string& prefix, - t_pb_type* pb_type, +std::string generate_physical_block_instance_name(t_pb_type* pb_type, const size_t& index) { - std::string instance_name = generate_physical_block_module_name(prefix, pb_type); - /* Add index to the name */ - instance_name += std::string("_"); - instance_name += std::to_string(index); - - return instance_name; -} - -/********************************************************************* - * This function is a wrapper for the function generate_physical_block_module_name() - * which can automatically decode the io_side and add a prefix - **********************************************************************/ -std::string generate_grid_physical_block_module_name(const std::string& prefix, - t_pb_type* pb_type, - const e_side& border_side) { - std::string module_name_prefix = generate_grid_block_prefix(prefix, border_side); - return generate_physical_block_module_name(module_name_prefix, pb_type); -} - -/********************************************************************* - * Generate the instance name for physical block in Grid with a given index - **********************************************************************/ -std::string generate_grid_physical_block_instance_name(const std::string& prefix, - t_pb_type* pb_type, - const e_side& border_side, - const size_t& index) { - std::string module_name_prefix = generate_grid_block_prefix(prefix, border_side); - std::string instance_name = generate_physical_block_module_name(module_name_prefix, pb_type); + std::string instance_name = generate_physical_block_module_name(pb_type); /* Add index to the name */ instance_name += std::string("_"); instance_name += std::to_string(index); diff --git a/openfpga/src/base/openfpga_naming.h b/openfpga/src/base/openfpga_naming.h index 0c0c640ca..4bc22f3c9 100644 --- a/openfpga/src/base/openfpga_naming.h +++ b/openfpga/src/base/openfpga_naming.h @@ -219,23 +219,11 @@ std::string generate_grid_block_instance_name(const std::string& prefix, const e_side& io_side, const vtr::Point& grid_coord); -std::string generate_physical_block_module_name(const std::string& prefix, - t_pb_type* physical_pb_type); +std::string generate_physical_block_module_name(t_pb_type* physical_pb_type); -std::string generate_physical_block_instance_name(const std::string& prefix, - t_pb_type* pb_type, +std::string generate_physical_block_instance_name(t_pb_type* pb_type, const size_t& index); -std::string generate_grid_physical_block_module_name(const std::string& prefix, - t_pb_type* pb_type, - const e_side& border_side); - -std::string generate_grid_physical_block_instance_name(const std::string& prefix, - t_pb_type* pb_type, - const e_side& border_side, - const size_t& index); - - e_side find_grid_border_side(const vtr::Point& device_size, const vtr::Point& grid_coordinate); diff --git a/openfpga/src/base/openfpga_reserved_words.h b/openfpga/src/base/openfpga_reserved_words.h index 345c1183b..f04490c0d 100644 --- a/openfpga/src/base/openfpga_reserved_words.h +++ b/openfpga/src/base/openfpga_reserved_words.h @@ -10,9 +10,13 @@ /* begin namespace openfpga */ namespace openfpga { +/* IO PORT */ +/* Prefix of global input, output and inout ports of FPGA fabric */ +constexpr char* GIO_INOUT_PREFIX = "gfpga_pad_"; /* Grid naming constant strings */ constexpr char* GRID_MODULE_NAME_PREFIX = "grid_"; +constexpr char* LOGICAL_MODULE_NAME_PREFIX = "logical_tile_"; /* Memory naming constant strings */ constexpr char* GRID_MEM_INSTANCE_PREFIX = "mem_"; diff --git a/openfpga/src/fabric/build_device_module.cpp b/openfpga/src/fabric/build_device_module.cpp index 40d856022..d6f759b30 100644 --- a/openfpga/src/fabric/build_device_module.cpp +++ b/openfpga/src/fabric/build_device_module.cpp @@ -14,7 +14,7 @@ #include "build_lut_modules.h" #include "build_wire_modules.h" #include "build_memory_modules.h" -//#include "build_grid_modules.h" +#include "build_grid_modules.h" //#include "build_routing_modules.h" //#include "build_top_module.h" #include "build_device_module.h" @@ -27,7 +27,8 @@ namespace openfpga { * for a FPGA fabric *******************************************************************/ ModuleManager build_device_module_graph(const DeviceContext& vpr_device_ctx, - const OpenfpgaContext& openfpga_ctx) { + const OpenfpgaContext& openfpga_ctx, + const bool& duplicate_grid_pin) { vtr::ScopedStartFinishTimer timer("Build fabric module graph"); /* Module manager to be built */ @@ -67,9 +68,12 @@ ModuleManager build_device_module_graph(const DeviceContext& vpr_device_ctx, openfpga_ctx.arch().config_protocol.type()); /* Build grid and programmable block modules */ - //build_grid_modules(module_manager, arch.spice->circuit_lib, mux_lib, - // arch.sram_inf.verilog_sram_inf_orgz->type, sram_model, - // TRUE == vpr_setup.FPGA_SPICE_Opts.duplicate_grid_pin); + build_grid_modules(module_manager, vpr_device_ctx, + openfpga_ctx.vpr_device_annotation(), + openfpga_ctx.arch().circuit_lib, + openfpga_ctx.mux_lib(), + openfpga_ctx.arch().config_protocol.type(), + sram_model, duplicate_grid_pin); //if (TRUE == vpr_setup.FPGA_SPICE_Opts.compact_routing_hierarchy) { // build_unique_routing_modules(module_manager, L_device_rr_gsb, arch.spice->circuit_lib, diff --git a/openfpga/src/fabric/build_device_module.h b/openfpga/src/fabric/build_device_module.h index 1e6bb2226..fd646844f 100644 --- a/openfpga/src/fabric/build_device_module.h +++ b/openfpga/src/fabric/build_device_module.h @@ -15,7 +15,8 @@ namespace openfpga { ModuleManager build_device_module_graph(const DeviceContext& vpr_device_ctx, - const OpenfpgaContext& openfpga_ctx); + const OpenfpgaContext& openfpga_ctx, + const bool& duplicate_grid_pin); } /* end namespace openfpga */ diff --git a/openfpga/src/fabric/build_grid_module_duplicated_pins.cpp b/openfpga/src/fabric/build_grid_module_duplicated_pins.cpp index fc1f754df..d63b85998 100644 --- a/openfpga/src/fabric/build_grid_module_duplicated_pins.cpp +++ b/openfpga/src/fabric/build_grid_module_duplicated_pins.cpp @@ -157,6 +157,7 @@ void add_grid_module_net_connect_duplicated_pb_graph_pin(ModuleManager& module_m */ size_t grid_pin_index = pb_graph_pin->pin_count_in_cluster + child_instance * grid_type_descriptor->num_pins / grid_type_descriptor->capacity; + int pin_width = grid_type_descriptor->pin_height_offset[grid_pin_index]; int pin_height = grid_type_descriptor->pin_height_offset[grid_pin_index]; for (const e_side& side : grid_pin_sides) { diff --git a/openfpga/src/fabric/build_grid_modules.cpp b/openfpga/src/fabric/build_grid_modules.cpp new file mode 100644 index 000000000..df0a63d03 --- /dev/null +++ b/openfpga/src/fabric/build_grid_modules.cpp @@ -0,0 +1,1107 @@ +/******************************************************************** + * This file includes functions to print Verilog modules for a Grid + * (CLBs, I/Os, heterogeneous blocks etc.) + *******************************************************************/ +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_geometry.h" +#include "vtr_log.h" +#include "vtr_assert.h" +#include "vtr_time.h" + +/* Headers from vpr library */ +#include "vpr_utils.h" + +#include "circuit_library_utils.h" +#include "openfpga_reserved_words.h" +#include "openfpga_naming.h" +#include "openfpga_interconnect_types.h" +#include "pb_type_utils.h" +#include "pb_graph_utils.h" +#include "module_manager_utils.h" + +#include "build_grid_module_utils.h" +#include "build_grid_module_duplicated_pins.h" +#include "build_grid_modules.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Add ports/pins to a grid module + * This function will iterate over all the pins that are defined + * in type_descripter and give a name by its height, side and index + * + * In particular, for I/O grid, only part of the ports on required + * on a specific side. + *******************************************************************/ +static +void add_grid_module_pb_type_ports(ModuleManager& module_manager, + const ModuleId& grid_module, + t_physical_tile_type_ptr grid_type_descriptor, + const e_side& border_side) { + /* Ensure that we have a valid grid_type_descriptor */ + VTR_ASSERT(nullptr != grid_type_descriptor); + + /* Find the pin side for I/O grids*/ + std::vector grid_pin_sides; + /* For I/O grids, we care only one side + * Otherwise, we will iterate all the 4 sides + */ + if (true == is_io_type(grid_type_descriptor)) { + grid_pin_sides.push_back(find_grid_module_pin_side(grid_type_descriptor, border_side)); + } else { + grid_pin_sides = {TOP, RIGHT, BOTTOM, LEFT}; + } + + /* Create a map between pin class type and grid pin direction */ + std::map pin_type2type_map; + pin_type2type_map[RECEIVER] = ModuleManager::MODULE_INPUT_PORT; + pin_type2type_map[DRIVER] = ModuleManager::MODULE_OUTPUT_PORT; + + /* Iterate over sides, height and pins */ + for (const e_side& side : grid_pin_sides) { + for (int iwidth = 0; iwidth < grid_type_descriptor->width; ++iwidth) { + for (int iheight = 0; iheight < grid_type_descriptor->height; ++iheight) { + for (int ipin = 0; ipin < grid_type_descriptor->num_pins; ++ipin) { + if (true != grid_type_descriptor->pinloc[iwidth][iheight][side][ipin]) { + continue; + } + /* Reach here, it means this pin is on this side */ + int class_id = grid_type_descriptor->pin_class[ipin]; + e_pin_type pin_class_type = grid_type_descriptor->class_inf[class_id].type; + /* Generate the pin name, + * we give a empty coordinate but it will not be used (see details in the function + */ + vtr::Point dummy_coordinate; + std::string port_name = generate_grid_port_name(dummy_coordinate, iwidth, iheight, side, ipin, false); + BasicPort grid_port(port_name, 0, 0); + /* Add the port to the module */ + module_manager.add_port(grid_module, grid_port, pin_type2type_map[pin_class_type]); + } + } + } + } +} + +/******************************************************************** + * Add module nets to connect ports/pins of a grid module + * to its child modules + * This function will iterate over all the pins that are defined + * in type_descripter and find the corresponding pin in the top + * pb_graph_node of the grid + *******************************************************************/ +static +void add_grid_module_nets_connect_pb_type_ports(ModuleManager& module_manager, + const ModuleId& grid_module, + const ModuleId& child_module, + const size_t& child_instance, + t_physical_tile_type_ptr grid_type_descriptor, + const e_side& border_side) { + /* Ensure that we have a valid grid_type_descriptor */ + VTR_ASSERT(nullptr != grid_type_descriptor); + + for (t_logical_block_type_ptr lb_type : grid_type_descriptor->equivalent_sites) { + t_pb_graph_node* top_pb_graph_node = lb_type->pb_graph_head; + VTR_ASSERT(nullptr != top_pb_graph_node); + + for (int iport = 0; iport < top_pb_graph_node->num_input_ports; ++iport) { + for (int ipin = 0; ipin < top_pb_graph_node->num_input_pins[iport]; ++ipin) { + add_grid_module_net_connect_pb_graph_pin(module_manager, grid_module, + child_module, child_instance, + grid_type_descriptor, + &(top_pb_graph_node->input_pins[iport][ipin]), + border_side, + INPUT2INPUT_INTERC); + + } + } + + for (int iport = 0; iport < top_pb_graph_node->num_output_ports; ++iport) { + for (int ipin = 0; ipin < top_pb_graph_node->num_output_pins[iport]; ++ipin) { + add_grid_module_net_connect_pb_graph_pin(module_manager, grid_module, + child_module, child_instance, + grid_type_descriptor, + &(top_pb_graph_node->output_pins[iport][ipin]), + border_side, + OUTPUT2OUTPUT_INTERC); + } + } + + for (int iport = 0; iport < top_pb_graph_node->num_clock_ports; ++iport) { + for (int ipin = 0; ipin < top_pb_graph_node->num_clock_pins[iport]; ++ipin) { + add_grid_module_net_connect_pb_graph_pin(module_manager, grid_module, + child_module, child_instance, + grid_type_descriptor, + &(top_pb_graph_node->clock_pins[iport][ipin]), + border_side, + INPUT2INPUT_INTERC); + } + } + } +} + +/******************************************************************** + * Print Verilog modules of a primitive node in the pb_graph_node graph + * This generic function can support all the different types of primitive nodes + * i.e., Look-Up Tables (LUTs), Flip-flops (FFs) and hard logic blocks such as adders. + * + * The Verilog module will consist of two parts: + * 1. Logic module of the primitive node + * This module performs the logic function of the block + * 2. Memory module of the primitive node + * This module stores the configuration bits for the logic module + * if the logic module is a programmable resource, such as LUT + * + * Verilog module structure: + * + * Primitive block + * +---------------------------------------+ + * | | + * | +---------+ +---------+ | + * in |----->| |--->| |<------|configuration lines + * | | Logic |... | Memory | | + * out|<-----| |--->| | | + * | +---------+ +---------+ | + * | | + * +---------------------------------------+ + * + *******************************************************************/ +static +void build_primitive_block_module(ModuleManager& module_manager, + const VprDeviceAnnotation& device_annotation, + const CircuitLibrary& circuit_lib, + const e_config_protocol_type& sram_orgz_type, + const CircuitModelId& sram_model, + t_pb_graph_node* primitive_pb_graph_node) { + /* Ensure a valid pb_graph_node */ + VTR_ASSERT(nullptr != primitive_pb_graph_node); + + /* Find the circuit model id linked to the pb_graph_node */ + const CircuitModelId& primitive_model = device_annotation.pb_type_circuit_model(primitive_pb_graph_node->pb_type); + + /* Generate the module name for this primitive pb_graph_node*/ + std::string primitive_module_name = generate_physical_block_module_name(primitive_pb_graph_node->pb_type); + + /* Create a module of the primitive LUT and register it to module manager */ + ModuleId primitive_module = module_manager.add_module(primitive_module_name); + /* Ensure that the module has been created and thus unique! */ + VTR_ASSERT(ModuleId::INVALID() != primitive_module); + + /* Note: to cooperate with the pb_type hierarchy and connections, we add the port of primitive pb_type here. + * Since we have linked pb_type ports to circuit models when setting up FPGA-X2P, + * no ports of the circuit model will be missing here + */ + add_primitive_pb_type_ports_to_module_manager(module_manager, primitive_module, + primitive_pb_graph_node->pb_type, device_annotation); + + /* Add configuration ports */ + /* Shared SRAM ports*/ + size_t num_shared_config_bits = find_circuit_num_shared_config_bits(circuit_lib, primitive_model, sram_orgz_type); + if (0 < num_shared_config_bits) { + /* Check: this SRAM organization type must be memory-bank ! */ + VTR_ASSERT( CONFIG_MEM_MEMORY_BANK == sram_orgz_type ); + /* Generate a list of ports */ + add_reserved_sram_ports_to_module_manager(module_manager, primitive_module, + num_shared_config_bits); + } + + /* Regular (independent) SRAM ports */ + size_t num_config_bits = find_circuit_num_config_bits(circuit_lib, primitive_model); + if (0 < num_config_bits) { + add_sram_ports_to_module_manager(module_manager, primitive_module, + circuit_lib, sram_model, sram_orgz_type, + num_config_bits); + } + + /* Find the module id in the module manager */ + ModuleId logic_module = module_manager.find_module(circuit_lib.model_name(primitive_model)); + VTR_ASSERT(ModuleId::INVALID() != logic_module); + size_t logic_instance_id = module_manager.num_instance(primitive_module, logic_module); + /* Add the logic module as a child of primitive module */ + module_manager.add_child_module(primitive_module, logic_module); + + /* Add nets to connect the logic model ports to pb_type ports */ + add_primitive_pb_type_module_nets(module_manager, primitive_module, + logic_module, logic_instance_id, + circuit_lib, primitive_pb_graph_node->pb_type, + device_annotation); + + /* Add the associated memory module as a child of primitive module */ + std::string memory_module_name = generate_memory_module_name(circuit_lib, primitive_model, sram_model, std::string(MEMORY_MODULE_POSTFIX)); + ModuleId memory_module = module_manager.find_module(memory_module_name); + + /* Vectors to record all the memory modules have been added + * They are used to add module nets of configuration bus + */ + std::vector memory_modules; + std::vector memory_instances; + + /* If there is no memory module required, we can skip the assocated net addition */ + if (ModuleId::INVALID() != memory_module) { + size_t memory_instance_id = module_manager.num_instance(primitive_module, memory_module); + /* Add the memory module as a child of primitive module */ + module_manager.add_child_module(primitive_module, memory_module); + /* Set an instance name to bind to a block in bitstream generation */ + module_manager.set_child_instance_name(primitive_module, memory_module, memory_instance_id, memory_module_name); + + /* Add nets to connect regular and mode-select SRAM ports to the SRAM port of memory module */ + add_module_nets_between_logic_and_memory_sram_bus(module_manager, primitive_module, + logic_module, logic_instance_id, + memory_module, memory_instance_id, + circuit_lib, primitive_model); + /* Record memory-related information */ + module_manager.add_configurable_child(primitive_module, memory_module, memory_instance_id); + } + /* Add all the nets to connect configuration ports from memory module to primitive modules + * This is a one-shot addition that covers all the memory modules in this primitive module! + */ + if (false == memory_modules.empty()) { + add_module_nets_memory_config_bus(module_manager, primitive_module, + sram_orgz_type, circuit_lib.design_tech_type(sram_model)); + } + + /* Add global ports to the pb_module: + * This is a much easier job after adding sub modules (instances), + * we just need to find all the global ports from the child modules and build a list of it + */ + add_module_global_ports_from_child_modules(module_manager, primitive_module); + + /* Find the inout ports required by the primitive node, and add them to the module + * This is mainly due to the I/O blocks, which have inout ports for the top-level fabric + */ + if (CIRCUIT_MODEL_IOPAD == circuit_lib.model_type(primitive_model)) { + std::vector primitive_model_inout_ports = circuit_lib.model_ports_by_type(primitive_model, CIRCUIT_MODEL_PORT_INOUT); + for (auto port : primitive_model_inout_ports) { + BasicPort module_port(generate_fpga_global_io_port_name(std::string(GIO_INOUT_PREFIX), circuit_lib, primitive_model), circuit_lib.port_size(port)); + ModulePortId primitive_gpio_port_id = module_manager.add_port(primitive_module, module_port, ModuleManager::MODULE_GPIO_PORT); + ModulePortId logic_gpio_port_id = module_manager.find_module_port(logic_module, circuit_lib.port_prefix(port)); + BasicPort logic_gpio_port = module_manager.module_port(logic_module, logic_gpio_port_id); + VTR_ASSERT(logic_gpio_port.get_width() == module_port.get_width()); + + /* Wire the GPIO port form primitive_module to the logic module!*/ + for (size_t pin_id = 0; pin_id < module_port.pins().size(); ++pin_id) { + ModuleNetId net = module_manager.create_module_net(primitive_module); + module_manager.add_module_net_source(primitive_module, net, primitive_module, 0, primitive_gpio_port_id, module_port.pins()[pin_id]); + module_manager.add_module_net_sink(primitive_module, net, logic_module, logic_instance_id, logic_gpio_port_id, logic_gpio_port.pins()[pin_id]); + } + } + } +} + +/******************************************************************** + * This function add a net for a pin-to-pin connection defined in pb_graph + * It supports two cases for the pin-to-pin connection + * 1. The net source is a pb_graph_pin while the net sink is a pin of an interconnection + * 2. The net source is a pin of an interconnection while the net sink a pb_graph_pin + * The type is enabled by an argument pin2pin_interc_type + *******************************************************************/ +static +void add_module_pb_graph_pin2pin_net(ModuleManager& module_manager, + const ModuleId& pb_module, + const ModuleId& interc_module, + const size_t& interc_instance, + const std::string& interc_port_name, + const size_t& interc_pin_id, + t_pb_graph_pin* pb_graph_pin, + const enum e_pin2pin_interc_type& pin2pin_interc_type) { + + ModuleNetId pin2pin_net = module_manager.create_module_net(pb_module); + + /* Find port and pin ids for the module, which is the parent of pb_graph_pin */ + t_pb_type* pin_pb_type = pb_graph_pin->parent_node->pb_type; + /* Find the module contains the source pin */ + ModuleId pin_pb_type_module = module_manager.find_module(generate_physical_block_module_name(pin_pb_type)); + VTR_ASSERT(true == module_manager.valid_module_id(pin_pb_type_module)); + size_t pin_pb_type_instance = 0; /* Deposite the instance with a zero, which is the default value is the source module is actually pb_module itself */ + if (pin_pb_type_module != pb_module) { + pin_pb_type_instance = pb_graph_pin->parent_node->placement_index; + /* Ensure this is an valid instance */ + VTR_ASSERT(pin_pb_type_instance < module_manager.num_instance(pb_module, pin_pb_type_module)); + } + ModulePortId pin_module_port_id = module_manager.find_module_port(pin_pb_type_module, generate_pb_type_port_name(pb_graph_pin->port)); + VTR_ASSERT(true == module_manager.valid_module_port_id(pin_pb_type_module, pin_module_port_id)); + size_t pin_module_pin_id = pb_graph_pin->pin_number; + /* Ensure this is an valid pin index */ + VTR_ASSERT(pin_module_pin_id < module_manager.module_port(pin_pb_type_module, pin_module_port_id).get_width()); + + /* Find port and pin ids for the interconnection module */ + ModulePortId interc_port_id = module_manager.find_module_port(interc_module, interc_port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(interc_module, interc_port_id)); + /* Ensure this is an valid pin index */ + VTR_ASSERT(interc_pin_id < module_manager.module_port(interc_module, interc_port_id).get_width()); + + /* Add net sources and sinks: + * For input-to-input connection, net_source is pin_graph_pin, while net_sink is interc pin + * For output-to-output connection, net_source is interc pin, while net_sink is pin_graph pin + */ + switch (pin2pin_interc_type) { + case INPUT2INPUT_INTERC: + module_manager.add_module_net_source(pb_module, pin2pin_net, pin_pb_type_module, pin_pb_type_instance, pin_module_port_id, pin_module_pin_id); + module_manager.add_module_net_sink(pb_module, pin2pin_net, interc_module, interc_instance, interc_port_id, interc_pin_id); + break; + case OUTPUT2OUTPUT_INTERC: + module_manager.add_module_net_source(pb_module, pin2pin_net, interc_module, interc_instance, interc_port_id, interc_pin_id); + module_manager.add_module_net_sink(pb_module, pin2pin_net, pin_pb_type_module, pin_pb_type_instance, pin_module_port_id, pin_module_pin_id); + break; + default: + VTR_LOG_ERROR("Invalid pin-to-pin interconnection type!\n"); + exit(1); + } +} + +/******************************************************************** + * We check output_pins of cur_pb_graph_node and its the input_edges + * Built the interconnections between outputs of cur_pb_graph_node and outputs of child_pb_graph_node + * src_pb_graph_node.[in|out]_pins -----------------> des_pb_graph_node.[in|out]pins + * /|\ + * | + * input_pins, edges, output_pins + * + * This function does the following task: + * 1. identify pin interconnection type, + * 2. Identify the number of fan-in (Consider interconnection edges of only selected mode) + * 3. Add mux/direct connection as a child module to pb_module + * 4. Add nets related to the mux/direction + *******************************************************************/ +static +void add_module_pb_graph_pin_interc(ModuleManager& module_manager, + const ModuleId& pb_module, + std::vector& memory_modules, + std::vector& memory_instances, + const VprDeviceAnnotation& device_annotation, + const CircuitLibrary& circuit_lib, + t_pb_graph_pin* des_pb_graph_pin, + t_mode* physical_mode) { + /* Find the number of fan-in and detailed interconnection information + * related to the destination pb_graph_pin + */ + t_interconnect* cur_interc = pb_graph_pin_interc(des_pb_graph_pin, physical_mode); + size_t fan_in = pb_graph_pin_inputs(des_pb_graph_pin, cur_interc).size(); + + /* If no interconnection is needed, we can return early */ + if ((nullptr == cur_interc) || (0 == fan_in)) { + return; + } + + /* Initialize the interconnection type that will be physically implemented in module */ + enum e_interconnect interc_type = device_annotation.interconnect_physical_type(cur_interc); + const CircuitModelId& interc_circuit_model = device_annotation.interconnect_circuit_model(cur_interc); + + /* Find input ports of the wire module */ + std::vector interc_model_inputs = circuit_lib.model_ports_by_type(interc_circuit_model, CIRCUIT_MODEL_PORT_INPUT, true); /* the last argument to guarantee that we ignore any global inputs */ + /* Find output ports of the wire module */ + std::vector interc_model_outputs = circuit_lib.model_ports_by_type(interc_circuit_model, CIRCUIT_MODEL_PORT_OUTPUT, true); /* the last argument to guarantee that we ignore any global ports */ + + /* Ensure that we have only 1 input port and 1 output port, this is valid for both wire and MUX */ + VTR_ASSERT(1 == interc_model_inputs.size()); + VTR_ASSERT(1 == interc_model_outputs.size()); + + /* Branch on the type of physical implementation, + * We add instances of programmable interconnection + */ + switch (interc_type) { + case DIRECT_INTERC: { + /* Ensure direct interc has only one fan-in */ + VTR_ASSERT(1 == fan_in); + + /* For more than one mode defined, the direct interc has more than one input_edge , + * We need to find which edge is connected the pin we want + */ + t_pb_graph_pin* src_pb_graph_pin = pb_graph_pin_inputs(des_pb_graph_pin, cur_interc)[0]; + + /* Ensure that circuit model is a wire */ + VTR_ASSERT(CIRCUIT_MODEL_WIRE == circuit_lib.model_type(interc_circuit_model)); + /* Find the wire module in the module manager */ + ModuleId wire_module = module_manager.find_module(circuit_lib.model_name(interc_circuit_model)); + VTR_ASSERT(true == module_manager.valid_module_id(wire_module)); + /* Get the instance id and add an instance of wire */ + size_t wire_instance = module_manager.num_instance(pb_module, wire_module); + module_manager.add_child_module(pb_module, wire_module); + + /* Ensure input and output ports of the wire model has only 1 pin respectively */ + VTR_ASSERT(1 == circuit_lib.port_size(interc_model_inputs[0])); + VTR_ASSERT(1 == circuit_lib.port_size(interc_model_outputs[0])); + + /* Add nets to connect the wires to ports of pb_module */ + /* First net is to connect input of src_pb_graph_node to input of the wire module */ + add_module_pb_graph_pin2pin_net(module_manager, pb_module, + wire_module, wire_instance, + circuit_lib.port_prefix(interc_model_inputs[0]), + 0, /* wire input port has only 1 pin */ + src_pb_graph_pin, + INPUT2INPUT_INTERC); + + /* Second net is to connect output of the wire module to output of des_pb_graph_pin */ + add_module_pb_graph_pin2pin_net(module_manager, pb_module, + wire_module, wire_instance, + circuit_lib.port_prefix(interc_model_outputs[0]), + 0, /* wire output port has only 1 pin */ + des_pb_graph_pin, + OUTPUT2OUTPUT_INTERC); + break; + } + case COMPLETE_INTERC: + case MUX_INTERC: { + /* Check: MUX should have at least 2 fan_in */ + VTR_ASSERT((2 == fan_in)||(2 < fan_in)); + /* Ensure that circuit model is a MUX */ + VTR_ASSERT(CIRCUIT_MODEL_MUX == circuit_lib.model_type(interc_circuit_model)); + /* Find the wire module in the module manager */ + ModuleId mux_module = module_manager.find_module(generate_mux_subckt_name(circuit_lib, interc_circuit_model, fan_in, std::string())); + VTR_ASSERT(true == module_manager.valid_module_id(mux_module)); + + /* Instanciate the MUX */ + size_t mux_instance = module_manager.num_instance(pb_module, mux_module); + module_manager.add_child_module(pb_module, mux_module); + /* Give an instance name: this name should be consistent with the block name given in SDC generator, + * If you want to bind the SDC generation to modules + */ + std::string mux_instance_name = generate_pb_mux_instance_name(GRID_MUX_INSTANCE_PREFIX, des_pb_graph_pin, std::string("")); + module_manager.set_child_instance_name(pb_module, mux_module, mux_instance, mux_instance_name); + + /* Instanciate a memory module for the MUX */ + std::string mux_mem_module_name = generate_mux_subckt_name(circuit_lib, + interc_circuit_model, + fan_in, + std::string(MEMORY_MODULE_POSTFIX)); + ModuleId mux_mem_module = module_manager.find_module(mux_mem_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(mux_mem_module)); + size_t mux_mem_instance = module_manager.num_instance(pb_module, mux_mem_module); + module_manager.add_child_module(pb_module, mux_mem_module); + /* Give an instance name: this name should be consistent with the block name given in bitstream manager, + * If you want to bind the bitstream generation to modules + */ + std::string mux_mem_instance_name = generate_pb_memory_instance_name(GRID_MEM_INSTANCE_PREFIX, des_pb_graph_pin, std::string("")); + module_manager.set_child_instance_name(pb_module, mux_mem_module, mux_mem_instance, mux_mem_instance_name); + /* Add this MUX as a configurable child to the pb_module */ + module_manager.add_configurable_child(pb_module, mux_mem_module, mux_mem_instance); + + /* Add nets to connect SRAM ports of the MUX to the SRAM port of memory module */ + add_module_nets_between_logic_and_memory_sram_bus(module_manager, pb_module, + mux_module, mux_instance, + mux_mem_module, mux_mem_instance, + circuit_lib, interc_circuit_model); + + /* Update memory modules and memory instance list */ + memory_modules.push_back(mux_mem_module); + memory_instances.push_back(mux_mem_instance); + + /* Ensure output port of the MUX model has only 1 pin, + * while the input port size is dependent on the architecture conext, + * no constaints on the circuit model definition + */ + VTR_ASSERT(1 == circuit_lib.port_size(interc_model_outputs[0])); + + /* Create nets to wire between the MUX and PB module */ + /* Add a net to wire the inputs of the multiplexer to its source pb_graph_pin inside pb_module + * Here is a tricky part. + * Not every input edges from the destination pb_graph_pin is used in the physical_model of pb_type + * So, we will skip these input edges when building nets + */ + size_t mux_input_pin_id = 0; + for (t_pb_graph_pin* src_pb_graph_pin : pb_graph_pin_inputs(des_pb_graph_pin, cur_interc)) { + /* Add a net, set its source and sink */ + add_module_pb_graph_pin2pin_net(module_manager, pb_module, + mux_module, mux_instance, + circuit_lib.port_prefix(interc_model_inputs[0]), + mux_input_pin_id, + src_pb_graph_pin, + INPUT2INPUT_INTERC); + mux_input_pin_id++; + } + /* Ensure all the fan_in has been covered */ + VTR_ASSERT(mux_input_pin_id == fan_in); + + /* Add a net to wire the output of the multiplexer to des_pb_graph_pin */ + add_module_pb_graph_pin2pin_net(module_manager, pb_module, + mux_module, mux_instance, + circuit_lib.port_prefix(interc_model_outputs[0]), + 0, /* MUX should have only 1 pin in its output port */ + des_pb_graph_pin, + OUTPUT2OUTPUT_INTERC); + break; + } + default: + VTR_LOG_ERROR("Invalid interconnection type for %s [at Architecture XML LINE%d]!\n", + cur_interc->name, cur_interc->line_num); + exit(1); + } +} + +/******************************************************************** + * Add modules and nets for programmable/non-programmable interconnections + * which end to a port of pb_module + * This function will add the following elements to a module + * 1. Instances of direct connections + * 2. Instances of programmable routing multiplexers + * 3. nets to connect direct connections/multiplexer + * + * +-----------------------------------------+ + * | + * | +--------------+ +------------+ + * |--->| |--->| | + * |... | Multiplexers |... | | + * |--->| |--->| | + * | +--------------+ | des_pb_ | + * | | graph_node | + * | +--------------+ | | + * |--->| |--->| | + * | ...| Direct |... | | + * |--->| Connections |--->| | + * | +--------------+ +------------+ + * | + * +----------------------------------------+ + + * + * Note: this function should be run after ALL the child pb_modules + * have been added to the pb_module and ALL the ports defined + * in pb_type have been added to the pb_module!!! + * + ********************************************************************/ +static +void add_module_pb_graph_port_interc(ModuleManager& module_manager, + const ModuleId& pb_module, + std::vector& memory_modules, + std::vector& memory_instances, + const VprDeviceAnnotation& device_annotation, + const CircuitLibrary& circuit_lib, + t_pb_graph_node* des_pb_graph_node, + const e_circuit_pb_port_type& pb_port_type, + t_mode* physical_mode) { + switch (pb_port_type) { + case CIRCUIT_PB_PORT_INPUT: { + for (int iport = 0; iport < des_pb_graph_node->num_input_ports; ++iport) { + for (int ipin = 0; ipin < des_pb_graph_node->num_input_pins[iport]; ++ipin) { + /* Get the selected edge of current pin*/ + add_module_pb_graph_pin_interc(module_manager, pb_module, + memory_modules, memory_instances, + device_annotation, + circuit_lib, + &(des_pb_graph_node->input_pins[iport][ipin]), + physical_mode); + } + } + break; + } + case CIRCUIT_PB_PORT_OUTPUT: { + for (int iport = 0; iport < des_pb_graph_node->num_output_ports; ++iport) { + for (int ipin = 0; ipin < des_pb_graph_node->num_output_pins[iport]; ++ipin) { + add_module_pb_graph_pin_interc(module_manager, pb_module, + memory_modules, memory_instances, + device_annotation, + circuit_lib, + &(des_pb_graph_node->output_pins[iport][ipin]), + physical_mode); + } + } + break; + } + case CIRCUIT_PB_PORT_CLOCK: { + for (int iport = 0; iport < des_pb_graph_node->num_clock_ports; ++iport) { + for (int ipin = 0; ipin < des_pb_graph_node->num_clock_pins[iport]; ++ipin) { + add_module_pb_graph_pin_interc(module_manager, pb_module, + memory_modules, memory_instances, + device_annotation, + circuit_lib, + &(des_pb_graph_node->clock_pins[iport][ipin]), + physical_mode); + } + } + break; + } + default: + VTR_LOG_ERROR("Invalid pb port type!\n"); + exit(1); + } +} + +/******************************************************************** + * TODO: + * Add modules and nets for programmable/non-programmable interconnections + * inside a module of pb_type + * This function will add the following elements to a module + * 1. Instances of direct connections + * 2. Instances of programmable routing multiplexers + * 3. nets to connect direct connections/multiplexer + * + * Pb_module + * +--------------------------------------------------------------+ + * | | + * | +--------------+ +------------+ +--------------+ | + * |--->| |--->| |--->| |--->| + * |... | Multiplexers |... | |... | Multiplexers |... | + * |--->| |--->| |--->| |--->| + * | +--------------+ | Child | +--------------+ | + * | | Pb_modules | | + * | +--------------+ | | +--------------+ | + * |--->| |--->| |--->| |--->| + * | ...| Direct |... | |... | Direct |... | + * |--->| Connections |--->| |--->| Connections |--->| + * | +--------------+ +------------+ +--------------+ | + * | | + * +--------------------------------------------------------------+ + * + * Note: this function should be run after ALL the child pb_modules + * have been added to the pb_module and ALL the ports defined + * in pb_type have been added to the pb_module!!! + * + ********************************************************************/ +static +void add_module_pb_graph_interc(ModuleManager& module_manager, + const ModuleId& pb_module, + std::vector& memory_modules, + std::vector& memory_instances, + const VprDeviceAnnotation& device_annotation, + const CircuitLibrary& circuit_lib, + t_pb_graph_node* physical_pb_graph_node, + const int& physical_mode_index) { + /* Check cur_pb_graph_node*/ + VTR_ASSERT(nullptr != physical_pb_graph_node); + + /* Assign physical mode */ + t_mode* physical_mode = &(physical_pb_graph_node->pb_type->modes[physical_mode_index]); + + /* We check output_pins of cur_pb_graph_node and its the input_edges + * Built the interconnections between outputs of cur_pb_graph_node and outputs of child_pb_graph_node + * child_pb_graph_node.output_pins -----------------> cur_pb_graph_node.outpins + * /|\ + * | + * input_pins, edges, output_pins + */ + add_module_pb_graph_port_interc(module_manager, pb_module, + memory_modules, memory_instances, + device_annotation, + circuit_lib, + physical_pb_graph_node, + CIRCUIT_PB_PORT_OUTPUT, + physical_mode); + + /* We check input_pins of child_pb_graph_node and its the input_edges + * Built the interconnections between inputs of cur_pb_graph_node and inputs of child_pb_graph_node + * cur_pb_graph_node.input_pins -----------------> child_pb_graph_node.input_pins + * /|\ + * | + * input_pins, edges, output_pins + */ + for (int child = 0; child < physical_pb_graph_node->pb_type->modes[physical_mode_index].num_pb_type_children; ++child) { + for (int inst = 0; inst < physical_pb_graph_node->pb_type->modes[physical_mode_index].pb_type_children[child].num_pb; ++inst) { + t_pb_graph_node* child_pb_graph_node = &(physical_pb_graph_node->child_pb_graph_nodes[physical_mode_index][child][inst]); + /* For each child_pb_graph_node input pins*/ + add_module_pb_graph_port_interc(module_manager, pb_module, + memory_modules, memory_instances, + device_annotation, + circuit_lib, + child_pb_graph_node, + CIRCUIT_PB_PORT_INPUT, + physical_mode); + + /* For each child_pb_graph_node clock pins*/ + add_module_pb_graph_port_interc(module_manager, pb_module, + memory_modules, memory_instances, + device_annotation, + circuit_lib, + child_pb_graph_node, + CIRCUIT_PB_PORT_CLOCK, + physical_mode); + } + } +} + +/******************************************************************** + * Print Verilog modules of physical blocks inside a grid (CLB, I/O. etc.) + * This function will traverse the graph of complex logic block (t_pb_graph_node) + * in a recursive way, using a Depth First Search (DFS) algorithm. + * As such, primitive physical blocks (LUTs, FFs, etc.), leaf node of the pb_graph + * will be printed out first, while the top-level will be printed out in the last + * + * Note: this function will print a unique Verilog module for each type of + * t_pb_graph_node, i.e., t_pb_type, in the graph, in order to enable highly + * hierarchical Verilog organization as well as simplify the Verilog file sizes. + * + * Note: DFS is the right way. Do NOT use BFS. + * DFS can guarantee that all the sub-modules can be registered properly + * to its parent in module manager + *******************************************************************/ +static +void rec_build_logical_tile_modules(ModuleManager& module_manager, + const VprDeviceAnnotation& device_annotation, + const CircuitLibrary& circuit_lib, + const MuxLibrary& mux_lib, + const e_config_protocol_type& sram_orgz_type, + const CircuitModelId& sram_model, + t_pb_graph_node* physical_pb_graph_node) { + /* Check cur_pb_graph_node*/ + VTR_ASSERT(nullptr != physical_pb_graph_node); + + /* Get the pb_type definition related to the node */ + t_pb_type* physical_pb_type = physical_pb_graph_node->pb_type; + + /* Find the mode that physical implementation of a pb_type */ + t_mode* physical_mode = device_annotation.physical_mode(physical_pb_type); + + /* For non-leaf node in the pb_type graph: + * Recursively Depth-First Generate all the child pb_type at the level + */ + if (false == is_primitive_pb_type(physical_pb_type)) { + for (int ipb = 0; ipb < physical_mode->num_pb_type_children; ++ipb) { + /* Go recursive to visit the children */ + rec_build_logical_tile_modules(module_manager, device_annotation, + circuit_lib, mux_lib, + sram_orgz_type, sram_model, + &(physical_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ipb][0])); + } + } + + /* For leaf node, a primitive Verilog module will be generated */ + if (true == is_primitive_pb_type(physical_pb_type)) { + build_primitive_block_module(module_manager, device_annotation, + circuit_lib, + sram_orgz_type, sram_model, + physical_pb_graph_node); + /* Finish for primitive node, return */ + return; + } + + /* Generate the name of the Verilog module for this pb_type */ + std::string pb_module_name = generate_physical_block_module_name(physical_pb_type); + + /* Register the Verilog module in module manager */ + ModuleId pb_module = module_manager.add_module(pb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(pb_module)); + + /* Add ports to the Verilog module */ + add_pb_type_ports_to_module_manager(module_manager, pb_module, physical_pb_type); + + /* Vectors to record all the memory modules have been added + * They are used to add module nets of configuration bus + */ + std::vector memory_modules; + std::vector memory_instances; + + /* Add all the child Verilog modules as instances */ + for (int ichild = 0; ichild < physical_mode->num_pb_type_children; ++ichild) { + /* Get the name and module id for this child pb_type */ + std::string child_pb_module_name = generate_physical_block_module_name(&(physical_mode->pb_type_children[ichild])); + ModuleId child_pb_module = module_manager.find_module(child_pb_module_name); + /* We must have one valid id! */ + VTR_ASSERT(true == module_manager.valid_module_id(child_pb_module)); + + /* Each child may exist multiple times in the hierarchy*/ + for (int inst = 0; inst < physical_mode->pb_type_children[ichild].num_pb; ++inst) { + size_t child_instance_id = module_manager.num_instance(pb_module, child_pb_module); + /* Ensure the instance of this child module is the same as placement index, + * This check is necessary because placement_index is used to identify instance id for children + * when adding local interconnection for this pb_type + */ + VTR_ASSERT(child_instance_id == (size_t)physical_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ichild][inst].placement_index); + + /* Add the memory module as a child of primitive module */ + module_manager.add_child_module(pb_module, child_pb_module); + + /* Set an instance name to bind to a block in bitstream generation and SDC generation! */ + std::string child_pb_instance_name = generate_physical_block_instance_name(&(physical_pb_type->modes[physical_mode->index].pb_type_children[ichild]), inst); + module_manager.set_child_instance_name(pb_module, child_pb_module, child_instance_id, child_pb_instance_name); + + /* Identify if this sub module includes configuration bits, + * we will update the memory module and instance list + */ + if (0 < find_module_num_config_bits(module_manager, child_pb_module, + circuit_lib, sram_model, + sram_orgz_type)) { + module_manager.add_configurable_child(pb_module, child_pb_module, child_instance_id); + } + } + } + + /* Add modules and nets for programmable/non-programmable interconnections + * inside the Verilog module + */ + add_module_pb_graph_interc(module_manager, pb_module, + memory_modules, memory_instances, + device_annotation, + circuit_lib, physical_pb_graph_node, + physical_mode->index); + + /* Add global ports to the pb_module: + * This is a much easier job after adding sub modules (instances), + * we just need to find all the global ports from the child modules and build a list of it + */ + add_module_global_ports_from_child_modules(module_manager, pb_module); + + /* Count GPIO ports from the sub-modules under this Verilog module + * This is a much easier job after adding sub modules (instances), + * we just need to find all the I/O ports from the child modules and build a list of it + */ + add_module_gpio_ports_from_child_modules(module_manager, pb_module); + + /* Count shared SRAM ports from the sub-modules under this Verilog module + * This is a much easier job after adding sub modules (instances), + * we just need to find all the I/O ports from the child modules and build a list of it + */ + size_t module_num_shared_config_bits = find_module_num_shared_config_bits_from_child_modules(module_manager, pb_module); + if (0 < module_num_shared_config_bits) { + add_reserved_sram_ports_to_module_manager(module_manager, pb_module, module_num_shared_config_bits); + } + + /* Count SRAM ports from the sub-modules under this Verilog module + * This is a much easier job after adding sub modules (instances), + * we just need to find all the I/O ports from the child modules and build a list of it + */ + size_t module_num_config_bits = find_module_num_config_bits_from_child_modules(module_manager, pb_module, circuit_lib, sram_model, sram_orgz_type); + if (0 < module_num_config_bits) { + add_sram_ports_to_module_manager(module_manager, pb_module, circuit_lib, sram_model, sram_orgz_type, module_num_config_bits); + } + + /* Add module nets to connect memory cells inside + * This is a one-shot addition that covers all the memory modules in this pb module! + */ + if (false == memory_modules.empty()) { + add_module_nets_memory_config_bus(module_manager, pb_module, + sram_orgz_type, circuit_lib.design_tech_type(sram_model)); + } +} + +/***************************************************************************** + * This function will create a Verilog file and print out a Verilog netlist + * for a type of physical block + * + * For IO blocks: + * The param 'border_side' is required, which is specify which side of fabric + * the I/O block locates at. + *****************************************************************************/ +static +void build_physical_tile_module(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const e_config_protocol_type& sram_orgz_type, + const CircuitModelId& sram_model, + t_physical_tile_type_ptr phy_block_type, + const e_side& border_side, + const bool& duplicate_grid_pin) { + /* Check code: if this is an IO block, the border side MUST be valid */ + if (true == is_io_type(phy_block_type)) { + VTR_ASSERT(NUM_SIDES != border_side); + } + + /* Create a Module for the top-level physical block, and add to module manager */ + std::string grid_module_name = generate_grid_block_module_name(std::string(GRID_MODULE_NAME_PREFIX), + std::string(phy_block_type->name), + is_io_type(phy_block_type), + border_side); + ModuleId grid_module = module_manager.add_module(grid_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(grid_module)); + + /* Now each physical tile may have a number of diffrent logical blocks + * We assume the following organization: + * + * Physical tile + * +----------------------- + * | + * | pb_typeA[0] - from equivalent site[A] + * | +-------------------- + * | | + * | +-------------------- + * | + * | pb_typeB[0] - from equivalent site[B] + * | +-------------------- + * | | + * | +-------------------- + * ... ... + * | pb_typeA[capacity - 1] - from equivalent site[A] + * | +-------------------- + * | | + * | +-------------------- + * | + * | pb_typeB[capacity - 1] - from equivalent site[B] + * | +-------------------- + * | | + * | +-------------------- + * | + * +----------------------- + */ + for (int iz = 0; iz < phy_block_type->capacity; ++iz) { + for (t_logical_block_type_ptr lb_type : phy_block_type->equivalent_sites) { + /* Bypass empty pb_graph */ + if (nullptr == lb_type->pb_graph_head) { + continue; + } + std::string pb_module_name = generate_physical_block_module_name(lb_type->pb_graph_head->pb_type); + ModuleId pb_module = module_manager.find_module(pb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(pb_module)); + + /* Add all the sub modules */ + size_t pb_instance_id = module_manager.num_instance(grid_module, pb_module); + module_manager.add_child_module(grid_module, pb_module); + + /* Give the child module with a unique instance name */ + std::string instance_name = generate_physical_block_instance_name(lb_type->pb_graph_head->pb_type, iz); + /* Set an instance name to bind to a block in bitstream generation */ + module_manager.set_child_instance_name(grid_module, pb_module, pb_instance_id, instance_name); + + /* Identify if this sub module includes configuration bits, + * we will update the memory module and instance list + */ + if (0 < find_module_num_config_bits(module_manager, pb_module, + circuit_lib, sram_model, + sram_orgz_type)) { + module_manager.add_configurable_child(grid_module, pb_module, pb_instance_id); + } + } + } + + /* Add grid ports(pins) to the module */ + if (false == duplicate_grid_pin) { + /* Default way to add these ports by following the definition in pb_types */ + add_grid_module_pb_type_ports(module_manager, grid_module, + phy_block_type, border_side); + /* Add module nets to connect the pb_type ports to sub modules */ + for (t_logical_block_type_ptr lb_type : phy_block_type->equivalent_sites) { + /* Bypass empty pb_graph */ + if (nullptr == lb_type->pb_graph_head) { + continue; + } + std::string pb_module_name = generate_physical_block_module_name(lb_type->pb_graph_head->pb_type); + ModuleId pb_module = module_manager.find_module(pb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(pb_module)); + for (const size_t& child_instance : module_manager.child_module_instances(grid_module, pb_module)) { + add_grid_module_nets_connect_pb_type_ports(module_manager, grid_module, + pb_module, child_instance, + phy_block_type, border_side); + } + } + } else { + VTR_ASSERT_SAFE(true == duplicate_grid_pin); + /* Add these ports with duplication */ + add_grid_module_duplicated_pb_type_ports(module_manager, grid_module, + phy_block_type, border_side); + + /* Add module nets to connect the duplicated pb_type ports to sub modules */ + for (t_logical_block_type_ptr lb_type : phy_block_type->equivalent_sites) { + /* Bypass empty pb_graph */ + if (nullptr == lb_type->pb_graph_head) { + continue; + } + std::string pb_module_name = generate_physical_block_module_name(lb_type->pb_graph_head->pb_type); + ModuleId pb_module = module_manager.find_module(pb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(pb_module)); + for (const size_t& child_instance : module_manager.child_module_instances(grid_module, pb_module)) { + add_grid_module_nets_connect_duplicated_pb_type_ports(module_manager, grid_module, + pb_module, child_instance, + phy_block_type, border_side); + } + } + } + + /* Add global ports to the pb_module: + * This is a much easier job after adding sub modules (instances), + * we just need to find all the global ports from the child modules and build a list of it + */ + add_module_global_ports_from_child_modules(module_manager, grid_module); + + /* Count GPIO ports from the sub-modules under this Verilog module + * This is a much easier job after adding sub modules (instances), + * we just need to find all the I/O ports from the child modules and build a list of it + */ + add_module_gpio_ports_from_child_modules(module_manager, grid_module); + + /* Count shared SRAM ports from the sub-modules under this Verilog module + * This is a much easier job after adding sub modules (instances), + * we just need to find all the I/O ports from the child modules and build a list of it + */ + size_t module_num_shared_config_bits = find_module_num_shared_config_bits_from_child_modules(module_manager, grid_module); + if (0 < module_num_shared_config_bits) { + add_reserved_sram_ports_to_module_manager(module_manager, grid_module, module_num_shared_config_bits); + } + + /* Count SRAM ports from the sub-modules under this Verilog module + * This is a much easier job after adding sub modules (instances), + * we just need to find all the I/O ports from the child modules and build a list of it + */ + size_t module_num_config_bits = find_module_num_config_bits_from_child_modules(module_manager, grid_module, circuit_lib, sram_model, sram_orgz_type); + if (0 < module_num_config_bits) { + add_sram_ports_to_module_manager(module_manager, grid_module, circuit_lib, sram_model, sram_orgz_type, module_num_config_bits); + } + + /* Add module nets to connect memory cells inside + * This is a one-shot addition that covers all the memory modules in this pb module! + */ + if (0 < module_manager.configurable_children(grid_module).size()) { + add_module_nets_memory_config_bus(module_manager, grid_module, + sram_orgz_type, circuit_lib.design_tech_type(sram_model)); + } +} + +/***************************************************************************** + * Create logic block modules in a compact way + * This function will achieve this goal in two step: + * - Build the modules for each logical tile which is based on pb_graph + * Note that there the pin/port does not carry any fixed physical location + * - Build the modules for each physical tile which is based on physical_tile_type_ptr + * Here, multiple logical tiles can be considered and each port/pin has a fixed + * physical location. This is where the feature of duplicate_pin_pin will be applied + * - Only one module for each I/O on each border side (IO_TYPE) + * - Only one module for each CLB (FILL_TYPE) + * - Only one module for each heterogeneous block + ****************************************************************************/ +void build_grid_modules(ModuleManager& module_manager, + const DeviceContext& device_ctx, + const VprDeviceAnnotation& device_annotation, + const CircuitLibrary& circuit_lib, + const MuxLibrary& mux_lib, + const e_config_protocol_type& sram_orgz_type, + const CircuitModelId& sram_model, + const bool& duplicate_grid_pin) { + /* Start time count */ + vtr::ScopedStartFinishTimer timer("Build grid modules"); + + /* Enumerate the types of logical tiles, and build a module for each + * Build modules for all the pb_types/pb_graph_nodes + * use a Depth-First Search Algorithm to print the sub-modules + * Note: DFS is the right way. Do NOT use BFS. + * DFS can guarantee that all the sub-modules can be registered properly + * to its parent in module manager + */ + /* Build modules starting from the top-level pb_type/pb_graph_node, and traverse the graph in a recursive way */ + for (const t_logical_block_type& logical_tile : device_ctx.logical_block_types) { + /* Bypass empty pb_graph */ + if (nullptr == logical_tile.pb_graph_head) { + continue; + } + rec_build_logical_tile_modules(module_manager, device_annotation, + circuit_lib, mux_lib, + sram_orgz_type, sram_model, + logical_tile.pb_graph_head); + } + + /* Enumerate the types of physical tiles + * Use the logical tile module to build the physical tiles + */ + for (const t_physical_tile_type& physical_tile : device_ctx.physical_tile_types) { + /* Bypass empty type or nullptr */ + if (true == is_empty_type(&physical_tile)) { + continue; + } else if (true == is_io_type(&physical_tile)) { + /* Special for I/O block, generate one module for each border side */ + for (int iside = 0; iside < NUM_SIDES; iside++) { + SideManager side_manager(iside); + build_physical_tile_module(module_manager, circuit_lib, + sram_orgz_type, sram_model, + &physical_tile, + side_manager.get_side(), + duplicate_grid_pin); + } + continue; + } else { + /* For CLB and heterogenenous blocks */ + build_physical_tile_module(module_manager, circuit_lib, + sram_orgz_type, sram_model, + &physical_tile, + NUM_SIDES, + duplicate_grid_pin); + } + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fabric/build_grid_modules.h b/openfpga/src/fabric/build_grid_modules.h new file mode 100644 index 000000000..4c02f3783 --- /dev/null +++ b/openfpga/src/fabric/build_grid_modules.h @@ -0,0 +1,30 @@ +#ifndef BUILD_GRID_MODULES_H +#define BUILD_GRID_MODULES_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "vpr_context.h" +#include "vpr_device_annotation.h" +#include "module_manager.h" +#include "mux_library.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void build_grid_modules(ModuleManager& module_manager, + const DeviceContext& device_ctx, + const VprDeviceAnnotation& device_annotation, + const CircuitLibrary& circuit_lib, + const MuxLibrary& mux_lib, + const e_config_protocol_type& sram_orgz_type, + const CircuitModelId& sram_model, + const bool& duplicate_grid_pin); + +} /* end namespace openfpga */ + +#endif From 89086ed08087e2e148afd282c46d410a2aee9173 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 13 Feb 2020 15:38:26 -0700 Subject: [PATCH 133/645] add verbose output to build grid module --- openfpga/src/base/openfpga_build_fabric.cpp | 3 +- openfpga/src/fabric/build_device_module.cpp | 5 +- openfpga/src/fabric/build_device_module.h | 3 +- openfpga/src/fabric/build_grid_modules.cpp | 51 +++++++++++++++++---- openfpga/src/fabric/build_grid_modules.h | 3 +- 5 files changed, 51 insertions(+), 14 deletions(-) diff --git a/openfpga/src/base/openfpga_build_fabric.cpp b/openfpga/src/base/openfpga_build_fabric.cpp index c15cf548f..879c127d6 100644 --- a/openfpga/src/base/openfpga_build_fabric.cpp +++ b/openfpga/src/base/openfpga_build_fabric.cpp @@ -72,7 +72,8 @@ void build_fabric(OpenfpgaContext& openfpga_context, openfpga_context.mutable_module_graph() = build_device_module_graph(g_vpr_ctx.device(), const_cast(openfpga_context), - cmd_context.option_enable(cmd, opt_duplicate_grid_pin)); + cmd_context.option_enable(cmd, opt_duplicate_grid_pin), + cmd_context.option_enable(cmd, opt_verbose)); } } /* end namespace openfpga */ diff --git a/openfpga/src/fabric/build_device_module.cpp b/openfpga/src/fabric/build_device_module.cpp index d6f759b30..fd7f1f31e 100644 --- a/openfpga/src/fabric/build_device_module.cpp +++ b/openfpga/src/fabric/build_device_module.cpp @@ -28,7 +28,8 @@ namespace openfpga { *******************************************************************/ ModuleManager build_device_module_graph(const DeviceContext& vpr_device_ctx, const OpenfpgaContext& openfpga_ctx, - const bool& duplicate_grid_pin) { + const bool& duplicate_grid_pin, + const bool& verbose) { vtr::ScopedStartFinishTimer timer("Build fabric module graph"); /* Module manager to be built */ @@ -73,7 +74,7 @@ ModuleManager build_device_module_graph(const DeviceContext& vpr_device_ctx, openfpga_ctx.arch().circuit_lib, openfpga_ctx.mux_lib(), openfpga_ctx.arch().config_protocol.type(), - sram_model, duplicate_grid_pin); + sram_model, duplicate_grid_pin, verbose); //if (TRUE == vpr_setup.FPGA_SPICE_Opts.compact_routing_hierarchy) { // build_unique_routing_modules(module_manager, L_device_rr_gsb, arch.spice->circuit_lib, diff --git a/openfpga/src/fabric/build_device_module.h b/openfpga/src/fabric/build_device_module.h index fd646844f..6830ca82b 100644 --- a/openfpga/src/fabric/build_device_module.h +++ b/openfpga/src/fabric/build_device_module.h @@ -16,7 +16,8 @@ namespace openfpga { ModuleManager build_device_module_graph(const DeviceContext& vpr_device_ctx, const OpenfpgaContext& openfpga_ctx, - const bool& duplicate_grid_pin); + const bool& duplicate_grid_pin, + const bool& verbose); } /* end namespace openfpga */ diff --git a/openfpga/src/fabric/build_grid_modules.cpp b/openfpga/src/fabric/build_grid_modules.cpp index df0a63d03..bbddd4964 100644 --- a/openfpga/src/fabric/build_grid_modules.cpp +++ b/openfpga/src/fabric/build_grid_modules.cpp @@ -175,7 +175,8 @@ void build_primitive_block_module(ModuleManager& module_manager, const CircuitLibrary& circuit_lib, const e_config_protocol_type& sram_orgz_type, const CircuitModelId& sram_model, - t_pb_graph_node* primitive_pb_graph_node) { + t_pb_graph_node* primitive_pb_graph_node, + const bool& verbose) { /* Ensure a valid pb_graph_node */ VTR_ASSERT(nullptr != primitive_pb_graph_node); @@ -185,6 +186,10 @@ void build_primitive_block_module(ModuleManager& module_manager, /* Generate the module name for this primitive pb_graph_node*/ std::string primitive_module_name = generate_physical_block_module_name(primitive_pb_graph_node->pb_type); + VTR_LOGV(verbose, + "Building module '%s'...", + primitive_module_name.c_str()); + /* Create a module of the primitive LUT and register it to module manager */ ModuleId primitive_module = module_manager.add_module(primitive_module_name); /* Ensure that the module has been created and thus unique! */ @@ -289,6 +294,8 @@ void build_primitive_block_module(ModuleManager& module_manager, } } } + + VTR_LOGV(verbose, "Done\n"); } /******************************************************************** @@ -732,7 +739,8 @@ void rec_build_logical_tile_modules(ModuleManager& module_manager, const MuxLibrary& mux_lib, const e_config_protocol_type& sram_orgz_type, const CircuitModelId& sram_model, - t_pb_graph_node* physical_pb_graph_node) { + t_pb_graph_node* physical_pb_graph_node, + const bool& verbose) { /* Check cur_pb_graph_node*/ VTR_ASSERT(nullptr != physical_pb_graph_node); @@ -751,7 +759,8 @@ void rec_build_logical_tile_modules(ModuleManager& module_manager, rec_build_logical_tile_modules(module_manager, device_annotation, circuit_lib, mux_lib, sram_orgz_type, sram_model, - &(physical_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ipb][0])); + &(physical_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ipb][0]), + verbose); } } @@ -760,7 +769,8 @@ void rec_build_logical_tile_modules(ModuleManager& module_manager, build_primitive_block_module(module_manager, device_annotation, circuit_lib, sram_orgz_type, sram_model, - physical_pb_graph_node); + physical_pb_graph_node, + verbose); /* Finish for primitive node, return */ return; } @@ -768,6 +778,10 @@ void rec_build_logical_tile_modules(ModuleManager& module_manager, /* Generate the name of the Verilog module for this pb_type */ std::string pb_module_name = generate_physical_block_module_name(physical_pb_type); + VTR_LOGV(verbose, + "Building module '%s'...", + pb_module_name.c_str()); + /* Register the Verilog module in module manager */ ModuleId pb_module = module_manager.add_module(pb_module_name); VTR_ASSERT(true == module_manager.valid_module_id(pb_module)); @@ -862,6 +876,8 @@ void rec_build_logical_tile_modules(ModuleManager& module_manager, add_module_nets_memory_config_bus(module_manager, pb_module, sram_orgz_type, circuit_lib.design_tech_type(sram_model)); } + + VTR_LOGV(verbose, "Done\n"); } /***************************************************************************** @@ -879,7 +895,8 @@ void build_physical_tile_module(ModuleManager& module_manager, const CircuitModelId& sram_model, t_physical_tile_type_ptr phy_block_type, const e_side& border_side, - const bool& duplicate_grid_pin) { + const bool& duplicate_grid_pin, + const bool& verbose) { /* Check code: if this is an IO block, the border side MUST be valid */ if (true == is_io_type(phy_block_type)) { VTR_ASSERT(NUM_SIDES != border_side); @@ -890,6 +907,10 @@ void build_physical_tile_module(ModuleManager& module_manager, std::string(phy_block_type->name), is_io_type(phy_block_type), border_side); + VTR_LOGV(verbose, + "Building physical tile '%s'...", + grid_module_name.c_str()); + ModuleId grid_module = module_manager.add_module(grid_module_name); VTR_ASSERT(true == module_manager.valid_module_id(grid_module)); @@ -1031,6 +1052,8 @@ void build_physical_tile_module(ModuleManager& module_manager, add_module_nets_memory_config_bus(module_manager, grid_module, sram_orgz_type, circuit_lib.design_tech_type(sram_model)); } + + VTR_LOG("Done\n"); } /***************************************************************************** @@ -1052,7 +1075,8 @@ void build_grid_modules(ModuleManager& module_manager, const MuxLibrary& mux_lib, const e_config_protocol_type& sram_orgz_type, const CircuitModelId& sram_model, - const bool& duplicate_grid_pin) { + const bool& duplicate_grid_pin, + const bool& verbose) { /* Start time count */ vtr::ScopedStartFinishTimer timer("Build grid modules"); @@ -1064,6 +1088,8 @@ void build_grid_modules(ModuleManager& module_manager, * to its parent in module manager */ /* Build modules starting from the top-level pb_type/pb_graph_node, and traverse the graph in a recursive way */ + VTR_LOG("Building logical tiles..."); + VTR_LOGV(verbose, "\n"); for (const t_logical_block_type& logical_tile : device_ctx.logical_block_types) { /* Bypass empty pb_graph */ if (nullptr == logical_tile.pb_graph_head) { @@ -1072,12 +1098,16 @@ void build_grid_modules(ModuleManager& module_manager, rec_build_logical_tile_modules(module_manager, device_annotation, circuit_lib, mux_lib, sram_orgz_type, sram_model, - logical_tile.pb_graph_head); + logical_tile.pb_graph_head, + verbose); } + VTR_LOG("Done\n"); /* Enumerate the types of physical tiles * Use the logical tile module to build the physical tiles */ + VTR_LOG("Building physical tiles..."); + VTR_LOGV(verbose, "\n"); for (const t_physical_tile_type& physical_tile : device_ctx.physical_tile_types) { /* Bypass empty type or nullptr */ if (true == is_empty_type(&physical_tile)) { @@ -1090,7 +1120,8 @@ void build_grid_modules(ModuleManager& module_manager, sram_orgz_type, sram_model, &physical_tile, side_manager.get_side(), - duplicate_grid_pin); + duplicate_grid_pin, + verbose); } continue; } else { @@ -1099,9 +1130,11 @@ void build_grid_modules(ModuleManager& module_manager, sram_orgz_type, sram_model, &physical_tile, NUM_SIDES, - duplicate_grid_pin); + duplicate_grid_pin, + verbose); } } + VTR_LOG("Done\n"); } } /* end namespace openfpga */ diff --git a/openfpga/src/fabric/build_grid_modules.h b/openfpga/src/fabric/build_grid_modules.h index 4c02f3783..f2a8e967b 100644 --- a/openfpga/src/fabric/build_grid_modules.h +++ b/openfpga/src/fabric/build_grid_modules.h @@ -23,7 +23,8 @@ void build_grid_modules(ModuleManager& module_manager, const MuxLibrary& mux_lib, const e_config_protocol_type& sram_orgz_type, const CircuitModelId& sram_model, - const bool& duplicate_grid_pin); + const bool& duplicate_grid_pin, + const bool& verbose); } /* end namespace openfpga */ From cf440f92d3441fd52111c3c9997918ff9b9cbee0 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 13 Feb 2020 16:05:23 -0700 Subject: [PATCH 134/645] put routing module builder util function online --- .../src/fabric/build_essential_modules.cpp | 4 +- .../src/fabric/build_grid_module_utils.cpp | 2 +- openfpga/src/fabric/build_grid_modules.cpp | 6 +- openfpga/src/fabric/build_lut_modules.cpp | 9 +- openfpga/src/fabric/build_memory_modules.cpp | 4 +- openfpga/src/fabric/build_mux_modules.cpp | 6 +- .../src/fabric/build_routing_module_utils.cpp | 213 ++++++++++++++++++ .../src/fabric/build_routing_module_utils.h | 63 ++++++ 8 files changed, 292 insertions(+), 15 deletions(-) create mode 100644 openfpga/src/fabric/build_routing_module_utils.cpp create mode 100644 openfpga/src/fabric/build_routing_module_utils.h diff --git a/openfpga/src/fabric/build_essential_modules.cpp b/openfpga/src/fabric/build_essential_modules.cpp index a66a52979..e39282f2d 100644 --- a/openfpga/src/fabric/build_essential_modules.cpp +++ b/openfpga/src/fabric/build_essential_modules.cpp @@ -52,7 +52,7 @@ void build_invbuf_module(ModuleManager& module_manager, } /* Report errors if there are any */ if (0 < num_err) { - VTR_LOG_ERROR("Inverter/buffer circuit model '%s' is power-gated. At least one config-enable global port is required!\n", + VTR_LOGF_ERROR(__FILE__, __LINE__, "Inverter/buffer circuit model '%s' is power-gated. At least one config-enable global port is required!\n", circuit_lib.model_name(circuit_model).c_str()); exit(1); } @@ -98,7 +98,7 @@ void build_passgate_module(ModuleManager& module_manager, } break; default: - VTR_LOG_ERROR("Invalid topology for circuit model '%s'!\n", + VTR_LOGF_ERROR(__FILE__, __LINE__, "Invalid topology for circuit model '%s'!\n", circuit_lib.model_name(circuit_model).c_str()); exit(1); } diff --git a/openfpga/src/fabric/build_grid_module_utils.cpp b/openfpga/src/fabric/build_grid_module_utils.cpp index 1386e93fe..a61bc5f0a 100644 --- a/openfpga/src/fabric/build_grid_module_utils.cpp +++ b/openfpga/src/fabric/build_grid_module_utils.cpp @@ -102,7 +102,7 @@ void add_grid_module_net_connect_pb_graph_pin(ModuleManager& module_manager, module_manager.add_module_net_sink(grid_module, net, grid_module, 0, grid_module_port_id, grid_module_pin_id); break; default: - VTR_LOG_ERROR("Invalid pin-to-pin interconnection type!\n"); + VTR_LOGF_ERROR(__FILE__, __LINE__, "Invalid pin-to-pin interconnection type!\n"); exit(1); } } diff --git a/openfpga/src/fabric/build_grid_modules.cpp b/openfpga/src/fabric/build_grid_modules.cpp index bbddd4964..fcfbfa0aa 100644 --- a/openfpga/src/fabric/build_grid_modules.cpp +++ b/openfpga/src/fabric/build_grid_modules.cpp @@ -354,7 +354,7 @@ void add_module_pb_graph_pin2pin_net(ModuleManager& module_manager, module_manager.add_module_net_sink(pb_module, pin2pin_net, pin_pb_type_module, pin_pb_type_instance, pin_module_port_id, pin_module_pin_id); break; default: - VTR_LOG_ERROR("Invalid pin-to-pin interconnection type!\n"); + VTR_LOGF_ERROR(__FILE__, __LINE__, "Invalid pin-to-pin interconnection type!\n"); exit(1); } } @@ -532,7 +532,7 @@ void add_module_pb_graph_pin_interc(ModuleManager& module_manager, break; } default: - VTR_LOG_ERROR("Invalid interconnection type for %s [at Architecture XML LINE%d]!\n", + VTR_LOGF_ERROR(__FILE__, __LINE__, "Invalid interconnection type for %s [at Architecture XML LINE%d]!\n", cur_interc->name, cur_interc->line_num); exit(1); } @@ -620,7 +620,7 @@ void add_module_pb_graph_port_interc(ModuleManager& module_manager, break; } default: - VTR_LOG_ERROR("Invalid pb port type!\n"); + VTR_LOGF_ERROR(__FILE__, __LINE__, "Invalid pb port type!\n"); exit(1); } } diff --git a/openfpga/src/fabric/build_lut_modules.cpp b/openfpga/src/fabric/build_lut_modules.cpp index c799fe75a..184de6698 100644 --- a/openfpga/src/fabric/build_lut_modules.cpp +++ b/openfpga/src/fabric/build_lut_modules.cpp @@ -265,10 +265,11 @@ void build_lut_module(ModuleManager& module_manager, /* Sanitity check */ if ( true == circuit_lib.is_lut_fracturable(lut_model) ) { if (mode_select_port_lsb != circuit_lib.port_size(lut_mode_select_sram_ports[0])) { - VTR_LOG_ERROR("(ircuit model '%s' has a unmatched tri-state map '%s' implied by mode_port size='%d'!\n", - circuit_lib.model_name(lut_model).c_str(), - tri_state_map.c_str(), - circuit_lib.port_size(lut_mode_select_sram_ports[0])); + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Circuit model '%s' has a unmatched tri-state map '%s' implied by mode_port size='%d'!\n", + circuit_lib.model_name(lut_model).c_str(), + tri_state_map.c_str(), + circuit_lib.port_size(lut_mode_select_sram_ports[0])); exit(1); } } diff --git a/openfpga/src/fabric/build_memory_modules.cpp b/openfpga/src/fabric/build_memory_modules.cpp index 26b463377..299bf6d70 100644 --- a/openfpga/src/fabric/build_memory_modules.cpp +++ b/openfpga/src/fabric/build_memory_modules.cpp @@ -585,7 +585,7 @@ void build_memory_module(ModuleManager& module_manager, module_name, sram_model, num_mems); break; default: - VTR_LOG_ERROR("Invalid SRAM organization!\n"); + VTR_LOGF_ERROR(__FILE__, __LINE__, "Invalid SRAM organization!\n"); exit(1); } } @@ -636,7 +636,7 @@ void build_mux_memory_module(ModuleManager& module_manager, */ break; default: - VTR_LOG_ERROR("Invalid design technology of multiplexer '%s'\n", + VTR_LOGF_ERROR(__FILE__, __LINE__, "Invalid design technology of multiplexer '%s'\n", circuit_lib.model_name(mux_model).c_str()); exit(1); } diff --git a/openfpga/src/fabric/build_mux_modules.cpp b/openfpga/src/fabric/build_mux_modules.cpp index 0f430c4d3..41cb68118 100644 --- a/openfpga/src/fabric/build_mux_modules.cpp +++ b/openfpga/src/fabric/build_mux_modules.cpp @@ -346,7 +346,7 @@ void build_mux_branch_module(ModuleManager& module_manager, build_rram_mux_branch_module(module_manager, circuit_lib, mux_model, module_name, mux_graph); break; default: - VTR_LOG_ERROR("Invalid design technology of multiplexer '%s'\n", + VTR_LOGF_ERROR(__FILE__, __LINE__, "Invalid design technology of multiplexer '%s'\n", circuit_lib.model_name(mux_model).c_str()); exit(1); } @@ -1262,7 +1262,7 @@ void build_rram_mux_module(ModuleManager& module_manager, /* Error out for the conditions where we are not yet supported! */ if (CIRCUIT_MODEL_LUT == circuit_lib.model_type(circuit_model)) { /* RRAM LUT is not supported now... */ - VTR_LOG_ERROR("RRAM-based LUT is not supported for circuit model '%s')!\n", + VTR_LOGF_ERROR(__FILE__, __LINE__, "RRAM-based LUT is not supported for circuit model '%s')!\n", circuit_lib.model_name(circuit_model).c_str()); exit(1); } @@ -1367,7 +1367,7 @@ void build_mux_module(ModuleManager& module_manager, build_rram_mux_module(module_manager, circuit_lib, circuit_model, module_name, mux_graph); break; default: - VTR_LOG_ERROR("Invalid design technology of multiplexer '%s'\n", + VTR_LOGF_ERROR(__FILE__, __LINE__, "Invalid design technology of multiplexer '%s'\n", circuit_lib.model_name(circuit_model).c_str()); exit(1); } diff --git a/openfpga/src/fabric/build_routing_module_utils.cpp b/openfpga/src/fabric/build_routing_module_utils.cpp new file mode 100644 index 000000000..c717604c5 --- /dev/null +++ b/openfpga/src/fabric/build_routing_module_utils.cpp @@ -0,0 +1,213 @@ +/******************************************************************** + * This file includes most utilized functions that are used to build modules + * for global routing architecture of a FPGA fabric + * Covering: + * 1. Connection blocks + * 2. Switch blocks + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_log.h" +#include "vtr_assert.h" +#include "vtr_geometry.h" + +#include "openfpga_naming.h" + +#include "build_routing_module_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/********************************************************************* + * Generate a port for a routing track of a swtich block + ********************************************************************/ +ModulePortId find_switch_block_module_chan_port(const ModuleManager& module_manager, + const ModuleId& sb_module, + const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const e_side& chan_side, + const RRNodeId& cur_rr_node, + const PORTS& cur_rr_node_direction) { + /* Get the index in sb_info of cur_rr_node */ + int index = rr_gsb.get_node_index(rr_graph, cur_rr_node, chan_side, cur_rr_node_direction); + /* Make sure this node is included in this sb_info */ + VTR_ASSERT((-1 != index) && (NUM_SIDES != chan_side)); + + std::string chan_port_name = generate_sb_module_track_port_name(rr_graph.node_type(rr_gsb.get_chan_node(chan_side, index)), + chan_side, index, + rr_gsb.get_chan_node_direction(chan_side, index)); + + /* Must find a valid port id in the Switch Block module */ + ModulePortId chan_port_id = module_manager.find_module_port(sb_module, chan_port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(sb_module, chan_port_id)); + return chan_port_id; +} + +/********************************************************************* + * Generate an input port for routing multiplexer inside the switch block + * In addition to give the Routing Resource node of the input + * Users should provide the side of input, which is different case by case: + * 1. When the input is a pin of a CLB/Logic Block, the input_side should + * be the side of the node on its grid! + * For example, the input pin is on the top side of a switch block + * but on the right side of a switch block + * +--------+ + * | | + * | Grid |---+ + * | | | + * +--------+ v input_pin + * +----------------+ + * | Switch Block | + * +----------------+ + * 2. When the input is a routing track, the input_side should be + * the side of the node locating on the switch block + ********************************************************************/ +ModulePortId find_switch_block_module_input_port(const ModuleManager& module_manager, + const ModuleId& sb_module, + const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const e_side& input_side, + const RRNodeId& input_rr_node) { + /* Deposit an invalid value */ + ModulePortId input_port_id = ModulePortId::INVALID(); + /* Generate the input port object */ + switch (rr_graph.node_type(input_rr_node)) { + /* case SOURCE: */ + case OPIN: { + /* Find the coordinator (grid_x and grid_y) for the input port */ + vtr::Point input_port_coord(rr_graph.node_xlow(input_rr_node), + rr_graph.node_ylow(input_rr_node)); + + /* Find the side where the grid pin locates in the grid */ + enum e_side grid_pin_side = rr_graph.node_side(input_rr_node); + VTR_ASSERT(NUM_SIDES != grid_pin_side); + + std::string input_port_name = generate_sb_module_grid_port_name(input_side, + grid_pin_side, + rr_graph.node_pin_num(input_rr_node)); + /* Must find a valid port id in the Switch Block module */ + input_port_id = module_manager.find_module_port(sb_module, input_port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(sb_module, input_port_id)); + break; + } + case CHANX: + case CHANY: { + input_port_id = find_switch_block_module_chan_port(module_manager, sb_module, rr_graph, + rr_gsb, input_side, input_rr_node, IN_PORT); + break; + } + default: /* SOURCE, IPIN, SINK are invalid*/ + VTR_LOGF_ERROR(__FILE__, __LINE__, "Invalid rr_node type! Should be [OPIN|CHANX|CHANY].\n"); + exit(1); + } + + return input_port_id; +} + +/********************************************************************* + * Generate a list of input ports for routing multiplexer inside the switch block + ********************************************************************/ +std::vector find_switch_block_module_input_ports(const ModuleManager& module_manager, + const ModuleId& sb_module, + const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const std::vector& input_rr_nodes) { + std::vector input_ports; + + for (const RRNodeId& input_rr_node : input_rr_nodes) { + /* Find the side where the input locates in the Switch Block */ + enum e_side input_pin_side = NUM_SIDES; + /* The input could be at any side of the switch block, find it */ + int index = -1; + rr_gsb.get_node_side_and_index(rr_graph, input_rr_node, IN_PORT, input_pin_side, index); + VTR_ASSERT(NUM_SIDES != input_pin_side); + VTR_ASSERT(-1 != index); + + input_ports.push_back(find_switch_block_module_input_port(module_manager, sb_module, rr_graph, rr_gsb, input_pin_side, input_rr_node)); + } + + return input_ports; +} + +/********************************************************************* + * Generate an input port for routing multiplexer inside the connection block + * which is the middle output of a routing track + ********************************************************************/ +ModulePortId find_connection_block_module_chan_port(const ModuleManager& module_manager, + const ModuleId& cb_module, + const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const t_rr_type& cb_type, + const RRNodeId& chan_rr_node) { + ModulePortId input_port_id; + /* Generate the input port object */ + switch (rr_graph.node_type(chan_rr_node)) { + case CHANX: + case CHANY: { + /* Create port description for the routing track middle output */ + vtr::Point port_coord(rr_gsb.get_cb_x(cb_type), rr_gsb.get_cb_y(cb_type)); + int chan_node_track_id = rr_gsb.get_cb_chan_node_index(cb_type, chan_rr_node); + /* Create a port description for the middle output */ + std::string input_port_name = generate_cb_module_track_port_name(cb_type, + chan_node_track_id, + IN_PORT); + /* Must find a valid port id in the Switch Block module */ + input_port_id = module_manager.find_module_port(cb_module, input_port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(cb_module, input_port_id)); + break; + } + default: /* OPIN, SOURCE, IPIN, SINK are invalid*/ + VTR_LOGF_ERROR(__FILE__, __LINE__, "Invalid rr_node type! Should be [OPIN|CHANX|CHANY].\n"); + exit(1); + } + + return input_port_id; +} + +/********************************************************************* + * Generate a port for a routing track of a swtich block + ********************************************************************/ +ModulePortId find_connection_block_module_ipin_port(const ModuleManager& module_manager, + const ModuleId& cb_module, + const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const RRNodeId& src_rr_node) { + + /* Ensure the src_rr_node is an input pin of a CLB */ + VTR_ASSERT(IPIN == rr_graph.node_type(src_rr_node)); + /* Create port description for input pin of a CLB */ + vtr::Point port_coord(rr_graph.node_xlow(src_rr_node), rr_graph.node_ylow(src_rr_node)); + /* Search all the sides of a SB, see this drive_rr_node is an INPUT of this SB */ + enum e_side cb_ipin_side = NUM_SIDES; + int cb_ipin_index = -1; + rr_gsb.get_node_side_and_index(rr_graph, src_rr_node, OUT_PORT, cb_ipin_side, cb_ipin_index); + /* We need to be sure that drive_rr_node is part of the CB */ + VTR_ASSERT((-1 != cb_ipin_index)&&(NUM_SIDES != cb_ipin_side)); + std::string port_name = generate_cb_module_grid_port_name(cb_ipin_side, + rr_graph.node_pin_num(rr_gsb.get_ipin_node(cb_ipin_side, cb_ipin_index))); + + /* Must find a valid port id in the Switch Block module */ + ModulePortId ipin_port_id = module_manager.find_module_port(cb_module, port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(cb_module, ipin_port_id)); + return ipin_port_id; +} + +/********************************************************************* + * Generate a list of routing track middle output ports + * for routing multiplexer inside the connection block + ********************************************************************/ +std::vector find_connection_block_module_input_ports(const ModuleManager& module_manager, + const ModuleId& cb_module, + const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const t_rr_type& cb_type, + const std::vector& input_rr_nodes) { + std::vector input_ports; + + for (auto input_rr_node : input_rr_nodes) { + input_ports.push_back(find_connection_block_module_chan_port(module_manager, cb_module, rr_graph, rr_gsb, cb_type, input_rr_node)); + } + + return input_ports; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fabric/build_routing_module_utils.h b/openfpga/src/fabric/build_routing_module_utils.h new file mode 100644 index 000000000..257a788cb --- /dev/null +++ b/openfpga/src/fabric/build_routing_module_utils.h @@ -0,0 +1,63 @@ +#ifndef BUILD_ROUTING_MODULE_UTILS_H +#define BUILD_ROUTING_MODULE_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ + +#include +#include "rr_gsb.h" +#include "module_manager.h" +#include "vpr_types.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +ModulePortId find_switch_block_module_chan_port(const ModuleManager& module_manager, + const ModuleId& sb_module, + const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const e_side& chan_side, + const RRNodeId& cur_rr_node, + const PORTS& cur_rr_node_direction); + +ModulePortId find_switch_block_module_input_port(const ModuleManager& module_manager, + const ModuleId& sb_module, + const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const e_side& input_side, + const RRNodeId& input_rr_node); + +std::vector find_switch_block_module_input_ports(const ModuleManager& module_manager, + const ModuleId& sb_module, + const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const std::vector& input_rr_nodes); + +ModulePortId find_connection_block_module_chan_port(const ModuleManager& module_manager, + const ModuleId& cb_module, + const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const t_rr_type& cb_type, + const RRNodeId& chan_rr_node); + +ModulePortId find_connection_block_module_ipin_port(const ModuleManager& module_manager, + const ModuleId& cb_module, + const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const RRNodeId& src_rr_node); + +std::vector find_connection_block_module_input_ports(const ModuleManager& module_manager, + const ModuleId& cb_module, + const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const t_rr_type& cb_type, + const std::vector& input_rr_nodes); + +} /* end namespace openfpga */ + +#endif From afe8278670dab736c0e5bd2ce51cec354dcab2ec Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 13 Feb 2020 17:35:29 -0700 Subject: [PATCH 135/645] put routing module builder online --- openfpga/src/base/openfpga_build_fabric.cpp | 1 + openfpga/src/fabric/build_device_module.cpp | 32 +- openfpga/src/fabric/build_device_module.h | 1 + openfpga/src/fabric/build_routing_modules.cpp | 1001 +++++++++++++++++ openfpga/src/fabric/build_routing_modules.h | 41 + .../src/utils/openfpga_rr_graph_utils.cpp | 47 + openfpga/src/utils/openfpga_rr_graph_utils.h | 9 + 7 files changed, 1120 insertions(+), 12 deletions(-) create mode 100644 openfpga/src/fabric/build_routing_modules.cpp create mode 100644 openfpga/src/fabric/build_routing_modules.h diff --git a/openfpga/src/base/openfpga_build_fabric.cpp b/openfpga/src/base/openfpga_build_fabric.cpp index 879c127d6..5c3d8a69d 100644 --- a/openfpga/src/base/openfpga_build_fabric.cpp +++ b/openfpga/src/base/openfpga_build_fabric.cpp @@ -72,6 +72,7 @@ void build_fabric(OpenfpgaContext& openfpga_context, openfpga_context.mutable_module_graph() = build_device_module_graph(g_vpr_ctx.device(), const_cast(openfpga_context), + cmd_context.option_enable(cmd, opt_compress_routing), cmd_context.option_enable(cmd, opt_duplicate_grid_pin), cmd_context.option_enable(cmd, opt_verbose)); } diff --git a/openfpga/src/fabric/build_device_module.cpp b/openfpga/src/fabric/build_device_module.cpp index fd7f1f31e..db3d3dfda 100644 --- a/openfpga/src/fabric/build_device_module.cpp +++ b/openfpga/src/fabric/build_device_module.cpp @@ -15,7 +15,7 @@ #include "build_wire_modules.h" #include "build_memory_modules.h" #include "build_grid_modules.h" -//#include "build_routing_modules.h" +#include "build_routing_modules.h" //#include "build_top_module.h" #include "build_device_module.h" @@ -28,6 +28,7 @@ namespace openfpga { *******************************************************************/ ModuleManager build_device_module_graph(const DeviceContext& vpr_device_ctx, const OpenfpgaContext& openfpga_ctx, + const bool& compress_routing, const bool& duplicate_grid_pin, const bool& verbose) { vtr::ScopedStartFinishTimer timer("Build fabric module graph"); @@ -76,17 +77,24 @@ ModuleManager build_device_module_graph(const DeviceContext& vpr_device_ctx, openfpga_ctx.arch().config_protocol.type(), sram_model, duplicate_grid_pin, verbose); - //if (TRUE == vpr_setup.FPGA_SPICE_Opts.compact_routing_hierarchy) { - // build_unique_routing_modules(module_manager, L_device_rr_gsb, arch.spice->circuit_lib, - // arch.sram_inf.verilog_sram_inf_orgz->type, sram_model, - // vpr_setup.RoutingArch, rr_switches); - //} else { - // VTR_ASSERT(FALSE == vpr_setup.FPGA_SPICE_Opts.compact_routing_hierarchy); - // build_flatten_routing_modules(module_manager, L_device_rr_gsb, arch.spice->circuit_lib, - // arch.sram_inf.verilog_sram_inf_orgz->type, sram_model, - // vpr_setup.RoutingArch, rr_switches); - //} - + if (true == compress_routing) { + build_unique_routing_modules(module_manager, + vpr_device_ctx, + openfpga_ctx.vpr_device_annotation(), + openfpga_ctx.device_rr_gsb(), + openfpga_ctx.arch().circuit_lib, + openfpga_ctx.arch().config_protocol.type(), + sram_model, verbose); + } else { + VTR_ASSERT_SAFE(false == compress_routing); + build_flatten_routing_modules(module_manager, + vpr_device_ctx, + openfpga_ctx.vpr_device_annotation(), + openfpga_ctx.device_rr_gsb(), + openfpga_ctx.arch().circuit_lib, + openfpga_ctx.arch().config_protocol.type(), + sram_model, verbose); + } /* Build FPGA fabric top-level module */ //build_top_module(module_manager, arch.spice->circuit_lib, diff --git a/openfpga/src/fabric/build_device_module.h b/openfpga/src/fabric/build_device_module.h index 6830ca82b..fd585d986 100644 --- a/openfpga/src/fabric/build_device_module.h +++ b/openfpga/src/fabric/build_device_module.h @@ -16,6 +16,7 @@ namespace openfpga { ModuleManager build_device_module_graph(const DeviceContext& vpr_device_ctx, const OpenfpgaContext& openfpga_ctx, + const bool& compress_routing, const bool& duplicate_grid_pin, const bool& verbose); diff --git a/openfpga/src/fabric/build_routing_modules.cpp b/openfpga/src/fabric/build_routing_modules.cpp new file mode 100644 index 000000000..a08a0680f --- /dev/null +++ b/openfpga/src/fabric/build_routing_modules.cpp @@ -0,0 +1,1001 @@ +/******************************************************************** + * This file includes functions that are used to build modules + * for global routing architecture of a FPGA fabric + * Covering: + * 1. Connection blocks + * 2. Switch blocks + *******************************************************************/ +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_geometry.h" +#include "vtr_log.h" +#include "vtr_time.h" + +/* Headers from openfpgautil library */ +#include "openfpga_side_manager.h" + +#include "openfpga_reserved_words.h" +#include "openfpga_naming.h" + +#include "openfpga_rr_graph_utils.h" +#include "module_manager_utils.h" +#include "build_module_graph_utils.h" +#include "build_routing_module_utils.h" + +#include "build_routing_modules.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/********************************************************************* + * Generate a short interconneciton in switch box + * There are two cases should be noticed. + * 1. The actual fan-in of cur_rr_node is 0. In this case, + the cur_rr_node need to be short connected to itself + which is on the opposite side of this switch block + * 2. The actual fan-in of cur_rr_node is 0. In this case, + * The cur_rr_node need to connected to the drive_rr_node + ********************************************************************/ +static +void build_switch_block_module_short_interc(ModuleManager& module_manager, + const ModuleId& sb_module, + const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const e_side& chan_side, + const RRNodeId& cur_rr_node, + const RRNodeId& drive_rr_node, + const std::map& input_port_to_module_nets) { + /* Find the name of output port */ + ModulePortId output_port_id = find_switch_block_module_chan_port(module_manager, sb_module, + rr_graph, rr_gsb, + chan_side, cur_rr_node, OUT_PORT); + enum e_side input_pin_side = chan_side; + int index = -1; + + /* Generate the input port object */ + switch (rr_graph.node_type(drive_rr_node)) { + case OPIN: { + rr_gsb.get_node_side_and_index(rr_graph, drive_rr_node, IN_PORT, input_pin_side, index); + break; + } + case CHANX: + case CHANY: { + /* This should be an input in the data structure of RRGSB */ + if (cur_rr_node == drive_rr_node) { + /* To be strict, the input should locate on the opposite side. + * Use the else part if this may change in some architecture. + */ + SideManager side_manager(chan_side); + input_pin_side = side_manager.get_opposite(); + } else { + /* The input could be at any side of the switch block, find it */ + rr_gsb.get_node_side_and_index(rr_graph, drive_rr_node, IN_PORT, input_pin_side, index); + } + break; + } + default: /* SOURCE, IPIN, SINK are invalid*/ + VTR_LOGF_ERROR(__FILE__, __LINE__, "Invalid rr_node type! Should be [OPIN|CHANX|CHANY].\n"); + exit(1); + } + /* Find the name of input port */ + ModulePortId input_port_id = find_switch_block_module_input_port(module_manager, sb_module, rr_graph, rr_gsb, input_pin_side, drive_rr_node); + + /* The input port and output port must match in size */ + BasicPort input_port = module_manager.module_port(sb_module, input_port_id); + BasicPort output_port = module_manager.module_port(sb_module, output_port_id); + VTR_ASSERT(input_port.get_width() == output_port.get_width()); + + /* Create a module net for this short-wire connection */ + for (size_t pin_id = 0; pin_id < input_port.pins().size(); ++pin_id) { + ModuleNetId net = input_port_to_module_nets.at(input_port_id); + /* Skip Configuring the net source, it is done before */ + /* Configure the net sink */ + module_manager.add_module_net_sink(sb_module, net, sb_module, 0, output_port_id, output_port.pins()[pin_id]); + } +} + +/********************************************************************* + * Build a instance of a routing multiplexer as well as + * associated memory modules for a connection inside a switch block + ********************************************************************/ +static +void build_switch_block_mux_module(ModuleManager& module_manager, + const ModuleId& sb_module, + const VprDeviceAnnotation& device_annotation, + const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const CircuitLibrary& circuit_lib, + const e_side& chan_side, + const size_t& chan_node_id, + const RRNodeId& cur_rr_node, + const std::vector& driver_rr_nodes, + const RRSwitchId& switch_index, + const std::map& input_port_to_module_nets) { + /* Check current rr_node is CHANX or CHANY*/ + VTR_ASSERT((CHANX == rr_graph.node_type(cur_rr_node)) || (CHANY == rr_graph.node_type(cur_rr_node))); + + /* Get the circuit model id of the routing multiplexer */ + CircuitModelId mux_model = device_annotation.rr_switch_circuit_model(switch_index); + + /* Find the input size of the implementation of a routing multiplexer */ + size_t datapath_mux_size = driver_rr_nodes.size(); + + /* Find the module name of the multiplexer and try to find it in the module manager */ + std::string mux_module_name = generate_mux_subckt_name(circuit_lib, mux_model, datapath_mux_size, std::string("")); + ModuleId mux_module = module_manager.find_module(mux_module_name); + VTR_ASSERT (true == module_manager.valid_module_id(mux_module)); + + /* Get the MUX instance id from the module manager */ + size_t mux_instance_id = module_manager.num_instance(sb_module, mux_module); + /* Instanciate the MUX Module */ + module_manager.add_child_module(sb_module, mux_module); + + /* Give an instance name: this name should be consistent with the block name given in SDC manager, + * If you want to bind the SDC generation to modules + */ + std::string mux_instance_name = generate_sb_memory_instance_name(SWITCH_BLOCK_MUX_INSTANCE_PREFIX, chan_side, chan_node_id, std::string("")); + module_manager.set_child_instance_name(sb_module, mux_module, mux_instance_id, mux_instance_name); + + /* Generate input ports that are wired to the input bus of the routing multiplexer */ + std::vector sb_input_port_ids = find_switch_block_module_input_ports(module_manager, sb_module, rr_graph, rr_gsb, driver_rr_nodes); + + /* Link input bus port to Switch Block inputs */ + std::vector mux_model_input_ports = circuit_lib.model_ports_by_type(mux_model, CIRCUIT_MODEL_PORT_INPUT, true); + VTR_ASSERT(1 == mux_model_input_ports.size()); + /* Find the module port id of the input port */ + ModulePortId mux_input_port_id = module_manager.find_module_port(mux_module, circuit_lib.port_prefix(mux_model_input_ports[0])); + VTR_ASSERT(true == module_manager.valid_module_port_id(mux_module, mux_input_port_id)); + BasicPort mux_input_port = module_manager.module_port(mux_module, mux_input_port_id); + + /* Check port size should match */ + VTR_ASSERT(mux_input_port.get_width() == sb_input_port_ids.size()); + for (size_t pin_id = 0; pin_id < sb_input_port_ids.size(); ++pin_id) { + /* Use the exising net */ + ModuleNetId net = input_port_to_module_nets.at(sb_input_port_ids[pin_id]); + /* Configure the net source only if it is not yet in the source list */ + if (false == module_manager.net_source_exist(sb_module, net, sb_module, 0, sb_input_port_ids[pin_id], 0)) { + module_manager.add_module_net_source(sb_module, net, sb_module, 0, sb_input_port_ids[pin_id], 0); + } + /* Configure the net sink */ + module_manager.add_module_net_sink(sb_module, net, mux_module, mux_instance_id, mux_input_port_id, mux_input_port.pins()[pin_id]); + } + + /* Link output port to Switch Block outputs */ + std::vector mux_model_output_ports = circuit_lib.model_ports_by_type(mux_model, CIRCUIT_MODEL_PORT_OUTPUT, true); + VTR_ASSERT(1 == mux_model_output_ports.size()); + /* Use the port name convention in the circuit library */ + ModulePortId mux_output_port_id = module_manager.find_module_port(mux_module, circuit_lib.port_prefix(mux_model_output_ports[0])); + VTR_ASSERT(true == module_manager.valid_module_port_id(mux_module, mux_output_port_id)); + BasicPort mux_output_port = module_manager.module_port(mux_module, mux_output_port_id); + ModulePortId sb_output_port_id = find_switch_block_module_chan_port(module_manager, sb_module, rr_graph, rr_gsb, chan_side, cur_rr_node, OUT_PORT); + BasicPort sb_output_port = module_manager.module_port(sb_module, sb_output_port_id); + + /* Check port size should match */ + VTR_ASSERT(sb_output_port.get_width() == mux_output_port.get_width()); + for (size_t pin_id = 0; pin_id < mux_output_port.pins().size(); ++pin_id) { + ModuleNetId net = module_manager.create_module_net(sb_module); + /* Configuring the net source */ + module_manager.add_module_net_source(sb_module, net, mux_module, mux_instance_id, mux_output_port_id, mux_output_port.pins()[pin_id]); + /* Configure the net sink */ + module_manager.add_module_net_sink(sb_module, net, sb_module, 0, sb_output_port_id, sb_output_port.pins()[pin_id]); + } + + /* Instanciate memory modules */ + /* Find the name and module id of the memory module */ + std::string mem_module_name = generate_mux_subckt_name(circuit_lib, mux_model, datapath_mux_size, std::string(MEMORY_MODULE_POSTFIX)); + ModuleId mem_module = module_manager.find_module(mem_module_name); + VTR_ASSERT (true == module_manager.valid_module_id(mem_module)); + + size_t mem_instance_id = module_manager.num_instance(sb_module, mem_module); + module_manager.add_child_module(sb_module, mem_module); + /* Give an instance name: this name should be consistent with the block name given in bitstream manager, + * If you want to bind the bitstream generation to modules + */ + std::string mem_instance_name = generate_sb_memory_instance_name(SWITCH_BLOCK_MEM_INSTANCE_PREFIX, chan_side, chan_node_id, std::string("")); + module_manager.set_child_instance_name(sb_module, mem_module, mem_instance_id, mem_instance_name); + + /* Add nets to connect regular and mode-select SRAM ports to the SRAM port of memory module */ + add_module_nets_between_logic_and_memory_sram_bus(module_manager, sb_module, + mux_module, mux_instance_id, + mem_module, mem_instance_id, + circuit_lib, mux_model); + /* Update memory and instance list */ + module_manager.add_configurable_child(sb_module, mem_module, mem_instance_id); +} + +/********************************************************************* + * Generate child modules for a interconnection inside switch block + * The interconnection could be either a wire or a routing multiplexer, + * which depends on the fan-in of the rr_nodes in the switch block + ********************************************************************/ +static +void build_switch_block_interc_modules(ModuleManager& module_manager, + const ModuleId& sb_module, + const VprDeviceAnnotation& device_annotation, + const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const CircuitLibrary& circuit_lib, + const e_side& chan_side, + const size_t& chan_node_id, + const std::map& input_port_to_module_nets) { + std::vector driver_rr_nodes; + + /* Get the node */ + const RRNodeId& cur_rr_node = rr_gsb.get_chan_node(chan_side, chan_node_id); + + /* Determine if the interc lies inside a channel wire, that is interc between segments */ + if (false == rr_gsb.is_sb_node_passing_wire(rr_graph, chan_side, chan_node_id)) { + driver_rr_nodes = get_rr_graph_configurable_driver_nodes(rr_graph, cur_rr_node); + /* Special: if there are zero-driver nodes. We skip here */ + if (0 == driver_rr_nodes.size()) { + return; + } + } + + if (0 == driver_rr_nodes.size()) { + /* Print a special direct connection*/ + build_switch_block_module_short_interc(module_manager, sb_module, + rr_graph, rr_gsb, + chan_side, cur_rr_node, + cur_rr_node, + input_port_to_module_nets); + } else if (1 == driver_rr_nodes.size()) { + /* Print a direct connection*/ + build_switch_block_module_short_interc(module_manager, sb_module, + rr_graph, rr_gsb, chan_side, cur_rr_node, + driver_rr_nodes[0], + input_port_to_module_nets); + } else if (1 < driver_rr_nodes.size()) { + /* Print the multiplexer, fan_in >= 2 */ + std::vector driver_switches = get_rr_graph_driver_switches(rr_graph, cur_rr_node); + VTR_ASSERT(1 == driver_switches.size()); + build_switch_block_mux_module(module_manager, + sb_module, device_annotation, rr_graph, rr_gsb, + circuit_lib, + chan_side, chan_node_id, cur_rr_node, + driver_rr_nodes, + driver_switches[0], + input_port_to_module_nets); + } /*Nothing should be done else*/ +} + + +/******************************************************************** + * Build a module for a switch block whose detailed description is + * available in a RRGSB object + * A Switch Box module consists of following ports: + * 1. Channel Y [x][y] inputs + * 2. Channel X [x+1][y] inputs + * 3. Channel Y [x][y-1] outputs + * 4. Channel X [x][y] outputs + * 5. Grid[x][y+1] Right side outputs pins + * 6. Grid[x+1][y+1] Left side output pins + * 7. Grid[x+1][y+1] Bottom side output pins + * 8. Grid[x+1][y] Top side output pins + * 9. Grid[x+1][y] Left side output pins + * 10. Grid[x][y] Right side output pins + * 11. Grid[x][y] Top side output pins + * 12. Grid[x][y+1] Bottom side output pins + * + * Location of a Switch Box in FPGA fabric: + * + * -------------- -------------- + * | | | | + * | Grid | ChanY | Grid | + * | [x][y+1] | [x][y+1] | [x+1][y+1] | + * | | | | + * -------------- -------------- + * ---------- + * ChanX | Switch | ChanX + * [x][y] | Box | [x+1][y] + * | [x][y] | + * ---------- + * -------------- -------------- + * | | | | + * | Grid | ChanY | Grid | + * | [x][y] | [x][y] | [x+1][y] | + * | | | | + * -------------- -------------- + * + * Switch Block pin location map + * + * Grid[x][y+1] ChanY[x][y+1] Grid[x+1][y+1] + * right_pins inputs/outputs left_pins + * | ^ | + * | | | + * v v v + * +-----------------------------------------------+ + * | | + * Grid[x][y+1] | | Grid[x+1][y+1] + * bottom_pins---->| |<---- bottom_pins + * | | + * ChanX[x][y] | Switch Box [x][y] | ChanX[x+1][y] + * inputs/outputs<--->| |<---> inputs/outputs + * | | + * Grid[x][y+1] | | Grid[x+1][y+1] + * top_pins---->| |<---- top_pins + * | | + * +-----------------------------------------------+ + * ^ ^ ^ + * | | | + * | v | + * Grid[x][y] ChanY[x][y] Grid[x+1][y] + * right_pins inputs/outputs left_pins + * + * + ********************************************************************/ +static +void build_switch_block_module(ModuleManager& module_manager, + const VprDeviceAnnotation& device_annotation, + const RRGraph& rr_graph, + const CircuitLibrary& circuit_lib, + const e_config_protocol_type& sram_orgz_type, + const CircuitModelId& sram_model, + const RRGSB& rr_gsb, + const bool& verbose) { + /* Create a Module of Switch Block and add to module manager */ + vtr::Point gsb_coordinate(rr_gsb.get_sb_x(), rr_gsb.get_sb_y()); + ModuleId sb_module = module_manager.add_module(generate_switch_block_module_name(gsb_coordinate)); + + VTR_LOGV(verbose, + "Building module '%s'...", + generate_switch_block_module_name(gsb_coordinate).c_str()); + + /* Create a cache (fast look up) for module nets whose source are input ports */ + std::map input_port_to_module_nets; + + /* Add routing channel ports at each side of the GSB */ + for (size_t side = 0; side < rr_gsb.get_num_sides(); ++side) { + SideManager side_manager(side); + + for (size_t itrack = 0; itrack < rr_gsb.get_chan_width(side_manager.get_side()); ++itrack) { + std::string port_name = generate_sb_module_track_port_name(rr_graph.node_type(rr_gsb.get_chan_node(side_manager.get_side(), itrack)), + side_manager.get_side(), itrack, + rr_gsb.get_chan_node_direction(side_manager.get_side(), itrack)); + BasicPort module_port(port_name, 1); /* Every track has a port size of 1 */ + + switch (rr_gsb.get_chan_node_direction(side_manager.get_side(), itrack)) { + case OUT_PORT: + module_manager.add_port(sb_module, module_port, ModuleManager::MODULE_OUTPUT_PORT); + break; + case IN_PORT: { + ModulePortId input_port_id = module_manager.add_port(sb_module, module_port, ModuleManager::MODULE_INPUT_PORT); + /* Cache the input net */ + ModuleNetId net = module_manager.create_module_net(sb_module); + module_manager.add_module_net_source(sb_module, net, sb_module, 0, input_port_id, 0); + input_port_to_module_nets[input_port_id] = net; + break; + } + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid direction of chan[%d][%d]_track[%d]!\n", + rr_gsb.get_sb_x(), rr_gsb.get_sb_y(), itrack); + exit(1); + } + } + /* Dump OPINs of adjacent CLBs */ + for (size_t inode = 0; inode < rr_gsb.get_num_opin_nodes(side_manager.get_side()); ++inode) { + vtr::Point port_coord(rr_graph.node_xlow(rr_gsb.get_opin_node(side_manager.get_side(), inode)), + rr_graph.node_ylow(rr_gsb.get_opin_node(side_manager.get_side(), inode))); + std::string port_name = generate_sb_module_grid_port_name(side_manager.get_side(), + rr_graph.node_side(rr_gsb.get_opin_node(side_manager.get_side(), inode)), + rr_graph.node_pin_num(rr_gsb.get_opin_node(side_manager.get_side(), inode))); + BasicPort module_port(port_name, 1); /* Every grid output has a port size of 1 */ + /* Grid outputs are inputs of switch blocks */ + ModulePortId input_port_id = module_manager.add_port(sb_module, module_port, ModuleManager::MODULE_INPUT_PORT); + + /* Cache the input net */ + ModuleNetId net = module_manager.create_module_net(sb_module); + module_manager.add_module_net_source(sb_module, net, sb_module, 0, input_port_id, 0); + input_port_to_module_nets[input_port_id] = net; + } + } + + /* Add routing multiplexers as child modules */ + for (size_t side = 0; side < rr_gsb.get_num_sides(); ++side) { + SideManager side_manager(side); + for (size_t itrack = 0; itrack < rr_gsb.get_chan_width(side_manager.get_side()); ++itrack) { + /* We care INC_DIRECTION tracks at this side*/ + if (OUT_PORT == rr_gsb.get_chan_node_direction(side_manager.get_side(), itrack)) { + build_switch_block_interc_modules(module_manager, + sb_module, device_annotation, rr_graph, rr_gsb, + circuit_lib, + side_manager.get_side(), + itrack, + input_port_to_module_nets); + } + } + } + + /* Add global ports to the pb_module: + * This is a much easier job after adding sub modules (instances), + * we just need to find all the global ports from the child modules and build a list of it + */ + add_module_global_ports_from_child_modules(module_manager, sb_module); + + /* Count shared SRAM ports from the sub-modules under this Verilog module + * This is a much easier job after adding sub modules (instances), + * we just need to find all the I/O ports from the child modules and build a list of it + */ + size_t module_num_shared_config_bits = find_module_num_shared_config_bits_from_child_modules(module_manager, sb_module); + if (0 < module_num_shared_config_bits) { + add_reserved_sram_ports_to_module_manager(module_manager, sb_module, module_num_shared_config_bits); + } + + /* Count SRAM ports from the sub-modules under this Verilog module + * This is a much easier job after adding sub modules (instances), + * we just need to find all the I/O ports from the child modules and build a list of it + */ + size_t module_num_config_bits = find_module_num_config_bits_from_child_modules(module_manager, sb_module, circuit_lib, sram_model, sram_orgz_type); + if (0 < module_num_config_bits) { + add_sram_ports_to_module_manager(module_manager, sb_module, circuit_lib, sram_model, sram_orgz_type, module_num_config_bits); + } + + /* Add all the nets to connect configuration ports from memory module to primitive modules + * This is a one-shot addition that covers all the memory modules in this primitive module! + */ + if (0 < module_manager.configurable_children(sb_module).size()) { + add_module_nets_memory_config_bus(module_manager, sb_module, + sram_orgz_type, circuit_lib.design_tech_type(sram_model)); + } + + VTR_LOGV(verbose, "Done\n"); +} + +/********************************************************************* + * Print a short interconneciton in connection + ********************************************************************/ +static +void build_connection_block_module_short_interc(ModuleManager& module_manager, + const ModuleId& cb_module, + const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const t_rr_type& cb_type, + const RRNodeId& src_rr_node, + const std::map& input_port_to_module_nets) { + /* Ensure we have only one 1 driver node */ + std::vector driver_rr_nodes = get_rr_graph_configurable_driver_nodes(rr_graph, src_rr_node); + + /* We have OPINs since we may have direct connections: + * These connections should be handled by other functions in the compact_netlist.c + * So we just return here for OPINs + */ + if (0 == driver_rr_nodes.size()) { + return; + } + + /* Find the driver node */ + VTR_ASSERT_SAFE(1 == driver_rr_nodes.size()); + const RRNodeId& driver_rr_node = driver_rr_nodes[0]; + + VTR_ASSERT((CHANX == rr_graph.node_type(driver_rr_node)) || (CHANY == rr_graph.node_type(driver_rr_node))); + + /* Create port description for the routing track middle output */ + ModulePortId input_port_id = find_connection_block_module_chan_port(module_manager, cb_module, rr_graph, rr_gsb, cb_type, driver_rr_node); + + /* Create port description for input pin of a CLB */ + ModulePortId ipin_port_id = find_connection_block_module_ipin_port(module_manager, cb_module, rr_graph, rr_gsb, src_rr_node); + + /* The input port and output port must match in size */ + BasicPort input_port = module_manager.module_port(cb_module, input_port_id); + BasicPort ipin_port = module_manager.module_port(cb_module, ipin_port_id); + VTR_ASSERT(input_port.get_width() == ipin_port.get_width()); + + /* Create a module net for this short-wire connection */ + for (size_t pin_id = 0; pin_id < input_port.pins().size(); ++pin_id) { + ModuleNetId net = input_port_to_module_nets.at(input_port_id); + /* Skip Configuring the net source, it is done before */ + /* Configure the net sink */ + module_manager.add_module_net_sink(cb_module, net, cb_module, 0, ipin_port_id, ipin_port.pins()[pin_id]); + } +} + +/********************************************************************* + * Build a instance of a routing multiplexer as well as + * associated memory modules for a connection inside a connection block + ********************************************************************/ +static +void build_connection_block_mux_module(ModuleManager& module_manager, + const ModuleId& cb_module, + const VprDeviceAnnotation& device_annotation, + const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const t_rr_type& cb_type, + const CircuitLibrary& circuit_lib, + const e_side& cb_ipin_side, + const size_t& ipin_index, + const std::map& input_port_to_module_nets) { + const RRNodeId& cur_rr_node = rr_gsb.get_ipin_node(cb_ipin_side, ipin_index); + /* Check current rr_node is an input pin of a CLB */ + VTR_ASSERT(IPIN == rr_graph.node_type(cur_rr_node)); + + /* Build a vector of driver rr_nodes */ + std::vector driver_rr_nodes = get_rr_graph_configurable_driver_nodes(rr_graph, cur_rr_node); + + std::vector driver_switches = get_rr_graph_driver_switches(rr_graph, cur_rr_node); + VTR_ASSERT(1 == driver_switches.size()); + + /* Get the circuit model id of the routing multiplexer */ + CircuitModelId mux_model = device_annotation.rr_switch_circuit_model(driver_switches[0]); + + /* Find the input size of the implementation of a routing multiplexer */ + size_t datapath_mux_size = driver_rr_nodes.size(); + + /* Find the module name of the multiplexer and try to find it in the module manager */ + std::string mux_module_name = generate_mux_subckt_name(circuit_lib, mux_model, datapath_mux_size, std::string("")); + ModuleId mux_module = module_manager.find_module(mux_module_name); + VTR_ASSERT (true == module_manager.valid_module_id(mux_module)); + + /* Get the MUX instance id from the module manager */ + size_t mux_instance_id = module_manager.num_instance(cb_module, mux_module); + module_manager.add_child_module(cb_module, mux_module); + + /* Give an instance name: this name should be consistent with the block name given in SDC manager, + * If you want to bind the SDC generation to modules + */ + std::string mux_instance_name = generate_cb_mux_instance_name(CONNECTION_BLOCK_MUX_INSTANCE_PREFIX, rr_graph.node_side(rr_gsb.get_ipin_node(cb_ipin_side, ipin_index)), ipin_index, std::string("")); + module_manager.set_child_instance_name(cb_module, mux_module, mux_instance_id, mux_instance_name); + + /* TODO: Generate input ports that are wired to the input bus of the routing multiplexer */ + std::vector cb_input_port_ids = find_connection_block_module_input_ports(module_manager, cb_module, rr_graph, rr_gsb, cb_type, driver_rr_nodes); + + /* Link input bus port to Switch Block inputs */ + std::vector mux_model_input_ports = circuit_lib.model_ports_by_type(mux_model, CIRCUIT_MODEL_PORT_INPUT, true); + VTR_ASSERT(1 == mux_model_input_ports.size()); + /* Find the module port id of the input port */ + ModulePortId mux_input_port_id = module_manager.find_module_port(mux_module, circuit_lib.port_prefix(mux_model_input_ports[0])); + VTR_ASSERT(true == module_manager.valid_module_port_id(mux_module, mux_input_port_id)); + BasicPort mux_input_port = module_manager.module_port(mux_module, mux_input_port_id); + + /* Check port size should match */ + VTR_ASSERT(mux_input_port.get_width() == cb_input_port_ids.size()); + for (size_t pin_id = 0; pin_id < cb_input_port_ids.size(); ++pin_id) { + /* Use the exising net */ + ModuleNetId net = input_port_to_module_nets.at(cb_input_port_ids[pin_id]); + /* No need to configure the net source since it is already done before */ + /* Configure the net sink */ + module_manager.add_module_net_sink(cb_module, net, mux_module, mux_instance_id, mux_input_port_id, mux_input_port.pins()[pin_id]); + } + + /* Link output port to Switch Block outputs */ + std::vector mux_model_output_ports = circuit_lib.model_ports_by_type(mux_model, CIRCUIT_MODEL_PORT_OUTPUT, true); + VTR_ASSERT(1 == mux_model_output_ports.size()); + /* Use the port name convention in the circuit library */ + ModulePortId mux_output_port_id = module_manager.find_module_port(mux_module, circuit_lib.port_prefix(mux_model_output_ports[0])); + VTR_ASSERT(true == module_manager.valid_module_port_id(mux_module, mux_output_port_id)); + BasicPort mux_output_port = module_manager.module_port(mux_module, mux_output_port_id); + ModulePortId cb_output_port_id = find_connection_block_module_ipin_port(module_manager, cb_module, rr_graph, rr_gsb, cur_rr_node); + BasicPort cb_output_port = module_manager.module_port(cb_module, cb_output_port_id); + + /* Check port size should match */ + VTR_ASSERT(cb_output_port.get_width() == mux_output_port.get_width()); + for (size_t pin_id = 0; pin_id < mux_output_port.pins().size(); ++pin_id) { + ModuleNetId net = module_manager.create_module_net(cb_module); + /* Configuring the net source */ + module_manager.add_module_net_source(cb_module, net, mux_module, mux_instance_id, mux_output_port_id, mux_output_port.pins()[pin_id]); + /* Configure the net sink */ + module_manager.add_module_net_sink(cb_module, net, cb_module, 0, cb_output_port_id, cb_output_port.pins()[pin_id]); + } + + /* Instanciate memory modules */ + /* Find the name and module id of the memory module */ + std::string mem_module_name = generate_mux_subckt_name(circuit_lib, mux_model, datapath_mux_size, std::string(MEMORY_MODULE_POSTFIX)); + ModuleId mem_module = module_manager.find_module(mem_module_name); + VTR_ASSERT (true == module_manager.valid_module_id(mem_module)); + + size_t mem_instance_id = module_manager.num_instance(cb_module, mem_module); + module_manager.add_child_module(cb_module, mem_module); + + /* Give an instance name: this name should be consistent with the block name given in bitstream manager, + * If you want to bind the bitstream generation to modules + */ + std::string mem_instance_name = generate_cb_memory_instance_name(CONNECTION_BLOCK_MEM_INSTANCE_PREFIX, rr_graph.node_side(rr_gsb.get_ipin_node(cb_ipin_side, ipin_index)), ipin_index, std::string("")); + module_manager.set_child_instance_name(cb_module, mem_module, mem_instance_id, mem_instance_name); + + /* Add nets to connect regular and mode-select SRAM ports to the SRAM port of memory module */ + add_module_nets_between_logic_and_memory_sram_bus(module_manager, cb_module, + mux_module, mux_instance_id, + mem_module, mem_instance_id, + circuit_lib, mux_model); + /* Update memory and instance list */ + module_manager.add_configurable_child(cb_module, mem_module, mem_instance_id); +} + +/******************************************************************** + * Print internal connections of a connection block + * For a IPIN node that is driven by only 1 fan-in, + * a short wire will be created + * For a IPIN node that is driven by more than two fan-ins, + * a routing multiplexer will be instanciated + ********************************************************************/ +static +void build_connection_block_interc_modules(ModuleManager& module_manager, + const ModuleId& cb_module, + const VprDeviceAnnotation& device_annotation, + const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const t_rr_type& cb_type, + const CircuitLibrary& circuit_lib, + const e_side& cb_ipin_side, + const size_t& ipin_index, + const std::map& input_port_to_module_nets) { + const RRNodeId& src_rr_node = rr_gsb.get_ipin_node(cb_ipin_side, ipin_index); + + if (1 > rr_graph.node_in_edges(src_rr_node).size()) { + return; /* This port has no driver, skip it */ + } else if (1 == rr_graph.node_in_edges(src_rr_node).size()) { + /* Print a direct connection */ + build_connection_block_module_short_interc(module_manager, cb_module, rr_graph, rr_gsb, cb_type, src_rr_node, input_port_to_module_nets); + + } else if (1 < rr_graph.node_in_edges(src_rr_node).size()) { + /* Print the multiplexer, fan_in >= 2 */ + build_connection_block_mux_module(module_manager, + cb_module, device_annotation, + rr_graph, rr_gsb, cb_type, + circuit_lib, + cb_ipin_side, ipin_index, + input_port_to_module_nets); + } /*Nothing should be done else*/ +} + +/******************************************************************** + * Generate a module of a connection Box (Type: [CHANX|CHANY]) + * Actually it is very similiar to switch box but + * the difference is connection boxes connect Grid INPUT Pins to channels + * NOTE: direct connection between CLBs should NOT be included inside this + * module! They should be added in the top-level module as their connection + * is not limited to adjacent CLBs!!! + * + * Location of a X- and Y-direction Connection Block in FPGA fabric + * +------------+ +-------------+ + * | |------>| | + * | CLB |<------| Y-direction | + * | | ... | Connection | + * | |------>| Block | + * +------------+ +-------------+ + * | ^ ... | | ^ ... | + * v | v v | v + * +-------------------+ +-------------+ + * --->| |--->| | + * <---| X-direction |<---| Switch | + * ...| Connection block |... | Block | + * --->| |--->| | + * +-------------------+ +-------------+ + * + * Internal structure: + * This is an example of a X-direction connection block + * Note that middle output ports are shorted wire from inputs of routing tracks, + * which are also the inputs of routing multiplexer of the connection block + * + * CLB Input Pins + * (IPINs) + * ^ ^ ^ + * | | ... | + * +--------------------------+ + * | ^ ^ ^ | + * | | | ... | | + * | +--------------------+ | + * | | routing | | + * | | multiplexers | | + * | +--------------------+ | + * | middle outputs | + * | of routing channel | + * | ^ ^ ^ ^ ^ ^ ^ ^ | + * | | | | | ... | | | | | + * in[0] -->|------------------------->|---> out[0] + * out[1] <--|<-------------------------|<--- in[1] + * | ... | + * in[W-2] -->|------------------------->|---> out[W-2] + * out[W-1] <--|<-------------------------|<--- in[W-1] + * +--------------------------+ + * + * W: routing channel width + * + ********************************************************************/ +static +void build_connection_block_module(ModuleManager& module_manager, + const VprDeviceAnnotation& device_annotation, + const RRGraph& rr_graph, + const CircuitLibrary& circuit_lib, + const e_config_protocol_type& sram_orgz_type, + const CircuitModelId& sram_model, + const RRGSB& rr_gsb, + const t_rr_type& cb_type, + const bool& verbose) { + /* Create the netlist */ + vtr::Point gsb_coordinate(rr_gsb.get_cb_x(cb_type), rr_gsb.get_cb_y(cb_type)); + + /* Create a Verilog Module based on the circuit model, and add to module manager */ + ModuleId cb_module = module_manager.add_module(generate_connection_block_module_name(cb_type, gsb_coordinate)); + + VTR_LOGV(verbose, + "Building module '%s'...", + generate_connection_block_module_name(cb_type, gsb_coordinate).c_str()); + + /* Add the input and output ports of routing tracks in the channel + * Routing tracks pass through the connection blocks + */ + for (size_t itrack = 0; itrack < rr_gsb.get_cb_chan_width(cb_type); ++itrack) { + vtr::Point port_coord(rr_gsb.get_cb_x(cb_type), rr_gsb.get_cb_y(cb_type)); + std::string port_name = generate_cb_module_track_port_name(cb_type, + itrack, + IN_PORT); + BasicPort module_port(port_name, 1); /* Every track has a port size of 1 */ + module_manager.add_port(cb_module, module_port, ModuleManager::MODULE_INPUT_PORT); + } + for (size_t itrack = 0; itrack < rr_gsb.get_cb_chan_width(cb_type); ++itrack) { + vtr::Point port_coord(rr_gsb.get_cb_x(cb_type), rr_gsb.get_cb_y(cb_type)); + std::string port_name = generate_cb_module_track_port_name(cb_type, + itrack, + OUT_PORT); + BasicPort module_port(port_name, 1); /* Every track has a port size of 1 */ + module_manager.add_port(cb_module, module_port, ModuleManager::MODULE_OUTPUT_PORT); + } + + /* Add the input pins of grids, which are output ports of the connection block */ + std::vector cb_ipin_sides = rr_gsb.get_cb_ipin_sides(cb_type); + for (size_t iside = 0; iside < cb_ipin_sides.size(); ++iside) { + enum e_side cb_ipin_side = cb_ipin_sides[iside]; + for (size_t inode = 0; inode < rr_gsb.get_num_ipin_nodes(cb_ipin_side); ++inode) { + const RRNodeId& ipin_node = rr_gsb.get_ipin_node(cb_ipin_side, inode); + vtr::Point port_coord(rr_graph.node_xlow(ipin_node), rr_graph.node_ylow(ipin_node)); + std::string port_name = generate_cb_module_grid_port_name(cb_ipin_side, + rr_graph.node_pin_num(ipin_node)); + BasicPort module_port(port_name, 1); /* Every grid output has a port size of 1 */ + /* Grid outputs are inputs of switch blocks */ + module_manager.add_port(cb_module, module_port, ModuleManager::MODULE_OUTPUT_PORT); + } + } + + /* Create a cache (fast look up) for module nets whose source are input ports */ + std::map input_port_to_module_nets; + + /* Generate short-wire connection for each routing track : + * Each input port is short-wired to its output port and middle output port + * + * in[i] ----------> out[i] + * | + * +-----> mid_out[i] + */ + for (size_t itrack = 0; itrack < rr_gsb.get_cb_chan_width(cb_type); ++itrack) { + vtr::Point port_coord(rr_gsb.get_cb_x(cb_type), rr_gsb.get_cb_y(cb_type)); + /* Create a port description for the input */ + std::string input_port_name = generate_cb_module_track_port_name(cb_type, + itrack, + IN_PORT); + ModulePortId input_port_id = module_manager.find_module_port(cb_module, input_port_name); + BasicPort input_port = module_manager.module_port(cb_module, input_port_id); + + /* Create a port description for the output */ + std::string output_port_name = generate_cb_module_track_port_name(cb_type, + itrack, + OUT_PORT); + ModulePortId output_port_id = module_manager.find_module_port(cb_module, output_port_name); + BasicPort output_port = module_manager.module_port(cb_module, output_port_id); + + /* Ensure port size matching */ + VTR_ASSERT(1 == input_port.get_width()); + VTR_ASSERT(input_port.get_width() == output_port.get_width()); + + /* Create short-wires: input port ---> output port + * Do short-wires: input port ---> middle output port + */ + for (size_t pin_id = 0; pin_id < input_port.pins().size(); ++pin_id) { + ModuleNetId net = module_manager.create_module_net(cb_module); + module_manager.add_module_net_source(cb_module, net, cb_module, 0, input_port_id, input_port.pins()[pin_id]); + module_manager.add_module_net_sink(cb_module, net, cb_module, 0, output_port_id, output_port.pins()[pin_id]); + /* Cache the module net */ + input_port_to_module_nets[input_port_id] = net; + } + } + + /* Add sub modules of routing multiplexers or direct interconnect*/ + for (size_t iside = 0; iside < cb_ipin_sides.size(); ++iside) { + enum e_side cb_ipin_side = cb_ipin_sides[iside]; + for (size_t inode = 0; inode < rr_gsb.get_num_ipin_nodes(cb_ipin_side); ++inode) { + build_connection_block_interc_modules(module_manager, + cb_module, device_annotation, + rr_graph, + rr_gsb, cb_type, + circuit_lib, + cb_ipin_side, inode, + input_port_to_module_nets); + } + } + + /* Add global ports to the pb_module: + * This is a much easier job after adding sub modules (instances), + * we just need to find all the global ports from the child modules and build a list of it + */ + add_module_global_ports_from_child_modules(module_manager, cb_module); + + /* Count shared SRAM ports from the sub-modules under this Verilog module + * This is a much easier job after adding sub modules (instances), + * we just need to find all the I/O ports from the child modules and build a list of it + */ + size_t module_num_shared_config_bits = find_module_num_shared_config_bits_from_child_modules(module_manager, cb_module); + if (0 < module_num_shared_config_bits) { + add_reserved_sram_ports_to_module_manager(module_manager, cb_module, module_num_shared_config_bits); + } + + /* Count SRAM ports from the sub-modules under this Verilog module + * This is a much easier job after adding sub modules (instances), + * we just need to find all the I/O ports from the child modules and build a list of it + */ + size_t module_num_config_bits = find_module_num_config_bits_from_child_modules(module_manager, cb_module, circuit_lib, sram_model, sram_orgz_type); + if (0 < module_num_config_bits) { + add_sram_ports_to_module_manager(module_manager, cb_module, circuit_lib, sram_model, sram_orgz_type, module_num_config_bits); + } + + /* Add all the nets to connect configuration ports from memory module to primitive modules + * This is a one-shot addition that covers all the memory modules in this primitive module! + */ + if (0 < module_manager.configurable_children(cb_module).size()) { + add_module_nets_memory_config_bus(module_manager, cb_module, + sram_orgz_type, circuit_lib.design_tech_type(sram_model)); + } + + VTR_LOGV(verbose, "Done\n"); +} + + +/******************************************************************** + * Iterate over all the connection blocks in a device + * and build a module for each of them + *******************************************************************/ +static +void build_flatten_connection_block_modules(ModuleManager& module_manager, + const DeviceContext& device_ctx, + const VprDeviceAnnotation& device_annotation, + const DeviceRRGSB& device_rr_gsb, + const CircuitLibrary& circuit_lib, + const e_config_protocol_type& sram_orgz_type, + const CircuitModelId& sram_model, + const t_rr_type& cb_type, + const bool& verbose) { + /* Build unique X-direction connection block modules */ + vtr::Point cb_range = device_rr_gsb.get_gsb_range(); + + for (size_t ix = 0; ix < cb_range.x(); ++ix) { + for (size_t iy = 0; iy < cb_range.y(); ++iy) { + /* Check if the connection block exists in the device! + * Some of them do NOT exist due to heterogeneous blocks (height > 1) + * We will skip those modules + */ + const RRGSB& rr_gsb = device_rr_gsb.get_gsb(ix, iy); + if (false == rr_gsb.is_cb_exist(cb_type)) { + continue; + } + build_connection_block_module(module_manager, + device_annotation, + device_ctx.rr_graph, + circuit_lib, + sram_orgz_type, sram_model, + rr_gsb, cb_type, + verbose); + } + } +} + +/******************************************************************** + * A top-level function of this file + * Build all the modules for global routing architecture of a FPGA fabric + * in a flatten way: + * Each connection block and switch block will be generated as a unique module + * Covering: + * 1. Connection blocks + * 2. Switch blocks + *******************************************************************/ +void build_flatten_routing_modules(ModuleManager& module_manager, + const DeviceContext& device_ctx, + const VprDeviceAnnotation& device_annotation, + const DeviceRRGSB& device_rr_gsb, + const CircuitLibrary& circuit_lib, + const e_config_protocol_type& sram_orgz_type, + const CircuitModelId& sram_model, + const bool& verbose) { + + vtr::ScopedStartFinishTimer timer("Build routing modules..."); + + vtr::Point sb_range = device_rr_gsb.get_gsb_range(); + + /* Build unique switch block modules */ + for (size_t ix = 0; ix < sb_range.x(); ++ix) { + for (size_t iy = 0; iy < sb_range.y(); ++iy) { + const RRGSB& rr_gsb = device_rr_gsb.get_gsb(ix, iy); + if (false == rr_gsb.is_sb_exist()) { + continue; + } + build_switch_block_module(module_manager, + device_annotation, + device_ctx.rr_graph, + circuit_lib, + sram_orgz_type, sram_model, + rr_gsb, + verbose); + } + } + + build_flatten_connection_block_modules(module_manager, + device_ctx, + device_annotation, + device_rr_gsb, + circuit_lib, + sram_orgz_type, sram_model, + CHANX, + verbose); + + build_flatten_connection_block_modules(module_manager, + device_ctx, + device_annotation, + device_rr_gsb, + circuit_lib, + sram_orgz_type, sram_model, + CHANY, + verbose); +} + +/******************************************************************** + * A top-level function of this file + * Build all the unique modules for global routing architecture of a FPGA fabric + * This function will use unique module list built in device_rr_gsb, + * to build only unique modules (in terms of graph connections) of + * 1. Connection blocks + * 2. Switch blocks + * + * Note: this function SHOULD be called only when + * the option compact_routing_hierarchy is turned on!!! + *******************************************************************/ +void build_unique_routing_modules(ModuleManager& module_manager, + const DeviceContext& device_ctx, + const VprDeviceAnnotation& device_annotation, + const DeviceRRGSB& device_rr_gsb, + const CircuitLibrary& circuit_lib, + const e_config_protocol_type& sram_orgz_type, + const CircuitModelId& sram_model, + const bool& verbose) { + + vtr::ScopedStartFinishTimer timer("Build unique routing modules..."); + + /* Build unique switch block modules */ + for (size_t isb = 0; isb < device_rr_gsb.get_num_sb_unique_module(); ++isb) { + const RRGSB& unique_mirror = device_rr_gsb.get_sb_unique_module(isb); + build_switch_block_module(module_manager, + device_annotation, + device_ctx.rr_graph, + circuit_lib, + sram_orgz_type, sram_model, + unique_mirror, + verbose); + } + + /* Build unique X-direction connection block modules */ + for (size_t icb = 0; icb < device_rr_gsb.get_num_cb_unique_module(CHANX); ++icb) { + const RRGSB& unique_mirror = device_rr_gsb.get_cb_unique_module(CHANX, icb); + + build_connection_block_module(module_manager, + device_annotation, + device_ctx.rr_graph, + circuit_lib, + sram_orgz_type, sram_model, + unique_mirror, CHANX, + verbose); + } + + /* Build unique X-direction connection block modules */ + for (size_t icb = 0; icb < device_rr_gsb.get_num_cb_unique_module(CHANY); ++icb) { + const RRGSB& unique_mirror = device_rr_gsb.get_cb_unique_module(CHANY, icb); + + build_connection_block_module(module_manager, + device_annotation, + device_ctx.rr_graph, + circuit_lib, + sram_orgz_type, sram_model, + unique_mirror, CHANY, + verbose); + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fabric/build_routing_modules.h b/openfpga/src/fabric/build_routing_modules.h new file mode 100644 index 000000000..d8eb09784 --- /dev/null +++ b/openfpga/src/fabric/build_routing_modules.h @@ -0,0 +1,41 @@ +#ifndef BUILD_ROUTING_MODULES_H +#define BUILD_ROUTING_MODULES_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "vpr_context.h" +#include "vpr_device_annotation.h" +#include "device_rr_gsb.h" +#include "mux_library.h" +#include "circuit_library.h" +#include "module_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void build_flatten_routing_modules(ModuleManager& module_manager, + const DeviceContext& device_ctx, + const VprDeviceAnnotation& device_annotation, + const DeviceRRGSB& device_rr_gsb, + const CircuitLibrary& circuit_lib, + const e_config_protocol_type& sram_orgz_type, + const CircuitModelId& sram_model, + const bool& verbose); + +void build_unique_routing_modules(ModuleManager& module_manager, + const DeviceContext& device_ctx, + const VprDeviceAnnotation& device_annotation, + const DeviceRRGSB& device_rr_gsb, + const CircuitLibrary& circuit_lib, + const e_config_protocol_type& sram_orgz_type, + const CircuitModelId& sram_model, + const bool& verbose); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/utils/openfpga_rr_graph_utils.cpp b/openfpga/src/utils/openfpga_rr_graph_utils.cpp index 21ec1b640..ba789b4da 100644 --- a/openfpga/src/utils/openfpga_rr_graph_utils.cpp +++ b/openfpga/src/utils/openfpga_rr_graph_utils.cpp @@ -81,4 +81,51 @@ std::vector get_rr_graph_driver_switches(const RRGraph& rr_graph, return driver_switches; } +/************************************************************************ + * Find the driver nodes for a node in the rr_graph + ***********************************************************************/ +std::vector get_rr_graph_driver_nodes(const RRGraph& rr_graph, + const RRNodeId& node) { + std::vector driver_nodes; + for (const RREdgeId& edge : rr_graph.node_in_edges(node)) { + driver_nodes.push_back(rr_graph.edge_src_node(edge)); + } + + return driver_nodes; +} + +/************************************************************************ + * Find the configurable driver nodes for a node in the rr_graph + ***********************************************************************/ +std::vector get_rr_graph_configurable_driver_nodes(const RRGraph& rr_graph, + const RRNodeId& node) { + std::vector driver_nodes; + for (const RREdgeId& edge : rr_graph.node_in_edges(node)) { + /* Bypass non-configurable edges */ + if (false == rr_graph.edge_is_configurable(edge)) { + continue; + } + driver_nodes.push_back(rr_graph.edge_src_node(edge)); + } + + return driver_nodes; +} + +/************************************************************************ + * Find the configurable driver nodes for a node in the rr_graph + ***********************************************************************/ +std::vector get_rr_graph_non_configurable_driver_nodes(const RRGraph& rr_graph, + const RRNodeId& node) { + std::vector driver_nodes; + for (const RREdgeId& edge : rr_graph.node_in_edges(node)) { + /* Bypass configurable edges */ + if (true == rr_graph.edge_is_configurable(edge)) { + continue; + } + driver_nodes.push_back(rr_graph.edge_src_node(edge)); + } + + return driver_nodes; +} + } /* end namespace openfpga */ diff --git a/openfpga/src/utils/openfpga_rr_graph_utils.h b/openfpga/src/utils/openfpga_rr_graph_utils.h index 0c4d174c6..0466892f8 100644 --- a/openfpga/src/utils/openfpga_rr_graph_utils.h +++ b/openfpga/src/utils/openfpga_rr_graph_utils.h @@ -26,6 +26,15 @@ vtr::Point get_track_rr_node_end_coordinate(const RRGraph& rr_graph, std::vector get_rr_graph_driver_switches(const RRGraph& rr_graph, const RRNodeId& node); +std::vector get_rr_graph_driver_nodes(const RRGraph& rr_graph, + const RRNodeId& node); + +std::vector get_rr_graph_configurable_driver_nodes(const RRGraph& rr_graph, + const RRNodeId& node); + +std::vector get_rr_graph_non_configurable_driver_nodes(const RRGraph& rr_graph, + const RRNodeId& node); + } /* end namespace openfpga */ #endif From 36179b6cedc612c4b830a198d3c6ac975dff37cf Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 14 Feb 2020 10:00:24 -0700 Subject: [PATCH 136/645] start moving top-module builder. Now adapt the utils --- .../src/fabric/build_top_module_utils.cpp | 90 +++++++++++++++++++ openfpga/src/fabric/build_top_module_utils.h | 31 +++++++ 2 files changed, 121 insertions(+) create mode 100644 openfpga/src/fabric/build_top_module_utils.cpp create mode 100644 openfpga/src/fabric/build_top_module_utils.h diff --git a/openfpga/src/fabric/build_top_module_utils.cpp b/openfpga/src/fabric/build_top_module_utils.cpp new file mode 100644 index 000000000..141d2dd54 --- /dev/null +++ b/openfpga/src/fabric/build_top_module_utils.cpp @@ -0,0 +1,90 @@ +/******************************************************************** + * This file include most utilized functions for building the module + * graph for FPGA fabric + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_assert.h" + +/* Headers from vpr library */ +#include "vpr_utils.h" + +#include "openfpga_naming.h" + +/* Module builder headers */ +#include "build_top_module_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Generate the name for a grid block, by considering + * 1. if it locates on the border with given device size + * 2. its type + * + * This function is mainly used in the top-level module generation + *******************************************************************/ +std::string generate_grid_block_module_name_in_top_module(const std::string& prefix, + const DeviceGrid& grids, + const vtr::Point& grid_coord) { + /* Determine if the grid locates at the border */ + vtr::Point device_size(grids.width(), grids.height()); + e_side border_side = find_grid_border_side(device_size, grid_coord); + + return generate_grid_block_module_name(prefix, + std::string(grids[grid_coord.x()][grid_coord.y()].type->name), + is_io_type(grids[grid_coord.x()][grid_coord.y()].type), + border_side); +} + +/******************************************************************** + * Find the cb_type of a GSB in the top-level module + * depending on the side of SB + * TOP/BOTTOM side: CHANY + * RIGHT/LEFT side: CHANX + *******************************************************************/ +t_rr_type find_top_module_cb_type_by_sb_side(const e_side& sb_side) { + VTR_ASSERT(NUM_SIDES != sb_side); + + if ((TOP == sb_side) || (BOTTOM == sb_side)) { + return CHANY; + } + + VTR_ASSERT((RIGHT == sb_side) || (LEFT == sb_side)); + return CHANX; +} + +/******************************************************************** + * Find the GSB coordinate for a CB in the top-level module + * depending on the side of a SB + * TODO: use vtr::Point to replace DeviceCoordinator + *******************************************************************/ +vtr::Point find_top_module_gsb_coordinate_by_sb_side(const RRGSB& rr_gsb, + const e_side& sb_side) { + VTR_ASSERT(NUM_SIDES != sb_side); + + vtr::Point gsb_coordinate; + + if ((TOP == sb_side) || (LEFT == sb_side)) { + gsb_coordinate.set_x(rr_gsb.get_x()); + gsb_coordinate.set_y(rr_gsb.get_y()); + return gsb_coordinate; + } + + VTR_ASSERT((RIGHT == sb_side) || (BOTTOM == sb_side)); + + /* RIGHT side: x + 1 */ + if (RIGHT == sb_side) { + gsb_coordinate.set_x(rr_gsb.get_x() + 1); + gsb_coordinate.set_y(rr_gsb.get_y()); + } + + /* BOTTOM side: y - 1 */ + if (BOTTOM == sb_side) { + gsb_coordinate.set_x(rr_gsb.get_x()); + gsb_coordinate.set_y(rr_gsb.get_y() - 1); + } + + return gsb_coordinate; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fabric/build_top_module_utils.h b/openfpga/src/fabric/build_top_module_utils.h new file mode 100644 index 000000000..c0bcbcee5 --- /dev/null +++ b/openfpga/src/fabric/build_top_module_utils.h @@ -0,0 +1,31 @@ +#ifndef BUILD_TOP_MODULE_UTILS_H +#define BUILD_TOP_MODULE_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "vtr_geometry.h" +#include "device_grid.h" +#include "rr_gsb.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +std::string generate_grid_block_module_name_in_top_module(const std::string& prefix, + const DeviceGrid& grids, + const vtr::Point& grid_coord); + +t_rr_type find_top_module_cb_type_by_sb_side(const e_side& sb_side); + +vtr::Point find_top_module_gsb_coordinate_by_sb_side(const RRGSB& rr_gsb, + const e_side& sb_side); + +} /* end namespace openfpga */ + +#endif From 9dc9c2c9f793fe6835157f6b924d4d2d9d70a603 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 14 Feb 2020 10:45:24 -0700 Subject: [PATCH 137/645] add build top module connection functions --- .../fabric/build_top_module_connection.cpp | 676 ++++++++++++++++++ .../src/fabric/build_top_module_connection.h | 35 + openfpga/src/utils/rr_gsb_utils.cpp | 39 + openfpga/src/utils/rr_gsb_utils.h | 23 + 4 files changed, 773 insertions(+) create mode 100644 openfpga/src/fabric/build_top_module_connection.cpp create mode 100644 openfpga/src/fabric/build_top_module_connection.h create mode 100644 openfpga/src/utils/rr_gsb_utils.cpp create mode 100644 openfpga/src/utils/rr_gsb_utils.h diff --git a/openfpga/src/fabric/build_top_module_connection.cpp b/openfpga/src/fabric/build_top_module_connection.cpp new file mode 100644 index 000000000..f1895305e --- /dev/null +++ b/openfpga/src/fabric/build_top_module_connection.cpp @@ -0,0 +1,676 @@ +/******************************************************************** + * This file include most utilized functions for building connections + * inside the module graph for FPGA fabric + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_assert.h" + +/* Headers from openfpgautil library */ +#include "openfpga_side_manager.h" + +#include "openfpga_reserved_words.h" +#include "openfpga_naming.h" +#include "pb_type_utils.h" +#include "rr_gsb_utils.h" + +#include "build_top_module_utils.h" +#include "build_top_module_connection.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Add module nets to connect a GSB to adjacent grid ports/pins + * as well as connection blocks + * This function will create nets for the following types of connections + * between grid output pins of Switch block and adjacent grids + * In this case, the net source is the grid pin, while the net sink + * is the switch block pin + * + * +------------+ +------------+ + * | | | | + * | Grid | | Grid | + * | [x][y+1] | | [x+1][y+1] | + * | |----+ +----| | + * +------------+ | | +------------+ + * | v v | + * | +------------+ | + * +------>| |<-----+ + * | Switch | + * | Block | + * +------>| [x][y] |<-----+ + * | +------------+ | + * | ^ ^ | + * | | | | + * +------------+ | | +------------+ + * | |----+ +-----| | + * | Grid | | Grid | + * | [x][y] | | [x+1][y] | + * | | | | + * +------------+ +------------+ + + * + *******************************************************************/ +static +void add_top_module_nets_connect_grids_and_sb(ModuleManager& module_manager, + const ModuleId& top_module, + const DeviceGrid& grids, + const vtr::Matrix& grid_instance_ids, + const RRGraph& rr_graph, + const DeviceRRGSB& device_rr_gsb, + const RRGSB& rr_gsb, + const vtr::Matrix& sb_instance_ids, + const bool& compact_routing_hierarchy) { + + /* We could have two different coordinators, one is the instance, the other is the module */ + vtr::Point instance_sb_coordinate(rr_gsb.get_sb_x(), rr_gsb.get_sb_y()); + vtr::Point module_gsb_coordinate(rr_gsb.get_x(), rr_gsb.get_y()); + + /* If we use compact routing hierarchy, we should find the unique module of CB, which is added to the top module */ + if (true == compact_routing_hierarchy) { + vtr::Point gsb_coord(rr_gsb.get_x(), rr_gsb.get_y()); + const RRGSB& unique_mirror = device_rr_gsb.get_sb_unique_module(gsb_coord); + module_gsb_coordinate.set_x(unique_mirror.get_x()); + module_gsb_coordinate.set_y(unique_mirror.get_y()); + } + + /* This is the source cb that is added to the top module */ + const RRGSB& module_sb = device_rr_gsb.get_gsb(module_gsb_coordinate); + vtr::Point module_sb_coordinate(module_sb.get_sb_x(), module_sb.get_sb_y()); + + /* Collect sink-related information */ + std::string sink_sb_module_name = generate_switch_block_module_name(module_sb_coordinate); + ModuleId sink_sb_module = module_manager.find_module(sink_sb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(sink_sb_module)); + size_t sink_sb_instance = sb_instance_ids[instance_sb_coordinate.x()][instance_sb_coordinate.y()]; + + /* Connect grid output pins (OPIN) to switch block grid pins */ + for (size_t side = 0; side < module_sb.get_num_sides(); ++side) { + SideManager side_manager(side); + for (size_t inode = 0; inode < module_sb.get_num_opin_nodes(side_manager.get_side()); ++inode) { + /* Collect source-related information */ + /* Generate the grid module name by considering if it locates on the border */ + vtr::Point grid_coordinate(rr_graph.node_xlow(rr_gsb.get_opin_node(side_manager.get_side(), inode)), + rr_graph.node_ylow(rr_gsb.get_opin_node(side_manager.get_side(), inode))); + std::string src_grid_module_name = generate_grid_block_module_name_in_top_module(std::string(GRID_MODULE_NAME_PREFIX), grids, grid_coordinate); + ModuleId src_grid_module = module_manager.find_module(src_grid_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(src_grid_module)); + size_t src_grid_instance = grid_instance_ids[grid_coordinate.x()][grid_coordinate.y()]; + size_t src_grid_pin_index = rr_graph.node_pin_num(rr_gsb.get_opin_node(side_manager.get_side(), inode)); + size_t src_grid_pin_width = grids[grid_coordinate.x()][grid_coordinate.y()].type->pin_width_offset[src_grid_pin_index]; + size_t src_grid_pin_height = grids[grid_coordinate.x()][grid_coordinate.y()].type->pin_height_offset[src_grid_pin_index]; + std::string src_grid_port_name = generate_grid_port_name(grid_coordinate, src_grid_pin_width, src_grid_pin_height, + rr_graph.node_side(rr_gsb.get_opin_node(side_manager.get_side(), inode)), + src_grid_pin_index, false); + ModulePortId src_grid_port_id = module_manager.find_module_port(src_grid_module, src_grid_port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(src_grid_module, src_grid_port_id)); + BasicPort src_grid_port = module_manager.module_port(src_grid_module, src_grid_port_id); + + /* Collect sink-related information */ + vtr::Point sink_sb_port_coord(rr_graph.node_xlow(module_sb.get_opin_node(side_manager.get_side(), inode)), + rr_graph.node_ylow(module_sb.get_opin_node(side_manager.get_side(), inode))); + std::string sink_sb_port_name = generate_sb_module_grid_port_name(side_manager.get_side(), + rr_graph.node_side(module_sb.get_opin_node(side_manager.get_side(), inode)), + src_grid_pin_index); + ModulePortId sink_sb_port_id = module_manager.find_module_port(sink_sb_module, sink_sb_port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(sink_sb_module, sink_sb_port_id)); + BasicPort sink_sb_port = module_manager.module_port(sink_sb_module, sink_sb_port_id); + + /* Source and sink port should match in size */ + VTR_ASSERT(src_grid_port.get_width() == sink_sb_port.get_width()); + + /* Create a net for each pin */ + for (size_t pin_id = 0; pin_id < src_grid_port.pins().size(); ++pin_id) { + ModuleNetId net = module_manager.create_module_net(top_module); + /* Configure the net source */ + module_manager.add_module_net_source(top_module, net, src_grid_module, src_grid_instance, src_grid_port_id, src_grid_port.pins()[pin_id]); + /* Configure the net sink */ + module_manager.add_module_net_sink(top_module, net, sink_sb_module, sink_sb_instance, sink_sb_port_id, sink_sb_port.pins()[pin_id]); + } + } + } +} + +/******************************************************************** + * Add module nets to connect a GSB to adjacent grid ports/pins + * as well as connection blocks + * This function will create nets for the following types of connections + * between grid output pins of Switch block and adjacent grids + * In this case, the net source is the grid pin, while the net sink + * is the switch block pin + * + * In particular, this function considers the duplicated output pins of grids + * when creating the connecting nets. + * The follow figure shows the different pin postfix to be considered when + * connecting the grid pins to SB inputs + * + * +------------+ +------------+ + * | | | | + * | Grid | | Grid | + * | [x][y+1] |lower lower| [x+1][y+1] | + * | |----+ +----| | + * +------------+ | | +------------+ + * |lower v v |upper + * | +------------+ | + * +------>| |<-----+ + * | Switch | + * | Block | + * +------>| [x][y] |<-----+ + * | +------------+ | + * | ^ ^ | + * |lower | | |upper + * +------------+ | | +------------+ + * | |----+ +-----| | + * | Grid |upper upper | Grid | + * | [x][y] | | [x+1][y] | + * | | | | + * +------------+ +------------+ + * + *******************************************************************/ +static +void add_top_module_nets_connect_grids_and_sb_with_duplicated_pins(ModuleManager& module_manager, + const ModuleId& top_module, + const DeviceGrid& grids, + const vtr::Matrix& grid_instance_ids, + const RRGraph& rr_graph, + const DeviceRRGSB& device_rr_gsb, + const RRGSB& rr_gsb, + const vtr::Matrix& sb_instance_ids, + const bool& compact_routing_hierarchy) { + + /* We could have two different coordinators, one is the instance, the other is the module */ + vtr::Point instance_sb_coordinate(rr_gsb.get_sb_x(), rr_gsb.get_sb_y()); + vtr::Point module_gsb_coordinate(rr_gsb.get_x(), rr_gsb.get_y()); + + /* If we use compact routing hierarchy, we should find the unique module of CB, which is added to the top module */ + if (true == compact_routing_hierarchy) { + vtr::Point gsb_coord(rr_gsb.get_x(), rr_gsb.get_y()); + const RRGSB& unique_mirror = device_rr_gsb.get_sb_unique_module(gsb_coord); + module_gsb_coordinate.set_x(unique_mirror.get_x()); + module_gsb_coordinate.set_y(unique_mirror.get_y()); + } + + /* This is the source cb that is added to the top module */ + const RRGSB& module_sb = device_rr_gsb.get_gsb(module_gsb_coordinate); + vtr::Point module_sb_coordinate(module_sb.get_sb_x(), module_sb.get_sb_y()); + + /* Collect sink-related information */ + std::string sink_sb_module_name = generate_switch_block_module_name(module_sb_coordinate); + ModuleId sink_sb_module = module_manager.find_module(sink_sb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(sink_sb_module)); + size_t sink_sb_instance = sb_instance_ids[instance_sb_coordinate.x()][instance_sb_coordinate.y()]; + + /* Create a truth table for the postfix to be used regarding to the different side of switch blocks */ + std::map sb_side2postfix_map; + /* Boolean variable "true" indicates the upper postfix in naming functions + * Boolean variable "false" indicates the lower postfix in naming functions + */ + sb_side2postfix_map[TOP] = false; + sb_side2postfix_map[RIGHT] = true; + sb_side2postfix_map[BOTTOM] = true; + sb_side2postfix_map[LEFT] = false; + + /* Connect grid output pins (OPIN) to switch block grid pins */ + for (size_t side = 0; side < module_sb.get_num_sides(); ++side) { + SideManager side_manager(side); + for (size_t inode = 0; inode < module_sb.get_num_opin_nodes(side_manager.get_side()); ++inode) { + /* Collect source-related information */ + /* Generate the grid module name by considering if it locates on the border */ + vtr::Point grid_coordinate(rr_graph.node_xlow(rr_gsb.get_opin_node(side_manager.get_side(), inode)), + rr_graph.node_ylow(rr_gsb.get_opin_node(side_manager.get_side(), inode))); + std::string src_grid_module_name = generate_grid_block_module_name_in_top_module(std::string(GRID_MODULE_NAME_PREFIX), grids, grid_coordinate); + ModuleId src_grid_module = module_manager.find_module(src_grid_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(src_grid_module)); + size_t src_grid_instance = grid_instance_ids[grid_coordinate.x()][grid_coordinate.y()]; + size_t src_grid_pin_index = rr_graph.node_pin_num(rr_gsb.get_opin_node(side_manager.get_side(), inode)); + size_t src_grid_pin_width = grids[grid_coordinate.x()][grid_coordinate.y()].type->pin_width_offset[src_grid_pin_index]; + size_t src_grid_pin_height = grids[grid_coordinate.x()][grid_coordinate.y()].type->pin_height_offset[src_grid_pin_index]; + + /* Pins for direct connection are NOT duplicated. + * Follow the traditional recipe when adding nets! + * Xifan: I assume that each direct connection pin must have Fc=0. + * For other duplicated pins, we follow the new naming + */ + std::string src_grid_port_name; + if (0. == grids[grid_coordinate.x()][grid_coordinate.y()].type->fc_specs[src_grid_pin_index].fc_value) { + src_grid_port_name = generate_grid_port_name(grid_coordinate, src_grid_pin_width, src_grid_pin_height, + rr_graph.node_side(rr_gsb.get_opin_node(side_manager.get_side(), inode)), + src_grid_pin_index, false); + } else { + src_grid_port_name = generate_grid_duplicated_port_name(src_grid_pin_width, src_grid_pin_height, + rr_graph.node_side(rr_gsb.get_opin_node(side_manager.get_side(), inode)), + src_grid_pin_index, sb_side2postfix_map[side_manager.get_side()]); + } + ModulePortId src_grid_port_id = module_manager.find_module_port(src_grid_module, src_grid_port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(src_grid_module, src_grid_port_id)); + BasicPort src_grid_port = module_manager.module_port(src_grid_module, src_grid_port_id); + + /* Collect sink-related information */ + vtr::Point sink_sb_port_coord(rr_graph.node_xlow(module_sb.get_opin_node(side_manager.get_side(), inode)), + rr_graph.node_ylow(module_sb.get_opin_node(side_manager.get_side(), inode))); + std::string sink_sb_port_name = generate_sb_module_grid_port_name(side_manager.get_side(), + rr_graph.node_side(module_sb.get_opin_node(side_manager.get_side(), inode)), + src_grid_pin_index); + ModulePortId sink_sb_port_id = module_manager.find_module_port(sink_sb_module, sink_sb_port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(sink_sb_module, sink_sb_port_id)); + BasicPort sink_sb_port = module_manager.module_port(sink_sb_module, sink_sb_port_id); + + /* Source and sink port should match in size */ + VTR_ASSERT(src_grid_port.get_width() == sink_sb_port.get_width()); + + /* Create a net for each pin */ + for (size_t pin_id = 0; pin_id < src_grid_port.pins().size(); ++pin_id) { + ModuleNetId net = module_manager.create_module_net(top_module); + /* Configure the net source */ + module_manager.add_module_net_source(top_module, net, src_grid_module, src_grid_instance, src_grid_port_id, src_grid_port.pins()[pin_id]); + /* Configure the net sink */ + module_manager.add_module_net_sink(top_module, net, sink_sb_module, sink_sb_instance, sink_sb_port_id, sink_sb_port.pins()[pin_id]); + } + } + } +} + +/******************************************************************** + * This function will create nets for the connections + * between grid input pins and connection blocks + * In this case, the net source is the connection block pin, + * while the net sink is the grid input + * + * +------------+ +------------------+ +------------+ + * | | | | | | + * | Grid |<-----| Connection Block |----->| Grid | + * | [x][y+1] | | Y-direction | | [x+1][y+1] | + * | | | [x][y+1] | | | + * +------------+ +------------------+ +------------+ + * ^ + * | + * +------------+ +------------------+ + * | Connection | | | + * | Block | | Switch Block | + * | X-direction| | [x][y] | + * | [x][y] | | | + * +------------+ +------------------+ + * | + * v + * +------------+ + * | | + * | Grid | + * | [x][y] | + * | | + * +------------+ + * + * + * Relationship between source connection block and its unique module + * Take an example of a CBY + * + * grid_pin name should follow unique module of Grid[x][y+1] + * cb_pin name should follow unique module of CBY[x][y+1] + * + * However, instace id should follow the origin Grid and Connection block + * + * + * +------------+ +------------------+ + * | | | | + * | Grid |<------------| Connection Block | + * | [x][y+1] | | Y-direction | + * | | | [x][y+1] | + * +------------+ +------------------+ + * ^ + * || unique mirror + * +------------+ +------------------+ + * | | | | + * | Grid |<------------| Connection Block | + * | [i][j+1] | | Y-direction | + * | | | [i][j+1] | + * +------------+ +------------------+ + * + *******************************************************************/ +static +void add_top_module_nets_connect_grids_and_cb(ModuleManager& module_manager, + const ModuleId& top_module, + const DeviceGrid& grids, + const vtr::Matrix& grid_instance_ids, + const RRGraph& rr_graph, + const DeviceRRGSB& device_rr_gsb, + const RRGSB& rr_gsb, + const t_rr_type& cb_type, + const vtr::Matrix& cb_instance_ids, + const bool& compact_routing_hierarchy) { + /* We could have two different coordinators, one is the instance, the other is the module */ + vtr::Point instance_cb_coordinate(rr_gsb.get_cb_x(cb_type), rr_gsb.get_cb_y(cb_type)); + vtr::Point module_gsb_coordinate(rr_gsb.get_x(), rr_gsb.get_y()); + + /* Skip those Connection blocks that do not exist */ + if (false == rr_gsb.is_cb_exist(cb_type)) { + return; + } + + /* Skip if the cb does not contain any configuration bits! */ + if (true == connection_block_contain_only_routing_tracks(rr_gsb, cb_type)) { + return; + } + + /* If we use compact routing hierarchy, we should find the unique module of CB, which is added to the top module */ + if (true == compact_routing_hierarchy) { + vtr::Point gsb_coord(rr_gsb.get_x(), rr_gsb.get_y()); + const RRGSB& unique_mirror = device_rr_gsb.get_cb_unique_module(cb_type, gsb_coord); + module_gsb_coordinate.set_x(unique_mirror.get_x()); + module_gsb_coordinate.set_y(unique_mirror.get_y()); + } + + /* This is the source cb that is added to the top module */ + const RRGSB& module_cb = device_rr_gsb.get_gsb(module_gsb_coordinate); + vtr::Point module_cb_coordinate(module_cb.get_cb_x(cb_type), module_cb.get_cb_y(cb_type)); + + /* Collect source-related information */ + std::string src_cb_module_name = generate_connection_block_module_name(cb_type, module_cb_coordinate); + ModuleId src_cb_module = module_manager.find_module(src_cb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(src_cb_module)); + /* Instance id should follow the instance cb coordinate */ + size_t src_cb_instance = cb_instance_ids[instance_cb_coordinate.x()][instance_cb_coordinate.y()]; + + /* Iterate over the output pins of the Connection Block */ + std::vector cb_ipin_sides = module_cb.get_cb_ipin_sides(cb_type); + for (size_t iside = 0; iside < cb_ipin_sides.size(); ++iside) { + enum e_side cb_ipin_side = cb_ipin_sides[iside]; + for (size_t inode = 0; inode < module_cb.get_num_ipin_nodes(cb_ipin_side); ++inode) { + /* Collect source-related information */ + RRNodeId module_ipin_node = module_cb.get_ipin_node(cb_ipin_side, inode); + vtr::Point cb_src_port_coord(rr_graph.node_xlow(module_ipin_node), + rr_graph.node_ylow(module_ipin_node)); + std::string src_cb_port_name = generate_cb_module_grid_port_name(cb_ipin_side, + rr_graph.node_pin_num(module_ipin_node)); + ModulePortId src_cb_port_id = module_manager.find_module_port(src_cb_module, src_cb_port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(src_cb_module, src_cb_port_id)); + BasicPort src_cb_port = module_manager.module_port(src_cb_module, src_cb_port_id); + + /* Collect sink-related information */ + /* Note that we use the instance cb pin here!!! + * because it has the correct coordinator for the grid!!! + */ + RRNodeId instance_ipin_node = rr_gsb.get_ipin_node(cb_ipin_side, inode); + vtr::Point grid_coordinate(rr_graph.node_xlow(instance_ipin_node), + rr_graph.node_ylow(instance_ipin_node)); + std::string sink_grid_module_name = generate_grid_block_module_name_in_top_module(std::string(GRID_MODULE_NAME_PREFIX), grids, grid_coordinate); + ModuleId sink_grid_module = module_manager.find_module(sink_grid_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(sink_grid_module)); + size_t sink_grid_instance = grid_instance_ids[grid_coordinate.x()][grid_coordinate.y()]; + size_t sink_grid_pin_index = rr_graph.node_pin_num(instance_ipin_node); + size_t sink_grid_pin_width = grids[grid_coordinate.x()][grid_coordinate.y()].type->pin_width_offset[sink_grid_pin_index]; + size_t sink_grid_pin_height = grids[grid_coordinate.x()][grid_coordinate.y()].type->pin_height_offset[sink_grid_pin_index]; + std::string sink_grid_port_name = generate_grid_port_name(grid_coordinate, sink_grid_pin_width, sink_grid_pin_height, + rr_graph.node_side(rr_gsb.get_ipin_node(cb_ipin_side, inode)), + sink_grid_pin_index, false); + ModulePortId sink_grid_port_id = module_manager.find_module_port(sink_grid_module, sink_grid_port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(sink_grid_module, sink_grid_port_id)); + BasicPort sink_grid_port = module_manager.module_port(sink_grid_module, sink_grid_port_id); + + /* Source and sink port should match in size */ + VTR_ASSERT(src_cb_port.get_width() == sink_grid_port.get_width()); + + /* Create a net for each pin */ + for (size_t pin_id = 0; pin_id < src_cb_port.pins().size(); ++pin_id) { + ModuleNetId net = module_manager.create_module_net(top_module); + /* Configure the net source */ + module_manager.add_module_net_source(top_module, net, src_cb_module, src_cb_instance, src_cb_port_id, src_cb_port.pins()[pin_id]); + /* Configure the net sink */ + module_manager.add_module_net_sink(top_module, net, sink_grid_module, sink_grid_instance, sink_grid_port_id, sink_grid_port.pins()[pin_id]); + } + } + } +} + +/******************************************************************** + * This function will create nets for the connections + * between connection block and switch block pins + * Two cases should be considered: + * a. The switch block pin denotes an input of a routing track + * The net source is an output of a routing track of connection block + * while the net sink is an input of a routing track of switch block + * b. The switch block pin denotes an output of a routing track + * The net source is an output of routing track of switch block + * while the net sink is an input of a routing track of connection block + * + * +------------+ +------------------+ +------------+ + * | | | | | | + * | Grid | | Connection Block | | Grid | + * | [x][y+1] | | Y-direction | | [x+1][y+1] | + * | | | [x][y+1] | | | + * +------------+ +------------------+ +------------+ + * | ^ + * v | + * +------------+ +------------------+ +------------+ + * | Connection |----->| |----->| Connection | + * | Block | | Switch Block | | Block | + * | X-direction|<-----| [x][y] |<-----| X-direction| + * | [x][y] | | | | [x+1][y] | + * +------------+ +------------------+ +------------+ + * | ^ + * v | + * +------------+ +------------------+ +------------+ + * | | | | | | + * | Grid | | Connection Block | | Grid | + * | [x][y] | | Y-direction | | [x][y+1] | + * | | | [x][y] | | | + * +------------+ +------------------+ +------------+ + * + * Here, to achieve the purpose, we can simply iterate over the + * four sides of switch block and make connections to adjancent + * connection blocks + * + *******************************************************************/ +static +void add_top_module_nets_connect_sb_and_cb(ModuleManager& module_manager, + const ModuleId& top_module, + const RRGraph& rr_graph, + const DeviceRRGSB& device_rr_gsb, + const RRGSB& rr_gsb, + const vtr::Matrix& sb_instance_ids, + const std::map>& cb_instance_ids, + const bool& compact_routing_hierarchy) { + /* We could have two different coordinators, one is the instance, the other is the module */ + vtr::Point instance_sb_coordinate(rr_gsb.get_sb_x(), rr_gsb.get_sb_y()); + vtr::Point module_gsb_sb_coordinate(rr_gsb.get_x(), rr_gsb.get_y()); + + /* Skip those Switch blocks that do not exist */ + if (false == rr_gsb.is_sb_exist()) { + return; + } + + /* If we use compact routing hierarchy, we should find the unique module of CB, which is added to the top module */ + if (true == compact_routing_hierarchy) { + vtr::Point gsb_coord(rr_gsb.get_x(), rr_gsb.get_y()); + const RRGSB& unique_mirror = device_rr_gsb.get_sb_unique_module(gsb_coord); + module_gsb_sb_coordinate.set_x(unique_mirror.get_x()); + module_gsb_sb_coordinate.set_y(unique_mirror.get_y()); + } + + /* This is the source cb that is added to the top module */ + const RRGSB& module_sb = device_rr_gsb.get_gsb(module_gsb_sb_coordinate); + vtr::Point module_sb_coordinate(module_sb.get_sb_x(), module_sb.get_sb_y()); + std::string sb_module_name = generate_switch_block_module_name(module_sb_coordinate); + ModuleId sb_module_id = module_manager.find_module(sb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(sb_module_id)); + size_t sb_instance = sb_instance_ids[instance_sb_coordinate.x()][instance_sb_coordinate.y()]; + + /* Connect grid output pins (OPIN) to switch block grid pins */ + for (size_t side = 0; side < module_sb.get_num_sides(); ++side) { + SideManager side_manager(side); + /* Iterate over the routing tracks on this side */ + /* Early skip: if there is no routing tracks at this side */ + if (0 == module_sb.get_chan_width(side_manager.get_side())) { + continue; + } + /* Find the Connection Block module */ + /* We find the original connection block and then spot its unique mirror! + * Do NOT use module_sb here!!! + */ + t_rr_type cb_type = find_top_module_cb_type_by_sb_side(side_manager.get_side()); + vtr::Point instance_gsb_cb_coordinate = find_top_module_gsb_coordinate_by_sb_side(rr_gsb, side_manager.get_side()); + vtr::Point module_gsb_cb_coordinate = find_top_module_gsb_coordinate_by_sb_side(rr_gsb, side_manager.get_side()); + + /* Skip those Connection blocks that do not exist: + * 1. The CB does not exist in the device level! We should skip! + * 2. The CB does exist but we need to make sure if the GSB includes such CBs + * For TOP and LEFT side, check the existence using RRGSB method is_cb_exist() + * FOr RIGHT and BOTTOM side, find the adjacent RRGSB and then use is_cb_exist() + */ + if ( TOP == side_manager.get_side() || LEFT == side_manager.get_side() ) { + if ( false == rr_gsb.is_cb_exist(cb_type)) { + continue; + } + } + + if ( RIGHT == side_manager.get_side() || BOTTOM == side_manager.get_side() ) { + const RRGSB& adjacent_gsb = device_rr_gsb.get_gsb(module_gsb_cb_coordinate); + if ( false == adjacent_gsb.is_cb_exist(cb_type)) { + continue; + } + } + + /* If we use compact routing hierarchy, we should find the unique module of CB, which is added to the top module */ + if (true == compact_routing_hierarchy) { + const RRGSB& unique_mirror = device_rr_gsb.get_cb_unique_module(cb_type, module_gsb_cb_coordinate); + module_gsb_cb_coordinate.set_x(unique_mirror.get_x()); + module_gsb_cb_coordinate.set_y(unique_mirror.get_y()); + } + + const RRGSB& module_cb = device_rr_gsb.get_gsb(module_gsb_cb_coordinate); + vtr::Point module_cb_coordinate(module_cb.get_cb_x(cb_type), module_cb.get_cb_y(cb_type)); + std::string cb_module_name = generate_connection_block_module_name(cb_type, module_cb_coordinate); + ModuleId cb_module_id = module_manager.find_module(cb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(cb_module_id)); + const RRGSB& instance_cb = device_rr_gsb.get_gsb(instance_gsb_cb_coordinate); + vtr::Point instance_cb_coordinate(instance_cb.get_cb_x(cb_type), instance_cb.get_cb_y(cb_type)); + size_t cb_instance = cb_instance_ids.at(cb_type)[instance_cb_coordinate.x()][instance_cb_coordinate.y()]; + + for (size_t itrack = 0; itrack < module_sb.get_chan_width(side_manager.get_side()); ++itrack) { + std::string sb_port_name = generate_sb_module_track_port_name(rr_graph.node_type(module_sb.get_chan_node(side_manager.get_side(), itrack)), + side_manager.get_side(), itrack, + module_sb.get_chan_node_direction(side_manager.get_side(), itrack)); + /* Prepare SB-related port information */ + ModulePortId sb_port_id = module_manager.find_module_port(sb_module_id, sb_port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(sb_module_id, sb_port_id)); + BasicPort sb_port = module_manager.module_port(sb_module_id, sb_port_id); + + /* Prepare CB-related port information */ + PORTS cb_port_direction = OUT_PORT; + /* The cb port direction should be opposite to the sb port !!! */ + if (OUT_PORT == module_sb.get_chan_node_direction(side_manager.get_side(), itrack)) { + cb_port_direction = IN_PORT; + } else { + VTR_ASSERT(IN_PORT == module_sb.get_chan_node_direction(side_manager.get_side(), itrack)); + } + std::string cb_port_name = generate_cb_module_track_port_name(cb_type, + itrack, + cb_port_direction); + ModulePortId cb_port_id = module_manager.find_module_port(cb_module_id, cb_port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(cb_module_id, cb_port_id)); + BasicPort cb_port = module_manager.module_port(cb_module_id, cb_port_id); + + /* Source and sink port should match in size */ + VTR_ASSERT(cb_port.get_width() == sb_port.get_width()); + + /* Create a net for each pin */ + for (size_t pin_id = 0; pin_id < cb_port.pins().size(); ++pin_id) { + ModuleNetId net = module_manager.create_module_net(top_module); + /* Configure the net source and sink: + * If sb port is an output (source), cb port is an input (sink) + * If sb port is an input (sink), cb port is an output (source) + */ + if (OUT_PORT == module_sb.get_chan_node_direction(side_manager.get_side(), itrack)) { + module_manager.add_module_net_sink(top_module, net, cb_module_id, cb_instance, cb_port_id, cb_port.pins()[pin_id]); + module_manager.add_module_net_source(top_module, net, sb_module_id, sb_instance, sb_port_id, sb_port.pins()[pin_id]); + } else { + VTR_ASSERT(IN_PORT == module_sb.get_chan_node_direction(side_manager.get_side(), itrack)); + module_manager.add_module_net_source(top_module, net, cb_module_id, cb_instance, cb_port_id, cb_port.pins()[pin_id]); + module_manager.add_module_net_sink(top_module, net, sb_module_id, sb_instance, sb_port_id, sb_port.pins()[pin_id]); + } + } + } + } +} + +/******************************************************************** + * Add module nets to connect the grid ports/pins to Connection Blocks + * and Switch Blocks + * To make it easy, this function will iterate over all the General + * Switch Blocks (GSBs), through which we can obtain the coordinates + * of all the grids, connection blocks and switch blocks that are + * supposed to be connected tightly. + * + * As such, we have completed all the connection for each grid. + * There is no need to iterate over the grids + * + * +-------------------------+ +---------------------------------+ + * | | | Y-direction CB | + * | Grid[x][y+1] | | [x][y + 1] | + * | | +---------------------------------+ + * +-------------------------+ + * TOP SIDE + * +-------------+ +---------------------------------+ + * | | | OPIN_NODE CHAN_NODES OPIN_NODES | + * | | | | + * | | | OPIN_NODES OPIN_NODES | + * | X-direction | | | + * | CB | LEFT SIDE | Switch Block | RIGHT SIDE + * | [x][y] | | [x][y] | + * | | | | + * | | | CHAN_NODES CHAN_NODES | + * | | | | + * | | | OPIN_NODES OPIN_NODES | + * | | | | + * | | | OPIN_NODE CHAN_NODES OPIN_NODES | + * +-------------+ +---------------------------------+ + * BOTTOM SIDE + *******************************************************************/ +void add_top_module_nets_connect_grids_and_gsbs(ModuleManager& module_manager, + const ModuleId& top_module, + const DeviceGrid& grids, + const vtr::Matrix& grid_instance_ids, + const RRGraph& rr_graph, + const DeviceRRGSB& device_rr_gsb, + const vtr::Matrix& sb_instance_ids, + const std::map>& cb_instance_ids, + const bool& compact_routing_hierarchy, + const bool& duplicate_grid_pin) { + + vtr::Point gsb_range = device_rr_gsb.get_gsb_range(); + + for (size_t ix = 0; ix < gsb_range.x(); ++ix) { + for (size_t iy = 0; iy < gsb_range.y(); ++iy) { + vtr::Point gsb_coordinate(ix, iy); + const RRGSB& rr_gsb = device_rr_gsb.get_gsb(ix, iy); + /* Connect the grid pins of the GSB to adjacent grids */ + if (false == duplicate_grid_pin) { + add_top_module_nets_connect_grids_and_sb(module_manager, top_module, + grids, grid_instance_ids, + rr_graph, device_rr_gsb, rr_gsb, sb_instance_ids, + compact_routing_hierarchy); + } else { + VTR_ASSERT_SAFE(true == duplicate_grid_pin); + add_top_module_nets_connect_grids_and_sb_with_duplicated_pins(module_manager, top_module, + grids, grid_instance_ids, + rr_graph, device_rr_gsb, rr_gsb, sb_instance_ids, + compact_routing_hierarchy); + } + + add_top_module_nets_connect_grids_and_cb(module_manager, top_module, + grids, grid_instance_ids, + rr_graph, device_rr_gsb, rr_gsb, CHANX, cb_instance_ids.at(CHANX), + compact_routing_hierarchy); + + add_top_module_nets_connect_grids_and_cb(module_manager, top_module, + grids, grid_instance_ids, + rr_graph, device_rr_gsb, rr_gsb, CHANY, cb_instance_ids.at(CHANY), + compact_routing_hierarchy); + + add_top_module_nets_connect_sb_and_cb(module_manager, top_module, + rr_graph, device_rr_gsb, rr_gsb, sb_instance_ids, cb_instance_ids, + compact_routing_hierarchy); + + } + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fabric/build_top_module_connection.h b/openfpga/src/fabric/build_top_module_connection.h new file mode 100644 index 000000000..7f7d06905 --- /dev/null +++ b/openfpga/src/fabric/build_top_module_connection.h @@ -0,0 +1,35 @@ +#ifndef BUILD_TOP_MODULE_CONNECTION_H +#define BUILD_TOP_MODULE_CONNECTION_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include "vtr_geometry.h" +#include "vtr_ndmatrix.h" +#include "device_grid.h" +#include "rr_graph_obj.h" +#include "device_rr_gsb.h" +#include "module_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void add_top_module_nets_connect_grids_and_gsbs(ModuleManager& module_manager, + const ModuleId& top_module, + const DeviceGrid& grids, + const vtr::Matrix& grid_instance_ids, + const RRGraph& rr_graph, + const DeviceRRGSB& device_rr_gsb, + const vtr::Matrix& sb_instance_ids, + const std::map>& cb_instance_ids, + const bool& compact_routing_hierarchy, + const bool& duplicate_grid_pin); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/utils/rr_gsb_utils.cpp b/openfpga/src/utils/rr_gsb_utils.cpp new file mode 100644 index 000000000..3f332e6db --- /dev/null +++ b/openfpga/src/utils/rr_gsb_utils.cpp @@ -0,0 +1,39 @@ +/******************************************************************** + * This file includes most utilized functions for data structure + * DeviceRRGSB + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_assert.h" + +/* Headers from openfpgautil library */ +#include "openfpga_side_manager.h" + +#include "rr_gsb_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Find if a X-direction or Y-direction Connection Block contains + * routing tracks only (zero configuration bits and routing multiplexers) + *******************************************************************/ +bool connection_block_contain_only_routing_tracks(const RRGSB& rr_gsb, + const t_rr_type& cb_type) { + bool routing_track_only = true; + + /* Find routing multiplexers on the sides of a Connection block where IPIN nodes locate */ + std::vector cb_sides = rr_gsb.get_cb_ipin_sides(cb_type); + + for (size_t side = 0; side < cb_sides.size(); ++side) { + enum e_side cb_ipin_side = cb_sides[side]; + SideManager side_manager(cb_ipin_side); + if (0 < rr_gsb.get_num_ipin_nodes(cb_ipin_side)) { + routing_track_only = false; + break; + } + } + + return routing_track_only; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/utils/rr_gsb_utils.h b/openfpga/src/utils/rr_gsb_utils.h new file mode 100644 index 000000000..57e3e23fc --- /dev/null +++ b/openfpga/src/utils/rr_gsb_utils.h @@ -0,0 +1,23 @@ +#ifndef RR_GSB_UTILS_H +#define RR_GSB_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "rr_gsb.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +bool connection_block_contain_only_routing_tracks(const RRGSB& rr_gsb, + const t_rr_type& cb_type); + +} /* end namespace openfpga */ + +#endif From c855ab24f5bc2206094384fc4a23d3f4c9179db7 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 14 Feb 2020 11:07:04 -0700 Subject: [PATCH 138/645] put build top module memory connections online --- .../src/fabric/build_top_module_memory.cpp | 447 ++++++++++++++++++ openfpga/src/fabric/build_top_module_memory.h | 43 ++ openfpga/src/utils/module_manager_utils.cpp | 3 + 3 files changed, 493 insertions(+) create mode 100644 openfpga/src/fabric/build_top_module_memory.cpp create mode 100644 openfpga/src/fabric/build_top_module_memory.h diff --git a/openfpga/src/fabric/build_top_module_memory.cpp b/openfpga/src/fabric/build_top_module_memory.cpp new file mode 100644 index 000000000..37f67f0be --- /dev/null +++ b/openfpga/src/fabric/build_top_module_memory.cpp @@ -0,0 +1,447 @@ +/******************************************************************** + * This file includes functions that are used to organize memories + * in the top module of FPGA fabric + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" + +/* Headers from vpr library */ +#include "vpr_utils.h" + +#include "rr_gsb_utils.h" +#include "openfpga_reserved_words.h" +#include "openfpga_naming.h" + +#include "module_manager_utils.h" +#include "build_top_module_memory.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * This function adds the CBX/CBY of a tile + * to the memory modules and memory instances + * This function is designed for organizing memory modules in top-level + * module + *******************************************************************/ +static +void organize_top_module_tile_cb_modules(ModuleManager& module_manager, + const ModuleId& top_module, + const CircuitLibrary& circuit_lib, + const e_config_protocol_type& sram_orgz_type, + const CircuitModelId& sram_model, + const vtr::Matrix& cb_instance_ids, + const DeviceRRGSB& device_rr_gsb, + const RRGSB& rr_gsb, + const t_rr_type& cb_type, + const bool& compact_routing_hierarchy) { + /* If the CB does not exist, we can skip addition */ + if ( false == rr_gsb.is_cb_exist(cb_type)) { + return; + } + + /* Skip if the cb does not contain any configuration bits! */ + if (true == connection_block_contain_only_routing_tracks(rr_gsb, cb_type)) { + return; + } + + vtr::Point cb_coord(rr_gsb.get_cb_x(cb_type), rr_gsb.get_cb_y(cb_type)); + /* If we use compact routing hierarchy, we should instanciate the unique module of SB */ + if (true == compact_routing_hierarchy) { + /* Note: use GSB coordinate when inquire for unique modules!!! */ + const RRGSB& unique_mirror = device_rr_gsb.get_cb_unique_module(cb_type, vtr::Point(rr_gsb.get_x(), rr_gsb.get_y())); + cb_coord.set_x(unique_mirror.get_cb_x(cb_type)); + cb_coord.set_y(unique_mirror.get_cb_y(cb_type)); + } + + std::string cb_module_name = generate_connection_block_module_name(cb_type, cb_coord); + ModuleId cb_module = module_manager.find_module(cb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(cb_module)); + + /* Identify if this sub module includes configuration bits, + * we will update the memory module and instance list + */ + if (0 < find_module_num_config_bits(module_manager, cb_module, + circuit_lib, sram_model, + sram_orgz_type)) { + /* Note that use the original CB coodinate for instance id searching ! */ + module_manager.add_configurable_child(top_module, cb_module, cb_instance_ids[rr_gsb.get_cb_x(cb_type)][rr_gsb.get_cb_y(cb_type)]); + } +} + +/******************************************************************** + * This function adds the SB, CBX, CBY and Grid of a tile + * to the memory modules and memory instances + * This function is designed for organizing memory modules in top-level + * module + *******************************************************************/ +static +void organize_top_module_tile_memory_modules(ModuleManager& module_manager, + const ModuleId& top_module, + const CircuitLibrary& circuit_lib, + const e_config_protocol_type& sram_orgz_type, + const CircuitModelId& sram_model, + const DeviceGrid& grids, + const vtr::Matrix& grid_instance_ids, + const DeviceRRGSB& device_rr_gsb, + const vtr::Matrix& sb_instance_ids, + const std::map>& cb_instance_ids, + const bool& compact_routing_hierarchy, + const vtr::Point& tile_coord, + const e_side& tile_border_side) { + + vtr::Point gsb_coord_range = device_rr_gsb.get_gsb_range(); + + vtr::Point gsb_coord(tile_coord.x(), tile_coord.y() - 1); + + /* We do NOT consider SB and CBs if the gsb is not in the range! */ + if ( (gsb_coord.x() < gsb_coord_range.x()) + && (gsb_coord.y() < gsb_coord_range.y()) ) { + const RRGSB& rr_gsb = device_rr_gsb.get_gsb(gsb_coord.x(), gsb_coord.y()); + /* Find Switch Block: unique module id and instance id! + * Note that switch block does always exist in a GSB + */ + vtr::Point sb_coord(rr_gsb.get_sb_x(), rr_gsb.get_sb_y()); + /* If we use compact routing hierarchy, we should instanciate the unique module of SB */ + if (true == compact_routing_hierarchy) { + const RRGSB& unique_mirror = device_rr_gsb.get_sb_unique_module(sb_coord); + sb_coord.set_x(unique_mirror.get_sb_x()); + sb_coord.set_y(unique_mirror.get_sb_y()); + } + std::string sb_module_name = generate_switch_block_module_name(sb_coord); + ModuleId sb_module = module_manager.find_module(sb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(sb_module)); + + /* Identify if this sub module includes configuration bits, + * we will update the memory module and instance list + */ + /* If the CB does not exist, we can skip addition */ + if ( true == rr_gsb.is_sb_exist()) { + if (0 < find_module_num_config_bits(module_manager, sb_module, + circuit_lib, sram_model, + sram_orgz_type)) { + module_manager.add_configurable_child(top_module, sb_module, sb_instance_ids[rr_gsb.get_sb_x()][rr_gsb.get_sb_y()]); + } + } + + /* Try to find and add CBX and CBY */ + organize_top_module_tile_cb_modules(module_manager, top_module, circuit_lib, + sram_orgz_type, sram_model, + cb_instance_ids.at(CHANX), + device_rr_gsb, rr_gsb, CHANX, + compact_routing_hierarchy); + + organize_top_module_tile_cb_modules(module_manager, top_module, circuit_lib, + sram_orgz_type, sram_model, + cb_instance_ids.at(CHANY), + device_rr_gsb, rr_gsb, CHANY, + compact_routing_hierarchy); + } + + /* Find the module name for this type of grid */ + t_physical_tile_type_ptr grid_type = grids[tile_coord.x()][tile_coord.y()].type; + + /* Skip EMPTY Grid */ + if (true == is_empty_type(grid_type)) { + return; + } + /* Skip width > 1 or height > 1 Grid, which should already been processed when offset=0 */ + if ( (0 < grids[tile_coord.x()][tile_coord.y()].width_offset) + || (0 < grids[tile_coord.x()][tile_coord.y()].height_offset) ) { + return; + } + + std::string grid_module_name_prefix(GRID_MODULE_NAME_PREFIX); + std::string grid_module_name = generate_grid_block_module_name(grid_module_name_prefix, std::string(grid_type->name), is_io_type(grid_type), tile_border_side); + ModuleId grid_module = module_manager.find_module(grid_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(grid_module)); + + /* Identify if this sub module includes configuration bits, + * we will update the memory module and instance list + */ + if (0 < find_module_num_config_bits(module_manager, grid_module, + circuit_lib, sram_model, + sram_orgz_type)) { + module_manager.add_configurable_child(top_module, grid_module, grid_instance_ids[tile_coord.x()][tile_coord.y()]); + } +} + +/******************************************************************** + * Organize the list of memory modules and instances + * This function will record all the sub modules of the top-level module + * (those have memory ports) to two lists: + * 1. memory_modules records the module ids + * 2. memory_instances records the instance ids + * To keep a clean memory connection between sub modules and top-level module, + * the sequence of memory_modules and memory_instances will follow + * a chain of tiles considering their physical location + * + * Inter tile connection: + * +--------------------------------------------------------+ + * | +------+------+-----+------+ | + * | | I/O | I/O | ... | I/O | | + * | | TOP | TOP | | TOP | | + * | +------+------+-----+------+ | + * | +---------------------------------->tail | + * | +------+ | +------+------+-----+------+ +------+ | + * | | | | | | | | | | | | + * | | I/O | | | Tile | Tile | ... | Tile | | I/O | | + * | | LEFT | | | [h+1]| [h+2]| | [n] | |RIGHT | | + * | +------+ | +------+------+-----+------+ +------+ | + * | +-------------------------------+ | + * | ... ... ... ... ... | ... | + * | +-------------------------------+ | + * | +------+ | +------+------+-----+------+ +------+ | + * | | | | | | | | | | | | + * | | I/O | | | Tile | Tile | ... | Tile | | I/O | | + * | | LEFT | | | [i+1]| [i+2]| | [j] | |RIGHT | | + * | +------+ | +------+------+-----+------+ +------+ | + * | +-------------------------------+ | + * | +------+ +------+------+-----+------+ | +------+ | + * | | | | | | | | | | | | + * | | I/O | | Tile | Tile | ... | Tile | | | I/O | | + * | | LEFT | | [0] | [1] | | [i] | | |RIGHT | | + * | +------+ +------+------+-----+------+ | +------+ | + * +-------------------------------------------+ | + * +------+------+-----+------+ | + * | I/O | I/O | ... | I/O | | + * |BOTTOM|BOTTOM| |BOTTOM| | + * +------+------+-----+------+ | + * head >-----------------------------------------------+ + * + * Inner tile connection + * + * Tile + * +---------------+----------+ + * <-+---------------+ + | + * | | | | + * | CLB | | CBY | + * | +-|-+ | + * | | | | + * +---------------+----------+ + * | +-+----+-----+---<--- + * | CBX | SB | + * | | | + * +---------------+----------+ + * + *******************************************************************/ +void organize_top_module_memory_modules(ModuleManager& module_manager, + const ModuleId& top_module, + const CircuitLibrary& circuit_lib, + const e_config_protocol_type& sram_orgz_type, + const CircuitModelId& sram_model, + const DeviceGrid& grids, + const vtr::Matrix& grid_instance_ids, + const DeviceRRGSB& device_rr_gsb, + const vtr::Matrix& sb_instance_ids, + const std::map>& cb_instance_ids, + const bool& compact_routing_hierarchy) { + + /* Ensure clean vectors to return */ + VTR_ASSERT(true == module_manager.configurable_children(top_module).empty()); + + /* First, organize the I/O tiles on the border */ + /* Special for the I/O tileas on RIGHT and BOTTOM, + * which are only I/O blocks, which do NOT contain CBs and SBs + */ + std::vector io_sides{BOTTOM, RIGHT, TOP, LEFT}; + std::map>> io_coords; + + /* BOTTOM side I/Os */ + for (size_t ix = 1; ix < grids.width() - 1; ++ix) { + io_coords[BOTTOM].push_back(vtr::Point(ix, 0)); + } + + /* RIGHT side I/Os */ + for (size_t iy = 1; iy < grids.height() - 1; ++iy) { + io_coords[RIGHT].push_back(vtr::Point(grids.width() - 1, iy)); + } + + /* TOP side I/Os + * Special case for TOP side: We need tile at ix = 0, which has a SB!!! + * + * TOP-LEFT CORNER of FPGA fabric + * + * +--------+ +-------+ + * | EMPTY | | EMPTY | + * | Grid | | CBX | + * | [0][x] | | | + * +--------+ +-------+ + * +--------+ +--------+ + * | EMPTY | | SB | + * | CBX | | [0][x] | + * +--------+ +--------+ + * + */ + for (size_t ix = grids.width() - 2; ix >= 1; --ix) { + io_coords[TOP].push_back(vtr::Point(ix, grids.height() - 1)); + } + io_coords[TOP].push_back(vtr::Point(0, grids.height() - 1)); + + /* LEFT side I/Os */ + for (size_t iy = grids.height() - 2; iy >= 1; --iy) { + io_coords[LEFT].push_back(vtr::Point(0, iy)); + } + + for (const e_side& io_side : io_sides) { + for (const vtr::Point& io_coord : io_coords[io_side]) { + /* Identify the GSB that surrounds the grid */ + organize_top_module_tile_memory_modules(module_manager, top_module, + circuit_lib, sram_orgz_type, sram_model, + grids, grid_instance_ids, + device_rr_gsb, sb_instance_ids, cb_instance_ids, + compact_routing_hierarchy, + io_coord, io_side); + } + } + + /* For the core grids */ + std::vector> core_coords; + bool positive_direction = true; + for (size_t iy = 1; iy < grids.height() - 1; ++iy) { + /* For positive direction: -----> */ + if (true == positive_direction) { + for (size_t ix = 1; ix < grids.width() - 1; ++ix) { + core_coords.push_back(vtr::Point(ix, iy)); + } + } else { + VTR_ASSERT(false == positive_direction); + /* For negative direction: -----> */ + for (size_t ix = grids.width() - 2; ix >= 1; --ix) { + core_coords.push_back(vtr::Point(ix, iy)); + } + } + /* Flip the positive direction to be negative */ + positive_direction = !positive_direction; + } + + for (const vtr::Point& core_coord : core_coords) { + organize_top_module_tile_memory_modules(module_manager, top_module, + circuit_lib, sram_orgz_type, sram_model, + grids, grid_instance_ids, + device_rr_gsb, sb_instance_ids, cb_instance_ids, + compact_routing_hierarchy, + core_coord, NUM_SIDES); + } +} + + +/********************************************************************* + * Add the port-to-port connection between all the memory modules + * and their parent module + * + * Create nets to wire the control signals of memory module to + * the configuration ports of primitive module + * + * Configuration Chain + * ------------------- + * + * config_bus (head) config_bus (tail) + * | ^ + * primitive | | + * +---------------------------------------------+ + * | | | | + * | v | | + * | +-------------------------------------+ | + * | | CMOS-based Memory Modules | | + * | +-------------------------------------+ | + * | | | | + * | v v | + * | sram_out sram_outb | + * | | + * +---------------------------------------------+ + * + * Memory bank + * ----------- + * + * config_bus (BL) config_bus (WL) + * | | + * primitive | | + * +---------------------------------------------+ + * | | | | + * | v v | + * | +-------------------------------------+ | + * | | CMOS-based Memory Modules | | + * | +-------------------------------------+ | + * | | | | + * | v v | + * | sram_out sram_outb | + * | | + * +---------------------------------------------+ + * + **********************************************************************/ +static +void add_top_module_nets_cmos_memory_config_bus(ModuleManager& module_manager, + const ModuleId& parent_module, + const e_config_protocol_type& sram_orgz_type) { + switch (sram_orgz_type) { + case CONFIG_MEM_STANDALONE: + /* Nothing to do */ + break; + case CONFIG_MEM_SCAN_CHAIN: { + add_module_nets_cmos_memory_chain_config_bus(module_manager, parent_module, CONFIG_MEM_SCAN_CHAIN); + break; + } + case CONFIG_MEM_MEMORY_BANK: + /* TODO: */ + break; + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid type of SRAM organization!\n"); + exit(1); + } +} + + +/******************************************************************** + * TODO: + * Add the port-to-port connection between a memory module + * and the configuration bus of a primitive module + * + * Create nets to wire the control signals of memory module to + * the configuration ports of primitive module + * + * Primitive module + * +----------------------------+ + * | +--------+ | + * config | | | | + * ports --->|--------------->| Memory | | + * | | Module | | + * | | | | + * | +--------+ | + * +----------------------------+ + * The detailed config ports really depend on the type + * of SRAM organization. + * + * The config_bus in the argument is the reserved address of configuration + * bus in the parent_module for this memory module + * + * The configuration bus connection will depend not only + * the design technology of the memory cells but also the + * configuration styles of FPGA fabric. + * Here we will branch on the design technology + * + * Note: this function SHOULD be called after the pb_type_module is created + * and its child module (logic_module and memory_module) is created! + *******************************************************************/ +void add_top_module_nets_memory_config_bus(ModuleManager& module_manager, + const ModuleId& parent_module, + const e_config_protocol_type& sram_orgz_type, + const e_circuit_model_design_tech& mem_tech) { + switch (mem_tech) { + case CIRCUIT_MODEL_DESIGN_CMOS: + add_top_module_nets_cmos_memory_config_bus(module_manager, parent_module, + sram_orgz_type); + break; + case CIRCUIT_MODEL_DESIGN_RRAM: + /* TODO: */ + break; + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid type of memory design technology!\n"); + exit(1); + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fabric/build_top_module_memory.h b/openfpga/src/fabric/build_top_module_memory.h new file mode 100644 index 000000000..dfa9a5c78 --- /dev/null +++ b/openfpga/src/fabric/build_top_module_memory.h @@ -0,0 +1,43 @@ +#ifndef BUILD_TOP_MODULE_MEMORY_H +#define BUILD_TOP_MODULE_MEMORY_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ + +#include +#include +#include "vtr_ndmatrix.h" +#include "module_manager.h" +#include "circuit_types.h" +#include "circuit_library.h" +#include "device_grid.h" +#include "device_rr_gsb.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void organize_top_module_memory_modules(ModuleManager& module_manager, + const ModuleId& top_module, + const CircuitLibrary& circuit_lib, + const e_config_protocol_type& sram_orgz_type, + const CircuitModelId& sram_model, + const DeviceGrid& grids, + const vtr::Matrix& grid_instance_ids, + const DeviceRRGSB& device_rr_gsb, + const vtr::Matrix& sb_instance_ids, + const std::map>& cb_instance_ids, + const bool& compact_routing_hierarchy); + +void add_top_module_nets_memory_config_bus(ModuleManager& module_manager, + const ModuleId& parent_module, + const e_config_protocol_type& sram_orgz_type, + const e_circuit_model_design_tech& mem_tech); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/utils/module_manager_utils.cpp b/openfpga/src/utils/module_manager_utils.cpp index 25a77a752..9797763b0 100644 --- a/openfpga/src/utils/module_manager_utils.cpp +++ b/openfpga/src/utils/module_manager_utils.cpp @@ -10,6 +10,9 @@ #include "vtr_log.h" #include "vtr_assert.h" +/* Headers from openfpgautil library */ +#include "openfpga_port.h" + #include "openfpga_naming.h" #include "memory_utils.h" #include "pb_type_utils.h" From 59c13550e0e750482be53581eff556d0a49c251e Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 14 Feb 2020 17:40:59 -0700 Subject: [PATCH 139/645] add direct annotation with inter-column/row syntax --- .../libarchopenfpga/arch/sample_arch.xml | 2 +- .../libarchopenfpga/src/arch_direct.cpp | 112 ++++++++++++++++++ libopenfpga/libarchopenfpga/src/arch_direct.h | 103 ++++++++++++++++ .../libarchopenfpga/src/arch_direct_fwd.h | 22 ++++ .../libarchopenfpga/src/direct_types.h | 22 ---- .../libarchopenfpga/src/openfpga_arch.h | 3 +- .../src/read_xml_openfpga_arch.cpp | 4 +- .../src/read_xml_routing_circuit.cpp | 84 +++++++++++-- .../src/read_xml_routing_circuit.h | 8 +- .../src/write_xml_openfpga_arch.cpp | 3 +- .../src/write_xml_routing_circuit.cpp | 31 ++++- .../src/write_xml_routing_circuit.h | 3 +- openfpga/src/annotation/annotate_rr_graph.cpp | 11 +- .../src/annotation/vpr_device_annotation.cpp | 20 ++-- .../src/annotation/vpr_device_annotation.h | 9 +- 15 files changed, 372 insertions(+), 65 deletions(-) create mode 100644 libopenfpga/libarchopenfpga/src/arch_direct.cpp create mode 100644 libopenfpga/libarchopenfpga/src/arch_direct.h create mode 100644 libopenfpga/libarchopenfpga/src/arch_direct_fwd.h delete mode 100644 libopenfpga/libarchopenfpga/src/direct_types.h diff --git a/libopenfpga/libarchopenfpga/arch/sample_arch.xml b/libopenfpga/libarchopenfpga/arch/sample_arch.xml index dafaa83dd..1d9f8f5a0 100644 --- a/libopenfpga/libarchopenfpga/arch/sample_arch.xml +++ b/libopenfpga/libarchopenfpga/arch/sample_arch.xml @@ -266,7 +266,7 @@ - + diff --git a/libopenfpga/libarchopenfpga/src/arch_direct.cpp b/libopenfpga/libarchopenfpga/src/arch_direct.cpp new file mode 100644 index 000000000..a741dd878 --- /dev/null +++ b/libopenfpga/libarchopenfpga/src/arch_direct.cpp @@ -0,0 +1,112 @@ +#include "vtr_assert.h" + +#include "arch_direct.h" + +/************************************************************************ + * Member functions for class ArchDirect + ***********************************************************************/ + +/************************************************************************ + * Constructors + ***********************************************************************/ +ArchDirect::ArchDirect() { + return; +} + +/************************************************************************ + * Public Accessors : aggregates + ***********************************************************************/ +ArchDirect::arch_direct_range ArchDirect::directs() const { + return vtr::make_range(direct_ids_.begin(), direct_ids_.end()); +} + +/************************************************************************ + * Public Accessors + ***********************************************************************/ +ArchDirectId ArchDirect::direct(const std::string& name) const { + if (0 < direct_name2ids_.count(name)) { + return direct_name2ids_.at(name); + } + return ArchDirectId::INVALID(); +} + +std::string ArchDirect::name(const ArchDirectId& direct_id) const { + /* validate the direct_id */ + VTR_ASSERT(valid_direct_id(direct_id)); + return names_[direct_id]; +} + +CircuitModelId ArchDirect::circuit_model(const ArchDirectId& direct_id) const { + /* validate the direct_id */ + VTR_ASSERT(valid_direct_id(direct_id)); + return circuit_models_[direct_id]; +} + +e_direct_type ArchDirect::type(const ArchDirectId& direct_id) const { + /* validate the direct_id */ + VTR_ASSERT(valid_direct_id(direct_id)); + return types_[direct_id]; +} + +e_direct_direction ArchDirect::x_dir(const ArchDirectId& direct_id) const { + /* validate the direct_id */ + VTR_ASSERT(valid_direct_id(direct_id)); + return directions_[direct_id].x(); +} + +e_direct_direction ArchDirect::y_dir(const ArchDirectId& direct_id) const { + /* validate the direct_id */ + VTR_ASSERT(valid_direct_id(direct_id)); + return directions_[direct_id].y(); +} + +/************************************************************************ + * Public Mutators + ***********************************************************************/ +ArchDirectId ArchDirect::add_direct(const std::string& name) { + if (0 < direct_name2ids_.count(name)) { + return ArchDirectId::INVALID(); + } + + /* This is a legal name. we can create a new id */ + ArchDirectId direct = ArchDirectId(direct_ids_.size()); + direct_ids_.push_back(direct); + names_.push_back(name); + circuit_models_.push_back(CircuitModelId::INVALID()); + types_.emplace_back(NUM_DIRECT_TYPES); + directions_.emplace_back(vtr::Point(NUM_DIRECT_DIRECTIONS, NUM_DIRECT_DIRECTIONS)); + + /* Register in the name-to-id map */ + direct_name2ids_[name] = direct; + + return direct; +} + +void ArchDirect::set_circuit_model(const ArchDirectId& direct_id, const CircuitModelId& circuit_model) { + /* validate the direct_id */ + VTR_ASSERT(valid_direct_id(direct_id)); + circuit_models_[direct_id] = circuit_model; +} + +void ArchDirect::set_type(const ArchDirectId& direct_id, const e_direct_type& type) { + /* validate the direct_id */ + VTR_ASSERT(valid_direct_id(direct_id)); + types_[direct_id] = type; +} + +void ArchDirect::set_direction(const ArchDirectId& direct_id, + const e_direct_direction& x_dir, + const e_direct_direction& y_dir) { + /* validate the direct_id */ + VTR_ASSERT(valid_direct_id(direct_id)); + directions_[direct_id].set_x(x_dir); + directions_[direct_id].set_y(y_dir); +} + +/************************************************************************ + * Internal invalidators/validators + ***********************************************************************/ +/* Validators */ +bool ArchDirect::valid_direct_id(const ArchDirectId& direct_id) const { + return ( size_t(direct_id) < direct_ids_.size() ) && ( direct_id == direct_ids_[direct_id] ); +} diff --git a/libopenfpga/libarchopenfpga/src/arch_direct.h b/libopenfpga/libarchopenfpga/src/arch_direct.h new file mode 100644 index 000000000..067b257d1 --- /dev/null +++ b/libopenfpga/libarchopenfpga/src/arch_direct.h @@ -0,0 +1,103 @@ +#ifndef ARCH_DIRECT_H +#define ARCH_DIRECT_H + +#include + +#include "vtr_vector.h" +#include "vtr_geometry.h" + +#include "circuit_library_fwd.h" +#include "arch_direct_fwd.h" + +/******************************************************************** + * Define the types of point to point connection between CLBs + * These types are supplementary to the original VPR direct connections + * Here we extend to the cross-row and cross-column connections + ********************************************************************/ +enum e_direct_type { + INNER_COLUMN, + INNER_ROW, + INTER_COLUMN, + INTER_ROW, + NUM_DIRECT_TYPES +}; +constexpr std::array DIRECT_TYPE_STRING = {{"inner_column", "inner_row", "inter_column", "inter_row"}}; + +enum e_direct_direction { + POSITIVE_DIR, + NEGATIVE_DIR, + NUM_DIRECT_DIRECTIONS +}; +constexpr std::array DIRECT_DIRECTION_STRING = {{"positive", "negative"}}; + +/******************************************************************** + * A data base to describe the direct connection in OpenFPGA architecture + * For each direct connection, it will include + * - name: the identifier to annotate the original direct connection in VPR architecture + * - circuit model: the circuit model to used to implement this connection + * - type: if this connection should be cross-column or cross-row + * - x-direction: how this connection is going to be applied to adjacent columns + * a positive x-direction means that column A will be connected + * to the column B on the right side of column A + * - y-direction: how this connection is going to be applied to adjacent rows + * a positive y-direction means that row A will be connected + * to the row B on the bottom side of row A + * + * Note that: this is the data structure to be used when parsing the XML + * this is NOT the data structure to be use in core engine + ********************************************************************/ +class ArchDirect { + public: /* Types */ + typedef vtr::vector::const_iterator arch_direct_iterator; + /* Create range */ + typedef vtr::Range arch_direct_range; + + public: /* Constructors */ + ArchDirect(); + + public: /* Accessors: aggregates */ + arch_direct_range directs() const; + + public: /* Public Accessors: Basic data query on directs */ + ArchDirectId direct(const std::string& name) const; + std::string name(const ArchDirectId& direct_id) const; + CircuitModelId circuit_model(const ArchDirectId& direct_id) const; + e_direct_type type(const ArchDirectId& direct_id) const; + e_direct_direction x_dir(const ArchDirectId& direct_id) const; + e_direct_direction y_dir(const ArchDirectId& direct_id) const; + public: /* Public Mutators */ + ArchDirectId add_direct(const std::string& name); + void set_circuit_model(const ArchDirectId& direct_id, const CircuitModelId& circuit_model); + void set_type(const ArchDirectId& direct_id, const e_direct_type& type); + void set_direction(const ArchDirectId& direct_id, + const e_direct_direction& x_dir, + const e_direct_direction& y_dir); + public: /* Public invalidators/validators */ + bool valid_direct_id(const ArchDirectId& direct_id) const; + private: /* Internal data */ + vtr::vector direct_ids_; + + /* Unique name: the identifier to annotate the original direct connection in VPR architecture */ + vtr::vector names_; + + /* circuit model: the circuit model to used to implement this connection */ + vtr::vector circuit_models_; + + /* type: if this connection should be cross-column or cross-row */ + vtr::vector types_; + + /* + * x-direction: how this connection is going to be applied to adjacent columns + * a positive x-direction means that column A will be connected + * to the column B on the right side of column A + * y-direction: how this connection is going to be applied to adjacent rows + * a positive y-direction means that row A will be connected + * to the row B on the bottom side of row A + */ + vtr::vector> directions_; + + /* Fast look-up */ + std::map direct_name2ids_; +}; + +#endif diff --git a/libopenfpga/libarchopenfpga/src/arch_direct_fwd.h b/libopenfpga/libarchopenfpga/src/arch_direct_fwd.h new file mode 100644 index 000000000..8a1f302f3 --- /dev/null +++ b/libopenfpga/libarchopenfpga/src/arch_direct_fwd.h @@ -0,0 +1,22 @@ +/************************************************************************ + * A header file for ArchDirect class, including critical data declaration + * Please include this file only for using any TechnologyLibrary data structure + * Refer to arch_direct.h for more details + ***********************************************************************/ + +/************************************************************************ + * Create strong id for ArchDirect to avoid illegal type casting + ***********************************************************************/ +#ifndef ARCH_DIRECT_FWD_H +#define ARCH_DIRECT_FWD_H + +#include "vtr_strong_id.h" + +struct arch_direct_id_tag; + +typedef vtr::StrongId ArchDirectId; + +/* Short declaration of class */ +class ArchDirect; + +#endif diff --git a/libopenfpga/libarchopenfpga/src/direct_types.h b/libopenfpga/libarchopenfpga/src/direct_types.h deleted file mode 100644 index 6b44f1fa5..000000000 --- a/libopenfpga/libarchopenfpga/src/direct_types.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef DIRECT_TYPES_H -#define DIRECT_TYPES_H - -/******************************************************************** - * Define the types of point to point connection between CLBs - * These types are supplementary to the original VPR direct connections - * Here we extend to the cross-row and cross-column connections - ********************************************************************/ -enum e_point2point_interconnection_type { - NO_P2P, - P2P_DIRECT_COLUMN, - P2P_DIRECT_ROW, - NUM_POINT2POINT_INTERCONNECT_TYPE -}; - -enum e_point2point_interconnection_dir { - POSITIVE_DIR, - NEGATIVE_DIR, - NUM_POINT2POINT_INTERCONNECT_DIR -}; - -#endif diff --git a/libopenfpga/libarchopenfpga/src/openfpga_arch.h b/libopenfpga/libarchopenfpga/src/openfpga_arch.h index 7f102dc85..a3dd71a12 100644 --- a/libopenfpga/libarchopenfpga/src/openfpga_arch.h +++ b/libopenfpga/libarchopenfpga/src/openfpga_arch.h @@ -8,6 +8,7 @@ #include "technology_library.h" #include "simulation_setting.h" #include "config_protocol.h" +#include "arch_direct.h" #include "pb_type_annotation.h" /* namespace openfpga begins */ @@ -48,7 +49,7 @@ struct Arch { /* Mapping from the names of direct connection * to circuit models in circuit library */ - std::map direct2circuit; + ArchDirect arch_direct; /* Pb type annotations * Bind from operating to physical diff --git a/libopenfpga/libarchopenfpga/src/read_xml_openfpga_arch.cpp b/libopenfpga/libarchopenfpga/src/read_xml_openfpga_arch.cpp index b35955f97..4c773f87f 100644 --- a/libopenfpga/libarchopenfpga/src/read_xml_openfpga_arch.cpp +++ b/libopenfpga/libarchopenfpga/src/read_xml_openfpga_arch.cpp @@ -94,8 +94,8 @@ openfpga::Arch read_xml_openfpga_arch(const char* arch_file_name) { openfpga_arch.circuit_lib); /* Parse the direct circuit definition */ - openfpga_arch.direct2circuit = read_xml_direct_circuit(xml_openfpga_arch, loc_data, - openfpga_arch.circuit_lib); + openfpga_arch.arch_direct = read_xml_direct_circuit(xml_openfpga_arch, loc_data, + openfpga_arch.circuit_lib); /* Parse the pb_type annotation */ openfpga_arch.pb_type_annotations = read_xml_pb_type_annotations(xml_openfpga_arch, loc_data); diff --git a/libopenfpga/libarchopenfpga/src/read_xml_routing_circuit.cpp b/libopenfpga/libarchopenfpga/src/read_xml_routing_circuit.cpp index 060e26f1b..3114276ae 100644 --- a/libopenfpga/libarchopenfpga/src/read_xml_routing_circuit.cpp +++ b/libopenfpga/libarchopenfpga/src/read_xml_routing_circuit.cpp @@ -185,20 +185,53 @@ std::map read_xml_routing_segment_circuit(pugi::xml return seg2circuit; } +/******************************************************************** + * Convert string to the enumerate of direct type + *******************************************************************/ +static +e_direct_type string_to_direct_type(const std::string& type_string) { + if (std::string("column") == type_string) { + return INTER_COLUMN; + } + + if (std::string("row") == type_string) { + return INTER_ROW; + } + + return NUM_DIRECT_TYPES; +} + +/******************************************************************** + * Convert string to the enumerate of direct direction type + *******************************************************************/ +static +e_direct_direction string_to_direct_direction(const std::string& type_string) { + if (std::string("positive") == type_string) { + return POSITIVE_DIR; + } + + if (std::string("negative") == type_string) { + return NEGATIVE_DIR; + } + + return NUM_DIRECT_DIRECTIONS; +} + + /******************************************************************** * Parse XML codes about to an object of name-to-circuit mapping * Note: this function should be called AFTER the parsing of circuit library!!! *******************************************************************/ -std::map read_xml_direct_circuit(pugi::xml_node& Node, - const pugiutil::loc_data& loc_data, - const CircuitLibrary& circuit_lib) { - std::map direct2circuit; +ArchDirect read_xml_direct_circuit(pugi::xml_node& Node, + const pugiutil::loc_data& loc_data, + const CircuitLibrary& circuit_lib) { + ArchDirect arch_direct; /* Parse direct list, this is optional. May not be used */ pugi::xml_node xml_directs= get_single_child(Node, "direct_connection", loc_data, pugiutil::ReqOpt::OPTIONAL); /* Not found, we can return */ if (!xml_directs) { - return direct2circuit; + return arch_direct; } /* Iterate over the children under this node, @@ -219,17 +252,46 @@ std::map read_xml_direct_circuit(pugi::xml_node& No circuit_lib, direct_model_name, CIRCUIT_MODEL_WIRE); - /* Ensure that there is no duplicated seg names defined here */ - std::map::const_iterator it = direct2circuit.find(direct_name); - if (it != direct2circuit.end()) { + /* Add to the Arch direct database */ + ArchDirectId direct = arch_direct.add_direct(direct_name); + if (false == arch_direct.valid_direct_id(direct)) { archfpga_throw(loc_data.filename_c_str(), loc_data.line(xml_direct), "Direct name '%s' has been defined more than once!\n", direct_name.c_str()); } + arch_direct.set_circuit_model(direct, direct_model); - /* Pass all the check, we can add it to the map */ - direct2circuit[direct_name] = direct_model; + /* Add more information*/ + std::string direct_type_name = get_attribute(xml_direct, "type", loc_data).as_string(); + e_direct_type direct_type = string_to_direct_type(direct_type_name); + + if (NUM_DIRECT_TYPES == direct_type) { + archfpga_throw(loc_data.filename_c_str(), loc_data.line(xml_direct), + "Direct type '%s' is not support! Acceptable values are [column|row]\n", + direct_type_name.c_str()); + } + + arch_direct.set_type(direct, direct_type); + + std::string x_dir_name = get_attribute(xml_direct, "x_dir", loc_data).as_string(); + std::string y_dir_name = get_attribute(xml_direct, "y_dir", loc_data).as_string(); + e_direct_direction x_dir = string_to_direct_direction(x_dir_name); + e_direct_direction y_dir = string_to_direct_direction(y_dir_name); + + if (NUM_DIRECT_DIRECTIONS == x_dir) { + archfpga_throw(loc_data.filename_c_str(), loc_data.line(xml_direct), + "Direct x-direction '%s' is not support! Acceptable values are [positive|column]\n", + x_dir_name.c_str()); + } + + if (NUM_DIRECT_DIRECTIONS == y_dir) { + archfpga_throw(loc_data.filename_c_str(), loc_data.line(xml_direct), + "Direct y-direction '%s' is not support! Acceptable values are [positive|column]\n", + y_dir_name.c_str()); + } + + arch_direct.set_direction(direct, x_dir, y_dir); } - return direct2circuit; + return arch_direct; } diff --git a/libopenfpga/libarchopenfpga/src/read_xml_routing_circuit.h b/libopenfpga/libarchopenfpga/src/read_xml_routing_circuit.h index e00627022..28d14710d 100644 --- a/libopenfpga/libarchopenfpga/src/read_xml_routing_circuit.h +++ b/libopenfpga/libarchopenfpga/src/read_xml_routing_circuit.h @@ -10,6 +10,7 @@ #include "pugixml_util.hpp" #include "pugixml.hpp" #include "circuit_library.h" +#include "arch_direct.h" /******************************************************************** * Function declaration @@ -26,8 +27,9 @@ std::map read_xml_routing_segment_circuit(pugi::xml const pugiutil::loc_data& loc_data, const CircuitLibrary& circuit_lib); -std::map read_xml_direct_circuit(pugi::xml_node& Node, - const pugiutil::loc_data& loc_data, - const CircuitLibrary& circuit_lib); +ArchDirect read_xml_direct_circuit(pugi::xml_node& Node, + const pugiutil::loc_data& loc_data, + const CircuitLibrary& circuit_lib); + #endif diff --git a/libopenfpga/libarchopenfpga/src/write_xml_openfpga_arch.cpp b/libopenfpga/libarchopenfpga/src/write_xml_openfpga_arch.cpp index eb999dcd1..6d0a8ff06 100644 --- a/libopenfpga/libarchopenfpga/src/write_xml_openfpga_arch.cpp +++ b/libopenfpga/libarchopenfpga/src/write_xml_openfpga_arch.cpp @@ -56,8 +56,7 @@ void write_xml_openfpga_arch(const char* fname, write_xml_routing_segment_circuit(fp, fname, openfpga_arch.circuit_lib, openfpga_arch.routing_seg2circuit); /* Write the direct connection circuit definition */ - write_xml_direct_circuit(fp, fname, openfpga_arch.circuit_lib, openfpga_arch.direct2circuit); - + write_xml_direct_circuit(fp, fname, openfpga_arch.circuit_lib, openfpga_arch.arch_direct); /* Write the pb_type annotations */ openfpga::write_xml_pb_type_annotations(fp, fname, openfpga_arch. pb_type_annotations); diff --git a/libopenfpga/libarchopenfpga/src/write_xml_routing_circuit.cpp b/libopenfpga/libarchopenfpga/src/write_xml_routing_circuit.cpp index 70f95f37f..978d38516 100644 --- a/libopenfpga/libarchopenfpga/src/write_xml_routing_circuit.cpp +++ b/libopenfpga/libarchopenfpga/src/write_xml_routing_circuit.cpp @@ -38,6 +38,29 @@ void write_xml_routing_component_circuit(std::fstream& fp, } } +/******************************************************************** + * Write switch circuit model definition in XML format + *******************************************************************/ +static +void write_xml_direct_component_circuit(std::fstream& fp, + const char* fname, + const std::string& direct_tag_name, + const CircuitLibrary& circuit_lib, + const ArchDirect& arch_direct, + const ArchDirectId& direct_id) { + /* Validate the file stream */ + openfpga::check_file_stream(fname, fp); + + /* Iterate over the mapping */ + fp << "\t\t" << "<" << direct_tag_name; + write_xml_attribute(fp, "name", arch_direct.name(direct_id).c_str()); + write_xml_attribute(fp, "circuit_model_name", circuit_lib.model_name(arch_direct.circuit_model(direct_id)).c_str()); + write_xml_attribute(fp, "type", DIRECT_TYPE_STRING[arch_direct.type(direct_id)]); + write_xml_attribute(fp, "x_dir", DIRECT_DIRECTION_STRING[arch_direct.x_dir(direct_id)]); + write_xml_attribute(fp, "y_dir", DIRECT_DIRECTION_STRING[arch_direct.y_dir(direct_id)]); + fp << "/>" << "\n"; +} + /******************************************************************** * Write Connection block circuit models in XML format *******************************************************************/ @@ -104,9 +127,9 @@ void write_xml_routing_segment_circuit(std::fstream& fp, void write_xml_direct_circuit(std::fstream& fp, const char* fname, const CircuitLibrary& circuit_lib, - const std::map& direct2circuit) { + const ArchDirect& arch_direct) { /* If the direct2circuit is empty, we do not output XML */ - if (direct2circuit.empty()) { + if (0 == arch_direct.directs().size()) { return; } @@ -117,7 +140,9 @@ void write_xml_direct_circuit(std::fstream& fp, fp << "\t" << "" << "\n"; /* Write each direct connection circuit definition */ - write_xml_routing_component_circuit(fp, fname, std::string("direct"), circuit_lib, direct2circuit); + for (const ArchDirectId& direct_id : arch_direct.directs()) { + write_xml_direct_component_circuit(fp, fname, std::string("direct"), circuit_lib, arch_direct, direct_id); + } /* Finish writing the root node */ fp << "\t" << "" << "\n"; diff --git a/libopenfpga/libarchopenfpga/src/write_xml_routing_circuit.h b/libopenfpga/libarchopenfpga/src/write_xml_routing_circuit.h index 7cf2cd8ae..bddb7cc56 100644 --- a/libopenfpga/libarchopenfpga/src/write_xml_routing_circuit.h +++ b/libopenfpga/libarchopenfpga/src/write_xml_routing_circuit.h @@ -9,6 +9,7 @@ #include #include "circuit_library.h" +#include "arch_direct.h" /******************************************************************** * Function declaration @@ -31,6 +32,6 @@ void write_xml_routing_segment_circuit(std::fstream& fp, void write_xml_direct_circuit(std::fstream& fp, const char* fname, const CircuitLibrary& circuit_lib, - const std::map& direct2circuit); + const ArchDirect& arch_direct); #endif diff --git a/openfpga/src/annotation/annotate_rr_graph.cpp b/openfpga/src/annotation/annotate_rr_graph.cpp index 351c248bb..d0097ced6 100644 --- a/openfpga/src/annotation/annotate_rr_graph.cpp +++ b/openfpga/src/annotation/annotate_rr_graph.cpp @@ -524,13 +524,16 @@ void annotate_direct_circuit_models(const DeviceContext& vpr_device_ctx, for (int idirect = 0; idirect < vpr_device_ctx.arch->num_directs; ++idirect) { std::string direct_name = vpr_device_ctx.arch->Directs[idirect].name; - CircuitModelId circuit_model = CircuitModelId::INVALID(); /* The name-to-circuit mapping is stored in either cb_switch-to-circuit or sb_switch-to-circuit, * Try to find one and update the device annotation */ - if (0 < openfpga_arch.direct2circuit.count(direct_name)) { - circuit_model = openfpga_arch.direct2circuit.at(direct_name); + ArchDirectId direct_id = openfpga_arch.arch_direct.direct(direct_name); + /* Cannot find a direct, no annotation needed for this direct */ + if (ArchDirectId::INVALID() == direct_id) { + continue; } + + CircuitModelId circuit_model = openfpga_arch.arch_direct.circuit_model(direct_id); /* Cannot find a circuit model, error out! */ if (CircuitModelId::INVALID() == circuit_model) { VTR_LOG_ERROR("Fail to find a circuit model for a direct connection '%s'!\nPlease check your OpenFPGA architecture XML!\n", @@ -547,7 +550,7 @@ void annotate_direct_circuit_models(const DeviceContext& vpr_device_ctx, } /* Now update the device annotation */ - vpr_device_annotation.add_direct_circuit_model(idirect, circuit_model); + vpr_device_annotation.add_direct_annotation(idirect, direct_id); VTR_LOGV(verbose_output, "Binded a direct connection '%s' to circuit model '%s'\n", direct_name.c_str(), diff --git a/openfpga/src/annotation/vpr_device_annotation.cpp b/openfpga/src/annotation/vpr_device_annotation.cpp index aa2e6fce9..5b36e3885 100644 --- a/openfpga/src/annotation/vpr_device_annotation.cpp +++ b/openfpga/src/annotation/vpr_device_annotation.cpp @@ -233,13 +233,12 @@ CircuitModelId VprDeviceAnnotation::rr_segment_circuit_model(const RRSegmentId& return rr_segment_circuit_models_.at(rr_segment); } -CircuitModelId VprDeviceAnnotation::direct_circuit_model(const size_t& direct) const { +ArchDirectId VprDeviceAnnotation::direct_annotation(const size_t& direct) const { /* Ensure that the rr_switch is in the list */ - std::map::const_iterator it = direct_circuit_models_.find(direct); - if (it == direct_circuit_models_.end()) { - return CircuitModelId::INVALID(); + if (0 == direct_annotations_.count(direct)) { + return ArchDirectId::INVALID(); } - return direct_circuit_models_.at(direct); + return direct_annotations_.at(direct); } /************************************************************************ @@ -461,15 +460,14 @@ void VprDeviceAnnotation::add_rr_segment_circuit_model(const RRSegmentId& rr_seg rr_segment_circuit_models_[rr_segment] = circuit_model; } -void VprDeviceAnnotation::add_direct_circuit_model(const size_t& direct, const CircuitModelId& circuit_model) { +void VprDeviceAnnotation::add_direct_annotation(const size_t& direct, const ArchDirectId& arch_direct_id) { /* Warn any override attempt */ - std::map::const_iterator it = direct_circuit_models_.find(direct); - if (it != direct_circuit_models_.end()) { - VTR_LOG_WARN("Override the annotation between direct '%ld' and its circuit_model '%ld'!\n", - size_t(direct), size_t(circuit_model)); + if (0 < direct_annotations_.count(direct)) { + VTR_LOG_WARN("Override the annotation between direct '%ld' and its annotation '%ld'!\n", + size_t(direct), size_t(arch_direct_id)); } - direct_circuit_models_[direct] = circuit_model; + direct_annotations_[direct] = arch_direct_id; } } /* End namespace openfpga*/ diff --git a/openfpga/src/annotation/vpr_device_annotation.h b/openfpga/src/annotation/vpr_device_annotation.h index 74868ae61..5c083e367 100644 --- a/openfpga/src/annotation/vpr_device_annotation.h +++ b/openfpga/src/annotation/vpr_device_annotation.h @@ -18,6 +18,7 @@ /* Header from openfpgautil library */ #include "openfpga_port.h" #include "circuit_library.h" +#include "arch_direct.h" /* Begin namespace openfpga */ namespace openfpga { @@ -71,7 +72,7 @@ class VprDeviceAnnotation { t_pb_graph_pin* physical_pb_graph_pin(t_pb_graph_pin* pb_graph_pin) const; CircuitModelId rr_switch_circuit_model(const RRSwitchId& rr_switch) const; CircuitModelId rr_segment_circuit_model(const RRSegmentId& rr_segment) const; - CircuitModelId direct_circuit_model(const size_t& direct) const; + ArchDirectId direct_annotation(const size_t& direct) const; public: /* Public mutators */ void add_pb_type_physical_mode(t_pb_type* pb_type, t_mode* physical_mode); void add_physical_pb_type(t_pb_type* operating_pb_type, t_pb_type* physical_pb_type); @@ -91,7 +92,7 @@ class VprDeviceAnnotation { void add_physical_pb_graph_pin(t_pb_graph_pin* operating_pb_graph_pin, t_pb_graph_pin* physical_pb_graph_pin); void add_rr_switch_circuit_model(const RRSwitchId& rr_switch, const CircuitModelId& circuit_model); void add_rr_segment_circuit_model(const RRSegmentId& rr_segment, const CircuitModelId& circuit_model); - void add_direct_circuit_model(const size_t& direct, const CircuitModelId& circuit_model); + void add_direct_annotation(const size_t& direct, const ArchDirectId& arch_direct_id); private: /* Internal data */ /* Pair a regular pb_type to its physical pb_type */ std::map physical_pb_types_; @@ -173,8 +174,8 @@ class VprDeviceAnnotation { /* Pair a Routing Segment (rr_segment) to a circuit model */ std::map rr_segment_circuit_models_; - /* Pair a direct connection (direct) to a circuit model */ - std::map direct_circuit_models_; + /* Pair a direct connection (direct) to a annotation which contains circuit model id */ + std::map direct_annotations_; }; } /* End namespace openfpga*/ From 7e86cf107915b00cc3095aba31808e9e6c8b4706 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 14 Feb 2020 19:11:49 -0700 Subject: [PATCH 140/645] add tile direct data structure --- openfpga/src/tile_direct/tile_direct.cpp | 102 +++++++++++++++++++++ openfpga/src/tile_direct/tile_direct.h | 78 ++++++++++++++++ openfpga/src/tile_direct/tile_direct_fwd.h | 23 +++++ 3 files changed, 203 insertions(+) create mode 100644 openfpga/src/tile_direct/tile_direct.cpp create mode 100644 openfpga/src/tile_direct/tile_direct.h create mode 100644 openfpga/src/tile_direct/tile_direct_fwd.h diff --git a/openfpga/src/tile_direct/tile_direct.cpp b/openfpga/src/tile_direct/tile_direct.cpp new file mode 100644 index 000000000..0face1b7e --- /dev/null +++ b/openfpga/src/tile_direct/tile_direct.cpp @@ -0,0 +1,102 @@ +/****************************************************************************** + * Memember functions for data structure TileDirect + ******************************************************************************/ +#include "vtr_assert.h" + +#include "tile_direct.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/************************************************** + * Public Accessors + *************************************************/ +TileDirect::tile_direct_range TileDirect::directs() const { + return vtr::make_range(direct_ids_.begin(), direct_ids_.end()); +} + +t_physical_tile_type_ptr TileDirect::from_tile(const TileDirectId& direct_id) const { + /* Validate the direct_id */ + VTR_ASSERT(valid_direct_id(direct_id)); + return from_tiles_[direct_id]; +} + +vtr::Point TileDirect::from_tile_coordinate(const TileDirectId& direct_id) const { + /* Validate the direct_id */ + VTR_ASSERT(valid_direct_id(direct_id)); + return from_tile_coords_[direct_id]; +} + +size_t TileDirect::from_tile_pin(const TileDirectId& direct_id) const { + /* Validate the direct_id */ + VTR_ASSERT(valid_direct_id(direct_id)); + return from_tile_pins_[direct_id]; +} + +e_side TileDirect::from_tile_side(const TileDirectId& direct_id) const { + /* Validate the direct_id */ + VTR_ASSERT(valid_direct_id(direct_id)); + return from_tile_sides_[direct_id]; +} + +t_physical_tile_type_ptr TileDirect::to_tile(const TileDirectId& direct_id) const { + /* Validate the direct_id */ + VTR_ASSERT(valid_direct_id(direct_id)); + return to_tiles_[direct_id]; +} + +vtr::Point TileDirect::to_tile_coordinate(const TileDirectId& direct_id) const { + /* Validate the direct_id */ + VTR_ASSERT(valid_direct_id(direct_id)); + return to_tile_coords_[direct_id]; +} + +size_t TileDirect::to_tile_pin(const TileDirectId& direct_id) const { + /* Validate the direct_id */ + VTR_ASSERT(valid_direct_id(direct_id)); + return to_tile_pins_[direct_id]; +} + +e_side TileDirect::to_tile_side(const TileDirectId& direct_id) const { + /* Validate the direct_id */ + VTR_ASSERT(valid_direct_id(direct_id)); + return to_tile_sides_[direct_id]; +} + +/****************************************************************************** + * Private Mutators + ******************************************************************************/ +TileDirectId TileDirect::add_direct(t_physical_tile_type_ptr from_tile, + const vtr::Point& from_tile_coord, + const e_side& from_tile_side, + const size_t& from_tile_pin, + t_physical_tile_type_ptr to_tile, + const vtr::Point& to_tile_coord, + const e_side& to_tile_side, + const size_t& to_tile_pin) { + /* Create an new id */ + TileDirectId direct = TileDirectId(direct_ids_.size()); + direct_ids_.push_back(direct); + + /* Allocate other attributes */ + from_tiles_.push_back(from_tile); + from_tile_coords_.push_back(from_tile_coord); + from_tile_sides_.push_back(from_tile_side); + from_tile_pins_.push_back(from_tile_pin); + + to_tiles_.push_back(to_tile); + to_tile_coords_.push_back(to_tile_coord); + to_tile_sides_.push_back(to_tile_side); + to_tile_pins_.push_back(to_tile_pin); + + return direct; +} + +/****************************************************************************** + * Private validators/invalidators + ******************************************************************************/ +bool TileDirect::valid_direct_id(const TileDirectId& direct_id) const { + return ( size_t(direct_id) < direct_ids_.size() ) && ( direct_id == direct_ids_[direct_id] ); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/tile_direct/tile_direct.h b/openfpga/src/tile_direct/tile_direct.h new file mode 100644 index 000000000..562b559ca --- /dev/null +++ b/openfpga/src/tile_direct/tile_direct.h @@ -0,0 +1,78 @@ +#ifndef TILE_DIRECT_H +#define TILE_DIRECT_H + +/******************************************************************** + * Include header files required by the data structure definition + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_geometry.h" +#include "vtr_vector.h" + +/* Headers from readarch library */ +#include "physical_types.h" + +#include "tile_direct_fwd.h" + +/* Begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * TileDirect object aims to be a database to store all the information + * about direct connection between tiles + * - starting tile and end tile for each point-to-point direct connection + * - circuit model to implement each direct connection + * + * TileDirect is compiled from ArchDirect for a specific FPGA fabric. + *******************************************************************/ +class TileDirect { + public: /* Types and ranges */ + typedef vtr::vector::const_iterator tile_direct_iterator; + typedef vtr::Range tile_direct_range; + public: /* Public aggregators */ + tile_direct_range directs() const; + t_physical_tile_type_ptr from_tile(const TileDirectId& direct_id) const; + vtr::Point from_tile_coordinate(const TileDirectId& direct_id) const; + e_side from_tile_side(const TileDirectId& direct_id) const; + size_t from_tile_pin(const TileDirectId& direct_id) const; + t_physical_tile_type_ptr to_tile(const TileDirectId& direct_id) const; + vtr::Point to_tile_coordinate(const TileDirectId& direct_id) const; + e_side to_tile_side(const TileDirectId& direct_id) const; + size_t to_tile_pin(const TileDirectId& direct_id) const; + public: /* Public mutators */ + TileDirectId add_direct(t_physical_tile_type_ptr from_tile, + const vtr::Point& from_tile_coord, + const e_side& from_tile_side, + const size_t& from_tile_pin, + t_physical_tile_type_ptr to_tile, + const vtr::Point& to_tile_coord, + const e_side& to_tile_side, + const size_t& to_tile_pin); + public: /* Public validators/invalidators */ + bool valid_direct_id(const TileDirectId& direct_id) const; + private: /* Internal Data */ + vtr::vector direct_ids_; + + /* Detailed information about the starting tile + * - tile type description + * - tile coordinate + * - tile pin id + */ + vtr::vector from_tiles_; + vtr::vector> from_tile_coords_; + vtr::vector from_tile_sides_; + vtr::vector from_tile_pins_; + + /* Detailed information about the ending tile + * - tile type description + * - tile coordinate + * - tile pin id + */ + vtr::vector to_tiles_; + vtr::vector> to_tile_coords_; + vtr::vector to_tile_sides_; + vtr::vector to_tile_pins_; +}; + +} /* End namespace openfpga*/ + +#endif diff --git a/openfpga/src/tile_direct/tile_direct_fwd.h b/openfpga/src/tile_direct/tile_direct_fwd.h new file mode 100644 index 000000000..c83a00ee9 --- /dev/null +++ b/openfpga/src/tile_direct/tile_direct_fwd.h @@ -0,0 +1,23 @@ +/************************************************** + * This file includes only declarations for + * the data structures for TileDirect + * Please refer to tile_direct.h for more details + *************************************************/ +#ifndef TILE_DIRECT_FWD_H +#define TILE_DIRECT_FWD_H + +#include "vtr_strong_id.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/* Strong Ids for ModuleManager */ +struct tile_direct_id_tag; + +typedef vtr::StrongId TileDirectId; + +class TileDirect; + +} /* end namespace openfpga */ + +#endif From 213c611c0b323b9d2e4e03f8cef52b881a29134d Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 14 Feb 2020 22:21:32 -0700 Subject: [PATCH 141/645] add tile direct builder --- openfpga/src/base/openfpga_context.h | 8 +- openfpga/src/base/openfpga_link_arch.cpp | 7 + .../src/tile_direct/build_tile_direct.cpp | 367 ++++++++++++++++++ openfpga/src/tile_direct/build_tile_direct.h | 25 ++ openfpga/src/tile_direct/tile_direct.cpp | 15 + openfpga/src/tile_direct/tile_direct.h | 8 + 6 files changed, 429 insertions(+), 1 deletion(-) create mode 100644 openfpga/src/tile_direct/build_tile_direct.cpp create mode 100644 openfpga/src/tile_direct/build_tile_direct.h diff --git a/openfpga/src/base/openfpga_context.h b/openfpga/src/base/openfpga_context.h index 6f9ee0910..ac3cc99c8 100644 --- a/openfpga/src/base/openfpga_context.h +++ b/openfpga/src/base/openfpga_context.h @@ -8,6 +8,7 @@ #include "vpr_clustering_annotation.h" #include "vpr_routing_annotation.h" #include "mux_library.h" +#include "tile_direct.h" #include "module_manager.h" #include "device_rr_gsb.h" @@ -47,6 +48,7 @@ class OpenfpgaContext : public Context { const openfpga::VprRoutingAnnotation& vpr_routing_annotation() const { return vpr_routing_annotation_; } const openfpga::DeviceRRGSB& device_rr_gsb() const { return device_rr_gsb_; } const openfpga::MuxLibrary& mux_lib() const { return mux_lib_; } + const openfpga::TileDirect& tile_direct() const { return tile_direct_; } const openfpga::ModuleManager& module_graph() const { return module_graph_; } public: /* Public mutators */ openfpga::Arch& mutable_arch() { return arch_; } @@ -56,6 +58,7 @@ class OpenfpgaContext : public Context { openfpga::VprRoutingAnnotation& mutable_vpr_routing_annotation() { return vpr_routing_annotation_; } openfpga::DeviceRRGSB& mutable_device_rr_gsb() { return device_rr_gsb_; } openfpga::MuxLibrary& mutable_mux_lib() { return mux_lib_; } + openfpga::TileDirect& mutable_tile_direct() { return tile_direct_; } openfpga::ModuleManager& mutable_module_graph() { return module_graph_; } private: /* Internal data */ /* Data structure to store information from read_openfpga_arch library */ @@ -67,7 +70,7 @@ class OpenfpgaContext : public Context { /* Naming fix to netlist */ openfpga::VprNetlistAnnotation vpr_netlist_annotation_; - /* TODO: Pin net fix to cluster results */ + /* Pin net fix to cluster results */ openfpga::VprClusteringAnnotation vpr_clustering_annotation_; /* Routing results annotation */ @@ -79,6 +82,9 @@ class OpenfpgaContext : public Context { /* Library of physical implmentation of routing multiplexers */ openfpga::MuxLibrary mux_lib_; + /* Inner/inter-column/row tile direct connections */ + openfpga::TileDirect tile_direct_; + /* Fabric module graph */ openfpga::ModuleManager module_graph_; }; diff --git a/openfpga/src/base/openfpga_link_arch.cpp b/openfpga/src/base/openfpga_link_arch.cpp index 78afc93a2..7ee522b6a 100644 --- a/openfpga/src/base/openfpga_link_arch.cpp +++ b/openfpga/src/base/openfpga_link_arch.cpp @@ -14,6 +14,7 @@ #include "annotate_routing.h" #include "annotate_rr_graph.h" #include "mux_library_builder.h" +#include "build_tile_direct.h" #include "openfpga_link_arch.h" /* Include global variables of VPR */ @@ -106,6 +107,12 @@ void link_arch(OpenfpgaContext& openfpga_context, /* Build multiplexer library */ openfpga_context.mutable_mux_lib() = build_device_mux_library(g_vpr_ctx.device(), const_cast(openfpga_context)); + + /* Build tile direct annotation */ + openfpga_context.mutable_tile_direct() = build_device_tile_direct(g_vpr_ctx.device(), + openfpga_context.arch().arch_direct, + openfpga_context.arch().circuit_lib); + } } /* end namespace openfpga */ diff --git a/openfpga/src/tile_direct/build_tile_direct.cpp b/openfpga/src/tile_direct/build_tile_direct.cpp new file mode 100644 index 000000000..5a40e2ca5 --- /dev/null +++ b/openfpga/src/tile_direct/build_tile_direct.cpp @@ -0,0 +1,367 @@ +/*************************************************************************************** + * This file includes functions that build the point-to-point direct connections + * between tiles (programmable blocks) + ***************************************************************************************/ + +/* Headers from vtrutil library */ +#include "vtr_log.h" +#include "vtr_assert.h" +#include "vtr_time.h" + +/* Headers from openfpgautil library */ +#include "openfpga_tokenizer.h" +#include "openfpga_port.h" +#include "openfpga_port_parser.h" + +/* Headers from vpr library */ +#include "vpr_utils.h" + +#include "build_tile_direct.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/*************************************************************************************** + * Parse the from tile name from the direct definition + * The definition string should be in the following format: + * .[:] + ***************************************************************************************/ +static +std::string parse_direct_tile_name(const std::string& direct_tile_inf) { + StringToken tokenizer(direct_tile_inf); + std::vector tokens = tokenizer.split('.'); + /* We should have only 2 elements and the first is tile name */ + if (2 != tokens.size()) { + VTR_LOG_ERROR("Invalid definition on direct tile '%s'!\n\tExpect .[:].\n", + direct_tile_inf.c_str()); + } + + return tokens[0]; +} + +/*************************************************************************************** + * Check if a pin is located on a given side of physical tile + * If the given side is NUM_SIDES, we will search all the sides + ***************************************************************************************/ +static +bool is_pin_locate_at_physical_tile_side(t_physical_tile_type_ptr physical_tile, + const size_t& pin_width_offset, + const size_t& pin_height_offset, + const size_t& pin_id, + const e_side& pin_side) { + if (NUM_SIDES == pin_side) { + for (size_t side = 0; side < NUM_SIDES; ++side) { + if (true == physical_tile->pinloc[pin_width_offset][pin_height_offset][side][pin_id]) { + return true; + } + } + } + + return physical_tile->pinloc[pin_width_offset][pin_height_offset][size_t(pin_side)][pin_id]; +} + +/*************************************************************************************** + * Find the pin ids of a physical tile based on the given port name, LSB and MSB + ***************************************************************************************/ +static +std::vector find_physical_tile_pin_id(t_physical_tile_type_ptr physical_tile, + const size_t& pin_width_offset, + const size_t& pin_height_offset, + const BasicPort& tile_port, + const e_side& pin_side) { + std::vector pin_ids; + + /* Walk through the port of the tile */ + for (const t_physical_tile_port& physical_tile_port : physical_tile->ports) { + if (std::string(physical_tile_port.name) != tile_port.get_name()) { + continue; + } + /* If the wanted port is invalid, it assumes that we want the full port */ + if (false == tile_port.is_valid()) { + for (int ipin = 0; ipin < physical_tile_port.num_pins; ++ipin) { + int pin_id = physical_tile_port.absolute_first_pin_index + ipin; + VTR_ASSERT(pin_id < physical_tile->num_pins); + /* Check if the pin is located on the wanted side */ + if (true == is_pin_locate_at_physical_tile_side(physical_tile, + pin_width_offset, + pin_height_offset, + pin_id, pin_side)) { + pin_ids.push_back(pin_id); + } + } + continue; + } + /* Find the LSB and MSB of the pin */ + VTR_ASSERT_SAFE(true == tile_port.is_valid()); + BasicPort ref_port(physical_tile_port.name, physical_tile_port.num_pins); + if (false == ref_port.contained(tile_port)) { + VTR_LOG_ERROR("Defined direct port '%s[%lu:%lu]' is out of range for physical port '%s[%lu:%lu]'!\n", + tile_port.get_name().c_str(), + tile_port.get_lsb(), tile_port.get_msb(), + ref_port.get_name().c_str(), + ref_port.get_lsb(), ref_port.get_msb()); + exit(1); + } + for (const size_t& ipin : tile_port.pins()) { + int pin_id = physical_tile_port.absolute_first_pin_index + ipin; + VTR_ASSERT(pin_id < physical_tile->num_pins); + /* Check if the pin is located on the wanted side */ + if (true == is_pin_locate_at_physical_tile_side(physical_tile, + pin_width_offset, + pin_height_offset, + pin_id, pin_side)) { + + pin_ids.push_back(pin_id); + } + } + } + + return pin_ids; +} + +/*************************************************************************************** + * Build the point-to-point direct connections based on + * - original VPR arch definition + * This is limited to the inner-column and inner-row connections + * + * Build the inner-column and inner-row connections + * + * +------+ + * | Tile | + * +------+ +------+ +------+ + * | or | Tile |--->| Tile | + * v +------+ +------+ + * +------+ + * | Tile | + * +------+ + * + ***************************************************************************************/ +static +void build_inner_column_row_tile_direct(TileDirect& tile_direct, + t_direct_inf& vpr_direct, + const DeviceContext& device_ctx, + const ArchDirectId& arch_direct_id) { + /* Get the source tile and pin information */ + std::string from_tile_name = parse_direct_tile_name(std::string(vpr_direct.from_pin)); + PortParser from_tile_port_parser(std::string(vpr_direct.from_pin)); + const BasicPort& from_tile_port = from_tile_port_parser.port(); + + /* Get the sink tile and pin information */ + std::string to_tile_name = parse_direct_tile_name(std::string(vpr_direct.to_pin)); + PortParser to_tile_port_parser(std::string(vpr_direct.to_pin)); + const BasicPort& to_tile_port = to_tile_port_parser.port(); + + /* Walk through the device fabric and find the grid that fit the source */ + for (size_t x = 0; x < device_ctx.grid.width(); ++x) { + for (size_t y = 0; y < device_ctx.grid.height(); ++y) { + /* Bypass empty grid */ + if (true == is_empty_type(device_ctx.grid[x][y].type)) { + continue; + } + + /* Bypass the grid that does not fit the from_tile name */ + if (from_tile_name != std::string(device_ctx.grid[x][y].type->name)) { + continue; + } + + /* Try to find the pin in this tile */ + std::vector from_pins = find_physical_tile_pin_id(device_ctx.grid[x][y].type, + device_ctx.grid[x][y].width_offset, + device_ctx.grid[x][y].height_offset, + from_tile_port, + vpr_direct.from_side); + /* If nothing found, we can continue */ + if (0 == from_pins.size()) { + continue; + } + + /* We should try to the sink grid for inner-column/row direct connections */ + vtr::Point from_grid_coord(x, y); + vtr::Point to_grid_coord(x + vpr_direct.x_offset, y + vpr_direct.y_offset); + if ((to_grid_coord.x() >= device_ctx.grid.width()) + || (to_grid_coord.y() >= device_ctx.grid.height())) { + continue; + } + + /* Bypass the grid that does not fit the from_tile name */ + if (to_tile_name != std::string(device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].type->name)) { + continue; + } + /* Try to find the pin in this tile */ + std::vector to_pins = find_physical_tile_pin_id(device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].type, + device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].width_offset, + device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].height_offset, + to_tile_port, + vpr_direct.to_side); + /* If nothing found, we can continue */ + if (0 == to_pins.size()) { + continue; + } + + /* If from port and to port do not match in sizes, error out */ + if (from_pins.size() != to_pins.size()) { + VTR_LOG_ERROR("From_port '%s[%lu:%lu] of direct '%s' does not match to_port '%s[%lu:%lu]'!\n", + from_tile_port.get_name().c_str(), + from_tile_port.get_lsb(), + from_tile_port.get_msb(), + vpr_direct.name, + to_tile_port.get_name().c_str(), + to_tile_port.get_lsb(), + to_tile_port.get_msb()); + exit(1); + } + + /* Now add the tile direct */ + for (size_t ipin = 0; ipin < from_pins.size(); ++ipin) { + TileDirectId tile_direct_id = tile_direct.add_direct(device_ctx.grid[x][y].type, + from_grid_coord, vpr_direct.from_side, from_pins[ipin], + device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].type, + to_grid_coord, vpr_direct.to_side, to_pins[ipin]); + tile_direct.set_arch_direct_id(tile_direct_id, arch_direct_id); + } + + } + } +} + +/*************************************************************************************** + * Build the point-to-point direct connections based on + * - OpenFPGA arch definition + * This is limited to the inter-column and inter-row connections + * + * Build the inter-column and inter-row connections + * + * +------+ +------+ + * | Tile | | Tile | + * +------+ +------+ + * | ^ + * | | + * +------------- + * + * +------+ + * | Tile |-------+ + * +------+ | + * | + * +------+ | + * | Tile |<------+ + * +------+ + * + * + ***************************************************************************************/ +static +void build_inter_column_row_tile_direct(TileDirect& tile_direct, + t_direct_inf& vpr_direct, + const DeviceContext& device_ctx, + const ArchDirect& arch_direct, + const ArchDirectId& arch_direct_id, + const CircuitLibrary& circuit_lib) { + /* Get the source tile and pin information */ + std::string from_tile_name = parse_direct_tile_name(std::string(vpr_direct.from_pin)); + PortParser from_tile_port_parser(std::string(vpr_direct.from_pin)); + const BasicPort& from_tile_port = from_tile_port_parser.port(); + + /* Get the sink tile and pin information */ + std::string to_tile_name = parse_direct_tile_name(std::string(vpr_direct.to_pin)); + PortParser to_tile_port_parser(std::string(vpr_direct.to_pin)); + const BasicPort& to_tile_port = to_tile_port_parser.port(); + + /* Walk through the device fabric and find the grid that fit the source */ + for (size_t x = 0; x < device_ctx.grid.width(); ++x) { + for (size_t y = 0; y < device_ctx.grid.height(); ++y) { + /* Bypass empty grid */ + if (true == is_empty_type(device_ctx.grid[x][y].type)) { + continue; + } + + /* Bypass the grid that does not fit the from_tile name */ + if (from_tile_name != std::string(device_ctx.grid[x][y].type->name)) { + continue; + } + + /* Try to find the pin in this tile */ + std::vector from_pins = find_physical_tile_pin_id(device_ctx.grid[x][y].type, + device_ctx.grid[x][y].width_offset, + device_ctx.grid[x][y].height_offset, + from_tile_port, + vpr_direct.from_side); + /* If nothing found, we can continue */ + if (0 == from_pins.size()) { + continue; + } + + /* We should try to the sink grid for inner-column/row direct connections */ + vtr::Point from_grid_coord(x, y); + vtr::Point to_grid_coord(x + vpr_direct.x_offset, y + vpr_direct.y_offset); + if ((to_grid_coord.x() >= device_ctx.grid.width()) + || (to_grid_coord.y() >= device_ctx.grid.height())) { + continue; + } + + /* Bypass the grid that does not fit the from_tile name */ + if (to_tile_name != std::string(device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].type->name)) { + continue; + } + /* Try to find the pin in this tile */ + std::vector to_pins = find_physical_tile_pin_id(device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].type, + device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].width_offset, + device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].height_offset, + to_tile_port, + vpr_direct.to_side); + /* If nothing found, we can continue */ + if (0 == to_pins.size()) { + continue; + } + + /* If from port and to port do not match in sizes, error out */ + if (from_pins.size() != to_pins.size()) { + VTR_LOG_ERROR("From_port '%s[%lu:%lu] of direct '%s' does not match to_port '%s[%lu:%lu]'!\n", + from_tile_port.get_name().c_str(), + from_tile_port.get_lsb(), + from_tile_port.get_msb(), + vpr_direct.name, + to_tile_port.get_name().c_str(), + to_tile_port.get_lsb(), + to_tile_port.get_msb()); + exit(1); + } + + /* Now add the tile direct */ + for (size_t ipin = 0; ipin < from_pins.size(); ++ipin) { + TileDirectId tile_direct_id = tile_direct.add_direct(device_ctx.grid[x][y].type, + from_grid_coord, vpr_direct.from_side, from_pins[ipin], + device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].type, + to_grid_coord, vpr_direct.to_side, to_pins[ipin]); + tile_direct.set_arch_direct_id(tile_direct_id, arch_direct_id); + } + + } + } +} + +/*************************************************************************************** + * Top-level functions that build the point-to-point direct connections + * between tiles (programmable blocks) + ***************************************************************************************/ +TileDirect build_device_tile_direct(const DeviceContext& device_ctx, + const ArchDirect& arch_direct, + const CircuitLibrary& circuit_lib) { + vtr::ScopedStartFinishTimer timer("Build the annotation about direct connection between tiles"); + + TileDirect tile_direct; + + /* Walk through each direct definition in the VPR arch */ + for (int idirect = 0; idirect < device_ctx.arch->num_directs; ++idirect) { + ArchDirectId arch_direct_id = arch_direct.direct(std::string(device_ctx.arch->Directs[idirect].name)); + VTR_ASSERT(ArchDirectId::INVALID() != arch_direct_id); + /* Build from original VPR arch definition */ + build_inner_column_row_tile_direct(tile_direct, + device_ctx.arch->Directs[idirect], + device_ctx, + arch_direct_id); + /* TODO: Build from OpenFPGA arch definition */ + } + + return tile_direct; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/tile_direct/build_tile_direct.h b/openfpga/src/tile_direct/build_tile_direct.h new file mode 100644 index 000000000..08d0397ce --- /dev/null +++ b/openfpga/src/tile_direct/build_tile_direct.h @@ -0,0 +1,25 @@ +#ifndef BUILD_TILE_DIRECT_H +#define BUILD_TILE_DIRECT_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "vpr_context.h" +#include "arch_direct.h" +#include "tile_direct.h" +#include "circuit_library.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +TileDirect build_device_tile_direct(const DeviceContext& device_ctx, + const ArchDirect& arch_direct, + const CircuitLibrary& circuit_lib); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/tile_direct/tile_direct.cpp b/openfpga/src/tile_direct/tile_direct.cpp index 0face1b7e..d0d1f6122 100644 --- a/openfpga/src/tile_direct/tile_direct.cpp +++ b/openfpga/src/tile_direct/tile_direct.cpp @@ -63,6 +63,12 @@ e_side TileDirect::to_tile_side(const TileDirectId& direct_id) const { return to_tile_sides_[direct_id]; } +ArchDirectId TileDirect::arch_direct(const TileDirectId& direct_id) const { + /* Validate the direct_id */ + VTR_ASSERT(valid_direct_id(direct_id)); + return arch_directs_[direct_id]; +} + /****************************************************************************** * Private Mutators ******************************************************************************/ @@ -89,9 +95,18 @@ TileDirectId TileDirect::add_direct(t_physical_tile_type_ptr from_tile, to_tile_sides_.push_back(to_tile_side); to_tile_pins_.push_back(to_tile_pin); + arch_directs_.emplace_back(ArchDirectId::INVALID()); + return direct; } +void TileDirect::set_arch_direct_id(const TileDirectId& tile_direct_id, + const ArchDirectId& arch_direct_id) { + /* Validate the direct_id */ + VTR_ASSERT(valid_direct_id(tile_direct_id)); + arch_directs_[tile_direct_id] = arch_direct_id; +} + /****************************************************************************** * Private validators/invalidators ******************************************************************************/ diff --git a/openfpga/src/tile_direct/tile_direct.h b/openfpga/src/tile_direct/tile_direct.h index 562b559ca..d8d0e180f 100644 --- a/openfpga/src/tile_direct/tile_direct.h +++ b/openfpga/src/tile_direct/tile_direct.h @@ -11,6 +11,9 @@ /* Headers from readarch library */ #include "physical_types.h" +/* Headers from readarchopenfpga library */ +#include "arch_direct.h" + #include "tile_direct_fwd.h" /* Begin namespace openfpga */ @@ -38,6 +41,7 @@ class TileDirect { vtr::Point to_tile_coordinate(const TileDirectId& direct_id) const; e_side to_tile_side(const TileDirectId& direct_id) const; size_t to_tile_pin(const TileDirectId& direct_id) const; + ArchDirectId arch_direct(const TileDirectId& direct_id) const; public: /* Public mutators */ TileDirectId add_direct(t_physical_tile_type_ptr from_tile, const vtr::Point& from_tile_coord, @@ -47,6 +51,8 @@ class TileDirect { const vtr::Point& to_tile_coord, const e_side& to_tile_side, const size_t& to_tile_pin); + void set_arch_direct_id(const TileDirectId& tile_direct_id, + const ArchDirectId& arch_direct_id); public: /* Public validators/invalidators */ bool valid_direct_id(const TileDirectId& direct_id) const; private: /* Internal Data */ @@ -71,6 +77,8 @@ class TileDirect { vtr::vector> to_tile_coords_; vtr::vector to_tile_sides_; vtr::vector to_tile_pins_; + + vtr::vector arch_directs_; }; } /* End namespace openfpga*/ From 539f13720a1f0d1e6968d6983c72486b3f54866c Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 15 Feb 2020 13:42:53 -0700 Subject: [PATCH 142/645] tile direct supports inter-column/inter-row direct connections --- openfpga/src/base/openfpga_link_arch.cpp | 27 +- .../src/fabric/build_top_module_directs.cpp | 151 ++++++ .../src/fabric/build_top_module_directs.h | 33 ++ .../src/tile_direct/build_tile_direct.cpp | 471 +++++++++++++++--- openfpga/src/tile_direct/build_tile_direct.h | 3 +- openfpga/src/tile_direct/tile_direct.cpp | 18 +- openfpga/src/tile_direct/tile_direct.h | 8 +- 7 files changed, 590 insertions(+), 121 deletions(-) create mode 100644 openfpga/src/fabric/build_top_module_directs.cpp create mode 100644 openfpga/src/fabric/build_top_module_directs.h diff --git a/openfpga/src/base/openfpga_link_arch.cpp b/openfpga/src/base/openfpga_link_arch.cpp index 7ee522b6a..567ad2e08 100644 --- a/openfpga/src/base/openfpga_link_arch.cpp +++ b/openfpga/src/base/openfpga_link_arch.cpp @@ -53,7 +53,7 @@ bool is_vpr_rr_graph_supported(const RRGraph& rr_graph) { * - physical pb_graph nodes and pb_graph pins * - circuit models for global routing architecture *******************************************************************/ -void link_arch(OpenfpgaContext& openfpga_context, +void link_arch(OpenfpgaContext& openfpga_ctx, const Command& cmd, const CommandContext& cmd_context) { vtr::ScopedStartFinishTimer timer("Link OpenFPGA architecture to VPR architecture"); @@ -65,8 +65,8 @@ void link_arch(OpenfpgaContext& openfpga_context, * - mode selection bits for pb_type and pb interconnect * - circuit models for pb_type and pb interconnect */ - annotate_pb_types(g_vpr_ctx.device(), openfpga_context.arch(), - openfpga_context.mutable_vpr_device_annotation(), + annotate_pb_types(g_vpr_ctx.device(), openfpga_ctx.arch(), + openfpga_ctx.mutable_vpr_device_annotation(), cmd_context.option_enable(cmd, opt_verbose)); /* Annotate pb_graph_nodes @@ -75,21 +75,21 @@ void link_arch(OpenfpgaContext& openfpga_context, * - Bind pins from operating pb_graph_node to their physical pb_graph_node pins */ annotate_pb_graph(g_vpr_ctx.device(), - openfpga_context.mutable_vpr_device_annotation(), + openfpga_ctx.mutable_vpr_device_annotation(), cmd_context.option_enable(cmd, opt_verbose)); /* Annotate routing architecture to circuit library */ annotate_rr_graph_circuit_models(g_vpr_ctx.device(), - openfpga_context.arch(), - openfpga_context.mutable_vpr_device_annotation(), + openfpga_ctx.arch(), + openfpga_ctx.mutable_vpr_device_annotation(), cmd_context.option_enable(cmd, opt_verbose)); /* Annotate net mapping to each rr_node */ - openfpga_context.mutable_vpr_routing_annotation().init(g_vpr_ctx.device().rr_graph); + openfpga_ctx.mutable_vpr_routing_annotation().init(g_vpr_ctx.device().rr_graph); annotate_rr_node_nets(g_vpr_ctx.device(), g_vpr_ctx.clustering(), g_vpr_ctx.routing(), - openfpga_context.mutable_vpr_routing_annotation(), + openfpga_ctx.mutable_vpr_routing_annotation(), cmd_context.option_enable(cmd, opt_verbose)); /* Build the routing graph annotation @@ -101,17 +101,16 @@ void link_arch(OpenfpgaContext& openfpga_context, } annotate_device_rr_gsb(g_vpr_ctx.device(), - openfpga_context.mutable_device_rr_gsb(), + openfpga_ctx.mutable_device_rr_gsb(), cmd_context.option_enable(cmd, opt_verbose)); /* Build multiplexer library */ - openfpga_context.mutable_mux_lib() = build_device_mux_library(g_vpr_ctx.device(), - const_cast(openfpga_context)); + openfpga_ctx.mutable_mux_lib() = build_device_mux_library(g_vpr_ctx.device(), + const_cast(openfpga_ctx)); /* Build tile direct annotation */ - openfpga_context.mutable_tile_direct() = build_device_tile_direct(g_vpr_ctx.device(), - openfpga_context.arch().arch_direct, - openfpga_context.arch().circuit_lib); + openfpga_ctx.mutable_tile_direct() = build_device_tile_direct(g_vpr_ctx.device(), + openfpga_ctx.arch().arch_direct); } diff --git a/openfpga/src/fabric/build_top_module_directs.cpp b/openfpga/src/fabric/build_top_module_directs.cpp new file mode 100644 index 000000000..63d4a1882 --- /dev/null +++ b/openfpga/src/fabric/build_top_module_directs.cpp @@ -0,0 +1,151 @@ +/******************************************************************** + * This file includes functions that are used to add module nets + * for direct connections between CLBs/heterogeneous blocks + * in the top-level module of a FPGA fabric + *******************************************************************/ +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" + +/* Headers from openfpgautil library */ +#include "openfpga_port.h" + +/* Headers from vpr library */ +#include "vpr_utils.h" + +#include "openfpga_reserved_words.h" +#include "openfpga_naming.h" +#include "module_manager_utils.h" + +#include "build_top_module_directs.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Add module net for one direction connection between two CLBs or + * two grids + * This function will + * 1. find the pin id and port id of the source clb port in module manager + * 2. find the pin id and port id of the destination clb port in module manager + * 3. add a direct connection module to the top module + * 4. add a first module net and configure its source and sink, + * in order to connect the source pin to the input of the top module + * 4. add a second module net and configure its source and sink, + * in order to connect the sink pin to the output of the top module + *******************************************************************/ +static +void add_module_nets_tile_direct_connection(ModuleManager& module_manager, + const ModuleId& top_module, + const CircuitLibrary& circuit_lib, + const DeviceGrid& grids, + const vtr::Matrix& grid_instance_ids, + const TileDirect& tile_direct, + const TileDirectId& tile_direct_id, + const ArchDirect& arch_direct) { + vtr::Point device_size(grids.width(), grids.height()); + + /* Find the module name of source clb */ + vtr::Point src_clb_coord = tile_direct.from_tile_coordinate(tile_direct_id); + t_physical_tile_type_ptr src_grid_type = grids[src_clb_coord.x()][src_clb_coord.y()].type; + e_side src_grid_border_side = find_grid_border_side(device_size, src_clb_coord); + std::string src_module_name_prefix(GRID_MODULE_NAME_PREFIX); + std::string src_module_name = generate_grid_block_module_name(src_module_name_prefix, std::string(src_grid_type->name), is_io_type(src_grid_type), src_grid_border_side); + ModuleId src_grid_module = module_manager.find_module(src_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(src_grid_module)); + /* Record the instance id */ + size_t src_grid_instance = grid_instance_ids[src_clb_coord.x()][src_clb_coord.y()]; + + /* Find the module name of sink clb */ + vtr::Point des_clb_coord = tile_direct.to_tile_coordinate(tile_direct_id); + t_physical_tile_type_ptr sink_grid_type = grids[des_clb_coord.x()][des_clb_coord.y()].type; + e_side sink_grid_border_side = find_grid_border_side(device_size, des_clb_coord); + std::string sink_module_name_prefix(GRID_MODULE_NAME_PREFIX); + std::string sink_module_name = generate_grid_block_module_name(sink_module_name_prefix, std::string(sink_grid_type->name), is_io_type(sink_grid_type), sink_grid_border_side); + ModuleId sink_grid_module = module_manager.find_module(sink_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(sink_grid_module)); + /* Record the instance id */ + size_t sink_grid_instance = grid_instance_ids[des_clb_coord.x()][des_clb_coord.y()]; + + /* Find the module id of a direct connection module */ + CircuitModelId direct_circuit_model = arch_direct.circuit_model(tile_direct.arch_direct(tile_direct_id)); + std::string direct_module_name = circuit_lib.model_name(direct_circuit_model); + ModuleId direct_module = module_manager.find_module(direct_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(direct_module)); + + /* Find inputs and outputs of the direct circuit module */ + std::vector direct_input_ports = circuit_lib.model_ports_by_type(direct_circuit_model, CIRCUIT_MODEL_PORT_INPUT, true); + VTR_ASSERT(1 == direct_input_ports.size()); + ModulePortId direct_input_port_id = module_manager.find_module_port(direct_module, circuit_lib.port_prefix(direct_input_ports[0])); + VTR_ASSERT(true == module_manager.valid_module_port_id(direct_module, direct_input_port_id)); + VTR_ASSERT(1 == module_manager.module_port(direct_module, direct_input_port_id).get_width()); + + std::vector direct_output_ports = circuit_lib.model_ports_by_type(direct_circuit_model, CIRCUIT_MODEL_PORT_OUTPUT, true); + VTR_ASSERT(1 == direct_output_ports.size()); + ModulePortId direct_output_port_id = module_manager.find_module_port(direct_module, circuit_lib.port_prefix(direct_output_ports[0])); + VTR_ASSERT(true == module_manager.valid_module_port_id(direct_module, direct_output_port_id)); + VTR_ASSERT(1 == module_manager.module_port(direct_module, direct_output_port_id).get_width()); + + /* Generate the pin name of source port/pin in the grid */ + e_side src_pin_grid_side = tile_direct.from_tile_side(tile_direct_id); + size_t src_tile_pin = tile_direct.from_tile_pin(tile_direct_id); + size_t src_pin_width = grids[src_clb_coord.x()][src_clb_coord.y()].type->pin_width_offset[src_tile_pin]; + size_t src_pin_height = grids[src_clb_coord.x()][src_clb_coord.y()].type->pin_height_offset[src_tile_pin]; + std::string src_port_name = generate_grid_port_name(src_clb_coord, src_pin_width, src_pin_height, src_pin_grid_side, src_tile_pin, false); + ModulePortId src_port_id = module_manager.find_module_port(src_grid_module, src_port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(src_grid_module, src_port_id)); + VTR_ASSERT(1 == module_manager.module_port(src_grid_module, src_port_id).get_width()); + + /* Generate the pin name of sink port/pin in the grid */ + e_side sink_pin_grid_side = tile_direct.to_tile_side(tile_direct_id); + size_t sink_tile_pin = tile_direct.to_tile_pin(tile_direct_id); + size_t sink_pin_width = grids[des_clb_coord.x()][des_clb_coord.y()].type->pin_width_offset[src_tile_pin]; + size_t sink_pin_height = grids[des_clb_coord.x()][des_clb_coord.y()].type->pin_height_offset[src_tile_pin]; + + std::string sink_port_name = generate_grid_port_name(des_clb_coord, sink_pin_width, sink_pin_height, sink_pin_grid_side, sink_tile_pin, false); + ModulePortId sink_port_id = module_manager.find_module_port(sink_grid_module, sink_port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(sink_grid_module, sink_port_id)); + VTR_ASSERT(1 == module_manager.module_port(sink_grid_module, sink_port_id).get_width()); + + /* Add a submodule of direct connection module to the top-level module */ + size_t direct_instance_id = module_manager.num_instance(top_module, direct_module); + module_manager.add_child_module(top_module, direct_module); + + /* Create the 1st module net */ + ModuleNetId net_direct_src = module_manager.create_module_net(top_module); + /* Connect the wire between src_pin of clb and direct_instance input*/ + module_manager.add_module_net_source(top_module, net_direct_src, src_grid_module, src_grid_instance, src_port_id, 0); + module_manager.add_module_net_sink(top_module, net_direct_src, direct_module, direct_instance_id, direct_input_port_id, 0); + + /* Create the 2nd module net */ + ModuleNetId net_direct_sink = module_manager.create_module_net(top_module); + /* Connect the wire between direct_instance output and sink_pin of clb */ + module_manager.add_module_net_source(top_module, net_direct_sink, direct_module, direct_instance_id, direct_output_port_id, 0); + module_manager.add_module_net_sink(top_module, net_direct_sink, sink_grid_module, sink_grid_instance, sink_port_id, 0); +} + +/******************************************************************** + * Add module net of clb-to-clb direct connections to module manager + * Note that the direct connections are not limited to CLBs only. + * It can be more generic and thus cover all the grid types, + * such as heterogeneous blocks + *******************************************************************/ +void add_top_module_nets_tile_direct_connections(ModuleManager& module_manager, + const ModuleId& top_module, + const CircuitLibrary& circuit_lib, + const DeviceGrid& grids, + const vtr::Matrix& grid_instance_ids, + const TileDirect& tile_direct, + const ArchDirect& arch_direct) { + + for (const TileDirectId& tile_direct_id : tile_direct.directs()) { + add_module_nets_tile_direct_connection(module_manager, top_module, circuit_lib, + grids, grid_instance_ids, + tile_direct, tile_direct_id, + arch_direct); + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fabric/build_top_module_directs.h b/openfpga/src/fabric/build_top_module_directs.h new file mode 100644 index 000000000..e9855db70 --- /dev/null +++ b/openfpga/src/fabric/build_top_module_directs.h @@ -0,0 +1,33 @@ +#ifndef BUILD_TOP_MODULE_DIRECTS_H +#define BUILD_TOP_MODULE_DIRECTS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include "vtr_geometry.h" +#include "vtr_ndmatrix.h" +#include "arch_direct.h" +#include "tile_direct.h" +#include "device_grid.h" +#include "module_manager.h" +#include "circuit_library.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void add_top_module_nets_tile_direct_connections(ModuleManager& module_manager, + const ModuleId& top_module, + const CircuitLibrary& circuit_lib, + const DeviceGrid& grids, + const vtr::Matrix& grid_instance_ids, + const TileDirect& tile_direct, + const ArchDirect& arch_direct); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/tile_direct/build_tile_direct.cpp b/openfpga/src/tile_direct/build_tile_direct.cpp index 5a40e2ca5..a99370a0b 100644 --- a/openfpga/src/tile_direct/build_tile_direct.cpp +++ b/openfpga/src/tile_direct/build_tile_direct.cpp @@ -119,26 +119,250 @@ std::vector find_physical_tile_pin_id(t_physical_tile_type_ptr physical_ return pin_ids; } +/******************************************************************** + * Check if the grid coorindate given is in the device grid range + *******************************************************************/ +static +bool is_grid_coordinate_exist_in_device(const DeviceGrid& device_grid, + const vtr::Point& grid_coordinate) { + return (grid_coordinate < vtr::Point(device_grid.width(), device_grid.height())); +} + +/******************************************************************** + * Find the coordinate of a grid in a specific column + * with a given type + * This function will return the coordinate of the grid that satifies + * the type requirement + *******************************************************************/ +static +vtr::Point find_grid_coordinate_given_type(const DeviceGrid& grids, + const std::vector>& candidate_coords, + const std::string& wanted_grid_type_name) { + for (vtr::Point coord : candidate_coords) { + /* If the next column is not longer in device range, we can return */ + if (false == is_grid_coordinate_exist_in_device(grids, coord)) { + continue; + } + if (wanted_grid_type_name == std::string(grids[coord.x()][coord.y()].type->name)) { + return coord; + } + } + /* Return an valid coordinate */ + return vtr::Point(grids.width(), grids.height()); +} + +/******************************************************************** + * Find the coordinate of the destination clb/heterogeneous block + * considering intra column/row direct connections in core grids + *******************************************************************/ +static +vtr::Point find_inter_direct_destination_coordinate(const DeviceGrid& grids, + const vtr::Point& src_coord, + const std::string des_tile_type_name, + const ArchDirect& arch_direct, + const ArchDirectId& arch_direct_id) { + vtr::Point des_coord(grids.width(), grids.height()); + + std::vector first_search_space; + std::vector second_search_space; + + /* Cross column connection from Bottom to Top on Right + * The next column may NOT have the grid type we want! + * Think about heterogeneous architecture! + * Our search space will start from the next column + * and ends at the RIGHT side of fabric + */ + if (INTER_COLUMN == arch_direct.type(arch_direct_id)) { + if (POSITIVE_DIR == arch_direct.x_dir(arch_direct_id)) { + /* Our first search space will be in x-direction: + * + * x ... nx + * +-----+ + * |Grid | -----> + * +-----+ + */ + for (size_t ix = src_coord.x() + 1; ix < grids.width() - 1; ++ix) { + first_search_space.push_back(ix); + } + } else { + VTR_ASSERT(NEGATIVE_DIR == arch_direct.x_dir(arch_direct_id)); + /* Our first search space will be in x-direction: + * + * 1 ... x + * +-----+ + * < -------|Grid | + * +-----+ + */ + for (size_t ix = src_coord.x() - 1; ix >= 1; --ix) { + first_search_space.push_back(ix); + } + } + + /* Our second search space will be in y-direction: + * + * +------+ + * | Grid | ny + * +------+ + * | . + * | . + * v . + * +------+ + * | Grid | 1 + * +------+ + */ + for (size_t iy = 1 ; iy < grids.height() - 1; ++iy) { + second_search_space.push_back(iy); + } + + /* For negative direction, our second search space will be in y-direction: + * + * +------+ + * | Grid | ny + * +------+ + * ^ . + * | . + * | . + * +------+ + * | Grid | 1 + * +------+ + */ + if (NEGATIVE_DIR == arch_direct.y_dir(arch_direct_id)) { + std::reverse(second_search_space.begin(), second_search_space.end()); + } + } + + + /* Cross row connection from Bottom to Top on Right + * The next column may NOT have the grid type we want! + * Think about heterogeneous architecture! + * Our search space will start from the next column + * and ends at the RIGHT side of fabric + */ + if (INTER_ROW == arch_direct.type(arch_direct_id)) { + if (POSITIVE_DIR == arch_direct.y_dir(arch_direct_id)) { + /* Our first search space will be in y-direction: + * + * +------+ + * | Grid | ny + * +------+ + * ^ . + * | . + * | . + * +------+ + * | Grid | y + * +------+ + */ + for (size_t iy = src_coord.y() + 1; iy < grids.height() - 1; ++iy) { + first_search_space.push_back(iy); + } + } else { + VTR_ASSERT(NEGATIVE_DIR == arch_direct.y_dir(arch_direct_id)); + /* For negative y-direction, + * Our first search space will be in y-direction: + * + * +------+ + * | Grid | ny + * +------+ + * | . + * | . + * v . + * +------+ + * | Grid | y + * +------+ + */ + for (size_t iy = src_coord.y() - 1; iy >= 1; --iy) { + first_search_space.push_back(iy); + } + } + + /* Our second search space will be in x-direction: + * + * 1 ... nx + * +------+ +------+ + * | Grid |------>| Grid | + * +------+ +------+ + */ + for (size_t ix = 1 ; ix < grids.width() - 1; ++ix) { + second_search_space.push_back(ix); + } + + /* For negative direction, + * our second search space will be in x-direction: + * + * 1 ... nx + * +------+ +------+ + * | Grid |<------| Grid | + * +------+ +------+ + */ + if (NEGATIVE_DIR == arch_direct.x_dir(arch_direct_id)) { + std::reverse(second_search_space.begin(), second_search_space.end()); + } + } + + for (size_t ix : first_search_space) { + std::vector> next_col_row_coords; + for (size_t iy : second_search_space) { + if (INTER_COLUMN == arch_direct.type(arch_direct_id)) { + next_col_row_coords.push_back(vtr::Point(ix, iy)); + } else { + VTR_ASSERT(INTER_ROW == arch_direct.type(arch_direct_id)); + /* For cross-row connection, our search space is flipped */ + next_col_row_coords.push_back(vtr::Point(iy, ix)); + } + } + vtr::Point des_coord_cand = find_grid_coordinate_given_type(grids, next_col_row_coords, des_tile_type_name); + /* For a valid coordinate, we can return */ + if (true == is_grid_coordinate_exist_in_device(grids, des_coord_cand)) { + return des_coord_cand; + } + } + return des_coord; +} + +/*************************************************************************************** + * Report error for port matching failure + ***************************************************************************************/ +static +void report_direct_from_port_and_to_port_mismatch(const t_direct_inf& vpr_direct, + const BasicPort& from_tile_port, + const BasicPort& to_tile_port) { + VTR_LOG_ERROR("From_port '%s[%lu:%lu] of direct '%s' does not match to_port '%s[%lu:%lu]'!\n", + from_tile_port.get_name().c_str(), + from_tile_port.get_lsb(), + from_tile_port.get_msb(), + vpr_direct.name, + to_tile_port.get_name().c_str(), + to_tile_port.get_lsb(), + to_tile_port.get_msb()); +} + /*************************************************************************************** * Build the point-to-point direct connections based on * - original VPR arch definition * This is limited to the inner-column and inner-row connections + * Note that the direct connections are not limited to CLBs only. + * It can be more generic and thus cover all the grid types, + * such as heterogeneous blocks * - * Build the inner-column and inner-row connections - * - * +------+ - * | Tile | - * +------+ +------+ +------+ - * | or | Tile |--->| Tile | - * v +------+ +------+ + * This function supports the following type of direct connection: + * 1. Direct connection between tiles in the same column or row + * +------+ +------+ + * | | | | + * | Tile |----->| Tile | + * | | | | + * +------+ +------+ + * | direction connection + * v * +------+ + * | | * | Tile | + * | | * +------+ * ***************************************************************************************/ static void build_inner_column_row_tile_direct(TileDirect& tile_direct, - t_direct_inf& vpr_direct, + const t_direct_inf& vpr_direct, const DeviceContext& device_ctx, const ArchDirectId& arch_direct_id) { /* Get the source tile and pin information */ @@ -166,10 +390,10 @@ void build_inner_column_row_tile_direct(TileDirect& tile_direct, /* Try to find the pin in this tile */ std::vector from_pins = find_physical_tile_pin_id(device_ctx.grid[x][y].type, - device_ctx.grid[x][y].width_offset, - device_ctx.grid[x][y].height_offset, - from_tile_port, - vpr_direct.from_side); + device_ctx.grid[x][y].width_offset, + device_ctx.grid[x][y].height_offset, + from_tile_port, + vpr_direct.from_side); /* If nothing found, we can continue */ if (0 == from_pins.size()) { continue; @@ -178,8 +402,7 @@ void build_inner_column_row_tile_direct(TileDirect& tile_direct, /* We should try to the sink grid for inner-column/row direct connections */ vtr::Point from_grid_coord(x, y); vtr::Point to_grid_coord(x + vpr_direct.x_offset, y + vpr_direct.y_offset); - if ((to_grid_coord.x() >= device_ctx.grid.width()) - || (to_grid_coord.y() >= device_ctx.grid.height())) { + if (false == is_grid_coordinate_exist_in_device(device_ctx.grid, to_grid_coord)) { continue; } @@ -200,61 +423,60 @@ void build_inner_column_row_tile_direct(TileDirect& tile_direct, /* If from port and to port do not match in sizes, error out */ if (from_pins.size() != to_pins.size()) { - VTR_LOG_ERROR("From_port '%s[%lu:%lu] of direct '%s' does not match to_port '%s[%lu:%lu]'!\n", - from_tile_port.get_name().c_str(), - from_tile_port.get_lsb(), - from_tile_port.get_msb(), - vpr_direct.name, - to_tile_port.get_name().c_str(), - to_tile_port.get_lsb(), - to_tile_port.get_msb()); + report_direct_from_port_and_to_port_mismatch(vpr_direct, from_tile_port, to_tile_port); exit(1); } /* Now add the tile direct */ for (size_t ipin = 0; ipin < from_pins.size(); ++ipin) { - TileDirectId tile_direct_id = tile_direct.add_direct(device_ctx.grid[x][y].type, - from_grid_coord, vpr_direct.from_side, from_pins[ipin], - device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].type, + TileDirectId tile_direct_id = tile_direct.add_direct(from_grid_coord, vpr_direct.from_side, from_pins[ipin], to_grid_coord, vpr_direct.to_side, to_pins[ipin]); tile_direct.set_arch_direct_id(tile_direct_id, arch_direct_id); } - } } } -/*************************************************************************************** +/******************************************************************** * Build the point-to-point direct connections based on * - OpenFPGA arch definition * This is limited to the inter-column and inter-row connections * - * Build the inter-column and inter-row connections + * Note that the direct connections are not limited to CLBs only. + * It can be more generic and thus cover all the grid types, + * such as heterogeneous blocks * - * +------+ +------+ - * | Tile | | Tile | - * +------+ +------+ - * | ^ - * | | - * +------------- + * This function supports the following type of direct connection: * - * +------+ - * | Tile |-------+ - * +------+ | - * | - * +------+ | - * | Tile |<------+ - * +------+ - * + * 1. Direct connections across columns and rows + * +------+ + * | | + * | v + * +------+ | +------+ + * | | | | | + * | Grid | | | Grid | + * | | | | | + * +------+ | +------+ + * | + * +------+ | +------+ + * | | | | | + * | Grid | | | Grid | + * | | | | | + * +------+ | +------+ + * | | + * +------+ * - ***************************************************************************************/ + * Note that: this will only apply to the core grids! + * I/Os or any blocks on the border of fabric are NOT supported! + * + *******************************************************************/ static void build_inter_column_row_tile_direct(TileDirect& tile_direct, - t_direct_inf& vpr_direct, + const t_direct_inf& vpr_direct, const DeviceContext& device_ctx, const ArchDirect& arch_direct, - const ArchDirectId& arch_direct_id, - const CircuitLibrary& circuit_lib) { + const ArchDirectId& arch_direct_id) { + /* Get the source tile and pin information */ std::string from_tile_name = parse_direct_tile_name(std::string(vpr_direct.from_pin)); PortParser from_tile_port_parser(std::string(vpr_direct.from_pin)); @@ -265,42 +487,63 @@ void build_inter_column_row_tile_direct(TileDirect& tile_direct, PortParser to_tile_port_parser(std::string(vpr_direct.to_pin)); const BasicPort& to_tile_port = to_tile_port_parser.port(); - /* Walk through the device fabric and find the grid that fit the source */ - for (size_t x = 0; x < device_ctx.grid.width(); ++x) { - for (size_t y = 0; y < device_ctx.grid.height(); ++y) { - /* Bypass empty grid */ - if (true == is_empty_type(device_ctx.grid[x][y].type)) { - continue; + /* Go through the direct connection list, see if we need intra-column/row connection here */ + if ( (INTER_COLUMN != arch_direct.type(arch_direct_id)) + && (INTER_ROW != arch_direct.type(arch_direct_id))) { + return; + } + /* For cross-column connection, we will search the first valid grid in each column + * from y = 1 to y = ny + * + * +------+ + * | Grid | y=ny + * +------+ + * ^ + * | search direction (when y_dir is negative) + * ... + * | + * +------+ + * | Grid | y=1 + * +------+ + * + */ + if (INTER_COLUMN == arch_direct.type(arch_direct_id)) { + for (size_t ix = 1; ix < device_ctx.grid.width() - 1; ++ix) { + std::vector> next_col_src_grid_coords; + /* For negative y- direction, we should start from y = ny */ + for (size_t iy = 1; iy < device_ctx.grid.height() - 1; ++iy) { + next_col_src_grid_coords.push_back(vtr::Point(ix, iy)); + } + /* For positive y- direction, we should start from y = 1 */ + if (POSITIVE_DIR == arch_direct.y_dir(arch_direct_id)) { + std::reverse(next_col_src_grid_coords.begin(), next_col_src_grid_coords.end()); } /* Bypass the grid that does not fit the from_tile name */ - if (from_tile_name != std::string(device_ctx.grid[x][y].type->name)) { + vtr::Point from_grid_coord = find_grid_coordinate_given_type(device_ctx.grid, next_col_src_grid_coords, from_tile_name); + /* Skip if we do not have a valid coordinate for source CLB/heterogeneous block */ + if (false == is_grid_coordinate_exist_in_device(device_ctx.grid, from_grid_coord)) { continue; } /* Try to find the pin in this tile */ - std::vector from_pins = find_physical_tile_pin_id(device_ctx.grid[x][y].type, - device_ctx.grid[x][y].width_offset, - device_ctx.grid[x][y].height_offset, + std::vector from_pins = find_physical_tile_pin_id(device_ctx.grid[from_grid_coord.x()][from_grid_coord.y()].type, + device_ctx.grid[from_grid_coord.x()][from_grid_coord.y()].width_offset, + device_ctx.grid[from_grid_coord.x()][from_grid_coord.y()].height_offset, from_tile_port, vpr_direct.from_side); /* If nothing found, we can continue */ if (0 == from_pins.size()) { continue; } - - /* We should try to the sink grid for inner-column/row direct connections */ - vtr::Point from_grid_coord(x, y); - vtr::Point to_grid_coord(x + vpr_direct.x_offset, y + vpr_direct.y_offset); - if ((to_grid_coord.x() >= device_ctx.grid.width()) - || (to_grid_coord.y() >= device_ctx.grid.height())) { - continue; + + /* For a valid coordinate, we can find the coordinate of the destination clb */ + vtr::Point to_grid_coord = find_inter_direct_destination_coordinate(device_ctx.grid, from_grid_coord, to_tile_name, arch_direct, arch_direct_id); + /* If destination clb is valid, we should add something */ + if (false == is_grid_coordinate_exist_in_device(device_ctx.grid, to_grid_coord)) { + continue; } - /* Bypass the grid that does not fit the from_tile name */ - if (to_tile_name != std::string(device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].type->name)) { - continue; - } /* Try to find the pin in this tile */ std::vector to_pins = find_physical_tile_pin_id(device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].type, device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].width_offset, @@ -314,26 +557,88 @@ void build_inter_column_row_tile_direct(TileDirect& tile_direct, /* If from port and to port do not match in sizes, error out */ if (from_pins.size() != to_pins.size()) { - VTR_LOG_ERROR("From_port '%s[%lu:%lu] of direct '%s' does not match to_port '%s[%lu:%lu]'!\n", - from_tile_port.get_name().c_str(), - from_tile_port.get_lsb(), - from_tile_port.get_msb(), - vpr_direct.name, - to_tile_port.get_name().c_str(), - to_tile_port.get_lsb(), - to_tile_port.get_msb()); + report_direct_from_port_and_to_port_mismatch(vpr_direct, from_tile_port, to_tile_port); exit(1); } /* Now add the tile direct */ for (size_t ipin = 0; ipin < from_pins.size(); ++ipin) { - TileDirectId tile_direct_id = tile_direct.add_direct(device_ctx.grid[x][y].type, - from_grid_coord, vpr_direct.from_side, from_pins[ipin], - device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].type, + TileDirectId tile_direct_id = tile_direct.add_direct(from_grid_coord, vpr_direct.from_side, from_pins[ipin], to_grid_coord, vpr_direct.to_side, to_pins[ipin]); tile_direct.set_arch_direct_id(tile_direct_id, arch_direct_id); } + } + return; /* Go to next direct type */ + } + + /* Reach here, it must be a cross-row connection */ + VTR_ASSERT(INTER_ROW == arch_direct.type(arch_direct_id)); + /* For cross-row connection, we will search the first valid grid in each column + * from x = 1 to x = nx + * + * x=1 x=nx + * +------+ +------+ + * | Grid | <--- ... ---- | Grid | + * +------+ +------+ + * + */ + for (size_t iy = 1; iy < device_ctx.grid.height() - 1; ++iy) { + std::vector> next_col_src_grid_coords; + /* For negative x- direction, we should start from x = nx */ + for (size_t ix = 1; ix < device_ctx.grid.width() - 1; ++ix) { + next_col_src_grid_coords.push_back(vtr::Point(ix, iy)); + } + /* For positive x- direction, we should start from x = 1 */ + if (POSITIVE_DIR == arch_direct.x_dir(arch_direct_id)) { + std::reverse(next_col_src_grid_coords.begin(), next_col_src_grid_coords.end()); + } + vtr::Point from_grid_coord = find_grid_coordinate_given_type(device_ctx.grid, next_col_src_grid_coords, from_tile_name); + /* Skip if we do not have a valid coordinate for source CLB/heterogeneous block */ + if (false == is_grid_coordinate_exist_in_device(device_ctx.grid, from_grid_coord)) { + continue; + } + + /* Try to find the pin in this tile */ + std::vector from_pins = find_physical_tile_pin_id(device_ctx.grid[from_grid_coord.x()][from_grid_coord.y()].type, + device_ctx.grid[from_grid_coord.x()][from_grid_coord.y()].width_offset, + device_ctx.grid[from_grid_coord.x()][from_grid_coord.y()].height_offset, + from_tile_port, + vpr_direct.from_side); + /* If nothing found, we can continue */ + if (0 == from_pins.size()) { + continue; + } + + /* For a valid coordinate, we can find the coordinate of the destination clb */ + vtr::Point to_grid_coord = find_inter_direct_destination_coordinate(device_ctx.grid, from_grid_coord, to_tile_name, arch_direct, arch_direct_id); + /* If destination clb is valid, we should add something */ + if (false == is_grid_coordinate_exist_in_device(device_ctx.grid, to_grid_coord)) { + continue; + } + + /* Try to find the pin in this tile */ + std::vector to_pins = find_physical_tile_pin_id(device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].type, + device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].width_offset, + device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].height_offset, + to_tile_port, + vpr_direct.to_side); + /* If nothing found, we can continue */ + if (0 == to_pins.size()) { + continue; + } + + /* If from port and to port do not match in sizes, error out */ + if (from_pins.size() != to_pins.size()) { + report_direct_from_port_and_to_port_mismatch(vpr_direct, from_tile_port, to_tile_port); + exit(1); + } + + /* Now add the tile direct */ + for (size_t ipin = 0; ipin < from_pins.size(); ++ipin) { + TileDirectId tile_direct_id = tile_direct.add_direct(from_grid_coord, vpr_direct.from_side, from_pins[ipin], + to_grid_coord, vpr_direct.to_side, to_pins[ipin]); + tile_direct.set_arch_direct_id(tile_direct_id, arch_direct_id); } } } @@ -343,8 +648,7 @@ void build_inter_column_row_tile_direct(TileDirect& tile_direct, * between tiles (programmable blocks) ***************************************************************************************/ TileDirect build_device_tile_direct(const DeviceContext& device_ctx, - const ArchDirect& arch_direct, - const CircuitLibrary& circuit_lib) { + const ArchDirect& arch_direct) { vtr::ScopedStartFinishTimer timer("Build the annotation about direct connection between tiles"); TileDirect tile_direct; @@ -358,7 +662,12 @@ TileDirect build_device_tile_direct(const DeviceContext& device_ctx, device_ctx.arch->Directs[idirect], device_ctx, arch_direct_id); - /* TODO: Build from OpenFPGA arch definition */ + /* Build from OpenFPGA arch definition */ + build_inter_column_row_tile_direct(tile_direct, + device_ctx.arch->Directs[idirect], + device_ctx, + arch_direct, + arch_direct_id); } return tile_direct; diff --git a/openfpga/src/tile_direct/build_tile_direct.h b/openfpga/src/tile_direct/build_tile_direct.h index 08d0397ce..78ca2f6b0 100644 --- a/openfpga/src/tile_direct/build_tile_direct.h +++ b/openfpga/src/tile_direct/build_tile_direct.h @@ -17,8 +17,7 @@ namespace openfpga { TileDirect build_device_tile_direct(const DeviceContext& device_ctx, - const ArchDirect& arch_direct, - const CircuitLibrary& circuit_lib); + const ArchDirect& arch_direct); } /* end namespace openfpga */ diff --git a/openfpga/src/tile_direct/tile_direct.cpp b/openfpga/src/tile_direct/tile_direct.cpp index d0d1f6122..228724c78 100644 --- a/openfpga/src/tile_direct/tile_direct.cpp +++ b/openfpga/src/tile_direct/tile_direct.cpp @@ -15,12 +15,6 @@ TileDirect::tile_direct_range TileDirect::directs() const { return vtr::make_range(direct_ids_.begin(), direct_ids_.end()); } -t_physical_tile_type_ptr TileDirect::from_tile(const TileDirectId& direct_id) const { - /* Validate the direct_id */ - VTR_ASSERT(valid_direct_id(direct_id)); - return from_tiles_[direct_id]; -} - vtr::Point TileDirect::from_tile_coordinate(const TileDirectId& direct_id) const { /* Validate the direct_id */ VTR_ASSERT(valid_direct_id(direct_id)); @@ -39,12 +33,6 @@ e_side TileDirect::from_tile_side(const TileDirectId& direct_id) const { return from_tile_sides_[direct_id]; } -t_physical_tile_type_ptr TileDirect::to_tile(const TileDirectId& direct_id) const { - /* Validate the direct_id */ - VTR_ASSERT(valid_direct_id(direct_id)); - return to_tiles_[direct_id]; -} - vtr::Point TileDirect::to_tile_coordinate(const TileDirectId& direct_id) const { /* Validate the direct_id */ VTR_ASSERT(valid_direct_id(direct_id)); @@ -72,11 +60,9 @@ ArchDirectId TileDirect::arch_direct(const TileDirectId& direct_id) const { /****************************************************************************** * Private Mutators ******************************************************************************/ -TileDirectId TileDirect::add_direct(t_physical_tile_type_ptr from_tile, - const vtr::Point& from_tile_coord, +TileDirectId TileDirect::add_direct(const vtr::Point& from_tile_coord, const e_side& from_tile_side, const size_t& from_tile_pin, - t_physical_tile_type_ptr to_tile, const vtr::Point& to_tile_coord, const e_side& to_tile_side, const size_t& to_tile_pin) { @@ -85,12 +71,10 @@ TileDirectId TileDirect::add_direct(t_physical_tile_type_ptr from_tile, direct_ids_.push_back(direct); /* Allocate other attributes */ - from_tiles_.push_back(from_tile); from_tile_coords_.push_back(from_tile_coord); from_tile_sides_.push_back(from_tile_side); from_tile_pins_.push_back(from_tile_pin); - to_tiles_.push_back(to_tile); to_tile_coords_.push_back(to_tile_coord); to_tile_sides_.push_back(to_tile_side); to_tile_pins_.push_back(to_tile_pin); diff --git a/openfpga/src/tile_direct/tile_direct.h b/openfpga/src/tile_direct/tile_direct.h index d8d0e180f..84593e80c 100644 --- a/openfpga/src/tile_direct/tile_direct.h +++ b/openfpga/src/tile_direct/tile_direct.h @@ -33,21 +33,17 @@ class TileDirect { typedef vtr::Range tile_direct_range; public: /* Public aggregators */ tile_direct_range directs() const; - t_physical_tile_type_ptr from_tile(const TileDirectId& direct_id) const; vtr::Point from_tile_coordinate(const TileDirectId& direct_id) const; e_side from_tile_side(const TileDirectId& direct_id) const; size_t from_tile_pin(const TileDirectId& direct_id) const; - t_physical_tile_type_ptr to_tile(const TileDirectId& direct_id) const; vtr::Point to_tile_coordinate(const TileDirectId& direct_id) const; e_side to_tile_side(const TileDirectId& direct_id) const; size_t to_tile_pin(const TileDirectId& direct_id) const; ArchDirectId arch_direct(const TileDirectId& direct_id) const; public: /* Public mutators */ - TileDirectId add_direct(t_physical_tile_type_ptr from_tile, - const vtr::Point& from_tile_coord, + TileDirectId add_direct(const vtr::Point& from_tile_coord, const e_side& from_tile_side, const size_t& from_tile_pin, - t_physical_tile_type_ptr to_tile, const vtr::Point& to_tile_coord, const e_side& to_tile_side, const size_t& to_tile_pin); @@ -63,7 +59,6 @@ class TileDirect { * - tile coordinate * - tile pin id */ - vtr::vector from_tiles_; vtr::vector> from_tile_coords_; vtr::vector from_tile_sides_; vtr::vector from_tile_pins_; @@ -73,7 +68,6 @@ class TileDirect { * - tile coordinate * - tile pin id */ - vtr::vector to_tiles_; vtr::vector> to_tile_coords_; vtr::vector to_tile_sides_; vtr::vector to_tile_pins_; From 85627dc128e7888f73393f22d6d5806124e0547e Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 15 Feb 2020 14:13:32 -0700 Subject: [PATCH 143/645] put build top module online --- openfpga/src/fabric/build_device_module.cpp | 19 +- openfpga/src/fabric/build_top_module.cpp | 367 ++++++++++++++++++++ openfpga/src/fabric/build_top_module.h | 39 +++ 3 files changed, 417 insertions(+), 8 deletions(-) create mode 100644 openfpga/src/fabric/build_top_module.cpp create mode 100644 openfpga/src/fabric/build_top_module.h diff --git a/openfpga/src/fabric/build_device_module.cpp b/openfpga/src/fabric/build_device_module.cpp index db3d3dfda..a137407ed 100644 --- a/openfpga/src/fabric/build_device_module.cpp +++ b/openfpga/src/fabric/build_device_module.cpp @@ -16,7 +16,7 @@ #include "build_memory_modules.h" #include "build_grid_modules.h" #include "build_routing_modules.h" -//#include "build_top_module.h" +#include "build_top_module.h" #include "build_device_module.h" /* begin namespace openfpga */ @@ -97,12 +97,15 @@ ModuleManager build_device_module_graph(const DeviceContext& vpr_device_ctx, } /* Build FPGA fabric top-level module */ - //build_top_module(module_manager, arch.spice->circuit_lib, - // device_size, grids, L_device_rr_gsb, - // clb2clb_directs, - // arch.sram_inf.verilog_sram_inf_orgz->type, sram_model, - // TRUE == vpr_setup.FPGA_SPICE_Opts.compact_routing_hierarchy, - // TRUE == vpr_setup.FPGA_SPICE_Opts.duplicate_grid_pin); + build_top_module(module_manager, openfpga_ctx.arch().circuit_lib, + vpr_device_ctx.grid, + vpr_device_ctx.rr_graph, + openfpga_ctx.device_rr_gsb(), + openfpga_ctx.tile_direct(), + openfpga_ctx.arch().arch_direct, + openfpga_ctx.arch().config_protocol.type(), + sram_model, + compress_routing, duplicate_grid_pin); /* Now a critical correction has to be done! * In the module construction, we always use prefix of ports because they are binded @@ -111,7 +114,7 @@ ModuleManager build_device_module_graph(const DeviceContext& vpr_device_ctx, * rename the ports of primitive modules using lib_name instead of prefix * (which have no children and are probably linked to a standard cell!) */ - //rename_primitive_module_port_names(module_manager, arch.spice->circuit_lib); + rename_primitive_module_port_names(module_manager, openfpga_ctx.arch().circuit_lib); return module_manager; } diff --git a/openfpga/src/fabric/build_top_module.cpp b/openfpga/src/fabric/build_top_module.cpp new file mode 100644 index 000000000..3357b2b95 --- /dev/null +++ b/openfpga/src/fabric/build_top_module.cpp @@ -0,0 +1,367 @@ +/******************************************************************** + * This file includes functions that are used to print the top-level + * module for the FPGA fabric in Verilog format + *******************************************************************/ +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_time.h" +#include "vtr_log.h" + +/* Headers from vpr library */ +#include "vpr_utils.h" + +#include "rr_gsb_utils.h" +#include "openfpga_reserved_words.h" +#include "openfpga_naming.h" +#include "module_manager_utils.h" +#include "build_top_module_utils.h" +#include "build_top_module_connection.h" +#include "build_top_module_memory.h" +#include "build_top_module_directs.h" + +#include "build_module_graph_utils.h" +#include "build_top_module.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Add a instance of a grid module to the top module + *******************************************************************/ +static +size_t add_top_module_grid_instance(ModuleManager& module_manager, + const ModuleId& top_module, + t_physical_tile_type_ptr grid_type, + const e_side& border_side, + const vtr::Point& grid_coord) { + /* Find the module name for this type of grid */ + std::string grid_module_name_prefix(GRID_MODULE_NAME_PREFIX); + std::string grid_module_name = generate_grid_block_module_name(grid_module_name_prefix, std::string(grid_type->name), is_io_type(grid_type), border_side); + ModuleId grid_module = module_manager.find_module(grid_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(grid_module)); + /* Record the instance id */ + size_t grid_instance = module_manager.num_instance(top_module, grid_module); + /* Add the module to top_module */ + module_manager.add_child_module(top_module, grid_module); + /* Set an unique name to the instance + * Note: it is your risk to gurantee the name is unique! + */ + std::string instance_name = generate_grid_block_instance_name(grid_module_name_prefix, std::string(grid_type->name), is_io_type(grid_type), border_side, grid_coord); + module_manager.set_child_instance_name(top_module, grid_module, grid_instance, instance_name); + + return grid_instance; +} + +/******************************************************************** + * Add all the grids as sub-modules across the fabric + * The grid modules are created for each unique type of grid (based + * on the type in data structure data_structure + * Here, we will iterate over the full fabric (coordinates) + * and instanciate the grid modules + * + * Return an 2-D array of instance ids of the grid modules that + * have been added + * + * This function assumes an island-style floorplanning for FPGA fabric + * + * + * +-----------------------------------+ + * | I/O grids | + * | TOP side | + * +-----------------------------------+ + * + * +-----------+ +-----------------------------------+ +------------+ + * | | | | | | + * | I/O grids | | Core grids | | I/O grids | + * | LEFT side | | (CLB, Heterogeneous blocks, etc.) | | RIGHT side | + * | | | | | | + * +-----------+ +-----------------------------------+ +------------+ + * + * +-----------------------------------+ + * | I/O grids | + * | BOTTOM side | + * +-----------------------------------+ + * + *******************************************************************/ +static +vtr::Matrix add_top_module_grid_instances(ModuleManager& module_manager, + const ModuleId& top_module, + const DeviceGrid& grids) { + /* Reserve an array for the instance ids */ + vtr::Matrix grid_instance_ids({grids.width(), grids.height()}); + grid_instance_ids.fill(size_t(-1)); + + /* Instanciate core grids */ + for (size_t ix = 1; ix < grids.width() - 1; ++ix) { + for (size_t iy = 1; iy < grids.height() - 1; ++iy) { + /* Bypass EMPTY grid */ + if (true == is_empty_type(grids[ix][iy].type)) { + continue; + } + /* Skip width or height > 1 tiles (mostly heterogeneous blocks) */ + if ( (0 < grids[ix][iy].width_offset) + || (0 < grids[ix][iy].height_offset)) { + continue; + } + /* We should not meet any I/O grid */ + VTR_ASSERT(false == is_io_type(grids[ix][iy].type)); + /* Add a grid module to top_module*/ + vtr::Point grid_coord(ix, iy); + grid_instance_ids[ix][iy] = add_top_module_grid_instance(module_manager, top_module, + grids[ix][iy].type, + NUM_SIDES, grid_coord); + } + } + + /* Instanciate I/O grids */ + /* Create the coordinate range for each side of FPGA fabric */ + std::vector io_sides{TOP, RIGHT, BOTTOM, LEFT}; + std::map>> io_coordinates; + + /* TOP side*/ + for (size_t ix = 1; ix < grids.width() - 1; ++ix) { + io_coordinates[TOP].push_back(vtr::Point(ix, grids.height() - 1)); + } + + /* RIGHT side */ + for (size_t iy = 1; iy < grids.height() - 1; ++iy) { + io_coordinates[RIGHT].push_back(vtr::Point(grids.width() - 1, iy)); + } + + /* BOTTOM side*/ + for (size_t ix = 1; ix < grids.width() - 1; ++ix) { + io_coordinates[BOTTOM].push_back(vtr::Point(ix, 0)); + } + + /* LEFT side */ + for (size_t iy = 1; iy < grids.height() - 1; ++iy) { + io_coordinates[LEFT].push_back(vtr::Point(0, iy)); + } + + /* Add instances of I/O grids to top_module */ + for (const e_side& io_side : io_sides) { + for (const vtr::Point& io_coordinate : io_coordinates[io_side]) { + /* Bypass EMPTY grid */ + if (true == is_empty_type(grids[io_coordinate.x()][io_coordinate.y()].type)) { + continue; + } + /* Skip width, height > 1 tiles (mostly heterogeneous blocks) */ + if ( (0 < grids[io_coordinate.x()][io_coordinate.y()].width_offset) + || (0 < grids[io_coordinate.x()][io_coordinate.y()].height_offset)) { + continue; + } + /* We should not meet any I/O grid */ + VTR_ASSERT(true == is_io_type(grids[io_coordinate.x()][io_coordinate.y()].type)); + /* Add a grid module to top_module*/ + grid_instance_ids[io_coordinate.x()][io_coordinate.y()] = add_top_module_grid_instance(module_manager, top_module, grids[io_coordinate.x()][io_coordinate.y()].type, io_side, io_coordinate); + } + } + + return grid_instance_ids; +} + +/******************************************************************** + * Add switch blocks across the FPGA fabric to the top-level module + * Return an 2-D array of instance ids of the switch blocks that + * have been added + *******************************************************************/ +static +vtr::Matrix add_top_module_switch_block_instances(ModuleManager& module_manager, + const ModuleId& top_module, + const DeviceRRGSB& device_rr_gsb, + const bool& compact_routing_hierarchy) { + vtr::Point sb_range = device_rr_gsb.get_gsb_range(); + + /* Reserve an array for the instance ids */ + vtr::Matrix sb_instance_ids({sb_range.x(), sb_range.y()}); + sb_instance_ids.fill(size_t(-1)); + + for (size_t ix = 0; ix < sb_range.x(); ++ix) { + for (size_t iy = 0; iy < sb_range.y(); ++iy) { + /* If we use compact routing hierarchy, we should instanciate the unique module of SB */ + const RRGSB& rr_gsb = device_rr_gsb.get_gsb(ix, iy); + + if ( false == rr_gsb.is_sb_exist() ) { + continue; + } + + vtr::Point sb_coordinate(rr_gsb.get_sb_x(), rr_gsb.get_sb_y()); + if (true == compact_routing_hierarchy) { + vtr::Point sb_coord(ix, iy); + const RRGSB& unique_mirror = device_rr_gsb.get_sb_unique_module(sb_coord); + sb_coordinate.set_x(unique_mirror.get_sb_x()); + sb_coordinate.set_y(unique_mirror.get_sb_y()); + } + std::string sb_module_name = generate_switch_block_module_name(sb_coordinate); + ModuleId sb_module = module_manager.find_module(sb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(sb_module)); + /* Record the instance id */ + sb_instance_ids[rr_gsb.get_sb_x()][rr_gsb.get_sb_y()] = module_manager.num_instance(top_module, sb_module); + /* Add the module to top_module */ + module_manager.add_child_module(top_module, sb_module); + /* Set an unique name to the instance + * Note: it is your risk to gurantee the name is unique! + */ + module_manager.set_child_instance_name(top_module, sb_module, + sb_instance_ids[rr_gsb.get_sb_x()][rr_gsb.get_sb_y()], + generate_switch_block_module_name(vtr::Point(rr_gsb.get_sb_x(), rr_gsb.get_sb_y()))); + } + } + + return sb_instance_ids; +} + +/******************************************************************** + * Add switch blocks across the FPGA fabric to the top-level module + *******************************************************************/ +static +vtr::Matrix add_top_module_connection_block_instances(ModuleManager& module_manager, + const ModuleId& top_module, + const DeviceRRGSB& device_rr_gsb, + const t_rr_type& cb_type, + const bool& compact_routing_hierarchy) { + vtr::Point cb_range = device_rr_gsb.get_gsb_range(); + + /* Reserve an array for the instance ids */ + vtr::Matrix cb_instance_ids({cb_range.x(), cb_range.y()}); + cb_instance_ids.fill(size_t(-1)); + + for (size_t ix = 0; ix < cb_range.x(); ++ix) { + for (size_t iy = 0; iy < cb_range.y(); ++iy) { + /* Check if the connection block exists in the device! + * Some of them do NOT exist due to heterogeneous blocks (height > 1) + * We will skip those modules + */ + const RRGSB& rr_gsb = device_rr_gsb.get_gsb(ix, iy); + vtr::Point cb_coordinate(rr_gsb.get_cb_x(cb_type), rr_gsb.get_cb_y(cb_type)); + if ( false == rr_gsb.is_cb_exist(cb_type) ) { + continue; + } + /* If we use compact routing hierarchy, we should instanciate the unique module of SB */ + if (true == compact_routing_hierarchy) { + vtr::Point cb_coord(ix, iy); + /* Note: use GSB coordinate when inquire for unique modules!!! */ + const RRGSB& unique_mirror = device_rr_gsb.get_cb_unique_module(cb_type, cb_coord); + cb_coordinate.set_x(unique_mirror.get_cb_x(cb_type)); + cb_coordinate.set_y(unique_mirror.get_cb_y(cb_type)); + } + std::string cb_module_name = generate_connection_block_module_name(cb_type, cb_coordinate); + ModuleId cb_module = module_manager.find_module(cb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(cb_module)); + /* Record the instance id */ + cb_instance_ids[rr_gsb.get_cb_x(cb_type)][rr_gsb.get_cb_y(cb_type)] = module_manager.num_instance(top_module, cb_module); + /* Add the module to top_module */ + module_manager.add_child_module(top_module, cb_module); + /* Set an unique name to the instance + * Note: it is your risk to gurantee the name is unique! + */ + std::string cb_instance_name = generate_connection_block_module_name(cb_type, vtr::Point(rr_gsb.get_cb_x(cb_type), rr_gsb.get_cb_y(cb_type))); + module_manager.set_child_instance_name(top_module, cb_module, + cb_instance_ids[rr_gsb.get_cb_x(cb_type)][rr_gsb.get_cb_y(cb_type)], + cb_instance_name); + } + } + + return cb_instance_ids; +} + +/******************************************************************** + * Print the top-level module for the FPGA fabric in Verilog format + * This function will + * 1. name the top-level module + * 2. include dependent netlists + * - User defined netlists + * - Auto-generated netlists + * 3. Add the submodules to the top-level graph + * 4. Add module nets to connect datapath ports + * 5. Add module nets/submodules to connect configuration ports + *******************************************************************/ +void build_top_module(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const DeviceGrid& grids, + const RRGraph& rr_graph, + const DeviceRRGSB& device_rr_gsb, + const TileDirect& tile_direct, + const ArchDirect& arch_direct, + const e_config_protocol_type& sram_orgz_type, + const CircuitModelId& sram_model, + const bool& compact_routing_hierarchy, + const bool& duplicate_grid_pin) { + + vtr::ScopedStartFinishTimer timer("Build FPGA fabric module"); + + /* Create a module as the top-level fabric, and add it to the module manager */ + std::string top_module_name = generate_fpga_top_module_name(); + ModuleId top_module = module_manager.add_module(top_module_name); + + std::map> cb_instance_ids; + + /* Add sub modules, which are grid, SB and CBX/CBY modules as instances */ + /* Add all the grids across the fabric */ + vtr::Matrix grid_instance_ids = add_top_module_grid_instances(module_manager, top_module, grids); + /* Add all the SBs across the fabric */ + vtr::Matrix sb_instance_ids = add_top_module_switch_block_instances(module_manager, top_module, device_rr_gsb, compact_routing_hierarchy); + /* Add all the CBX and CBYs across the fabric */ + cb_instance_ids[CHANX] = add_top_module_connection_block_instances(module_manager, top_module, device_rr_gsb, CHANX, compact_routing_hierarchy); + cb_instance_ids[CHANY] = add_top_module_connection_block_instances(module_manager, top_module, device_rr_gsb, CHANY, compact_routing_hierarchy); + + /* Add module nets to connect the sub modules */ + add_top_module_nets_connect_grids_and_gsbs(module_manager, top_module, + grids, grid_instance_ids, + rr_graph, device_rr_gsb, sb_instance_ids, cb_instance_ids, + compact_routing_hierarchy, duplicate_grid_pin); + /* Add inter-CLB direct connections */ + add_top_module_nets_tile_direct_connections(module_manager, top_module, circuit_lib, + grids, grid_instance_ids, + tile_direct, arch_direct); + + /* Add global ports to the pb_module: + * This is a much easier job after adding sub modules (instances), + * we just need to find all the global ports from the child modules and build a list of it + */ + add_module_global_ports_from_child_modules(module_manager, top_module); + + /* Add GPIO ports from the sub-modules under this Verilog module + * This is a much easier job after adding sub modules (instances), + * we just need to find all the I/O ports from the child modules and build a list of it + */ + add_module_gpio_ports_from_child_modules(module_manager, top_module); + + /* Add shared SRAM ports from the sub-modules under this Verilog module + * This is a much easier job after adding sub modules (instances), + * we just need to find all the I/O ports from the child modules and build a list of it + */ + size_t module_num_shared_config_bits = find_module_num_shared_config_bits_from_child_modules(module_manager, top_module); + if (0 < module_num_shared_config_bits) { + add_reserved_sram_ports_to_module_manager(module_manager, top_module, module_num_shared_config_bits); + } + + /* Add SRAM ports from the sub-modules under this Verilog module + * This is a much easier job after adding sub modules (instances), + * we just need to find all the I/O ports from the child modules and build a list of it + */ + size_t module_num_config_bits = find_module_num_config_bits_from_child_modules(module_manager, top_module, circuit_lib, sram_model, sram_orgz_type); + if (0 < module_num_config_bits) { + add_sram_ports_to_module_manager(module_manager, top_module, circuit_lib, sram_model, sram_orgz_type, module_num_config_bits); + } + + /* Organize the list of memory modules and instances */ + organize_top_module_memory_modules(module_manager, top_module, + circuit_lib, sram_orgz_type, sram_model, + grids, grid_instance_ids, + device_rr_gsb, sb_instance_ids, cb_instance_ids, + compact_routing_hierarchy); + + /* Add module nets to connect memory cells inside + * This is a one-shot addition that covers all the memory modules in this pb module! + */ + if (0 < module_manager.configurable_children(top_module).size()) { + add_top_module_nets_memory_config_bus(module_manager, top_module, + sram_orgz_type, circuit_lib.design_tech_type(sram_model)); + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fabric/build_top_module.h b/openfpga/src/fabric/build_top_module.h new file mode 100644 index 000000000..3b6badfb2 --- /dev/null +++ b/openfpga/src/fabric/build_top_module.h @@ -0,0 +1,39 @@ +#ifndef BUILD_TOP_MODULE_H +#define BUILD_TOP_MODULE_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ + +#include +#include "vtr_geometry.h" +#include "device_grid.h" +#include "rr_graph_obj.h" +#include "device_rr_gsb.h" +#include "circuit_library.h" +#include "tile_direct.h" +#include "arch_direct.h" +#include "module_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void build_top_module(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const DeviceGrid& grids, + const RRGraph& rr_graph, + const DeviceRRGSB& device_rr_gsb, + const TileDirect& tile_direct, + const ArchDirect& arch_direct, + const e_config_protocol_type& sram_orgz_type, + const CircuitModelId& sram_model, + const bool& compact_routing_hierarchy, + const bool& duplicate_grid_pin); + +} /* end namespace openfpga */ + +#endif From 622c7826d1f48cf8a457aeda1dadd11ce05fda7f Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 15 Feb 2020 15:03:00 -0700 Subject: [PATCH 144/645] start transplanting fpga_verilog --- .../libopenfpgautil/src/openfpga_digest.cpp | 113 +++++++++++++++++ .../libopenfpgautil/src/openfpga_digest.h | 8 ++ openfpga/src/fpga_verilog/verilog_api.cpp | 118 ++++++++++++++++++ openfpga/src/fpga_verilog/verilog_api.h | 36 ++++++ openfpga/src/fpga_verilog/verilog_constants.h | 40 ++++++ .../src/fpga_verilog/verilog_port_types.h | 16 +++ 6 files changed, 331 insertions(+) create mode 100644 openfpga/src/fpga_verilog/verilog_api.cpp create mode 100644 openfpga/src/fpga_verilog/verilog_api.h create mode 100644 openfpga/src/fpga_verilog/verilog_constants.h create mode 100644 openfpga/src/fpga_verilog/verilog_port_types.h diff --git a/libopenfpga/libopenfpgautil/src/openfpga_digest.cpp b/libopenfpga/libopenfpgautil/src/openfpga_digest.cpp index f1b44d788..3340ea8b1 100644 --- a/libopenfpga/libopenfpgautil/src/openfpga_digest.cpp +++ b/libopenfpga/libopenfpgautil/src/openfpga_digest.cpp @@ -2,6 +2,9 @@ * This file includes functions that handles the file outputting * in OpenFPGA framework *******************************************************************/ +#include +#include + /* Headers from vtrutil library */ #include "vtr_log.h" @@ -37,4 +40,114 @@ void check_file_stream(const char* fname, } } +/******************************************************************** + * Format a directory path: + * 1. Replace "\" with "/" + * 2. add a "/" if the string does not end with a "/" + *******************************************************************/ +std::string format_dir_path(const std::string& dir_path_to_format) { + std::string formatted_dir_path = dir_path_to_format; + + char illegal_back_slash = '\\'; + char legal_back_slash = '/'; + +#ifdef _WIN32 +/* For windows OS, replace any '/' with '\' */ + char illegal_back_slash = '/'; + char legal_back_slash = '\\'; +#endif + + /* Replace "\" with "/" */ + std::replace(formatted_dir_path.begin(), formatted_dir_path.end(), illegal_back_slash, legal_back_slash); + + /* Add a back slash the string is not ended like this! */ + if (legal_back_slash != formatted_dir_path.back()) { + formatted_dir_path.push_back(legal_back_slash); + } + + return formatted_dir_path; +} + +/******************************************************************** + * Extract full file name from a full path of file + * For example: / + * This function will return + ********************************************************************/ +std::string find_path_file_name(const std::string& file_name) { + + char back_slash = '/'; + +#ifdef _WIN32 +/* For windows OS, replace any '/' with '\' */ + char back_slash = '\\'; +#endif + + /* Find the last '/' in the string and return the left part */ + size_t found = file_name.rfind(back_slash); + if (found != std::string::npos) { + return file_name.substr(found + 1); + } + /* Not found. The input is the file name! Return the original string */ + return file_name; +} + +/******************************************************************** + * Extract full directory path from a full path of file + * For example: / + * This function will return + ********************************************************************/ +std::string find_path_dir_name(const std::string& file_name) { + + char back_slash = '/'; + +#ifdef _WIN32 +/* For windows OS, replace any '/' with '\' */ + char back_slash = '\\'; +#endif + + /* Find the last '/' in the string and return the left part */ + size_t found = file_name.rfind(back_slash); + if (found != std::string::npos) { + return file_name.substr(0, found); + } + /* Not found, return an empty string */ + return std::string(); +} + +/******************************************************************** + * Create a directory with a given path + ********************************************************************/ +bool create_dir_path(const char* dir_path) { + /* Give up if the path is empty */ + if (nullptr == dir_path) { + VTR_LOG_ERROR("dir_path is empty and nothing is created.\n"); + return false; + } + + /* Try to create a directory */ + int ret = mkdir(dir_path, S_IRWXU|S_IRWXG|S_IROTH|S_IXOTH); + + /* Analyze the return flag and output status */ + switch (ret) { + case 0: + VTR_LOG("Succeed to create directory '%s'\n", + dir_path); + return true; + case -1: + if (EEXIST == errno) { + VTR_LOG_ERROR("Directory '%s' already exists. Will overwrite contents\n", + dir_path); + return true; + } + break; + default: + VTR_LOG_ERROR("Create directory '%s'...Failed!\n", + dir_path); + exit(1); + return false; + } + + return false; +} + } /* namespace openfpga ends */ diff --git a/libopenfpga/libopenfpgautil/src/openfpga_digest.h b/libopenfpga/libopenfpgautil/src/openfpga_digest.h index 332249d56..90af3dc4e 100644 --- a/libopenfpga/libopenfpgautil/src/openfpga_digest.h +++ b/libopenfpga/libopenfpgautil/src/openfpga_digest.h @@ -17,6 +17,14 @@ bool valid_file_stream(std::fstream& fp); void check_file_stream(const char* fname, std::fstream& fp); +std::string format_dir_path(const std::string& dir_path_to_format); + +std::string find_path_file_name(const std::string& file_name); + +std::string find_path_dir_name(const std::string& file_name); + +bool create_dir_path(const char* dir_path); + } /* namespace openfpga ends */ #endif diff --git a/openfpga/src/fpga_verilog/verilog_api.cpp b/openfpga/src/fpga_verilog/verilog_api.cpp new file mode 100644 index 000000000..7de1b95e9 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_api.cpp @@ -0,0 +1,118 @@ +/******************************************************************** + * This file include top-level function of FPGA-Verilog + ********************************************************************/ + +/* Headers from vtrutil library */ +#include "vtr_log.h" +#include "vtr_assert.h" +#include "vtr_time.h" + +#include "circuit_library_utils.h" + +/* Headers from openfpgautil library */ +#include "openfpga_digest.h" + +#include "device_rr_gsb.h" +#include "verilog_constants.h" +//#include "verilog_submodules.h" +//#include "verilog_routing.h" +//#include "verilog_submodules.h" +//#include "verilog_grid.h" +//#include "verilog_routing.h" +//#include "verilog_top_module.h" + +/* Header file for this source file */ +#include "verilog_api.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Top-level function of FPGA-Verilog + * This function will generate + * 1. primitive modules required by the full fabric + * which are LUTs, routing multiplexer, logic gates, transmission-gates etc. + * 2. Routing modules, which are Switch Blocks (SBs) and Connection Blocks (CBs) + * 3. Logic block modules, which are Configuration Logic Blocks (CLBs) + * 4. FPGA module, which are the full FPGA fabric with configuration protocol + * 5. A wrapper module, which encapsulate the FPGA module in a Verilog module which have the same port as the input benchmark + * 6. Testbench, where a FPGA module is configured with a bitstream and then driven by input vectors + * 7. Pre-configured testbench, which can skip the configuration phase and pre-configure the FPGA module. This testbench is created for quick verification and formal verification purpose. + * 8. Verilog netlist including preprocessing flags and all the Verilog netlists that have been generated + ********************************************************************/ +void fabric_verilog(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const MuxLibrary& mux_lib, + const DeviceGrid& grids, + const DeviceRRGSB& device_rr_gsb, + const std::string& output_directory, + const bool& compress_routing, + const bool& dump_explict_verilog, + const bool& verbose) { + + vtr::ScopedStartFinishTimer timer("Generate Verilog netlists for FPGA fabric\n"); + + std::string src_dir_path = format_dir_path(output_directory); + + /* Create directories */ + create_dir_path(src_dir_path.c_str()); + + /* Sub directory under SRC directory to contain all the primitive block netlists */ + std::string submodule_dir_path = src_dir_path + std::string(DEFAULT_SUBMODULE_DIR_NAME); + create_dir_path(submodule_dir_path.c_str()); + + /* Sub directory under SRC directory to contain all the logic block netlists */ + std::string lb_dir_path = src_dir_path + std::string(DEFAULT_LB_DIR_NAME); + create_dir_path(lb_dir_path.c_str()); + + /* Sub directory under SRC directory to contain all the routing block netlists */ + std::string rr_dir_path = src_dir_path + std::string(DEFAULT_RR_DIR_NAME); + create_dir_path(rr_dir_path.c_str()); + + /* Print Verilog files containing preprocessing flags */ + //print_verilog_preprocessing_flags_netlist(std::string(src_dir_path), + // vpr_setup.FPGA_SPICE_Opts.SynVerilogOpts); + + //print_verilog_simulation_preprocessing_flags(std::string(src_dir_path), + // vpr_setup.FPGA_SPICE_Opts.SynVerilogOpts); + + /* Generate primitive Verilog modules, which are corner stones of FPGA fabric + * Note that this function MUST be called before Verilog generation of + * core logic (i.e., logic blocks and routing resources) !!! + * This is because that this function will add the primitive Verilog modules to + * the module manager. + * Without the modules in the module manager, core logic generation is not possible!!! + */ + //print_verilog_submodules(module_manager, mux_lib, sram_verilog_orgz_info, src_dir_path.c_str(), submodule_dir_path.c_str(), + // Arch, vpr_setup.FPGA_SPICE_Opts.SynVerilogOpts); + + /* Generate routing blocks */ + //if (true == compress_routing) { + // print_verilog_unique_routing_modules(module_manager, device_rr_gsb, + // src_dir_path, rr_dir_path, + // dump_explicit_verilog); + //} else { + // VTR_ASSERT(false == compress_routing); + // print_verilog_flatten_routing_modules(module_manager, device_rr_gsb, + // src_dir_path, rr_dir_path, + // dump_explicit_verilog); + //} + + /* Generate grids */ + //print_verilog_grids(module_manager, + // src_dir_path, lb_dir_path, + // dump_explicit_verilog); + + /* Generate FPGA fabric */ + //print_verilog_top_module(module_manager, + // std::string(vpr_setup.FileNameOpts.ArchFile), + // src_dir_path, + // dump_explicit_verilog); + + /* Given a brief stats on how many Verilog modules have been written to files */ + VTR_LOGV(verbose, + "Outputted %lu Verilog modules in total\n", + module_manager.num_modules()); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_api.h b/openfpga/src/fpga_verilog/verilog_api.h new file mode 100644 index 000000000..9d601ee46 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_api.h @@ -0,0 +1,36 @@ +#ifndef VERILOG_API_H +#define VERILOG_API_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ + +#include +#include +#include "vpr_types.h" +#include "mux_library.h" +#include "circuit_library.h" +#include "device_grid.h" +#include "device_rr_gsb.h" +#include "module_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void fabric_verilog(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const MuxLibrary& mux_lib, + const DeviceGrid& grids, + const DeviceRRGSB& device_rr_gsb, + const std::string& output_directory, + const bool& compress_routing, + const bool& dump_explict_verilog, + const bool& verbose); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_verilog/verilog_constants.h b/openfpga/src/fpga_verilog/verilog_constants.h new file mode 100644 index 000000000..10a6c0669 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_constants.h @@ -0,0 +1,40 @@ +#ifndef VERILOG_CONSTANTS_H +#define VERILOG_CONSTANTS_H + +/* global parameters for dumping synthesizable verilog */ + +constexpr char* VERILOG_NETLIST_FILE_POSTFIX = ".v"; +constexpr float VERILOG_SIM_TIMESCALE = 1e-9; // Verilog Simulation time scale (minimum time unit) : 1ns + +constexpr char* VERILOG_TIMING_PREPROC_FLAG = "ENABLE_TIMING"; // the flag to enable timing definition during compilation +constexpr char* VERILOG_SIGNAL_INIT_PREPROC_FLAG = "ENABLE_SIGNAL_INITIALIZATION"; // the flag to enable signal initialization during compilation +constexpr char* VERILOG_FORMAL_VERIFICATGION_PREPROC_FLAG = "ENABLE_FORMAL_VERIFICATION"; // the flag to enable formal verification during compilation +constexpr char* INITIAL_SIMULATION_FLAG = "INITIAL_SIMULATION"; // the flag to enable initial functional verification +constexpr char* AUTOCHECKED_SIMULATION_FLAG = "AUTOCHECKED_SIMULATION"; // the flag to enable autochecked functional verification +constexpr char* FORMAL_SIMULATION_FLAG = "FORMAL_SIMULATION"; // the flag to enable formal functional verification + +constexpr char* DEFAULT_LB_DIR_NAME = "lb/"; +constexpr char* DEFAULT_RR_DIR_NAME = "routing/"; +constexpr char* DEFAULT_SUBMODULE_DIR_NAME = "sub_module/"; +constexpr char* MODELSIM_SIMULATION_TIME_UNIT = "ms"; + +// Icarus variables and flag +constexpr char* ICARUS_SIMULATION_FLAG = "ICARUS_SIMULATOR"; // the flag to enable specific Verilog code in testbenches +// End of Icarus variables and flag + +constexpr char* VERILOG_TOP_POSTFIX = "_top.v"; +constexpr char* DEFINES_VERILOG_FILE_NAME = "fpga_defines.v"; +constexpr char* DEFINES_VERILOG_SIMULATION_FILE_NAME = "define_simulation.v"; +constexpr char* SUBMODULE_VERILOG_FILE_NAME = "sub_module.v"; +constexpr char* LOGIC_BLOCK_VERILOG_FILE_NAME = "logic_blocks.v"; +constexpr char* LUTS_VERILOG_FILE_NAME = "luts.v"; +constexpr char* ROUTING_VERILOG_FILE_NAME = "routing.v"; +constexpr char* MUXES_VERILOG_FILE_NAME = "muxes.v"; +constexpr char* LOCAL_ENCODER_VERILOG_FILE_NAME = "local_encoder.v"; +constexpr char* MEMORIES_VERILOG_FILE_NAME = "memories.v"; +constexpr char* WIRES_VERILOG_FILE_NAME = "wires.v"; +constexpr char* ESSENTIALS_VERILOG_FILE_NAME = "inv_buf_passgate.v"; +constexpr char* CONFIG_PERIPHERAL_VERILOG_FILE_NAME = "config_peripherals.v"; +constexpr char* USER_DEFINED_TEMPLATE_VERILOG_FILE_NAME = "user_defined_templates.v"; + +#endif diff --git a/openfpga/src/fpga_verilog/verilog_port_types.h b/openfpga/src/fpga_verilog/verilog_port_types.h new file mode 100644 index 000000000..2100ba272 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_port_types.h @@ -0,0 +1,16 @@ +#ifndef VERILOG_PORT_TYPES_H +#define VERILOG_PORT_TYPES_H + +enum e_dump_verilog_port_type { + VERILOG_PORT_INPUT, + VERILOG_PORT_OUTPUT, + VERILOG_PORT_INOUT, + VERILOG_PORT_WIRE, + VERILOG_PORT_REG, + VERILOG_PORT_CONKT, + NUM_VERILOG_PORT_TYPES +}; +constexpr std::array VERILOG_PORT_TYPE_STRING = {{"input", "output", "inout", "wire", "reg", ""}}; /* string version of enum e_verilog_port_type */ + +#endif + From 8b0df8632c35332342401e2766a400e3a01962a4 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 15 Feb 2020 20:38:45 -0700 Subject: [PATCH 145/645] bring fpga verilog create directory online --- openfpga/src/base/openfpga_context.h | 6 +++ openfpga/src/base/openfpga_flow_manager.cpp | 26 ++++++++++ openfpga/src/base/openfpga_flow_manager.h | 28 ++++++++++ openfpga/src/base/openfpga_setup_command.cpp | 2 +- .../src/base/openfpga_verilog_command.cpp | 51 +++++++++++++++++++ openfpga/src/base/openfpga_verilog_command.h | 21 ++++++++ openfpga/src/fpga_verilog/verilog_api.cpp | 20 ++++---- openfpga/src/fpga_verilog/verilog_api.h | 18 +++---- openfpga/src/main.cpp | 4 ++ openfpga/test_script/s298_k6_frac.openfpga | 4 ++ 10 files changed, 160 insertions(+), 20 deletions(-) create mode 100644 openfpga/src/base/openfpga_flow_manager.cpp create mode 100644 openfpga/src/base/openfpga_flow_manager.h create mode 100644 openfpga/src/base/openfpga_verilog_command.cpp create mode 100644 openfpga/src/base/openfpga_verilog_command.h diff --git a/openfpga/src/base/openfpga_context.h b/openfpga/src/base/openfpga_context.h index ac3cc99c8..5726cbba9 100644 --- a/openfpga/src/base/openfpga_context.h +++ b/openfpga/src/base/openfpga_context.h @@ -10,6 +10,7 @@ #include "mux_library.h" #include "tile_direct.h" #include "module_manager.h" +#include "openfpga_flow_manager.h" #include "device_rr_gsb.h" /******************************************************************** @@ -50,6 +51,7 @@ class OpenfpgaContext : public Context { const openfpga::MuxLibrary& mux_lib() const { return mux_lib_; } const openfpga::TileDirect& tile_direct() const { return tile_direct_; } const openfpga::ModuleManager& module_graph() const { return module_graph_; } + const openfpga::FlowManager& flow_manager() const { return flow_manager_; } public: /* Public mutators */ openfpga::Arch& mutable_arch() { return arch_; } openfpga::VprDeviceAnnotation& mutable_vpr_device_annotation() { return vpr_device_annotation_; } @@ -60,6 +62,7 @@ class OpenfpgaContext : public Context { openfpga::MuxLibrary& mutable_mux_lib() { return mux_lib_; } openfpga::TileDirect& mutable_tile_direct() { return tile_direct_; } openfpga::ModuleManager& mutable_module_graph() { return module_graph_; } + openfpga::FlowManager& mutable_flow_manager() { return flow_manager_; } private: /* Internal data */ /* Data structure to store information from read_openfpga_arch library */ openfpga::Arch arch_; @@ -87,6 +90,9 @@ class OpenfpgaContext : public Context { /* Fabric module graph */ openfpga::ModuleManager module_graph_; + + /* Flow status */ + openfpga::FlowManager flow_manager_; }; #endif diff --git a/openfpga/src/base/openfpga_flow_manager.cpp b/openfpga/src/base/openfpga_flow_manager.cpp new file mode 100644 index 000000000..bc97f291d --- /dev/null +++ b/openfpga/src/base/openfpga_flow_manager.cpp @@ -0,0 +1,26 @@ +/****************************************************************************** + * Memember functions for data structure FlowManager + ******************************************************************************/ +#include "vtr_assert.h" + +#include "openfpga_flow_manager.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/************************************************** + * Public Accessors + *************************************************/ +bool FlowManager::compress_routing() const { + return compress_routing_; +} + +/****************************************************************************** + * Private Mutators + ******************************************************************************/ +void FlowManager::set_compress_routing(const bool& enabled) { + compress_routing_ = enabled; +} + + +} /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_flow_manager.h b/openfpga/src/base/openfpga_flow_manager.h new file mode 100644 index 000000000..6a6ae7c75 --- /dev/null +++ b/openfpga/src/base/openfpga_flow_manager.h @@ -0,0 +1,28 @@ +#ifndef FLOW_MANAGER_H +#define FLOW_MANAGER_H + +/******************************************************************** + * Include header files required by the data structure definition + *******************************************************************/ +/* Begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * FlowManager aims to resolve the dependency between OpenFPGA functional + * code blocks + * It can provide flags for downstream modules about if the data structures + * they require have already been constructed + * + *******************************************************************/ +class FlowManager { + public: /* Public accessors */ + bool compress_routing() const; + public: /* Public mutators */ + void set_compress_routing(const bool& enabled); + private: /* Internal Data */ + bool compress_routing_; +}; + +} /* End namespace openfpga*/ + +#endif diff --git a/openfpga/src/base/openfpga_setup_command.cpp b/openfpga/src/base/openfpga_setup_command.cpp index 1b6971167..e84227187 100644 --- a/openfpga/src/base/openfpga_setup_command.cpp +++ b/openfpga/src/base/openfpga_setup_command.cpp @@ -135,7 +135,7 @@ void add_openfpga_setup_commands(openfpga::Shell& shell) { shell.set_command_execute_function(shell_cmd_build_fabric_id, build_fabric); /* The 'build_fabric' command should NOT be executed before 'link_openfpga_arch' */ std::vector cmd_dependency_build_fabric; - cmd_dependency_lut_truth_table_fixup.push_back(shell_cmd_link_openfpga_arch_id); + cmd_dependency_build_fabric.push_back(shell_cmd_link_openfpga_arch_id); shell.set_command_dependency(shell_cmd_build_fabric_id, cmd_dependency_build_fabric); } diff --git a/openfpga/src/base/openfpga_verilog_command.cpp b/openfpga/src/base/openfpga_verilog_command.cpp new file mode 100644 index 000000000..fa3ff9c4b --- /dev/null +++ b/openfpga/src/base/openfpga_verilog_command.cpp @@ -0,0 +1,51 @@ +/******************************************************************** + * Add commands to the OpenFPGA shell interface, + * in purpose of generate Verilog netlists modeling the full FPGA fabric + * This is one of the core engine of openfpga, including: + * - generate_fabric_verilog : generate Verilog netlists about FPGA fabric + * - generate_fabric_verilog_testbench : TODO: generate Verilog testbenches + *******************************************************************/ +#include "openfpga_verilog.h" +#include "openfpga_verilog_command.h" + +/* begin namespace openfpga */ +namespace openfpga { + +void add_openfpga_verilog_commands(openfpga::Shell& shell) { + /* Get the unique id of 'build_fabric' command which is to be used in creating the dependency graph */ + const ShellCommandId& shell_cmd_build_fabric_id = shell.command(std::string("build_fabric")); + + /* Add a new class of commands */ + ShellCommandClassId openfpga_verilog_cmd_class = shell.add_command_class("FPGA-Verilog"); + + /******************************** + * Command 'wirte_fabric_verilog' + */ + Command shell_cmd_write_fabric_verilog("write_fabric_verilog"); + /* Add an option '--file' in short '-f'*/ + CommandOptionId fabric_verilog_output_opt = shell_cmd_write_fabric_verilog.add_option("file", true, "Specify the output directory for Verilog netlists"); + shell_cmd_write_fabric_verilog.set_option_short_name(fabric_verilog_output_opt, "f"); + shell_cmd_write_fabric_verilog.set_option_require_value(fabric_verilog_output_opt, openfpga::OPT_STRING); + /* Add an option '--explicit_port_mapping' */ + shell_cmd_write_fabric_verilog.add_option("explicit_port_mapping", false, "Use explicit port mapping in Verilog netlists"); + /* Add an option '--include_timing' */ + shell_cmd_write_fabric_verilog.add_option("include_timing", false, "Enable timing annotation in Verilog netlists"); + /* Add an option '--include_signal_init' */ + shell_cmd_write_fabric_verilog.add_option("include_signal_init", false, "Initialize all the signals in Verilog netlists"); + /* Add an option '--support_icarus_simulator' */ + shell_cmd_write_fabric_verilog.add_option("support_icarus_simulator", false, "Fine-tune Verilog netlists to support icarus simulator"); + /* Add an option '--verbose' */ + shell_cmd_write_fabric_verilog.add_option("verbose", false, "Enable verbose output"); + + /* Add command 'write_fabric_verilog' to the Shell */ + ShellCommandId shell_cmd_write_fabric_verilog_id = shell.add_command(shell_cmd_write_fabric_verilog, "generate Verilog netlists modeling full FPGA fabric"); + shell.set_command_class(shell_cmd_write_fabric_verilog_id, openfpga_verilog_cmd_class); + shell.set_command_execute_function(shell_cmd_write_fabric_verilog_id, write_fabric_verilog); + + /* The 'build_fabric' command should NOT be executed before 'link_openfpga_arch' */ + std::vector cmd_dependency_write_fabric_verilog; + cmd_dependency_write_fabric_verilog.push_back(shell_cmd_build_fabric_id); + shell.set_command_dependency(shell_cmd_write_fabric_verilog_id, cmd_dependency_write_fabric_verilog); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_verilog_command.h b/openfpga/src/base/openfpga_verilog_command.h new file mode 100644 index 000000000..a99b809dd --- /dev/null +++ b/openfpga/src/base/openfpga_verilog_command.h @@ -0,0 +1,21 @@ +#ifndef OPENFPGA_VERILOG_COMMAND_H +#define OPENFPGA_VERILOG_COMMAND_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "shell.h" +#include "openfpga_context.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void add_openfpga_verilog_commands(openfpga::Shell& shell); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_verilog/verilog_api.cpp b/openfpga/src/fpga_verilog/verilog_api.cpp index 7de1b95e9..0ad2cecb6 100644 --- a/openfpga/src/fpga_verilog/verilog_api.cpp +++ b/openfpga/src/fpga_verilog/verilog_api.cpp @@ -40,17 +40,17 @@ namespace openfpga { * 7. Pre-configured testbench, which can skip the configuration phase and pre-configure the FPGA module. This testbench is created for quick verification and formal verification purpose. * 8. Verilog netlist including preprocessing flags and all the Verilog netlists that have been generated ********************************************************************/ -void fabric_verilog(ModuleManager& module_manager, - const CircuitLibrary& circuit_lib, - const MuxLibrary& mux_lib, - const DeviceGrid& grids, - const DeviceRRGSB& device_rr_gsb, - const std::string& output_directory, - const bool& compress_routing, - const bool& dump_explict_verilog, - const bool& verbose) { +void fpga_fabric_verilog(const ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const MuxLibrary& mux_lib, + const DeviceGrid& grids, + const DeviceRRGSB& device_rr_gsb, + const std::string& output_directory, + const bool& compress_routing, + const bool& dump_explict_verilog, + const bool& verbose) { - vtr::ScopedStartFinishTimer timer("Generate Verilog netlists for FPGA fabric\n"); + vtr::ScopedStartFinishTimer timer("Write Verilog netlists for FPGA fabric\n"); std::string src_dir_path = format_dir_path(output_directory); diff --git a/openfpga/src/fpga_verilog/verilog_api.h b/openfpga/src/fpga_verilog/verilog_api.h index 9d601ee46..91f78e918 100644 --- a/openfpga/src/fpga_verilog/verilog_api.h +++ b/openfpga/src/fpga_verilog/verilog_api.h @@ -21,15 +21,15 @@ /* begin namespace openfpga */ namespace openfpga { -void fabric_verilog(ModuleManager& module_manager, - const CircuitLibrary& circuit_lib, - const MuxLibrary& mux_lib, - const DeviceGrid& grids, - const DeviceRRGSB& device_rr_gsb, - const std::string& output_directory, - const bool& compress_routing, - const bool& dump_explict_verilog, - const bool& verbose); +void fpga_fabric_verilog(const ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const MuxLibrary& mux_lib, + const DeviceGrid& grids, + const DeviceRRGSB& device_rr_gsb, + const std::string& output_directory, + const bool& compress_routing, + const bool& dump_explict_verilog, + const bool& verbose); } /* end namespace openfpga */ diff --git a/openfpga/src/main.cpp b/openfpga/src/main.cpp index c7c31b00c..792223e3c 100644 --- a/openfpga/src/main.cpp +++ b/openfpga/src/main.cpp @@ -12,6 +12,7 @@ /* Header file from openfpga */ #include "vpr_command.h" #include "openfpga_setup_command.h" +#include "openfpga_verilog_command.h" #include "basic_command.h" #include "openfpga_title.h" @@ -52,6 +53,9 @@ int main(int argc, char** argv) { /* Add openfpga setup commands */ openfpga::add_openfpga_setup_commands(shell); + /* Add openfpga verilog commands */ + openfpga::add_openfpga_verilog_commands(shell); + /* Add basic commands: exit, help, etc. * Note: * This MUST be the last command group to be added! diff --git a/openfpga/test_script/s298_k6_frac.openfpga b/openfpga/test_script/s298_k6_frac.openfpga index 9d4edcced..1dece6d57 100644 --- a/openfpga/test_script/s298_k6_frac.openfpga +++ b/openfpga/test_script/s298_k6_frac.openfpga @@ -21,5 +21,9 @@ lut_truth_table_fixup #--verbose # - Enable pin duplication on grid modules build_fabric --compress_routing --duplicate_grid_pin --verbose +# Write the Verilog netlit for FPGA fabric +# - Enable the use of explicit port mapping in Verilog netlist +write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --verbose + # Finish and exit OpenFPGA exit From da79ef687c53351b4a184f1e7ab02156f05dd9d3 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 15 Feb 2020 20:54:37 -0700 Subject: [PATCH 146/645] add missing files --- openfpga/src/base/openfpga_verilog.cpp | 38 ++++++++++++++++++++++++++ openfpga/src/base/openfpga_verilog.h | 23 ++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 openfpga/src/base/openfpga_verilog.cpp create mode 100644 openfpga/src/base/openfpga_verilog.h diff --git a/openfpga/src/base/openfpga_verilog.cpp b/openfpga/src/base/openfpga_verilog.cpp new file mode 100644 index 000000000..076127f4a --- /dev/null +++ b/openfpga/src/base/openfpga_verilog.cpp @@ -0,0 +1,38 @@ +/******************************************************************** + * This file includes functions to compress the hierachy of routing architecture + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_time.h" +#include "vtr_log.h" + +#include "verilog_api.h" +#include "openfpga_verilog.h" + +/* Include global variables of VPR */ +#include "globals.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * A wrapper function to call the fabric_verilog function of FPGA-Verilog + *******************************************************************/ +void write_fabric_verilog(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context) { + + CommandOptionId opt_output_dir = cmd.option("file"); + CommandOptionId opt_explicit_port_mapping = cmd.option("explicit_port_mapping"); + CommandOptionId opt_verbose = cmd.option("verbose"); + + fpga_fabric_verilog(openfpga_ctx.module_graph(), + openfpga_ctx.arch().circuit_lib, + openfpga_ctx.mux_lib(), + g_vpr_ctx.device().grid, + openfpga_ctx.device_rr_gsb(), + cmd_context.option_value(cmd, opt_output_dir), + openfpga_ctx.flow_manager().compress_routing(), + cmd_context.option_enable(cmd, opt_explicit_port_mapping), + cmd_context.option_enable(cmd, opt_verbose)); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_verilog.h b/openfpga/src/base/openfpga_verilog.h new file mode 100644 index 000000000..a62d19411 --- /dev/null +++ b/openfpga/src/base/openfpga_verilog.h @@ -0,0 +1,23 @@ +#ifndef OPENFPGA_VERILOG_H +#define OPENFPGA_VERILOG_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "command.h" +#include "command_context.h" +#include "openfpga_context.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void write_fabric_verilog(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context); + +} /* end namespace openfpga */ + +#endif From bf54be3d00ac77b0d98e575804b03ef83007db6c Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 15 Feb 2020 21:39:47 -0700 Subject: [PATCH 147/645] add option data structure for FPGA Verilog --- openfpga/src/base/openfpga_verilog.cpp | 20 ++++- openfpga/src/fpga_verilog/verilog_api.cpp | 9 +-- openfpga/src/fpga_verilog/verilog_api.h | 6 +- openfpga/src/fpga_verilog/verilog_options.cpp | 73 +++++++++++++++++++ openfpga/src/fpga_verilog/verilog_options.h | 48 ++++++++++++ 5 files changed, 142 insertions(+), 14 deletions(-) create mode 100644 openfpga/src/fpga_verilog/verilog_options.cpp create mode 100644 openfpga/src/fpga_verilog/verilog_options.h diff --git a/openfpga/src/base/openfpga_verilog.cpp b/openfpga/src/base/openfpga_verilog.cpp index 076127f4a..2612fe76a 100644 --- a/openfpga/src/base/openfpga_verilog.cpp +++ b/openfpga/src/base/openfpga_verilog.cpp @@ -22,17 +22,29 @@ void write_fabric_verilog(OpenfpgaContext& openfpga_ctx, CommandOptionId opt_output_dir = cmd.option("file"); CommandOptionId opt_explicit_port_mapping = cmd.option("explicit_port_mapping"); + CommandOptionId opt_include_timing = cmd.option("include_timing"); + CommandOptionId opt_include_signal_init = cmd.option("include_signal_init"); + CommandOptionId opt_support_icarus_simulator = cmd.option("support_icarus_simulator"); CommandOptionId opt_verbose = cmd.option("verbose"); + + /* This is an intermediate data structure which is designed to modularize the FPGA-Verilog + * Keep it independent from any other outside data structures + */ + FabricVerilogOption options; + options.set_output_directory(cmd_context.option_value(cmd, opt_output_dir)); + options.set_explicit_port_mapping(cmd_context.option_enable(cmd, opt_explicit_port_mapping)); + options.set_include_timing(cmd_context.option_enable(cmd, opt_include_timing)); + options.set_include_signal_init(cmd_context.option_enable(cmd, opt_include_signal_init)); + options.set_support_icarus_simulator(cmd_context.option_enable(cmd, opt_support_icarus_simulator)); + options.set_verbose_output(cmd_context.option_enable(cmd, opt_verbose)); + options.set_compress_routing(openfpga_ctx.flow_manager().compress_routing()); fpga_fabric_verilog(openfpga_ctx.module_graph(), openfpga_ctx.arch().circuit_lib, openfpga_ctx.mux_lib(), g_vpr_ctx.device().grid, openfpga_ctx.device_rr_gsb(), - cmd_context.option_value(cmd, opt_output_dir), - openfpga_ctx.flow_manager().compress_routing(), - cmd_context.option_enable(cmd, opt_explicit_port_mapping), - cmd_context.option_enable(cmd, opt_verbose)); + options); } } /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_api.cpp b/openfpga/src/fpga_verilog/verilog_api.cpp index 0ad2cecb6..d8dd8dab8 100644 --- a/openfpga/src/fpga_verilog/verilog_api.cpp +++ b/openfpga/src/fpga_verilog/verilog_api.cpp @@ -45,14 +45,11 @@ void fpga_fabric_verilog(const ModuleManager& module_manager, const MuxLibrary& mux_lib, const DeviceGrid& grids, const DeviceRRGSB& device_rr_gsb, - const std::string& output_directory, - const bool& compress_routing, - const bool& dump_explict_verilog, - const bool& verbose) { + const FabricVerilogOption& options) { vtr::ScopedStartFinishTimer timer("Write Verilog netlists for FPGA fabric\n"); - std::string src_dir_path = format_dir_path(output_directory); + std::string src_dir_path = format_dir_path(options.output_directory()); /* Create directories */ create_dir_path(src_dir_path.c_str()); @@ -110,7 +107,7 @@ void fpga_fabric_verilog(const ModuleManager& module_manager, // dump_explicit_verilog); /* Given a brief stats on how many Verilog modules have been written to files */ - VTR_LOGV(verbose, + VTR_LOGV(options.verbose_output(), "Outputted %lu Verilog modules in total\n", module_manager.num_modules()); } diff --git a/openfpga/src/fpga_verilog/verilog_api.h b/openfpga/src/fpga_verilog/verilog_api.h index 91f78e918..a24c0a1e8 100644 --- a/openfpga/src/fpga_verilog/verilog_api.h +++ b/openfpga/src/fpga_verilog/verilog_api.h @@ -13,6 +13,7 @@ #include "device_grid.h" #include "device_rr_gsb.h" #include "module_manager.h" +#include "verilog_options.h" /******************************************************************** * Function declaration @@ -26,10 +27,7 @@ void fpga_fabric_verilog(const ModuleManager& module_manager, const MuxLibrary& mux_lib, const DeviceGrid& grids, const DeviceRRGSB& device_rr_gsb, - const std::string& output_directory, - const bool& compress_routing, - const bool& dump_explict_verilog, - const bool& verbose); + const FabricVerilogOption& options); } /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_options.cpp b/openfpga/src/fpga_verilog/verilog_options.cpp new file mode 100644 index 000000000..a0dfd7e2a --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_options.cpp @@ -0,0 +1,73 @@ +/****************************************************************************** + * Memember functions for data structure FabricVerilogOption + ******************************************************************************/ +#include "vtr_assert.h" + +#include "verilog_options.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/************************************************** + * Public Accessors + *************************************************/ +std::string FabricVerilogOption::output_directory() const { + return output_directory_; +} + +bool FabricVerilogOption::support_icarus_simulator() const { + return support_icarus_simulator_; +} + +bool FabricVerilogOption::include_timing() const { + return include_timing_; +} + +bool FabricVerilogOption::include_signal_init() const { + return include_signal_init_; +} + +bool FabricVerilogOption::explicit_port_mapping() const { + return explicit_port_mapping_; +} + +bool FabricVerilogOption::compress_routing() const { + return compress_routing_; +} + +bool FabricVerilogOption::verbose_output() const { + return verbose_output_; +} + +/****************************************************************************** + * Private Mutators + ******************************************************************************/ +void FabricVerilogOption::set_output_directory(const std::string& output_dir) { + output_directory_ = output_dir; +} + +void FabricVerilogOption::set_support_icarus_simulator(const bool& enabled) { + support_icarus_simulator_ = enabled; +} + +void FabricVerilogOption::set_include_timing(const bool& enabled) { + include_timing_ = enabled; +} + +void FabricVerilogOption::set_include_signal_init(const bool& enabled) { + include_signal_init_ = enabled; +} + +void FabricVerilogOption::set_explicit_port_mapping(const bool& enabled) { + explicit_port_mapping_ = enabled; +} + +void FabricVerilogOption::set_compress_routing(const bool& enabled) { + compress_routing_ = enabled; +} + +void FabricVerilogOption::set_verbose_output(const bool& enabled) { + verbose_output_ = enabled; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_options.h b/openfpga/src/fpga_verilog/verilog_options.h new file mode 100644 index 000000000..336ec542a --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_options.h @@ -0,0 +1,48 @@ +#ifndef VERILOG_OPTIONS_H +#define VERILOG_OPTIONS_H + +/******************************************************************** + * Include header files required by the data structure definition + *******************************************************************/ +#include + +/* Begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * FlowManager aims to resolve the dependency between OpenFPGA functional + * code blocks + * It can provide flags for downstream modules about if the data structures + * they require have already been constructed + * + *******************************************************************/ +class FabricVerilogOption { + public: /* Public accessors */ + std::string output_directory() const; + bool support_icarus_simulator() const; + bool include_timing() const; + bool include_signal_init() const; + bool explicit_port_mapping() const; + bool compress_routing() const; + bool verbose_output() const; + public: /* Public mutators */ + void set_output_directory(const std::string& output_dir); + void set_support_icarus_simulator(const bool& enabled); + void set_include_timing(const bool& enabled); + void set_include_signal_init(const bool& enabled); + void set_explicit_port_mapping(const bool& enabled); + void set_compress_routing(const bool& enabled); + void set_verbose_output(const bool& enabled); + private: /* Internal Data */ + std::string output_directory_; + bool support_icarus_simulator_; + bool include_signal_init_; + bool include_timing_; + bool explicit_port_mapping_; + bool compress_routing_; + bool verbose_output_; +}; + +} /* End namespace openfpga*/ + +#endif From 0d5292ad0da38f66440805281288ce6823f1b9ed Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 15 Feb 2020 23:26:59 -0700 Subject: [PATCH 148/645] adapt verilog writer utils --- .../src/fpga_verilog/verilog_writer_utils.cpp | 1397 +++++++++++++++++ .../src/fpga_verilog/verilog_writer_utils.h | 187 +++ 2 files changed, 1584 insertions(+) create mode 100644 openfpga/src/fpga_verilog/verilog_writer_utils.cpp create mode 100644 openfpga/src/fpga_verilog/verilog_writer_utils.h diff --git a/openfpga/src/fpga_verilog/verilog_writer_utils.cpp b/openfpga/src/fpga_verilog/verilog_writer_utils.cpp new file mode 100644 index 000000000..5798b05fc --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_writer_utils.cpp @@ -0,0 +1,1397 @@ +/************************************************ + * Include functions for most frequently + * used Verilog writers + ***********************************************/ +#include +#include +#include +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" + +/* Headers from readarchopenfpga library */ +#include "circuit_types.h" + +/* Headers from openfpgautil library */ +#include "openfpga_digest.h" + +#include "openfpga_naming.h" +#include "circuit_library_utils.h" +#include "verilog_constants.h" +#include "verilog_writer_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/************************************************ + * Generate header comments for a Verilog netlist + * include the description + ***********************************************/ +void print_verilog_file_header(std::fstream& fp, + const std::string& usage) { + valid_file_stream(fp); + + auto end = std::chrono::system_clock::now(); + std::time_t end_time = std::chrono::system_clock::to_time_t(end); + + fp << "//-------------------------------------------" << std::endl; + fp << "//\tFPGA Synthesizable Verilog Netlist" << std::endl; + fp << "//\tDescription: " << usage << std::endl; + fp << "//\tAuthor: Xifan TANG" << std::endl; + fp << "//\tOrganization: University of Utah" << std::endl; + fp << "//\tDate: " << std::ctime(&end_time) ; + fp << "//-------------------------------------------" << std::endl; + fp << "//----- Time scale -----" << std::endl; + fp << "`timescale 1ns / 1ps" << std::endl; + fp << std::endl; +} + +/******************************************************************** + * Print Verilog codes to include a netlist + *******************************************************************/ +void print_verilog_include_netlist(std::fstream& fp, + const std::string& netlist_name) { + valid_file_stream(fp); + + fp << "`include \"" << netlist_name << "\"" << std::endl; +} + +/******************************************************************** + * Print Verilog codes to define a preprocessing flag + *******************************************************************/ +void print_verilog_define_flag(std::fstream& fp, + const std::string& flag_name, + const int& flag_value) { + valid_file_stream(fp); + + fp << "`define " << flag_name << " " << flag_value << std::endl; +} + +/************************************************ + * Generate include files for a Verilog netlist + ***********************************************/ +void print_verilog_include_defines_preproc_file(std::fstream& fp, + const std::string& verilog_dir) { + + /* Generate the file name */ + std::string include_file_path = format_dir_path(verilog_dir); + include_file_path += std::string(DEFINES_VERILOG_FILE_NAME); + + print_verilog_include_netlist(fp, include_file_path); +} + +/************************************************ + * Print a Verilog comment line + ***********************************************/ +void print_verilog_comment(std::fstream& fp, + const std::string& comment) { + valid_file_stream(fp); + + fp << "// " << comment << std::endl; +} + +/************************************************ + * Print the declaration of a Verilog preprocessing flag + ***********************************************/ +void print_verilog_preprocessing_flag(std::fstream& fp, + const std::string& preproc_flag) { + valid_file_stream(fp); + + fp << "`ifdef " << preproc_flag << std::endl; +} + +/************************************************ + * Print the endif of a Verilog preprocessing flag + ***********************************************/ +void print_verilog_endif(std::fstream& fp) { + valid_file_stream(fp); + + fp << "`endif" << std::endl; +} + +/************************************************ + * Print a Verilog module definition + * We use the following format: + * module (); + ***********************************************/ +void print_verilog_module_definition(std::fstream& fp, + const ModuleManager& module_manager, const ModuleId& module_id) { + valid_file_stream(fp); + + print_verilog_comment(fp, std::string("----- Verilog module for " + module_manager.module_name(module_id) + " -----")); + + std::string module_head_line = "module " + module_manager.module_name(module_id) + "("; + fp << module_head_line; + + /* port type2type mapping */ + std::map port_type2type_map; + port_type2type_map[ModuleManager::MODULE_GLOBAL_PORT] = VERILOG_PORT_CONKT; + port_type2type_map[ModuleManager::MODULE_GPIO_PORT] = VERILOG_PORT_CONKT; + port_type2type_map[ModuleManager::MODULE_INOUT_PORT] = VERILOG_PORT_CONKT; + port_type2type_map[ModuleManager::MODULE_INPUT_PORT] = VERILOG_PORT_CONKT; + port_type2type_map[ModuleManager::MODULE_OUTPUT_PORT] = VERILOG_PORT_CONKT; + port_type2type_map[ModuleManager::MODULE_CLOCK_PORT] = VERILOG_PORT_CONKT; + + /* Port sequence: global, inout, input, output and clock ports, */ + size_t port_cnt = 0; + bool printed_ifdef = false; /* A flag to tell if an ifdef has been printed for the last port */ + for (const auto& kv : port_type2type_map) { + for (const auto& port : module_manager.module_ports_by_type(module_id, kv.first)) { + if (0 != port_cnt) { + /* Do not dump a comma for the first port */ + fp << "," << std::endl; + } + + if (true == printed_ifdef) { + /* Print an endif to pair the ifdef */ + print_verilog_endif(fp); + /* Reset the flag */ + printed_ifdef = false; + } + + ModulePortId port_id = module_manager.find_module_port(module_id, port.get_name()); + VTR_ASSERT(ModulePortId::INVALID() != port_id); + /* Print pre-processing flag for a port, if defined */ + std::string preproc_flag = module_manager.port_preproc_flag(module_id, port_id); + if (false == preproc_flag.empty()) { + /* Print an ifdef Verilog syntax */ + print_verilog_preprocessing_flag(fp, preproc_flag); + /* Raise the flag */ + printed_ifdef = true; + } + + /* Create a space for "module " except the first line! */ + if (0 != port_cnt) { + std::string port_whitespace(module_head_line.length(), ' '); + fp << port_whitespace; + } + /* Print port: only the port name is enough */ + fp << port.get_name(); + + /* Increase the counter */ + port_cnt++; + } + } + fp << ");" << std::endl; +} + +/************************************************ + * Print a Verilog module ports based on the module id + ***********************************************/ +void print_verilog_module_ports(std::fstream& fp, + const ModuleManager& module_manager, const ModuleId& module_id) { + valid_file_stream(fp); + + /* port type2type mapping */ + std::map port_type2type_map; + port_type2type_map[ModuleManager::MODULE_GLOBAL_PORT] = VERILOG_PORT_INPUT; + port_type2type_map[ModuleManager::MODULE_GPIO_PORT] = VERILOG_PORT_INOUT; + port_type2type_map[ModuleManager::MODULE_INOUT_PORT] = VERILOG_PORT_INOUT; + port_type2type_map[ModuleManager::MODULE_INPUT_PORT] = VERILOG_PORT_INPUT; + port_type2type_map[ModuleManager::MODULE_OUTPUT_PORT] = VERILOG_PORT_OUTPUT; + port_type2type_map[ModuleManager::MODULE_CLOCK_PORT] = VERILOG_PORT_INPUT; + + /* Port sequence: global, inout, input, output and clock ports, */ + for (const auto& kv : port_type2type_map) { + for (const auto& port : module_manager.module_ports_by_type(module_id, kv.first)) { + ModulePortId port_id = module_manager.find_module_port(module_id, port.get_name()); + VTR_ASSERT(ModulePortId::INVALID() != port_id); + /* Print pre-processing flag for a port, if defined */ + std::string preproc_flag = module_manager.port_preproc_flag(module_id, port_id); + if (false == preproc_flag.empty()) { + /* Print an ifdef Verilog syntax */ + print_verilog_preprocessing_flag(fp, preproc_flag); + } + + /* Print port */ + fp << "//----- " << module_manager.module_port_type_str(kv.first) << " -----" << std::endl; + fp << generate_verilog_port(kv.second, port); + fp << ";" << std::endl; + + if (false == preproc_flag.empty()) { + /* Print an endif to pair the ifdef */ + print_verilog_endif(fp); + } + } + } + + /* Output any port that is also wire connection */ + fp << std::endl; + fp << "//----- BEGIN wire-connection ports -----" << std::endl; + for (const auto& kv : port_type2type_map) { + for (const auto& port : module_manager.module_ports_by_type(module_id, kv.first)) { + /* Skip the ports that are not registered */ + ModulePortId port_id = module_manager.find_module_port(module_id, port.get_name()); + VTR_ASSERT(ModulePortId::INVALID() != port_id); + if (false == module_manager.port_is_wire(module_id, port_id)) { + continue; + } + + /* Print pre-processing flag for a port, if defined */ + std::string preproc_flag = module_manager.port_preproc_flag(module_id, port_id); + if (false == preproc_flag.empty()) { + /* Print an ifdef Verilog syntax */ + print_verilog_preprocessing_flag(fp, preproc_flag); + } + + /* Print port */ + fp << generate_verilog_port(VERILOG_PORT_WIRE, port); + fp << ";" << std::endl; + + if (false == preproc_flag.empty()) { + /* Print an endif to pair the ifdef */ + print_verilog_endif(fp); + } + } + } + fp << "//----- END wire-connection ports -----" << std::endl; + fp << std::endl; + + + /* Output any port that is registered */ + fp << std::endl; + fp << "//----- BEGIN Registered ports -----" << std::endl; + for (const auto& kv : port_type2type_map) { + for (const auto& port : module_manager.module_ports_by_type(module_id, kv.first)) { + /* Skip the ports that are not registered */ + ModulePortId port_id = module_manager.find_module_port(module_id, port.get_name()); + VTR_ASSERT(ModulePortId::INVALID() != port_id); + if (false == module_manager.port_is_register(module_id, port_id)) { + continue; + } + + /* Print pre-processing flag for a port, if defined */ + std::string preproc_flag = module_manager.port_preproc_flag(module_id, port_id); + if (false == preproc_flag.empty()) { + /* Print an ifdef Verilog syntax */ + print_verilog_preprocessing_flag(fp, preproc_flag); + } + + /* Print port */ + fp << generate_verilog_port(VERILOG_PORT_REG, port); + fp << ";" << std::endl; + + if (false == preproc_flag.empty()) { + /* Print an endif to pair the ifdef */ + print_verilog_endif(fp); + } + } + } + fp << "//----- END Registered ports -----" << std::endl; + fp << std::endl; +} + +/************************************************ + * Print a Verilog module declaration (definition + port list + * We use the following format: + * module (); + * + ***********************************************/ +void print_verilog_module_declaration(std::fstream& fp, + const ModuleManager& module_manager, const ModuleId& module_id) { + valid_file_stream(fp); + + print_verilog_module_definition(fp, module_manager, module_id); + + print_verilog_module_ports(fp, module_manager, module_id); +} + + +/******************************************************************** + * Print an instance in Verilog format (a generic version) + * This function will require user to provide an instance name + * + * This function will output the port map by referring to a port-to-port + * mapping: + * -> + * The key of the port-to-port mapping is the port name of the module: + * The value of the port-to-port mapping is the port information of the instance + * With link between module and instance, the function can output a Verilog + * instance easily, supporting both explicit port mapping: + * .() + * and inexplicit port mapping + * + * + * Note that, it is not necessary that the port-to-port mapping + * covers all the module ports. + * Any instance/module port which are not specified in the port-to-port + * mapping will be output by the module port name. + *******************************************************************/ +void print_verilog_module_instance(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& module_id, + const std::string& instance_name, + const std::map& port2port_name_map, + const bool& use_explicit_port_map) { + + valid_file_stream(fp); + + /* Check: all the key ports in the port2port_name_map does exist in the child module */ + for (const auto& kv : port2port_name_map) { + ModulePortId module_port_id = module_manager.find_module_port(module_id, kv.first); + VTR_ASSERT(ModulePortId::INVALID() != module_port_id); + } + + /* Print module name */ + fp << "\t" << module_manager.module_name(module_id) << " "; + /* Print instance name */ + fp << instance_name << " (" << std::endl; + + /* Print each port with/without explicit port map */ + /* port type2type mapping */ + std::map port_type2type_map; + port_type2type_map[ModuleManager::MODULE_GLOBAL_PORT] = VERILOG_PORT_CONKT; + port_type2type_map[ModuleManager::MODULE_GPIO_PORT] = VERILOG_PORT_CONKT; + port_type2type_map[ModuleManager::MODULE_INOUT_PORT] = VERILOG_PORT_CONKT; + port_type2type_map[ModuleManager::MODULE_INPUT_PORT] = VERILOG_PORT_CONKT; + port_type2type_map[ModuleManager::MODULE_OUTPUT_PORT] = VERILOG_PORT_CONKT; + port_type2type_map[ModuleManager::MODULE_CLOCK_PORT] = VERILOG_PORT_CONKT; + + /* Port sequence: global, inout, input, output and clock ports, */ + size_t port_cnt = 0; + for (const auto& kv : port_type2type_map) { + for (const auto& port : module_manager.module_ports_by_type(module_id, kv.first)) { + if (0 != port_cnt) { + /* Do not dump a comma for the first port */ + fp << "," << std::endl; + } + /* Print port */ + fp << "\t\t"; + /* if explicit port map is required, output the port name */ + if (true == use_explicit_port_map) { + fp << "." << port.get_name() << "("; + } + /* Try to find the instanced port name in the name map */ + if (port2port_name_map.find(port.get_name()) != port2port_name_map.end()) { + /* Found it, we assign the port name */ + /* TODO: make sure the port width matches! */ + ModulePortId module_port_id = module_manager.find_module_port(module_id, port.get_name()); + /* Get the port from module */ + BasicPort module_port = module_manager.module_port(module_id, module_port_id); + VTR_ASSERT(module_port.get_width() == port2port_name_map.at(port.get_name()).get_width()); + fp << generate_verilog_port(kv.second, port2port_name_map.at(port.get_name())); + } else { + /* Not found, we give the default port name */ + fp << generate_verilog_port(kv.second, port); + } + /* if explicit port map is required, output the pair of branket */ + if (true == use_explicit_port_map) { + fp << ")"; + } + port_cnt++; + } + } + + /* Print an end to the instance */ + fp << ");" << std::endl; +} + + +/************************************************ + * Print an instance for a Verilog module + * This function is a wrapper for the generic version of + * print_verilog_module_instance() + * This function create an instance name based on the index + * of the child module in its parent module + ***********************************************/ +void print_verilog_module_instance(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& parent_module_id, const ModuleId& child_module_id, + const std::map& port2port_name_map, + const bool& use_explicit_port_map) { + + /* Create instance name, _ */ + std::string instance_name = module_manager.module_name(child_module_id) + + "_" + + std::to_string(module_manager.num_instance(parent_module_id, child_module_id)) + + "_"; + + print_verilog_module_instance(fp, module_manager, child_module_id, instance_name, + port2port_name_map, use_explicit_port_map); +} + +/************************************************ + * Print an end line for a Verilog module + ***********************************************/ +void print_verilog_module_end(std::fstream& fp, + const std::string& module_name) { + valid_file_stream(fp); + + fp << "endmodule" << std::endl; + print_verilog_comment(fp, std::string("----- END Verilog module for " + module_name + " -----")); + fp << std::endl; +} + +/************************************************ + * Generate a string of a Verilog port + ***********************************************/ +std::string generate_verilog_port(const enum e_dump_verilog_port_type& verilog_port_type, + const BasicPort& port_info) { + std::string verilog_line; + + /* Ensure the port type is valid */ + VTR_ASSERT(verilog_port_type < NUM_VERILOG_PORT_TYPES); + + std::string size_str = "[" + std::to_string(port_info.get_lsb()) + ":" + std::to_string(port_info.get_msb()) + "]"; + + /* Only connection require a format of [:] + * others require a format of [:] + */ + if (VERILOG_PORT_CONKT == verilog_port_type) { + /* When LSB == MSB, we can use a simplified format []*/ + if ( 1 == port_info.get_width()) { + size_str = "[" + std::to_string(port_info.get_lsb()) + "]"; + } + verilog_line = port_info.get_name() + size_str; + } else { + verilog_line = VERILOG_PORT_TYPE_STRING[verilog_port_type]; + verilog_line += " " + size_str + " " + port_info.get_name(); + } + + return verilog_line; +} + +/******************************************************************** + * Evaluate if two Verilog ports can be merged: + * If the port name is same, it can merged + *******************************************************************/ +bool two_verilog_ports_mergeable(const BasicPort& portA, + const BasicPort& portB) { + if (0 == portA.get_name().compare(portB.get_name())) { + return true; + } + return false; +} + +/******************************************************************** + * Merge two Verilog ports, return the merged port + * The ports should have the same name + * The new LSB will be minimum of the LSBs of the two ports + * The new MSB will the maximum of the MSBs of the two ports + *******************************************************************/ +BasicPort merge_two_verilog_ports(const BasicPort& portA, + const BasicPort& portB) { + BasicPort merged_port; + + VTR_ASSERT(true == two_verilog_ports_mergeable(portA, portB)); + + merged_port.set_name(portA.get_name()); + merged_port.set_lsb((size_t)std::min((int)portA.get_lsb(), (int)portB.get_lsb())); + merged_port.set_msb((size_t)std::max((int)portA.get_msb(), (int)portB.get_msb())); + + return merged_port; +} + +/************************************************ + * This function takes a list of ports and + * combine the port string by comparing the name + * and width of ports. + * For example, two ports A and B share the same name is + * mergable as long as A's MSB + 1 == B's LSB + * Note that the port sequence really matters! + * This function will NOT change the sequence + * of ports in the list port_info + ***********************************************/ +std::vector combine_verilog_ports(const std::vector& ports) { + std::vector merged_ports; + + /* Directly return if there are no ports */ + if (0 == ports.size()) { + return merged_ports; + } + /* Push the first port to the merged ports */ + merged_ports.push_back(ports[0]); + + /* Iterate over ports */ + for (const auto& port : ports) { + /* Bypass the first port, it is already in the list */ + if (&port == &ports[0]) { + continue; + } + /* Identify if the port name can be potentially merged: if the port name is already in the merged port list, it may be merged */ + bool merged = false; + for (auto& merged_port : merged_ports) { + if (false == port.mergeable(merged_port)) { + /* Unable to merge, Go to next */ + continue; + } + /* May be merged, check LSB of port and MSB of merged_port */ + if (merged_port.get_msb() + 1 != port.get_lsb()) { + /* Unable to merge, Go to next */ + continue; + } + /* Reach here, we should merge the ports, + * LSB of merged_port remains the same, + * MSB of merged_port will be updated + * to the MSB of port + */ + merged_port.set_msb(port.get_msb()); + merged = true; + break; + } + if (false == merged) { + /* Unable to merge, add the port to merged port list */ + merged_ports.push_back(port); + } + } + + return merged_ports; +} + +/************************************************ + * Generate the string of a list of verilog ports + ***********************************************/ +std::string generate_verilog_ports(const std::vector& merged_ports) { + + /* Output the string of ports: + * If there is only one port in the merged_port list + * we only output the port. + * If there are more than one port in the merged port list, we output an concatenated port: + * {, , ... } + */ + VTR_ASSERT(0 < merged_ports.size()); + if ( 1 == merged_ports.size()) { + /* Use connection type of verilog port */ + return generate_verilog_port(VERILOG_PORT_CONKT, merged_ports[0]); + } + + std::string verilog_line = "{"; + for (const auto& port : merged_ports) { + /* The first port does not need a comma */ + if (&port != &merged_ports[0]) { + verilog_line += ", "; + } + verilog_line += generate_verilog_port(VERILOG_PORT_CONKT, port); + } + verilog_line += "}"; + + return verilog_line; +} + +/******************************************************************** + * Generate a bus port (could be used to create a local wire) + * for a list of Verilog ports + * The bus port will be created by aggregating the ports in the list + * A bus port name may be need only there are many ports with + * different names. It is hard to name the bus port + *******************************************************************/ +BasicPort generate_verilog_bus_port(const std::vector& input_ports, + const std::string& bus_port_name) { + /* Try to combine the ports */ + std::vector combined_input_ports = combine_verilog_ports(input_ports); + + /* Create a port data structure that is to be returned */ + BasicPort bus_port; + + if (1 == combined_input_ports.size()) { + bus_port = combined_input_ports[0]; + } else { + /* TODO: the naming could be more flexible? */ + bus_port.set_name(bus_port_name); + /* Deposite a [0:0] port */ + bus_port.set_width(1); + for (const auto& port : combined_input_ports) { + bus_port.combine(port); + } + } + + return bus_port; +} + +/******************************************************************** + * Generate a bus wire declaration for a list of Verilog ports + * Output ports: the local_wire name + * Input ports: the driving ports + * When there are more than two ports, a bus wiring will be created + * {, , ... } + *******************************************************************/ +std::string generate_verilog_local_wire(const BasicPort& output_port, + const std::vector& input_ports) { + /* Try to combine the ports */ + std::vector combined_input_ports = combine_verilog_ports(input_ports); + + /* If we have more than 1 port in the combined ports , + * output a local wire */ + VTR_ASSERT(0 < combined_input_ports.size()); + + /* Must check: the port width matches */ + size_t input_ports_width = 0; + for (const auto& port : combined_input_ports) { + /* We must have valid ports! */ + VTR_ASSERT( 0 < port.get_width() ); + input_ports_width += port.get_width(); + } + VTR_ASSERT( input_ports_width == output_port.get_width() ); + + std::string wire_str; + wire_str += generate_verilog_port(VERILOG_PORT_WIRE, output_port); + wire_str += " = "; + wire_str += generate_verilog_ports(combined_input_ports); + wire_str += ";"; + + return wire_str; +} + +/******************************************************************** + * Generate a string for a constant value in Verilog format: + * <#.of bits>'b + *******************************************************************/ +std::string generate_verilog_constant_values(const std::vector& const_values) { + std::string str = std::to_string(const_values.size()); + str += "'b"; + for (const auto& val : const_values) { + str += std::to_string(val); + } + return str; +} + +/******************************************************************** + * Generate a verilog port with a deposite of constant values + ********************************************************************/ +std::string generate_verilog_port_constant_values(const BasicPort& output_port, + const std::vector& const_values) { + std::string port_str; + + /* Must check: the port width matches */ + VTR_ASSERT( const_values.size() == output_port.get_width() ); + + port_str = generate_verilog_port(VERILOG_PORT_CONKT, output_port); + port_str += " = "; + port_str += generate_verilog_constant_values(const_values); + return port_str; +} + +/******************************************************************** + * Generate a wire connection, that assigns constant values to a + * Verilog port + *******************************************************************/ +void print_verilog_wire_constant_values(std::fstream& fp, + const BasicPort& output_port, + const std::vector& const_values) { + /* Make sure we have a valid file handler*/ + valid_file_stream(fp); + + fp << "\t"; + fp << "assign "; + fp << generate_verilog_port_constant_values(output_port, const_values); + fp << ";" << std::endl; +} + +/******************************************************************** + * Deposit constant values to a Verilog port + *******************************************************************/ +void print_verilog_deposit_wire_constant_values(std::fstream& fp, + const BasicPort& output_port, + const std::vector& const_values) { + /* Make sure we have a valid file handler*/ + valid_file_stream(fp); + + fp << "\t"; + fp << "$deposit("; + fp << generate_verilog_port(VERILOG_PORT_CONKT, output_port); + fp << ", "; + fp << generate_verilog_constant_values(const_values); + fp << ");" << std::endl; +} + +/******************************************************************** + * Generate a wire connection, that assigns constant values to a + * Verilog port + *******************************************************************/ +void print_verilog_force_wire_constant_values(std::fstream& fp, + const BasicPort& output_port, + const std::vector& const_values) { + /* Make sure we have a valid file handler*/ + valid_file_stream(fp); + + fp << "\t"; + fp << "force "; + fp << generate_verilog_port_constant_values(output_port, const_values); + fp << ";" << std::endl; +} + +/******************************************************************** + * Generate a wire connection for two Verilog ports + * using "assign" syntax + *******************************************************************/ +void print_verilog_wire_connection(std::fstream& fp, + const BasicPort& output_port, + const BasicPort& input_port, + const bool& inverted) { + /* Make sure we have a valid file handler*/ + valid_file_stream(fp); + + /* Must check: the port width matches */ + VTR_ASSERT( input_port.get_width() == output_port.get_width() ); + + fp << "\t"; + fp << "assign "; + fp << generate_verilog_port(VERILOG_PORT_CONKT, output_port); + fp << " = "; + + if (true == inverted) { + fp << "~"; + } + + fp << generate_verilog_port(VERILOG_PORT_CONKT, input_port); + fp << ";" << std::endl; +} + +/******************************************************************** + * Generate a wire connection for two Verilog ports + * using "assign" syntax + *******************************************************************/ +void print_verilog_register_connection(std::fstream& fp, + const BasicPort& output_port, + const BasicPort& input_port, + const bool& inverted) { + /* Make sure we have a valid file handler*/ + valid_file_stream(fp); + + /* Must check: the port width matches */ + VTR_ASSERT( input_port.get_width() == output_port.get_width() ); + + fp << "\t"; + fp << generate_verilog_port(VERILOG_PORT_CONKT, output_port); + fp << " <= "; + + if (true == inverted) { + fp << "~"; + } + + fp << generate_verilog_port(VERILOG_PORT_CONKT, input_port); + fp << ";" << std::endl; +} + + +/******************************************************************** + * Generate an instance of a buffer module + * with given information about the input and output ports of instance + * + * Buffer instance + * +----------------------------------------+ + * instance_input_port --->| buffer_input_port buffer_output_port|----> instance_output_port + * +----------------------------------------+ + * + * Restrictions: + * Buffer must have only 1 input (non-global) port and 1 output (non-global) port + *******************************************************************/ +void print_verilog_buffer_instance(std::fstream& fp, + ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const ModuleId& parent_module_id, + const CircuitModelId& buffer_model, + const BasicPort& instance_input_port, + const BasicPort& instance_output_port) { + /* Make sure we have a valid file handler*/ + valid_file_stream(fp); + + /* To match the context, Buffer should have only 2 non-global ports: 1 input port and 1 output port */ + std::vector buffer_model_input_ports = circuit_lib.model_ports_by_type(buffer_model, CIRCUIT_MODEL_PORT_INPUT, true); + std::vector buffer_model_output_ports = circuit_lib.model_ports_by_type(buffer_model, CIRCUIT_MODEL_PORT_OUTPUT, true); + VTR_ASSERT(1 == buffer_model_input_ports.size()); + VTR_ASSERT(1 == buffer_model_output_ports.size()); + + /* Get the moduleId for the buffer module */ + ModuleId buffer_module_id = module_manager.find_module(circuit_lib.model_name(buffer_model)); + /* We must have one */ + VTR_ASSERT(ModuleId::INVALID() != buffer_module_id); + + /* Create a port-to-port map */ + std::map buffer_port2port_name_map; + + /* Build the link between buffer_input_port[0] and output_node_pre_buffer + * Build the link between buffer_output_port[0] and output_node_bufferred + */ + { /* Create a code block to accommodate the local variables */ + std::string module_input_port_name = circuit_lib.port_lib_name(buffer_model_input_ports[0]); + buffer_port2port_name_map[module_input_port_name] = instance_input_port; + std::string module_output_port_name = circuit_lib.port_lib_name(buffer_model_output_ports[0]); + buffer_port2port_name_map[module_output_port_name] = instance_output_port; + } + + /* Output an instance of the module */ + print_verilog_module_instance(fp, module_manager, parent_module_id, buffer_module_id, buffer_port2port_name_map, circuit_lib.dump_explicit_port_map(buffer_model)); + + /* IMPORTANT: this update MUST be called after the instance outputting!!!! + * update the module manager with the relationship between the parent and child modules + */ + module_manager.add_child_module(parent_module_id, buffer_module_id); +} + +/******************************************************************** + * Print local wires that are used for SRAM configuration + * The local wires are strongly dependent on the organization of SRAMs. + * Standalone SRAMs: + * ----------------- + * No need for local wires, their outputs are port of the module + * + * Module + * +------------------------------+ + * | Sub-module | + * | +---------------------+ | + * | | sram_out|---->|---->sram_out + * | | | | + * | | sram_out|---->|---->sram_out + * | | | | + * | +---------------------+ | + * +------------------------------+ + * + * Configuration chain-style + * ------------------------- + * wire [0:N] config_bus + * + * + * Module + * +--------------------------------------------------------------+ + * | config_bus config_bus config_bus config_bus | + * | [0] [1] [2] [N] | + * | | | | | | + * | v v v v | + * ccff_head| ----------+ +---------+ +------------+ +----------------|-> ccff_tail + * | | ^ | ^ | ^ | + * | head v |tail v | v | | + * | +----------+ +----------+ +----------+ | + * | | Memory | | Memory | | Memory | | + * | | Module | | Module | ... | Module | | + * | | [0] | | [1] | | [N] | | + * | +----------+ +----------+ +----------+ | + * | | | | | + * | v v v | + * | +----------+ +----------+ +----------+ | + * | | MUX | | MUX | | MUX | | + * | | Module | | Module | ... | Module | | + * | | [0] | | [1] | | [N] | | + * | +----------+ +----------+ +----------+ | + * | | + * +--------------------------------------------------------------+ + * + * Memory bank-style + * ----------------- + * two ports will be added, which are regular output and inverted output + * Note that the outputs are the data outputs of SRAMs + * BL/WLs of memory decoders are ports of module but not local wires + * + * Module + * +-------------------------------------------------+ + * | | + BL/WL bus --+--------+------------+-----------------+ | + * | | | | | + * | BL/WL v BL/WL v BL/WL v | + * | +----------+ +----------+ +----------+ | + * | | Memory | | Memory | | Memory | | + * | | Module | | Module | ... | Module | | + * | | [0] | | [1] | | [N] | | + * | +----------+ +----------+ +----------+ | + * | | | | | + * | v v v | + * | +----------+ +----------+ +----------+ | + * | | MUX | | MUX | | MUX | | + * | | Module | | Module | ... | Module | | + * | | [0] | | [1] | | [N] | | + * | +----------+ +----------+ +----------+ | + * | | + * +-------------------------------------------------+ + * + ********************************************************************/ +void print_verilog_local_sram_wires(std::fstream& fp, + const CircuitLibrary& circuit_lib, + const CircuitModelId& sram_model, + const e_config_protocol_type sram_orgz_type, + const size_t& port_size) { + /* Make sure we have a valid file handler*/ + valid_file_stream(fp); + + /* Port size must be at least one! */ + if (0 == port_size) { + return; + } + + /* Depend on the configuraion style */ + switch(sram_orgz_type) { + case CONFIG_MEM_STANDALONE: + /* Nothing to do here */ + break; + case CONFIG_MEM_SCAN_CHAIN: { + /* Generate the name of local wire for the CCFF inputs, CCFF output and inverted output */ + /* [0] => CCFF input */ + BasicPort ccff_config_bus_port(generate_local_config_bus_port_name(), port_size); + fp << generate_verilog_port(VERILOG_PORT_WIRE, ccff_config_bus_port) << ";" << std::endl; + /* Connect first CCFF to the head */ + /* Head is always a 1-bit port */ + BasicPort ccff_head_port(generate_sram_port_name(sram_orgz_type, CIRCUIT_MODEL_PORT_INPUT), 1); + BasicPort ccff_head_local_port(ccff_config_bus_port.get_name(), 1); + print_verilog_wire_connection(fp, ccff_head_local_port, ccff_head_port, false); + /* Connect last CCFF to the tail */ + /* Tail is always a 1-bit port */ + BasicPort ccff_tail_port(generate_sram_port_name(sram_orgz_type, CIRCUIT_MODEL_PORT_OUTPUT), 1); + BasicPort ccff_tail_local_port(ccff_config_bus_port.get_name(), ccff_config_bus_port.get_msb(), ccff_config_bus_port.get_msb()); + print_verilog_wire_connection(fp, ccff_tail_local_port, ccff_tail_port, false); + break; + } + case CONFIG_MEM_MEMORY_BANK: { + /* Generate the name of local wire for the SRAM output and inverted output */ + std::vector sram_ports; + /* [0] => SRAM output */ + sram_ports.push_back(BasicPort(generate_sram_local_port_name(circuit_lib, sram_model, sram_orgz_type, CIRCUIT_MODEL_PORT_INPUT), port_size)); + /* [1] => SRAM inverted output */ + sram_ports.push_back(BasicPort(generate_sram_local_port_name(circuit_lib, sram_model, sram_orgz_type, CIRCUIT_MODEL_PORT_OUTPUT), port_size)); + /* Print local wire definition */ + for (const auto& sram_port : sram_ports) { + fp << generate_verilog_port(VERILOG_PORT_WIRE, sram_port) << ";" << std::endl; + } + + break; + } + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid SRAM organization!\n"); + exit(1); + } +} + +/********************************************************************* + * Print a number of bus ports which are wired to the configuration + * ports of a CMOS (SRAM-based) routing multiplexer + * This port is supposed to be used locally inside a Verilog/SPICE module + * + * The following shows a few representative examples: + * + * For standalone configuration style: + * ------------------------------------ + * No bus needed + * + * Configuration chain-style + * ------------------------- + * wire [0:N] config_bus + * + * config_bus config_bus config_bus config_bus + * [0] [1] [2] [N] + * | | | | + * v v v v + * ccff_head ----------+ +---------+ +------------+ +----> ccff_tail + * | ^ | ^ | ^ + * head v |tail v | v | + * +----------+ +----------+ +----------+ + * | Memory | | Memory | | Memory | + * | Module | | Module | ... | Module | + * | [0] | | [1] | | [N] | + * +----------+ +----------+ +----------+ + * | | | + * v v v + * +----------+ +----------+ +----------+ + * | MUX | | MUX | | MUX | + * | Module | | Module | ... | Module | + * | [0] | | [1] | | [N] | + * +----------+ +----------+ +----------+ + * + * Memory bank-style + * ----------------- + * BL/WL bus --+------------+--------------------> + * | | | + * BL/WL v BL/WL v BL/WL v + * +----------+ +----------+ +----------+ + * | Memory | | Memory | | Memory | + * | Module | | Module | ... | Module | + * | [0] | | [1] | | [N] | + * +----------+ +----------+ +----------+ + * | | | + * v v v + * +----------+ +----------+ +----------+ + * | MUX | | MUX | | MUX | + * | Module | | Module | ... | Module | + * | [0] | | [1] | | [N] | + * +----------+ +----------+ +----------+ + * + *********************************************************************/ +void print_verilog_local_config_bus(std::fstream& fp, + const std::string& prefix, + const e_config_protocol_type& sram_orgz_type, + const size_t& instance_id, + const size_t& num_conf_bits) { + /* Make sure we have a valid file handler*/ + valid_file_stream(fp); + + switch(sram_orgz_type) { + case CONFIG_MEM_STANDALONE: + /* Not need for configuration bus + * The configuration ports of SRAM are directly wired to the ports of modules + */ + break; + case CONFIG_MEM_SCAN_CHAIN: + case CONFIG_MEM_MEMORY_BANK: { + /* Two configuration buses should be outputted + * One for the regular SRAM ports of a routing multiplexer + * The other for the inverted SRAM ports of a routing multiplexer + */ + BasicPort config_port(generate_local_sram_port_name(prefix, instance_id, CIRCUIT_MODEL_PORT_INPUT), + num_conf_bits); + fp << generate_verilog_port(VERILOG_PORT_WIRE, config_port) << ";" << std::endl; + BasicPort inverted_config_port(generate_local_sram_port_name(prefix, instance_id, CIRCUIT_MODEL_PORT_OUTPUT), + num_conf_bits); + fp << generate_verilog_port(VERILOG_PORT_WIRE, inverted_config_port) << ";" << std::endl; + break; + } + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid SRAM organization!\n"); + exit(1); + } +} + +/********************************************************************* + * Print a number of bus ports which are wired to the configuration + * ports of a ReRAM-based routing multiplexer + * This port is supposed to be used locally inside a Verilog/SPICE module + * + * Currently support: + * For memory-bank configuration style: + * ------------------------------------ + * Different than CMOS routing multiplexers, ReRAM multiplexers require + * reserved BL/WLs to be grouped in buses + * + * Module Port + * | + * v + * regular/reserved bus_port --+----------------+----> ... + * | | + * bl/wl/../sram_ports v v + * +-----------+ +-----------+ + * | Memory | | Memory | + * | Module[0] | | Module[1] | ... + * +-----------+ +-----------+ + * | | + * v v + * +-----------+ +-----------+ + * | Routing | | Routing | + * | MUX [0] | | MUX[1] | ... + * +-----------+ +-----------+ + * + *********************************************************************/ +static +void print_verilog_rram_mux_config_bus(std::fstream& fp, + const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const e_config_protocol_type& sram_orgz_type, + const size_t& mux_size, + const size_t& mux_instance_id, + const size_t& num_reserved_conf_bits, + const size_t& num_conf_bits) { + /* Make sure we have a valid file handler*/ + valid_file_stream(fp); + + switch(sram_orgz_type) { + case CONFIG_MEM_STANDALONE: + /* Not need for configuration bus + * The configuration ports of SRAM are directly wired to the ports of modules + */ + break; + case CONFIG_MEM_SCAN_CHAIN: { + /* Not supported yet. + * Configuration chain may be only applied to ReRAM-based multiplexers with local decoders + */ + break; + } + case CONFIG_MEM_MEMORY_BANK: { + /* This is currently most used in ReRAM FPGAs */ + /* Print configuration bus to group reserved BL/WLs */ + BasicPort reserved_bl_bus(generate_reserved_sram_port_name(CIRCUIT_MODEL_PORT_BL), + num_reserved_conf_bits); + fp << generate_verilog_port(VERILOG_PORT_WIRE, reserved_bl_bus) << ";" << std::endl; + BasicPort reserved_wl_bus(generate_reserved_sram_port_name(CIRCUIT_MODEL_PORT_WL), + num_reserved_conf_bits); + fp << generate_verilog_port(VERILOG_PORT_WIRE, reserved_wl_bus) << ";" << std::endl; + + /* Print configuration bus to group BL/WLs */ + BasicPort bl_bus(generate_mux_config_bus_port_name(circuit_lib, mux_model, mux_size, 0, false), + num_conf_bits + num_reserved_conf_bits); + fp << generate_verilog_port(VERILOG_PORT_WIRE, bl_bus) << ";" << std::endl; + BasicPort wl_bus(generate_mux_config_bus_port_name(circuit_lib, mux_model, mux_size, 1, false), + num_conf_bits + num_reserved_conf_bits); + fp << generate_verilog_port(VERILOG_PORT_WIRE, wl_bus) << ";" << std::endl; + + /* Print bus to group SRAM outputs, this is to interface memory cells to routing multiplexers */ + BasicPort sram_output_bus(generate_mux_sram_port_name(circuit_lib, mux_model, mux_size, mux_instance_id, CIRCUIT_MODEL_PORT_INPUT), + num_conf_bits); + fp << generate_verilog_port(VERILOG_PORT_WIRE, sram_output_bus) << ";" << std::endl; + BasicPort inverted_sram_output_bus(generate_mux_sram_port_name(circuit_lib, mux_model, mux_size, mux_instance_id, CIRCUIT_MODEL_PORT_OUTPUT), + num_conf_bits); + fp << generate_verilog_port(VERILOG_PORT_WIRE, inverted_sram_output_bus) << ";" << std::endl; + + /* Get the SRAM model of the mux_model */ + std::vector sram_models = find_circuit_sram_models(circuit_lib, mux_model); + /* TODO: maybe later multiplexers may have mode select ports... This should be relaxed */ + VTR_ASSERT( 1 == sram_models.size() ); + + /* Wire the reserved configuration bits to part of bl/wl buses */ + BasicPort bl_bus_reserved_bits(bl_bus.get_name(), num_reserved_conf_bits); + print_verilog_wire_connection(fp, bl_bus_reserved_bits, reserved_bl_bus, false); + BasicPort wl_bus_reserved_bits(wl_bus.get_name(), num_reserved_conf_bits); + print_verilog_wire_connection(fp, wl_bus_reserved_bits, reserved_wl_bus, false); + + /* Connect SRAM BL/WLs to bus */ + BasicPort mux_bl_wire(generate_sram_port_name(sram_orgz_type, CIRCUIT_MODEL_PORT_BL), + num_conf_bits); + BasicPort bl_bus_regular_bits(bl_bus.get_name(), num_reserved_conf_bits, num_reserved_conf_bits + num_conf_bits - 1); + print_verilog_wire_connection(fp, bl_bus_regular_bits, mux_bl_wire, false); + BasicPort mux_wl_wire(generate_sram_port_name(sram_orgz_type, CIRCUIT_MODEL_PORT_WL), + num_conf_bits); + BasicPort wl_bus_regular_bits(wl_bus.get_name(), num_reserved_conf_bits, num_reserved_conf_bits + num_conf_bits - 1); + print_verilog_wire_connection(fp, wl_bus_regular_bits, mux_wl_wire, false); + + break; + } + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid SRAM organization!\n"); + exit(1); + } + +} + +/********************************************************************* + * Print a number of bus ports which are wired to the configuration + * ports of a memory module, which consists of a number of configuration + * memory cells, such as SRAMs. + * Note that the configuration bus will only interface the memory + * module, rather than the programming routing multiplexers, LUTs, IOs + * etc. This helps us to keep clean and simple Verilog generation + *********************************************************************/ +void print_verilog_mux_config_bus(std::fstream& fp, + const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const e_config_protocol_type& sram_orgz_type, + const size_t& mux_size, + const size_t& mux_instance_id, + const size_t& num_reserved_conf_bits, + const size_t& num_conf_bits) { + /* Depend on the design technology of this MUX: + * bus connections are different + * SRAM MUX: bus is connected to the output ports of SRAM + * RRAM MUX: bus is connected to the BL/WL of MUX + * TODO: Maybe things will become even more complicated, + * the bus connections may depend on the type of configuration circuit... + * Currently, this is fine. + */ + switch (circuit_lib.design_tech_type(mux_model)) { + case CIRCUIT_MODEL_DESIGN_CMOS: { + std::string prefix = generate_mux_subckt_name(circuit_lib, mux_model, mux_size, std::string()); + print_verilog_local_config_bus(fp, prefix, sram_orgz_type, mux_instance_id, num_conf_bits); + break; + } + case CIRCUIT_MODEL_DESIGN_RRAM: + print_verilog_rram_mux_config_bus(fp, circuit_lib, mux_model, sram_orgz_type, mux_size, mux_instance_id, num_reserved_conf_bits, num_conf_bits); + break; + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid design technology for routing multiplexer!\n"); + exit(1); + } +} + +/********************************************************************* + * Print a wire to connect MUX configuration ports + * This function connects the sram ports to the ports of a Verilog module + * used for formal verification + * + * Note: MSB and LSB of formal verification configuration bus MUST be updated + * before running this function !!!! + *********************************************************************/ +void print_verilog_formal_verification_mux_sram_ports_wiring(std::fstream& fp, + const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const size_t& mux_size, + const size_t& mux_instance_id, + const size_t& num_conf_bits, + const BasicPort& fm_config_bus) { + BasicPort mux_sram_output(generate_mux_sram_port_name(circuit_lib, mux_model, mux_size, mux_instance_id, CIRCUIT_MODEL_PORT_INPUT), + num_conf_bits); + /* Get the SRAM model of the mux_model */ + std::vector sram_models = find_circuit_sram_models(circuit_lib, mux_model); + /* TODO: maybe later multiplexers may have mode select ports... This should be relaxed */ + VTR_ASSERT( 1 == sram_models.size() ); + BasicPort formal_verification_port; + formal_verification_port.set_name(generate_formal_verification_sram_port_name(circuit_lib, sram_models[0])); + VTR_ASSERT(num_conf_bits == fm_config_bus.get_width()); + formal_verification_port.set_lsb(fm_config_bus.get_lsb()); + formal_verification_port.set_msb(fm_config_bus.get_msb()); + print_verilog_wire_connection(fp, mux_sram_output, formal_verification_port, false); +} + +/******************************************************************** + * Print stimuli for a pulse generation + * + * |<--- pulse width --->| + * +------ flip_value + * | + * initial_value ----------------------+ + * + *******************************************************************/ +void print_verilog_pulse_stimuli(std::fstream& fp, + const BasicPort& port, + const size_t& initial_value, + const float& pulse_width, + const size_t& flip_value) { + /* Validate the file stream */ + valid_file_stream(fp); + + /* Config_done signal: indicate when configuration is finished */ + fp << "initial" << std::endl; + fp << "\tbegin" << std::endl; + fp << "\t"; + std::vector initial_values(port.get_width(), initial_value); + fp << "\t"; + fp << generate_verilog_port_constant_values(port, initial_values); + fp << ";" << std::endl; + + /* if flip_value is the same as initial value, we do not need to flip the signal ! */ + if (flip_value != initial_value) { + fp << "\t" << "#" << std::setprecision(10) << pulse_width; + std::vector port_flip_values(port.get_width(), flip_value); + fp << "\t"; + fp << generate_verilog_port_constant_values(port, port_flip_values); + fp << ";" << std::endl; + } + + fp << "\tend" << std::endl; + + /* Print an empty line as splitter */ + fp << std::endl; +} + +/******************************************************************** + * Print stimuli for a pulse generation + * This function supports multiple signal switching under different pulse width + * + * |<-- wait condition -->| + * |<--- pulse width --->| + * +------ flip_values + * | + * initial_value ------- ... --------------------------------+ + * + *******************************************************************/ +void print_verilog_pulse_stimuli(std::fstream& fp, + const BasicPort& port, + const size_t& initial_value, + const std::vector& pulse_widths, + const std::vector& flip_values, + const std::string& wait_condition) { + /* Validate the file stream */ + valid_file_stream(fp); + + /* Config_done signal: indicate when configuration is finished */ + fp << "initial" << std::endl; + fp << "\tbegin" << std::endl; + fp << "\t"; + std::vector initial_values(port.get_width(), initial_value); + fp << "\t"; + fp << generate_verilog_port_constant_values(port, initial_values); + fp << ";" << std::endl; + + /* Set a wait condition if specified */ + if (false == wait_condition.empty()) { + fp << "\twait(" << wait_condition << ")" << std::endl; + } + + /* Number of flip conditions and values should match */ + VTR_ASSERT(flip_values.size() == pulse_widths.size()); + for (size_t ipulse = 0; ipulse < pulse_widths.size(); ++ipulse) { + fp << "\t" << "#" << std::setprecision(10) << pulse_widths[ipulse]; + std::vector port_flip_value(port.get_width(), flip_values[ipulse]); + fp << "\t"; + fp << generate_verilog_port_constant_values(port, port_flip_value); + fp << ";" << std::endl; + } + + fp << "\tend" << std::endl; + + /* Print an empty line as splitter */ + fp << std::endl; +} + +/******************************************************************** + * Print stimuli for a clock signal + * This function can support if the clock signal should wait for a period + * of time and then start + * pulse width + * |<----->| + * +-------+ +-------+ + * | | | | + * initial_value --- ... ---+ +-------+ +------ ... + * |<--wait_condition-->| + * + *******************************************************************/ +void print_verilog_clock_stimuli(std::fstream& fp, + const BasicPort& port, + const size_t& initial_value, + const float& pulse_width, + const std::string& wait_condition) { + /* Validate the file stream */ + valid_file_stream(fp); + + /* Config_done signal: indicate when configuration is finished */ + fp << "initial" << std::endl; + fp << "\tbegin" << std::endl; + + std::vector initial_values(port.get_width(), initial_value); + fp << "\t\t"; + fp << generate_verilog_port_constant_values(port, initial_values); + fp << ";" << std::endl; + + fp << "\tend" << std::endl; + fp << "always"; + + /* Set a wait condition if specified */ + if (true == wait_condition.empty()) { + fp << std::endl; + } else { + fp << " wait(" << wait_condition << ")" << std::endl; + } + + fp << "\tbegin" << std::endl; + fp << "\t\t" << "#" << std::setprecision(10) << pulse_width; + + fp << "\t"; + fp << generate_verilog_port(VERILOG_PORT_CONKT, port); + fp << " = "; + fp << "~"; + fp << generate_verilog_port(VERILOG_PORT_CONKT, port); + fp << ";" << std::endl; + + fp << "\tend" << std::endl; + + /* Print an empty line as splitter */ + fp << std::endl; +} + +/******************************************************************** + * Output a header file that includes a number of Verilog netlists + * so that it can be easily included in a top-level netlist + ********************************************************************/ +void print_verilog_netlist_include_header_file(const std::vector& netlists_to_be_included, + const char* subckt_dir, + const char* header_file_name) { + std::string verilog_fname(std::string(subckt_dir) + std::string(header_file_name)); + + /* Create the file stream */ + std::fstream fp; + fp.open(verilog_fname, std::fstream::out | std::fstream::trunc); + + valid_file_stream(fp); + + /* Generate the descriptions*/ + print_verilog_file_header(fp, "Header file to include other Verilog netlists"); + + /* Output file names */ + for (const std::string& netlist_name : netlists_to_be_included) { + fp << "`include \"" << netlist_name << "\"" << std::endl; + } + + /* close file stream */ + fp.close(); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_writer_utils.h b/openfpga/src/fpga_verilog/verilog_writer_utils.h new file mode 100644 index 000000000..b145d8be6 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_writer_utils.h @@ -0,0 +1,187 @@ +/************************************************ + * Header file for verilog_writer_utils.cpp + * Include function declaration for most frequently + * used Verilog writers + ***********************************************/ +#ifndef VERILOG_WRITER_UTILS_H +#define VERILOG_WRITER_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include +#include "openfpga_port.h" +#include "verilog_port_types.h" +#include "module_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +/* Tips: for naming your function in this header/source file + * If a function outputs to a file, its name should begin with "print_verilog" + * If a function creates a string without outputting to a file, its name should begin with "generate_verilog" + * Please show respect to this naming convention, in order to keep a clean header/source file + * as well maintain a easy way to identify the functions + */ + +void print_verilog_file_header(std::fstream& fp, + const std::string& usage); + +void print_verilog_include_netlist(std::fstream& fp, + const std::string& netlist_name); + +void print_verilog_define_flag(std::fstream& fp, + const std::string& flag_name, + const int& flag_value); + +void print_verilog_include_defines_preproc_file(std::fstream& fp, + const std::string& verilog_dir); + +void print_verilog_comment(std::fstream& fp, + const std::string& comment); + +void print_verilog_preprocessing_flag(std::fstream& fp, + const std::string& preproc_flag); + +void print_verilog_endif(std::fstream& fp); + +void print_verilog_module_definition(std::fstream& fp, + const ModuleManager& module_manager, const ModuleId& module_id); + +void print_verilog_module_ports(std::fstream& fp, + const ModuleManager& module_manager, const ModuleId& module_id); + +void print_verilog_module_declaration(std::fstream& fp, + const ModuleManager& module_manager, const ModuleId& module_id); + +void print_verilog_module_instance(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& module_id, + const std::string& instance_name, + const std::map& port2port_name_map, + const bool& use_explicit_port_map); + +void print_verilog_module_instance(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& parent_module_id, const ModuleId& child_module_id, + const std::map& port2port_name_map, + const bool& use_explicit_port_map); + +void print_verilog_module_end(std::fstream& fp, + const std::string& module_name); + +std::string generate_verilog_port(const enum e_dump_verilog_port_type& dump_port_type, + const BasicPort& port_info); + +bool two_verilog_ports_mergeable(const BasicPort& portA, + const BasicPort& portB); + +BasicPort merge_two_verilog_ports(const BasicPort& portA, + const BasicPort& portB); + +std::vector combine_verilog_ports(const std::vector& ports); + +std::string generate_verilog_ports(const std::vector& merged_ports); + +BasicPort generate_verilog_bus_port(const std::vector& input_ports, + const std::string& bus_port_name); + +std::string generate_verilog_local_wire(const BasicPort& output_port, + const std::vector& input_ports); + +std::string generate_verilog_constant_values(const std::vector& const_values); + +std::string generate_verilog_port_constant_values(const BasicPort& output_port, + const std::vector& const_values); + +void print_verilog_wire_constant_values(std::fstream& fp, + const BasicPort& output_port, + const std::vector& const_values); + +void print_verilog_deposit_wire_constant_values(std::fstream& fp, + const BasicPort& output_port, + const std::vector& const_values); + +void print_verilog_force_wire_constant_values(std::fstream& fp, + const BasicPort& output_port, + const std::vector& const_values); + +void print_verilog_wire_connection(std::fstream& fp, + const BasicPort& output_port, + const BasicPort& input_port, + const bool& inverted); + +void print_verilog_register_connection(std::fstream& fp, + const BasicPort& output_port, + const BasicPort& input_port, + const bool& inverted); + +void print_verilog_buffer_instance(std::fstream& fp, + ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const ModuleId& parent_module_id, + const CircuitModelId& buffer_model, + const BasicPort& instance_input_port, + const BasicPort& instance_output_port); + +void print_verilog_local_sram_wires(std::fstream& fp, + const CircuitLibrary& circuit_lib, + const CircuitModelId& sram_model, + const e_config_protocol_type sram_orgz_type, + const size_t& port_size); + +void print_verilog_local_config_bus(std::fstream& fp, + const std::string& prefix, + const e_config_protocol_type& sram_orgz_type, + const size_t& instance_id, + const size_t& num_conf_bits); + +void print_verilog_mux_config_bus(std::fstream& fp, + const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const e_config_protocol_type& sram_orgz_type, + const size_t& mux_size, + const size_t& mux_instance_id, + const size_t& num_reserved_conf_bits, + const size_t& num_conf_bits); + +void print_verilog_formal_verification_mux_sram_ports_wiring(std::fstream& fp, + const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const size_t& mux_size, + const size_t& mux_instance_id, + const size_t& num_conf_bits, + const BasicPort& fm_config_bus); + +void print_verilog_pulse_stimuli(std::fstream& fp, + const BasicPort& port, + const size_t& initial_value, + const float& pulse_width, + const size_t& flip_value); + +void print_verilog_pulse_stimuli(std::fstream& fp, + const BasicPort& port, + const size_t& initial_value, + const std::vector& pulse_widths, + const std::vector& flip_values, + const std::string& wait_condition); + +void print_verilog_clock_stimuli(std::fstream& fp, + const BasicPort& port, + const size_t& initial_value, + const float& pulse_width, + const std::string& wait_condition); + +void print_verilog_netlist_include_header_file(const std::vector& netlists_to_be_included, + const char* subckt_dir, + const char* header_file_name); + +} /* end namespace openfpga */ + +#endif From 4cb61e2138b74ac840f4f934c64e5d0d5393cc11 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 16 Feb 2020 00:03:24 -0700 Subject: [PATCH 149/645] bring preprocessing flag Verilog netlists online --- openfpga/src/fpga_verilog/verilog_api.cpp | 9 +- .../verilog_auxiliary_netlists.cpp | 200 ++++++++++++++++++ .../fpga_verilog/verilog_auxiliary_netlists.h | 31 +++ openfpga/src/fpga_verilog/verilog_constants.h | 8 +- openfpga/src/fpga_verilog/verilog_options.cpp | 28 +++ openfpga/src/fpga_verilog/verilog_options.h | 11 + 6 files changed, 281 insertions(+), 6 deletions(-) create mode 100644 openfpga/src/fpga_verilog/verilog_auxiliary_netlists.cpp create mode 100644 openfpga/src/fpga_verilog/verilog_auxiliary_netlists.h diff --git a/openfpga/src/fpga_verilog/verilog_api.cpp b/openfpga/src/fpga_verilog/verilog_api.cpp index d8dd8dab8..71ec0708d 100644 --- a/openfpga/src/fpga_verilog/verilog_api.cpp +++ b/openfpga/src/fpga_verilog/verilog_api.cpp @@ -14,6 +14,7 @@ #include "device_rr_gsb.h" #include "verilog_constants.h" +#include "verilog_auxiliary_netlists.h" //#include "verilog_submodules.h" //#include "verilog_routing.h" //#include "verilog_submodules.h" @@ -67,11 +68,11 @@ void fpga_fabric_verilog(const ModuleManager& module_manager, create_dir_path(rr_dir_path.c_str()); /* Print Verilog files containing preprocessing flags */ - //print_verilog_preprocessing_flags_netlist(std::string(src_dir_path), - // vpr_setup.FPGA_SPICE_Opts.SynVerilogOpts); + print_verilog_preprocessing_flags_netlist(std::string(src_dir_path), + options); - //print_verilog_simulation_preprocessing_flags(std::string(src_dir_path), - // vpr_setup.FPGA_SPICE_Opts.SynVerilogOpts); + print_verilog_simulation_preprocessing_flags(std::string(src_dir_path), + options); /* Generate primitive Verilog modules, which are corner stones of FPGA fabric * Note that this function MUST be called before Verilog generation of diff --git a/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.cpp b/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.cpp new file mode 100644 index 000000000..f1a12798d --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.cpp @@ -0,0 +1,200 @@ +/******************************************************************** + * This file includes functions that are used to generate Verilog files + * or code blocks, with a focus on + * `include user-defined or auto-generated netlists in Verilog format + *******************************************************************/ +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" + +/* Headers from openfpgautil library */ +#include "openfpga_digest.h" + +#include "openfpga_naming.h" +#include "circuit_library_utils.h" +#include "verilog_constants.h" +#include "verilog_writer_utils.h" +#include "verilog_auxiliary_netlists.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Local constant variables + *******************************************************************/ +constexpr char* TOP_INCLUDE_NETLIST_FILE_NAME_POSTFIX = "_include_netlists.v"; + +/******************************************************************** + * Print a file that includes all the netlists that have been generated + * and user-defined. + * Some netlists are open to compile under specific preprocessing flags + *******************************************************************/ +void print_include_netlists(const std::string& src_dir, + const std::string& circuit_name, + const std::string& reference_benchmark_file, + const CircuitLibrary& circuit_lib) { + std::string verilog_fname = src_dir + circuit_name + std::string(TOP_INCLUDE_NETLIST_FILE_NAME_POSTFIX); + + /* Create the file stream */ + std::fstream fp; + fp.open(verilog_fname, std::fstream::out | std::fstream::trunc); + + /* Validate the file stream */ + check_file_stream(verilog_fname.c_str(), fp); + + /* Print the title */ + print_verilog_file_header(fp, std::string("Netlist Summary")); + + /* Print preprocessing flags */ + print_verilog_comment(fp, std::string("------ Include defines: preproc flags -----")); + print_verilog_include_netlist(fp, std::string(src_dir + std::string(DEFINES_VERILOG_FILE_NAME))); + fp << std::endl; + + print_verilog_comment(fp, std::string("------ Include simulation defines -----")); + print_verilog_include_netlist(fp, src_dir + std::string(DEFINES_VERILOG_SIMULATION_FILE_NAME)); + fp << std::endl; + + /* Include all the user-defined netlists */ + for (const std::string& user_defined_netlist : find_circuit_library_unique_verilog_netlists(circuit_lib)) { + print_verilog_include_netlist(fp, user_defined_netlist); + } + + /* Include all the primitive modules */ + print_verilog_include_netlist(fp, src_dir + std::string(DEFAULT_SUBMODULE_DIR_NAME) + std::string(SUBMODULE_VERILOG_FILE_NAME)); + fp << std::endl; + + /* Include all the CLB, heterogeneous block modules */ + print_verilog_include_netlist(fp, src_dir + std::string(DEFAULT_LB_DIR_NAME) + std::string(LOGIC_BLOCK_VERILOG_FILE_NAME)); + fp << std::endl; + + /* Include all the routing architecture modules */ + print_verilog_include_netlist(fp, src_dir + std::string(DEFAULT_RR_DIR_NAME) + std::string(ROUTING_VERILOG_FILE_NAME)); + fp << std::endl; + + /* Include FPGA top module */ + print_verilog_include_netlist(fp, src_dir + generate_fpga_top_netlist_name(std::string(VERILOG_NETLIST_FILE_POSTFIX))); + fp << std::endl; + + /* Include reference benchmark netlist only when auto-check flag is enabled */ + print_verilog_preprocessing_flag(fp, std::string(AUTOCHECKED_SIMULATION_FLAG)); + fp << "\t"; + print_verilog_include_netlist(fp, std::string(reference_benchmark_file)); + print_verilog_endif(fp); + fp << std::endl; + + /* Include formal verification netlists only when formal verification flag is enable */ + print_verilog_preprocessing_flag(fp, std::string(VERILOG_FORMAL_VERIFICATION_PREPROC_FLAG)); + fp << "\t"; + print_verilog_include_netlist(fp, src_dir + circuit_name + std::string(FORMAL_VERIFICATION_VERILOG_FILE_POSTFIX)); + + /* Include formal verification testbench only when formal simulation flag is enabled */ + fp << "\t"; + print_verilog_preprocessing_flag(fp, std::string(FORMAL_SIMULATION_FLAG)); + fp << "\t\t"; + print_verilog_include_netlist(fp, src_dir + circuit_name + std::string(RANDOM_TOP_TESTBENCH_VERILOG_FILE_POSTFIX)); + fp << "\t"; + print_verilog_endif(fp); + + print_verilog_endif(fp); + fp << std::endl; + + /* Include top-level testbench only when auto-check flag is enabled */ + print_verilog_preprocessing_flag(fp, std::string(AUTOCHECKED_SIMULATION_FLAG)); + fp << "\t"; + print_verilog_include_netlist(fp, src_dir + circuit_name + std::string(AUTOCHECK_TOP_TESTBENCH_VERILOG_FILE_POSTFIX)); + print_verilog_endif(fp); + fp << std::endl; + + /* Close the file stream */ + fp.close(); +} + +/******************************************************************** + * Print a Verilog file containing preprocessing flags + * which are used enable/disable some features in FPGA Verilog modules + *******************************************************************/ +void print_verilog_preprocessing_flags_netlist(const std::string& src_dir, + const FabricVerilogOption& fpga_verilog_opts) { + + std::string verilog_fname = src_dir + std::string(DEFINES_VERILOG_FILE_NAME); + + /* Create the file stream */ + std::fstream fp; + fp.open(verilog_fname, std::fstream::out | std::fstream::trunc); + + /* Validate the file stream */ + check_file_stream(verilog_fname.c_str(), fp); + + /* Print the title */ + print_verilog_file_header(fp, std::string("Preprocessing flags to enable/disable features in FPGA Verilog modules")); + + /* To enable timing */ + if (true == fpga_verilog_opts.include_timing()) { + print_verilog_define_flag(fp, std::string(VERILOG_TIMING_PREPROC_FLAG), 1); + fp << std::endl; + } + + /* To enable timing */ + if (true == fpga_verilog_opts.include_signal_init()) { + print_verilog_define_flag(fp, std::string(VERILOG_SIGNAL_INIT_PREPROC_FLAG), 1); + fp << std::endl; + } + + /* To enable formal verfication flag */ + if (true == fpga_verilog_opts.print_formal_verification_top_netlist()) { + print_verilog_define_flag(fp, std::string(VERILOG_FORMAL_VERIFICATION_PREPROC_FLAG), 1); + fp << std::endl; + } + + /* To enable functional verfication with Icarus */ + if (true == fpga_verilog_opts.support_icarus_simulator()) { + print_verilog_define_flag(fp, std::string(ICARUS_SIMULATOR_FLAG), 1); + fp << std::endl; + } + + /* Close the file stream */ + fp.close(); +} + +/******************************************************************** + * Print a Verilog file containing simulation-related preprocessing flags + *******************************************************************/ +void print_verilog_simulation_preprocessing_flags(const std::string& src_dir, + const FabricVerilogOption& fpga_verilog_opts) { + + std::string verilog_fname = src_dir + std::string(DEFINES_VERILOG_SIMULATION_FILE_NAME); + + /* Create the file stream */ + std::fstream fp; + fp.open(verilog_fname, std::fstream::out | std::fstream::trunc); + + /* Validate the file stream */ + check_file_stream(verilog_fname.c_str(), fp); + + /* Print the title */ + print_verilog_file_header(fp, std::string("Preprocessing flags to enable/disable simulation features")); + + /* To enable manualy checked simulation */ + if (true == fpga_verilog_opts.print_top_testbench()) { + print_verilog_define_flag(fp, std::string(INITIAL_SIMULATION_FLAG), 1); + fp << std::endl; + } + + /* To enable auto-checked simulation */ + if (true == fpga_verilog_opts.print_autocheck_top_testbench()) { + print_verilog_define_flag(fp, std::string(AUTOCHECKED_SIMULATION_FLAG), 1); + fp << std::endl; + } + + /* To enable pre-configured FPGA simulation */ + if (true == fpga_verilog_opts.print_formal_verification_top_netlist()) { + print_verilog_define_flag(fp, std::string(FORMAL_SIMULATION_FLAG), 1); + fp << std::endl; + } + + /* Close the file stream */ + fp.close(); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.h b/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.h new file mode 100644 index 000000000..42f3b0042 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.h @@ -0,0 +1,31 @@ +#ifndef VERILOG_AUXILIARY_NETLISTS_H +#define VERILOG_AUXILIARY_NETLISTS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include "circuit_library.h" +#include "verilog_options.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_include_netlists(const std::string& src_dir, + const std::string& circuit_name, + const std::string& reference_benchmark_file, + const CircuitLibrary& circuit_lib); + +void print_verilog_preprocessing_flags_netlist(const std::string& src_dir, + const FabricVerilogOption& fpga_verilog_opts); + +void print_verilog_simulation_preprocessing_flags(const std::string& src_dir, + const FabricVerilogOption& fpga_verilog_opts); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_verilog/verilog_constants.h b/openfpga/src/fpga_verilog/verilog_constants.h index 10a6c0669..9562bd730 100644 --- a/openfpga/src/fpga_verilog/verilog_constants.h +++ b/openfpga/src/fpga_verilog/verilog_constants.h @@ -8,7 +8,7 @@ constexpr float VERILOG_SIM_TIMESCALE = 1e-9; // Verilog Simulation time scale ( constexpr char* VERILOG_TIMING_PREPROC_FLAG = "ENABLE_TIMING"; // the flag to enable timing definition during compilation constexpr char* VERILOG_SIGNAL_INIT_PREPROC_FLAG = "ENABLE_SIGNAL_INITIALIZATION"; // the flag to enable signal initialization during compilation -constexpr char* VERILOG_FORMAL_VERIFICATGION_PREPROC_FLAG = "ENABLE_FORMAL_VERIFICATION"; // the flag to enable formal verification during compilation +constexpr char* VERILOG_FORMAL_VERIFICATION_PREPROC_FLAG = "ENABLE_FORMAL_VERIFICATION"; // the flag to enable formal verification during compilation constexpr char* INITIAL_SIMULATION_FLAG = "INITIAL_SIMULATION"; // the flag to enable initial functional verification constexpr char* AUTOCHECKED_SIMULATION_FLAG = "AUTOCHECKED_SIMULATION"; // the flag to enable autochecked functional verification constexpr char* FORMAL_SIMULATION_FLAG = "FORMAL_SIMULATION"; // the flag to enable formal functional verification @@ -19,10 +19,14 @@ constexpr char* DEFAULT_SUBMODULE_DIR_NAME = "sub_module/"; constexpr char* MODELSIM_SIMULATION_TIME_UNIT = "ms"; // Icarus variables and flag -constexpr char* ICARUS_SIMULATION_FLAG = "ICARUS_SIMULATOR"; // the flag to enable specific Verilog code in testbenches +constexpr char* ICARUS_SIMULATOR_FLAG = "ICARUS_SIMULATOR"; // the flag to enable specific Verilog code in testbenches // End of Icarus variables and flag constexpr char* VERILOG_TOP_POSTFIX = "_top.v"; +constexpr char* FORMAL_VERIFICATION_VERILOG_FILE_POSTFIX = "_top_formal_verification.v"; +constexpr char* TOP_TESTBENCH_VERILOG_FILE_POSTFIX = "_top_tb.v"; /* !!! must be consist with the modelsim_testbench_module_postfix */ +constexpr char* AUTOCHECK_TOP_TESTBENCH_VERILOG_FILE_POSTFIX = "_autocheck_top_tb.v"; /* !!! must be consist with the modelsim_autocheck_testbench_module_postfix */ +constexpr char* RANDOM_TOP_TESTBENCH_VERILOG_FILE_POSTFIX = "_formal_random_top_tb.v"; constexpr char* DEFINES_VERILOG_FILE_NAME = "fpga_defines.v"; constexpr char* DEFINES_VERILOG_SIMULATION_FILE_NAME = "define_simulation.v"; constexpr char* SUBMODULE_VERILOG_FILE_NAME = "sub_module.v"; diff --git a/openfpga/src/fpga_verilog/verilog_options.cpp b/openfpga/src/fpga_verilog/verilog_options.cpp index a0dfd7e2a..2539d5e51 100644 --- a/openfpga/src/fpga_verilog/verilog_options.cpp +++ b/openfpga/src/fpga_verilog/verilog_options.cpp @@ -35,6 +35,22 @@ bool FabricVerilogOption::compress_routing() const { return compress_routing_; } +bool FabricVerilogOption::print_top_testbench() const { + return print_top_testbench_; +} + +bool FabricVerilogOption::print_formal_verification_top_netlist() const { + return print_formal_verification_top_netlist_; +} + +bool FabricVerilogOption::print_autocheck_top_testbench() const { + return false == reference_verilog_file_path_.empty(); +} + +std::string FabricVerilogOption::reference_verilog_file_path() const { + return reference_verilog_file_path_; +} + bool FabricVerilogOption::verbose_output() const { return verbose_output_; } @@ -66,6 +82,18 @@ void FabricVerilogOption::set_compress_routing(const bool& enabled) { compress_routing_ = enabled; } +void FabricVerilogOption::set_print_top_testbench(const bool& enabled) { + print_top_testbench_ = enabled; +} + +void FabricVerilogOption::set_print_formal_verification_top_netlist(const bool& enabled) { + print_formal_verification_top_netlist_ = enabled; +} + +void FabricVerilogOption::set_print_autocheck_top_testbench(const std::string& reference_verilog_file_path) { + reference_verilog_file_path_ = reference_verilog_file_path; +} + void FabricVerilogOption::set_verbose_output(const bool& enabled) { verbose_output_ = enabled; } diff --git a/openfpga/src/fpga_verilog/verilog_options.h b/openfpga/src/fpga_verilog/verilog_options.h index 336ec542a..182cabcf2 100644 --- a/openfpga/src/fpga_verilog/verilog_options.h +++ b/openfpga/src/fpga_verilog/verilog_options.h @@ -24,6 +24,10 @@ class FabricVerilogOption { bool include_signal_init() const; bool explicit_port_mapping() const; bool compress_routing() const; + bool print_top_testbench() const; + bool print_formal_verification_top_netlist() const; + bool print_autocheck_top_testbench() const; + std::string reference_verilog_file_path() const; bool verbose_output() const; public: /* Public mutators */ void set_output_directory(const std::string& output_dir); @@ -32,6 +36,9 @@ class FabricVerilogOption { void set_include_signal_init(const bool& enabled); void set_explicit_port_mapping(const bool& enabled); void set_compress_routing(const bool& enabled); + void set_print_top_testbench(const bool& enabled); + void set_print_formal_verification_top_netlist(const bool& enabled); + void set_print_autocheck_top_testbench(const std::string& reference_verilog_file_path); void set_verbose_output(const bool& enabled); private: /* Internal Data */ std::string output_directory_; @@ -40,6 +47,10 @@ class FabricVerilogOption { bool include_timing_; bool explicit_port_mapping_; bool compress_routing_; + bool print_top_testbench_; + bool print_formal_verification_top_netlist_; + /* print_autocheck_top_testbench will be enabled when reference file path is not empty */ + std::string reference_verilog_file_path_; bool verbose_output_; }; From 2eba8823320ad8c74de0816f8dade1c39289d67c Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 16 Feb 2020 11:41:20 -0700 Subject: [PATCH 150/645] put verilog submodules online. ready to bring the how submodule writer online --- .../fpga_verilog/verilog_submodule_utils.cpp | 243 ++++++++++++++++++ .../fpga_verilog/verilog_submodule_utils.h | 37 +++ .../src/fpga_verilog/verilog_writer_utils.cpp | 48 ++-- 3 files changed, 304 insertions(+), 24 deletions(-) create mode 100644 openfpga/src/fpga_verilog/verilog_submodule_utils.cpp create mode 100644 openfpga/src/fpga_verilog/verilog_submodule_utils.h diff --git a/openfpga/src/fpga_verilog/verilog_submodule_utils.cpp b/openfpga/src/fpga_verilog/verilog_submodule_utils.cpp new file mode 100644 index 000000000..f4f02472d --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_submodule_utils.cpp @@ -0,0 +1,243 @@ +/************************************************ + * This file includes most utilized functions for + * generating Verilog sub-modules + * such as timing matrix and signal initialization + ***********************************************/ +#include +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" + +/* Headers from openfpgautil library */ +#include "openfpga_port.h" +#include "openfpga_digest.h" + +/* Headers from readarchopenfpga library */ +#include "circuit_types.h" + +#include "module_manager_utils.h" +#include "verilog_constants.h" +#include "verilog_writer_utils.h" +#include "verilog_submodule_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/* All values are printed with this precision value. The higher the + * value, the more accurate timing assignment is. Using a number of 6 + * guarentees that a precision of femtosecond which is sufficent for + * electrical simulation (simulation timescale is 10-9 + */ +/* constexpr int FLOAT_PRECISION = std::numeric_limits::max_digits10; */ +constexpr int FLOAT_PRECISION = 6; + +/************************************************ + * Print a timing matrix defined in theecircuit model + * into a Verilog format. + * This function print all the timing edges available + * in the circuit model (any pin-to-pin delay) + ***********************************************/ +void print_verilog_submodule_timing(std::fstream& fp, + const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model) { + /* return if there is no delay info */ + if ( 0 == circuit_lib.num_delay_info(circuit_model)) { + return; + } + + /* Return if there is no ports */ + if (0 == circuit_lib.num_model_ports(circuit_model)) { + return; + } + + /* Ensure a valid file handler*/ + VTR_ASSERT(true == valid_file_stream(fp)); + + fp << std::endl; + fp << "`ifdef " << VERILOG_TIMING_PREPROC_FLAG << std::endl; + print_verilog_comment(fp, std::string("------ BEGIN Pin-to-pin Timing constraints -----")); + fp << "\tspecify" << std::endl; + + /* Read out pin-to-pin delays by finding out all the edges belonging to a circuit model */ + for (const auto& timing_edge : circuit_lib.timing_edges_by_model(circuit_model)) { + CircuitPortId src_port = circuit_lib.timing_edge_src_port(timing_edge); + size_t src_pin = circuit_lib.timing_edge_src_pin(timing_edge); + BasicPort src_port_info(circuit_lib.port_lib_name(src_port), src_pin, src_pin); + + CircuitPortId sink_port = circuit_lib.timing_edge_sink_port(timing_edge); + size_t sink_pin = circuit_lib.timing_edge_sink_pin(timing_edge); + BasicPort sink_port_info(circuit_lib.port_lib_name(sink_port), sink_pin, sink_pin); + + fp << "\t\t"; + fp << "(" << generate_verilog_port(VERILOG_PORT_CONKT, src_port_info); + fp << " => "; + fp << generate_verilog_port(VERILOG_PORT_CONKT, sink_port_info) << ")"; + fp << " = "; + fp << "(" << std::setprecision(FLOAT_PRECISION) << circuit_lib.timing_edge_delay(timing_edge, CIRCUIT_MODEL_DELAY_RISE) / VERILOG_SIM_TIMESCALE; + fp << ", "; + fp << std::setprecision(FLOAT_PRECISION) << circuit_lib.timing_edge_delay(timing_edge, CIRCUIT_MODEL_DELAY_FALL) / VERILOG_SIM_TIMESCALE << ")"; + fp << ";" << std::endl; + } + + fp << "\tendspecify" << std::endl; + print_verilog_comment(fp, std::string("------ END Pin-to-pin Timing constraints -----")); + fp << "`endif" << std::endl; + +} + +void print_verilog_submodule_signal_init(std::fstream& fp, + const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model) { + /* Ensure a valid file handler*/ + VTR_ASSERT(true == valid_file_stream(fp)); + + fp << std::endl; + fp << "`ifdef " << VERILOG_SIGNAL_INIT_PREPROC_FLAG << std::endl; + print_verilog_comment(fp, std::string("------ BEGIN driver initialization -----")); + fp << "\tinitial begin" << std::endl; + fp << "\t`ifdef " << VERILOG_FORMAL_VERIFICATION_PREPROC_FLAG << std::endl; + + /* Only for formal verification: deposite a zero signal values */ + /* Initialize each input port */ + for (const auto& input_port : circuit_lib.model_input_ports(circuit_model)) { + BasicPort input_port_info(circuit_lib.port_lib_name(input_port), circuit_lib.port_size(input_port)); + fp << "\t\t$deposit("; + fp << generate_verilog_port(VERILOG_PORT_CONKT, input_port_info); + fp << ", " << circuit_lib.port_size(input_port) << "'b" << std::string(circuit_lib.port_size(input_port), '0'); + fp << ");" << std::endl; + } + fp << "\t`else" << std::endl; + + /* Regular case: deposite initial signal values: a random value */ + for (const auto& input_port : circuit_lib.model_input_ports(circuit_model)) { + BasicPort input_port_info(circuit_lib.port_lib_name(input_port), circuit_lib.port_size(input_port)); + fp << "\t\t$deposit("; + fp << generate_verilog_port(VERILOG_PORT_CONKT, input_port_info); + fp << ", $random);" << std::endl; + } + + fp << "\t`endif\n" << std::endl; + fp << "\tend" << std::endl; + print_verilog_comment(fp, std::string("------ END driver initialization -----")); + fp << "`endif" << std::endl; +} + +/********************************************************************* + * Register all the user-defined modules in the module manager + * Walk through the circuit library and add user-defined circuit models + * to the module_manager + ********************************************************************/ +void add_user_defined_verilog_modules(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib) { + /* Iterate over Verilog modules */ + for (const auto& model : circuit_lib.models()) { + /* We only care about user-defined models */ + if (true == circuit_lib.model_verilog_netlist(model).empty()) { + continue; + } + /* Skip Routing channel wire models because they need a different name. Do it later */ + if (CIRCUIT_MODEL_CHAN_WIRE == circuit_lib.model_type(model)) { + continue; + } + /* Reach here, the model requires a user-defined Verilog netlist, + * Try to find it in the module manager + * If not found, register it in the module_manager + */ + ModuleId module_id = module_manager.find_module(circuit_lib.model_name(model)); + if (ModuleId::INVALID() == module_id) { + add_circuit_model_to_module_manager(module_manager, circuit_lib, model); + } + } +} + +/********************************************************************* + * Print a template for a user-defined circuit model + * The template will include just the port declaration of the Verilog module + * The template aims to help user to write Verilog codes with a guaranteed + * module definition, which can be correctly instanciated (with correct + * port mapping) in the FPGA fabric + ********************************************************************/ +static +void print_one_verilog_template_module(const ModuleManager& module_manager, + std::fstream& fp, + const std::string& module_name) { + /* Ensure a valid file handler*/ + VTR_ASSERT(true == valid_file_stream(fp)); + + print_verilog_comment(fp, std::string("----- Template Verilog module for " + module_name + " -----")); + + /* Find the module in module manager, which should be already registered */ + /* TODO: routing channel wire model may have a different name! */ + ModuleId template_module = module_manager.find_module(module_name); + VTR_ASSERT(ModuleId::INVALID() != template_module); + + /* dump module definition + ports */ + print_verilog_module_declaration(fp, module_manager, template_module); + /* Finish dumping ports */ + + print_verilog_comment(fp, std::string("----- Internal logic should start here -----")); + + /* Add some empty lines as placeholders for the internal logic*/ + fp << std::endl << std::endl; + + print_verilog_comment(fp, std::string("----- Internal logic should end here -----")); + + /* Put an end to the Verilog module */ + print_verilog_module_end(fp, module_name); + + /* Add an empty line as a splitter */ + fp << std::endl; +} + +/********************************************************************* + * Print a template of all the submodules that are user-defined + * The template will include just the port declaration of the submodule + * The template aims to help user to write Verilog codes with a guaranteed + * module definition, which can be correctly instanciated (with correct + * port mapping) in the FPGA fabric + ********************************************************************/ +void print_verilog_submodule_templates(const ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const std::string& verilog_dir, + const std::string& submodule_dir) { + std::string verilog_fname(submodule_dir + USER_DEFINED_TEMPLATE_VERILOG_FILE_NAME); + + /* Create the file stream */ + std::fstream fp; + fp.open(verilog_fname, std::fstream::out | std::fstream::trunc); + + check_file_stream(verilog_fname.c_str(), fp); + + /* Print out debugging information for if the file is not opened/created properly */ + VTR_LOG("Creating template for user-defined Verilog modules '%s'...", + verilog_fname.c_str()); + + print_verilog_file_header(fp, "Template for user-defined Verilog modules"); + + print_verilog_include_defines_preproc_file(fp, verilog_dir); + + /* Output essential models*/ + for (const auto& model : circuit_lib.models()) { + /* Focus on user-defined modules, which must have a Verilog netlist defined */ + if (circuit_lib.model_verilog_netlist(model).empty()) { + continue; + } + /* Skip Routing channel wire models because they need a different name. Do it later */ + if (CIRCUIT_MODEL_CHAN_WIRE == circuit_lib.model_type(model)) { + continue; + } + /* Print a Verilog template for the circuit model */ + print_one_verilog_template_module(module_manager, fp, circuit_lib.model_name(model)); + } + + /* close file stream */ + fp.close(); + + /* No need to add the template to the subckt include files! */ + VTR_LOG("Done\n"); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_submodule_utils.h b/openfpga/src/fpga_verilog/verilog_submodule_utils.h new file mode 100644 index 000000000..fedcb63a8 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_submodule_utils.h @@ -0,0 +1,37 @@ +#ifndef VERILOG_SUBMODULE_UTILS_H +#define VERILOG_SUBMODULE_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "module_manager.h" +#include "circuit_library.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_verilog_submodule_timing(std::fstream& fp, + const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model); + +void print_verilog_submodule_signal_init(std::fstream& fp, + const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model); + +void add_user_defined_verilog_modules(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib); + +void print_verilog_submodule_templates(const ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const std::string& verilog_dir, + const std::string& submodule_dir); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_verilog/verilog_writer_utils.cpp b/openfpga/src/fpga_verilog/verilog_writer_utils.cpp index 5798b05fc..28f2ed5db 100644 --- a/openfpga/src/fpga_verilog/verilog_writer_utils.cpp +++ b/openfpga/src/fpga_verilog/verilog_writer_utils.cpp @@ -32,7 +32,7 @@ namespace openfpga { ***********************************************/ void print_verilog_file_header(std::fstream& fp, const std::string& usage) { - valid_file_stream(fp); + VTR_ASSERT(true == valid_file_stream(fp)); auto end = std::chrono::system_clock::now(); std::time_t end_time = std::chrono::system_clock::to_time_t(end); @@ -54,7 +54,7 @@ void print_verilog_file_header(std::fstream& fp, *******************************************************************/ void print_verilog_include_netlist(std::fstream& fp, const std::string& netlist_name) { - valid_file_stream(fp); + VTR_ASSERT(true == valid_file_stream(fp)); fp << "`include \"" << netlist_name << "\"" << std::endl; } @@ -65,7 +65,7 @@ void print_verilog_include_netlist(std::fstream& fp, void print_verilog_define_flag(std::fstream& fp, const std::string& flag_name, const int& flag_value) { - valid_file_stream(fp); + VTR_ASSERT(true == valid_file_stream(fp)); fp << "`define " << flag_name << " " << flag_value << std::endl; } @@ -88,7 +88,7 @@ void print_verilog_include_defines_preproc_file(std::fstream& fp, ***********************************************/ void print_verilog_comment(std::fstream& fp, const std::string& comment) { - valid_file_stream(fp); + VTR_ASSERT(true == valid_file_stream(fp)); fp << "// " << comment << std::endl; } @@ -98,7 +98,7 @@ void print_verilog_comment(std::fstream& fp, ***********************************************/ void print_verilog_preprocessing_flag(std::fstream& fp, const std::string& preproc_flag) { - valid_file_stream(fp); + VTR_ASSERT(true == valid_file_stream(fp)); fp << "`ifdef " << preproc_flag << std::endl; } @@ -107,7 +107,7 @@ void print_verilog_preprocessing_flag(std::fstream& fp, * Print the endif of a Verilog preprocessing flag ***********************************************/ void print_verilog_endif(std::fstream& fp) { - valid_file_stream(fp); + VTR_ASSERT(true == valid_file_stream(fp)); fp << "`endif" << std::endl; } @@ -119,7 +119,7 @@ void print_verilog_endif(std::fstream& fp) { ***********************************************/ void print_verilog_module_definition(std::fstream& fp, const ModuleManager& module_manager, const ModuleId& module_id) { - valid_file_stream(fp); + VTR_ASSERT(true == valid_file_stream(fp)); print_verilog_comment(fp, std::string("----- Verilog module for " + module_manager.module_name(module_id) + " -----")); @@ -183,7 +183,7 @@ void print_verilog_module_definition(std::fstream& fp, ***********************************************/ void print_verilog_module_ports(std::fstream& fp, const ModuleManager& module_manager, const ModuleId& module_id) { - valid_file_stream(fp); + VTR_ASSERT(true == valid_file_stream(fp)); /* port type2type mapping */ std::map port_type2type_map; @@ -292,7 +292,7 @@ void print_verilog_module_ports(std::fstream& fp, ***********************************************/ void print_verilog_module_declaration(std::fstream& fp, const ModuleManager& module_manager, const ModuleId& module_id) { - valid_file_stream(fp); + VTR_ASSERT(true == valid_file_stream(fp)); print_verilog_module_definition(fp, module_manager, module_id); @@ -327,7 +327,7 @@ void print_verilog_module_instance(std::fstream& fp, const std::map& port2port_name_map, const bool& use_explicit_port_map) { - valid_file_stream(fp); + VTR_ASSERT(true == valid_file_stream(fp)); /* Check: all the key ports in the port2port_name_map does exist in the child module */ for (const auto& kv : port2port_name_map) { @@ -418,7 +418,7 @@ void print_verilog_module_instance(std::fstream& fp, ***********************************************/ void print_verilog_module_end(std::fstream& fp, const std::string& module_name) { - valid_file_stream(fp); + VTR_ASSERT(true == valid_file_stream(fp)); fp << "endmodule" << std::endl; print_verilog_comment(fp, std::string("----- END Verilog module for " + module_name + " -----")); @@ -672,7 +672,7 @@ void print_verilog_wire_constant_values(std::fstream& fp, const BasicPort& output_port, const std::vector& const_values) { /* Make sure we have a valid file handler*/ - valid_file_stream(fp); + VTR_ASSERT(true == valid_file_stream(fp)); fp << "\t"; fp << "assign "; @@ -687,7 +687,7 @@ void print_verilog_deposit_wire_constant_values(std::fstream& fp, const BasicPort& output_port, const std::vector& const_values) { /* Make sure we have a valid file handler*/ - valid_file_stream(fp); + VTR_ASSERT(true == valid_file_stream(fp)); fp << "\t"; fp << "$deposit("; @@ -705,7 +705,7 @@ void print_verilog_force_wire_constant_values(std::fstream& fp, const BasicPort& output_port, const std::vector& const_values) { /* Make sure we have a valid file handler*/ - valid_file_stream(fp); + VTR_ASSERT(true == valid_file_stream(fp)); fp << "\t"; fp << "force "; @@ -722,7 +722,7 @@ void print_verilog_wire_connection(std::fstream& fp, const BasicPort& input_port, const bool& inverted) { /* Make sure we have a valid file handler*/ - valid_file_stream(fp); + VTR_ASSERT(true == valid_file_stream(fp)); /* Must check: the port width matches */ VTR_ASSERT( input_port.get_width() == output_port.get_width() ); @@ -749,7 +749,7 @@ void print_verilog_register_connection(std::fstream& fp, const BasicPort& input_port, const bool& inverted) { /* Make sure we have a valid file handler*/ - valid_file_stream(fp); + VTR_ASSERT(true == valid_file_stream(fp)); /* Must check: the port width matches */ VTR_ASSERT( input_port.get_width() == output_port.get_width() ); @@ -787,7 +787,7 @@ void print_verilog_buffer_instance(std::fstream& fp, const BasicPort& instance_input_port, const BasicPort& instance_output_port) { /* Make sure we have a valid file handler*/ - valid_file_stream(fp); + VTR_ASSERT(true == valid_file_stream(fp)); /* To match the context, Buffer should have only 2 non-global ports: 1 input port and 1 output port */ std::vector buffer_model_input_ports = circuit_lib.model_ports_by_type(buffer_model, CIRCUIT_MODEL_PORT_INPUT, true); @@ -903,7 +903,7 @@ void print_verilog_local_sram_wires(std::fstream& fp, const e_config_protocol_type sram_orgz_type, const size_t& port_size) { /* Make sure we have a valid file handler*/ - valid_file_stream(fp); + VTR_ASSERT(true == valid_file_stream(fp)); /* Port size must be at least one! */ if (0 == port_size) { @@ -1013,7 +1013,7 @@ void print_verilog_local_config_bus(std::fstream& fp, const size_t& instance_id, const size_t& num_conf_bits) { /* Make sure we have a valid file handler*/ - valid_file_stream(fp); + VTR_ASSERT(true == valid_file_stream(fp)); switch(sram_orgz_type) { case CONFIG_MEM_STANDALONE: @@ -1081,7 +1081,7 @@ void print_verilog_rram_mux_config_bus(std::fstream& fp, const size_t& num_reserved_conf_bits, const size_t& num_conf_bits) { /* Make sure we have a valid file handler*/ - valid_file_stream(fp); + VTR_ASSERT(true == valid_file_stream(fp)); switch(sram_orgz_type) { case CONFIG_MEM_STANDALONE: @@ -1236,7 +1236,7 @@ void print_verilog_pulse_stimuli(std::fstream& fp, const float& pulse_width, const size_t& flip_value) { /* Validate the file stream */ - valid_file_stream(fp); + VTR_ASSERT(true == valid_file_stream(fp)); /* Config_done signal: indicate when configuration is finished */ fp << "initial" << std::endl; @@ -1280,7 +1280,7 @@ void print_verilog_pulse_stimuli(std::fstream& fp, const std::vector& flip_values, const std::string& wait_condition) { /* Validate the file stream */ - valid_file_stream(fp); + VTR_ASSERT(true == valid_file_stream(fp)); /* Config_done signal: indicate when configuration is finished */ fp << "initial" << std::endl; @@ -1330,7 +1330,7 @@ void print_verilog_clock_stimuli(std::fstream& fp, const float& pulse_width, const std::string& wait_condition) { /* Validate the file stream */ - valid_file_stream(fp); + VTR_ASSERT(true == valid_file_stream(fp)); /* Config_done signal: indicate when configuration is finished */ fp << "initial" << std::endl; @@ -1380,7 +1380,7 @@ void print_verilog_netlist_include_header_file(const std::vector& n std::fstream fp; fp.open(verilog_fname, std::fstream::out | std::fstream::trunc); - valid_file_stream(fp); + VTR_ASSERT(true == valid_file_stream(fp)); /* Generate the descriptions*/ print_verilog_file_header(fp, "Header file to include other Verilog netlists"); From cf34339e96cfbd20c8f93c5e11c2966dfb9142d9 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 16 Feb 2020 11:57:19 -0700 Subject: [PATCH 151/645] adapt essential gates for submodule generation --- .../fpga_verilog/verilog_essential_gates.cpp | 585 ++++++++++++++++++ .../fpga_verilog/verilog_essential_gates.h | 25 + 2 files changed, 610 insertions(+) create mode 100644 openfpga/src/fpga_verilog/verilog_essential_gates.cpp create mode 100644 openfpga/src/fpga_verilog/verilog_essential_gates.h diff --git a/openfpga/src/fpga_verilog/verilog_essential_gates.cpp b/openfpga/src/fpga_verilog/verilog_essential_gates.cpp new file mode 100644 index 000000000..c18bece61 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_essential_gates.cpp @@ -0,0 +1,585 @@ +/************************************************ + * This file includes functions on + * outputting Verilog netlists for essential gates + * which are inverters, buffers, transmission-gates + * logic gates etc. + ***********************************************/ +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" + +/* Headers from openfpgautil library */ +#include "openfpga_port.h" +#include "openfpga_digest.h" + +#include "openfpga_naming.h" +#include "module_manager.h" +#include "module_manager_utils.h" + +#include "verilog_constants.h" +#include "verilog_writer_utils.h" +#include "verilog_submodule_utils.h" +#include "verilog_essential_gates.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/************************************************ + * Print Verilog body codes of a power-gated inverter + * This function does NOT generate any port map ! + ***********************************************/ +static +void print_verilog_power_gated_invbuf_body(std::fstream& fp, + const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const CircuitPortId& input_port, + const CircuitPortId& output_port, + const std::vector& power_gate_ports) { + /* Ensure a valid file handler*/ + VTR_ASSERT(true == valid_file_stream(fp)); + + print_verilog_comment(fp, std::string("----- Verilog codes of a power-gated inverter -----")); + + /* Create a sensitive list */ + fp << "\treg " << circuit_lib.port_prefix(output_port) << "_reg;" << std::endl; + + fp << "\talways @(" << std::endl; + /* Power-gate port first*/ + for (const auto& power_gate_port : power_gate_ports) { + /* Skip first comma to dump*/ + if (0 < &power_gate_port - &power_gate_ports[0]) { + fp << ","; + } + fp << circuit_lib.port_prefix(power_gate_port); + } + fp << circuit_lib.port_prefix(input_port) << ") begin" << std::endl; + + /* Dump the case of power-gated */ + fp << "\t\tif ("; + /* For the first pin, we skip output comma */ + size_t port_cnt = 0; + for (const auto& power_gate_port : power_gate_ports) { + for (const auto& power_gate_pin : circuit_lib.pins(power_gate_port)) { + if (0 < port_cnt) { + fp << std::endl << "\t\t&&"; + } + fp << "("; + + /* Power-gated signal are disable during operating, enabled during configuration, + * Therefore, we need to reverse them here + */ + if (0 == circuit_lib.port_default_value(power_gate_port)) { + fp << "~"; + } + + fp << circuit_lib.port_prefix(power_gate_port) << "[" << power_gate_pin << "])"; + + port_cnt++; /* Update port counter*/ + } + } + + fp << ") begin" << std::endl; + fp << "\t\t\tassign " << circuit_lib.port_prefix(output_port) << "_reg = "; + + /* Branch on the type of inverter/buffer: + * 1. If this is an inverter or an tapered(multi-stage) buffer with odd number of stages, + * we invert the input to output + * 2. If this is a buffer or an tapere(multi-stage) buffer with even number of stages, + * we wire the input to output + */ + if ( (CIRCUIT_MODEL_BUF_INV == circuit_lib.buffer_type(circuit_model)) + || ( (CIRCUIT_MODEL_BUF_BUF == circuit_lib.buffer_type(circuit_model)) + && (size_t(-1) != circuit_lib.buffer_num_levels(circuit_model)) + && (1 == circuit_lib.buffer_num_levels(circuit_model) % 2 ) ) ) { + fp << "~"; + } + + fp << circuit_lib.port_prefix(input_port) << ";" << std::endl; + fp << "\t\tend else begin" << std::endl; + fp << "\t\t\tassign " << circuit_lib.port_prefix(output_port) << "_reg = 1'bz;" << std::endl; + fp << "\t\tend" << std::endl; + fp << "\tend" << std::endl; + fp << "\tassign " << circuit_lib.port_prefix(output_port) << " = " << circuit_lib.port_prefix(output_port) << "_reg;" << std::endl; +} + +/************************************************ + * Print Verilog body codes of a regular inverter + * This function does NOT generate any port map ! + ***********************************************/ +static +void print_verilog_invbuf_body(std::fstream& fp, + const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const CircuitPortId& input_port, + const CircuitPortId& output_port) { + /* Ensure a valid file handler*/ + VTR_ASSERT(true == valid_file_stream(fp)); + + print_verilog_comment(fp, std::string("----- Verilog codes of a regular inverter -----")); + + fp << "\tassign " << circuit_lib.port_prefix(output_port) << " = (" << circuit_lib.port_prefix(input_port) << " === 1'bz)? $random : "; + + /* Branch on the type of inverter/buffer: + * 1. If this is an inverter or an tapered(multi-stage) buffer with odd number of stages, + * we invert the input to output + * 2. If this is a buffer or an tapere(multi-stage) buffer with even number of stages, + * we wire the input to output + */ + if ( (CIRCUIT_MODEL_BUF_INV == circuit_lib.buffer_type(circuit_model)) + || ( (CIRCUIT_MODEL_BUF_BUF == circuit_lib.buffer_type(circuit_model)) + && (size_t(-1) != circuit_lib.buffer_num_levels(circuit_model)) + && (1 == circuit_lib.buffer_num_levels(circuit_model) % 2 ) ) ) { + fp << "~"; + } + + fp << circuit_lib.port_prefix(input_port) << ";" << std::endl; +} + +/************************************************ + * Print a Verilog module of inverter or buffer + * or tapered buffer to a file + ***********************************************/ +static +void print_verilog_invbuf_module(ModuleManager& module_manager, + std::fstream& fp, + const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model) { + /* Ensure a valid file handler*/ + VTR_ASSERT(true == valid_file_stream(fp)); + + /* Find the input port, output port and global inputs*/ + std::vector input_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_INPUT, true); + std::vector output_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_OUTPUT, true); + std::vector global_ports = circuit_lib.model_global_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_INPUT, true, true); + + /* Make sure: + * There is only 1 input port and 1 output port, + * each size of which is 1 + */ + VTR_ASSERT( (1 == input_ports.size()) && (1 == circuit_lib.port_size(input_ports[0])) ); + VTR_ASSERT( (1 == output_ports.size()) && (1 == circuit_lib.port_size(output_ports[0])) ); + + /* TODO: move the check codes to check_circuit_library.h */ + /* If the circuit model is power-gated, we need to find at least one global config_enable signals */ + if (true == circuit_lib.is_power_gated(circuit_model)) { + /* Check all the ports we have are good for a power-gated circuit model */ + size_t num_err = 0; + /* We need at least one global port */ + if (0 == global_ports.size()) { + num_err++; + } + /* All the global ports should be config_enable */ + for (const auto& port : global_ports) { + if (false == circuit_lib.port_is_config_enable(port)) { + num_err++; + } + } + /* Report errors if there are any */ + if (0 < num_err) { + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Inverter/buffer circuit model '%s' is power-gated. At least one config-enable global port is required!\n", + circuit_lib.model_name(circuit_model).c_str()); + exit(1); + } + } + + /* Create a Verilog Module based on the circuit model, and add to module manager */ + ModuleId module_id = module_manager.find_module(circuit_lib.model_name(circuit_model)); + VTR_ASSERT(true == module_manager.valid_module_id(module_id)); + + /* dump module definition + ports */ + print_verilog_module_declaration(fp, module_manager, module_id); + /* Finish dumping ports */ + + /* Assign logics : depending on topology */ + /* Error out for unsupported technology */ + if ( ( CIRCUIT_MODEL_BUF_INV != circuit_lib.buffer_type(circuit_model)) + && ( CIRCUIT_MODEL_BUF_BUF != circuit_lib.buffer_type(circuit_model)) ) { + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid topology for circuit model '%s'!\n", + circuit_lib.model_name(circuit_model).c_str()); + exit(1); + } + + if (true == circuit_lib.is_power_gated(circuit_model)) { + /* Output Verilog codes for a power-gated inverter */ + print_verilog_power_gated_invbuf_body(fp, circuit_lib, circuit_model, input_ports[0], output_ports[0], global_ports); + } else { + /* Output Verilog codes for a regular inverter */ + print_verilog_invbuf_body(fp, circuit_lib, circuit_model, input_ports[0], output_ports[0]); + } + + /* Print timing info */ + print_verilog_submodule_timing(fp, circuit_lib, circuit_model); + + /* Print signal initialization */ + print_verilog_submodule_signal_init(fp, circuit_lib, circuit_model); + + /* Put an end to the Verilog module */ + print_verilog_module_end(fp, circuit_lib.model_name(circuit_model)); +} + +/************************************************ + * Print a Verilog module of a pass-gate, + * either transmission-gate or pass-transistor + ***********************************************/ +static +void print_verilog_passgate_module(ModuleManager& module_manager, + std::fstream& fp, + const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model) { + /* Ensure a valid file handler*/ + VTR_ASSERT(true == valid_file_stream(fp)); + + /* Find the input port, output port*/ + std::vector input_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_INPUT, true); + std::vector output_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_OUTPUT, true); + std::vector global_ports = circuit_lib.model_global_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_INPUT, true, true); + + switch (circuit_lib.pass_gate_logic_type(circuit_model)) { + case CIRCUIT_MODEL_PASS_GATE_TRANSMISSION: + /* Make sure: + * There is only 3 input port (in, sel, selb), + * each size of which is 1 + */ + VTR_ASSERT( 3 == input_ports.size() ); + for (const auto& input_port : input_ports) { + VTR_ASSERT(1 == circuit_lib.port_size(input_port)); + } + break; + case CIRCUIT_MODEL_PASS_GATE_TRANSISTOR: + /* Make sure: + * There is only 2 input port (in, sel), + * each size of which is 1 + */ + VTR_ASSERT( 2 == input_ports.size() ); + for (const auto& input_port : input_ports) { + VTR_ASSERT(1 == circuit_lib.port_size(input_port)); + } + break; + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid topology for circuit model '%s'!\n", + circuit_lib.model_name(circuit_model).c_str()); + exit(1); + } + + /* Make sure: + * There is only 1 output port, + * each size of which is 1 + */ + VTR_ASSERT( (1 == output_ports.size()) && (1 == circuit_lib.port_size(output_ports[0])) ); + + /* Create a Verilog Module based on the circuit model, and add to module manager */ + ModuleId module_id = module_manager.find_module(circuit_lib.model_name(circuit_model)); + VTR_ASSERT(true == module_manager.valid_module_id(module_id)); + + /* dump module definition + ports */ + print_verilog_module_declaration(fp, module_manager, module_id); + /* Finish dumping ports */ + + /* Dump logics: we propagate input to the output when the gate is '1' + * the input is blocked from output when the gate is '0' + */ + fp << "\tassign " << circuit_lib.port_prefix(output_ports[0]) << " = "; + fp << circuit_lib.port_prefix(input_ports[1]) << " ? " << circuit_lib.port_prefix(input_ports[0]); + fp << " : 1'bz;" << std::endl; + + /* Print timing info */ + print_verilog_submodule_timing(fp, circuit_lib, circuit_model); + + /* Print signal initialization */ + print_verilog_submodule_signal_init(fp, circuit_lib, circuit_model); + + /* Put an end to the Verilog module */ + print_verilog_module_end(fp, circuit_lib.model_name(circuit_model)); +} + +/************************************************ + * Print Verilog body codes of an N-input AND gate + ***********************************************/ +static +void print_verilog_and_or_gate_body(std::fstream& fp, + const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const std::vector& input_ports, + const std::vector& output_ports) { + /* Ensure a valid file handler*/ + VTR_ASSERT(true == valid_file_stream(fp)); + + /* Find the logic operator for the gate */ + std::string gate_verilog_operator; + switch (circuit_lib.gate_type(circuit_model)) { + case CIRCUIT_MODEL_GATE_AND: + gate_verilog_operator = "&"; + break; + case CIRCUIT_MODEL_GATE_OR: + gate_verilog_operator = "|"; + break; + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid topology for circuit model '%s'!\n", + circuit_lib.model_name(circuit_model).c_str()); + exit(1); + } + + /* Output verilog codes */ + print_verilog_comment(fp, std::string("----- Verilog codes of a " + std::to_string(input_ports.size()) + "-input " + std::to_string(output_ports.size()) + "-output AND gate -----")); + + for (const auto& output_port : output_ports) { + for (const auto& output_pin : circuit_lib.pins(output_port)) { + BasicPort output_port_info(circuit_lib.port_prefix(output_port), output_pin, output_pin); + fp << "\tassign " << generate_verilog_port(VERILOG_PORT_CONKT, output_port_info); + fp << " = "; + + size_t port_cnt = 0; + for (const auto& input_port : input_ports) { + for (const auto& input_pin : circuit_lib.pins(input_port)) { + /* Do not output AND/OR operator for the first element in the loop */ + if (0 < port_cnt) { + fp << " " << gate_verilog_operator << " "; + } + + BasicPort input_port_info(circuit_lib.port_prefix(input_port), input_pin, input_pin); + fp << generate_verilog_port(VERILOG_PORT_CONKT, input_port_info); + + /* Increment the counter for port */ + port_cnt++; + } + } + fp << ";" << std::endl; + } + } +} + +/************************************************ + * Print Verilog body codes of an 2-input MUX gate + ***********************************************/ +static +void print_verilog_mux2_gate_body(std::fstream& fp, + const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const std::vector& input_ports, + const std::vector& output_ports) { + /* Ensure a valid file handler*/ + VTR_ASSERT(true == valid_file_stream(fp)); + + /* TODO: Move the check codes to check_circuit_library.cpp */ + size_t num_err = 0; + /* Check on the port sequence and map */ + /* MUX2 should only have 1 output port with size 1 */ + if (1 != output_ports.size()) { + VTR_LOGF_ERROR(__FILE__, __LINE__, + "MUX2 circuit model '%s' must have only 1 output!\n", + circuit_lib.model_name(circuit_model).c_str()); + num_err++; + } + for (const auto& output_port : output_ports) { + /* Bypass port size of 1 */ + if (1 == circuit_lib.port_size(output_port)) { + continue; + } + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Output port size of a MUX2 circuit model '%s' must be 1!\n", + circuit_lib.model_name(circuit_model).c_str()); + num_err++; + } + /* MUX2 should only have 3 output port, each of which has a port size of 1 */ + if (3 != input_ports.size()) { + VTR_LOGF_ERROR(__FILE__, __LINE__, + "MUX2 circuit model '%s' must have only 3 input!\n", + circuit_lib.model_name(circuit_model).c_str()); + num_err++; + } + + for (const auto& input_port : input_ports) { + /* Bypass port size of 1 */ + if (1 == circuit_lib.port_size(input_port)) { + continue; + } + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Input size MUX2 circuit model '%s' must be 1!\n", + circuit_lib.model_name(circuit_model).c_str()); + num_err++; + } + if (0 < num_err) { + exit(1); + } + + /* Now, we output the logic of MUX2 + * IMPORTANT Restriction: + * We always assum the first two inputs are data inputs + * the third input is the select port + */ + fp << "\tassign "; + BasicPort out_port_info(circuit_lib.port_prefix(output_ports[0]), 0, 0); + BasicPort sel_port_info(circuit_lib.port_prefix(input_ports[2]), 0, 0); + BasicPort in0_port_info(circuit_lib.port_prefix(input_ports[0]), 0, 0); + BasicPort in1_port_info(circuit_lib.port_prefix(input_ports[1]), 0, 0); + + fp << generate_verilog_port(VERILOG_PORT_CONKT, out_port_info); + fp << " = "; + fp << generate_verilog_port(VERILOG_PORT_CONKT, sel_port_info); + fp << " ? "; + fp << generate_verilog_port(VERILOG_PORT_CONKT, in0_port_info); + fp << " : "; + fp << generate_verilog_port(VERILOG_PORT_CONKT, in1_port_info); + fp << ";" << std::endl; +} + +/************************************************ + * Print a Verilog module of a logic gate + * which are standard cells + * Supported gate types: + * 1. N-input AND + * 2. N-input OR + * 3. 2-input MUX + ***********************************************/ +static +void print_verilog_gate_module(ModuleManager& module_manager, + std::fstream& fp, + const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model) { + /* Ensure a valid file handler*/ + VTR_ASSERT(true == valid_file_stream(fp)); + + /* Find the input port, output port*/ + std::vector input_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_INPUT, true); + std::vector output_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_OUTPUT, true); + std::vector global_ports = circuit_lib.model_global_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_INPUT, true, true); + + /* Make sure: + * There is only 1 output port, + * each size of which is 1 + */ + VTR_ASSERT( (1 == output_ports.size()) && (1 == circuit_lib.port_size(output_ports[0])) ); + + /* Create a Verilog Module based on the circuit model, and add to module manager */ + ModuleId module_id = module_manager.find_module(circuit_lib.model_name(circuit_model)); + VTR_ASSERT(true == module_manager.valid_module_id(module_id)); + + /* dump module definition + ports */ + print_verilog_module_declaration(fp, module_manager, module_id); + /* Finish dumping ports */ + + /* Dump logics */ + switch (circuit_lib.gate_type(circuit_model)) { + case CIRCUIT_MODEL_GATE_AND: + case CIRCUIT_MODEL_GATE_OR: + print_verilog_and_or_gate_body(fp, circuit_lib, circuit_model, input_ports, output_ports); + break; + case CIRCUIT_MODEL_GATE_MUX2: + print_verilog_mux2_gate_body(fp, circuit_lib, circuit_model, input_ports, output_ports); + break; + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid topology for circuit model '%s'!\n", + circuit_lib.model_name(circuit_model).c_str()); + exit(1); + } + + /* Print timing info */ + print_verilog_submodule_timing(fp, circuit_lib, circuit_model); + + /* Print signal initialization */ + print_verilog_submodule_signal_init(fp, circuit_lib, circuit_model); + + /* Put an end to the Verilog module */ + print_verilog_module_end(fp, circuit_lib.model_name(circuit_model)); +} + +/************************************************ + * Generate the Verilog netlist for a constant generator, + * i.e., either VDD or GND + ***********************************************/ +static +void print_verilog_constant_generator_module(const ModuleManager& module_manager, + std::fstream& fp, + const size_t& const_value) { + /* Find the module in module manager */ + std::string module_name = generate_const_value_module_name(const_value); + ModuleId const_val_module = module_manager.find_module(module_name); + VTR_ASSERT(true == module_manager.valid_module_id(const_val_module)); + + /* Ensure a valid file handler*/ + VTR_ASSERT(true == valid_file_stream(fp)); + + /* dump module definition + ports */ + print_verilog_module_declaration(fp, module_manager, const_val_module); + /* Finish dumping ports */ + + /* Find the only output*/ + for (const ModulePortId& module_port_id : module_manager.module_ports(const_val_module)) { + BasicPort module_port = module_manager.module_port(const_val_module, module_port_id); + print_verilog_wire_constant_values(fp, module_port, std::vector(1, const_value)); + } + + /* Put an end to the Verilog module */ + print_verilog_module_end(fp, module_name); +} + +/************************************************ + * Generate the Verilog netlist for essential gates + * include inverters, buffers, transmission-gates, + * etc. + ***********************************************/ +void print_verilog_submodule_essentials(ModuleManager& module_manager, + std::vector& netlist_names, + const std::string& verilog_dir, + const std::string& submodule_dir, + const CircuitLibrary& circuit_lib) { + /* TODO: remove .bak when this part is completed and tested */ + std::string verilog_fname = submodule_dir + std::string(ESSENTIALS_VERILOG_FILE_NAME); + + std::fstream fp; + + /* Create the file stream */ + fp.open(verilog_fname, std::fstream::out | std::fstream::trunc); + /* Check if the file stream if valid or not */ + check_file_stream(verilog_fname.c_str(), fp); + + /* Create file */ + VTR_LOG("Generating Verilog netlist '%s' for essential gates...", + verilog_fname.c_str()); + + print_verilog_file_header(fp, "Essential gates"); + + print_verilog_include_defines_preproc_file(fp, verilog_dir); + + /* Print constant generators */ + /* VDD */ + print_verilog_constant_generator_module(module_manager, fp, 0); + /* GND */ + print_verilog_constant_generator_module(module_manager, fp, 1); + + for (const auto& circuit_model : circuit_lib.models()) { + /* By pass user-defined modules */ + if (!circuit_lib.model_verilog_netlist(circuit_model).empty()) { + continue; + } + if (CIRCUIT_MODEL_INVBUF == circuit_lib.model_type(circuit_model)) { + print_verilog_invbuf_module(module_manager, fp, circuit_lib, circuit_model); + continue; + } + if (CIRCUIT_MODEL_PASSGATE == circuit_lib.model_type(circuit_model)) { + print_verilog_passgate_module(module_manager, fp, circuit_lib, circuit_model); + continue; + } + if (CIRCUIT_MODEL_GATE == circuit_lib.model_type(circuit_model)) { + print_verilog_gate_module(module_manager, fp, circuit_lib, circuit_model); + continue; + } + } + + /* Close file handler*/ + fp.close(); + + /* Add fname to the netlist name list */ + netlist_names.push_back(verilog_fname); + + VTR_LOG("Done\n"); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_essential_gates.h b/openfpga/src/fpga_verilog/verilog_essential_gates.h new file mode 100644 index 000000000..33ce49c6c --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_essential_gates.h @@ -0,0 +1,25 @@ +#ifndef VERILOG_ESSENTIAL_GATES_H +#define VERILOG_ESSENTIAL_GATES_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include "circuit_library.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_verilog_submodule_essentials(ModuleManager& module_manager, + std::vector& netlist_names, + const std::string& verilog_dir, + const std::string& submodule_dir, + const CircuitLibrary& circuit_lib); + +} /* end namespace openfpga */ + +#endif From 3efd1a2a6d59711e1aadcded3bc3c0bb3e20a742 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 16 Feb 2020 12:04:03 -0700 Subject: [PATCH 152/645] print verilog module writer online --- .../fpga_verilog/verilog_module_writer.cpp | 502 ++++++++++++++++++ .../src/fpga_verilog/verilog_module_writer.h | 24 + 2 files changed, 526 insertions(+) create mode 100644 openfpga/src/fpga_verilog/verilog_module_writer.cpp create mode 100644 openfpga/src/fpga_verilog/verilog_module_writer.h diff --git a/openfpga/src/fpga_verilog/verilog_module_writer.cpp b/openfpga/src/fpga_verilog/verilog_module_writer.cpp new file mode 100644 index 000000000..15a5bc28d --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_module_writer.cpp @@ -0,0 +1,502 @@ +/******************************************************************** + * This file includes functions to write a Verilog module + * based on its definition in Module Manager + * + * Note that Verilog writer functions are just an outputter for the + * module definition. + * You should NOT modify any content of the module manager + * Please use const keyword to restrict this! + *******************************************************************/ +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" + +/* Headers from openfpgautil library */ +#include "openfpga_port.h" +#include "openfpga_digest.h" + +#include "module_manager_utils.h" +#include "verilog_port_types.h" +#include "verilog_writer_utils.h" +#include "verilog_module_writer.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Generate the name of a local wire for a undriven port inside Verilog + * module + *******************************************************************/ +static +std::string generate_verilog_undriven_local_wire_name(const ModuleManager& module_manager, + const ModuleId& module, + const ModulePortId& module_port_id) { + return module_manager.module_port(module, module_port_id).get_name(); +} + +/******************************************************************** + * Name a net for a local wire for a verilog module + * 1. If this is a local wire, name it after the __ + * 2. If this is not a local wire, name it after the port name of parent module + * + * In addition, it will assign the pin index as well + * + * Restriction: this function requires each net has single driver + * which is definitely always true in circuits. + *******************************************************************/ +static +BasicPort generate_verilog_port_for_module_net(const ModuleManager& module_manager, + const ModuleId& module_id, + const ModuleNetId& module_net) { + /* Check all the sink modules of the net, + * if we have a source module is the current module, this is not local wire + */ + for (ModuleNetSrcId src_id : module_manager.module_net_sources(module_id, module_net)) { + if (module_id == module_manager.net_source_modules(module_id, module_net)[src_id]) { + /* Here, this is not a local wire, return the port name of the src_port */ + ModulePortId net_src_port = module_manager.net_source_ports(module_id, module_net)[src_id]; + size_t src_pin_index = module_manager.net_source_pins(module_id, module_net)[src_id]; + return BasicPort(module_manager.module_port(module_id, net_src_port).get_name(), src_pin_index, src_pin_index); + } + } + + /* Check all the sink modules of the net */ + for (ModuleNetSinkId sink_id : module_manager.module_net_sinks(module_id, module_net)) { + if (module_id == module_manager.net_sink_modules(module_id, module_net)[sink_id]) { + /* Here, this is not a local wire, return the port name of the sink_port */ + ModulePortId net_sink_port = module_manager.net_sink_ports(module_id, module_net)[sink_id]; + size_t sink_pin_index = module_manager.net_sink_pins(module_id, module_net)[sink_id]; + return BasicPort(module_manager.module_port(module_id, net_sink_port).get_name(), sink_pin_index, sink_pin_index); + } + } + + /* Reach here, this is a local wire */ + std::string net_name; + + /* Each net must only one 1 source */ + VTR_ASSERT(1 == module_manager.net_source_modules(module_id, module_net).size()); + + /* Get the source module */ + ModuleId net_src_module = module_manager.net_source_modules(module_id, module_net)[ModuleNetSrcId(0)]; + /* Get the instance id */ + size_t net_src_instance = module_manager.net_source_instances(module_id, module_net)[ModuleNetSrcId(0)]; + /* Get the port id */ + ModulePortId net_src_port = module_manager.net_source_ports(module_id, module_net)[ModuleNetSrcId(0)]; + /* Get the pin id */ + size_t net_src_pin = module_manager.net_source_pins(module_id, module_net)[ModuleNetSrcId(0)]; + + /* Load user-defined name if we have it */ + if (false == module_manager.net_name(module_id, module_net).empty()) { + net_name = module_manager.net_name(module_id, module_net); + } else { + net_name = module_manager.module_name(net_src_module); + net_name += std::string("_") + std::to_string(net_src_instance) + std::string("_"); + net_name += module_manager.module_port(net_src_module, net_src_port).get_name(); + } + + return BasicPort(net_name, net_src_pin, net_src_pin); +} + +/******************************************************************** + * Find all the nets that are going to be local wires + * And organize it in a vector of ports + * Verilog wire writter function will use the output of this function + * to write up local wire declaration in Verilog format + *******************************************************************/ +static +std::map> find_verilog_module_local_wires(const ModuleManager& module_manager, + const ModuleId& module_id) { + std::map> local_wires; + + /* Local wires come from the child modules */ + for (ModuleNetId module_net : module_manager.module_nets(module_id)) { + /* Bypass dangling nets: + * Xifan Tang: I comment this part because it will shadow our problems in creating module graph + * Indeed this make a robust and a smooth Verilog module writing + * But I do want the module graph create is nice and clean !!! + */ + /* + if ( (0 == module_manager.net_source_modules(module_id, module_net).size()) + && (0 == module_manager.net_source_modules(module_id, module_net).size()) ) { + continue; + } + */ + + /* We only care local wires */ + if (false == module_net_is_local_wire(module_manager, module_id, module_net)) { + continue; + } + /* Find the name for this local wire */ + BasicPort local_wire_candidate = generate_verilog_port_for_module_net(module_manager, module_id, module_net); + /* Cache the net name, try to find it in the cache. + * If you can find one, it means this port may be mergeable, try to do merging. If merge fail, add to the local wire list + * If you cannot find one, it means that this port is not mergeable, add to the local wire list immediately. + */ + std::map>::iterator it = local_wires.find(local_wire_candidate.get_name()); + bool merged = false; + if (it != local_wires.end()) { + /* Try to merge to one the port in the list that can absorb the current local wire */ + for (BasicPort& local_wire : local_wires[local_wire_candidate.get_name()]) { + /* check if the candidate can be combined to an existing local wire */ + if (true == two_verilog_ports_mergeable(local_wire, local_wire_candidate)) { + /* Merge the ports */ + local_wire = merge_two_verilog_ports(local_wire, local_wire_candidate); + merged = true; + break; + } + } + } + + /* If not merged/not found in the cache, push the port to the list */ + if (false == merged) { + local_wires[local_wire_candidate.get_name()].push_back(local_wire_candidate); + } + } + + /* Local wires could also happen for undriven ports of child module */ + for (const ModuleId& child : module_manager.child_modules(module_id)) { + for (size_t instance : module_manager.child_module_instances(module_id, child)) { + for (const ModulePortId& child_port_id : module_manager.module_ports(child)) { + BasicPort child_port = module_manager.module_port(child, child_port_id); + std::vector undriven_pins; + for (size_t child_pin : child_port.pins()) { + /* Find the net linked to the pin */ + ModuleNetId net = module_manager.module_instance_port_net(module_id, child, instance, + child_port_id, child_pin); + /* We only care undriven ports */ + if (ModuleNetId::INVALID() == net) { + undriven_pins.push_back(child_pin); + } + } + if (true == undriven_pins.empty()) { + continue; + } + /* Reach here, we need a local wire, we will create a port only for the undriven pins of the port! */ + BasicPort instance_port; + instance_port.set_name(generate_verilog_undriven_local_wire_name(module_manager, child, child_port_id)); + /* We give the same port name as child module, this case happens to global ports */ + instance_port.set_width(*std::min_element(undriven_pins.begin(), undriven_pins.end()), + *std::max_element(undriven_pins.begin(), undriven_pins.end())); + + local_wires[instance_port.get_name()].push_back(instance_port); + } + } + } + + return local_wires; +} + +/******************************************************************** + * Print a Verilog wire connection + * We search all the sinks of the net, + * if we find a module output, we try to find the next module output + * among the sinks of the net + * For each module output (except the first one), we print a wire connection + *******************************************************************/ +static +void print_verilog_module_output_short_connection(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& module_id, + const ModuleNetId& module_net) { + /* Ensure a valid file stream */ + VTR_ASSERT(true == valid_file_stream(fp)); + + bool first_port = true; + BasicPort src_port; + + /* We have found a module input, now check all the sink modules of the net */ + for (ModuleNetSinkId net_sink : module_manager.module_net_sinks(module_id, module_net)) { + ModuleId sink_module = module_manager.net_sink_modules(module_id, module_net)[net_sink]; + if (module_id != sink_module) { + continue; + } + + /* Find the sink port and pin information */ + ModulePortId sink_port_id = module_manager.net_sink_ports(module_id, module_net)[net_sink]; + size_t sink_pin = module_manager.net_sink_pins(module_id, module_net)[net_sink]; + BasicPort sink_port(module_manager.module_port(module_id, sink_port_id).get_name(), sink_pin, sink_pin); + + /* For the first module output, this is the source port, we do nothing and go to the next */ + if (true == first_port) { + src_port = sink_port; + /* Flip the flag */ + first_port = false; + continue; + } + + /* We need to print a wire connection here */ + print_verilog_wire_connection(fp, sink_port, src_port, false); + } +} + + +/******************************************************************** + * Print a Verilog wire connection + * We search all the sources of the net, + * if we find a module input, we try to find a module output + * among the sinks of the net + * If we find such a pair, we print a wire connection + *******************************************************************/ +static +void print_verilog_module_local_short_connection(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& module_id, + const ModuleNetId& module_net) { + /* Ensure a valid file stream */ + VTR_ASSERT(true == valid_file_stream(fp)); + + for (ModuleNetSrcId net_src : module_manager.module_net_sources(module_id, module_net)) { + ModuleId src_module = module_manager.net_source_modules(module_id, module_net)[net_src]; + if (module_id != src_module) { + continue; + } + /* Find the source port and pin information */ + print_verilog_comment(fp, std::string("----- Net source id " + std::to_string(size_t(net_src)) + " -----")); + ModulePortId src_port_id = module_manager.net_source_ports(module_id, module_net)[net_src]; + size_t src_pin = module_manager.net_source_pins(module_id, module_net)[net_src]; + BasicPort src_port(module_manager.module_port(module_id, src_port_id).get_name(), src_pin, src_pin); + + /* We have found a module input, now check all the sink modules of the net */ + for (ModuleNetSinkId net_sink : module_manager.module_net_sinks(module_id, module_net)) { + ModuleId sink_module = module_manager.net_sink_modules(module_id, module_net)[net_sink]; + if (module_id != sink_module) { + continue; + } + + /* Find the sink port and pin information */ + print_verilog_comment(fp, std::string("----- Net sink id " + std::to_string(size_t(net_sink)) + " -----")); + ModulePortId sink_port_id = module_manager.net_sink_ports(module_id, module_net)[net_sink]; + size_t sink_pin = module_manager.net_sink_pins(module_id, module_net)[net_sink]; + BasicPort sink_port(module_manager.module_port(module_id, sink_port_id).get_name(), sink_pin, sink_pin); + + /* We need to print a wire connection here */ + print_verilog_wire_connection(fp, sink_port, src_port, false); + } + } +} + +/******************************************************************** + * Print short connections inside a Verilog module + * The short connection is defined as the direct connection + * between an input port of the module and an output port of the module + * This type of connection is not covered when printing Verilog instances + * Therefore, they are covered in this function + * + * module + * +-----------------------------+ + * | | + * inputA--->|---------------------------->|--->outputB + * | | + * | | + * | | + * +-----------------------------+ + *******************************************************************/ +static +void print_verilog_module_local_short_connections(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& module_id) { + /* Local wires come from the child modules */ + for (ModuleNetId module_net : module_manager.module_nets(module_id)) { + /* We only care the nets that indicate short connections */ + if (false == module_net_include_local_short_connection(module_manager, module_id, module_net)) { + continue; + } + print_verilog_comment(fp, std::string("----- Local connection due to Wire " + std::to_string(size_t(module_net)) + " -----")); + print_verilog_module_local_short_connection(fp, module_manager, module_id, module_net); + } +} + +/******************************************************************** + * Print output short connections inside a Verilog module + * The output short connection is defined as the direct connection + * between two output ports of the module + * This type of connection is not covered when printing Verilog instances + * Therefore, they are covered in this function + * + * module + * +-----------------------------+ + * | + * src------>+--------------->|--->outputA + * | | + * | | + * +--------------->|--->outputB + * +-----------------------------+ + *******************************************************************/ +static +void print_verilog_module_output_short_connections(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& module_id) { + /* Local wires come from the child modules */ + for (ModuleNetId module_net : module_manager.module_nets(module_id)) { + /* We only care the nets that indicate short connections */ + if (false == module_net_include_output_short_connection(module_manager, module_id, module_net)) { + continue; + } + print_verilog_module_output_short_connection(fp, module_manager, module_id, module_net); + } +} + +/******************************************************************** + * Write a Verilog instance to a file + * This function will name the input and output connections to + * the inputs/output or local wires available in the parent module + * + * Parent_module + * +-----------------------------+ + * | | + * | +--------------+ | + * | | | | + * | | child_module | | + * | | [instance] | | + * | +--------------+ | + * | | + * +-----------------------------+ + * + *******************************************************************/ +static +void write_verilog_instance_to_file(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const ModuleId& child_module, + const size_t& instance_id, + const bool& use_explicit_port_map) { + /* Ensure a valid file stream */ + VTR_ASSERT(true == valid_file_stream(fp)); + + /* Print module name */ + fp << "\t" << module_manager.module_name(child_module) << " "; + /* Print instance name: + * if we have an instance name, use it; + * if not, we use a default name _ + */ + if (true == module_manager.instance_name(parent_module, child_module, instance_id).empty()) { + fp << module_manager.module_name(child_module) << "_" << instance_id << "_" << " (" << std::endl; + } else { + fp << module_manager.instance_name(parent_module, child_module, instance_id) << " (" << std::endl; + } + + /* Print each port with/without explicit port map */ + /* port type2type mapping */ + std::map port_type2type_map; + port_type2type_map[ModuleManager::MODULE_GLOBAL_PORT] = VERILOG_PORT_CONKT; + port_type2type_map[ModuleManager::MODULE_GPIO_PORT] = VERILOG_PORT_CONKT; + port_type2type_map[ModuleManager::MODULE_INOUT_PORT] = VERILOG_PORT_CONKT; + port_type2type_map[ModuleManager::MODULE_INPUT_PORT] = VERILOG_PORT_CONKT; + port_type2type_map[ModuleManager::MODULE_OUTPUT_PORT] = VERILOG_PORT_CONKT; + port_type2type_map[ModuleManager::MODULE_CLOCK_PORT] = VERILOG_PORT_CONKT; + + /* Port sequence: global, inout, input, output and clock ports, */ + size_t port_cnt = 0; + for (const auto& kv : port_type2type_map) { + for (const ModulePortId& child_port_id : module_manager.module_port_ids_by_type(child_module, kv.first)) { + BasicPort child_port = module_manager.module_port(child_module, child_port_id); + if (0 != port_cnt) { + /* Do not dump a comma for the first port */ + fp << "," << std::endl; + } + /* Print port */ + fp << "\t\t"; + /* if explicit port map is required, output the port name */ + if (true == use_explicit_port_map) { + fp << "." << child_port.get_name() << "("; + } + + /* Create the port name and width to be used by the instance */ + std::vector instance_ports; + for (size_t child_pin : child_port.pins()) { + /* Find the net linked to the pin */ + ModuleNetId net = module_manager.module_instance_port_net(parent_module, child_module, instance_id, + child_port_id, child_pin); + BasicPort instance_port; + if (ModuleNetId::INVALID() == net) { + /* We give the same port name as child module, this case happens to global ports */ + instance_port.set_name(generate_verilog_undriven_local_wire_name(module_manager, child_module, child_port_id)); + instance_port.set_width(child_pin, child_pin); + } else { + /* Find the name for this child port */ + instance_port = generate_verilog_port_for_module_net(module_manager, parent_module, net); + } + /* Create the port information for the net */ + instance_ports.push_back(instance_port); + } + /* Try to merge the ports */ + std::vector merged_ports = combine_verilog_ports(instance_ports); + + /* Print a verilog port by combining the instance ports */ + fp << generate_verilog_ports(merged_ports); + + /* if explicit port map is required, output the pair of branket */ + if (true == use_explicit_port_map) { + fp << ")"; + } + port_cnt++; + } + } + + /* Print an end to the instance */ + fp << ");" << std::endl; +} + +/******************************************************************** + * Write a Verilog module to a file + * This is a key function, maybe most frequently called in our Verilog writer + * Note that file stream must be valid + *******************************************************************/ +void write_verilog_module_to_file(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& module_id, + const bool& use_explicit_port_map) { + + VTR_ASSERT(true == valid_file_stream(fp)); + + /* Ensure we have a valid module_id */ + VTR_ASSERT(module_manager.valid_module_id(module_id)); + + /* Print module declaration */ + print_verilog_module_declaration(fp, module_manager, module_id); + + /* Print an empty line as splitter */ + fp << std::endl; + + /* Print internal wires */ + std::map> local_wires = find_verilog_module_local_wires(module_manager, module_id); + for (std::pair> port_group : local_wires) { + for (const BasicPort& local_wire : port_group.second) { + fp << generate_verilog_port(VERILOG_PORT_WIRE, local_wire) << ";" << std::endl; + } + } + + /* Print an empty line as splitter */ + fp << std::endl; + + /* Print local connection (from module inputs to output! */ + print_verilog_comment(fp, std::string("----- BEGIN Local short connections -----")); + print_verilog_module_local_short_connections(fp, module_manager, module_id); + print_verilog_comment(fp, std::string("----- END Local short connections -----")); + + print_verilog_comment(fp, std::string("----- BEGIN Local output short connections -----")); + print_verilog_module_output_short_connections(fp, module_manager, module_id); + + print_verilog_comment(fp, std::string("----- END Local output short connections -----")); + /* Print an empty line as splitter */ + fp << std::endl; + + /* Print instances */ + for (ModuleId child_module : module_manager.child_modules(module_id)) { + for (size_t instance : module_manager.child_module_instances(module_id, child_module)) { + /* Print an instance */ + write_verilog_instance_to_file(fp, module_manager, module_id, child_module, instance, use_explicit_port_map); + /* Print an empty line as splitter */ + fp << std::endl; + } + } + + /* Print an end for the module */ + print_verilog_module_end(fp, module_manager.module_name(module_id)); + + /* Print an empty line as splitter */ + fp << std::endl; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_module_writer.h b/openfpga/src/fpga_verilog/verilog_module_writer.h new file mode 100644 index 000000000..1c0391492 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_module_writer.h @@ -0,0 +1,24 @@ +#ifndef VERILOG_MODULE_WRITER_H +#define VERILOG_MODULE_WRITER_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include "module_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void write_verilog_module_to_file(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& module_id, + const bool& use_explicit_port_map); + +} /* end namespace openfpga */ + +#endif From a88c4bc954f38edef0fb29ee7e753b357cab47dd Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 16 Feb 2020 12:21:59 -0700 Subject: [PATCH 153/645] add decode utils to libopenfpga and adapt local decoder writer in Verilog --- .../libopenfpgautil/src/openfpga_decode.cpp | 55 +++++ .../libopenfpgautil/src/openfpga_decode.h | 23 ++ .../src/fpga_verilog/verilog_decoders.cpp | 231 ++++++++++++++++++ openfpga/src/fpga_verilog/verilog_decoders.h | 32 +++ 4 files changed, 341 insertions(+) create mode 100644 libopenfpga/libopenfpgautil/src/openfpga_decode.cpp create mode 100644 libopenfpga/libopenfpgautil/src/openfpga_decode.h create mode 100644 openfpga/src/fpga_verilog/verilog_decoders.cpp create mode 100644 openfpga/src/fpga_verilog/verilog_decoders.h diff --git a/libopenfpga/libopenfpgautil/src/openfpga_decode.cpp b/libopenfpga/libopenfpgautil/src/openfpga_decode.cpp new file mode 100644 index 000000000..9839f13b9 --- /dev/null +++ b/libopenfpga/libopenfpgautil/src/openfpga_decode.cpp @@ -0,0 +1,55 @@ +/*************************************************************************************** + * This file includes functions that are used to decode integer to binary vectors + * or the reverse operation + ***************************************************************************************/ +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" + +#include "openfpga_decode.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Convert an integer to an one-hot encoding integer array + ********************************************************************/ +std::vector ito1hot_vec(const size_t& in_int, + const size_t& bin_len) { + /* Make sure we do not have any overflow! */ + VTR_ASSERT ( (in_int <= bin_len) ); + + /* Initialize */ + std::vector ret(bin_len, 0); + + if (bin_len == in_int) { + return ret; /* all zero case */ + } + ret[in_int] = 1; /* Keep a good sequence of bits */ + + return ret; +} + +/******************************************************************** + * Converter an integer to a binary vector + ********************************************************************/ +std::vector itobin_vec(const size_t& in_int, + const size_t& bin_len) { + std::vector ret(bin_len, 0); + + /* Make sure we do not have any overflow! */ + VTR_ASSERT ( (in_int < pow(2., bin_len)) ); + + size_t temp = in_int; + for (size_t i = 0; i < bin_len; i++) { + if (1 == temp % 2) { + ret[i] = 1; /* Keep a good sequence of bits */ + } + temp = temp / 2; + } + + return ret; +} + +} /* end namespace openfpga */ diff --git a/libopenfpga/libopenfpgautil/src/openfpga_decode.h b/libopenfpga/libopenfpgautil/src/openfpga_decode.h new file mode 100644 index 000000000..c79254f63 --- /dev/null +++ b/libopenfpga/libopenfpgautil/src/openfpga_decode.h @@ -0,0 +1,23 @@ +#ifndef OPENFPGA_DECODE_H +#define OPENFPGA_DECODE_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include + +/******************************************************************** + * Function declaration + *******************************************************************/ +/* namespace openfpga begins */ +namespace openfpga { + +std::vector ito1hot_vec(const size_t& in_int, + const size_t& bin_len); + +std::vector itobin_vec(const size_t& in_int, + const size_t& bin_len); + +} /* namespace openfpga ends */ + +#endif diff --git a/openfpga/src/fpga_verilog/verilog_decoders.cpp b/openfpga/src/fpga_verilog/verilog_decoders.cpp new file mode 100644 index 000000000..7689d3ed6 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_decoders.cpp @@ -0,0 +1,231 @@ +/*************************************************************************************** + * This file includes functions to generate Verilog modules of decoders + ***************************************************************************************/ +/* TODO: merge verilog_decoder.c to this source file and rename to verilog_decoder.cpp */ +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" + +/* Headers from openfpgautil library */ +#include "openfpga_digest.h" +#include "openfpga_decode.h" + +#include "decoder_library_utils.h" +#include "module_manager.h" + +#include "openfpga_naming.h" + +#include "verilog_constants.h" +#include "verilog_writer_utils.h" +#include "verilog_decoders.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/*************************************************************************************** + * Create a Verilog module for a decoder with a given output size + * + * Inputs + * | | ... | + * v v v + * +-----------+ + * / \ + * / Decoder \ + * +-----------------+ + * | | | ... | | | + * v v v v v v + * Outputs + * + * The outputs are assumes to be one-hot codes (at most only one '1' exist) + * Considering this fact, there are only num_of_outputs conditions to be encoded. + * Therefore, the number of inputs is ceil(log(num_of_outputs)/log(2)) + ***************************************************************************************/ +static +void print_verilog_mux_local_decoder_module(std::fstream& fp, + ModuleManager& module_manager, + const DecoderLibrary& decoder_lib, + const DecoderId& decoder) { + /* Get the number of inputs */ + size_t addr_size = decoder_lib.addr_size(decoder); + size_t data_size = decoder_lib.data_size(decoder); + + /* Validate the FILE handler */ + VTR_ASSERT(true == valid_file_stream(fp)); + + /* TODO: create a name for the local encoder */ + std::string module_name = generate_mux_local_decoder_subckt_name(addr_size, data_size); + + /* Create a Verilog Module based on the circuit model, and add to module manager */ + ModuleId module_id = module_manager.find_module(module_name); + VTR_ASSERT(true == module_manager.valid_module_id(module_id)); + /* Add module ports */ + /* Add each input port */ + BasicPort addr_port(generate_mux_local_decoder_addr_port_name(), addr_size); + /* Add each output port */ + BasicPort data_port(generate_mux_local_decoder_data_port_name(), data_size); + /* Data port is registered. It should be outputted as + * output reg [lsb:msb] data + */ + /* Add data_in port */ + BasicPort data_inv_port(generate_mux_local_decoder_data_inv_port_name(), data_size); + VTR_ASSERT(true == decoder_lib.use_data_inv_port(decoder)); + + /* dump module definition + ports */ + print_verilog_module_declaration(fp, module_manager, module_id); + /* Finish dumping ports */ + + print_verilog_comment(fp, std::string("----- BEGIN Verilog codes for Decoder convert " + std::to_string(addr_size) + "-bit addr to " + std::to_string(data_size) + "-bit data -----")); + + /* Print the truth table of this decoder */ + /* Internal logics */ + /* Early exit: Corner case for data size = 1 the logic is very simple: + * data = addr; + * data_inv = ~data_inv + */ + if (1 == data_size) { + print_verilog_wire_connection(fp, data_port, addr_port, false); + print_verilog_wire_connection(fp, data_inv_port, addr_port, true); + print_verilog_comment(fp, std::string("----- END Verilog codes for Decoder convert " + std::to_string(addr_size) + "-bit addr to " + std::to_string(data_size) + "-bit data -----")); + + /* Put an end to the Verilog module */ + print_verilog_module_end(fp, module_name); + return; + } + + /* We use a magic number -1 as the addr=1 should be mapped to ...1 + * Otherwise addr will map addr=1 to ..10 + * Note that there should be a range for the shift operators + * We should narrow the encoding to be applied to a given set of data + * This will lead to that any addr which falls out of the op code of data + * will give a all-zero code + * For example: + * data is 5-bit while addr is 3-bit + * data=8'b0_0000 will be encoded to addr=3'b001; + * data=8'b0_0001 will be encoded to addr=3'b010; + * data=8'b0_0010 will be encoded to addr=3'b011; + * data=8'b0_0100 will be encoded to addr=3'b100; + * data=8'b0_1000 will be encoded to addr=3'b101; + * data=8'b1_0000 will be encoded to addr=3'b110; + * The rest of addr codes 3'b110, 3'b111 will be decoded to data=8'b0_0000; + */ + + fp << "\t" << "always@(" << generate_verilog_port(VERILOG_PORT_CONKT, addr_port) << ")" << std::endl; + fp << "\t" << "case (" << generate_verilog_port(VERILOG_PORT_CONKT, addr_port) << ")" << std::endl; + /* Create a string for addr and data */ + for (size_t i = 0; i < data_size; ++i) { + fp << "\t\t" << generate_verilog_constant_values(itobin_vec(i, addr_size)); + fp << " : "; + fp << generate_verilog_port_constant_values(data_port, ito1hot_vec(i, data_size)); + fp << ";" << std::endl; + } + fp << "\t\t" << "default : "; + fp << generate_verilog_port_constant_values(data_port, ito1hot_vec(data_size - 1, data_size)); + fp << ";" << std::endl; + fp << "\t" << "endcase" << std::endl; + + print_verilog_wire_connection(fp, data_inv_port, data_port, true); + + print_verilog_comment(fp, std::string("----- END Verilog codes for Decoder convert " + std::to_string(addr_size) + "-bit addr to " + std::to_string(data_size) + "-bit data -----")); + + /* Put an end to the Verilog module */ + print_verilog_module_end(fp, module_name); +} + + +/*************************************************************************************** + * This function will generate all the unique Verilog modules of local decoders for + * the multiplexers used in a FPGA fabric + * It will reach the goal in two steps: + * 1. Find the unique local decoders w.r.t. the number of inputs/outputs + * We will generate the subgraphs from the multiplexing graph of each multiplexers + * The number of memory bits is the number of outputs. + * From that we can infer the number of inputs of each local decoders. + * Here is an illustrative example of how local decoders are interfaced with multi-level MUXes + * + * +---------+ +---------+ + * | Local | | Local | + * | Decoder | | Decoder | + * | A | | B | + * +---------+ +---------+ + * | ... | | ... | + * v v v v + * +--------------+ +--------------+ + * | MUX Level 0 |--->| MUX Level 1 | + * +--------------+ +--------------+ + * 2. Generate local decoder Verilog modules using behavioral description. + * Note that the implementation of local decoders can be dependent on the technology + * and standard cell libraries. + * Therefore, behavioral Verilog is used and the local decoders should be synthesized + * before running the back-end flow for FPGA fabric + * See more details in the function print_verilog_mux_local_decoder() for more details + ***************************************************************************************/ +void print_verilog_submodule_mux_local_decoders(ModuleManager& module_manager, + std::vector& netlist_names, + const MuxLibrary& mux_lib, + const CircuitLibrary& circuit_lib, + const std::string& verilog_dir, + const std::string& submodule_dir) { + std::string verilog_fname(submodule_dir + std::string(LOCAL_ENCODER_VERILOG_FILE_NAME)); + + /* Create the file stream */ + std::fstream fp; + fp.open(verilog_fname, std::fstream::out | std::fstream::trunc); + + check_file_stream(verilog_fname.c_str(), fp); + + /* Print out debugging information for if the file is not opened/created properly */ + VTR_LOG("Writing Verilog netlist for local decoders for multiplexers '%s'...", + verilog_fname.c_str()); + + print_verilog_file_header(fp, "Local Decoders for Multiplexers"); + + print_verilog_include_defines_preproc_file(fp, verilog_dir); + + /* Create a library for local encoders with different sizes */ + DecoderLibrary decoder_lib; + + /* Find unique local decoders for unique branches shared by the multiplexers */ + for (auto mux : mux_lib.muxes()) { + /* Local decoders are need only when users specify them */ + CircuitModelId mux_circuit_model = mux_lib.mux_circuit_model(mux); + /* If this MUX does not need local decoder, we skip it */ + if (false == circuit_lib.mux_use_local_encoder(mux_circuit_model)) { + continue; + } + + const MuxGraph& mux_graph = mux_lib.mux_graph(mux); + /* Create a mux graph for the branch circuit */ + std::vector branch_mux_graphs = mux_graph.build_mux_branch_graphs(); + /* Add the decoder to the decoder library */ + for (auto branch_mux_graph : branch_mux_graphs) { + /* The decoder size depends on the number of memories of a branch MUX. + * Note that only when there are >=2 memories, a decoder is needed + */ + size_t decoder_data_size = branch_mux_graph.num_memory_bits(); + if (0 == decoder_data_size) { + continue; + } + /* Try to find if the decoder already exists in the library, + * If there is no such decoder, add it to the library + */ + add_mux_local_decoder_to_library(decoder_lib, decoder_data_size); + } + } + + /* Generate Verilog modules for the found unique local encoders */ + for (const auto& decoder : decoder_lib.decoders()) { + print_verilog_mux_local_decoder_module(fp, module_manager, decoder_lib, decoder); + } + + /* Close the file stream */ + fp.close(); + + /* Add fname to the netlist name list */ + netlist_names.push_back(verilog_fname); + + VTR_LOG("Done\n"); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_decoders.h b/openfpga/src/fpga_verilog/verilog_decoders.h new file mode 100644 index 000000000..41f932932 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_decoders.h @@ -0,0 +1,32 @@ +#ifndef VERILOG_DECODERS_H +#define VERILOG_DECODERS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include + +#include "circuit_library.h" +#include "mux_graph.h" +#include "mux_library.h" +#include "module_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_verilog_submodule_mux_local_decoders(ModuleManager& module_manager, + std::vector& netlist_names, + const MuxLibrary& mux_lib, + const CircuitLibrary& circuit_lib, + const std::string& verilog_dir, + const std::string& submodule_dir); + +} /* end namespace openfpga */ + +#endif From c9d8120ae09b3c7ffda1d294931562ef4f4c6963 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 16 Feb 2020 12:35:41 -0700 Subject: [PATCH 154/645] adapt Verilog mux writer --- openfpga/src/fpga_verilog/verilog_constants.h | 2 + openfpga/src/fpga_verilog/verilog_mux.cpp | 1281 +++++++++++++++++ openfpga/src/fpga_verilog/verilog_mux.h | 32 + 3 files changed, 1315 insertions(+) create mode 100644 openfpga/src/fpga_verilog/verilog_mux.cpp create mode 100644 openfpga/src/fpga_verilog/verilog_mux.h diff --git a/openfpga/src/fpga_verilog/verilog_constants.h b/openfpga/src/fpga_verilog/verilog_constants.h index 9562bd730..49a73bf6d 100644 --- a/openfpga/src/fpga_verilog/verilog_constants.h +++ b/openfpga/src/fpga_verilog/verilog_constants.h @@ -41,4 +41,6 @@ constexpr char* ESSENTIALS_VERILOG_FILE_NAME = "inv_buf_passgate.v"; constexpr char* CONFIG_PERIPHERAL_VERILOG_FILE_NAME = "config_peripherals.v"; constexpr char* USER_DEFINED_TEMPLATE_VERILOG_FILE_NAME = "user_defined_templates.v"; +constexpr char* VERILOG_MUX_BASIS_POSTFIX = "_basis"; + #endif diff --git a/openfpga/src/fpga_verilog/verilog_mux.cpp b/openfpga/src/fpga_verilog/verilog_mux.cpp new file mode 100644 index 000000000..b78a9362a --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_mux.cpp @@ -0,0 +1,1281 @@ +/*********************************************** + * This file includes functions to generate + * Verilog submodules for multiplexers. + * including both fundamental submodules + * such as a branch in a multiplexer + * and the full multiplexer + **********************************************/ +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" + +/* Headers from readarch library */ +#include "physical_types.h" + +/* Headers from readarcopenfpga library */ +#include "circuit_types.h" + +/* Headers from openfpgautil library */ +#include "openfpga_digest.h" + +#include "mux_graph.h" +#include "module_manager.h" +#include "mux_utils.h" +#include "circuit_library_utils.h" +#include "decoder_library_utils.h" + +#include "openfpga_naming.h" + +#include "verilog_constants.h" +#include "verilog_writer_utils.h" +#include "verilog_module_writer.h" +#include "verilog_mux.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/********************************************************************* + * Generate behavior-level Verilog codes modeling an branch circuit + * for a multiplexer with the given size + *********************************************************************/ +static +void generate_verilog_cmos_mux_branch_body_behavioral(std::fstream& fp, + const BasicPort& input_port, + const BasicPort& output_port, + const BasicPort& mem_port, + const MuxGraph& mux_graph, + const size_t& default_mem_val) { + /* Make sure we have a valid file handler*/ + VTR_ASSERT(true == valid_file_stream(fp)); + + /* Verilog Behavior description for a MUX */ + print_verilog_comment(fp, std::string("---- Behavioral-level description -----")); + + /* Add an internal register for the output */ + BasicPort outreg_port("out_reg", mux_graph.num_outputs()); + /* Print the port */ + fp << "\t" << generate_verilog_port(VERILOG_PORT_REG, outreg_port) << ";" << std::endl; + + /* Generate the case-switch table */ + fp << "\talways @(" << generate_verilog_port(VERILOG_PORT_CONKT, input_port) << ", " << generate_verilog_port(VERILOG_PORT_CONKT, mem_port) << ")" << std::endl; + fp << "\tcase (" << generate_verilog_port(VERILOG_PORT_CONKT, mem_port) << ")" << std::endl; + + /* Output the netlist following the connections in mux_graph */ + /* Iterate over the inputs */ + for (const auto& mux_input : mux_graph.inputs()) { + BasicPort cur_input_port(input_port.get_name(), size_t(mux_graph.input_id(mux_input)), size_t(mux_graph.input_id(mux_input))); + /* Iterate over the outputs */ + for (const auto& mux_output : mux_graph.outputs()) { + BasicPort cur_output_port(output_port.get_name(), size_t(mux_graph.output_id(mux_output)), size_t(mux_graph.output_id(mux_output))); + /* if there is a connection between the input and output, a tgate will be outputted */ + std::vector edges = mux_graph.find_edges(mux_input, mux_output); + /* There should be only one edge or no edge*/ + VTR_ASSERT((1 == edges.size()) || (0 == edges.size())); + /* No need to output tgates if there are no edges between two nodes */ + if (0 == edges.size()) { + continue; + } + /* For each case, generate the logic levels for all the inputs */ + /* In each case, only one mem is enabled */ + fp << "\t\t" << mem_port.get_width() << "'b"; + std::string case_code(mem_port.get_width(), default_mem_val); + + /* Find the mem_id controlling the edge */ + MuxMemId mux_mem = mux_graph.find_edge_mem(edges[0]); + /* Flip a bit by the mem_id */ + if (false == mux_graph.is_edge_use_inv_mem(edges[0])) { + case_code[size_t(mux_mem)] = '1'; + } else { + case_code[size_t(mux_mem)] = '0'; + } + fp << case_code << ": " << generate_verilog_port(VERILOG_PORT_CONKT, outreg_port) << " <= "; + fp << generate_verilog_port(VERILOG_PORT_CONKT, cur_input_port) << ";" << std::endl; + } + } + + /* Default case: outputs are at high-impedance state 'z' */ + std::string default_case(mux_graph.num_outputs(), 'z'); + fp << "\t\tdefault: " << generate_verilog_port(VERILOG_PORT_CONKT, outreg_port) << " <= "; + fp << mux_graph.num_outputs() << "'b" << default_case << ";" << std::endl; + + /* End the case */ + fp << "\tendcase" << std::endl; + + /* Wire registers to output ports */ + fp << "\tassign " << generate_verilog_port(VERILOG_PORT_CONKT, output_port) << " = "; + fp << generate_verilog_port(VERILOG_PORT_CONKT, outreg_port) << ";" << std::endl; +} + +/********************************************************************* + * Generate Verilog codes modeling an branch circuit + * for a CMOS multiplexer with the given size + * Support structural and behavioral Verilog codes + *********************************************************************/ +static +void print_verilog_cmos_mux_branch_module_behavioral(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + std::fstream& fp, + const CircuitModelId& mux_model, + const std::string& module_name, + const MuxGraph& mux_graph) { + /* Get the tgate model */ + CircuitModelId tgate_model = circuit_lib.pass_gate_logic_model(mux_model); + + /* Skip output if the tgate model is a MUX2, it is handled by essential-gate generator */ + if (CIRCUIT_MODEL_GATE == circuit_lib.model_type(tgate_model)) { + VTR_ASSERT(CIRCUIT_MODEL_GATE_MUX2 == circuit_lib.gate_type(tgate_model)); + return; + } + + /* Make sure we have a valid file handler*/ + VTR_ASSERT(true == valid_file_stream(fp)); + + /* Generate the Verilog netlist according to the mux_graph */ + /* Find out the number of inputs */ + size_t num_inputs = mux_graph.num_inputs(); + /* Find out the number of outputs */ + size_t num_outputs = mux_graph.num_outputs(); + /* Find out the number of memory bits */ + size_t num_mems = mux_graph.num_memory_bits(); + + /* Check codes to ensure the port of Verilog netlists will match */ + /* MUX graph must have only 1 output */ + VTR_ASSERT(1 == num_outputs); + /* MUX graph must have only 1 level*/ + VTR_ASSERT(1 == mux_graph.num_levels()); + + /* Create a Verilog Module based on the circuit model, and add to module manager */ + ModuleId mux_module = module_manager.find_module(module_name); + VTR_ASSERT(true == module_manager.valid_module_id(mux_module)); + /* Find module ports */ + /* Find each input port */ + BasicPort input_port("in", num_inputs); + /* Find each output port */ + BasicPort output_port("out", num_outputs); + /* Find each memory port */ + BasicPort mem_port("mem", num_mems); + + /* dump module definition + ports */ + print_verilog_module_declaration(fp, module_manager, mux_module); + + /* Print the internal logic in behavioral Verilog codes */ + /* Get the default value of SRAM ports */ + std::vector regular_sram_ports = find_circuit_regular_sram_ports(circuit_lib, mux_model); + VTR_ASSERT(1 == regular_sram_ports.size()); + std::string mem_default_val = std::to_string(circuit_lib.port_default_value(regular_sram_ports[0])); + /* Mem string must be only 1-bit! */ + VTR_ASSERT(1 == mem_default_val.length()); + generate_verilog_cmos_mux_branch_body_behavioral(fp, input_port, output_port, mem_port, mux_graph, mem_default_val[0]); + + /* Put an end to the Verilog module */ + print_verilog_module_end(fp, module_name); +} + +/********************************************************************* + * Dump a structural verilog for RRAM MUX basis module + * This is only called when structural verilog dumping option is enabled for this spice model + * IMPORTANT: the structural verilog can NOT be used for functionality verification!!! + * TODO: This part is quite restricted to the way we implemented our RRAM FPGA + * Should be reworked to be more generic !!! + * + * By structural the schematic is splitted into two parts: left part and right part + * The left part includes BLB[0..N-1] and WL[0..N-1] signals as well as RRAMs + * The right part includes BLB[N] and WL[N] + * Corresponding Schematic is as follows: + * + * LEFT PART | RIGHT PART + * + * BLB[0] BLB[N] + * | | + * \|/ \|/ + * in[0] ---->RRAM[0]-----+ + * | + * BLB[1] | + * | | + * \|/ | + * in[1] ---->RRAM[1]-----+ + * |-----> out[0] + * ... + * | + * in[N-1] ---->RRAM[N-1]---+ + * /|\ /|\ + * | | + * BLB[N-1] WL[N] + * + * Working principle: + * 1. Set a RRAM[i]: enable BLB[i] and WL[N] + * 2. Reset a RRAM[i]: enable BLB[N] and WL[i] + * 3. Operation: disable all BLBs and WLs + * + * The structure is done in the way we implement the physical layout of RRAM MUX + * It is NOT the only road to the goal!!! + *********************************************************************/ +static +void generate_verilog_rram_mux_branch_body_structural(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + std::fstream& fp, + const ModuleId& module_id, + const CircuitModelId& circuit_model, + const BasicPort& input_port, + const BasicPort& output_port, + const BasicPort& blb_port, + const BasicPort& wl_port, + const MuxGraph& mux_graph) { + std::string progTE_module_name("PROG_TE"); + std::string progBE_module_name("PROG_BE"); + + /* Make sure we have a valid file handler*/ + VTR_ASSERT(true == valid_file_stream(fp)); + + /* Verilog Behavior description for a MUX */ + print_verilog_comment(fp, std::string("---- Structure-level description of RRAM MUX -----")); + + /* Print internal structure of 4T1R programming structures + * Written in structural Verilog + * The whole structure-level description is divided into two parts: + * 1. Left part consists of N PROG_TE modules, each of which + * includes a PMOS, a NMOS and a RRAM, which is actually the left + * part of a 4T1R programming structure + * 2. Right part includes only a PROG_BE module, which consists + * of a PMOS and a NMOS, which is actually the right part of a + * 4T1R programming sturcture + */ + /* Create a module for the progTE and register it in the module manager + * Structure of progTE + * + * +----------+ + * in--->| | + * BLB-->| progTE |--> out + * WL--->| | + * +----------+ + */ + ModuleId progTE_module_id = module_manager.add_module(progTE_module_name); + /* If there is already such as module inside, we just ned to find the module id */ + if (ModuleId::INVALID() == progTE_module_id) { + progTE_module_id = module_manager.find_module(progTE_module_name); + /* We should have a valid id! */ + VTR_ASSERT(ModuleId::INVALID() != progTE_module_id); + } + /* Add ports to the module */ + /* input port */ + BasicPort progTE_in_port("A", 1); + module_manager.add_port(progTE_module_id, progTE_in_port, ModuleManager::MODULE_INPUT_PORT); + /* WL port */ + BasicPort progTE_wl_port("WL", 1); + module_manager.add_port(progTE_module_id, progTE_wl_port, ModuleManager::MODULE_INPUT_PORT); + /* BLB port */ + BasicPort progTE_blb_port("BLB", 1); + module_manager.add_port(progTE_module_id, progTE_blb_port, ModuleManager::MODULE_INPUT_PORT); + /* output port */ + BasicPort progTE_out_port("Z", 1); + module_manager.add_port(progTE_module_id, progTE_out_port, ModuleManager::MODULE_INPUT_PORT); + + /* LEFT part: Verilog code generation */ + /* Iterate over the inputs */ + for (const auto& mux_input : mux_graph.inputs()) { + BasicPort cur_input_port(input_port.get_name(), size_t(mux_graph.input_id(mux_input)), size_t(mux_graph.input_id(mux_input))); + /* Iterate over the outputs */ + for (const auto& mux_output : mux_graph.outputs()) { + BasicPort cur_output_port(output_port.get_name(), size_t(mux_graph.output_id(mux_output)), size_t(mux_graph.output_id(mux_output))); + /* if there is a connection between the input and output, a tgate will be outputted */ + std::vector edges = mux_graph.find_edges(mux_input, mux_output); + /* There should be only one edge or no edge*/ + VTR_ASSERT((1 == edges.size()) || (0 == edges.size())); + /* No need to output tgates if there are no edges between two nodes */ + if (0 == edges.size()) { + continue; + } + /* Create a port-to-port name map */ + std::map port2port_name_map; + /* input port */ + port2port_name_map[progTE_in_port.get_name()] = cur_input_port; + /* output port */ + port2port_name_map[progTE_out_port.get_name()] = cur_output_port; + /* Find the mem_id controlling the edge */ + MuxMemId mux_mem = mux_graph.find_edge_mem(edges[0]); + BasicPort cur_blb_port(blb_port.get_name(), size_t(mux_mem), size_t(mux_mem)); + BasicPort cur_wl_port(wl_port.get_name(), size_t(mux_mem), size_t(mux_mem)); + /* RRAM configuration port: there should not be any inverted edge in RRAM MUX! */ + VTR_ASSERT( false == mux_graph.is_edge_use_inv_mem(edges[0]) ); + /* wire mem to mem of module, and wire mem_inv to mem_inv of module */ + port2port_name_map[progTE_blb_port.get_name()] = cur_blb_port; + port2port_name_map[progTE_wl_port.get_name()] = cur_wl_port; + /* Output an instance of the module */ + print_verilog_module_instance(fp, module_manager, module_id, progTE_module_id, port2port_name_map, circuit_lib.dump_explicit_port_map(circuit_model)); + /* IMPORTANT: this update MUST be called after the instance outputting!!!! + * update the module manager with the relationship between the parent and child modules + */ + module_manager.add_child_module(module_id, progTE_module_id); + } + } + + /* Create a module for the progBE and register it in the module manager + * Structure of progBE + * + * +----------+ + * | | + * BLB-->| progBE |<-> out + * WL--->| | + * +----------+ + */ + ModuleId progBE_module_id = module_manager.add_module(progBE_module_name); + /* If there is already such as module inside, we just ned to find the module id */ + if (ModuleId::INVALID() == progBE_module_id) { + progBE_module_id = module_manager.find_module(progBE_module_name); + /* We should have a valid id! */ + VTR_ASSERT(ModuleId::INVALID() != progBE_module_id); + } + /* Add ports to the module */ + /* inout port */ + BasicPort progBE_inout_port("INOUT", 1); + module_manager.add_port(progBE_module_id, progBE_inout_port, ModuleManager::MODULE_INOUT_PORT); + /* WL port */ + BasicPort progBE_wl_port("WL", 1); + module_manager.add_port(progBE_module_id, progBE_wl_port, ModuleManager::MODULE_INPUT_PORT); + /* BLB port */ + BasicPort progBE_blb_port("BLB", 1); + module_manager.add_port(progBE_module_id, progBE_blb_port, ModuleManager::MODULE_INPUT_PORT); + + /* RIGHT part: Verilog code generation */ + /* Iterate over the outputs */ + for (const auto& mux_output : mux_graph.outputs()) { + BasicPort cur_output_port(output_port.get_name(), size_t(mux_graph.output_id(mux_output)), size_t(mux_graph.output_id(mux_output))); + /* Create a port-to-port name map */ + std::map port2port_name_map; + /* Wire the output port to the INOUT port */ + port2port_name_map[progBE_inout_port.get_name()] = cur_output_port; + /* Find the mem_id controlling the edge */ + BasicPort cur_blb_port(blb_port.get_name(), mux_graph.num_memory_bits(), mux_graph.num_memory_bits()); + BasicPort cur_wl_port(wl_port.get_name(), mux_graph.num_memory_bits(), mux_graph.num_memory_bits()); + port2port_name_map[progBE_blb_port.get_name()] = cur_blb_port; + port2port_name_map[progBE_wl_port.get_name()] = cur_wl_port; + /* Output an instance of the module */ + print_verilog_module_instance(fp, module_manager, module_id, progBE_module_id, port2port_name_map, circuit_lib.dump_explicit_port_map(circuit_model)); + /* IMPORTANT: this update MUST be called after the instance outputting!!!! + * update the module manager with the relationship between the parent and child modules + */ + module_manager.add_child_module(module_id, progBE_module_id); + } +} + +/********************************************************************* + * Generate behavior-level Verilog codes modeling an branch circuit + * for a RRAM-based multiplexer with the given size + * Corresponding Schematic is as follows: + * + * BLB[0] BLB[N] + * | | + * \|/ \|/ + * in[0] ---->RRAM[0]-----+ + * | + * BLB[1] | + * | | + * \|/ | + * in[1] ---->RRAM[1]-----+ + * |-----> out[0] + * ... + * | + * in[N-1] ---->RRAM[N-1]---+ + * /|\ /|\ + * | | + * BLB[N-1] WL[N] + * + * Working principle: + * 1. Set a RRAM[i]: enable BLB[i] and WL[N] + * 2. Reset a RRAM[i]: enable BLB[N] and WL[i] + * 3. Operation: disable all BLBs and WLs + * + * TODO: Elaborate the codes to output the circuit logic + * following the mux_graph! + *********************************************************************/ +static +void generate_verilog_rram_mux_branch_body_behavioral(std::fstream& fp, + const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const BasicPort& input_port, + const BasicPort& output_port, + const BasicPort& blb_port, + const BasicPort& wl_port, + const MuxGraph& mux_graph) { + /* Make sure we have a valid file handler*/ + VTR_ASSERT(true == valid_file_stream(fp)); + + /* Verilog Behavior description for a MUX */ + print_verilog_comment(fp, std::string("---- Behavioral-level description of RRAM MUX -----")); + + /* Add an internal register for the output */ + BasicPort outreg_port("out_reg", mux_graph.num_inputs()); + /* Print the port */ + fp << "\t" << generate_verilog_port(VERILOG_PORT_REG, outreg_port) << ";" << std::endl; + + /* Print the internal logics */ + fp << "\t" << "always @("; + fp << generate_verilog_port(VERILOG_PORT_CONKT, blb_port); + fp << ", "; + fp << generate_verilog_port(VERILOG_PORT_CONKT, wl_port); + fp << ")"; + fp << " begin" << std::endl; + + /* Only when the last bit of wl is enabled, + * the propagating path can be changed + * (RRAM value can be changed) */ + fp << "\t\t" << "if ("; + BasicPort set_enable_port(wl_port.get_name(), wl_port.get_width() - 1, wl_port.get_width() - 1); + fp << generate_verilog_port(VERILOG_PORT_CONKT, set_enable_port); + /* We need two config-enable ports: prog_EN and prog_ENb */ + bool find_prog_EN = false; + bool find_prog_ENb = false; + for (const auto& port : circuit_lib.model_global_ports(circuit_model, true)) { + /* Bypass non-config-enable ports */ + if (false == circuit_lib.port_is_config_enable(port)) { + continue; + } + /* Reach here, the port should be is_config_enable */ + /* Create a port object */ + fp << " && "; + BasicPort prog_en_port(circuit_lib.port_prefix(port), circuit_lib.port_size(port)); + if ( 0 == circuit_lib.port_default_value(port)) { + /* Default value = 0 means that this is a prog_EN port */ + fp << generate_verilog_port(VERILOG_PORT_CONKT, prog_en_port); + find_prog_EN = true; + } else { + VTR_ASSERT ( 1 == circuit_lib.port_default_value(port)); + /* Default value = 1 means that this is a prog_ENb port, add inversion in the if condition */ + fp << "(~" << generate_verilog_port(VERILOG_PORT_CONKT, prog_en_port) << ")"; + find_prog_ENb = true; + } + } + /* Check if we find any config_enable signals */ + if (false == find_prog_EN) { + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Unable to find a config_enable signal with default value 0 for a RRAM MUX '%s'!\n", + circuit_lib.model_name(circuit_model).c_str()); + exit(1); + } + if (false == find_prog_ENb) { + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Unable to find a config_enable signal with default value 1 for a RRAM MUX '%s'!\n", + circuit_lib.model_name(circuit_model).c_str()); + exit(1); + } + + /* Finish the if clause */ + fp << ") begin" << std::endl; + + for (const auto& mux_input : mux_graph.inputs()) { + /* First if clause need tabs */ + if ( 0 == size_t(mux_graph.input_id(mux_input)) ) { + fp << "\t\t\t"; + } + fp << "if (1 == "; + /* Create a temp port of a BLB bit */ + BasicPort cur_blb_port(blb_port.get_name(), size_t(mux_graph.input_id(mux_input)), size_t(mux_graph.input_id(mux_input))); + fp << generate_verilog_port(VERILOG_PORT_CONKT, cur_blb_port); + fp << ") begin" << std::endl; + fp << "\t\t\t\t" << "assign "; + fp << outreg_port.get_name(); + fp << " = " << size_t(mux_graph.input_id(mux_input)) << ";" << std::endl; + fp << "\t\t\t" << "end else "; + } + fp << "begin" << std::endl; + fp << "\t\t\t\t" << "assign "; + fp << outreg_port.get_name(); + fp << " = 0;" << std::endl; + fp << "\t\t\t" << "end" << std::endl; + fp << "\t\t" << "end" << std::endl; + fp << "\t" << "end" << std::endl; + + fp << "\t" << "assign "; + fp << generate_verilog_port(VERILOG_PORT_CONKT, output_port); + fp << " = "; + fp << input_port.get_name() << "["; + fp << outreg_port.get_name(); + fp << "];" << std::endl; +} + +/********************************************************************* + * Generate Verilog codes modeling an branch circuit + * for a RRAM-based multiplexer with the given size + * Support structural and behavioral Verilog codes + *********************************************************************/ +static +void generate_verilog_rram_mux_branch_module(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + std::fstream& fp, + const CircuitModelId& circuit_model, + const std::string& module_name, + const MuxGraph& mux_graph, + const bool& use_structural_verilog) { + /* Make sure we have a valid file handler*/ + VTR_ASSERT(true == valid_file_stream(fp)); + + /* Get the input ports from the mux */ + std::vector mux_input_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_INPUT, true); + /* Get the output ports from the mux */ + std::vector mux_output_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_OUTPUT, true); + /* Get the BL and WL ports from the mux */ + std::vector mux_blb_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_BLB, true); + std::vector mux_wl_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_WL, true); + + /* Generate the Verilog netlist according to the mux_graph */ + /* Find out the number of inputs */ + size_t num_inputs = mux_graph.num_inputs(); + /* Find out the number of outputs */ + size_t num_outputs = mux_graph.num_outputs(); + /* Find out the number of memory bits */ + size_t num_mems = mux_graph.num_memory_bits(); + + /* Check codes to ensure the port of Verilog netlists will match */ + /* MUX graph must have only 1 output */ + VTR_ASSERT(1 == num_outputs); + /* MUX graph must have only 1 level*/ + VTR_ASSERT(1 == mux_graph.num_levels()); + /* MUX graph must have only 1 input and 1 BLB and 1 WL port */ + VTR_ASSERT(1 == mux_input_ports.size()); + VTR_ASSERT(1 == mux_output_ports.size()); + VTR_ASSERT(1 == mux_blb_ports.size()); + VTR_ASSERT(1 == mux_wl_ports.size()); + + /* Create a Verilog Module based on the circuit model, and add to module manager */ + ModuleId module_id = module_manager.find_module(module_name); + VTR_ASSERT(true == module_manager.valid_module_id(module_id)); + + /* Find each input port */ + BasicPort input_port(circuit_lib.port_prefix(mux_input_ports[0]), num_inputs); + + /* Find each output port */ + BasicPort output_port(circuit_lib.port_prefix(mux_output_ports[0]), num_outputs); + + /* Find RRAM programming ports, + * RRAM MUXes require one more pair of BLB and WL + * to configure the memories. See schematic for details + */ + BasicPort blb_port(circuit_lib.port_prefix(mux_blb_ports[0]), num_mems + 1); + + BasicPort wl_port(circuit_lib.port_prefix(mux_wl_ports[0]), num_mems + 1); + + /* dump module definition + ports */ + print_verilog_module_declaration(fp, module_manager, module_id); + + /* Print the internal logic in either structural or behavioral Verilog codes */ + if (true == use_structural_verilog) { + generate_verilog_rram_mux_branch_body_structural(module_manager, circuit_lib, fp, module_id, circuit_model, input_port, output_port, blb_port, wl_port, mux_graph); + } else { + generate_verilog_rram_mux_branch_body_behavioral(fp, circuit_lib, circuit_model, input_port, output_port, blb_port, wl_port, mux_graph); + } + + /* Put an end to the Verilog module */ + print_verilog_module_end(fp, module_name); +} + +/*********************************************** + * Generate Verilog codes modeling an branch circuit + * for a multiplexer with the given size + **********************************************/ +static +void generate_verilog_mux_branch_module(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + std::fstream& fp, + const CircuitModelId& mux_model, + const size_t& mux_size, + const MuxGraph& mux_graph, + const bool& use_explicit_port_map) { + std::string module_name = generate_mux_branch_subckt_name(circuit_lib, mux_model, mux_size, mux_graph.num_inputs(), VERILOG_MUX_BASIS_POSTFIX); + + /* Multiplexers built with different technology is in different organization */ + switch (circuit_lib.design_tech_type(mux_model)) { + case CIRCUIT_MODEL_DESIGN_CMOS: + /* Skip module writing if the branch subckt is a standard cell! */ + if (true == circuit_lib.valid_model_id(circuit_lib.model(module_name))) { + /* This model must be a MUX2 gate */ + VTR_ASSERT(CIRCUIT_MODEL_GATE == circuit_lib.model_type(circuit_lib.model(module_name))); + VTR_ASSERT(CIRCUIT_MODEL_GATE_MUX2 == circuit_lib.gate_type(circuit_lib.model(module_name))); + break; + } + if (true == circuit_lib.dump_structural_verilog(mux_model)) { + /* Structural verilog can be easily generated by module writer */ + ModuleId mux_module = module_manager.find_module(module_name); + VTR_ASSERT(true == module_manager.valid_module_id(mux_module)); + write_verilog_module_to_file(fp, module_manager, mux_module, + use_explicit_port_map || circuit_lib.dump_explicit_port_map(mux_model)); + /* Add an empty line as a splitter */ + fp << std::endl; + } else { + /* Behavioral verilog requires customized generation */ + print_verilog_cmos_mux_branch_module_behavioral(module_manager, circuit_lib, fp, mux_model, module_name, mux_graph); + } + break; + case CIRCUIT_MODEL_DESIGN_RRAM: + generate_verilog_rram_mux_branch_module(module_manager, circuit_lib, fp, mux_model, module_name, mux_graph, + circuit_lib.dump_structural_verilog(mux_model)); + break; + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid design technology of multiplexer '%s'\n", + circuit_lib.model_name(mux_model).c_str()); + exit(1); + } +} + +/******************************************************************** + * Generate the input bufferes for a multiplexer or LUT in Verilog codes + * 1. If input are required to be buffered (specified by users), + * buffers will be added to all the datapath inputs. + * 2. If input are required to NOT be buffered (specified by users), + * all the datapath inputs will be short wired to MUX inputs. + * + * For those Multiplexers or LUTs require a constant input: + * the last input of multiplexer will be wired to a constant voltage level + *******************************************************************/ +static +void generate_verilog_cmos_mux_module_input_buffers(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + std::fstream& fp, + const ModuleId& module_id, + const CircuitModelId& circuit_model, + const MuxGraph& mux_graph) { + /* Make sure we have a valid file handler*/ + VTR_ASSERT(true == valid_file_stream(fp)); + + /* Get the input ports from the mux */ + std::vector mux_input_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_INPUT, true); + /* We should have only 1 input port! */ + VTR_ASSERT(1 == mux_input_ports.size()); + + /* Get the input port from MUX module */ + ModulePortId module_input_port_id = module_manager.find_module_port(module_id, circuit_lib.port_prefix(mux_input_ports[0])); + VTR_ASSERT(ModulePortId::INVALID() != module_input_port_id); + /* Get the port from module */ + BasicPort module_input_port = module_manager.module_port(module_id, module_input_port_id); + + /* Iterate over all the inputs in the MUX graph */ + for (const auto& input_node : mux_graph.inputs()) { + /* Fetch fundamental information from MUX graph w.r.t. the input node */ + MuxInputId input_index = mux_graph.input_id(input_node); + VTR_ASSERT(MuxInputId::INVALID() != input_index); + + size_t input_node_level = mux_graph.node_level(input_node); + size_t input_node_index_at_level = mux_graph.node_index_at_level(input_node); + + /* Create the port information of the MUX input, which is the input of buffer instance */ + BasicPort instance_input_port(module_input_port.get_name(), size_t(input_index), size_t(input_index)); + + /* Create the port information of the MUX graph input, which is the output of buffer instance */ + BasicPort instance_output_port(generate_mux_node_name(input_node_level, false), input_node_index_at_level, input_node_index_at_level); + + /* For last input: + * Add a constant value to the last input, if this MUX needs a constant input + */ + if ( (MuxInputId(mux_graph.num_inputs() - 1) == mux_graph.input_id(input_node)) + && (true == circuit_lib.mux_add_const_input(circuit_model)) ) { + /* Get the constant input value */ + size_t const_value = circuit_lib.mux_const_input_value(circuit_model); + VTR_ASSERT( (0 == const_value) || (1 == const_value) ); + /* For the output of the buffer instance: + * Get the last inputs from the MUX graph and generate the node name in MUX module. + */ + print_verilog_comment(fp, std::string("---- BEGIN short-wire a multiplexing structure input to a constant value -----")); + print_verilog_wire_constant_values(fp, instance_output_port, std::vector(1, const_value)); + print_verilog_comment(fp, std::string("---- END short-wire a multiplexing structure input to a constant value -----")); + fp << std::endl; + continue; /* Finish here */ + } + + /* If the inputs are not supposed to be buffered */ + if (false == circuit_lib.is_input_buffered(circuit_model)) { + print_verilog_comment(fp, std::string("---- BEGIN short-wire a multiplexing structure input to MUX module input -----")); + + /* Short wire all the datapath inputs to the MUX inputs */ + print_verilog_wire_connection(fp, instance_output_port, instance_input_port, false); + + print_verilog_comment(fp, std::string("---- END short-wire a multiplexing structure input to MUX module input -----")); + fp << std::endl; + continue; /* Finish here */ + } + + /* Reach here, we need a buffer, create a port-to-port map and output the buffer instance */ + print_verilog_comment(fp, std::string("---- BEGIN Instanciation of an input buffer module -----")); + + /* Now we need to add intermediate buffers by instanciating the modules */ + CircuitModelId buffer_model = circuit_lib.input_buffer_model(circuit_model); + /* We must have a valid model id */ + VTR_ASSERT(CircuitModelId::INVALID() != buffer_model); + + print_verilog_buffer_instance(fp, module_manager, circuit_lib, module_id, buffer_model, instance_input_port, instance_output_port); + + print_verilog_comment(fp, std::string("---- END Instanciation of an input buffer module -----")); + fp << std::endl; + } +} + +/******************************************************************** + * Generate the output bufferes for a multiplexer or LUT in Verilog codes + * 1. If output are required to be buffered (specified by users), + * buffers will be added to all the outputs. + * 2. If output are required to NOT be buffered (specified by users), + * all the outputs will be short wired to MUX outputs. + *******************************************************************/ +static +void generate_verilog_cmos_mux_module_output_buffers(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + std::fstream& fp, + const ModuleId& module_id, + const CircuitModelId& circuit_model, + const MuxGraph& mux_graph) { + /* Make sure we have a valid file handler*/ + VTR_ASSERT(true == valid_file_stream(fp)); + + /* Get the output ports from the mux */ + std::vector mux_output_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_OUTPUT, true); + + /* Iterate over all the outputs in the MUX module */ + for (const auto& output_port : mux_output_ports) { + /* Get the output port from MUX module */ + ModulePortId module_output_port_id = module_manager.find_module_port(module_id, circuit_lib.port_prefix(output_port)); + VTR_ASSERT(ModulePortId::INVALID() != module_output_port_id); + /* Get the port from module */ + BasicPort module_output_port = module_manager.module_port(module_id, module_output_port_id); + + /* Iterate over each pin of the output port */ + for (const auto& pin : circuit_lib.pins(output_port)) { + /* Fetch fundamental information from MUX graph w.r.t. the input node */ + /* Deposite the last level of the graph, which is a default value */ + size_t output_node_level = mux_graph.num_node_levels() - 1; + /* If there is a fracturable level specified for the output, we find the exact level */ + if (size_t(-1) != circuit_lib.port_lut_frac_level(output_port)) { + output_node_level = circuit_lib.port_lut_frac_level(output_port); + } + /* Deposite a zero, which is a default value */ + size_t output_node_index_at_level = 0; + /* If there are output masks, we find the node_index */ + if (!circuit_lib.port_lut_output_mask(output_port).empty()) { + output_node_index_at_level = circuit_lib.port_lut_output_mask(output_port).at(pin); + } + /* Double check the node exists in the Mux Graph */ + VTR_ASSERT(MuxNodeId::INVALID() != mux_graph.node_id(output_node_level, output_node_index_at_level)); + + /* Create the port information of the MUX input, which is the input of buffer instance */ + BasicPort instance_input_port(generate_mux_node_name(output_node_level, false), output_node_index_at_level, output_node_index_at_level); + + /* Create the port information of the module output at the given pin range, which is the output of buffer instance */ + BasicPort instance_output_port(module_output_port.get_name(), pin, pin); + + /* If the output is not supposed to be buffered */ + if (false == circuit_lib.is_output_buffered(circuit_model)) { + print_verilog_comment(fp, std::string("---- BEGIN short-wire a multiplexing structure output to MUX module output -----")); + + /* Short wire all the datapath inputs to the MUX inputs */ + print_verilog_wire_connection(fp, instance_output_port, instance_input_port, false); + + print_verilog_comment(fp, std::string("---- END short-wire a multiplexing structure output to MUX module output -----")); + fp << std::endl; + continue; /* Finish here */ + } + + /* Reach here, we need a buffer, create a port-to-port map and output the buffer instance */ + print_verilog_comment(fp, std::string("---- BEGIN Instanciation of an output buffer module -----")); + + /* Now we need to add intermediate buffers by instanciating the modules */ + CircuitModelId buffer_model = circuit_lib.output_buffer_model(circuit_model); + /* We must have a valid model id */ + VTR_ASSERT(CircuitModelId::INVALID() != buffer_model); + + print_verilog_buffer_instance(fp, module_manager, circuit_lib, module_id, buffer_model, instance_input_port, instance_output_port); + + print_verilog_comment(fp, std::string("---- END Instanciation of an output buffer module -----")); + fp << std::endl; + } + } +} + +/******************************************************************** + * Generate the 4T1R-based internal logic + * (multiplexing structure) for a multiplexer in Verilog codes + * This function will : + * 1. build a multiplexing structure by instanciating the branch circuits + * generated before + * 2. add intermediate buffers between multiplexing stages if specified. + *******************************************************************/ +static +void generate_verilog_rram_mux_module_multiplexing_structure(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + std::fstream& fp, + const ModuleId& module_id, + const CircuitModelId& circuit_model, + const MuxGraph& mux_graph) { + /* Make sure we have a valid file handler*/ + VTR_ASSERT(true == valid_file_stream(fp)); + + /* Find the actual mux size */ + size_t mux_size = find_mux_num_datapath_inputs(circuit_lib, circuit_model, mux_graph.num_inputs()); + + /* Get the BL and WL ports from the mux */ + std::vector mux_blb_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_BLB, true); + std::vector mux_wl_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_WL, true); + /* MUX graph must have only 1 BLB and 1 WL port */ + VTR_ASSERT(1 == mux_blb_ports.size()); + VTR_ASSERT(1 == mux_wl_ports.size()); + + /* Build the location map of intermediate buffers */ + std::vector inter_buffer_location_map = build_mux_intermediate_buffer_location_map(circuit_lib, circuit_model, mux_graph.num_node_levels()); + + print_verilog_comment(fp, std::string("---- BEGIN Internal Logic of a RRAM-based MUX module -----")); + + print_verilog_comment(fp, std::string("---- BEGIN Internal wires of a RRAM-based MUX module -----")); + /* Print local wires which are the nodes in the mux graph */ + for (size_t level = 0; level < mux_graph.num_levels(); ++level) { + /* Print the internal wires located at this level */ + BasicPort internal_wire_port(generate_mux_node_name(level, false), mux_graph.num_nodes_at_level(level)); + fp << "\t" << generate_verilog_port(VERILOG_PORT_WIRE, internal_wire_port) << ";" << std::endl; + /* Identify if an intermediate buffer is needed */ + if (false == inter_buffer_location_map[level]) { + continue; + } + BasicPort internal_wire_buffered_port(generate_mux_node_name(level, true), mux_graph.num_nodes_at_level(level)); + fp << "\t" << generate_verilog_port(VERILOG_PORT_WIRE, internal_wire_buffered_port) << std::endl; + } + print_verilog_comment(fp, std::string("---- END Internal wires of a RRAM-based MUX module -----")); + fp << std::endl; + + /* Iterate over all the internal nodes and output nodes in the mux graph */ + for (const auto& node : mux_graph.non_input_nodes()) { + print_verilog_comment(fp, std::string("---- BEGIN Instanciation of a branch RRAM-based MUX module -----")); + /* Get the size of branch circuit + * Instanciate an branch circuit by the size (fan-in) of the node + */ + size_t branch_size = mux_graph.node_in_edges(node).size(); + + /* Get the node level and index in the current level */ + size_t output_node_level = mux_graph.node_level(node); + size_t output_node_index_at_level = mux_graph.node_index_at_level(node); + + /* Get the nodes which drive the root_node */ + std::vector input_nodes; + for (const auto& edge : mux_graph.node_in_edges(node)) { + /* Get the nodes drive the edge */ + for (const auto& src_node : mux_graph.edge_src_nodes(edge)) { + input_nodes.push_back(src_node); + } + } + /* Number of inputs should match the branch_input_size!!! */ + VTR_ASSERT(input_nodes.size() == branch_size); + + /* Get the mems in the branch circuits */ + std::vector mems; + for (const auto& edge : mux_graph.node_in_edges(node)) { + /* Get the mem control the edge */ + MuxMemId mem = mux_graph.find_edge_mem(edge); + /* Add the mem if it is not in the list */ + if (mems.end() == std::find(mems.begin(), mems.end(), mem)) { + mems.push_back(mem); + } + } + + /* Instanciate the branch module which is a tgate-based module + */ + std::string branch_module_name= generate_mux_branch_subckt_name(circuit_lib, circuit_model, mux_size, branch_size, VERILOG_MUX_BASIS_POSTFIX); + /* Get the moduleId for the submodule */ + ModuleId branch_module_id = module_manager.find_module(branch_module_name); + /* We must have one */ + VTR_ASSERT(ModuleId::INVALID() != branch_module_id); + + /* Create a port-to-port map */ + std::map port2port_name_map; + /* TODO: the branch module name should NOT be hard-coded. Use the port lib_name given by users! */ + + /* All the input node names organized in bus */ + std::vector branch_node_input_ports; + for (const auto& input_node : input_nodes) { + /* Generate the port info of each input node */ + size_t input_node_level = mux_graph.node_level(input_node); + size_t input_node_index_at_level = mux_graph.node_index_at_level(input_node); + BasicPort branch_node_input_port(generate_mux_node_name(input_node_level, inter_buffer_location_map[input_node_level]), input_node_index_at_level, input_node_index_at_level); + branch_node_input_ports.push_back(branch_node_input_port); + } + + /* Create the port info for the input */ + /* TODO: the naming could be more flexible? */ + BasicPort instance_input_port = generate_verilog_bus_port(branch_node_input_ports, std::string(generate_mux_node_name(output_node_level, false) + "_in")); + /* If we have more than 1 port in the combined instance ports , + * output a local wire */ + if (1 < combine_verilog_ports(branch_node_input_ports).size()) { + /* Print a local wire for the merged ports */ + fp << "\t" << generate_verilog_local_wire(instance_input_port, branch_node_input_ports) << std::endl; + } else { + /* Safety check */ + VTR_ASSERT(1 == combine_verilog_ports(branch_node_input_ports).size()); + } + + /* Link nodes to input ports for the branch module */ + ModulePortId module_input_port_id = module_manager.find_module_port(branch_module_id, "in"); + VTR_ASSERT(ModulePortId::INVALID() != module_input_port_id); + /* Get the port from module */ + BasicPort module_input_port = module_manager.module_port(branch_module_id, module_input_port_id); + port2port_name_map[module_input_port.get_name()] = instance_input_port; + + /* Link nodes to output ports for the branch module */ + BasicPort instance_output_port(generate_mux_node_name(output_node_level, false), output_node_index_at_level, output_node_index_at_level); + ModulePortId module_output_port_id = module_manager.find_module_port(branch_module_id, "out"); + VTR_ASSERT(ModulePortId::INVALID() != module_output_port_id); + /* Get the port from module */ + BasicPort module_output_port = module_manager.module_port(branch_module_id, module_output_port_id); + port2port_name_map[module_output_port.get_name()] = instance_output_port; + + /* All the mem node names organized in bus + * RRAM-based MUX uses BLB and WL to control memories + */ + std::vector branch_node_blb_ports; + for (const auto& mem : mems) { + /* Generate the port info of each mem node: + */ + BasicPort branch_node_blb_port(circuit_lib.port_prefix(mux_blb_ports[0]), size_t(mem), size_t(mem)); + branch_node_blb_ports.push_back(branch_node_blb_port); + } + /* Every stage, we have an additonal BLB and WL in controlling purpose + * The additional BLB is arranged at the tail of BLB port + * For example: + * The total port width is BLB[0 ... + - 1] + * The regular BLB used by branches are BLB[0 .. - 1] + * The additional BLB used by branches are BLB[ .. + - 1] + * + * output_node_level is always larger than the mem_level by 1 + */ + branch_node_blb_ports.push_back(BasicPort(circuit_lib.port_prefix(mux_blb_ports[0]), + mux_graph.num_memory_bits() + output_node_level - 1, + mux_graph.num_memory_bits() + output_node_level - 1) + ); + + /* Create the port info for the input */ + /* TODO: the naming could be more flexible? */ + BasicPort instance_blb_port = generate_verilog_bus_port(branch_node_blb_ports, std::string(generate_mux_node_name(output_node_level, false) + "_blb")); + /* If we have more than 1 port in the combined instance ports , + * output a local wire */ + if (1 < combine_verilog_ports(branch_node_blb_ports).size()) { + /* Print a local wire for the merged ports */ + fp << "\t" << generate_verilog_local_wire(instance_blb_port, branch_node_blb_ports) << std::endl; + } else { + /* Safety check */ + VTR_ASSERT(1 == combine_verilog_ports(branch_node_blb_ports).size()); + } + + /* Link nodes to BLB ports for the branch module */ + ModulePortId module_blb_port_id = module_manager.find_module_port(branch_module_id, circuit_lib.port_prefix(mux_blb_ports[0])); + VTR_ASSERT(ModulePortId::INVALID() != module_blb_port_id); + /* Get the port from module */ + BasicPort module_blb_port = module_manager.module_port(branch_module_id, module_blb_port_id); + port2port_name_map[module_blb_port.get_name()] = instance_blb_port; + + std::vector branch_node_wl_ports; + for (const auto& mem : mems) { + /* Generate the port info of each mem node: + */ + BasicPort branch_node_blb_port(circuit_lib.port_prefix(mux_wl_ports[0]), size_t(mem), size_t(mem)); + branch_node_wl_ports.push_back(branch_node_blb_port); + } + /* Every stage, we have an additonal BLB and WL in controlling purpose + * The additional BLB is arranged at the tail of BLB port + * For example: + * The total port width is WL[0 ... + - 1] + * The regular BLB used by branches are WL[0 .. - 1] + * The additional BLB used by branches are WL[ .. + - 1] + * + * output_node_level is always larger than the mem_level by 1 + */ + branch_node_wl_ports.push_back(BasicPort(circuit_lib.port_prefix(mux_wl_ports[0]), + mux_graph.num_memory_bits() + output_node_level - 1, + mux_graph.num_memory_bits() + output_node_level - 1) + ); + + /* Create the port info for the WL */ + /* TODO: the naming could be more flexible? */ + BasicPort instance_wl_port = generate_verilog_bus_port(branch_node_wl_ports, std::string(generate_mux_node_name(output_node_level, false) + "_wl")); + /* If we have more than 1 port in the combined instance ports , + * output a local wire */ + if (1 < combine_verilog_ports(branch_node_wl_ports).size()) { + /* Print a local wire for the merged ports */ + fp << "\t" << generate_verilog_local_wire(instance_wl_port, branch_node_wl_ports) << std::endl; + } else { + /* Safety check */ + VTR_ASSERT(1 == combine_verilog_ports(branch_node_wl_ports).size()); + } + + /* Link nodes to BLB ports for the branch module */ + ModulePortId module_wl_port_id = module_manager.find_module_port(branch_module_id, circuit_lib.port_prefix(mux_wl_ports[0])); + VTR_ASSERT(ModulePortId::INVALID() != module_wl_port_id); + /* Get the port from module */ + BasicPort module_wl_port = module_manager.module_port(branch_module_id, module_wl_port_id); + port2port_name_map[module_wl_port.get_name()] = instance_wl_port; + + /* Output an instance of the module */ + print_verilog_module_instance(fp, module_manager, module_id, branch_module_id, port2port_name_map, circuit_lib.dump_explicit_port_map(circuit_model)); + /* IMPORTANT: this update MUST be called after the instance outputting!!!! + * update the module manager with the relationship between the parent and child modules + */ + module_manager.add_child_module(module_id, branch_module_id); + + print_verilog_comment(fp, std::string("---- END Instanciation of a branch RRAM-based MUX module -----")); + fp << std::endl; + + if (false == inter_buffer_location_map[output_node_level]) { + continue; /* No need for intermediate buffers */ + } + + print_verilog_comment(fp, std::string("---- BEGIN Instanciation of an intermediate buffer modules -----")); + + /* Now we need to add intermediate buffers by instanciating the modules */ + CircuitModelId buffer_model = circuit_lib.lut_intermediate_buffer_model(circuit_model); + /* We must have a valid model id */ + VTR_ASSERT(CircuitModelId::INVALID() != buffer_model); + + BasicPort buffer_instance_input_port(generate_mux_node_name(output_node_level, false), output_node_index_at_level, output_node_index_at_level); + BasicPort buffer_instance_output_port(generate_mux_node_name(output_node_level, true), output_node_index_at_level, output_node_index_at_level); + + print_verilog_buffer_instance(fp, module_manager, circuit_lib, module_id, buffer_model, buffer_instance_input_port, buffer_instance_output_port); + + print_verilog_comment(fp, std::string("---- END Instanciation of an intermediate buffer module -----")); + fp << std::endl; + } + + print_verilog_comment(fp, std::string("---- END Internal Logic of a RRAM-based MUX module -----")); + fp << std::endl; +} + +/********************************************************************* + * Generate Verilog codes modeling a RRAM-based multiplexer with the given size + * The Verilog module will consist of three parts: + * 1. instances of the branch circuits of multiplexers which are generated before + * This builds up the 4T1R-based multiplexing structure + * + * BLB WL + * | | ... + * v v + * +--------+ + * in[0]-->| | BLB WL + * ...| Branch |-----+ | | + * in -->| 0 | | v v + * [N-1] +--------+ | +--------+ + * ... -->| | + * BLBs WLs ...| Branch | + * | | ... -->| X | + * v v +--------+ + * +--------+ | + * -->| | | + * ...| Branch |----+ + * -->| i | + * +--------+ + * + * 2. Input buffers/inverters + * 3. Output buffers/inverters + *********************************************************************/ +static +void generate_verilog_rram_mux_module(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + std::fstream& fp, + const CircuitModelId& circuit_model, + const std::string& module_name, + const MuxGraph& mux_graph) { + /* Error out for the conditions where we are not yet supported! */ + if (CIRCUIT_MODEL_LUT == circuit_lib.model_type(circuit_model)) { + /* RRAM LUT is not supported now... */ + VTR_LOGF_ERROR(__FILE__, __LINE__, + "RRAM-based LUT is not supported for circuit model '%s'!\n", + circuit_lib.model_name(circuit_model).c_str()); + exit(1); + } + + /* Get the global ports required by MUX (and any submodules) */ + std::vector mux_global_ports = circuit_lib.model_global_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_INPUT, true, true); + /* Get the input ports from the mux */ + std::vector mux_input_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_INPUT, true); + /* Get the output ports from the mux */ + std::vector mux_output_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_OUTPUT, true); + /* Get the BL and WL ports from the mux */ + std::vector mux_blb_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_BLB, true); + std::vector mux_wl_ports = circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_WL, true); + + /* Make sure we have a valid file handler*/ + VTR_ASSERT(true == valid_file_stream(fp)); + + /* Generate the Verilog netlist according to the mux_graph */ + /* Find out the number of data-path inputs */ + size_t num_inputs = find_mux_num_datapath_inputs(circuit_lib, circuit_model, mux_graph.num_inputs()); + /* Find out the number of outputs */ + size_t num_outputs = mux_graph.num_outputs(); + /* Find out the number of memory bits */ + size_t num_mems = mux_graph.num_memory_bits(); + + /* Check codes to ensure the port of Verilog netlists will match */ + /* MUX graph must have only 1 input and 1 BLB and 1 WL port */ + VTR_ASSERT(1 == mux_input_ports.size()); + VTR_ASSERT(1 == mux_blb_ports.size()); + VTR_ASSERT(1 == mux_wl_ports.size()); + + /* Create a Verilog Module based on the circuit model, and add to module manager */ + ModuleId module_id = module_manager.add_module(module_name); + VTR_ASSERT(ModuleId::INVALID() != module_id); + /* Add module ports */ + /* Add each global port */ + for (const auto& port : mux_global_ports) { + /* Configure each global port */ + BasicPort global_port(circuit_lib.port_prefix(port), circuit_lib.port_size(port)); + module_manager.add_port(module_id, global_port, ModuleManager::MODULE_GLOBAL_PORT); + } + /* Add each input port */ + size_t input_port_cnt = 0; + for (const auto& port : mux_input_ports) { + BasicPort input_port(circuit_lib.port_prefix(port), num_inputs); + module_manager.add_port(module_id, input_port, ModuleManager::MODULE_INPUT_PORT); + /* Update counter */ + input_port_cnt++; + } + /* Double check: We should have only 1 input port generated here! */ + VTR_ASSERT(1 == input_port_cnt); + + for (const auto& port : mux_output_ports) { + BasicPort output_port(circuit_lib.port_prefix(port), num_outputs); + if (CIRCUIT_MODEL_LUT == circuit_lib.model_type(circuit_model)) { + output_port.set_width(circuit_lib.port_size(port)); + } + module_manager.add_port(module_id, output_port, ModuleManager::MODULE_OUTPUT_PORT); + } + + /* BLB port */ + for (const auto& port : mux_blb_ports) { + /* IMPORTANT: RRAM-based MUX has an additional BLB pin per level + * So, the actual port width of BLB should be added by the number of levels of the MUX graph + */ + BasicPort blb_port(circuit_lib.port_prefix(port), num_mems + mux_graph.num_levels()); + module_manager.add_port(module_id, blb_port, ModuleManager::MODULE_INPUT_PORT); + } + + /* WL port */ + for (const auto& port : mux_wl_ports) { + /* IMPORTANT: RRAM-based MUX has an additional WL pin per level + * So, the actual port width of WL should be added by the number of levels of the MUX graph + */ + BasicPort wl_port(circuit_lib.port_prefix(port), num_mems + mux_graph.num_levels()); + module_manager.add_port(module_id, wl_port, ModuleManager::MODULE_INPUT_PORT); + } + + /* dump module definition + ports */ + print_verilog_module_declaration(fp, module_manager, module_id); + + /* TODO: Print the internal logic in Verilog codes */ + generate_verilog_rram_mux_module_multiplexing_structure(module_manager, circuit_lib, fp, module_id, circuit_model, mux_graph); + + /* Print the input and output buffers in Verilog codes */ + /* TODO, we should rename the follow functions to a generic name? Since they are applicable to both MUXes */ + generate_verilog_cmos_mux_module_input_buffers(module_manager, circuit_lib, fp, module_id, circuit_model, mux_graph); + generate_verilog_cmos_mux_module_output_buffers(module_manager, circuit_lib, fp, module_id, circuit_model, mux_graph); + + /* Put an end to the Verilog module */ + print_verilog_module_end(fp, module_name); +} + + +/*********************************************** + * Generate Verilog codes modeling a multiplexer + * with the given graph-level description + **********************************************/ +static +void generate_verilog_mux_module(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + std::fstream& fp, + const CircuitModelId& mux_model, + const MuxGraph& mux_graph, + const bool& use_explicit_port_map) { + std::string module_name = generate_mux_subckt_name(circuit_lib, mux_model, + find_mux_num_datapath_inputs(circuit_lib, mux_model, mux_graph.num_inputs()), + std::string("")); + + /* Multiplexers built with different technology is in different organization */ + switch (circuit_lib.design_tech_type(mux_model)) { + case CIRCUIT_MODEL_DESIGN_CMOS: { + /* Use Verilog writer to print the module to file */ + ModuleId mux_module = module_manager.find_module(module_name); + VTR_ASSERT(true == module_manager.valid_module_id(mux_module)); + write_verilog_module_to_file(fp, module_manager, mux_module, + ( use_explicit_port_map + || circuit_lib.dump_explicit_port_map(mux_model) + || circuit_lib.dump_explicit_port_map(circuit_lib.pass_gate_logic_model(mux_model)) ) + ); + /* Add an empty line as a splitter */ + fp << std::endl; + break; + } + case CIRCUIT_MODEL_DESIGN_RRAM: + /* TODO: RRAM-based Multiplexer Verilog module generation */ + generate_verilog_rram_mux_module(module_manager, circuit_lib, fp, mux_model, module_name, mux_graph); + break; + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid design technology of multiplexer '%s'\n", + circuit_lib.model_name(mux_model).c_str()); + exit(1); + } +} + + +/*********************************************** + * Generate Verilog modules for all the unique + * multiplexers in the FPGA device + **********************************************/ +void print_verilog_submodule_muxes(ModuleManager& module_manager, + std::vector& netlist_names, + const MuxLibrary& mux_lib, + const CircuitLibrary& circuit_lib, + const std::string& verilog_dir, + const std::string& submodule_dir, + const bool& use_explicit_port_map) { + + std::string verilog_fname(submodule_dir + std::string(MUXES_VERILOG_FILE_NAME)); + + /* Create the file stream */ + std::fstream fp; + fp.open(verilog_fname, std::fstream::out | std::fstream::trunc); + + check_file_stream(verilog_fname.c_str(), fp); + + /* Print out debugging information for if the file is not opened/created properly */ + VTR_LOG("Writing Verilog netlist for Multiplexers '%s' ...\n", + verilog_fname.c_str()); + + print_verilog_file_header(fp, "Multiplexers"); + + print_verilog_include_defines_preproc_file(fp, verilog_dir); + + /* Generate basis sub-circuit for unique branches shared by the multiplexers */ + for (auto mux : mux_lib.muxes()) { + const MuxGraph& mux_graph = mux_lib.mux_graph(mux); + CircuitModelId mux_circuit_model = mux_lib.mux_circuit_model(mux); + /* Create a mux graph for the branch circuit */ + std::vector branch_mux_graphs = mux_graph.build_mux_branch_graphs(); + /* Create branch circuits, which are N:1 one-level or 2:1 tree-like MUXes */ + for (auto branch_mux_graph : branch_mux_graphs) { + generate_verilog_mux_branch_module(module_manager, circuit_lib, fp, mux_circuit_model, + find_mux_num_datapath_inputs(circuit_lib, mux_circuit_model, mux_graph.num_inputs()), + branch_mux_graph, use_explicit_port_map); + } + } + + /* Generate unique Verilog modules for the multiplexers */ + for (auto mux : mux_lib.muxes()) { + const MuxGraph& mux_graph = mux_lib.mux_graph(mux); + CircuitModelId mux_circuit_model = mux_lib.mux_circuit_model(mux); + /* Create MUX circuits */ + generate_verilog_mux_module(module_manager, circuit_lib, fp, mux_circuit_model, mux_graph, use_explicit_port_map); + } + + /* Close the file stream */ + fp.close(); + + /* Add fname to the netlist name list */ + netlist_names.push_back(verilog_fname); + + VTR_LOG("Done\n"); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_mux.h b/openfpga/src/fpga_verilog/verilog_mux.h new file mode 100644 index 000000000..16e6c2f2a --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_mux.h @@ -0,0 +1,32 @@ +#ifndef VERILOG_MUX_H +#define VERILOG_MUX_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include + +#include "circuit_library.h" +#include "mux_graph.h" +#include "mux_library.h" +#include "module_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_verilog_submodule_muxes(ModuleManager& module_manager, + std::vector& netlist_names, + const MuxLibrary& mux_lib, + const CircuitLibrary& circuit_lib, + const std::string& verilog_dir, + const std::string& submodule_dir, + const bool& use_explicit_port_map); + +} /* end namespace openfpga */ + +#endif From 105ccabecc4ae7ae0d1b5a9391653afcae6593e2 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 16 Feb 2020 12:41:43 -0700 Subject: [PATCH 155/645] adapt memroy writer for verilog --- openfpga/src/fpga_verilog/verilog_constants.h | 1 + openfpga/src/fpga_verilog/verilog_memory.cpp | 195 ++++++++++++++++++ openfpga/src/fpga_verilog/verilog_memory.h | 31 +++ 3 files changed, 227 insertions(+) create mode 100644 openfpga/src/fpga_verilog/verilog_memory.cpp create mode 100644 openfpga/src/fpga_verilog/verilog_memory.h diff --git a/openfpga/src/fpga_verilog/verilog_constants.h b/openfpga/src/fpga_verilog/verilog_constants.h index 49a73bf6d..91f87c475 100644 --- a/openfpga/src/fpga_verilog/verilog_constants.h +++ b/openfpga/src/fpga_verilog/verilog_constants.h @@ -42,5 +42,6 @@ constexpr char* CONFIG_PERIPHERAL_VERILOG_FILE_NAME = "config_peripherals.v"; constexpr char* USER_DEFINED_TEMPLATE_VERILOG_FILE_NAME = "user_defined_templates.v"; constexpr char* VERILOG_MUX_BASIS_POSTFIX = "_basis"; +constexpr char* VERILOG_MEM_POSTFIX = "_mem"; #endif diff --git a/openfpga/src/fpga_verilog/verilog_memory.cpp b/openfpga/src/fpga_verilog/verilog_memory.cpp new file mode 100644 index 000000000..743647d44 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_memory.cpp @@ -0,0 +1,195 @@ +/********************************************************************* + * This file includes functions to generate Verilog submodules for + * the memories that are affiliated to multiplexers and other programmable + * circuit models, such as IOPADs, LUTs, etc. + ********************************************************************/ +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" + +/* Headers from openfpgautil library */ +#include "openfpga_digest.h" + +#include "mux_graph.h" +#include "module_manager.h" +#include "circuit_library_utils.h" +#include "mux_utils.h" + +#include "openfpga_naming.h" + +#include "verilog_constants.h" +#include "verilog_writer_utils.h" +#include "verilog_module_writer.h" +#include "verilog_memory.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/********************************************************************* + * Generate Verilog modules for the memories that are used + * by multiplexers + * + * +----------------+ + * mem_in --->| Memory Module |---> mem_out + * +----------------+ + * | | ... | | + * v v v v SRAM ports of multiplexer + * +---------------------+ + * in--->| Multiplexer Module |---> out + * +---------------------+ + ********************************************************************/ +static +void print_verilog_mux_memory_module(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + std::fstream& fp, + const CircuitModelId& mux_model, + const MuxGraph& mux_graph, + const bool& use_explicit_port_map) { + /* Multiplexers built with different technology is in different organization */ + switch (circuit_lib.design_tech_type(mux_model)) { + case CIRCUIT_MODEL_DESIGN_CMOS: { + /* Generate module name */ + std::string module_name = generate_mux_subckt_name(circuit_lib, mux_model, + find_mux_num_datapath_inputs(circuit_lib, mux_model, mux_graph.num_inputs()), + std::string(VERILOG_MEM_POSTFIX)); + ModuleId mem_module = module_manager.find_module(module_name); + VTR_ASSERT(true == module_manager.valid_module_id(mem_module)); + /* Write the module content in Verilog format */ + write_verilog_module_to_file(fp, module_manager, mem_module, + use_explicit_port_map || circuit_lib.dump_explicit_port_map(mux_model)); + + /* Add an empty line as a splitter */ + fp << std::endl; + break; + } + case CIRCUIT_MODEL_DESIGN_RRAM: + /* We do not need a memory submodule for RRAM MUX, + * RRAM are embedded in the datapath + * TODO: generate local encoders for RRAM-based multiplexers here!!! + */ + break; + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid design technology of multiplexer '%s'\n", + circuit_lib.model_name(mux_model).c_str()); + exit(1); + } +} + + +/********************************************************************* + * Generate Verilog modules for + * the memories that are affiliated to multiplexers and other programmable + * circuit models, such as IOPADs, LUTs, etc. + * + * We keep the memory modules separated from the multiplexers and other + * programmable circuit models, for the sake of supporting + * various configuration schemes. + * By following such organiztion, the Verilog modules of the circuit models + * implements the functionality (circuit logic) only, while the memory Verilog + * modules implements the memory circuits as well as configuration protocols. + * For example, the local decoders of multiplexers are implemented in the + * memory modules. + * Take another example, the memory circuit can implement the scan-chain or + * memory-bank organization for the memories. + ********************************************************************/ +void print_verilog_submodule_memories(ModuleManager& module_manager, + std::vector& netlist_names, + const MuxLibrary& mux_lib, + const CircuitLibrary& circuit_lib, + const std::string& verilog_dir, + const std::string& submodule_dir, + const bool& use_explicit_port_map) { + /* Plug in with the mux subckt */ + std::string verilog_fname(submodule_dir + std::string(MEMORIES_VERILOG_FILE_NAME)); + + /* Create the file stream */ + std::fstream fp; + fp.open(verilog_fname, std::fstream::out | std::fstream::trunc); + + check_file_stream(verilog_fname.c_str(), fp); + + /* Print out debugging information for if the file is not opened/created properly */ + VTR_LOG("Writing Verilog netlist for memories '%s' ...", + verilog_fname.c_str()); + + print_verilog_file_header(fp, "Memories used in FPGA"); + + print_verilog_include_defines_preproc_file(fp, verilog_dir); + + /* Create the memory circuits for the multiplexer */ + for (auto mux : mux_lib.muxes()) { + const MuxGraph& mux_graph = mux_lib.mux_graph(mux); + CircuitModelId mux_model = mux_lib.mux_circuit_model(mux); + /* Bypass the non-MUX circuit models (i.e., LUTs). + * They should be handled in a different way + * Memory circuits of LUT includes both regular and mode-select ports + */ + if (CIRCUIT_MODEL_MUX != circuit_lib.model_type(mux_model)) { + continue; + } + /* Create a Verilog module for the memories used by the multiplexer */ + print_verilog_mux_memory_module(module_manager, circuit_lib, fp, mux_model, mux_graph, use_explicit_port_map); + } + + /* Create the memory circuits for non-MUX circuit models. + * In this case, the memory modules are designed to interface + * the mode-select ports + */ + for (const auto& model : circuit_lib.models()) { + /* Bypass MUXes, they have already been considered */ + if (CIRCUIT_MODEL_MUX == circuit_lib.model_type(model)) { + continue; + } + /* Bypass those modules without any SRAM ports */ + std::vector sram_ports = circuit_lib.model_ports_by_type(model, CIRCUIT_MODEL_PORT_SRAM, true); + if (0 == sram_ports.size()) { + continue; + } + /* Find the name of memory module */ + /* Get the total number of SRAMs */ + size_t num_mems = 0; + for (const auto& port : sram_ports) { + num_mems += circuit_lib.port_size(port); + } + /* Get the circuit model for the memory circuit used by the multiplexer */ + std::vector sram_models; + for (const auto& port : sram_ports) { + CircuitModelId sram_model = circuit_lib.port_tri_state_model(port); + VTR_ASSERT(CircuitModelId::INVALID() != sram_model); + /* Found in the vector of sram_models, do not update and go to the next */ + if (sram_models.end() != std::find(sram_models.begin(), sram_models.end(), sram_model)) { + continue; + } + /* sram_model not found in the vector, update the sram_models */ + sram_models.push_back(sram_model); + } + /* Should have only 1 SRAM model */ + VTR_ASSERT( 1 == sram_models.size() ); + + /* Create the module name for the memory block */ + std::string module_name = generate_memory_module_name(circuit_lib, model, sram_models[0], std::string(VERILOG_MEM_POSTFIX)); + + ModuleId mem_module = module_manager.find_module(module_name); + VTR_ASSERT(true == module_manager.valid_module_id(mem_module)); + /* Write the module content in Verilog format */ + write_verilog_module_to_file(fp, module_manager, mem_module, + use_explicit_port_map || circuit_lib.dump_explicit_port_map(model)); + + /* Add an empty line as a splitter */ + fp << std::endl; + } + + /* Close the file stream */ + fp.close(); + + /* Add fname to the netlist name list */ + netlist_names.push_back(verilog_fname); + + VTR_LOG("Done\n"); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_memory.h b/openfpga/src/fpga_verilog/verilog_memory.h new file mode 100644 index 000000000..774e6feb9 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_memory.h @@ -0,0 +1,31 @@ +#ifndef VERILOG_MEMORY_H +#define VERILOG_MEMORY_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include + +#include "circuit_library.h" +#include "mux_graph.h" +#include "mux_library.h" +#include "module_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_verilog_submodule_memories(ModuleManager& module_manager, + std::vector& netlist_names, + const MuxLibrary& mux_lib, + const CircuitLibrary& circuit_lib, + const std::string& verilog_dir, + const std::string& submodule_dir, + const bool& use_explicit_port_map); + +} /* end namespace openfpga */ + +#endif From 5cc68b07301860d559c7a090afdb4cf43ed9a7b2 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 16 Feb 2020 12:45:58 -0700 Subject: [PATCH 156/645] adapt LUT Verilog writer --- openfpga/src/fpga_verilog/verilog_lut.cpp | 78 +++++++++++++++++++++++ openfpga/src/fpga_verilog/verilog_lut.h | 29 +++++++++ 2 files changed, 107 insertions(+) create mode 100644 openfpga/src/fpga_verilog/verilog_lut.cpp create mode 100644 openfpga/src/fpga_verilog/verilog_lut.h diff --git a/openfpga/src/fpga_verilog/verilog_lut.cpp b/openfpga/src/fpga_verilog/verilog_lut.cpp new file mode 100644 index 000000000..900ef3073 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_lut.cpp @@ -0,0 +1,78 @@ +/******************************************************************** + * This file includes functions to generate Verilog submodules for LUTs + ********************************************************************/ +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" + +/* Headers from openfpgautil library */ +#include "openfpga_digest.h" + +#include "mux_graph.h" +#include "module_manager.h" +#include "mux_utils.h" + +#include "openfpga_naming.h" + +#include "verilog_constants.h" +#include "verilog_writer_utils.h" +#include "verilog_module_writer.h" +#include "verilog_lut.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Print Verilog modules for the Look-Up Tables (LUTs) + * in the circuit library + ********************************************************************/ +void print_verilog_submodule_luts(ModuleManager& module_manager, + std::vector& netlist_names, + const CircuitLibrary& circuit_lib, + const std::string& verilog_dir, + const std::string& submodule_dir, + const bool& use_explicit_port_map) { + std::string verilog_fname = submodule_dir + std::string(LUTS_VERILOG_FILE_NAME); + + std::fstream fp; + + /* Create the file stream */ + fp.open(verilog_fname, std::fstream::out | std::fstream::trunc); + /* Check if the file stream if valid or not */ + check_file_stream(verilog_fname.c_str(), fp); + + /* Create file */ + VTR_LOG("Writing Verilog netlist for LUTs '%s'...", + verilog_fname.c_str()); + + print_verilog_file_header(fp, "Look-Up Tables"); + + print_verilog_include_defines_preproc_file(fp, verilog_dir); + + /* Search for each LUT circuit model */ + for (const auto& lut_model : circuit_lib.models()) { + /* Bypass user-defined and non-LUT modules */ + if ( (!circuit_lib.model_verilog_netlist(lut_model).empty()) + || (CIRCUIT_MODEL_LUT != circuit_lib.model_type(lut_model)) ) { + continue; + } + /* Find the module id */ + ModuleId lut_module = module_manager.find_module(circuit_lib.model_name(lut_model)); + VTR_ASSERT(true == module_manager.valid_module_id(lut_module)); + write_verilog_module_to_file(fp, module_manager, lut_module, + use_explicit_port_map || circuit_lib.dump_explicit_port_map(lut_model)); + } + + /* Close the file handler */ + fp.close(); + + /* Add fname to the netlist name list */ + netlist_names.push_back(verilog_fname); + + VTR_LOG("Done\n"); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_lut.h b/openfpga/src/fpga_verilog/verilog_lut.h new file mode 100644 index 000000000..f5d1345dc --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_lut.h @@ -0,0 +1,29 @@ +#ifndef VERILOG_LUT_H +#define VERILOG_LUT_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include + +#include "circuit_library.h" +#include "module_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_verilog_submodule_luts(ModuleManager& module_manager, + std::vector& netlist_names, + const CircuitLibrary& circuit_lib, + const std::string& verilog_dir, + const std::string& submodule_dir, + const bool& use_explicit_port_map); + +} /* end namespace openfpga */ + +#endif From 99c3712b6f649da9fea727b7df3ca01765aec98a Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 16 Feb 2020 12:59:37 -0700 Subject: [PATCH 157/645] adapt Verilog wire module writer --- openfpga/src/fpga_verilog/verilog_wire.cpp | 136 +++++++++++++++++++++ openfpga/src/fpga_verilog/verilog_wire.h | 28 +++++ 2 files changed, 164 insertions(+) create mode 100644 openfpga/src/fpga_verilog/verilog_wire.cpp create mode 100644 openfpga/src/fpga_verilog/verilog_wire.h diff --git a/openfpga/src/fpga_verilog/verilog_wire.cpp b/openfpga/src/fpga_verilog/verilog_wire.cpp new file mode 100644 index 000000000..2ac9e7ffa --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_wire.cpp @@ -0,0 +1,136 @@ +/*********************************************** + * This file includes functions to generate + * Verilog submodules for wires. + **********************************************/ +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" + +/* Headers from openfpgautil library */ +#include "openfpga_digest.h" + +#include "module_manager.h" +#include "module_manager_utils.h" + +#include "openfpga_naming.h" + +#include "verilog_constants.h" +#include "verilog_submodule_utils.h" +#include "verilog_writer_utils.h" +#include "verilog_wire.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Print a Verilog module of a regular wire segment + * Regular wire, which is 1-input and 1-output + * This type of wires are used in the local routing architecture + * +------+ + * input --->| wire |---> output + * +------+ + * + *******************************************************************/ +static +void print_verilog_wire_module(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + std::fstream& fp, + const CircuitModelId& wire_model) { + /* Ensure a valid file handler*/ + VTR_ASSERT(true == valid_file_stream(fp)); + + /* Find the input port, output port*/ + std::vector input_ports = circuit_lib.model_ports_by_type(wire_model, CIRCUIT_MODEL_PORT_INPUT, true); + std::vector output_ports = circuit_lib.model_ports_by_type(wire_model, CIRCUIT_MODEL_PORT_OUTPUT, true); + std::vector global_ports = circuit_lib.model_global_ports_by_type(wire_model, CIRCUIT_MODEL_PORT_INPUT, true, true); + + /* Makre sure the port size is what we want */ + VTR_ASSERT (1 == input_ports.size()); + VTR_ASSERT (1 == output_ports.size()); + VTR_ASSERT (1 == circuit_lib.port_size(input_ports[0])); + VTR_ASSERT (1 == circuit_lib.port_size(output_ports[0])); + + /* Create a Verilog Module based on the circuit model, and add to module manager */ + ModuleId wire_module = module_manager.find_module(circuit_lib.model_name(wire_model)); + VTR_ASSERT(true == module_manager.valid_module_id(wire_module)); + + /* dump module definition + ports */ + print_verilog_module_declaration(fp, module_manager, wire_module); + /* Finish dumping ports */ + + /* Print the internal logic of Verilog module */ + /* Find the input port of the module */ + ModulePortId module_input_port_id = module_manager.find_module_port(wire_module, circuit_lib.port_lib_name(input_ports[0])); + VTR_ASSERT(ModulePortId::INVALID() != module_input_port_id); + BasicPort module_input_port = module_manager.module_port(wire_module, module_input_port_id); + + /* Find the output port of the module */ + ModulePortId module_output_port_id = module_manager.find_module_port(wire_module, circuit_lib.port_lib_name(output_ports[0])); + VTR_ASSERT(ModulePortId::INVALID() != module_output_port_id); + BasicPort module_output_port = module_manager.module_port(wire_module, module_output_port_id); + + /* Print wire declaration for the inputs and outputs */ + fp << generate_verilog_port(VERILOG_PORT_WIRE, module_input_port) << ";" << std::endl; + fp << generate_verilog_port(VERILOG_PORT_WIRE, module_output_port) << ";" << std::endl; + + /* Direct shortcut */ + print_verilog_wire_connection(fp, module_output_port, module_input_port, false); + + /* Print timing info */ + print_verilog_submodule_timing(fp, circuit_lib, wire_model); + + /* Put an end to the Verilog module */ + print_verilog_module_end(fp, circuit_lib.model_name(wire_model)); + + /* Add an empty line as a splitter */ + fp << std::endl; +} + +/******************************************************************** + * Top-level function to print wire modules + *******************************************************************/ +void print_verilog_submodule_wires(ModuleManager& module_manager, + std::vector& netlist_names, + const CircuitLibrary& circuit_lib, + const std::string& verilog_dir, + const std::string& submodule_dir) { + std::string verilog_fname(submodule_dir + std::string(WIRES_VERILOG_FILE_NAME)); + + /* Create the file stream */ + std::fstream fp; + fp.open(verilog_fname, std::fstream::out | std::fstream::trunc); + + check_file_stream(verilog_fname.c_str(), fp); + + /* Print out debugging information for if the file is not opened/created properly */ + VTR_LOG("Writing Verilog netlist for wires '%s'...", + verilog_fname.c_str()); + + print_verilog_file_header(fp, "Wires"); + + print_verilog_include_defines_preproc_file(fp, verilog_dir); + + /* Print Verilog models for regular wires*/ + print_verilog_comment(fp, std::string("----- BEGIN Verilog modules for regular wires -----")); + for (const auto& model : circuit_lib.models_by_type(CIRCUIT_MODEL_WIRE)) { + /* Bypass user-defined circuit models */ + if (!circuit_lib.model_verilog_netlist(model).empty()) { + continue; + } + print_verilog_wire_module(module_manager, circuit_lib, fp, model); + } + print_verilog_comment(fp, std::string("----- END Verilog modules for regular wires -----")); + + /* Close the file stream */ + fp.close(); + + /* Add fname to the netlist name list */ + netlist_names.push_back(verilog_fname); + + VTR_LOG("Done\n"); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_wire.h b/openfpga/src/fpga_verilog/verilog_wire.h new file mode 100644 index 000000000..7d22ae4c8 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_wire.h @@ -0,0 +1,28 @@ +#ifndef VERILOG_WIRE_H +#define VERILOG_WIRE_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include + +#include "circuit_library.h" +#include "module_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_verilog_submodule_wires(ModuleManager& module_manager, + std::vector& netlist_names, + const CircuitLibrary& circuit_lib, + const std::string& verilog_dir, + const std::string& submodule_dir); + +} /* end namespace openfpga */ + +#endif From c6c3ef71f39654af33522288873dc4bcb440a92f Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 16 Feb 2020 13:35:18 -0700 Subject: [PATCH 158/645] adapt all the Verilog submodule writers and bring it onlien --- .../libopenfpgautil/src/openfpga_digest.cpp | 2 +- openfpga/src/base/openfpga_verilog.cpp | 10 +- .../src/base/openfpga_verilog_command.cpp | 9 ++ openfpga/src/fpga_verilog/verilog_api.cpp | 16 ++-- openfpga/src/fpga_verilog/verilog_api.h | 2 +- openfpga/src/fpga_verilog/verilog_mux.cpp | 2 +- openfpga/src/fpga_verilog/verilog_options.cpp | 25 +++++ openfpga/src/fpga_verilog/verilog_options.h | 6 ++ .../src/fpga_verilog/verilog_submodule.cpp | 96 +++++++++++++++++++ openfpga/src/fpga_verilog/verilog_submodule.h | 27 ++++++ .../fpga_verilog/verilog_submodule_utils.cpp | 6 ++ .../src/fpga_verilog/verilog_writer_utils.cpp | 6 ++ openfpga/test_script/s298_k6_frac.openfpga | 2 +- 13 files changed, 198 insertions(+), 11 deletions(-) create mode 100644 openfpga/src/fpga_verilog/verilog_submodule.cpp create mode 100644 openfpga/src/fpga_verilog/verilog_submodule.h diff --git a/libopenfpga/libopenfpgautil/src/openfpga_digest.cpp b/libopenfpga/libopenfpgautil/src/openfpga_digest.cpp index 3340ea8b1..4714700e4 100644 --- a/libopenfpga/libopenfpgautil/src/openfpga_digest.cpp +++ b/libopenfpga/libopenfpgautil/src/openfpga_digest.cpp @@ -135,7 +135,7 @@ bool create_dir_path(const char* dir_path) { return true; case -1: if (EEXIST == errno) { - VTR_LOG_ERROR("Directory '%s' already exists. Will overwrite contents\n", + VTR_LOG_WARN("Directory '%s' already exists. Will overwrite contents\n", dir_path); return true; } diff --git a/openfpga/src/base/openfpga_verilog.cpp b/openfpga/src/base/openfpga_verilog.cpp index 2612fe76a..018fe1249 100644 --- a/openfpga/src/base/openfpga_verilog.cpp +++ b/openfpga/src/base/openfpga_verilog.cpp @@ -25,6 +25,10 @@ void write_fabric_verilog(OpenfpgaContext& openfpga_ctx, CommandOptionId opt_include_timing = cmd.option("include_timing"); CommandOptionId opt_include_signal_init = cmd.option("include_signal_init"); CommandOptionId opt_support_icarus_simulator = cmd.option("support_icarus_simulator"); + CommandOptionId opt_print_user_defined_template = cmd.option("print_user_defined_template"); + CommandOptionId opt_print_top_testbench = cmd.option("print_top_testbench"); + CommandOptionId opt_print_formal_verification_top_netlist = cmd.option("print_formal_verification_top_netlist"); + CommandOptionId opt_print_autocheck_top_testbench = cmd.option("print_autocheck_top_testbench"); CommandOptionId opt_verbose = cmd.option("verbose"); /* This is an intermediate data structure which is designed to modularize the FPGA-Verilog @@ -36,10 +40,14 @@ void write_fabric_verilog(OpenfpgaContext& openfpga_ctx, options.set_include_timing(cmd_context.option_enable(cmd, opt_include_timing)); options.set_include_signal_init(cmd_context.option_enable(cmd, opt_include_signal_init)); options.set_support_icarus_simulator(cmd_context.option_enable(cmd, opt_support_icarus_simulator)); + options.set_print_user_defined_template(cmd_context.option_enable(cmd, opt_print_user_defined_template)); + options.set_print_top_testbench(cmd_context.option_enable(cmd, opt_print_top_testbench)); + options.set_print_formal_verification_top_netlist(cmd_context.option_enable(cmd, opt_print_formal_verification_top_netlist)); + options.set_print_autocheck_top_testbench(cmd_context.option_value(cmd, opt_print_autocheck_top_testbench)); options.set_verbose_output(cmd_context.option_enable(cmd, opt_verbose)); options.set_compress_routing(openfpga_ctx.flow_manager().compress_routing()); - fpga_fabric_verilog(openfpga_ctx.module_graph(), + fpga_fabric_verilog(openfpga_ctx.mutable_module_graph(), openfpga_ctx.arch().circuit_lib, openfpga_ctx.mux_lib(), g_vpr_ctx.device().grid, diff --git a/openfpga/src/base/openfpga_verilog_command.cpp b/openfpga/src/base/openfpga_verilog_command.cpp index fa3ff9c4b..7eb7674b9 100644 --- a/openfpga/src/base/openfpga_verilog_command.cpp +++ b/openfpga/src/base/openfpga_verilog_command.cpp @@ -34,6 +34,15 @@ void add_openfpga_verilog_commands(openfpga::Shell& shell) { shell_cmd_write_fabric_verilog.add_option("include_signal_init", false, "Initialize all the signals in Verilog netlists"); /* Add an option '--support_icarus_simulator' */ shell_cmd_write_fabric_verilog.add_option("support_icarus_simulator", false, "Fine-tune Verilog netlists to support icarus simulator"); + /* Add an option '--print_user_defined_template' */ + shell_cmd_write_fabric_verilog.add_option("print_user_defined_template", false, "Generate a template Verilog files for user-defined circuit models"); + /* Add an option '--print_top_testbench' */ + shell_cmd_write_fabric_verilog.add_option("print_top_testbench", false, "Generate a testbench for top-level fabric module"); + /* Add an option '--print_formal_verification_top_netlist' */ + shell_cmd_write_fabric_verilog.add_option("print_formal_verification_top_netlist", false, "Generate a top-level module which can be used in formal verification"); + /* Add an option '--print_autocheck_top_testbench' */ + CommandOptionId fabric_verilog_autocheck_tb_opt = shell_cmd_write_fabric_verilog.add_option("print_autocheck_top_testbench", false, "Generate a testbench for top-level fabric module with autocheck capability"); + shell_cmd_write_fabric_verilog.set_option_require_value(fabric_verilog_autocheck_tb_opt, openfpga::OPT_STRING); /* Add an option '--verbose' */ shell_cmd_write_fabric_verilog.add_option("verbose", false, "Enable verbose output"); diff --git a/openfpga/src/fpga_verilog/verilog_api.cpp b/openfpga/src/fpga_verilog/verilog_api.cpp index 71ec0708d..baa18abb4 100644 --- a/openfpga/src/fpga_verilog/verilog_api.cpp +++ b/openfpga/src/fpga_verilog/verilog_api.cpp @@ -15,11 +15,9 @@ #include "device_rr_gsb.h" #include "verilog_constants.h" #include "verilog_auxiliary_netlists.h" -//#include "verilog_submodules.h" +#include "verilog_submodule.h" //#include "verilog_routing.h" -//#include "verilog_submodules.h" //#include "verilog_grid.h" -//#include "verilog_routing.h" //#include "verilog_top_module.h" /* Header file for this source file */ @@ -40,8 +38,13 @@ namespace openfpga { * 6. Testbench, where a FPGA module is configured with a bitstream and then driven by input vectors * 7. Pre-configured testbench, which can skip the configuration phase and pre-configure the FPGA module. This testbench is created for quick verification and formal verification purpose. * 8. Verilog netlist including preprocessing flags and all the Verilog netlists that have been generated + * + * TODO: We should use module manager as a constant here. + * All the modification should be done before this writer! + * The only exception now is the user-defined modules. + * We should think clearly about how to handle them for both Verilog and SPICE generators! ********************************************************************/ -void fpga_fabric_verilog(const ModuleManager& module_manager, +void fpga_fabric_verilog(ModuleManager& module_manager, const CircuitLibrary& circuit_lib, const MuxLibrary& mux_lib, const DeviceGrid& grids, @@ -81,8 +84,9 @@ void fpga_fabric_verilog(const ModuleManager& module_manager, * the module manager. * Without the modules in the module manager, core logic generation is not possible!!! */ - //print_verilog_submodules(module_manager, mux_lib, sram_verilog_orgz_info, src_dir_path.c_str(), submodule_dir_path.c_str(), - // Arch, vpr_setup.FPGA_SPICE_Opts.SynVerilogOpts); + print_verilog_submodule(module_manager, mux_lib, circuit_lib, + src_dir_path, submodule_dir_path, + options); /* Generate routing blocks */ //if (true == compress_routing) { diff --git a/openfpga/src/fpga_verilog/verilog_api.h b/openfpga/src/fpga_verilog/verilog_api.h index a24c0a1e8..569650f2a 100644 --- a/openfpga/src/fpga_verilog/verilog_api.h +++ b/openfpga/src/fpga_verilog/verilog_api.h @@ -22,7 +22,7 @@ /* begin namespace openfpga */ namespace openfpga { -void fpga_fabric_verilog(const ModuleManager& module_manager, +void fpga_fabric_verilog(ModuleManager& module_manager, const CircuitLibrary& circuit_lib, const MuxLibrary& mux_lib, const DeviceGrid& grids, diff --git a/openfpga/src/fpga_verilog/verilog_mux.cpp b/openfpga/src/fpga_verilog/verilog_mux.cpp index b78a9362a..1e58e3fc8 100644 --- a/openfpga/src/fpga_verilog/verilog_mux.cpp +++ b/openfpga/src/fpga_verilog/verilog_mux.cpp @@ -1240,7 +1240,7 @@ void print_verilog_submodule_muxes(ModuleManager& module_manager, check_file_stream(verilog_fname.c_str(), fp); /* Print out debugging information for if the file is not opened/created properly */ - VTR_LOG("Writing Verilog netlist for Multiplexers '%s' ...\n", + VTR_LOG("Writing Verilog netlist for Multiplexers '%s' ...", verilog_fname.c_str()); print_verilog_file_header(fp, "Multiplexers"); diff --git a/openfpga/src/fpga_verilog/verilog_options.cpp b/openfpga/src/fpga_verilog/verilog_options.cpp index 2539d5e51..b3b398dd7 100644 --- a/openfpga/src/fpga_verilog/verilog_options.cpp +++ b/openfpga/src/fpga_verilog/verilog_options.cpp @@ -8,6 +8,23 @@ /* begin namespace openfpga */ namespace openfpga { +/************************************************** + * Public Constructors + *************************************************/ +FabricVerilogOption::FabricVerilogOption() { + output_directory_.clear(); + support_icarus_simulator_ = false; + include_signal_init_ = false; + include_timing_ = false; + explicit_port_mapping_ = false; + compress_routing_ = false; + print_top_testbench_ = false; + print_formal_verification_top_netlist_ = false; + reference_verilog_file_path_.clear(); + print_user_defined_template_ = false; + verbose_output_ = false; +} + /************************************************** * Public Accessors *************************************************/ @@ -51,6 +68,10 @@ std::string FabricVerilogOption::reference_verilog_file_path() const { return reference_verilog_file_path_; } +bool FabricVerilogOption::print_user_defined_template() const { + return print_user_defined_template_; +} + bool FabricVerilogOption::verbose_output() const { return verbose_output_; } @@ -94,6 +115,10 @@ void FabricVerilogOption::set_print_autocheck_top_testbench(const std::string& r reference_verilog_file_path_ = reference_verilog_file_path; } +void FabricVerilogOption::set_print_user_defined_template(const bool& enabled) { + print_user_defined_template_ = enabled; +} + void FabricVerilogOption::set_verbose_output(const bool& enabled) { verbose_output_ = enabled; } diff --git a/openfpga/src/fpga_verilog/verilog_options.h b/openfpga/src/fpga_verilog/verilog_options.h index 182cabcf2..48e32086b 100644 --- a/openfpga/src/fpga_verilog/verilog_options.h +++ b/openfpga/src/fpga_verilog/verilog_options.h @@ -17,6 +17,9 @@ namespace openfpga { * *******************************************************************/ class FabricVerilogOption { + public: /* Public constructor */ + /* Set default options */ + FabricVerilogOption(); public: /* Public accessors */ std::string output_directory() const; bool support_icarus_simulator() const; @@ -28,6 +31,7 @@ class FabricVerilogOption { bool print_formal_verification_top_netlist() const; bool print_autocheck_top_testbench() const; std::string reference_verilog_file_path() const; + bool print_user_defined_template() const; bool verbose_output() const; public: /* Public mutators */ void set_output_directory(const std::string& output_dir); @@ -39,6 +43,7 @@ class FabricVerilogOption { void set_print_top_testbench(const bool& enabled); void set_print_formal_verification_top_netlist(const bool& enabled); void set_print_autocheck_top_testbench(const std::string& reference_verilog_file_path); + void set_print_user_defined_template(const bool& enabled); void set_verbose_output(const bool& enabled); private: /* Internal Data */ std::string output_directory_; @@ -51,6 +56,7 @@ class FabricVerilogOption { bool print_formal_verification_top_netlist_; /* print_autocheck_top_testbench will be enabled when reference file path is not empty */ std::string reference_verilog_file_path_; + bool print_user_defined_template_; bool verbose_output_; }; diff --git a/openfpga/src/fpga_verilog/verilog_submodule.cpp b/openfpga/src/fpga_verilog/verilog_submodule.cpp new file mode 100644 index 000000000..c52fc2262 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_submodule.cpp @@ -0,0 +1,96 @@ +/********************************************************************* + * This file includes top-level function to generate Verilog primitive modules + * and print them to files + ********************************************************************/ + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" + +#include "verilog_submodule_utils.h" +#include "verilog_essential_gates.h" +#include "verilog_decoders.h" +#include "verilog_mux.h" +#include "verilog_lut.h" +#include "verilog_wire.h" +#include "verilog_memory.h" +#include "verilog_writer_utils.h" + +#include "verilog_constants.h" +#include "verilog_submodule.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/********************************************************************* + * Top-level function to generate primitive modules: + * 1. Logic gates: AND/OR, inverter, buffer and transmission-gate/pass-transistor + * 2. Routing multiplexers + * 3. Local encoders for routing multiplexers + * 4. Wires + * 5. Configuration memory blocks + * 6. Verilog template + ********************************************************************/ +void print_verilog_submodule(ModuleManager& module_manager, + const MuxLibrary& mux_lib, + const CircuitLibrary& circuit_lib, + const std::string& verilog_dir, + const std::string& submodule_dir, + const FabricVerilogOption& fpga_verilog_opts) { + + /* Register all the user-defined modules in the module manager + * This should be done prior to other steps in this function, + * because they will be instanciated by other primitive modules + */ + add_user_defined_verilog_modules(module_manager, circuit_lib); + + /* Create a vector to contain all the Verilog netlist names that have been generated in this function */ + std::vector netlist_names; + + + print_verilog_submodule_essentials(module_manager, + netlist_names, + verilog_dir, + submodule_dir, + circuit_lib); + + /* Routing multiplexers */ + /* NOTE: local decoders generation must go before the MUX generation!!! + * because local decoders modules will be instanciated in the MUX modules + */ + print_verilog_submodule_mux_local_decoders(module_manager, netlist_names, + mux_lib, circuit_lib, + verilog_dir, submodule_dir); + print_verilog_submodule_muxes(module_manager, netlist_names, mux_lib, circuit_lib, + verilog_dir, submodule_dir, + fpga_verilog_opts.explicit_port_mapping()); + + + /* LUTes */ + print_verilog_submodule_luts(module_manager, netlist_names, circuit_lib, + verilog_dir, submodule_dir, + fpga_verilog_opts.explicit_port_mapping()); + + /* Hard wires */ + print_verilog_submodule_wires(module_manager, netlist_names, circuit_lib, + verilog_dir, submodule_dir); + + /* 4. Memories */ + print_verilog_submodule_memories(module_manager, netlist_names, + mux_lib, circuit_lib, + verilog_dir, submodule_dir, + fpga_verilog_opts.explicit_port_mapping()); + + /* 5. Dump template for all the modules */ + if (true == fpga_verilog_opts.print_user_defined_template()) { + print_verilog_submodule_templates(module_manager, circuit_lib, + verilog_dir, submodule_dir); + } + + /* Create a header file to include all the subckts */ + print_verilog_netlist_include_header_file(netlist_names, + submodule_dir.c_str(), + SUBMODULE_VERILOG_FILE_NAME); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_submodule.h b/openfpga/src/fpga_verilog/verilog_submodule.h new file mode 100644 index 000000000..4f7cb511c --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_submodule.h @@ -0,0 +1,27 @@ +#ifndef VERILOG_SUBMODULE_H +#define VERILOG_SUBMODULE_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "module_manager.h" +#include "mux_library.h" +#include "verilog_options.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_verilog_submodule(ModuleManager& module_manager, + const MuxLibrary& mux_lib, + const CircuitLibrary& circuit_lib, + const std::string& verilog_dir, + const std::string& submodule_dir, + const FabricVerilogOption& fpga_verilog_opts); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_verilog/verilog_submodule_utils.cpp b/openfpga/src/fpga_verilog/verilog_submodule_utils.cpp index f4f02472d..b4255b3e9 100644 --- a/openfpga/src/fpga_verilog/verilog_submodule_utils.cpp +++ b/openfpga/src/fpga_verilog/verilog_submodule_utils.cpp @@ -132,6 +132,8 @@ void print_verilog_submodule_signal_init(std::fstream& fp, ********************************************************************/ void add_user_defined_verilog_modules(ModuleManager& module_manager, const CircuitLibrary& circuit_lib) { + VTR_LOG("Registering user-defined modules..."); + /* Iterate over Verilog modules */ for (const auto& model : circuit_lib.models()) { /* We only care about user-defined models */ @@ -149,8 +151,12 @@ void add_user_defined_verilog_modules(ModuleManager& module_manager, ModuleId module_id = module_manager.find_module(circuit_lib.model_name(model)); if (ModuleId::INVALID() == module_id) { add_circuit_model_to_module_manager(module_manager, circuit_lib, model); + VTR_LOG("Registered user-defined circuit model '%s'\n", + circuit_lib.model_name(model).c_str()); } } + + VTR_LOG("Done\n"); } /********************************************************************* diff --git a/openfpga/src/fpga_verilog/verilog_writer_utils.cpp b/openfpga/src/fpga_verilog/verilog_writer_utils.cpp index 28f2ed5db..600357c36 100644 --- a/openfpga/src/fpga_verilog/verilog_writer_utils.cpp +++ b/openfpga/src/fpga_verilog/verilog_writer_utils.cpp @@ -1374,8 +1374,12 @@ void print_verilog_clock_stimuli(std::fstream& fp, void print_verilog_netlist_include_header_file(const std::vector& netlists_to_be_included, const char* subckt_dir, const char* header_file_name) { + std::string verilog_fname(std::string(subckt_dir) + std::string(header_file_name)); + VTR_LOG("Writing header file for primitive modules '%s' ...", + verilog_fname.c_str()); + /* Create the file stream */ std::fstream fp; fp.open(verilog_fname, std::fstream::out | std::fstream::trunc); @@ -1392,6 +1396,8 @@ void print_verilog_netlist_include_header_file(const std::vector& n /* close file stream */ fp.close(); + + VTR_LOG("Done\n"); } } /* end namespace openfpga */ diff --git a/openfpga/test_script/s298_k6_frac.openfpga b/openfpga/test_script/s298_k6_frac.openfpga index 1dece6d57..c01774bd3 100644 --- a/openfpga/test_script/s298_k6_frac.openfpga +++ b/openfpga/test_script/s298_k6_frac.openfpga @@ -23,7 +23,7 @@ build_fabric --compress_routing --duplicate_grid_pin --verbose # Write the Verilog netlit for FPGA fabric # - Enable the use of explicit port mapping in Verilog netlist -write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --verbose +write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose # Finish and exit OpenFPGA exit From c20caa1fa385e415b7831039c01f9be98cb9c961 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 16 Feb 2020 14:47:54 -0700 Subject: [PATCH 159/645] routing module Verilog writer is online --- openfpga/src/fpga_verilog/verilog_api.cpp | 24 +- openfpga/src/fpga_verilog/verilog_constants.h | 2 + openfpga/src/fpga_verilog/verilog_routing.cpp | 355 ++++++++++++++++++ openfpga/src/fpga_verilog/verilog_routing.h | 33 ++ 4 files changed, 403 insertions(+), 11 deletions(-) create mode 100644 openfpga/src/fpga_verilog/verilog_routing.cpp create mode 100644 openfpga/src/fpga_verilog/verilog_routing.h diff --git a/openfpga/src/fpga_verilog/verilog_api.cpp b/openfpga/src/fpga_verilog/verilog_api.cpp index baa18abb4..aa68e0024 100644 --- a/openfpga/src/fpga_verilog/verilog_api.cpp +++ b/openfpga/src/fpga_verilog/verilog_api.cpp @@ -16,7 +16,7 @@ #include "verilog_constants.h" #include "verilog_auxiliary_netlists.h" #include "verilog_submodule.h" -//#include "verilog_routing.h" +#include "verilog_routing.h" //#include "verilog_grid.h" //#include "verilog_top_module.h" @@ -89,16 +89,18 @@ void fpga_fabric_verilog(ModuleManager& module_manager, options); /* Generate routing blocks */ - //if (true == compress_routing) { - // print_verilog_unique_routing_modules(module_manager, device_rr_gsb, - // src_dir_path, rr_dir_path, - // dump_explicit_verilog); - //} else { - // VTR_ASSERT(false == compress_routing); - // print_verilog_flatten_routing_modules(module_manager, device_rr_gsb, - // src_dir_path, rr_dir_path, - // dump_explicit_verilog); - //} + if (true == options.compress_routing()) { + print_verilog_unique_routing_modules(const_cast(module_manager), + device_rr_gsb, + src_dir_path, rr_dir_path, + options.explicit_port_mapping()); + } else { + VTR_ASSERT(false == options.compress_routing()); + print_verilog_flatten_routing_modules(const_cast(module_manager), + device_rr_gsb, + src_dir_path, rr_dir_path, + options.explicit_port_mapping()); + } /* Generate grids */ //print_verilog_grids(module_manager, diff --git a/openfpga/src/fpga_verilog/verilog_constants.h b/openfpga/src/fpga_verilog/verilog_constants.h index 91f87c475..d5e1f4edb 100644 --- a/openfpga/src/fpga_verilog/verilog_constants.h +++ b/openfpga/src/fpga_verilog/verilog_constants.h @@ -44,4 +44,6 @@ constexpr char* USER_DEFINED_TEMPLATE_VERILOG_FILE_NAME = "user_defined_template constexpr char* VERILOG_MUX_BASIS_POSTFIX = "_basis"; constexpr char* VERILOG_MEM_POSTFIX = "_mem"; +constexpr char* SB_VERILOG_FILE_NAME_PREFIX = "sb_"; + #endif diff --git a/openfpga/src/fpga_verilog/verilog_routing.cpp b/openfpga/src/fpga_verilog/verilog_routing.cpp new file mode 100644 index 000000000..0d9582556 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_routing.cpp @@ -0,0 +1,355 @@ +/********************************************************************* + * This file includes functions that are used for + * Verilog generation of FPGA routing architecture (global routing) + *********************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_time.h" +#include "vtr_log.h" + +/* Headers from openfpgautil library */ +#include "openfpga_digest.h" + +/* Include FPGA-Verilog header files*/ +#include "openfpga_naming.h" +#include "verilog_constants.h" +#include "verilog_writer_utils.h" +#include "verilog_module_writer.h" +#include "verilog_routing.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Print the sub-circuit of a connection Box (Type: [CHANX|CHANY]) + * Actually it is very similiar to switch box but + * the difference is connection boxes connect Grid INPUT Pins to channels + * NOTE: direct connection between CLBs should NOT be included inside this + * module! They should be added in the top-level module as their connection + * is not limited to adjacent CLBs!!! + * + * Location of a X- and Y-direction Connection Block in FPGA fabric + * +------------+ +-------------+ + * | |------>| | + * | CLB |<------| Y-direction | + * | | ... | Connection | + * | |------>| Block | + * +------------+ +-------------+ + * | ^ ... | | ^ ... | + * v | v v | v + * +-------------------+ +-------------+ + * --->| |--->| | + * <---| X-direction |<---| Switch | + * ...| Connection block |... | Block | + * --->| |--->| | + * +-------------------+ +-------------+ + * + * Internal structure: + * This is an example of a X-direction connection block + * Note that middle output ports are shorted wire from inputs of routing tracks, + * which are also the inputs of routing multiplexer of the connection block + * + * CLB Input Pins + * (IPINs) + * ^ ^ ^ + * | | ... | + * +--------------------------+ + * | ^ ^ ^ | + * | | | ... | | + * | +--------------------+ | + * | | routing | | + * | | multiplexers | | + * | +--------------------+ | + * | middle outputs | + * | of routing channel | + * | ^ ^ ^ ^ ^ ^ ^ ^ | + * | | | | | ... | | | | | + * in[0] -->|------------------------->|---> out[0] + * out[1] <--|<-------------------------|<--- in[1] + * | ... | + * in[W-2] -->|------------------------->|---> out[W-2] + * out[W-1] <--|<-------------------------|<--- in[W-1] + * +--------------------------+ + * + * W: routing channel width + * + ********************************************************************/ +static +void print_verilog_routing_connection_box_unique_module(const ModuleManager& module_manager, + std::vector& netlist_names, + const std::string& verilog_dir, + const std::string& subckt_dir, + const RRGSB& rr_gsb, + const t_rr_type& cb_type, + const bool& use_explicit_port_map) { + /* Create the netlist */ + vtr::Point gsb_coordinate(rr_gsb.get_cb_x(cb_type), rr_gsb.get_cb_y(cb_type)); + std::string verilog_fname(subckt_dir + generate_connection_block_netlist_name(cb_type, gsb_coordinate, std::string(VERILOG_NETLIST_FILE_POSTFIX))); + + /* Create the file stream */ + std::fstream fp; + fp.open(verilog_fname, std::fstream::out | std::fstream::trunc); + + check_file_stream(verilog_fname.c_str(), fp); + + print_verilog_file_header(fp, std::string("Verilog modules for Unique Connection Blocks[" + std::to_string(rr_gsb.get_cb_x(cb_type)) + "]["+ std::to_string(rr_gsb.get_cb_y(cb_type)) + "]")); + + /* Print preprocessing flags */ + print_verilog_include_defines_preproc_file(fp, verilog_dir); + + /* Create a Verilog Module based on the circuit model, and add to module manager */ + ModuleId cb_module = module_manager.find_module(generate_connection_block_module_name(cb_type, gsb_coordinate)); + VTR_ASSERT(true == module_manager.valid_module_id(cb_module)); + + /* Write the verilog module */ + write_verilog_module_to_file(fp, module_manager, cb_module, use_explicit_port_map); + + /* Add an empty line as a splitter */ + fp << std::endl; + + /* Close file handler */ + fp.close(); + + /* Add fname to the netlist name list */ + netlist_names.push_back(verilog_fname); +} + +/********************************************************************* + * Generate the Verilog module for a Switch Box. + * A Switch Box module consists of following ports: + * 1. Channel Y [x][y] inputs + * 2. Channel X [x+1][y] inputs + * 3. Channel Y [x][y-1] outputs + * 4. Channel X [x][y] outputs + * 5. Grid[x][y+1] Right side outputs pins + * 6. Grid[x+1][y+1] Left side output pins + * 7. Grid[x+1][y+1] Bottom side output pins + * 8. Grid[x+1][y] Top side output pins + * 9. Grid[x+1][y] Left side output pins + * 10. Grid[x][y] Right side output pins + * 11. Grid[x][y] Top side output pins + * 12. Grid[x][y+1] Bottom side output pins + * + * Location of a Switch Box in FPGA fabric: + * + * -------------- -------------- + * | | | | + * | Grid | ChanY | Grid | + * | [x][y+1] | [x][y+1] | [x+1][y+1] | + * | | | | + * -------------- -------------- + * ---------- + * ChanX | Switch | ChanX + * [x][y] | Box | [x+1][y] + * | [x][y] | + * ---------- + * -------------- -------------- + * | | | | + * | Grid | ChanY | Grid | + * | [x][y] | [x][y] | [x+1][y] | + * | | | | + * -------------- -------------- + * + * Switch Block pin location map + * + * Grid[x][y+1] ChanY[x][y+1] Grid[x+1][y+1] + * right_pins inputs/outputs left_pins + * | ^ | + * | | | + * v v v + * +-----------------------------------------------+ + * | | + * Grid[x][y+1] | | Grid[x+1][y+1] + * bottom_pins---->| |<---- bottom_pins + * | | + * ChanX[x][y] | Switch Box [x][y] | ChanX[x+1][y] + * inputs/outputs<--->| |<---> inputs/outputs + * | | + * Grid[x][y+1] | | Grid[x+1][y+1] + * top_pins---->| |<---- top_pins + * | | + * +-----------------------------------------------+ + * ^ ^ ^ + * | | | + * | v | + * Grid[x][y] ChanY[x][y] Grid[x+1][y] + * right_pins inputs/outputs left_pins + * + * + ********************************************************************/ +static +void print_verilog_routing_switch_box_unique_module(const ModuleManager& module_manager, + std::vector& netlist_names, + const std::string& verilog_dir, + const std::string& subckt_dir, + const RRGSB& rr_gsb, + const bool& use_explicit_port_map) { + /* Create the netlist */ + vtr::Point gsb_coordinate(rr_gsb.get_sb_x(), rr_gsb.get_sb_y()); + std::string verilog_fname(subckt_dir + generate_routing_block_netlist_name(SB_VERILOG_FILE_NAME_PREFIX, gsb_coordinate, std::string(VERILOG_NETLIST_FILE_POSTFIX))); + + /* Create the file stream */ + std::fstream fp; + fp.open(verilog_fname, std::fstream::out | std::fstream::trunc); + + check_file_stream(verilog_fname.c_str(), fp); + + print_verilog_file_header(fp, std::string("Verilog modules for Unique Switch Blocks[" + std::to_string(rr_gsb.get_sb_x()) + "]["+ std::to_string(rr_gsb.get_sb_y()) + "]")); + + /* Print preprocessing flags */ + print_verilog_include_defines_preproc_file(fp, verilog_dir); + + /* Create a Verilog Module based on the circuit model, and add to module manager */ + ModuleId sb_module = module_manager.find_module(generate_switch_block_module_name(gsb_coordinate)); + VTR_ASSERT(true == module_manager.valid_module_id(sb_module)); + + /* Write the verilog module */ + write_verilog_module_to_file(fp, module_manager, sb_module, use_explicit_port_map); + + /* Close file handler */ + fp.close(); + + /* Add fname to the netlist name list */ + netlist_names.push_back(verilog_fname); +} + +/******************************************************************** + * Iterate over all the connection blocks in a device + * and build a module for each of them + *******************************************************************/ +static +void print_verilog_flatten_connection_block_modules(const ModuleManager& module_manager, + std::vector& netlist_names, + const DeviceRRGSB& device_rr_gsb, + const std::string& verilog_dir, + const std::string& subckt_dir, + const t_rr_type& cb_type, + const bool& use_explicit_port_map) { + /* Build unique X-direction connection block modules */ + vtr::Point cb_range = device_rr_gsb.get_gsb_range(); + + for (size_t ix = 0; ix < cb_range.x(); ++ix) { + for (size_t iy = 0; iy < cb_range.y(); ++iy) { + /* Check if the connection block exists in the device! + * Some of them do NOT exist due to heterogeneous blocks (height > 1) + * We will skip those modules + */ + const RRGSB& rr_gsb = device_rr_gsb.get_gsb(ix, iy); + if (true != rr_gsb.is_cb_exist(cb_type)) { + continue; + } + print_verilog_routing_connection_box_unique_module(module_manager, netlist_names, + verilog_dir, + subckt_dir, + rr_gsb, cb_type, + use_explicit_port_map); + } + } +} + +/******************************************************************** + * A top-level function of this file + * Print all the modules for global routing architecture of a FPGA fabric + * in Verilog format in a flatten way: + * Each connection block and switch block will be generated as a unique module + * Covering: + * 1. Connection blocks + * 2. Switch blocks + *******************************************************************/ +void print_verilog_flatten_routing_modules(const ModuleManager& module_manager, + const DeviceRRGSB& device_rr_gsb, + const std::string& verilog_dir, + const std::string& subckt_dir, + const bool& use_explicit_port_map) { + /* Create a vector to contain all the Verilog netlist names that have been generated in this function */ + std::vector netlist_names; + + vtr::Point sb_range = device_rr_gsb.get_gsb_range(); + + /* Build unique switch block modules */ + for (size_t ix = 0; ix < sb_range.x(); ++ix) { + for (size_t iy = 0; iy < sb_range.y(); ++iy) { + const RRGSB& rr_gsb = device_rr_gsb.get_gsb(ix, iy); + if (true != rr_gsb.is_sb_exist()) { + continue; + } + print_verilog_routing_switch_box_unique_module(module_manager, netlist_names, + verilog_dir, + subckt_dir, + rr_gsb, + use_explicit_port_map); + } + } + + print_verilog_flatten_connection_block_modules(module_manager, netlist_names, device_rr_gsb, verilog_dir, subckt_dir, CHANX, use_explicit_port_map); + + print_verilog_flatten_connection_block_modules(module_manager, netlist_names, device_rr_gsb, verilog_dir, subckt_dir, CHANY, use_explicit_port_map); + + VTR_LOG("Writing header file for routing submodules '%s'...", + ROUTING_VERILOG_FILE_NAME); + print_verilog_netlist_include_header_file(netlist_names, + subckt_dir.c_str(), + ROUTING_VERILOG_FILE_NAME); + VTR_LOG("Done\n"); +} + + +/******************************************************************** + * A top-level function of this file + * Print all the unique modules for global routing architecture of a FPGA fabric + * in Verilog format, including: + * 1. Connection blocks + * 2. Switch blocks + * + * Note: this function SHOULD be called only when + * the option compact_routing_hierarchy is turned on!!! + *******************************************************************/ +void print_verilog_unique_routing_modules(const ModuleManager& module_manager, + const DeviceRRGSB& device_rr_gsb, + const std::string& verilog_dir, + const std::string& subckt_dir, + const bool& use_explicit_port_map) { + /* Create a vector to contain all the Verilog netlist names that have been generated in this function */ + std::vector netlist_names; + + /* Build unique switch block modules */ + for (size_t isb = 0; isb < device_rr_gsb.get_num_sb_unique_module(); ++isb) { + const RRGSB& unique_mirror = device_rr_gsb.get_sb_unique_module(isb); + print_verilog_routing_switch_box_unique_module(module_manager, netlist_names, + verilog_dir, + subckt_dir, + unique_mirror, + use_explicit_port_map); + } + + /* Build unique X-direction connection block modules */ + for (size_t icb = 0; icb < device_rr_gsb.get_num_cb_unique_module(CHANX); ++icb) { + const RRGSB& unique_mirror = device_rr_gsb.get_cb_unique_module(CHANX, icb); + + print_verilog_routing_connection_box_unique_module(module_manager, netlist_names, + verilog_dir, + subckt_dir, + unique_mirror, CHANX, + use_explicit_port_map); + } + + /* Build unique X-direction connection block modules */ + for (size_t icb = 0; icb < device_rr_gsb.get_num_cb_unique_module(CHANY); ++icb) { + const RRGSB& unique_mirror = device_rr_gsb.get_cb_unique_module(CHANY, icb); + + print_verilog_routing_connection_box_unique_module(module_manager, netlist_names, + verilog_dir, + subckt_dir, + unique_mirror, CHANY, + use_explicit_port_map); + } + + VTR_LOG("Writing header file for routing submodules '%s'...", + ROUTING_VERILOG_FILE_NAME); + print_verilog_netlist_include_header_file(netlist_names, + subckt_dir.c_str(), + ROUTING_VERILOG_FILE_NAME); + VTR_LOG("Done\n"); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_routing.h b/openfpga/src/fpga_verilog/verilog_routing.h new file mode 100644 index 000000000..2a1a58a66 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_routing.h @@ -0,0 +1,33 @@ +#ifndef VERILOG_ROUTING_H +#define VERILOG_ROUTING_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ + +#include "mux_library.h" +#include "module_manager.h" +#include "device_rr_gsb.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_verilog_flatten_routing_modules(const ModuleManager& module_manager, + const DeviceRRGSB& device_rr_gsb, + const std::string& verilog_dir, + const std::string& subckt_dir, + const bool& use_explicit_port_map); + +void print_verilog_unique_routing_modules(const ModuleManager& module_manager, + const DeviceRRGSB& device_rr_gsb, + const std::string& verilog_dir, + const std::string& subckt_dir, + const bool& use_explicit_port_map); + +} /* end namespace openfpga */ + +#endif From e37ac8a09800ca75cad7a3c03f89cb77eea4a5f5 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 16 Feb 2020 16:04:41 -0700 Subject: [PATCH 160/645] add grid module Verilog writer --- openfpga/src/base/openfpga_naming.cpp | 16 + openfpga/src/base/openfpga_naming.h | 4 + openfpga/src/base/openfpga_verilog.cpp | 3 +- openfpga/src/fpga_verilog/verilog_api.cpp | 13 +- openfpga/src/fpga_verilog/verilog_api.h | 6 +- openfpga/src/fpga_verilog/verilog_constants.h | 2 + openfpga/src/fpga_verilog/verilog_grid.cpp | 411 ++++++++++++++++++ openfpga/src/fpga_verilog/verilog_grid.h | 30 ++ openfpga/src/fpga_verilog/verilog_routing.cpp | 2 + 9 files changed, 479 insertions(+), 8 deletions(-) create mode 100644 openfpga/src/fpga_verilog/verilog_grid.cpp create mode 100644 openfpga/src/fpga_verilog/verilog_grid.h diff --git a/openfpga/src/base/openfpga_naming.cpp b/openfpga/src/base/openfpga_naming.cpp index 7aaafff5d..fab27a12b 100644 --- a/openfpga/src/base/openfpga_naming.cpp +++ b/openfpga/src/base/openfpga_naming.cpp @@ -905,6 +905,22 @@ std::string generate_mux_sram_port_name(const CircuitLibrary& circuit_lib, return generate_local_sram_port_name(prefix, mux_instance_id, port_type); } +/********************************************************************* + * Generate the netlist name of a logical tile + **********************************************************************/ +std::string generate_logical_tile_netlist_name(const std::string& prefix, + const t_pb_graph_node* pb_graph_head, + const std::string& postfix) { + /* This must be the root node */ + VTR_ASSERT(true == pb_graph_head->is_root()); + /* Add the name of physical block */ + std::string module_name = prefix + std::string(pb_graph_head->pb_type->name); + + module_name += postfix; + + return module_name; +} + /********************************************************************* * Generate the prefix for naming a grid block netlist or a grid module * This function will consider the io side and add it to the prefix diff --git a/openfpga/src/base/openfpga_naming.h b/openfpga/src/base/openfpga_naming.h index 4bc22f3c9..13d4c56c0 100644 --- a/openfpga/src/base/openfpga_naming.h +++ b/openfpga/src/base/openfpga_naming.h @@ -200,6 +200,10 @@ std::string generate_mux_sram_port_name(const CircuitLibrary& circuit_lib, const size_t& mux_instance_id, const e_circuit_model_port_type& port_type); +std::string generate_logical_tile_netlist_name(const std::string& prefix, + const t_pb_graph_node* pb_graph_head, + const std::string& postfix); + std::string generate_grid_block_prefix(const std::string& prefix, const e_side& io_side); diff --git a/openfpga/src/base/openfpga_verilog.cpp b/openfpga/src/base/openfpga_verilog.cpp index 018fe1249..72b8fb100 100644 --- a/openfpga/src/base/openfpga_verilog.cpp +++ b/openfpga/src/base/openfpga_verilog.cpp @@ -50,7 +50,8 @@ void write_fabric_verilog(OpenfpgaContext& openfpga_ctx, fpga_fabric_verilog(openfpga_ctx.mutable_module_graph(), openfpga_ctx.arch().circuit_lib, openfpga_ctx.mux_lib(), - g_vpr_ctx.device().grid, + g_vpr_ctx.device(), + openfpga_ctx.vpr_device_annotation(), openfpga_ctx.device_rr_gsb(), options); } diff --git a/openfpga/src/fpga_verilog/verilog_api.cpp b/openfpga/src/fpga_verilog/verilog_api.cpp index aa68e0024..ce339e813 100644 --- a/openfpga/src/fpga_verilog/verilog_api.cpp +++ b/openfpga/src/fpga_verilog/verilog_api.cpp @@ -17,7 +17,7 @@ #include "verilog_auxiliary_netlists.h" #include "verilog_submodule.h" #include "verilog_routing.h" -//#include "verilog_grid.h" +#include "verilog_grid.h" //#include "verilog_top_module.h" /* Header file for this source file */ @@ -47,7 +47,8 @@ namespace openfpga { void fpga_fabric_verilog(ModuleManager& module_manager, const CircuitLibrary& circuit_lib, const MuxLibrary& mux_lib, - const DeviceGrid& grids, + const DeviceContext& device_ctx, + const VprDeviceAnnotation& device_annotation, const DeviceRRGSB& device_rr_gsb, const FabricVerilogOption& options) { @@ -103,9 +104,11 @@ void fpga_fabric_verilog(ModuleManager& module_manager, } /* Generate grids */ - //print_verilog_grids(module_manager, - // src_dir_path, lb_dir_path, - // dump_explicit_verilog); + print_verilog_grids(const_cast(module_manager), + device_ctx, device_annotation, + src_dir_path, lb_dir_path, + options.explicit_port_mapping(), + options.verbose_output()); /* Generate FPGA fabric */ //print_verilog_top_module(module_manager, diff --git a/openfpga/src/fpga_verilog/verilog_api.h b/openfpga/src/fpga_verilog/verilog_api.h index 569650f2a..7494a5a8f 100644 --- a/openfpga/src/fpga_verilog/verilog_api.h +++ b/openfpga/src/fpga_verilog/verilog_api.h @@ -10,7 +10,8 @@ #include "vpr_types.h" #include "mux_library.h" #include "circuit_library.h" -#include "device_grid.h" +#include "vpr_context.h" +#include "vpr_device_annotation.h" #include "device_rr_gsb.h" #include "module_manager.h" #include "verilog_options.h" @@ -25,7 +26,8 @@ namespace openfpga { void fpga_fabric_verilog(ModuleManager& module_manager, const CircuitLibrary& circuit_lib, const MuxLibrary& mux_lib, - const DeviceGrid& grids, + const DeviceContext& device_ctx, + const VprDeviceAnnotation& device_annotation, const DeviceRRGSB& device_rr_gsb, const FabricVerilogOption& options); diff --git a/openfpga/src/fpga_verilog/verilog_constants.h b/openfpga/src/fpga_verilog/verilog_constants.h index d5e1f4edb..e355b7a5f 100644 --- a/openfpga/src/fpga_verilog/verilog_constants.h +++ b/openfpga/src/fpga_verilog/verilog_constants.h @@ -45,5 +45,7 @@ constexpr char* VERILOG_MUX_BASIS_POSTFIX = "_basis"; constexpr char* VERILOG_MEM_POSTFIX = "_mem"; constexpr char* SB_VERILOG_FILE_NAME_PREFIX = "sb_"; +constexpr char* LOGICAL_MODULE_VERILOG_FILE_NAME_PREFIX = "logical_tile_"; +constexpr char* GRID_VERILOG_FILE_NAME_PREFIX = "grid_"; #endif diff --git a/openfpga/src/fpga_verilog/verilog_grid.cpp b/openfpga/src/fpga_verilog/verilog_grid.cpp new file mode 100644 index 000000000..73e69ca29 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_grid.cpp @@ -0,0 +1,411 @@ +/******************************************************************** + * This file includes functions to print Verilog modules for a Grid + * (CLBs, I/Os, heterogeneous blocks etc.) + *******************************************************************/ +/* System header files */ +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_geometry.h" +#include "vtr_assert.h" +#include "vtr_log.h" + +/* Headers from readarch library */ +#include "physical_types.h" + +/* Headers from openfpgautil library */ +#include "openfpga_digest.h" +#include "openfpga_side_manager.h" + +/* Headers from vpr library */ +#include "vpr_utils.h" + +#include "openfpga_reserved_words.h" +#include "openfpga_naming.h" +#include "pb_type_utils.h" +#include "circuit_library_utils.h" +#include "module_manager_utils.h" + +#include "verilog_constants.h" +#include "verilog_writer_utils.h" +#include "verilog_module_writer.h" +#include "verilog_grid.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Print Verilog modules of a primitive node in the pb_graph_node graph + * This generic function can support all the different types of primitive nodes + * i.e., Look-Up Tables (LUTs), Flip-flops (FFs) and hard logic blocks such as adders. + * + * The Verilog module will consist of two parts: + * 1. Logic module of the primitive node + * This module performs the logic function of the block + * 2. Memory module of the primitive node + * This module stores the configuration bits for the logic module + * if the logic module is a programmable resource, such as LUT + * + * Verilog module structure: + * + * Primitive block + * +---------------------------------------+ + * | | + * | +---------+ +---------+ | + * in |----->| |--->| |<------|configuration lines + * | | Logic |... | Memory | | + * out|<-----| |--->| | | + * | +---------+ +---------+ | + * | | + * +---------------------------------------+ + * + *******************************************************************/ +static +void print_verilog_primitive_block(std::fstream& fp, + const ModuleManager& module_manager, + t_pb_graph_node* primitive_pb_graph_node, + const bool& use_explicit_mapping, + const bool& verbose) { + /* Ensure a valid file handler */ + VTR_ASSERT(true == valid_file_stream(fp)); + + /* Ensure a valid pb_graph_node */ + if (nullptr == primitive_pb_graph_node) { + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid primitive_pb_graph_node!\n"); + exit(1); + } + + /* Generate the module name for this primitive pb_graph_node*/ + std::string primitive_module_name = generate_physical_block_module_name(primitive_pb_graph_node->pb_type); + + /* Create a module of the primitive LUT and register it to module manager */ + ModuleId primitive_module = module_manager.find_module(primitive_module_name); + /* Ensure that the module has been created and thus unique! */ + VTR_ASSERT(true == module_manager.valid_module_id(primitive_module)); + + VTR_LOGV(verbose, + "Writing Verilog codes of logical tile primitive block '%s'...", + module_manager.module_name(primitive_module).c_str()); + + /* Write the verilog module */ + write_verilog_module_to_file(fp, module_manager, primitive_module, use_explicit_mapping); + + /* Add an empty line as a splitter */ + fp << std::endl; + + VTR_LOGV(verbose, "Done\n"); +} + +/******************************************************************** + * Print Verilog modules of physical blocks inside a grid (CLB, I/O. etc.) + * This function will traverse the graph of complex logic block (t_pb_graph_node) + * in a recursive way, using a Depth First Search (DFS) algorithm. + * As such, primitive physical blocks (LUTs, FFs, etc.), leaf node of the pb_graph + * will be printed out first, while the top-level will be printed out in the last + * + * Note: this function will print a unique Verilog module for each type of + * t_pb_graph_node, i.e., t_pb_type, in the graph, in order to enable highly + * hierarchical Verilog organization as well as simplify the Verilog file sizes. + * + * Note: DFS is the right way. Do NOT use BFS. + * DFS can guarantee that all the sub-modules can be registered properly + * to its parent in module manager + *******************************************************************/ +static +void rec_print_verilog_logical_tile(std::fstream& fp, + const ModuleManager& module_manager, + const VprDeviceAnnotation& device_annotation, + t_pb_graph_node* physical_pb_graph_node, + const bool& use_explicit_mapping, + const bool& verbose) { + /* Check the file handler*/ + VTR_ASSERT(true == valid_file_stream(fp)); + + /* Check cur_pb_graph_node*/ + if (nullptr == physical_pb_graph_node) { + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid physical_pb_graph_node\n"); + exit(1); + } + + /* Get the pb_type definition related to the node */ + t_pb_type* physical_pb_type = physical_pb_graph_node->pb_type; + + /* Find the mode that physical implementation of a pb_type */ + t_mode* physical_mode = device_annotation.physical_mode(physical_pb_type); + + /* For non-leaf node in the pb_type graph: + * Recursively Depth-First Generate all the child pb_type at the level + */ + if (false == is_primitive_pb_type(physical_pb_type)) { + for (int ipb = 0; ipb < physical_mode->num_pb_type_children; ++ipb) { + /* Go recursive to visit the children */ + rec_print_verilog_logical_tile(fp, + module_manager, device_annotation, + &(physical_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ipb][0]), + use_explicit_mapping, + verbose); + } + } + + /* For leaf node, a primitive Verilog module will be generated. + * Note that the primitive may be mapped to a standard cell, we force to use + * explict port mapping. This aims to avoid any port sequence issues!!! + */ + if (true == is_primitive_pb_type(physical_pb_type)) { + print_verilog_primitive_block(fp, module_manager, + physical_pb_graph_node, + true, + verbose); + /* Finish for primitive node, return */ + return; + } + + /* Generate the name of the Verilog module for this pb_type */ + std::string pb_module_name = generate_physical_block_module_name(physical_pb_type); + + /* Register the Verilog module in module manager */ + ModuleId pb_module = module_manager.find_module(pb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(pb_module)); + + VTR_LOGV(verbose, + "Writing Verilog codes of logical tile block '%s'...", + module_manager.module_name(pb_module).c_str()); + + /* Comment lines */ + print_verilog_comment(fp, std::string("----- BEGIN Physical programmable logic block Verilog module: " + std::string(physical_pb_type->name) + " -----")); + + /* Write the verilog module */ + write_verilog_module_to_file(fp, module_manager, pb_module, use_explicit_mapping); + + print_verilog_comment(fp, std::string("----- END Physical programmable logic block Verilog module: " + std::string(physical_pb_type->name) + " -----")); + + /* Add an empty line as a splitter */ + fp << std::endl; + + VTR_LOGV(verbose, "Done\n"); +} + +/***************************************************************************** + * This function will create a Verilog file and print out a Verilog netlist + * for the logical tile (pb_graph/pb_type) + *****************************************************************************/ +static +void print_verilog_logical_tile_netlist(const ModuleManager& module_manager, + std::vector& netlist_names, + const VprDeviceAnnotation& device_annotation, + const std::string& verilog_dir, + const std::string& subckt_dir, + t_pb_graph_node* pb_graph_head, + const bool& use_explicit_mapping, + const bool& verbose) { + /* Give a name to the Verilog netlist */ + /* Create the file name for Verilog */ + std::string verilog_fname(subckt_dir + + generate_logical_tile_netlist_name(std::string(LOGICAL_MODULE_VERILOG_FILE_NAME_PREFIX), pb_graph_head, std::string(VERILOG_NETLIST_FILE_POSTFIX)) + ); + + VTR_LOG("Writing Verilog netlist '%s' for logic tile '%s' ...", + verilog_fname.c_str(), pb_graph_head->pb_type->name); + VTR_LOGV(verbose, "\n"); + + /* Create the file stream */ + std::fstream fp; + fp.open(verilog_fname, std::fstream::out | std::fstream::trunc); + + check_file_stream(verilog_fname.c_str(), fp); + + print_verilog_file_header(fp, std::string("Verilog modules for logical tile: " + std::string(pb_graph_head->pb_type->name) + "]")); + + /* Print preprocessing flags */ + print_verilog_include_defines_preproc_file(fp, verilog_dir); + + /* Print Verilog modules for all the pb_types/pb_graph_nodes + * use a Depth-First Search Algorithm to print the sub-modules + * Note: DFS is the right way. Do NOT use BFS. + * DFS can guarantee that all the sub-modules can be registered properly + * to its parent in module manager + */ + /* Print Verilog modules starting from the top-level pb_type/pb_graph_node, and traverse the graph in a recursive way */ + rec_print_verilog_logical_tile(fp, module_manager, + device_annotation, + pb_graph_head, + use_explicit_mapping, + verbose); + + /* Add an empty line as a splitter */ + fp << std::endl; + + /* Close file handler */ + fp.close(); + + /* Add fname to the netlist name list */ + netlist_names.push_back(verilog_fname); + + VTR_LOG("Done\n"); + VTR_LOG("\n"); +} + +/***************************************************************************** + * This function will create a Verilog file and print out a Verilog netlist + * for a type of physical block + * + * For IO blocks: + * The param 'border_side' is required, which is specify which side of fabric + * the I/O block locates at. + *****************************************************************************/ +static +void print_verilog_physical_tile_netlist(const ModuleManager& module_manager, + std::vector& netlist_names, + const std::string& verilog_dir, + const std::string& subckt_dir, + t_physical_tile_type_ptr phy_block_type, + const e_side& border_side, + const bool& use_explicit_mapping) { + /* Check code: if this is an IO block, the border side MUST be valid */ + if (true == is_io_type(phy_block_type)) { + VTR_ASSERT(NUM_SIDES != border_side); + } + + /* Give a name to the Verilog netlist */ + /* Create the file name for Verilog */ + std::string verilog_fname(subckt_dir + + generate_grid_block_netlist_name(std::string(phy_block_type->name), + is_io_type(phy_block_type), + border_side, + std::string(VERILOG_NETLIST_FILE_POSTFIX)) + ); + + /* Echo status */ + if (true == is_io_type(phy_block_type)) { + SideManager side_manager(border_side); + VTR_LOG("Writing Verilog Netlist '%s' for physical tile '%s' at %s side ...", + verilog_fname.c_str(), phy_block_type->name, + side_manager.c_str()); + } else { + VTR_LOG("Writing Verilog Netlist '%s' for physical_tile '%s'...", + verilog_fname.c_str(), phy_block_type->name); + } + + /* Create the file stream */ + std::fstream fp; + fp.open(verilog_fname, std::fstream::out | std::fstream::trunc); + + check_file_stream(verilog_fname.c_str(), fp); + + print_verilog_file_header(fp, std::string("Verilog modules for physical tile: " + std::string(phy_block_type->name) + "]")); + + /* Print preprocessing flags */ + print_verilog_include_defines_preproc_file(fp, verilog_dir); + + /* Create a Verilog Module for the top-level physical block, and add to module manager */ + std::string grid_module_name = generate_grid_block_module_name(std::string(GRID_VERILOG_FILE_NAME_PREFIX), std::string(phy_block_type->name), is_io_type(phy_block_type), border_side); + ModuleId grid_module = module_manager.find_module(grid_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(grid_module)); + + /* Write the verilog module */ + print_verilog_comment(fp, std::string("----- BEGIN Grid Verilog module: " + module_manager.module_name(grid_module) + " -----")); + write_verilog_module_to_file(fp, module_manager, grid_module, use_explicit_mapping); + + print_verilog_comment(fp, std::string("----- END Grid Verilog module: " + module_manager.module_name(grid_module) + " -----")); + + /* Add an empty line as a splitter */ + fp << std::endl; + + /* Close file handler */ + fp.close(); + + /* Add fname to the netlist name list */ + netlist_names.push_back(verilog_fname); + + VTR_LOG("Done\n"); +} + +/***************************************************************************** + * Create logic block modules in a compact way: + * 1. Only one module for each I/O on each border side (IO_TYPE) + * 2. Only one module for each CLB (FILL_TYPE) + * 3. Only one module for each heterogeneous block + ****************************************************************************/ +void print_verilog_grids(const ModuleManager& module_manager, + const DeviceContext& device_ctx, + const VprDeviceAnnotation& device_annotation, + const std::string& verilog_dir, + const std::string& subckt_dir, + const bool& use_explicit_mapping, + const bool& verbose) { + /* Create a vector to contain all the Verilog netlist names that have been generated in this function */ + std::vector netlist_names; + + /* Enumerate the types of logical tiles, and build a module for each + * Write modules for all the pb_types/pb_graph_nodes + * use a Depth-First Search Algorithm to print the sub-modules + * Note: DFS is the right way. Do NOT use BFS. + * DFS can guarantee that all the sub-modules can be registered properly + * to its parent in module manager + */ + VTR_LOG("Writing logical tiles..."); + VTR_LOGV(verbose, "\n"); + for (const t_logical_block_type& logical_tile : device_ctx.logical_block_types) { + /* Bypass empty pb_graph */ + if (nullptr == logical_tile.pb_graph_head) { + continue; + } + print_verilog_logical_tile_netlist(module_manager, netlist_names, + device_annotation, + verilog_dir, subckt_dir, + logical_tile.pb_graph_head, + use_explicit_mapping, + verbose); + } + VTR_LOG("Writing logical tiles..."); + VTR_LOG("Done\n"); + + VTR_LOG("\n"); + + /* Enumerate the types of physical tiles + * Use the logical tile module to build the physical tiles + */ + VTR_LOG("Building physical tiles..."); + VTR_LOGV(verbose, "\n"); + for (const t_physical_tile_type& physical_tile : device_ctx.physical_tile_types) { + /* Bypass empty type or nullptr */ + if (true == is_empty_type(&physical_tile)) { + continue; + } else if (true == is_io_type(&physical_tile)) { + /* Special for I/O block, generate one module for each border side */ + for (int iside = 0; iside < NUM_SIDES; iside++) { + SideManager side_manager(iside); + print_verilog_physical_tile_netlist(module_manager, netlist_names, + verilog_dir, subckt_dir, + &physical_tile, + side_manager.get_side(), + use_explicit_mapping); + } + continue; + } else { + /* For CLB and heterogenenous blocks */ + print_verilog_physical_tile_netlist(module_manager, netlist_names, + verilog_dir, subckt_dir, + &physical_tile, + NUM_SIDES, + use_explicit_mapping); + } + } + VTR_LOG("Building physical tiles..."); + VTR_LOG("Done\n"); + VTR_LOG("\n"); + + /* Output a header file for all the logic blocks */ + std::string grid_verilog_fname(LOGIC_BLOCK_VERILOG_FILE_NAME); + VTR_LOG("Writing header file for grid Verilog modules '%s' ...", + grid_verilog_fname.c_str()); + print_verilog_netlist_include_header_file(netlist_names, + subckt_dir.c_str(), + grid_verilog_fname.c_str()); + VTR_LOG("Done\n"); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_grid.h b/openfpga/src/fpga_verilog/verilog_grid.h new file mode 100644 index 000000000..da8ea09fa --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_grid.h @@ -0,0 +1,30 @@ +#ifndef VERILOG_GRID_H +#define VERILOG_GRID_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include "vpr_context.h" +#include "module_manager.h" +#include "vpr_device_annotation.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_verilog_grids(const ModuleManager& module_manager, + const DeviceContext& device_ctx, + const VprDeviceAnnotation& device_annotation, + const std::string& verilog_dir, + const std::string& subckt_dir, + const bool& use_explicit_mapping, + const bool& verbose); + + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_verilog/verilog_routing.cpp b/openfpga/src/fpga_verilog/verilog_routing.cpp index 0d9582556..a3361fd06 100644 --- a/openfpga/src/fpga_verilog/verilog_routing.cpp +++ b/openfpga/src/fpga_verilog/verilog_routing.cpp @@ -291,6 +291,7 @@ void print_verilog_flatten_routing_modules(const ModuleManager& module_manager, subckt_dir.c_str(), ROUTING_VERILOG_FILE_NAME); VTR_LOG("Done\n"); + VTR_LOG("\n"); } @@ -350,6 +351,7 @@ void print_verilog_unique_routing_modules(const ModuleManager& module_manager, subckt_dir.c_str(), ROUTING_VERILOG_FILE_NAME); VTR_LOG("Done\n"); + VTR_LOG("\n"); } } /* end namespace openfpga */ From 11775c370b986f695d18da12cde3ae9e6f9e4b96 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 16 Feb 2020 16:18:14 -0700 Subject: [PATCH 161/645] bring FPGA top module verilog writer online. Fabric Verilog generator done --- openfpga/src/fpga_verilog/verilog_api.cpp | 11 ++- .../src/fpga_verilog/verilog_top_module.cpp | 75 +++++++++++++++++++ .../src/fpga_verilog/verilog_top_module.h | 23 ++++++ .../src/fpga_verilog/verilog_writer_utils.cpp | 5 -- 4 files changed, 103 insertions(+), 11 deletions(-) create mode 100644 openfpga/src/fpga_verilog/verilog_top_module.cpp create mode 100644 openfpga/src/fpga_verilog/verilog_top_module.h diff --git a/openfpga/src/fpga_verilog/verilog_api.cpp b/openfpga/src/fpga_verilog/verilog_api.cpp index ce339e813..645033d65 100644 --- a/openfpga/src/fpga_verilog/verilog_api.cpp +++ b/openfpga/src/fpga_verilog/verilog_api.cpp @@ -18,7 +18,7 @@ #include "verilog_submodule.h" #include "verilog_routing.h" #include "verilog_grid.h" -//#include "verilog_top_module.h" +#include "verilog_top_module.h" /* Header file for this source file */ #include "verilog_api.h" @@ -111,14 +111,13 @@ void fpga_fabric_verilog(ModuleManager& module_manager, options.verbose_output()); /* Generate FPGA fabric */ - //print_verilog_top_module(module_manager, - // std::string(vpr_setup.FileNameOpts.ArchFile), - // src_dir_path, - // dump_explicit_verilog); + print_verilog_top_module(const_cast(module_manager), + src_dir_path, + options.explicit_port_mapping()); /* Given a brief stats on how many Verilog modules have been written to files */ VTR_LOGV(options.verbose_output(), - "Outputted %lu Verilog modules in total\n", + "Written %lu Verilog modules in total\n", module_manager.num_modules()); } diff --git a/openfpga/src/fpga_verilog/verilog_top_module.cpp b/openfpga/src/fpga_verilog/verilog_top_module.cpp new file mode 100644 index 000000000..9bcfadc68 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_top_module.cpp @@ -0,0 +1,75 @@ +/******************************************************************** + * This file includes functions that are used to print the top-level + * module for the FPGA fabric in Verilog format + *******************************************************************/ +#include +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" + +/* Headers from openfpgautil library */ +#include "openfpga_digest.h" + +#include "openfpga_naming.h" + +#include "verilog_constants.h" +#include "verilog_writer_utils.h" +#include "verilog_module_writer.h" +#include "verilog_top_module.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Print the top-level module for the FPGA fabric in Verilog format + * This function will + * 1. name the top-level module + * 2. include dependent netlists + * - User defined netlists + * - Auto-generated netlists + * 3. Add the submodules to the top-level graph + * 4. Add module nets to connect datapath ports + * 5. Add module nets/submodules to connect configuration ports + *******************************************************************/ +void print_verilog_top_module(const ModuleManager& module_manager, + const std::string& verilog_dir, + const bool& use_explicit_mapping) { + /* Create a module as the top-level fabric, and add it to the module manager */ + std::string top_module_name = generate_fpga_top_module_name(); + ModuleId top_module = module_manager.find_module(top_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(top_module)); + + /* Start printing out Verilog netlists */ + /* Create the file name for Verilog netlist */ + std::string verilog_fname(verilog_dir + generate_fpga_top_netlist_name(std::string(VERILOG_NETLIST_FILE_POSTFIX))); + + VTR_LOG("Writing Verilog netlist for top-level module of FPGA fabric '%s'...", + verilog_fname.c_str()); + + /* Create the file stream */ + std::fstream fp; + fp.open(verilog_fname, std::fstream::out | std::fstream::trunc); + + check_file_stream(verilog_fname.c_str(), fp); + + print_verilog_file_header(fp, std::string("Top-level Verilog module for FPGA")); + + /* Print preprocessing flags */ + print_verilog_include_defines_preproc_file(fp, verilog_dir); + + /* Write the module content in Verilog format */ + write_verilog_module_to_file(fp, module_manager, top_module, use_explicit_mapping); + + /* Add an empty line as a splitter */ + fp << std::endl; + + /* Close file handler */ + fp.close(); + + VTR_LOG("Done\n"); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_top_module.h b/openfpga/src/fpga_verilog/verilog_top_module.h new file mode 100644 index 000000000..2fb451e16 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_top_module.h @@ -0,0 +1,23 @@ +#ifndef VERILOG_TOP_MODULE_H +#define VERILOG_TOP_MODULE_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include "module_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_verilog_top_module(const ModuleManager& module_manager, + const std::string& verilog_dir, + const bool& use_explicit_mapping); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_verilog/verilog_writer_utils.cpp b/openfpga/src/fpga_verilog/verilog_writer_utils.cpp index 600357c36..95f34ab1f 100644 --- a/openfpga/src/fpga_verilog/verilog_writer_utils.cpp +++ b/openfpga/src/fpga_verilog/verilog_writer_utils.cpp @@ -1377,9 +1377,6 @@ void print_verilog_netlist_include_header_file(const std::vector& n std::string verilog_fname(std::string(subckt_dir) + std::string(header_file_name)); - VTR_LOG("Writing header file for primitive modules '%s' ...", - verilog_fname.c_str()); - /* Create the file stream */ std::fstream fp; fp.open(verilog_fname, std::fstream::out | std::fstream::trunc); @@ -1396,8 +1393,6 @@ void print_verilog_netlist_include_header_file(const std::vector& n /* close file stream */ fp.close(); - - VTR_LOG("Done\n"); } } /* end namespace openfpga */ From 60f40a965783f2846fd1bb168c8a46522688f788 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 16 Feb 2020 16:35:26 -0700 Subject: [PATCH 162/645] use constant module manager as much as possible in Verilog writer --- .../src/fpga_verilog/verilog_decoders.cpp | 4 ++-- openfpga/src/fpga_verilog/verilog_decoders.h | 2 +- .../fpga_verilog/verilog_essential_gates.cpp | 8 ++++---- .../fpga_verilog/verilog_essential_gates.h | 2 +- openfpga/src/fpga_verilog/verilog_lut.cpp | 2 +- openfpga/src/fpga_verilog/verilog_lut.h | 2 +- openfpga/src/fpga_verilog/verilog_memory.cpp | 4 ++-- openfpga/src/fpga_verilog/verilog_memory.h | 2 +- .../src/fpga_verilog/verilog_submodule.cpp | 19 ++++++++++++------- openfpga/src/fpga_verilog/verilog_wire.cpp | 4 ++-- openfpga/src/fpga_verilog/verilog_wire.h | 2 +- 11 files changed, 28 insertions(+), 23 deletions(-) diff --git a/openfpga/src/fpga_verilog/verilog_decoders.cpp b/openfpga/src/fpga_verilog/verilog_decoders.cpp index 7689d3ed6..e9b9f6d02 100644 --- a/openfpga/src/fpga_verilog/verilog_decoders.cpp +++ b/openfpga/src/fpga_verilog/verilog_decoders.cpp @@ -44,7 +44,7 @@ namespace openfpga { ***************************************************************************************/ static void print_verilog_mux_local_decoder_module(std::fstream& fp, - ModuleManager& module_manager, + const ModuleManager& module_manager, const DecoderLibrary& decoder_lib, const DecoderId& decoder) { /* Get the number of inputs */ @@ -161,7 +161,7 @@ void print_verilog_mux_local_decoder_module(std::fstream& fp, * before running the back-end flow for FPGA fabric * See more details in the function print_verilog_mux_local_decoder() for more details ***************************************************************************************/ -void print_verilog_submodule_mux_local_decoders(ModuleManager& module_manager, +void print_verilog_submodule_mux_local_decoders(const ModuleManager& module_manager, std::vector& netlist_names, const MuxLibrary& mux_lib, const CircuitLibrary& circuit_lib, diff --git a/openfpga/src/fpga_verilog/verilog_decoders.h b/openfpga/src/fpga_verilog/verilog_decoders.h index 41f932932..271e7aa5c 100644 --- a/openfpga/src/fpga_verilog/verilog_decoders.h +++ b/openfpga/src/fpga_verilog/verilog_decoders.h @@ -20,7 +20,7 @@ /* begin namespace openfpga */ namespace openfpga { -void print_verilog_submodule_mux_local_decoders(ModuleManager& module_manager, +void print_verilog_submodule_mux_local_decoders(const ModuleManager& module_manager, std::vector& netlist_names, const MuxLibrary& mux_lib, const CircuitLibrary& circuit_lib, diff --git a/openfpga/src/fpga_verilog/verilog_essential_gates.cpp b/openfpga/src/fpga_verilog/verilog_essential_gates.cpp index c18bece61..a84d02bdf 100644 --- a/openfpga/src/fpga_verilog/verilog_essential_gates.cpp +++ b/openfpga/src/fpga_verilog/verilog_essential_gates.cpp @@ -142,7 +142,7 @@ void print_verilog_invbuf_body(std::fstream& fp, * or tapered buffer to a file ***********************************************/ static -void print_verilog_invbuf_module(ModuleManager& module_manager, +void print_verilog_invbuf_module(const ModuleManager& module_manager, std::fstream& fp, const CircuitLibrary& circuit_lib, const CircuitModelId& circuit_model) { @@ -226,7 +226,7 @@ void print_verilog_invbuf_module(ModuleManager& module_manager, * either transmission-gate or pass-transistor ***********************************************/ static -void print_verilog_passgate_module(ModuleManager& module_manager, +void print_verilog_passgate_module(const ModuleManager& module_manager, std::fstream& fp, const CircuitLibrary& circuit_lib, const CircuitModelId& circuit_model) { @@ -438,7 +438,7 @@ void print_verilog_mux2_gate_body(std::fstream& fp, * 3. 2-input MUX ***********************************************/ static -void print_verilog_gate_module(ModuleManager& module_manager, +void print_verilog_gate_module(const ModuleManager& module_manager, std::fstream& fp, const CircuitLibrary& circuit_lib, const CircuitModelId& circuit_model) { @@ -525,7 +525,7 @@ void print_verilog_constant_generator_module(const ModuleManager& module_manager * include inverters, buffers, transmission-gates, * etc. ***********************************************/ -void print_verilog_submodule_essentials(ModuleManager& module_manager, +void print_verilog_submodule_essentials(const ModuleManager& module_manager, std::vector& netlist_names, const std::string& verilog_dir, const std::string& submodule_dir, diff --git a/openfpga/src/fpga_verilog/verilog_essential_gates.h b/openfpga/src/fpga_verilog/verilog_essential_gates.h index 33ce49c6c..b7f9b519a 100644 --- a/openfpga/src/fpga_verilog/verilog_essential_gates.h +++ b/openfpga/src/fpga_verilog/verilog_essential_gates.h @@ -14,7 +14,7 @@ /* begin namespace openfpga */ namespace openfpga { -void print_verilog_submodule_essentials(ModuleManager& module_manager, +void print_verilog_submodule_essentials(const ModuleManager& module_manager, std::vector& netlist_names, const std::string& verilog_dir, const std::string& submodule_dir, diff --git a/openfpga/src/fpga_verilog/verilog_lut.cpp b/openfpga/src/fpga_verilog/verilog_lut.cpp index 900ef3073..a849c5827 100644 --- a/openfpga/src/fpga_verilog/verilog_lut.cpp +++ b/openfpga/src/fpga_verilog/verilog_lut.cpp @@ -29,7 +29,7 @@ namespace openfpga { * Print Verilog modules for the Look-Up Tables (LUTs) * in the circuit library ********************************************************************/ -void print_verilog_submodule_luts(ModuleManager& module_manager, +void print_verilog_submodule_luts(const ModuleManager& module_manager, std::vector& netlist_names, const CircuitLibrary& circuit_lib, const std::string& verilog_dir, diff --git a/openfpga/src/fpga_verilog/verilog_lut.h b/openfpga/src/fpga_verilog/verilog_lut.h index f5d1345dc..7c8b85b79 100644 --- a/openfpga/src/fpga_verilog/verilog_lut.h +++ b/openfpga/src/fpga_verilog/verilog_lut.h @@ -17,7 +17,7 @@ /* begin namespace openfpga */ namespace openfpga { -void print_verilog_submodule_luts(ModuleManager& module_manager, +void print_verilog_submodule_luts(const ModuleManager& module_manager, std::vector& netlist_names, const CircuitLibrary& circuit_lib, const std::string& verilog_dir, diff --git a/openfpga/src/fpga_verilog/verilog_memory.cpp b/openfpga/src/fpga_verilog/verilog_memory.cpp index 743647d44..de0418481 100644 --- a/openfpga/src/fpga_verilog/verilog_memory.cpp +++ b/openfpga/src/fpga_verilog/verilog_memory.cpp @@ -42,7 +42,7 @@ namespace openfpga { * +---------------------+ ********************************************************************/ static -void print_verilog_mux_memory_module(ModuleManager& module_manager, +void print_verilog_mux_memory_module(const ModuleManager& module_manager, const CircuitLibrary& circuit_lib, std::fstream& fp, const CircuitModelId& mux_model, @@ -96,7 +96,7 @@ void print_verilog_mux_memory_module(ModuleManager& module_manager, * Take another example, the memory circuit can implement the scan-chain or * memory-bank organization for the memories. ********************************************************************/ -void print_verilog_submodule_memories(ModuleManager& module_manager, +void print_verilog_submodule_memories(const ModuleManager& module_manager, std::vector& netlist_names, const MuxLibrary& mux_lib, const CircuitLibrary& circuit_lib, diff --git a/openfpga/src/fpga_verilog/verilog_memory.h b/openfpga/src/fpga_verilog/verilog_memory.h index 774e6feb9..9d29eb15f 100644 --- a/openfpga/src/fpga_verilog/verilog_memory.h +++ b/openfpga/src/fpga_verilog/verilog_memory.h @@ -18,7 +18,7 @@ /* begin namespace openfpga */ namespace openfpga { -void print_verilog_submodule_memories(ModuleManager& module_manager, +void print_verilog_submodule_memories(const ModuleManager& module_manager, std::vector& netlist_names, const MuxLibrary& mux_lib, const CircuitLibrary& circuit_lib, diff --git a/openfpga/src/fpga_verilog/verilog_submodule.cpp b/openfpga/src/fpga_verilog/verilog_submodule.cpp index c52fc2262..099118e5f 100644 --- a/openfpga/src/fpga_verilog/verilog_submodule.cpp +++ b/openfpga/src/fpga_verilog/verilog_submodule.cpp @@ -42,13 +42,13 @@ void print_verilog_submodule(ModuleManager& module_manager, * This should be done prior to other steps in this function, * because they will be instanciated by other primitive modules */ - add_user_defined_verilog_modules(module_manager, circuit_lib); + //add_user_defined_verilog_modules(module_manager, circuit_lib); /* Create a vector to contain all the Verilog netlist names that have been generated in this function */ std::vector netlist_names; - print_verilog_submodule_essentials(module_manager, + print_verilog_submodule_essentials(const_cast(module_manager), netlist_names, verilog_dir, submodule_dir, @@ -58,7 +58,8 @@ void print_verilog_submodule(ModuleManager& module_manager, /* NOTE: local decoders generation must go before the MUX generation!!! * because local decoders modules will be instanciated in the MUX modules */ - print_verilog_submodule_mux_local_decoders(module_manager, netlist_names, + print_verilog_submodule_mux_local_decoders(const_cast(module_manager), + netlist_names, mux_lib, circuit_lib, verilog_dir, submodule_dir); print_verilog_submodule_muxes(module_manager, netlist_names, mux_lib, circuit_lib, @@ -67,23 +68,27 @@ void print_verilog_submodule(ModuleManager& module_manager, /* LUTes */ - print_verilog_submodule_luts(module_manager, netlist_names, circuit_lib, + print_verilog_submodule_luts(const_cast(module_manager), + netlist_names, circuit_lib, verilog_dir, submodule_dir, fpga_verilog_opts.explicit_port_mapping()); /* Hard wires */ - print_verilog_submodule_wires(module_manager, netlist_names, circuit_lib, + print_verilog_submodule_wires(const_cast(module_manager), + netlist_names, circuit_lib, verilog_dir, submodule_dir); /* 4. Memories */ - print_verilog_submodule_memories(module_manager, netlist_names, + print_verilog_submodule_memories(const_cast(module_manager), + netlist_names, mux_lib, circuit_lib, verilog_dir, submodule_dir, fpga_verilog_opts.explicit_port_mapping()); /* 5. Dump template for all the modules */ if (true == fpga_verilog_opts.print_user_defined_template()) { - print_verilog_submodule_templates(module_manager, circuit_lib, + print_verilog_submodule_templates(const_cast(module_manager), + circuit_lib, verilog_dir, submodule_dir); } diff --git a/openfpga/src/fpga_verilog/verilog_wire.cpp b/openfpga/src/fpga_verilog/verilog_wire.cpp index 2ac9e7ffa..4f084fad9 100644 --- a/openfpga/src/fpga_verilog/verilog_wire.cpp +++ b/openfpga/src/fpga_verilog/verilog_wire.cpp @@ -35,7 +35,7 @@ namespace openfpga { * *******************************************************************/ static -void print_verilog_wire_module(ModuleManager& module_manager, +void print_verilog_wire_module(const ModuleManager& module_manager, const CircuitLibrary& circuit_lib, std::fstream& fp, const CircuitModelId& wire_model) { @@ -92,7 +92,7 @@ void print_verilog_wire_module(ModuleManager& module_manager, /******************************************************************** * Top-level function to print wire modules *******************************************************************/ -void print_verilog_submodule_wires(ModuleManager& module_manager, +void print_verilog_submodule_wires(const ModuleManager& module_manager, std::vector& netlist_names, const CircuitLibrary& circuit_lib, const std::string& verilog_dir, diff --git a/openfpga/src/fpga_verilog/verilog_wire.h b/openfpga/src/fpga_verilog/verilog_wire.h index 7d22ae4c8..55c39fb30 100644 --- a/openfpga/src/fpga_verilog/verilog_wire.h +++ b/openfpga/src/fpga_verilog/verilog_wire.h @@ -17,7 +17,7 @@ /* begin namespace openfpga */ namespace openfpga { -void print_verilog_submodule_wires(ModuleManager& module_manager, +void print_verilog_submodule_wires(const ModuleManager& module_manager, std::vector& netlist_names, const CircuitLibrary& circuit_lib, const std::string& verilog_dir, From 92076c1460bd96442f5013f3ae7f665e4921df2b Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 17 Feb 2020 16:59:24 -0700 Subject: [PATCH 163/645] refactored lb_rr_graph in the same principle of RRGraph object --- openfpga/src/repack/lb_rr_graph.cpp | 215 +++++++++++++++++++ openfpga/src/repack/lb_rr_graph.h | 296 ++++++++++++++++++++++++++ openfpga/src/repack/lb_rr_graph_fwd.h | 19 ++ vpr/src/pack/pack_types.h | 12 +- 4 files changed, 532 insertions(+), 10 deletions(-) create mode 100644 openfpga/src/repack/lb_rr_graph.cpp create mode 100644 openfpga/src/repack/lb_rr_graph.h create mode 100644 openfpga/src/repack/lb_rr_graph_fwd.h diff --git a/openfpga/src/repack/lb_rr_graph.cpp b/openfpga/src/repack/lb_rr_graph.cpp new file mode 100644 index 000000000..99b0ecd1b --- /dev/null +++ b/openfpga/src/repack/lb_rr_graph.cpp @@ -0,0 +1,215 @@ +/************************************************************************ + * Member Functions of LbRRGraph + * include mutators, accessors and utility functions + ***********************************************************************/ +#include "vtr_assert.h" +#include "vtr_log.h" +#include "lb_rr_graph.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/************************************************** + * Public Accessors: Aggregates + *************************************************/ +LbRRGraph::node_range LbRRGraph::nodes() const { + return vtr::make_range(node_ids_.begin(), node_ids_.end()); +} + +LbRRGraph::edge_range LbRRGraph::edges() const { + return vtr::make_range(edge_ids_.begin(), edge_ids_.end()); +} + +/************************************************** + * Public Accessors node-level attributes + *************************************************/ +e_lb_rr_type LbRRGraph::node_type(const LbRRNodeId& node) const { + VTR_ASSERT(true == valid_node_id(node)); + return node_types_[node]; +} + +short LbRRGraph::node_capacity(const LbRRNodeId& node) const { + VTR_ASSERT(true == valid_node_id(node)); + return node_capacities_[node]; +} + +t_pb_graph_pin* LbRRGraph::node_pb_graph_pin(const LbRRNodeId& node) const { + VTR_ASSERT(true == valid_node_id(node)); + return node_pb_graph_pins_[node]; +} + +float LbRRGraph::node_intrinsic_cost(const LbRRNodeId& node) const { + VTR_ASSERT(true == valid_node_id(node)); + return node_intrinsic_costs_[node]; +} + +std::vector LbRRGraph::node_in_edges(const LbRRNodeId& node, t_mode* mode) const { + std::vector in_edges; + + VTR_ASSERT(true == valid_node_id(node)); + for (const LbRREdgeId& edge : node_in_edges_[node]) { + if (mode == edge_mode(edge)) { + in_edges.push_back(edge); + } + } + + return in_edges; +} + +std::vector LbRRGraph::node_out_edges(const LbRRNodeId& node, t_mode* mode) const { + std::vector out_edges; + + VTR_ASSERT(true == valid_node_id(node)); + for (const LbRREdgeId& edge : node_out_edges_[node]) { + if (mode == edge_mode(edge)) { + out_edges.push_back(edge); + } + } + + return out_edges; +} + +LbRRNodeId LbRRGraph::find_node(const e_lb_rr_type& type, t_pb_graph_pin* pb_graph_pin) const { + if (size_t(type) >= node_lookup_.size()) { + return LbRRNodeId::INVALID(); + } + + if (0 == node_lookup_[size_t(type)].count(pb_graph_pin)) { + return LbRRNodeId::INVALID(); + } + + return node_lookup_[size_t(type)].at(pb_graph_pin); +} + +LbRRNodeId LbRRGraph::edge_src_node(const LbRREdgeId& edge) const { + VTR_ASSERT(true == valid_edge_id(edge)); + return edge_src_nodes_[edge]; +} + +LbRRNodeId LbRRGraph::edge_sink_node(const LbRREdgeId& edge) const { + VTR_ASSERT(true == valid_edge_id(edge)); + return edge_sink_nodes_[edge]; +} + +float LbRRGraph::edge_intrinsic_cost(const LbRREdgeId& edge) const { + VTR_ASSERT(true == valid_edge_id(edge)); + return edge_intrinsic_costs_[edge]; +} + +t_mode* LbRRGraph::edge_mode(const LbRREdgeId& edge) const { + VTR_ASSERT(true == valid_edge_id(edge)); + return edge_modes_[edge]; +} + +/****************************************************************************** + * Public Mutators + ******************************************************************************/ +void LbRRGraph::reserve_nodes(const unsigned long& num_nodes) { + node_ids_.reserve(num_nodes); + node_types_.reserve(num_nodes); + node_capacities_.reserve(num_nodes); + node_pb_graph_pins_.reserve(num_nodes); + node_intrinsic_costs_.reserve(num_nodes); + + node_in_edges_.reserve(num_nodes); + node_out_edges_.reserve(num_nodes); +} + +void LbRRGraph::reserve_edges(const unsigned long& num_edges) { + edge_ids_.reserve(num_edges); + edge_intrinsic_costs_.reserve(num_edges); + edge_modes_.reserve(num_edges); + + edge_src_nodes_.reserve(num_edges); + edge_sink_nodes_.reserve(num_edges); +} + +LbRRNodeId LbRRGraph::create_node(const e_lb_rr_type& type) { + /* Create an new id */ + LbRRNodeId node = LbRRNodeId(node_ids_.size()); + node_ids_.push_back(node); + + /* Allocate other attributes */ + node_types_.push_back(type); + node_capacities_.push_back(-1); + node_pb_graph_pins_.push_back(nullptr); + node_intrinsic_costs_.push_back(0.); + + node_in_edges_.emplace_back(); + node_out_edges_.emplace_back(); + + return node; +} + +void LbRRGraph::set_node_type(const LbRRNodeId& node, const e_lb_rr_type& type) { + VTR_ASSERT(true == valid_node_id(node)); + node_types_[node] = type; +} + +void LbRRGraph::set_node_capacity(const LbRRNodeId& node, const short& capacity) { + VTR_ASSERT(true == valid_node_id(node)); + node_capacities_[node] = capacity; +} + +void LbRRGraph::set_node_pb_graph_pin(const LbRRNodeId& node, t_pb_graph_pin* pb_graph_pin) { + VTR_ASSERT(true == valid_node_id(node)); + node_pb_graph_pins_[node] = pb_graph_pin; + + /* Register in fast node look-up */ + if (node_type(node) >= node_lookup_.size()) { + node_lookup_.resize(node_type(node) + 1); + } + + if (0 < node_lookup_[node_type(node)].count(pb_graph_pin)) { + VTR_LOG_WARN("Detect pb_graph_pin '%s[%lu]' is mapped to LbRRGraph nodes (exist: %lu) and (to be mapped: %lu). Overwrite is done\n", + pb_graph_pin->port->name, pb_graph_pin->pin_number, + size_t(node_lookup_[node_type(node)].at(pb_graph_pin)), + size_t(node)); + } + node_lookup_[node_type(node)][pb_graph_pin] = node; +} + +void LbRRGraph::set_node_intrinsic_cost(const LbRRNodeId& node, const float& cost) { + VTR_ASSERT(true == valid_node_id(node)); + node_intrinsic_costs_[node] = cost; +} + +LbRREdgeId LbRRGraph::create_edge(const LbRRNodeId& source, + const LbRRNodeId& sink, + t_mode* mode) { + VTR_ASSERT(true == valid_node_id(source)); + VTR_ASSERT(true == valid_node_id(sink)); + + /* Create an new id */ + LbRREdgeId edge = LbRREdgeId(edge_ids_.size()); + edge_ids_.push_back(edge); + + /* Allocate other attributes */ + edge_src_nodes_.push_back(source); + edge_sink_nodes_.push_back(sink); + edge_intrinsic_costs_.push_back(0.); + edge_modes_.push_back(mode); + + node_out_edges_[source].push_back(edge); + node_in_edges_[sink].push_back(edge); + + return edge; +} + +void LbRRGraph::set_edge_intrinsic_cost(const LbRREdgeId& edge, const float& cost) { + VTR_ASSERT(true == valid_edge_id(edge)); + edge_intrinsic_costs_[edge] = cost; +} + +/****************************************************************************** + * Public validators/invalidators + ******************************************************************************/ +bool LbRRGraph::valid_node_id(const LbRRNodeId& node_id) const { + return ( size_t(node_id) < node_ids_.size() ) && ( node_id == node_ids_[node_id] ); +} + +bool LbRRGraph::valid_edge_id(const LbRREdgeId& edge_id) const { + return ( size_t(edge_id) < edge_ids_.size() ) && ( edge_id == edge_ids_[edge_id] ); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/repack/lb_rr_graph.h b/openfpga/src/repack/lb_rr_graph.h new file mode 100644 index 000000000..c6cfbd552 --- /dev/null +++ b/openfpga/src/repack/lb_rr_graph.h @@ -0,0 +1,296 @@ +/************************************************************************ + * This file introduces a class to model a Routing Resource Graph (RRGraph or RRG) + * which is used by packer. + * + * Overview + * ======== + * RRGraph aims to describe in a general way how routing resources are connected + * inside a pb_graph + * + * A Routing Resource Graph (RRGraph or RRG) is a directed graph (has many cycles), + * which consists of a number of nodes and edges. + * + * Node + * ---- + * Each node represents a routing resource, which could be + * 1. an intermediate node(INTERMEDIATE_NODE), which are input/output pins of non-primitive and non-root pb_graph_nodes. It represents a pb_graph_pin that exists in a pb_graph + * 2. a virtual source (SOURCE), which are inputs of root pb_graph_nodes or outputs of primitive pb_graph_node + * 3. a sink node (SINK), which are outputs of root pb_graph_nodes or inputs or primitive pb_graph_node + * + * Edge + * ---- + * Each edge represents a connection between two pb_graph_pins + * It represents a pb_graph_edge in a pb_graph + * + * Guidlines on using the LbRRGraph data structure + * ============================================= + * + * For those want to access data from RRGraph + * ------------------------------------------ + * Some examples for most frequent data query: + * + * // Strongly suggest to use a read-only lb_rr_graph object + * const LbRRGraph& lb_rr_graph; + * + * // Access type of a node with a given node id + * // Get the unique node id that you may get + * // from other data structures or functions + * LbRRNodeId node_id; + * e_lb_rr_type node_type = lb_rr_graph.node_type(node_id); + * + * // Access all the fan-out edges from a given node + * for (const RREdgeId& out_edge_id : rr_graph.node_out_edges(node_id)) { + * // Do something with out_edge + * } + * // If you only want to learn the number of fan-out edges + * size_t num_out_edges = rr_graph.node_fan_out(node_id); + * + * Please refer to the detailed comments on each public accessors + * + * For those want to build/modify a LbRRGraph + * ----------------------------------------- + * Do NOT add a builder to this data structure! + * Builders should be kept as free functions that use the public mutators + * We suggest developers to create builders in separated C/C++ source files + * outside the rr_graph header and source files + * + * After build/modify a RRGraph, please do run a fundamental check, a public accessor. + * to ensure that your RRGraph does not include invalid nodes/edges/switches/segements + * as well as connections. + * The validate() function gurantees the consistency between internal data structures, + * such as the id cross-reference between nodes and edges etc., + * so failing it indicates a fatal bug! + * This is a must-do check! + * + * Example: + * RRGraph lb_rr_graph; + * ... // Building RRGraph + * lb_rr_graph.validate(); + * + * Optionally, we strongly recommend developers to run an advance check in check_rr_graph() + * This guarantees legal and routable RRGraph for VPR routers. + * + * This checks for connectivity or other information in the RRGraph that is unexpected + * or unusual in an FPGA, and likely indicates a problem in your graph generation. + * However, if you are intentionally creating an RRGraph with this unusual, + * buts still technically legal, behaviour, you can write your own check_rr_graph() with weaker assumptions. + * + * Note: Do NOT modify the coordinate system for nodes, they are designed for downstream drawers and routers + * + * For those want to extend RRGraph data structure + * -------------------------------------------------------------------------- + * Please avoid modifying any existing public/private accessors/mutators + * in order to keep a stable RRGraph object in the framework + * Developers may add more internal data to RRGraph as well as associate accessors/mutators + * Please update and comment on the added features properly to keep this data structure friendly to be extended. + * + * Try to keep your extension within only graph-related internal data to RRGraph. + * In other words, extension is necessary when the new node/edge attributes are needed. + * RRGraph should NOT include other data which are shared by other data structures outside. + * The rr-graph is the single largest data structure in VPR, + * so avoid adding unnecessary information per node or per edge to it, as it will impact memory footprint. + * Instead, using indices to point to the outside data source instead of embedding to RRGraph + * For example: + * For any placement/routing cost related information, try to extend t_rr_indexed_data, but not RRGraph + * For any placement/routing results, try to extend PlaceContext and RoutingContext, but not RRGraph + * + * For those want to develop placers or routers + * -------------------------------------------------------------------------- + * The RRGraph is designed to be a read-only database/graph, once created. + * Placement and routing should NOT change any attributes of RRGraph. + * Any placement and routing results should be stored in other data structures, such as PlaceContext and RoutingContext. + * + * Tracing Cross-Reference + * ======================= + * RRGraph is designed to a self-contained data structure as much as possible. + * It includes the switch information (rr_switch) and segment_information (rr_segment) + * which are necessary to build-up any external data structures. + * + * Internal cross-reference + * ------------------------ + * + * +--------+ +--------+ + * | | node_in_edges | | + * | | node_out_edges | | + * | Node |----------------->| Edge | + * | |<-----------------| | + * | | edge_src_node | | + * +--------+ edge_sink_node +--------+ + * + * + * External cross-reference + * ------------------------ + * The only cross-reference to outside data structures is the cost_index + * corresponding to the data structure t_rr_index_data + * Details can be found in the definition of t_rr_index_data + * This allows rapid look up by the router of additional information it needs for this node, using a flyweight pattern. + * + * +---------+ pb_graph_pin +----------------+ + * | RRGraph |---------------->| Pb_graph_node | + * +---------+ pb_graph_edge +----------------+ + * + ***********************************************************************/ +#ifndef LB_RR_GRAPH_OBJ_H +#define LB_RR_GRAPH_OBJ_H + +/* + * Notes in include header files in a head file + * Only include the neccessary header files + * that is required by the data types in the function/class declarations! + */ +/* Header files should be included in a sequence */ +/* Standard header files required go first */ +#include +#include + +/* Header from vtrutil library */ +#include "vtr_range.h" +#include "vtr_vector.h" + +/* Header from readarch library */ +#include "physical_types.h" + +/* Header from vpr library */ +#include "lb_rr_graph_types.h" + +#include "lb_rr_graph_fwd.h" + +/* begin namespace openfpga */ +namespace openfpga { + +class LbRRGraph { + public: /* Types */ + /* Iterators used to create iterator-based loop for nodes/edges/switches/segments */ + typedef vtr::vector::const_iterator node_iterator; + typedef vtr::vector::const_iterator edge_iterator; + + /* Ranges used to create range-based loop for nodes/edges/switches/segments */ + typedef vtr::Range node_range; + typedef vtr::Range edge_range; + + public: /* Accessors */ + /* Aggregates: create range-based loops for nodes/edges/switches/segments + * To iterate over the nodes/edges/switches/segments in a RRGraph, + * using a range-based loop is suggested. + * ----------------------------------------------------------------- + * Example: iterate over all the nodes + * // Strongly suggest to use a read-only rr_graph object + * const LbRRGraph& lb_rr_graph; + * for (const LbRRNodeId& node : lb_rr_graph.nodes()) { + * // Do something with each node + * } + * + * for (const LbRREdgeId& edge : lb_rr_graph.edges()) { + * // Do something with each edge + * } + * + */ + node_range nodes() const; + edge_range edges() const; + + /* Node-level attributes */ + e_lb_rr_type node_type(const LbRRNodeId& node) const; + short node_capacity(const LbRRNodeId& node) const; + t_pb_graph_pin* node_pb_graph_pin(const LbRRNodeId& node) const; + float node_intrinsic_cost(const LbRRNodeId& node) const; + + /* Get a list of edge ids, which are incoming edges to a node */ + std::vector node_in_edges(const LbRRNodeId& node, t_mode* mode) const; + + /* Get a list of edge ids, which are outgoing edges from a node */ + std::vector node_out_edges(const LbRRNodeId& node, t_mode* mode) const; + + LbRRNodeId find_node(const e_lb_rr_type& type, t_pb_graph_pin* pb_graph_pin) const; + + /* Get the source node which drives a edge */ + LbRRNodeId edge_src_node(const LbRREdgeId& edge) const; + /* Get the sink node which a edge ends to */ + LbRRNodeId edge_sink_node(const LbRREdgeId& edge) const; + + float edge_intrinsic_cost(const LbRREdgeId& edge) const; + t_mode* edge_mode(const LbRREdgeId& edge) const; + + public: /* Mutators */ + /* Reserve the lists of nodes, edges, switches etc. to be memory efficient. + * This function is mainly used to reserve memory space inside RRGraph, + * when adding a large number of nodes/edge/switches/segments, + * in order to avoid memory fragements + * For example: + * LbRRGraph lb_rr_graph; + * // Add 1000 CHANX nodes to the LbRRGraph object + * rr_graph.reserve_nodes(1000); + * for (size_t i = 0; i < 1000; ++i) { + * rr_graph.create_node(CHANX); + * } + */ + void reserve_nodes(const unsigned long& num_nodes); + void reserve_edges(const unsigned long& num_edges); + + /* Add new elements (node, edge, switch, etc.) to RRGraph */ + /* Add a node to the RRGraph with a deposited type + * Detailed node-level information should be added using the set_node_* functions + * For example: + * RRNodeId node = create_node(); + * set_node_xlow(node, 0); + */ + LbRRNodeId create_node(const e_lb_rr_type& type); + + /* Set node-level information */ + void set_node_type(const LbRRNodeId& node, const e_lb_rr_type& type); + + void set_node_capacity(const LbRRNodeId& node, const short& capacity); + + void set_node_pb_graph_pin(const LbRRNodeId& node, t_pb_graph_pin* pb_graph_pin); + + void set_node_intrinsic_cost(const LbRRNodeId& node, const float& cost); + + /* Add a edge to the RRGraph, by providing the source and sink node + * This function will automatically create a node and + * configure the nodes and edges in connection + */ + LbRREdgeId create_edge(const LbRRNodeId& source, const LbRRNodeId& sink, t_mode* mode); + void set_edge_intrinsic_cost(const LbRREdgeId& edge, const float& cost); + + public: /* Validators */ + /* Validate is the node id does exist in the RRGraph */ + bool valid_node_id(const LbRRNodeId& node) const; + + /* Validate is the edge id does exist in the RRGraph */ + bool valid_edge_id(const LbRREdgeId& edge) const; + + private: /* Internal Data */ + /* Node related data */ + vtr::vector node_ids_; + + vtr::vector node_types_; + + vtr::vector node_capacities_; + + vtr::vector node_pb_graph_pins_; + + vtr::vector node_intrinsic_costs_; + + /* Edges per node is sorted by modes: [][] */ + vtr::vector> node_in_edges_; + vtr::vector> node_out_edges_; + + /* Edge related data */ + /* Range of edge ids, use the unsigned long as + * the number of edges could be >10 times larger than the number of nodes! + */ + vtr::vector edge_ids_; + vtr::vector edge_src_nodes_; + vtr::vector edge_sink_nodes_; + vtr::vector edge_intrinsic_costs_; + vtr::vector edge_modes_; + + /* Fast look-up to search a node by its type, coordinator and ptc_num + * Indexing of fast look-up: [0..NUM_TYPES-1][t_pb_graph_pin*] + */ + typedef std::vector> NodeLookup; + mutable NodeLookup node_lookup_; +}; + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/repack/lb_rr_graph_fwd.h b/openfpga/src/repack/lb_rr_graph_fwd.h new file mode 100644 index 000000000..22363628b --- /dev/null +++ b/openfpga/src/repack/lb_rr_graph_fwd.h @@ -0,0 +1,19 @@ +#ifndef LB_RR_GRAPH_OBJ_FWD_H +#define LB_RR_GRAPH_OBJ_FWD_H +#include "vtr_strong_id.h" + +/*************************************************************** + * This file includes a light declaration for the class LbRRGraph + * For a detailed description and how to use the class LbRRGraph, + * please refer to lb_rr_graph_obj.h + ***************************************************************/ + +class LbRRGraph; + +struct lb_rr_node_id_tag; +struct lb_rr_edge_id_tag; + +typedef vtr::StrongId LbRRNodeId; +typedef vtr::StrongId LbRREdgeId; + +#endif diff --git a/vpr/src/pack/pack_types.h b/vpr/src/pack/pack_types.h index c8d266b57..794173217 100644 --- a/vpr/src/pack/pack_types.h +++ b/vpr/src/pack/pack_types.h @@ -13,20 +13,12 @@ #include "arch_types.h" #include "atom_netlist_fwd.h" +#include "lb_rr_graph_types.h" + /************************************************************************** * Packing Algorithm Enumerations ***************************************************************************/ -/* Describes different types of intra-logic cluster_ctx.blocks routing resource nodes */ -enum e_lb_rr_type { - LB_SOURCE = 0, - LB_SINK, - LB_INTERMEDIATE, - NUM_LB_RR_TYPES -}; -const std::vector lb_rr_type_str{ - "LB_SOURCE", "LB_SINK", "LB_INTERMEDIATE", "INVALID"}; - /************************************************************************** * Packing Algorithm Data Structures ***************************************************************************/ From 6c69b52ded70c75e8ad929e31b12fab0b97df025 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 17 Feb 2020 17:11:29 -0700 Subject: [PATCH 164/645] Add missing file --- openfpga/src/base/openfpga_context.h | 1 + vpr/src/pack/lb_rr_graph_types.h | 14 ++++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 vpr/src/pack/lb_rr_graph_types.h diff --git a/openfpga/src/base/openfpga_context.h b/openfpga/src/base/openfpga_context.h index 5726cbba9..a8b37dcf7 100644 --- a/openfpga/src/base/openfpga_context.h +++ b/openfpga/src/base/openfpga_context.h @@ -11,6 +11,7 @@ #include "tile_direct.h" #include "module_manager.h" #include "openfpga_flow_manager.h" +#include "lb_rr_graph.h" #include "device_rr_gsb.h" /******************************************************************** diff --git a/vpr/src/pack/lb_rr_graph_types.h b/vpr/src/pack/lb_rr_graph_types.h new file mode 100644 index 000000000..7abe7a5cf --- /dev/null +++ b/vpr/src/pack/lb_rr_graph_types.h @@ -0,0 +1,14 @@ +#ifndef LB_RR_GRPAH_TYPES_H +#define LB_RR_GRPAH_TYPES_H + +/* Describes different types of intra-logic cluster_ctx.blocks routing resource nodes */ +enum e_lb_rr_type { + LB_SOURCE = 0, + LB_SINK, + LB_INTERMEDIATE, + NUM_LB_RR_TYPES +}; +const std::vector lb_rr_type_str{ + "LB_SOURCE", "LB_SINK", "LB_INTERMEDIATE", "INVALID"}; + +#endif From 62e4f14e3099bb4c262ba398ecec22980957f7b2 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 17 Feb 2020 17:26:27 -0700 Subject: [PATCH 165/645] add lb_rr_graph to device annotation --- .../src/annotation/vpr_device_annotation.cpp | 18 ++++++++++++++++++ .../src/annotation/vpr_device_annotation.h | 6 ++++++ openfpga/src/base/openfpga_context.h | 1 - 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/openfpga/src/annotation/vpr_device_annotation.cpp b/openfpga/src/annotation/vpr_device_annotation.cpp index 5b36e3885..9be30591d 100644 --- a/openfpga/src/annotation/vpr_device_annotation.cpp +++ b/openfpga/src/annotation/vpr_device_annotation.cpp @@ -241,6 +241,14 @@ ArchDirectId VprDeviceAnnotation::direct_annotation(const size_t& direct) const return direct_annotations_.at(direct); } +LbRRGraph VprDeviceAnnotation::physical_lb_rr_graph(t_pb_graph_node* pb_graph_head) const { + /* Ensure that the rr_switch is in the list */ + if (0 == physical_lb_rr_graphs_.count(pb_graph_head)) { + return LbRRGraph(); + } + return physical_lb_rr_graphs_.at(pb_graph_head); +} + /************************************************************************ * Public mutators ***********************************************************************/ @@ -470,4 +478,14 @@ void VprDeviceAnnotation::add_direct_annotation(const size_t& direct, const Arch direct_annotations_[direct] = arch_direct_id; } +void VprDeviceAnnotation::add_physical_lb_rr_graph(t_pb_graph_node* pb_graph_head, const LbRRGraph& lb_rr_graph) { + /* Warn any override attempt */ + if (0 < physical_lb_rr_graphs_.count(pb_graph_head)) { + VTR_LOG_WARN("Override the physical lb_rr_graph for pb_graph_head '%s'!\n", + pb_graph_head->pb_type->name); + } + + physical_lb_rr_graphs_[pb_graph_head] = lb_rr_graph; +} + } /* End namespace openfpga*/ diff --git a/openfpga/src/annotation/vpr_device_annotation.h b/openfpga/src/annotation/vpr_device_annotation.h index 5c083e367..0ecff4371 100644 --- a/openfpga/src/annotation/vpr_device_annotation.h +++ b/openfpga/src/annotation/vpr_device_annotation.h @@ -19,6 +19,7 @@ #include "openfpga_port.h" #include "circuit_library.h" #include "arch_direct.h" +#include "lb_rr_graph.h" /* Begin namespace openfpga */ namespace openfpga { @@ -73,6 +74,7 @@ class VprDeviceAnnotation { CircuitModelId rr_switch_circuit_model(const RRSwitchId& rr_switch) const; CircuitModelId rr_segment_circuit_model(const RRSegmentId& rr_segment) const; ArchDirectId direct_annotation(const size_t& direct) const; + LbRRGraph physical_lb_rr_graph(t_pb_graph_node* pb_graph_head) const; public: /* Public mutators */ void add_pb_type_physical_mode(t_pb_type* pb_type, t_mode* physical_mode); void add_physical_pb_type(t_pb_type* operating_pb_type, t_pb_type* physical_pb_type); @@ -93,6 +95,7 @@ class VprDeviceAnnotation { void add_rr_switch_circuit_model(const RRSwitchId& rr_switch, const CircuitModelId& circuit_model); void add_rr_segment_circuit_model(const RRSegmentId& rr_segment, const CircuitModelId& circuit_model); void add_direct_annotation(const size_t& direct, const ArchDirectId& arch_direct_id); + void add_physical_lb_rr_graph(t_pb_graph_node* pb_graph_head, const LbRRGraph& lb_rr_graph); private: /* Internal data */ /* Pair a regular pb_type to its physical pb_type */ std::map physical_pb_types_; @@ -176,6 +179,9 @@ class VprDeviceAnnotation { /* Pair a direct connection (direct) to a annotation which contains circuit model id */ std::map direct_annotations_; + + /* Logical type routing resource graphs built from physical modes */ + std::map physical_lb_rr_graphs_; }; } /* End namespace openfpga*/ diff --git a/openfpga/src/base/openfpga_context.h b/openfpga/src/base/openfpga_context.h index a8b37dcf7..5726cbba9 100644 --- a/openfpga/src/base/openfpga_context.h +++ b/openfpga/src/base/openfpga_context.h @@ -11,7 +11,6 @@ #include "tile_direct.h" #include "module_manager.h" #include "openfpga_flow_manager.h" -#include "lb_rr_graph.h" #include "device_rr_gsb.h" /******************************************************************** From 8e97443410382fb47f006922ba8b533b7b165453 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 17 Feb 2020 17:57:43 -0700 Subject: [PATCH 166/645] start working on repack --- .../src/base/openfpga_bitstream_command.cpp | 38 +++++++++++++++++++ .../src/base/openfpga_bitstream_command.h | 21 ++++++++++ openfpga/src/base/openfpga_repack.cpp | 33 ++++++++++++++++ openfpga/src/base/openfpga_repack.h | 23 +++++++++++ openfpga/src/main.cpp | 4 ++ openfpga/src/repack/repack.cpp | 32 ++++++++++++++++ openfpga/src/repack/repack.h | 27 +++++++++++++ 7 files changed, 178 insertions(+) create mode 100644 openfpga/src/base/openfpga_bitstream_command.cpp create mode 100644 openfpga/src/base/openfpga_bitstream_command.h create mode 100644 openfpga/src/base/openfpga_repack.cpp create mode 100644 openfpga/src/base/openfpga_repack.h create mode 100644 openfpga/src/repack/repack.cpp create mode 100644 openfpga/src/repack/repack.h diff --git a/openfpga/src/base/openfpga_bitstream_command.cpp b/openfpga/src/base/openfpga_bitstream_command.cpp new file mode 100644 index 000000000..827dd1eca --- /dev/null +++ b/openfpga/src/base/openfpga_bitstream_command.cpp @@ -0,0 +1,38 @@ +/******************************************************************** + * Add commands to the OpenFPGA shell interface, + * in purpose of generate Verilog netlists modeling the full FPGA fabric + * This is one of the core engine of openfpga, including: + * - repack : create physical pbs and redo packing + *******************************************************************/ +#include "openfpga_repack.h" +#include "openfpga_bitstream_command.h" + +/* begin namespace openfpga */ +namespace openfpga { + +void add_openfpga_bitstream_commands(openfpga::Shell& shell) { + /* Get the unique id of 'build_fabric' command which is to be used in creating the dependency graph */ + const ShellCommandId& shell_cmd_build_fabric_id = shell.command(std::string("build_fabric")); + + /* Add a new class of commands */ + ShellCommandClassId openfpga_bitstream_cmd_class = shell.add_command_class("FPGA-Bitstream"); + + /******************************** + * Command 'repack' + */ + Command shell_cmd_repack("repack"); + /* Add an option '--verbose' */ + shell_cmd_repack.add_option("verbose", false, "Enable verbose output"); + + /* Add command 'repack' to the Shell */ + ShellCommandId shell_cmd_repack_id = shell.add_command(shell_cmd_repack, "Pack physical programmable logic blocks"); + shell.set_command_class(shell_cmd_repack_id, openfpga_bitstream_cmd_class); + shell.set_command_execute_function(shell_cmd_repack_id, repack); + + /* The 'repack' command should NOT be executed before 'build_fabric' */ + std::vector cmd_dependency_repack; + cmd_dependency_repack.push_back(shell_cmd_build_fabric_id); + shell.set_command_dependency(shell_cmd_repack_id, cmd_dependency_repack); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_bitstream_command.h b/openfpga/src/base/openfpga_bitstream_command.h new file mode 100644 index 000000000..d58555dd8 --- /dev/null +++ b/openfpga/src/base/openfpga_bitstream_command.h @@ -0,0 +1,21 @@ +#ifndef OPENFPGA_BITSTREAM_COMMAND_H +#define OPENFPGA_BITSTREAM_COMMAND_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "shell.h" +#include "openfpga_context.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void add_openfpga_bitstream_commands(openfpga::Shell& shell); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/base/openfpga_repack.cpp b/openfpga/src/base/openfpga_repack.cpp new file mode 100644 index 000000000..f605233da --- /dev/null +++ b/openfpga/src/base/openfpga_repack.cpp @@ -0,0 +1,33 @@ +/******************************************************************** + * This file includes functions to compress the hierachy of routing architecture + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_time.h" +#include "vtr_log.h" + +#include "verilog_api.h" +#include "repack.h" +#include "openfpga_repack.h" + +/* Include global variables of VPR */ +#include "globals.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * A wrapper function to call the fabric_verilog function of FPGA-Verilog + *******************************************************************/ +void repack(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context) { + + CommandOptionId opt_verbose = cmd.option("verbose"); + + pack_physical_pbs(g_vpr_ctx.device(), + openfpga_ctx.mutable_vpr_device_annotation(), + openfpga_ctx.mutable_vpr_clustering_annotation(), + openfpga_ctx.vpr_routing_annotation(), + cmd_context.option_enable(cmd, opt_verbose)); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_repack.h b/openfpga/src/base/openfpga_repack.h new file mode 100644 index 000000000..0fce62f78 --- /dev/null +++ b/openfpga/src/base/openfpga_repack.h @@ -0,0 +1,23 @@ +#ifndef OPENFPGA_REPACK_H +#define OPENFPGA_REPACK_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "command.h" +#include "command_context.h" +#include "openfpga_context.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void repack(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/main.cpp b/openfpga/src/main.cpp index 792223e3c..c018db4bf 100644 --- a/openfpga/src/main.cpp +++ b/openfpga/src/main.cpp @@ -13,6 +13,7 @@ #include "vpr_command.h" #include "openfpga_setup_command.h" #include "openfpga_verilog_command.h" +#include "openfpga_bitstream_command.h" #include "basic_command.h" #include "openfpga_title.h" @@ -56,6 +57,9 @@ int main(int argc, char** argv) { /* Add openfpga verilog commands */ openfpga::add_openfpga_verilog_commands(shell); + /* Add openfpga bitstream commands */ + openfpga::add_openfpga_bitstream_commands(shell); + /* Add basic commands: exit, help, etc. * Note: * This MUST be the last command group to be added! diff --git a/openfpga/src/repack/repack.cpp b/openfpga/src/repack/repack.cpp new file mode 100644 index 000000000..2a7dada40 --- /dev/null +++ b/openfpga/src/repack/repack.cpp @@ -0,0 +1,32 @@ +/*************************************************************************************** + * This file includes functions that are used to redo packing for physical pbs + ***************************************************************************************/ + +/* Headers from vtrutil library */ +#include "vtr_log.h" +#include "vtr_assert.h" +#include "vtr_time.h" + +#include "repack.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/*************************************************************************************** + * Top-level function to pack physical pb_graph + * This function will do : + * - create physical lb_rr_graph for each pb_graph considering physical modes only + * the lb_rr_graph willbe added to device annotation + * - annotate nets to be routed for each clustered block from operating modes of pb_graph + * to physical modes of pb_graph + * - rerun the routing for each clustered block + * - store the packing results to clustering annotation + ***************************************************************************************/ +void pack_physical_pbs(const DeviceContext& device_ctx, + VprDeviceAnnotation& device_annotation, + VprClusteringAnnotation& clustering_annotation, + const VprRoutingAnnotation& routing_annotation, + const bool& verbose) { +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/repack/repack.h b/openfpga/src/repack/repack.h new file mode 100644 index 000000000..733a7d1ac --- /dev/null +++ b/openfpga/src/repack/repack.h @@ -0,0 +1,27 @@ +#ifndef REPACK_H +#define REPACK_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "vpr_context.h" +#include "vpr_device_annotation.h" +#include "vpr_clustering_annotation.h" +#include "vpr_routing_annotation.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void pack_physical_pbs(const DeviceContext& device_ctx, + VprDeviceAnnotation& device_annotation, + VprClusteringAnnotation& clustering_annotation, + const VprRoutingAnnotation& routing_annotation, + const bool& verbose); + +} /* end namespace openfpga */ + +#endif From 409b3f68960168bce7728733200fb80ee571d5c8 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 17 Feb 2020 21:11:56 -0700 Subject: [PATCH 167/645] add lb_rr_graph builder for the refactored version --- openfpga/src/base/openfpga_link_arch.cpp | 5 + openfpga/src/base/openfpga_repack.cpp | 2 +- .../src/repack/build_physical_lb_rr_graph.cpp | 404 ++++++++++++++++++ .../src/repack/build_physical_lb_rr_graph.h | 23 + openfpga/src/repack/repack.cpp | 2 +- openfpga/src/repack/repack.h | 2 +- openfpga/test_script/s298_k6_frac.openfpga | 2 +- 7 files changed, 436 insertions(+), 4 deletions(-) create mode 100644 openfpga/src/repack/build_physical_lb_rr_graph.cpp create mode 100644 openfpga/src/repack/build_physical_lb_rr_graph.h diff --git a/openfpga/src/base/openfpga_link_arch.cpp b/openfpga/src/base/openfpga_link_arch.cpp index 567ad2e08..ac403592c 100644 --- a/openfpga/src/base/openfpga_link_arch.cpp +++ b/openfpga/src/base/openfpga_link_arch.cpp @@ -15,6 +15,7 @@ #include "annotate_rr_graph.h" #include "mux_library_builder.h" #include "build_tile_direct.h" +#include "build_physical_lb_rr_graph.h" #include "openfpga_link_arch.h" /* Include global variables of VPR */ @@ -112,6 +113,10 @@ void link_arch(OpenfpgaContext& openfpga_ctx, openfpga_ctx.mutable_tile_direct() = build_device_tile_direct(g_vpr_ctx.device(), openfpga_ctx.arch().arch_direct); + + build_physical_lb_rr_graphs(g_vpr_ctx.device(), + openfpga_ctx.mutable_vpr_device_annotation(), + cmd_context.option_enable(cmd, opt_verbose)); } } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_repack.cpp b/openfpga/src/base/openfpga_repack.cpp index f605233da..c58e2b4ff 100644 --- a/openfpga/src/base/openfpga_repack.cpp +++ b/openfpga/src/base/openfpga_repack.cpp @@ -24,7 +24,7 @@ void repack(OpenfpgaContext& openfpga_ctx, CommandOptionId opt_verbose = cmd.option("verbose"); pack_physical_pbs(g_vpr_ctx.device(), - openfpga_ctx.mutable_vpr_device_annotation(), + openfpga_ctx.vpr_device_annotation(), openfpga_ctx.mutable_vpr_clustering_annotation(), openfpga_ctx.vpr_routing_annotation(), cmd_context.option_enable(cmd, opt_verbose)); diff --git a/openfpga/src/repack/build_physical_lb_rr_graph.cpp b/openfpga/src/repack/build_physical_lb_rr_graph.cpp new file mode 100644 index 000000000..4b7107fbb --- /dev/null +++ b/openfpga/src/repack/build_physical_lb_rr_graph.cpp @@ -0,0 +1,404 @@ +/*************************************************************************************** + * This file includes functions that are used to redo packing for physical pbs + ***************************************************************************************/ + +/* Headers from vtrutil library */ +#include "vtr_log.h" +#include "vtr_assert.h" +#include "vtr_time.h" + +#include "pb_type_utils.h" + +#include "build_physical_lb_rr_graph.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/*************************************************************************************** + * Create all the intermediate nodes for lb_rr_graph for each pb_graph_node. + * Different from the lb_rr_graph builder in VPR packer, this function only consider + * the pb_graph_node under physical modes + ***************************************************************************************/ +static +void rec_build_physical_lb_rr_node_for_pb_graph_node(t_pb_graph_node* pb_graph_node, + LbRRGraph& lb_rr_graph, + const VprDeviceAnnotation& device_annotation) { + t_pb_type* pb_type = pb_graph_node->pb_type; + + /* TODO: think if we need to consider wire mode of LUT when creating the lb_rr_graph here! + * Should we create edges through the LUT input and output nodes? + */ + + /* The only difference between primitive node and intermediate nodes is + * the output pins of primitive node will be SINK node + * Otherwise it is always INTERMEDIATE node + */ + e_lb_rr_type output_pin_rr_type = LB_INTERMEDIATE; + if (true == is_primitive_pb_type(pb_type)) { + output_pin_rr_type = LB_SOURCE; + } + + /* alloc and load input pins that connect to sinks */ + for (int iport = 0; iport < pb_graph_node->num_input_ports; iport++) { + for (int ipin = 0; ipin < pb_graph_node->num_input_pins[iport]; ipin++) { + /* load intermediate indices */ + t_pb_graph_pin* pb_pin = &pb_graph_node->input_pins[iport][ipin]; + + /* alloc and load rr node info */ + LbRRNodeId node = lb_rr_graph.create_node(LB_INTERMEDIATE); + lb_rr_graph.set_node_capacity(node, 1); + lb_rr_graph.set_node_pb_graph_pin(node, pb_pin); + + /* TODO: Double check if this is the case */ + lb_rr_graph.set_node_intrinsic_cost(node, 1); + } + } + + /* alloc and load input pins that connect to sinks */ + for (int iport = 0; iport < pb_graph_node->num_clock_ports; iport++) { + for (int ipin = 0; ipin < pb_graph_node->num_clock_pins[iport]; ipin++) { + /* load intermediate indices */ + t_pb_graph_pin* pb_pin = &pb_graph_node->clock_pins[iport][ipin]; + + /* alloc and load rr node info */ + LbRRNodeId node = lb_rr_graph.create_node(LB_INTERMEDIATE); + lb_rr_graph.set_node_capacity(node, 1); + lb_rr_graph.set_node_pb_graph_pin(node, pb_pin); + + /* TODO: Double check if this is the case */ + lb_rr_graph.set_node_intrinsic_cost(node, 1); + } + } + + /* alloc and load output pins that are represented as rr sources */ + for (int iport = 0; iport < pb_graph_node->num_output_ports; iport++) { + for (int ipin = 0; ipin < pb_graph_node->num_output_pins[iport]; ipin++) { + /* load intermediate indices */ + t_pb_graph_pin* pb_pin = &pb_graph_node->output_pins[iport][ipin]; + + /* alloc and load rr node info */ + LbRRNodeId node = lb_rr_graph.create_node(output_pin_rr_type); + lb_rr_graph.set_node_capacity(node, 1); + lb_rr_graph.set_node_pb_graph_pin(node, pb_pin); + + /* TODO: Double check if this is the case */ + lb_rr_graph.set_node_intrinsic_cost(node, 1); + } + } + + if (true == is_primitive_pb_type(pb_type)) { + return; + } + + /* For non-primitive node: + * This pb_graph_node is a logic block or subcluster + * Go recusrively + */ + t_mode* physical_mode = device_annotation.physical_mode(pb_type); + for (int ipb_type = 0; ipb_type < physical_mode->num_pb_type_children; ipb_type++) { + for (int ipb = 0; ipb < physical_mode->pb_type_children[ipb_type].num_pb; ipb++) { + rec_build_physical_lb_rr_node_for_pb_graph_node(&(pb_graph_node->child_pb_graph_nodes[physical_mode->index][ipb_type][ipb]), lb_rr_graph, device_annotation); + } + } +} + +/*************************************************************************************** + * Build the edge for an input/clock pb_graph_pin for a primitive pb_graph node + * This function will identify if the port equivalence should be considered + ***************************************************************************************/ +static +void build_lb_rr_edge_primitive_pb_graph_input_pin(LbRRGraph& lb_rr_graph, + t_pb_graph_pin* pb_pin, + LbRRNodeId& sink_node) { + /* Find the node that we have already created */ + LbRRNodeId node = lb_rr_graph.find_node(LB_INTERMEDIATE, pb_pin); + VTR_ASSERT(true == lb_rr_graph.valid_node_id(node)); + + PortEquivalence port_equivalent = pb_pin->port->equivalent; + + if (port_equivalent == PortEquivalence::NONE || sink_node == LbRRNodeId::INVALID()) { + /* Create new sink for input to primitive */ + LbRRNodeId new_sink = lb_rr_graph.create_node(LB_SINK); + if (port_equivalent != PortEquivalence::NONE) { + lb_rr_graph.set_node_capacity(new_sink, pb_pin->port->num_pins); + } else { + lb_rr_graph.set_node_capacity(new_sink, 1); + } + sink_node = new_sink; + } + + /* Connect the nodes denoting the input pins to sink, since this is a primtive node, we do not have any mode */ + LbRREdgeId edge = lb_rr_graph.create_edge(node, sink_node, nullptr); + + /* TODO: Double check if this is the case */ + lb_rr_graph.set_edge_intrinsic_cost(edge, 1.); +} + +/*************************************************************************************** + * Build the edge for a pb_graph_pin for a non-primitive pb_graph node + * Note: + * - this function is NOT applicable to + * - any input pin of primitive pb_graph_node + * - any output pin of root pb_graph_node! + ***************************************************************************************/ +static +void build_lb_rr_edge_pb_graph_pin(LbRRGraph& lb_rr_graph, + t_pb_graph_pin* pb_pin, + const e_lb_rr_type& pin_rr_type, + t_mode* physical_mode) { + /* Find the node that we have already created */ + LbRRNodeId from_node = lb_rr_graph.find_node(pin_rr_type, pb_pin); + VTR_ASSERT(true == lb_rr_graph.valid_node_id(from_node)); + + /* Load edges only for physical mode! */ + for (int iedge = 0; iedge < pb_pin->num_output_edges; iedge++) { + VTR_ASSERT(1 == pb_pin->output_edges[iedge]->num_output_pins); + if (physical_mode != pb_pin->output_edges[iedge]->interconnect->parent_mode) { + continue; + } + /* Find the node that we have already created */ + LbRRNodeId to_node = lb_rr_graph.find_node(LB_INTERMEDIATE, pb_pin->output_edges[iedge]->output_pins[0]); + VTR_ASSERT(true == lb_rr_graph.valid_node_id(to_node)); + LbRREdgeId edge = lb_rr_graph.create_edge(from_node, to_node, physical_mode); + + /* TODO: Double check if this is the case */ + lb_rr_graph.set_edge_intrinsic_cost(edge, 1.); + } +} + +/*************************************************************************************** + * Build the edge for an output pb_graph_pin for a root pb_graph node + * These node should be connected to a command external lb_rr_node + ***************************************************************************************/ +static +void build_lb_rr_edge_root_pb_graph_pin(LbRRGraph& lb_rr_graph, + t_pb_graph_pin* pb_pin, + const e_lb_rr_type& pin_rr_type, + const LbRRNodeId& ext_rr_index) { + /* Find the node that we have already created */ + LbRRNodeId from_node = lb_rr_graph.find_node(pin_rr_type, pb_pin); + VTR_ASSERT(true == lb_rr_graph.valid_node_id(from_node)); + + LbRREdgeId edge = lb_rr_graph.create_edge(from_node, ext_rr_index, nullptr); + + /* TODO: Double check if this is the case */ + lb_rr_graph.set_edge_intrinsic_cost(edge, 1.); +} + +/*************************************************************************************** + * Create all the edges and special nodes (SOURCE/SINK) for lb_rr_graph for each pb_graph_node. + * Different from the lb_rr_graph builder in VPR packer, this function only consider + * the pb_graph_node under physical modes + ***************************************************************************************/ +static +void rec_build_physical_lb_rr_edge_for_pb_graph_node(t_pb_graph_node* pb_graph_node, + LbRRGraph& lb_rr_graph, + const LbRRNodeId& ext_rr_index, + const VprDeviceAnnotation& device_annotation) { + t_pb_type* pb_type = pb_graph_node->pb_type; + t_pb_graph_node* parent_node = pb_graph_node->parent_pb_graph_node; + + /* TODO: think if we need to consider wire mode of LUT when creating the lb_rr_graph here! + * Should we create edges through the LUT input and output nodes? + */ + + /* The only difference between primitive node and intermediate nodes is + * the output pins of primitive node will be SINK node + * Otherwise it is always INTERMEDIATE node + */ + /* The input pins should connect to sinks */ + for (int iport = 0; iport < pb_graph_node->num_input_ports; iport++) { + LbRRNodeId sink_node = LbRRNodeId::INVALID(); + for (int ipin = 0; ipin < pb_graph_node->num_input_pins[iport]; ipin++) { + /* load intermediate indices */ + t_pb_graph_pin* pb_pin = &pb_graph_node->input_pins[iport][ipin]; + t_mode* physical_mode = device_annotation.physical_mode(pb_type); + + if (true == is_primitive_pb_type(pb_type)) { + build_lb_rr_edge_primitive_pb_graph_input_pin(lb_rr_graph, pb_pin, sink_node); + } else { + VTR_ASSERT(false == is_primitive_pb_type(pb_type)); + build_lb_rr_edge_pb_graph_pin(lb_rr_graph, pb_pin, LB_INTERMEDIATE, physical_mode); + } + } + } + /* The input pins should connect to sinks */ + for (int iport = 0; iport < pb_graph_node->num_clock_ports; iport++) { + LbRRNodeId sink_node = LbRRNodeId::INVALID(); + for (int ipin = 0; ipin < pb_graph_node->num_clock_pins[iport]; ipin++) { + /* load intermediate indices */ + t_pb_graph_pin* pb_pin = &pb_graph_node->clock_pins[iport][ipin]; + t_mode* physical_mode = device_annotation.physical_mode(pb_type); + + if (true == is_primitive_pb_type(pb_type)) { + build_lb_rr_edge_primitive_pb_graph_input_pin(lb_rr_graph, pb_pin, sink_node); + } else { + VTR_ASSERT(false == is_primitive_pb_type(pb_type)); + build_lb_rr_edge_pb_graph_pin(lb_rr_graph, pb_pin, LB_INTERMEDIATE, physical_mode); + } + } + } + + e_lb_rr_type output_pin_rr_type = LB_INTERMEDIATE; + if (true == is_primitive_pb_type(pb_type)) { + output_pin_rr_type = LB_SOURCE; + } + + /* The output pins should connect to its fan-outs */ + for (int iport = 0; iport < pb_graph_node->num_output_ports; iport++) { + for (int ipin = 0; ipin < pb_graph_node->num_output_pins[iport]; ipin++) { + /* load intermediate indices */ + t_pb_graph_pin* pb_pin = &pb_graph_node->output_pins[iport][ipin]; + + if (true == pb_graph_node->is_root()) { + build_lb_rr_edge_root_pb_graph_pin(lb_rr_graph, pb_pin, output_pin_rr_type, ext_rr_index); + } else { + VTR_ASSERT(false == pb_graph_node->is_root()); + t_mode* physical_mode = device_annotation.physical_mode(parent_node->pb_type); + build_lb_rr_edge_pb_graph_pin(lb_rr_graph, pb_pin, output_pin_rr_type, physical_mode); + } + } + } + + if (true == is_primitive_pb_type(pb_type)) { + return; + } + + /* For non-primitive node: + * This pb_graph_node is a logic block or subcluster + * Go recusrively + */ + t_mode* physical_mode = device_annotation.physical_mode(pb_graph_node->pb_type); + for (int ipb_type = 0; ipb_type < physical_mode->num_pb_type_children; ipb_type++) { + for (int ipb = 0; ipb < physical_mode->pb_type_children[ipb_type].num_pb; ipb++) { + rec_build_physical_lb_rr_edge_for_pb_graph_node(&(pb_graph_node->child_pb_graph_nodes[physical_mode->index][ipb_type][ipb]), lb_rr_graph, ext_rr_index, device_annotation); + } + } +} + +/*************************************************************************************** + * This functio will create a physical lb_rr_graph for a pb_graph considering physical modes only + ***************************************************************************************/ +static +LbRRGraph build_lb_type_physical_lb_rr_graph(t_pb_graph_node* pb_graph_head, + const VprDeviceAnnotation& device_annotation, + const bool& verbose) { + LbRRGraph lb_rr_graph; + + /* TODO: ensure we have an empty lb_rr_graph */ + + /* Define the external source, sink, and external interconnect for the routing resource graph of the logic block type */ + LbRRNodeId ext_source_index = lb_rr_graph.create_node(LB_SOURCE); + LbRRNodeId ext_sink_index = lb_rr_graph.create_node(LB_SINK); + LbRRNodeId ext_rr_index = lb_rr_graph.create_node(LB_INTERMEDIATE); + + /* Build the main body of lb rr_graph by walking through the pb_graph recursively */ + /* Build all the regular nodes first */ + rec_build_physical_lb_rr_node_for_pb_graph_node(pb_graph_head, lb_rr_graph, device_annotation); + /* Build all the edges and special node (SOURCE/SINK) */ + rec_build_physical_lb_rr_edge_for_pb_graph_node(pb_graph_head, lb_rr_graph, ext_rr_index, device_annotation); + + /******************************************************************************* + * Build logic block source node + *******************************************************************************/ + t_pb_type* pb_type = pb_graph_head->pb_type; + + /* External source node drives all inputs going into logic block type */ + lb_rr_graph.set_node_capacity(ext_source_index, pb_type->num_input_pins + pb_type->num_clock_pins); + + for (int iport = 0; iport < pb_graph_head->num_input_ports; iport++) { + for (int ipin = 0; ipin < pb_graph_head->num_input_pins[iport]; ipin++) { + LbRRNodeId to_node = lb_rr_graph.find_node(LB_INTERMEDIATE, &(pb_graph_head->input_pins[iport][ipin])); + VTR_ASSERT(true == lb_rr_graph.valid_node_id(to_node)); + LbRREdgeId edge = lb_rr_graph.create_edge(ext_source_index, to_node, nullptr); + lb_rr_graph.set_edge_intrinsic_cost(edge, 1.); + } + } + + for (int iport = 0; iport < pb_graph_head->num_clock_ports; iport++) { + for (int ipin = 0; ipin < pb_graph_head->num_clock_pins[iport]; ipin++) { + LbRRNodeId to_node = lb_rr_graph.find_node(LB_INTERMEDIATE, &(pb_graph_head->clock_pins[iport][ipin])); + VTR_ASSERT(true == lb_rr_graph.valid_node_id(to_node)); + LbRREdgeId edge = lb_rr_graph.create_edge(ext_source_index, to_node, nullptr); + lb_rr_graph.set_edge_intrinsic_cost(edge, 1.); + } + } + + /******************************************************************************* + * Build logic block sink node + *******************************************************************************/ + + /* External sink node driven by all outputs exiting logic block type */ + lb_rr_graph.set_node_capacity(ext_sink_index, pb_type->num_output_pins); + + /******************************************************************************* + * Build node that approximates external interconnect + *******************************************************************************/ + + /* External rr node that drives all existing logic block input pins and is driven by all outputs exiting logic block type */ + lb_rr_graph.set_node_capacity(ext_rr_index, pb_type->num_output_pins); + + /* Connect opin of logic block to sink */ + { + LbRREdgeId edge = lb_rr_graph.create_edge(ext_rr_index, ext_sink_index, nullptr); + lb_rr_graph.set_edge_intrinsic_cost(edge, 1.); + } + + /* Connect opin of logic block to all input and clock pins of logic block type */ + for (int iport = 0; iport < pb_graph_head->num_input_ports; iport++) { + for (int ipin = 0; ipin < pb_graph_head->num_input_pins[iport]; ipin++) { + LbRRNodeId to_node = lb_rr_graph.find_node(LB_INTERMEDIATE, &(pb_graph_head->input_pins[iport][ipin])); + VTR_ASSERT(true == lb_rr_graph.valid_node_id(to_node)); + LbRREdgeId edge = lb_rr_graph.create_edge(ext_rr_index, to_node, nullptr); + /* set cost high to avoid using external interconnect unless necessary */ + lb_rr_graph.set_edge_intrinsic_cost(edge, 1000.); + } + } + for (int iport = 0; iport < pb_graph_head->num_clock_ports; iport++) { + for (int ipin = 0; ipin < pb_graph_head->num_clock_pins[iport]; ipin++) { + LbRRNodeId to_node = lb_rr_graph.find_node(LB_INTERMEDIATE, &(pb_graph_head->clock_pins[iport][ipin])); + VTR_ASSERT(true == lb_rr_graph.valid_node_id(to_node)); + LbRREdgeId edge = lb_rr_graph.create_edge(ext_rr_index, to_node, nullptr); + /* set cost high to avoid using external interconnect unless necessary */ + lb_rr_graph.set_edge_intrinsic_cost(edge, 1000.); + } + } + + VTR_LOGV(verbose, + "\n\tNumber of nodes: %lu\n", + lb_rr_graph.nodes().size()); + + VTR_LOGV(verbose, + "\n\tNumber of edges: %lu\n", + lb_rr_graph.edges().size()); + + return lb_rr_graph; +} + +/*************************************************************************************** + * This functio will create physical lb_rr_graph for each pb_graph considering physical modes only + * the lb_rr_graph willbe added to device annotation + ***************************************************************************************/ +void build_physical_lb_rr_graphs(const DeviceContext& device_ctx, + VprDeviceAnnotation& device_annotation, + const bool& verbose) { + vtr::ScopedStartFinishTimer timer("Build routing resource graph for the physical implementation of logical tile"); + + for (const t_logical_block_type& lb_type : device_ctx.logical_block_types) { + /* By pass nullptr for pb_graph head */ + if (nullptr == lb_type.pb_graph_head) { + continue; + } + + VTR_LOGV(verbose, + "Building routing resource graph for logical tile '%s'...", + lb_type.pb_graph_head->pb_type->name); + + const LbRRGraph& lb_rr_graph = build_lb_type_physical_lb_rr_graph(lb_type.pb_graph_head, const_cast(device_annotation), verbose); + device_annotation.add_physical_lb_rr_graph(lb_type.pb_graph_head, lb_rr_graph); + } + + VTR_LOGV(verbose, "Done\n"); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/repack/build_physical_lb_rr_graph.h b/openfpga/src/repack/build_physical_lb_rr_graph.h new file mode 100644 index 000000000..9499a9c1d --- /dev/null +++ b/openfpga/src/repack/build_physical_lb_rr_graph.h @@ -0,0 +1,23 @@ +#ifndef BUILD_PHYSICAL_LB_RR_GRAPH_H +#define BUILD_PHYSICAL_LB_RR_GRAPH_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "vpr_context.h" +#include "vpr_device_annotation.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void build_physical_lb_rr_graphs(const DeviceContext& device_ctx, + VprDeviceAnnotation& device_annotation, + const bool& verbose); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/repack/repack.cpp b/openfpga/src/repack/repack.cpp index 2a7dada40..690dbaa23 100644 --- a/openfpga/src/repack/repack.cpp +++ b/openfpga/src/repack/repack.cpp @@ -23,7 +23,7 @@ namespace openfpga { * - store the packing results to clustering annotation ***************************************************************************************/ void pack_physical_pbs(const DeviceContext& device_ctx, - VprDeviceAnnotation& device_annotation, + const VprDeviceAnnotation& device_annotation, VprClusteringAnnotation& clustering_annotation, const VprRoutingAnnotation& routing_annotation, const bool& verbose) { diff --git a/openfpga/src/repack/repack.h b/openfpga/src/repack/repack.h index 733a7d1ac..2f8acd241 100644 --- a/openfpga/src/repack/repack.h +++ b/openfpga/src/repack/repack.h @@ -17,7 +17,7 @@ namespace openfpga { void pack_physical_pbs(const DeviceContext& device_ctx, - VprDeviceAnnotation& device_annotation, + const VprDeviceAnnotation& device_annotation, VprClusteringAnnotation& clustering_annotation, const VprRoutingAnnotation& routing_annotation, const bool& verbose); diff --git a/openfpga/test_script/s298_k6_frac.openfpga b/openfpga/test_script/s298_k6_frac.openfpga index c01774bd3..3c44fddae 100644 --- a/openfpga/test_script/s298_k6_frac.openfpga +++ b/openfpga/test_script/s298_k6_frac.openfpga @@ -5,7 +5,7 @@ vpr ./test_vpr_arch/k6_frac_N10_40nm.xml ./test_blif/s298.blif --write_rr_graph read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml # Annotate the OpenFPGA architecture to VPR data base -link_openfpga_arch +link_openfpga_arch --verbose # Check and correct any naming conflicts in the BLIF netlist check_netlist_naming_conflict --fix --report ./netlist_renaming.xml From 6060440b97d40d32392ad62a061e5f3745656ef9 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 17 Feb 2020 21:14:15 -0700 Subject: [PATCH 168/645] fine tuning for the verbose output --- openfpga/src/repack/build_physical_lb_rr_graph.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openfpga/src/repack/build_physical_lb_rr_graph.cpp b/openfpga/src/repack/build_physical_lb_rr_graph.cpp index 4b7107fbb..aca8cb0d1 100644 --- a/openfpga/src/repack/build_physical_lb_rr_graph.cpp +++ b/openfpga/src/repack/build_physical_lb_rr_graph.cpp @@ -369,7 +369,7 @@ LbRRGraph build_lb_type_physical_lb_rr_graph(t_pb_graph_node* pb_graph_head, lb_rr_graph.nodes().size()); VTR_LOGV(verbose, - "\n\tNumber of edges: %lu\n", + "\tNumber of edges: %lu\n", lb_rr_graph.edges().size()); return lb_rr_graph; From ef11482a95f1c951f11cc9323984099453e8ecb4 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 18 Feb 2020 11:36:16 -0700 Subject: [PATCH 169/645] fix dependency error in pack_types header file --- vpr/src/pack/pack_types.h | 1 + 1 file changed, 1 insertion(+) diff --git a/vpr/src/pack/pack_types.h b/vpr/src/pack/pack_types.h index 794173217..e9b40462f 100644 --- a/vpr/src/pack/pack_types.h +++ b/vpr/src/pack/pack_types.h @@ -11,6 +11,7 @@ #include #include "arch_types.h" +#include "vpr_types.h" #include "atom_netlist_fwd.h" #include "lb_rr_graph_types.h" From ed25ccc70f21fafac3b5f9cabf2602aa3c426ba2 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 18 Feb 2020 12:00:27 -0700 Subject: [PATCH 170/645] start refactoring lb router in openfpga namespace --- openfpga/src/repack/lb_router.cpp | 1498 +++++++++++++++++++++++++++++ openfpga/src/repack/lb_router.h | 40 + 2 files changed, 1538 insertions(+) create mode 100644 openfpga/src/repack/lb_router.cpp create mode 100644 openfpga/src/repack/lb_router.h diff --git a/openfpga/src/repack/lb_router.cpp b/openfpga/src/repack/lb_router.cpp new file mode 100644 index 000000000..dd694fa49 --- /dev/null +++ b/openfpga/src/repack/lb_router.cpp @@ -0,0 +1,1498 @@ +/******************************************************************** + * Intra-logic block router determines if a candidate packing solution (or intermediate solution) can route. + * Adapted from original VPR cluster router to the use of LbRRGraph object + * + * Global Inputs: Architecture and netlist + * Input arguments: clustering info for one cluster (t_pb info) + * Working data set: t_routing_data contains intermediate work + * Output: Routable? true/false. If true, store/return the routed solution. + * + * Routing algorithm used is Pathfinder. + *******************************************************************/ +#include +#include +#include +#include +#include +#include + +#include "vtr_assert.h" +#include "vtr_log.h" + +#include "vpr_error.h" +#include "vpr_types.h" +#include "echo_files.h" + +#include "physical_types.h" +#include "globals.h" +#include "atom_netlist.h" +#include "vpr_utils.h" +#include "pack_types.h" +#include "pb_type_graph.h" +#include "lb_type_rr_graph.h" +#include "lb_router.h" + +/* #define PRINT_INTRA_LB_ROUTE */ + +/* begin namespace openfpga */ +namespace openfpga { + +/***************************************************************************************** + * Internal data structures + ******************************************************************************************/ + +enum e_commit_remove { RT_COMMIT, + RT_REMOVE }; + +// TODO: check if this hacky class memory reserve thing is still necessary, if not, then delete +/* Packing uses a priority queue that requires a large number of elements. This backdoor + * allows me to use a priority queue where I can pre-allocate the # of elements in the underlying container + * for efficiency reasons. Note: Must use vector with this */ +template +class reservable_pq : public std::priority_queue { + public: + typedef typename std::priority_queue::size_type size_type; + reservable_pq(size_type capacity = 0) { + reserve(capacity); + cur_cap = capacity; + } + void reserve(size_type capacity) { + this->c.reserve(capacity); + cur_cap = capacity; + } + void clear() { + this->c.clear(); + this->c.reserve(cur_cap); + } + + private: + size_type cur_cap; +}; + +/***************************************************************************************** + * Internal functions declarations + ******************************************************************************************/ +static void free_lb_net_rt(t_lb_trace* lb_trace); +static void free_lb_trace(t_lb_trace* lb_trace); +static void add_pin_to_rt_terminals(t_lb_router_data* router_data, const AtomPinId pin_id); +static void remove_pin_from_rt_terminals(t_lb_router_data* router_data, const AtomPinId pin_id); + +static void fix_duplicate_equivalent_pins(t_lb_router_data* router_data); + +static void commit_remove_rt(t_lb_trace* rt, t_lb_router_data* router_data, e_commit_remove op, std::unordered_map* mode_map, t_mode_selection_status* mode_status); +static bool is_skip_route_net(t_lb_trace* rt, t_lb_router_data* router_data); +static void add_source_to_rt(t_lb_router_data* router_data, int inet); +static void expand_rt(t_lb_router_data* router_data, int inet, reservable_pq, compare_expansion_node>& pq, int irt_net); +static void expand_rt_rec(t_lb_trace* rt, int prev_index, t_explored_node_tb* explored_node_tb, reservable_pq, compare_expansion_node>& pq, int irt_net, int explore_id_index); +static bool try_expand_nodes(t_lb_router_data* router_data, + t_intra_lb_net* lb_net, + t_expansion_node* exp_node, + reservable_pq, compare_expansion_node>& pq, + int itarget, + bool try_other_modes, + int verbosity); + +static void expand_edges(t_lb_router_data* router_data, + int mode, + int cur_inode, + float cur_cost, + int net_fanout, + reservable_pq, compare_expansion_node>& pq); + +static void expand_node(t_lb_router_data* router_data, t_expansion_node exp_node, reservable_pq, compare_expansion_node>& pq, int net_fanout); +static void expand_node_all_modes(t_lb_router_data* router_data, t_expansion_node exp_node, reservable_pq, compare_expansion_node>& pq, int net_fanout); + +static bool add_to_rt(t_lb_trace* rt, int node_index, t_lb_router_data* router_data, int irt_net); +static bool is_route_success(t_lb_router_data* router_data); +static t_lb_trace* find_node_in_rt(t_lb_trace* rt, int rt_index); +static void reset_explored_node_tb(t_lb_router_data* router_data); +static void save_and_reset_lb_route(t_lb_router_data* router_data); +static void load_trace_to_pb_route(t_pb_routes& pb_route, const int total_pins, const AtomNetId net_id, const int prev_pin_id, const t_lb_trace* trace); + +static std::string describe_lb_type_rr_node(int inode, + const t_lb_router_data* router_data); + +static std::vector find_congested_rr_nodes(const std::vector& lb_type_graph, + const t_lb_rr_node_stats* lb_rr_node_stats); +static std::vector find_incoming_rr_nodes(int dst_node, const t_lb_router_data* router_data); +static std::string describe_congested_rr_nodes(const std::vector& congested_rr_nodes, + const t_lb_router_data* router_data); +/***************************************************************************************** + * Debug functions declarations + ******************************************************************************************/ +#ifdef PRINT_INTRA_LB_ROUTE +static void print_route(const char* filename, t_lb_router_data* router_data); +static void print_route(FILE* fp, t_lb_router_data* router_data); +#endif +static void print_trace(FILE* fp, t_lb_trace* trace, t_lb_router_data* router_data); + +/***************************************************************************************** + * Constructor/Destructor functions + ******************************************************************************************/ + +/** + * Build data structures used by intra-logic block router + */ +t_lb_router_data* alloc_and_load_router_data(std::vector* lb_type_graph, t_logical_block_type_ptr type) { + t_lb_router_data* router_data = new t_lb_router_data; + int size; + + router_data->lb_type_graph = lb_type_graph; + size = router_data->lb_type_graph->size(); + router_data->lb_rr_node_stats = new t_lb_rr_node_stats[size]; + router_data->explored_node_tb = new t_explored_node_tb[size]; + router_data->intra_lb_nets = new std::vector; + router_data->atoms_added = new std::map; + router_data->lb_type = type; + + return router_data; +} + +/* free data used by router */ +void free_router_data(t_lb_router_data* router_data) { + if (router_data != nullptr && router_data->lb_type_graph != nullptr) { + delete[] router_data->lb_rr_node_stats; + router_data->lb_rr_node_stats = nullptr; + delete[] router_data->explored_node_tb; + router_data->explored_node_tb = nullptr; + router_data->lb_type_graph = nullptr; + delete router_data->atoms_added; + router_data->atoms_added = nullptr; + free_intra_lb_nets(router_data->intra_lb_nets); + free_intra_lb_nets(router_data->saved_lb_nets); + router_data->intra_lb_nets = nullptr; + delete router_data; + } +} + +static bool route_has_conflict(t_lb_trace* rt, t_lb_router_data* router_data) { + std::vector& lb_type_graph = *router_data->lb_type_graph; + + int cur_mode = -1; + for (unsigned int i = 0; i < rt->next_nodes.size(); i++) { + int new_mode = get_lb_type_rr_graph_edge_mode(lb_type_graph, + rt->current_node, rt->next_nodes[i].current_node); + if (cur_mode != -1 && cur_mode != new_mode) { + return true; + } + if (route_has_conflict(&rt->next_nodes[i], router_data) == true) { + return true; + } + cur_mode = new_mode; + } + + return false; +} + +// Check one edge for mode conflict. +static bool check_edge_for_route_conflicts(std::unordered_map* mode_map, + const t_pb_graph_pin* driver_pin, + const t_pb_graph_pin* pin) { + if (driver_pin == nullptr) { + return false; + } + + // Only check pins that are OUT_PORTs. + if (pin == nullptr || pin->port == nullptr || pin->port->type != OUT_PORT) { + return false; + } + VTR_ASSERT(!pin->port->is_clock); + + auto* pb_graph_node = pin->parent_node; + VTR_ASSERT(pb_graph_node->pb_type == pin->port->parent_pb_type); + + const t_pb_graph_edge* edge = get_edge_between_pins(driver_pin, pin); + VTR_ASSERT(edge != nullptr); + + auto mode_of_edge = edge->interconnect->parent_mode_index; + auto* mode = &pb_graph_node->pb_type->modes[mode_of_edge]; + + auto result = mode_map->insert(std::make_pair(pb_graph_node, mode)); + if (!result.second) { + if (result.first->second != mode) { + std::cout << vtr::string_fmt("Differing modes for block. Got %s mode, while previously was %s for interconnect %s.", + mode->name, result.first->second->name, + edge->interconnect->name) + << std::endl; + + // The illegal mode is added to the pb_graph_node as it resulted in a conflict during atom-to-atom routing. This mode cannot be used in the consequent cluster + // generation try. + if (std::find(pb_graph_node->illegal_modes.begin(), pb_graph_node->illegal_modes.end(), result.first->second->index) == pb_graph_node->illegal_modes.end()) { + pb_graph_node->illegal_modes.push_back(result.first->second->index); + } + + // If the number of illegal modes equals the number of available mode for a specific pb_graph_node it means that no cluster can be generated. This resuts + // in a fatal error. + if ((int)pb_graph_node->illegal_modes.size() >= pb_graph_node->pb_type->num_modes) { + VPR_FATAL_ERROR(VPR_ERROR_PACK, "There are no more available modes to be used. Routing Failed!"); + } + + return true; + } + } + + return false; +} + +/***************************************************************************************** + * Routing Functions + ******************************************************************************************/ + +/* Add pins of netlist atom to to current routing drivers/targets */ +void add_atom_as_target(t_lb_router_data* router_data, const AtomBlockId blk_id) { + const t_pb* pb; + auto& atom_ctx = g_vpr_ctx.atom(); + + std::map& atoms_added = *router_data->atoms_added; + + if (atoms_added.count(blk_id) > 0) { + VPR_FATAL_ERROR(VPR_ERROR_PACK, "Atom %s added twice to router\n", atom_ctx.nlist.block_name(blk_id).c_str()); + } + + pb = atom_ctx.lookup.atom_pb(blk_id); + + VTR_ASSERT(pb); + + atoms_added[blk_id] = true; + + set_reset_pb_modes(router_data, pb, true); + + for (auto pin_id : atom_ctx.nlist.block_pins(blk_id)) { + add_pin_to_rt_terminals(router_data, pin_id); + } + + fix_duplicate_equivalent_pins(router_data); +} + +/* Remove pins of netlist atom from current routing drivers/targets */ +void remove_atom_from_target(t_lb_router_data* router_data, const AtomBlockId blk_id) { + auto& atom_ctx = g_vpr_ctx.atom(); + + std::map& atoms_added = *router_data->atoms_added; + + const t_pb* pb = atom_ctx.lookup.atom_pb(blk_id); + + if (atoms_added.count(blk_id) == 0) { + return; + } + + set_reset_pb_modes(router_data, pb, false); + + for (auto pin_id : atom_ctx.nlist.block_pins(blk_id)) { + remove_pin_from_rt_terminals(router_data, pin_id); + } + + atoms_added.erase(blk_id); +} + +/* Set/Reset mode of rr nodes to the pb used. If set == true, then set all modes of the rr nodes affected by pb to the mode of the pb. + * Set all modes related to pb to 0 otherwise */ +void set_reset_pb_modes(t_lb_router_data* router_data, const t_pb* pb, const bool set) { + t_pb_type* pb_type; + t_pb_graph_node* pb_graph_node; + int mode = pb->mode; + int inode; + + VTR_ASSERT(mode >= 0); + + pb_graph_node = pb->pb_graph_node; + pb_type = pb_graph_node->pb_type; + + /* Input and clock pin modes are based on current pb mode */ + for (int iport = 0; iport < pb_graph_node->num_input_ports; iport++) { + for (int ipin = 0; ipin < pb_graph_node->num_input_pins[iport]; ipin++) { + inode = pb_graph_node->input_pins[iport][ipin].pin_count_in_cluster; + router_data->lb_rr_node_stats[inode].mode = (set == true) ? mode : -1; + } + } + for (int iport = 0; iport < pb_graph_node->num_clock_ports; iport++) { + for (int ipin = 0; ipin < pb_graph_node->num_clock_pins[iport]; ipin++) { + inode = pb_graph_node->clock_pins[iport][ipin].pin_count_in_cluster; + router_data->lb_rr_node_stats[inode].mode = (set == true) ? mode : -1; + } + } + + /* Output pin modes are based on parent pb, so set children to use new mode + * Output pin of top-level logic block is also set to mode 0 + */ + if (pb_type->num_modes != 0) { + for (int ichild_type = 0; ichild_type < pb_type->modes[mode].num_pb_type_children; ichild_type++) { + for (int ichild = 0; ichild < pb_type->modes[mode].pb_type_children[ichild_type].num_pb; ichild++) { + t_pb_graph_node* child_pb_graph_node = &pb_graph_node->child_pb_graph_nodes[mode][ichild_type][ichild]; + for (int iport = 0; iport < child_pb_graph_node->num_output_ports; iport++) { + for (int ipin = 0; ipin < child_pb_graph_node->num_output_pins[iport]; ipin++) { + inode = child_pb_graph_node->output_pins[iport][ipin].pin_count_in_cluster; + router_data->lb_rr_node_stats[inode].mode = (set == true) ? mode : -1; + } + } + } + } + } +} + +/* Expand all the nodes for a given lb_net */ +static bool try_expand_nodes(t_lb_router_data* router_data, + t_intra_lb_net* lb_net, + t_expansion_node* exp_node, + reservable_pq, compare_expansion_node>& pq, + int itarget, + bool try_other_modes, + int verbosity) { + bool is_impossible = false; + + do { + if (pq.empty()) { + /* No connection possible */ + is_impossible = true; + + if (verbosity > 3) { + //Print detailed debug info + auto& atom_nlist = g_vpr_ctx.atom().nlist; + AtomNetId net_id = lb_net->atom_net_id; + AtomPinId driver_pin = lb_net->atom_pins[0]; + AtomPinId sink_pin = lb_net->atom_pins[itarget]; + int driver_rr_node = lb_net->terminals[0]; + int sink_rr_node = lb_net->terminals[itarget]; + + VTR_LOG("\t\t\tNo possible routing path from %s to %s: needed for net '%s' from net pin '%s'", + describe_lb_type_rr_node(driver_rr_node, router_data).c_str(), + describe_lb_type_rr_node(sink_rr_node, router_data).c_str(), + atom_nlist.net_name(net_id).c_str(), + atom_nlist.pin_name(driver_pin).c_str()); + VTR_LOGV(sink_pin, " to net pin '%s'", atom_nlist.pin_name(sink_pin).c_str()); + VTR_LOG("\n"); + } + } else { + *exp_node = pq.top(); + pq.pop(); + int exp_inode = exp_node->node_index; + + if (router_data->explored_node_tb[exp_inode].explored_id != router_data->explore_id_index) { + /* First time node is popped implies path to this node is the lowest cost. + * If the node is popped a second time, then the path to that node is higher than this path so + * ignore. + */ + router_data->explored_node_tb[exp_inode].explored_id = router_data->explore_id_index; + router_data->explored_node_tb[exp_inode].prev_index = exp_node->prev_index; + if (exp_inode != lb_net->terminals[itarget]) { + if (!try_other_modes) { + expand_node(router_data, *exp_node, pq, lb_net->terminals.size() - 1); + } else { + expand_node_all_modes(router_data, *exp_node, pq, lb_net->terminals.size() - 1); + } + } + } + } + } while (exp_node->node_index != lb_net->terminals[itarget] && !is_impossible); + + return is_impossible; +} + +/* Attempt to route routing driver/targets on the current architecture + * Follows pathfinder negotiated congestion algorithm + */ +bool try_intra_lb_route(t_lb_router_data* router_data, + int verbosity, + t_mode_selection_status* mode_status) { + std::vector& lb_nets = *router_data->intra_lb_nets; + std::vector& lb_type_graph = *router_data->lb_type_graph; + bool is_routed = false; + bool is_impossible = false; + + mode_status->is_mode_conflict = false; + mode_status->try_expand_all_modes = false; + + t_expansion_node exp_node; + + /* Stores state info during route */ + reservable_pq, compare_expansion_node> pq; + + reset_explored_node_tb(router_data); + + /* Reset current routing */ + for (unsigned int inet = 0; inet < lb_nets.size(); inet++) { + free_lb_net_rt(lb_nets[inet].rt_tree); + lb_nets[inet].rt_tree = nullptr; + } + for (unsigned int inode = 0; inode < lb_type_graph.size(); inode++) { + router_data->lb_rr_node_stats[inode].historical_usage = 0; + router_data->lb_rr_node_stats[inode].occ = 0; + } + + std::unordered_map mode_map; + + /* Iteratively remove congestion until a successful route is found. + * Cap the total number of iterations tried so that if a solution does not exist, then the router won't run indefinitely */ + router_data->pres_con_fac = router_data->params.pres_fac; + for (int iter = 0; iter < router_data->params.max_iterations && !is_routed && !is_impossible; iter++) { + unsigned int inet; + /* Iterate across all nets internal to logic block */ + for (inet = 0; inet < lb_nets.size() && !is_impossible; inet++) { + int idx = inet; + if (is_skip_route_net(lb_nets[idx].rt_tree, router_data)) { + continue; + } + commit_remove_rt(lb_nets[idx].rt_tree, router_data, RT_REMOVE, &mode_map, mode_status); + free_lb_net_rt(lb_nets[idx].rt_tree); + lb_nets[idx].rt_tree = nullptr; + add_source_to_rt(router_data, idx); + + /* Route each sink of net */ + for (unsigned int itarget = 1; itarget < lb_nets[idx].terminals.size() && !is_impossible; itarget++) { + pq.clear(); + /* Get lowest cost next node, repeat until a path is found or if it is impossible to route */ + + expand_rt(router_data, idx, pq, idx); + + is_impossible = try_expand_nodes(router_data, &lb_nets[idx], &exp_node, pq, itarget, mode_status->expand_all_modes, verbosity); + + if (is_impossible && !mode_status->expand_all_modes) { + mode_status->try_expand_all_modes = true; + mode_status->expand_all_modes = true; + break; + } + + if (exp_node.node_index == lb_nets[idx].terminals[itarget]) { + /* Net terminal is routed, add this to the route tree, clear data structures, and keep going */ + is_impossible = add_to_rt(lb_nets[idx].rt_tree, exp_node.node_index, router_data, idx); + } + + if (verbosity > 5) { + VTR_LOG("Routing finished\n"); + VTR_LOG("\tS"); + print_trace(stdout, lb_nets[idx].rt_tree, router_data); + VTR_LOG("\n"); + } + + if (is_impossible) { + VTR_LOG("Routing was impossible!\n"); + } else if (mode_status->expand_all_modes) { + is_impossible = route_has_conflict(lb_nets[idx].rt_tree, router_data); + if (is_impossible) { + VTR_LOG("Routing was impossible due to modes!\n"); + } + } + + router_data->explore_id_index++; + if (router_data->explore_id_index > 2000000000) { + /* overflow protection */ + for (unsigned int id = 0; id < lb_type_graph.size(); id++) { + router_data->explored_node_tb[id].explored_id = OPEN; + router_data->explored_node_tb[id].enqueue_id = OPEN; + router_data->explore_id_index = 1; + } + } + } + + if (!is_impossible) { + commit_remove_rt(lb_nets[idx].rt_tree, router_data, RT_COMMIT, &mode_map, mode_status); + if (mode_status->is_mode_conflict) { + is_impossible = true; + } + } + } + + if (!is_impossible) { + is_routed = is_route_success(router_data); + } else { + --inet; + auto& atom_ctx = g_vpr_ctx.atom(); + VTR_LOGV(verbosity < 3, "Net '%s' is impossible to route within proposed %s cluster\n", + atom_ctx.nlist.net_name(lb_nets[inet].atom_net_id).c_str(), router_data->lb_type->name); + is_routed = false; + } + router_data->pres_con_fac *= router_data->params.pres_fac_mult; + } + + if (is_routed) { + save_and_reset_lb_route(router_data); + } else { + //Unroutable +#ifdef PRINT_INTRA_LB_ROUTE + print_route(getEchoFileName(E_ECHO_INTRA_LB_FAILED_ROUTE), router_data); +#endif + + if (verbosity > 3 && !is_impossible) { + //Report the congested nodes and associated nets + auto congested_rr_nodes = find_congested_rr_nodes(lb_type_graph, router_data->lb_rr_node_stats); + if (!congested_rr_nodes.empty()) { + VTR_LOG("%s\n", describe_congested_rr_nodes(congested_rr_nodes, router_data).c_str()); + } + } + + //Clean-up + for (unsigned int inet = 0; inet < lb_nets.size(); inet++) { + free_lb_net_rt(lb_nets[inet].rt_tree); + lb_nets[inet].rt_tree = nullptr; + } + } + return is_routed; +} + +/***************************************************************************************** + * Accessor Functions + ******************************************************************************************/ + +/* Creates an array [0..num_pb_graph_pins-1] lookup for intra-logic block routing. Given pb_graph_pin id for clb, lookup atom net that uses that pin. + * If pin is not used, stores OPEN at that pin location */ +t_pb_routes alloc_and_load_pb_route(const std::vector* intra_lb_nets, t_pb_graph_node* pb_graph_head) { + const std::vector& lb_nets = *intra_lb_nets; + int total_pins = pb_graph_head->total_pb_pins; + t_pb_routes pb_route; + + for (int inet = 0; inet < (int)lb_nets.size(); inet++) { + load_trace_to_pb_route(pb_route, total_pins, lb_nets[inet].atom_net_id, OPEN, lb_nets[inet].rt_tree); + } + + return pb_route; +} + +/* Free pin-to-atomic_net array lookup */ +void free_pb_route(t_pb_route* pb_route) { + if (pb_route != nullptr) { + delete[] pb_route; + } +} + +void free_intra_lb_nets(std::vector* intra_lb_nets) { + if (intra_lb_nets == nullptr) { + return; + } + std::vector& lb_nets = *intra_lb_nets; + for (unsigned int i = 0; i < lb_nets.size(); i++) { + lb_nets[i].terminals.clear(); + free_lb_net_rt(lb_nets[i].rt_tree); + lb_nets[i].rt_tree = nullptr; + } + delete intra_lb_nets; +} + +/*************************************************************************** + * Internal Functions + ****************************************************************************/ + +/* Recurse through route tree trace to populate pb pin to atom net lookup array */ +static void load_trace_to_pb_route(t_pb_routes& pb_route, const int total_pins, const AtomNetId net_id, const int prev_pin_id, const t_lb_trace* trace) { + int ipin = trace->current_node; + int driver_pb_pin_id = prev_pin_id; + int cur_pin_id = OPEN; + if (ipin < total_pins) { + /* This routing node corresponds with a pin. This node is virtual (ie. sink or source node) */ + cur_pin_id = ipin; + if (!pb_route.count(ipin)) { + pb_route.insert(std::make_pair(cur_pin_id, t_pb_route())); + pb_route[cur_pin_id].atom_net_id = net_id; + pb_route[cur_pin_id].driver_pb_pin_id = driver_pb_pin_id; + } else { + VTR_ASSERT(pb_route[cur_pin_id].atom_net_id == net_id); + } + } + for (int itrace = 0; itrace < (int)trace->next_nodes.size(); itrace++) { + load_trace_to_pb_route(pb_route, total_pins, net_id, cur_pin_id, &trace->next_nodes[itrace]); + } +} + +/* Free route tree for intra-logic block routing */ +static void free_lb_net_rt(t_lb_trace* lb_trace) { + if (lb_trace != nullptr) { + for (unsigned int i = 0; i < lb_trace->next_nodes.size(); i++) { + free_lb_trace(&lb_trace->next_nodes[i]); + } + lb_trace->next_nodes.clear(); + delete lb_trace; + } +} + +/* Free trace for intra-logic block routing */ +static void free_lb_trace(t_lb_trace* lb_trace) { + if (lb_trace != nullptr) { + for (unsigned int i = 0; i < lb_trace->next_nodes.size(); i++) { + free_lb_trace(&lb_trace->next_nodes[i]); + } + lb_trace->next_nodes.clear(); + } +} + +/* Given a pin of a net, assign route tree terminals for it + * Assumes that pin is not already assigned + */ +static void add_pin_to_rt_terminals(t_lb_router_data* router_data, const AtomPinId pin_id) { + std::vector& lb_nets = *router_data->intra_lb_nets; + std::vector& lb_type_graph = *router_data->lb_type_graph; + t_logical_block_type_ptr lb_type = router_data->lb_type; + bool found = false; + unsigned int ipos; + auto& atom_ctx = g_vpr_ctx.atom(); + + const t_pb_graph_pin* pb_graph_pin = find_pb_graph_pin(atom_ctx.nlist, atom_ctx.lookup, pin_id); + VTR_ASSERT(pb_graph_pin); + + AtomPortId port_id = atom_ctx.nlist.pin_port(pin_id); + AtomNetId net_id = atom_ctx.nlist.pin_net(pin_id); + + if (!net_id) { + //No net connected to this pin, so nothing to route + return; + } + + /* Find if current net is in route tree, if not, then add to rt. + * Code assumes that # of nets in cluster is small so a linear search through + * vector is faster than using more complex data structures + */ + for (ipos = 0; ipos < lb_nets.size(); ipos++) { + if (lb_nets[ipos].atom_net_id == net_id) { + found = true; + break; + } + } + if (found == false) { + struct t_intra_lb_net new_net; + + new_net.atom_net_id = net_id; + + ipos = lb_nets.size(); + lb_nets.push_back(new_net); + } + VTR_ASSERT(lb_nets[ipos].atom_net_id == net_id); + VTR_ASSERT(lb_nets[ipos].atom_pins.size() == lb_nets[ipos].terminals.size()); + + /* + * Determine whether or not this is a new intra lb net, if yes, then add to list of intra lb nets + */ + if (lb_nets[ipos].terminals.empty()) { + /* Add terminals */ + + //Default assumption is that the source is outside the current cluster (will be overriden later if required) + int source_terminal = get_lb_type_rr_graph_ext_source_index(lb_type); + lb_nets[ipos].terminals.push_back(source_terminal); + + AtomPinId net_driver_pin_id = atom_ctx.nlist.net_driver(net_id); + lb_nets[ipos].atom_pins.push_back(net_driver_pin_id); + + VTR_ASSERT_MSG(lb_type_graph[lb_nets[ipos].terminals[0]].type == LB_SOURCE, "Driver must be a source"); + } + + VTR_ASSERT(lb_nets[ipos].atom_pins.size() == lb_nets[ipos].terminals.size()); + + if (atom_ctx.nlist.port_type(port_id) == PortType::OUTPUT) { + //The current pin is the net driver, overwrite the default driver at index 0 + VTR_ASSERT_MSG(lb_nets[ipos].terminals[0] == get_lb_type_rr_graph_ext_source_index(lb_type), "Default driver must be external source"); + + VTR_ASSERT(atom_ctx.nlist.pin_type(pin_id) == PinType::DRIVER); + + //Override the default since this is the driver, and it is within the cluster + lb_nets[ipos].terminals[0] = pb_graph_pin->pin_count_in_cluster; + lb_nets[ipos].atom_pins[0] = pin_id; + + VTR_ASSERT_MSG(lb_type_graph[lb_nets[ipos].terminals[0]].type == LB_SOURCE, "Driver must be a source"); + + int sink_terminal = OPEN; + if (lb_nets[ipos].terminals.size() < atom_ctx.nlist.net_pins(net_id).size()) { + //Not all of the pins are within the cluster + if (lb_nets[ipos].terminals.size() == 1) { + //Only the source has been specified so far, must add cluster-external sink + sink_terminal = get_lb_type_rr_graph_ext_sink_index(lb_type); + lb_nets[ipos].terminals.push_back(sink_terminal); + lb_nets[ipos].atom_pins.push_back(AtomPinId::INVALID()); + } else { + VTR_ASSERT(lb_nets[ipos].terminals.size() > 1); + + //TODO: Figure out why we swap terminal 1 here (although it appears to work correctly, + // it's not clear why this is needed...) + + //Move current terminal 1 to end + sink_terminal = lb_nets[ipos].terminals[1]; + AtomPinId sink_atom_pin = lb_nets[ipos].atom_pins[1]; + lb_nets[ipos].terminals.push_back(sink_terminal); + lb_nets[ipos].atom_pins.push_back(sink_atom_pin); + + //Create external sink at terminal 1 + sink_terminal = get_lb_type_rr_graph_ext_sink_index(lb_type); + lb_nets[ipos].terminals[1] = sink_terminal; + lb_nets[ipos].atom_pins[1] = AtomPinId::INVALID(); + } + VTR_ASSERT(lb_type_graph[lb_nets[ipos].terminals[1]].type == LB_SINK); + } + } else { + //This is an input to a primitive + VTR_ASSERT(atom_ctx.nlist.port_type(port_id) == PortType::INPUT + || atom_ctx.nlist.port_type(port_id) == PortType::CLOCK); + VTR_ASSERT(atom_ctx.nlist.pin_type(pin_id) == PinType::SINK); + + //Get the rr node index associated with the pin + int pin_index = pb_graph_pin->pin_count_in_cluster; + VTR_ASSERT(lb_type_graph[pin_index].num_modes == 1); + VTR_ASSERT(lb_type_graph[pin_index].num_fanout[0] == 1); + + /* We actually route to the sink (to handle logically equivalent pins). + * The sink is one past the primitive input pin */ + int sink_index = lb_type_graph[pin_index].outedges[0][0].node_index; + VTR_ASSERT(lb_type_graph[sink_index].type == LB_SINK); + + if (lb_nets[ipos].terminals.size() == atom_ctx.nlist.net_pins(net_id).size() && lb_nets[ipos].terminals[1] == get_lb_type_rr_graph_ext_sink_index(lb_type)) { + /* If all sinks of net are all contained in the logic block, then the net does + * not need to route out of the logic block, so can replace the external sink + * with this last sink terminal */ + lb_nets[ipos].terminals[1] = sink_index; + lb_nets[ipos].atom_pins[1] = pin_id; + } else { + //Add as a regular sink + lb_nets[ipos].terminals.push_back(sink_index); + lb_nets[ipos].atom_pins.push_back(pin_id); + } + } + VTR_ASSERT(lb_nets[ipos].atom_pins.size() == lb_nets[ipos].terminals.size()); + + int num_lb_terminals = lb_nets[ipos].terminals.size(); + VTR_ASSERT(num_lb_terminals <= (int)atom_ctx.nlist.net_pins(net_id).size()); + VTR_ASSERT(num_lb_terminals >= 0); + +#ifdef VTR_ASSERT_SAFE_ENABLED + //Sanity checks + int num_extern_sources = 0; + int num_extern_sinks = 0; + for (size_t iterm = 0; iterm < lb_nets[ipos].terminals.size(); ++iterm) { + int inode = lb_nets[ipos].terminals[iterm]; + AtomPinId atom_pin = lb_nets[ipos].atom_pins[iterm]; + if (iterm == 0) { + //Net driver + VTR_ASSERT_SAFE_MSG(lb_type_graph[inode].type == LB_SOURCE, "Driver must be a source RR node"); + VTR_ASSERT_SAFE_MSG(atom_pin, "Driver have an associated atom pin"); + VTR_ASSERT_SAFE_MSG(atom_ctx.nlist.pin_type(atom_pin) == PinType::DRIVER, "Source RR must be associated with a driver pin in atom netlist"); + if (inode == get_lb_type_rr_graph_ext_source_index(lb_type)) { + ++num_extern_sources; + } + } else { + //Net sink + VTR_ASSERT_SAFE_MSG(lb_type_graph[inode].type == LB_SINK, "Non-driver must be a sink"); + + if (inode == get_lb_type_rr_graph_ext_sink_index(lb_type)) { + //External sink may have multiple potentially matching atom pins, so it's atom pin is left invalid + VTR_ASSERT_SAFE_MSG(!atom_pin, "Cluster external sink should have no valid atom pin"); + ++num_extern_sinks; + } else { + VTR_ASSERT_SAFE_MSG(atom_pin, "Intra-cluster sink must have an associated atom pin"); + VTR_ASSERT_SAFE_MSG(atom_ctx.nlist.pin_type(atom_pin) == PinType::SINK, "Intra-cluster Sink RR must be associated with a sink pin in atom netlist"); + } + } + } + VTR_ASSERT_SAFE_MSG(num_extern_sinks >= 0 && num_extern_sinks <= 1, "Net must have at most one external sink"); + VTR_ASSERT_SAFE_MSG(num_extern_sources >= 0 && num_extern_sources <= 1, "Net must have at most one external source"); +#endif +} + +/* Given a pin of a net, remove route tree terminals from it + */ +static void remove_pin_from_rt_terminals(t_lb_router_data* router_data, const AtomPinId pin_id) { + std::vector& lb_nets = *router_data->intra_lb_nets; + std::vector& lb_type_graph = *router_data->lb_type_graph; + t_logical_block_type_ptr lb_type = router_data->lb_type; + bool found = false; + unsigned int ipos; + auto& atom_ctx = g_vpr_ctx.atom(); + + const t_pb_graph_pin* pb_graph_pin = find_pb_graph_pin(atom_ctx.nlist, atom_ctx.lookup, pin_id); + + AtomPortId port_id = atom_ctx.nlist.pin_port(pin_id); + AtomNetId net_id = atom_ctx.nlist.pin_net(pin_id); + + if (!net_id) { + /* This is not a valid net */ + return; + } + + /* Find current net in route tree + * Code assumes that # of nets in cluster is small so a linear search through vector is faster than using more complex data structures + */ + for (ipos = 0; ipos < lb_nets.size(); ipos++) { + if (lb_nets[ipos].atom_net_id == net_id) { + found = true; + break; + } + } + VTR_ASSERT(found == true); + VTR_ASSERT(lb_nets[ipos].atom_net_id == net_id); + + VTR_ASSERT(lb_nets[ipos].atom_pins.size() == lb_nets[ipos].terminals.size()); + + auto port_type = atom_ctx.nlist.port_type(port_id); + if (port_type == PortType::OUTPUT) { + /* Net driver pin takes 0th position in terminals */ + int sink_terminal; + VTR_ASSERT(lb_nets[ipos].terminals[0] == pb_graph_pin->pin_count_in_cluster); + lb_nets[ipos].terminals[0] = get_lb_type_rr_graph_ext_source_index(lb_type); + + /* source terminal is now coming from outside logic block, do not need to route signal out of logic block */ + sink_terminal = get_lb_type_rr_graph_ext_sink_index(lb_type); + if (lb_nets[ipos].terminals[1] == sink_terminal) { + lb_nets[ipos].terminals[1] = lb_nets[ipos].terminals.back(); + lb_nets[ipos].terminals.pop_back(); + + lb_nets[ipos].atom_pins[1] = lb_nets[ipos].atom_pins.back(); + lb_nets[ipos].atom_pins.pop_back(); + } + } else { + VTR_ASSERT(port_type == PortType::INPUT || port_type == PortType::CLOCK); + + /* Remove sink from list of terminals */ + int pin_index = pb_graph_pin->pin_count_in_cluster; + unsigned int iterm; + + VTR_ASSERT(lb_type_graph[pin_index].num_modes == 1); + VTR_ASSERT(lb_type_graph[pin_index].num_fanout[0] == 1); + int sink_index = lb_type_graph[pin_index].outedges[0][0].node_index; + VTR_ASSERT(lb_type_graph[sink_index].type == LB_SINK); + + int target_index = -1; + //Search for the sink + found = false; + for (iterm = 0; iterm < lb_nets[ipos].terminals.size(); iterm++) { + if (lb_nets[ipos].terminals[iterm] == sink_index) { + target_index = sink_index; + found = true; + break; + } + } + if (!found) { + //Search for the pin + found = false; + for (iterm = 0; iterm < lb_nets[ipos].terminals.size(); iterm++) { + if (lb_nets[ipos].terminals[iterm] == pin_index) { + target_index = pin_index; + found = true; + break; + } + } + } + VTR_ASSERT(found == true); + VTR_ASSERT(lb_nets[ipos].terminals[iterm] == target_index); + VTR_ASSERT(iterm > 0); + + /* Drop terminal from list */ + lb_nets[ipos].terminals[iterm] = lb_nets[ipos].terminals.back(); + lb_nets[ipos].terminals.pop_back(); + + lb_nets[ipos].atom_pins[iterm] = lb_nets[ipos].atom_pins.back(); + lb_nets[ipos].atom_pins.pop_back(); + + if (lb_nets[ipos].terminals.size() == 1 && lb_nets[ipos].terminals[0] != get_lb_type_rr_graph_ext_source_index(lb_type)) { + /* The removed sink must be driven by an atom found in the cluster, add in special sink outside of cluster to represent this */ + lb_nets[ipos].terminals.push_back(get_lb_type_rr_graph_ext_sink_index(lb_type)); + lb_nets[ipos].atom_pins.push_back(AtomPinId::INVALID()); + } + + if (lb_nets[ipos].terminals.size() > 1 && lb_nets[ipos].terminals[1] != get_lb_type_rr_graph_ext_sink_index(lb_type) && lb_nets[ipos].terminals[0] != get_lb_type_rr_graph_ext_source_index(lb_type)) { + /* The removed sink must be driven by an atom found in the cluster, add in special sink outside of cluster to represent this */ + int terminal = lb_nets[ipos].terminals[1]; + lb_nets[ipos].terminals.push_back(terminal); + lb_nets[ipos].terminals[1] = get_lb_type_rr_graph_ext_sink_index(lb_type); + + AtomPinId pin = lb_nets[ipos].atom_pins[1]; + lb_nets[ipos].atom_pins.push_back(pin); + lb_nets[ipos].atom_pins[1] = AtomPinId::INVALID(); + } + } + VTR_ASSERT(lb_nets[ipos].atom_pins.size() == lb_nets[ipos].terminals.size()); + + if (lb_nets[ipos].terminals.size() == 1 && lb_nets[ipos].terminals[0] == get_lb_type_rr_graph_ext_source_index(lb_type)) { + /* This net is not routed, remove from list of nets in lb */ + lb_nets[ipos] = lb_nets.back(); + lb_nets.pop_back(); + } +} + +//It is possible that a net may connect multiple times to a logically equivalent set of primitive pins. +//The cluster router will only route one connection for a particular net to the common sink of the +//equivalent pins. +// +//To work around this, we fix all but one of these duplicate connections to route to specific pins, +//(instead of the common sink). This ensures a legal routing is produced and that the duplicate pins +//are not 'missing' in the clustered netlist. +static void fix_duplicate_equivalent_pins(t_lb_router_data* router_data) { + auto& atom_ctx = g_vpr_ctx.atom(); + + std::vector& lb_type_graph = *router_data->lb_type_graph; + std::vector& lb_nets = *router_data->intra_lb_nets; + + for (size_t ilb_net = 0; ilb_net < lb_nets.size(); ++ilb_net) { + //Collect all the sink terminals indicies which target a particular node + std::map> duplicate_terminals; + for (size_t iterm = 1; iterm < lb_nets[ilb_net].terminals.size(); ++iterm) { + int node = lb_nets[ilb_net].terminals[iterm]; + + duplicate_terminals[node].push_back(iterm); + } + + for (auto kv : duplicate_terminals) { + if (kv.second.size() < 2) continue; //Only process duplicates + + //Remap all the duplicate terminals so they target the pin instead of the sink + for (size_t idup_term = 0; idup_term < kv.second.size(); ++idup_term) { + int iterm = kv.second[idup_term]; //The index in terminals which is duplicated + + VTR_ASSERT(lb_nets[ilb_net].atom_pins.size() == lb_nets[ilb_net].terminals.size()); + AtomPinId atom_pin = lb_nets[ilb_net].atom_pins[iterm]; + VTR_ASSERT(atom_pin); + + const t_pb_graph_pin* pb_graph_pin = find_pb_graph_pin(atom_ctx.nlist, atom_ctx.lookup, atom_pin); + VTR_ASSERT(pb_graph_pin); + + if (pb_graph_pin->port->equivalent == PortEquivalence::NONE) continue; //Only need to remap equivalent ports + + //Remap this terminal to an explicit pin instead of the common sink + int pin_index = pb_graph_pin->pin_count_in_cluster; + + VTR_LOG_WARN( + "Found duplicate nets connected to logically equivalent pins. " + "Remapping intra lb net %d (atom net %zu '%s') from common sink " + "pb_route %d to fixed pin pb_route %d\n", + ilb_net, size_t(lb_nets[ilb_net].atom_net_id), atom_ctx.nlist.net_name(lb_nets[ilb_net].atom_net_id).c_str(), + kv.first, pin_index); + + VTR_ASSERT(lb_type_graph[pin_index].type == LB_INTERMEDIATE); + VTR_ASSERT(lb_type_graph[pin_index].num_fanout[0] == 1); + int sink_index = lb_type_graph[pin_index].outedges[0][0].node_index; + VTR_ASSERT(lb_type_graph[sink_index].type == LB_SINK); + VTR_ASSERT_MSG(sink_index == lb_nets[ilb_net].terminals[iterm], "Remapped pin must be connected to original sink"); + + //Change the target + lb_nets[ilb_net].terminals[iterm] = pin_index; + } + } + } +} + +/* Commit or remove route tree from currently routed solution */ +static void commit_remove_rt(t_lb_trace* rt, t_lb_router_data* router_data, e_commit_remove op, std::unordered_map* mode_map, t_mode_selection_status* mode_status) { + t_lb_rr_node_stats* lb_rr_node_stats; + t_explored_node_tb* explored_node_tb; + std::vector& lb_type_graph = *router_data->lb_type_graph; + int inode; + int incr; + + lb_rr_node_stats = router_data->lb_rr_node_stats; + explored_node_tb = router_data->explored_node_tb; + + if (rt == nullptr) { + return; + } + + inode = rt->current_node; + + /* Determine if node is being used or removed */ + if (op == RT_COMMIT) { + incr = 1; + if (lb_rr_node_stats[inode].occ >= lb_type_graph[inode].capacity) { + lb_rr_node_stats[inode].historical_usage += (lb_rr_node_stats[inode].occ - lb_type_graph[inode].capacity + 1); /* store historical overuse */ + } + } else { + incr = -1; + explored_node_tb[inode].inet = OPEN; + } + + lb_rr_node_stats[inode].occ += incr; + VTR_ASSERT(lb_rr_node_stats[inode].occ >= 0); + + auto& driver_node = lb_type_graph[inode]; + auto* driver_pin = driver_node.pb_graph_pin; + + /* Recursively update route tree */ + for (unsigned int i = 0; i < rt->next_nodes.size(); i++) { + // Check to see if there is no mode conflict between previous nets. + // A conflict is present if there are differing modes between a pb_graph_node + // and its children. + if (op == RT_COMMIT && mode_status->try_expand_all_modes) { + auto& node = lb_type_graph[rt->next_nodes[i].current_node]; + auto* pin = node.pb_graph_pin; + + if (check_edge_for_route_conflicts(mode_map, driver_pin, pin)) { + mode_status->is_mode_conflict = true; + } + } + + commit_remove_rt(&rt->next_nodes[i], router_data, op, mode_map, mode_status); + } +} + +/* Should net be skipped? If the net does not conflict with another net, then skip routing this net */ +static bool is_skip_route_net(t_lb_trace* rt, t_lb_router_data* router_data) { + t_lb_rr_node_stats* lb_rr_node_stats; + std::vector& lb_type_graph = *router_data->lb_type_graph; + int inode; + + lb_rr_node_stats = router_data->lb_rr_node_stats; + + if (rt == nullptr) { + return false; /* Net is not routed, therefore must route net */ + } + + inode = rt->current_node; + + /* Determine if node is overused */ + if (lb_rr_node_stats[inode].occ > lb_type_graph[inode].capacity) { + /* Conflict between this net and another net at this node, reroute net */ + return false; + } + + /* Recursively check that rest of route tree does not have a conflict */ + for (unsigned int i = 0; i < rt->next_nodes.size(); i++) { + if (!is_skip_route_net(&rt->next_nodes[i], router_data)) { + return false; + } + } + + /* No conflict, this net's current route is legal, skip routing this net */ + return true; +} + +/* At source mode as starting point to existing route tree */ +static void add_source_to_rt(t_lb_router_data* router_data, int inet) { + VTR_ASSERT((*router_data->intra_lb_nets)[inet].rt_tree == nullptr); + (*router_data->intra_lb_nets)[inet].rt_tree = new t_lb_trace; + (*router_data->intra_lb_nets)[inet].rt_tree->current_node = (*router_data->intra_lb_nets)[inet].terminals[0]; +} + +/* Expand all nodes found in route tree into priority queue */ +static void expand_rt(t_lb_router_data* router_data, int inet, reservable_pq, compare_expansion_node>& pq, int irt_net) { + std::vector& lb_nets = *router_data->intra_lb_nets; + + VTR_ASSERT(pq.empty()); + + expand_rt_rec(lb_nets[inet].rt_tree, OPEN, router_data->explored_node_tb, pq, irt_net, router_data->explore_id_index); +} + +/* Expand all nodes found in route tree into priority queue recursively */ +static void expand_rt_rec(t_lb_trace* rt, int prev_index, t_explored_node_tb* explored_node_tb, reservable_pq, compare_expansion_node>& pq, int irt_net, int explore_id_index) { + t_expansion_node enode; + + /* Perhaps should use a cost other than zero */ + enode.cost = 0; + enode.node_index = rt->current_node; + enode.prev_index = prev_index; + pq.push(enode); + explored_node_tb[enode.node_index].inet = irt_net; + explored_node_tb[enode.node_index].explored_id = OPEN; + explored_node_tb[enode.node_index].enqueue_id = explore_id_index; + explored_node_tb[enode.node_index].enqueue_cost = 0; + explored_node_tb[enode.node_index].prev_index = prev_index; + + for (unsigned int i = 0; i < rt->next_nodes.size(); i++) { + expand_rt_rec(&rt->next_nodes[i], rt->current_node, explored_node_tb, pq, irt_net, explore_id_index); + } +} + +/* Expand all edges of an expantion node */ +static void expand_edges(t_lb_router_data* router_data, + int mode, + int cur_inode, + float cur_cost, + int net_fanout, + reservable_pq, compare_expansion_node>& pq) { + std::vector& lb_type_graph = *router_data->lb_type_graph; + t_lb_rr_node_stats* lb_rr_node_stats = router_data->lb_rr_node_stats; + t_lb_router_params params = router_data->params; + t_expansion_node enode; + int usage; + float incr_cost; + + for (int iedge = 0; iedge < lb_type_graph[cur_inode].num_fanout[mode]; iedge++) { + /* Init new expansion node */ + enode.prev_index = cur_inode; + enode.node_index = lb_type_graph[cur_inode].outedges[mode][iedge].node_index; + enode.cost = cur_cost; + + /* Determine incremental cost of using expansion node */ + usage = lb_rr_node_stats[enode.node_index].occ + 1 - lb_type_graph[enode.node_index].capacity; + incr_cost = lb_type_graph[enode.node_index].intrinsic_cost; + incr_cost += lb_type_graph[cur_inode].outedges[mode][iedge].intrinsic_cost; + incr_cost += params.hist_fac * lb_rr_node_stats[enode.node_index].historical_usage; + if (usage > 0) { + incr_cost *= (usage * router_data->pres_con_fac); + } + + /* Adjust cost so that higher fanout nets prefer higher fanout routing nodes while lower fanout nets prefer lower fanout routing nodes */ + float fanout_factor = 1.0; + int next_mode = lb_rr_node_stats[enode.node_index].mode; + /* Assume first mode if a mode hasn't been forced. */ + if (next_mode == -1) { + next_mode = 0; + } + if (lb_type_graph[enode.node_index].num_fanout[next_mode] > 1) { + fanout_factor = 0.85 + (0.25 / net_fanout); + } else { + fanout_factor = 1.15 - (0.25 / net_fanout); + } + + incr_cost *= fanout_factor; + enode.cost = cur_cost + incr_cost; + + /* Add to queue if cost is lower than lowest cost path to this enode */ + if (router_data->explored_node_tb[enode.node_index].enqueue_id == router_data->explore_id_index) { + if (enode.cost < router_data->explored_node_tb[enode.node_index].enqueue_cost) { + pq.push(enode); + } + } else { + router_data->explored_node_tb[enode.node_index].enqueue_id = router_data->explore_id_index; + router_data->explored_node_tb[enode.node_index].enqueue_cost = enode.cost; + pq.push(enode); + } + } +} + +/* Expand all nodes found in route tree into priority queue */ +static void expand_node(t_lb_router_data* router_data, t_expansion_node exp_node, reservable_pq, compare_expansion_node>& pq, int net_fanout) { + int cur_node; + float cur_cost; + int mode; + t_expansion_node enode; + t_lb_rr_node_stats* lb_rr_node_stats = router_data->lb_rr_node_stats; + + cur_node = exp_node.node_index; + cur_cost = exp_node.cost; + mode = lb_rr_node_stats[cur_node].mode; + if (mode == -1) { + mode = 0; + } + + expand_edges(router_data, mode, cur_node, cur_cost, net_fanout, pq); +} + +/* Expand all nodes using all possible modes found in route tree into priority queue */ +static void expand_node_all_modes(t_lb_router_data* router_data, t_expansion_node exp_node, reservable_pq, compare_expansion_node>& pq, int net_fanout) { + std::vector& lb_type_graph = *router_data->lb_type_graph; + t_lb_rr_node_stats* lb_rr_node_stats = router_data->lb_rr_node_stats; + + int cur_inode = exp_node.node_index; + float cur_cost = exp_node.cost; + int cur_mode = lb_rr_node_stats[cur_inode].mode; + auto& node = lb_type_graph[cur_inode]; + auto* pin = node.pb_graph_pin; + + for (int mode = 0; mode < lb_type_graph[cur_inode].num_modes; mode++) { + /* If a mode has been forced, only add edges from that mode, otherwise add edges from all modes. */ + if (cur_mode != -1 && mode != cur_mode) { + continue; + } + + /* Check whether a mode is illegal. If it is then the node will not be expanded */ + bool is_illegal = false; + if (pin != nullptr) { + auto* pb_graph_node = pin->parent_node; + for (auto illegal_mode : pb_graph_node->illegal_modes) { + if (mode == illegal_mode) { + is_illegal = true; + break; + } + } + } + + if (is_illegal == true) { + continue; + } + + expand_edges(router_data, mode, cur_inode, cur_cost, net_fanout, pq); + } +} + +/* Add new path from existing route tree to target sink */ +static bool add_to_rt(t_lb_trace* rt, int node_index, t_lb_router_data* router_data, int irt_net) { + t_explored_node_tb* explored_node_tb = router_data->explored_node_tb; + std::vector trace_forward; + int rt_index, trace_index; + t_lb_trace* link_node; + t_lb_trace curr_node; + + /* Store path all the way back to route tree */ + rt_index = node_index; + while (explored_node_tb[rt_index].inet != irt_net) { + trace_forward.push_back(rt_index); + rt_index = explored_node_tb[rt_index].prev_index; + VTR_ASSERT(rt_index != OPEN); + } + + /* Find rt_index on the route tree */ + link_node = find_node_in_rt(rt, rt_index); + if (link_node == nullptr) { + VTR_LOG("Link node is nullptr. Routing impossible"); + return true; + } + + /* Add path to root tree */ + while (!trace_forward.empty()) { + trace_index = trace_forward.back(); + curr_node.current_node = trace_index; + link_node->next_nodes.push_back(curr_node); + link_node = &link_node->next_nodes.back(); + trace_forward.pop_back(); + } + + return false; +} + +/* Determine if a completed route is valid. A successful route has no congestion (ie. no routing resource is used by two nets). */ +static bool is_route_success(t_lb_router_data* router_data) { + std::vector& lb_type_graph = *router_data->lb_type_graph; + + for (unsigned int inode = 0; inode < lb_type_graph.size(); inode++) { + if (router_data->lb_rr_node_stats[inode].occ > lb_type_graph[inode].capacity) { + return false; + } + } + + return true; +} + +/* Given a route tree and an index of a node on the route tree, return a pointer to the trace corresponding to that index */ +static t_lb_trace* find_node_in_rt(t_lb_trace* rt, int rt_index) { + t_lb_trace* cur; + if (rt->current_node == rt_index) { + return rt; + } else { + for (unsigned int i = 0; i < rt->next_nodes.size(); i++) { + cur = find_node_in_rt(&rt->next_nodes[i], rt_index); + if (cur != nullptr) { + return cur; + } + } + } + return nullptr; +} + +#ifdef PRINT_INTRA_LB_ROUTE +/* Debug routine, print out current intra logic block route */ +static void print_route(const char* filename, t_lb_router_data* router_data) { + FILE* fp; + std::vector& lb_type_graph = *router_data->lb_type_graph; + + fp = fopen(filename, "w"); + for (unsigned int inode = 0; inode < lb_type_graph.size(); inode++) { + fprintf(fp, "node %d occ %d cap %d\n", inode, router_data->lb_rr_node_stats[inode].occ, lb_type_graph[inode].capacity); + } + + print_route(fp, router_data); + fclose(fp); +} + +static void print_route(FILE* fp, t_lb_router_data* router_data) { + std::vector& lb_nets = *router_data->intra_lb_nets; + fprintf(fp, "\n\n----------------------------------------------------\n\n"); + + auto& atom_ctx = g_vpr_ctx.atom(); + + for (unsigned int inet = 0; inet < lb_nets.size(); inet++) { + AtomNetId net_id = lb_nets[inet].atom_net_id; + fprintf(fp, "net %s num targets %d \n", atom_ctx.nlist.net_name(net_id).c_str(), (int)lb_nets[inet].terminals.size()); + fprintf(fp, "\tS"); + print_trace(fp, lb_nets[inet].rt_tree, router_data); + fprintf(fp, "\n\n"); + } +} +#endif + +/* Debug routine, print out trace of net */ +static void print_trace(FILE* fp, t_lb_trace* trace, t_lb_router_data* router_data) { + if (trace == NULL) { + fprintf(fp, "NULL"); + return; + } + for (unsigned int ibranch = 0; ibranch < trace->next_nodes.size(); ibranch++) { + auto current_node = trace->current_node; + auto current_str = describe_lb_type_rr_node(current_node, router_data); + auto next_node = trace->next_nodes[ibranch].current_node; + auto next_str = describe_lb_type_rr_node(next_node, router_data); + if (trace->next_nodes.size() > 1) { + fprintf(fp, "\n\tB"); + } + fprintf(fp, "(%d:%s-->%d:%s) ", current_node, current_str.c_str(), next_node, next_str.c_str()); + print_trace(fp, &trace->next_nodes[ibranch], router_data); + } +} + +static void reset_explored_node_tb(t_lb_router_data* router_data) { + std::vector& lb_type_graph = *router_data->lb_type_graph; + for (unsigned int inode = 0; inode < lb_type_graph.size(); inode++) { + router_data->explored_node_tb[inode].prev_index = OPEN; + router_data->explored_node_tb[inode].explored_id = OPEN; + router_data->explored_node_tb[inode].inet = OPEN; + router_data->explored_node_tb[inode].enqueue_id = OPEN; + router_data->explored_node_tb[inode].enqueue_cost = 0; + } +} + +/* Save last successful intra-logic block route and reset current traceback */ +static void save_and_reset_lb_route(t_lb_router_data* router_data) { + std::vector& lb_nets = *router_data->intra_lb_nets; + + /* Free old saved lb nets if exist */ + if (router_data->saved_lb_nets != nullptr) { + free_intra_lb_nets(router_data->saved_lb_nets); + router_data->saved_lb_nets = nullptr; + } + + /* Save current routed solution */ + router_data->saved_lb_nets = new std::vector(lb_nets.size()); + std::vector& saved_lb_nets = *router_data->saved_lb_nets; + + for (int inet = 0; inet < (int)saved_lb_nets.size(); inet++) { + /* + * Save and reset route tree data + */ + saved_lb_nets[inet].atom_net_id = lb_nets[inet].atom_net_id; + saved_lb_nets[inet].terminals.resize(lb_nets[inet].terminals.size()); + for (int iterm = 0; iterm < (int)lb_nets[inet].terminals.size(); iterm++) { + saved_lb_nets[inet].terminals[iterm] = lb_nets[inet].terminals[iterm]; + } + saved_lb_nets[inet].rt_tree = lb_nets[inet].rt_tree; + lb_nets[inet].rt_tree = nullptr; + } +} + +static std::vector find_congested_rr_nodes(const std::vector& lb_type_graph, + const t_lb_rr_node_stats* lb_rr_node_stats) { + std::vector congested_rr_nodes; + for (size_t inode = 0; inode < lb_type_graph.size(); ++inode) { + const t_lb_type_rr_node& rr_node = lb_type_graph[inode]; + const t_lb_rr_node_stats& rr_node_stats = lb_rr_node_stats[inode]; + + if (rr_node_stats.occ > rr_node.capacity) { + congested_rr_nodes.push_back(inode); + } + } + + return congested_rr_nodes; +} + +static std::string describe_lb_type_rr_node(int inode, + const t_lb_router_data* router_data) { + std::string description; + + const t_lb_type_rr_node& rr_node = (*router_data->lb_type_graph)[inode]; + t_logical_block_type_ptr lb_type = router_data->lb_type; + + const t_pb_graph_pin* pb_graph_pin = rr_node.pb_graph_pin; + + if (pb_graph_pin) { + description += "'" + pb_graph_pin->to_string(false) + "'"; + } else if (inode == get_lb_type_rr_graph_ext_source_index(lb_type)) { + VTR_ASSERT(rr_node.type == LB_SOURCE); + description = "cluster-external source (LB_SOURCE)"; + } else if (inode == get_lb_type_rr_graph_ext_sink_index(lb_type)) { + VTR_ASSERT(rr_node.type == LB_SINK); + description = "cluster-external sink (LB_SINK)"; + } else if (rr_node.type == LB_SINK) { + description = "cluster-internal sink (LB_SINK accessible via architecture pins: "; + + //To account for equivalent pins multiple pins may route to a single sink. + //As a result we need to fin all the nodes which connect to this sink in order + //to give user-friendly pin names + std::vector pin_descriptions; + std::vector pin_rrs = find_incoming_rr_nodes(inode, router_data); + for (int pin_rr_idx : pin_rrs) { + const t_pb_graph_pin* pin_pb_gpin = (*router_data->lb_type_graph)[pin_rr_idx].pb_graph_pin; + pin_descriptions.push_back(pin_pb_gpin->to_string()); + } + + description += vtr::join(pin_descriptions, ", "); + description += ")"; + + } else if (rr_node.type == LB_SOURCE) { + description = "cluster-internal source (LB_SOURCE)"; + } else if (rr_node.type == LB_INTERMEDIATE) { + description = "cluster-internal intermediate?"; + } else { + description = ""; + } + + return description; +} + +static std::vector find_incoming_rr_nodes(int dst_node, const t_lb_router_data* router_data) { + std::vector incoming_rr_nodes; + const auto& lb_rr_graph = *router_data->lb_type_graph; + for (size_t inode = 0; inode < lb_rr_graph.size(); ++inode) { + const t_lb_type_rr_node& rr_node = lb_rr_graph[inode]; + for (int mode = 0; mode < rr_node.num_modes; mode++) { + for (int iedge = 0; iedge < rr_node.num_fanout[mode]; ++iedge) { + const t_lb_type_rr_node_edge& rr_edge = rr_node.outedges[mode][iedge]; + + if (rr_edge.node_index == dst_node) { + //The current node connects to the destination node + incoming_rr_nodes.push_back(inode); + } + } + } + } + return incoming_rr_nodes; +} + +static std::string describe_congested_rr_nodes(const std::vector& congested_rr_nodes, + const t_lb_router_data* router_data) { + std::string description; + + const auto& lb_nets = *router_data->intra_lb_nets; + const auto& lb_type_graph = *router_data->lb_type_graph; + const auto& lb_rr_node_stats = router_data->lb_rr_node_stats; + + std::multimap congested_rr_node_to_nets; //From rr_node to net + for (unsigned int inet = 0; inet < lb_nets.size(); inet++) { + AtomNetId atom_net = lb_nets[inet].atom_net_id; + + //Walk the traceback to find congested RR nodes for each net + std::queue q; + + if (lb_nets[inet].rt_tree) { + q.push(*lb_nets[inet].rt_tree); + } + while (!q.empty()) { + t_lb_trace curr = q.front(); + q.pop(); + + for (const t_lb_trace& next_trace : curr.next_nodes) { + q.push(next_trace); + } + + int inode = curr.current_node; + const t_lb_type_rr_node& rr_node = lb_type_graph[inode]; + const t_lb_rr_node_stats& rr_node_stats = lb_rr_node_stats[inode]; + + if (rr_node_stats.occ > rr_node.capacity) { + //Congested + congested_rr_node_to_nets.insert({inode, atom_net}); + } + } + } + + VTR_ASSERT(!congested_rr_node_to_nets.empty()); + VTR_ASSERT(!congested_rr_nodes.empty()); + auto& atom_ctx = g_vpr_ctx.atom(); + for (int inode : congested_rr_nodes) { + const t_lb_type_rr_node& rr_node = lb_type_graph[inode]; + const t_lb_rr_node_stats& rr_node_stats = lb_rr_node_stats[inode]; + description += vtr::string_fmt("RR Node %d (%s) is congested (occ: %d > capacity: %d) with the following nets:\n", + inode, + describe_lb_type_rr_node(inode, router_data).c_str(), + rr_node_stats.occ, + rr_node.capacity); + auto range = congested_rr_node_to_nets.equal_range(inode); + for (auto itr = range.first; itr != range.second; ++itr) { + AtomNetId net = itr->second; + description += vtr::string_fmt("\tNet: %s\n", + atom_ctx.nlist.net_name(net).c_str()); + } + } + + return description; +} + +void reset_intra_lb_route(t_lb_router_data* router_data) { + for (auto& node : *router_data->lb_type_graph) { + auto* pin = node.pb_graph_pin; + if (pin == nullptr) { + continue; + } + VTR_ASSERT(pin->parent_node != nullptr); + pin->parent_node->illegal_modes.clear(); + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/repack/lb_router.h b/openfpga/src/repack/lb_router.h new file mode 100644 index 000000000..a27011df0 --- /dev/null +++ b/openfpga/src/repack/lb_router.h @@ -0,0 +1,40 @@ +/******************************************************************** + * Intra-logic block router determines if a candidate packing solution (or intermediate solution) can route. + * Adapted from original VPR cluster router to the use of LbRRGraph object + *******************************************************************/ +#ifndef LB_ROUTER_H +#define LB_ROUTER_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include "atom_netlist_fwd.h" +#include "pack_types.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +/* Constructors/Destructors */ +t_lb_router_data* alloc_and_load_router_data(std::vector* lb_type_graph, t_logical_block_type_ptr type); +void free_router_data(t_lb_router_data* router_data); +void free_intra_lb_nets(std::vector* intra_lb_nets); + +/* Routing Functions */ +void add_atom_as_target(t_lb_router_data* router_data, const AtomBlockId blk_id); +void remove_atom_from_target(t_lb_router_data* router_data, const AtomBlockId blk_id); +void set_reset_pb_modes(t_lb_router_data* router_data, const t_pb* pb, const bool set); +bool try_intra_lb_route(t_lb_router_data* router_data, int verbosity, t_mode_selection_status* mode_status); +void reset_intra_lb_route(t_lb_router_data* router_data); + +/* Accessor Functions */ +t_pb_routes alloc_and_load_pb_route(const std::vector* intra_lb_nets, t_pb_graph_node* pb_graph_head); +void free_pb_route(t_pb_route* free_pb_route); + +} /* end namespace openfpga */ + +#endif From d58d14df8ec9431ae1283db61883d033ece771a8 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 18 Feb 2020 16:50:56 -0700 Subject: [PATCH 171/645] start encapsulate the whole lb router in an object --- openfpga/src/repack/lb_router.cpp | 1498 --------------------- openfpga/src/repack/lb_router.h | 179 ++- openfpga/src/repack/lb_rr_graph.cpp | 41 + openfpga/src/repack/lb_rr_graph.h | 18 + openfpga/src/repack/lb_rr_graph_utils.cpp | 59 + openfpga/src/repack/lb_rr_graph_utils.h | 22 + 6 files changed, 301 insertions(+), 1516 deletions(-) delete mode 100644 openfpga/src/repack/lb_router.cpp create mode 100644 openfpga/src/repack/lb_rr_graph_utils.cpp create mode 100644 openfpga/src/repack/lb_rr_graph_utils.h diff --git a/openfpga/src/repack/lb_router.cpp b/openfpga/src/repack/lb_router.cpp deleted file mode 100644 index dd694fa49..000000000 --- a/openfpga/src/repack/lb_router.cpp +++ /dev/null @@ -1,1498 +0,0 @@ -/******************************************************************** - * Intra-logic block router determines if a candidate packing solution (or intermediate solution) can route. - * Adapted from original VPR cluster router to the use of LbRRGraph object - * - * Global Inputs: Architecture and netlist - * Input arguments: clustering info for one cluster (t_pb info) - * Working data set: t_routing_data contains intermediate work - * Output: Routable? true/false. If true, store/return the routed solution. - * - * Routing algorithm used is Pathfinder. - *******************************************************************/ -#include -#include -#include -#include -#include -#include - -#include "vtr_assert.h" -#include "vtr_log.h" - -#include "vpr_error.h" -#include "vpr_types.h" -#include "echo_files.h" - -#include "physical_types.h" -#include "globals.h" -#include "atom_netlist.h" -#include "vpr_utils.h" -#include "pack_types.h" -#include "pb_type_graph.h" -#include "lb_type_rr_graph.h" -#include "lb_router.h" - -/* #define PRINT_INTRA_LB_ROUTE */ - -/* begin namespace openfpga */ -namespace openfpga { - -/***************************************************************************************** - * Internal data structures - ******************************************************************************************/ - -enum e_commit_remove { RT_COMMIT, - RT_REMOVE }; - -// TODO: check if this hacky class memory reserve thing is still necessary, if not, then delete -/* Packing uses a priority queue that requires a large number of elements. This backdoor - * allows me to use a priority queue where I can pre-allocate the # of elements in the underlying container - * for efficiency reasons. Note: Must use vector with this */ -template -class reservable_pq : public std::priority_queue { - public: - typedef typename std::priority_queue::size_type size_type; - reservable_pq(size_type capacity = 0) { - reserve(capacity); - cur_cap = capacity; - } - void reserve(size_type capacity) { - this->c.reserve(capacity); - cur_cap = capacity; - } - void clear() { - this->c.clear(); - this->c.reserve(cur_cap); - } - - private: - size_type cur_cap; -}; - -/***************************************************************************************** - * Internal functions declarations - ******************************************************************************************/ -static void free_lb_net_rt(t_lb_trace* lb_trace); -static void free_lb_trace(t_lb_trace* lb_trace); -static void add_pin_to_rt_terminals(t_lb_router_data* router_data, const AtomPinId pin_id); -static void remove_pin_from_rt_terminals(t_lb_router_data* router_data, const AtomPinId pin_id); - -static void fix_duplicate_equivalent_pins(t_lb_router_data* router_data); - -static void commit_remove_rt(t_lb_trace* rt, t_lb_router_data* router_data, e_commit_remove op, std::unordered_map* mode_map, t_mode_selection_status* mode_status); -static bool is_skip_route_net(t_lb_trace* rt, t_lb_router_data* router_data); -static void add_source_to_rt(t_lb_router_data* router_data, int inet); -static void expand_rt(t_lb_router_data* router_data, int inet, reservable_pq, compare_expansion_node>& pq, int irt_net); -static void expand_rt_rec(t_lb_trace* rt, int prev_index, t_explored_node_tb* explored_node_tb, reservable_pq, compare_expansion_node>& pq, int irt_net, int explore_id_index); -static bool try_expand_nodes(t_lb_router_data* router_data, - t_intra_lb_net* lb_net, - t_expansion_node* exp_node, - reservable_pq, compare_expansion_node>& pq, - int itarget, - bool try_other_modes, - int verbosity); - -static void expand_edges(t_lb_router_data* router_data, - int mode, - int cur_inode, - float cur_cost, - int net_fanout, - reservable_pq, compare_expansion_node>& pq); - -static void expand_node(t_lb_router_data* router_data, t_expansion_node exp_node, reservable_pq, compare_expansion_node>& pq, int net_fanout); -static void expand_node_all_modes(t_lb_router_data* router_data, t_expansion_node exp_node, reservable_pq, compare_expansion_node>& pq, int net_fanout); - -static bool add_to_rt(t_lb_trace* rt, int node_index, t_lb_router_data* router_data, int irt_net); -static bool is_route_success(t_lb_router_data* router_data); -static t_lb_trace* find_node_in_rt(t_lb_trace* rt, int rt_index); -static void reset_explored_node_tb(t_lb_router_data* router_data); -static void save_and_reset_lb_route(t_lb_router_data* router_data); -static void load_trace_to_pb_route(t_pb_routes& pb_route, const int total_pins, const AtomNetId net_id, const int prev_pin_id, const t_lb_trace* trace); - -static std::string describe_lb_type_rr_node(int inode, - const t_lb_router_data* router_data); - -static std::vector find_congested_rr_nodes(const std::vector& lb_type_graph, - const t_lb_rr_node_stats* lb_rr_node_stats); -static std::vector find_incoming_rr_nodes(int dst_node, const t_lb_router_data* router_data); -static std::string describe_congested_rr_nodes(const std::vector& congested_rr_nodes, - const t_lb_router_data* router_data); -/***************************************************************************************** - * Debug functions declarations - ******************************************************************************************/ -#ifdef PRINT_INTRA_LB_ROUTE -static void print_route(const char* filename, t_lb_router_data* router_data); -static void print_route(FILE* fp, t_lb_router_data* router_data); -#endif -static void print_trace(FILE* fp, t_lb_trace* trace, t_lb_router_data* router_data); - -/***************************************************************************************** - * Constructor/Destructor functions - ******************************************************************************************/ - -/** - * Build data structures used by intra-logic block router - */ -t_lb_router_data* alloc_and_load_router_data(std::vector* lb_type_graph, t_logical_block_type_ptr type) { - t_lb_router_data* router_data = new t_lb_router_data; - int size; - - router_data->lb_type_graph = lb_type_graph; - size = router_data->lb_type_graph->size(); - router_data->lb_rr_node_stats = new t_lb_rr_node_stats[size]; - router_data->explored_node_tb = new t_explored_node_tb[size]; - router_data->intra_lb_nets = new std::vector; - router_data->atoms_added = new std::map; - router_data->lb_type = type; - - return router_data; -} - -/* free data used by router */ -void free_router_data(t_lb_router_data* router_data) { - if (router_data != nullptr && router_data->lb_type_graph != nullptr) { - delete[] router_data->lb_rr_node_stats; - router_data->lb_rr_node_stats = nullptr; - delete[] router_data->explored_node_tb; - router_data->explored_node_tb = nullptr; - router_data->lb_type_graph = nullptr; - delete router_data->atoms_added; - router_data->atoms_added = nullptr; - free_intra_lb_nets(router_data->intra_lb_nets); - free_intra_lb_nets(router_data->saved_lb_nets); - router_data->intra_lb_nets = nullptr; - delete router_data; - } -} - -static bool route_has_conflict(t_lb_trace* rt, t_lb_router_data* router_data) { - std::vector& lb_type_graph = *router_data->lb_type_graph; - - int cur_mode = -1; - for (unsigned int i = 0; i < rt->next_nodes.size(); i++) { - int new_mode = get_lb_type_rr_graph_edge_mode(lb_type_graph, - rt->current_node, rt->next_nodes[i].current_node); - if (cur_mode != -1 && cur_mode != new_mode) { - return true; - } - if (route_has_conflict(&rt->next_nodes[i], router_data) == true) { - return true; - } - cur_mode = new_mode; - } - - return false; -} - -// Check one edge for mode conflict. -static bool check_edge_for_route_conflicts(std::unordered_map* mode_map, - const t_pb_graph_pin* driver_pin, - const t_pb_graph_pin* pin) { - if (driver_pin == nullptr) { - return false; - } - - // Only check pins that are OUT_PORTs. - if (pin == nullptr || pin->port == nullptr || pin->port->type != OUT_PORT) { - return false; - } - VTR_ASSERT(!pin->port->is_clock); - - auto* pb_graph_node = pin->parent_node; - VTR_ASSERT(pb_graph_node->pb_type == pin->port->parent_pb_type); - - const t_pb_graph_edge* edge = get_edge_between_pins(driver_pin, pin); - VTR_ASSERT(edge != nullptr); - - auto mode_of_edge = edge->interconnect->parent_mode_index; - auto* mode = &pb_graph_node->pb_type->modes[mode_of_edge]; - - auto result = mode_map->insert(std::make_pair(pb_graph_node, mode)); - if (!result.second) { - if (result.first->second != mode) { - std::cout << vtr::string_fmt("Differing modes for block. Got %s mode, while previously was %s for interconnect %s.", - mode->name, result.first->second->name, - edge->interconnect->name) - << std::endl; - - // The illegal mode is added to the pb_graph_node as it resulted in a conflict during atom-to-atom routing. This mode cannot be used in the consequent cluster - // generation try. - if (std::find(pb_graph_node->illegal_modes.begin(), pb_graph_node->illegal_modes.end(), result.first->second->index) == pb_graph_node->illegal_modes.end()) { - pb_graph_node->illegal_modes.push_back(result.first->second->index); - } - - // If the number of illegal modes equals the number of available mode for a specific pb_graph_node it means that no cluster can be generated. This resuts - // in a fatal error. - if ((int)pb_graph_node->illegal_modes.size() >= pb_graph_node->pb_type->num_modes) { - VPR_FATAL_ERROR(VPR_ERROR_PACK, "There are no more available modes to be used. Routing Failed!"); - } - - return true; - } - } - - return false; -} - -/***************************************************************************************** - * Routing Functions - ******************************************************************************************/ - -/* Add pins of netlist atom to to current routing drivers/targets */ -void add_atom_as_target(t_lb_router_data* router_data, const AtomBlockId blk_id) { - const t_pb* pb; - auto& atom_ctx = g_vpr_ctx.atom(); - - std::map& atoms_added = *router_data->atoms_added; - - if (atoms_added.count(blk_id) > 0) { - VPR_FATAL_ERROR(VPR_ERROR_PACK, "Atom %s added twice to router\n", atom_ctx.nlist.block_name(blk_id).c_str()); - } - - pb = atom_ctx.lookup.atom_pb(blk_id); - - VTR_ASSERT(pb); - - atoms_added[blk_id] = true; - - set_reset_pb_modes(router_data, pb, true); - - for (auto pin_id : atom_ctx.nlist.block_pins(blk_id)) { - add_pin_to_rt_terminals(router_data, pin_id); - } - - fix_duplicate_equivalent_pins(router_data); -} - -/* Remove pins of netlist atom from current routing drivers/targets */ -void remove_atom_from_target(t_lb_router_data* router_data, const AtomBlockId blk_id) { - auto& atom_ctx = g_vpr_ctx.atom(); - - std::map& atoms_added = *router_data->atoms_added; - - const t_pb* pb = atom_ctx.lookup.atom_pb(blk_id); - - if (atoms_added.count(blk_id) == 0) { - return; - } - - set_reset_pb_modes(router_data, pb, false); - - for (auto pin_id : atom_ctx.nlist.block_pins(blk_id)) { - remove_pin_from_rt_terminals(router_data, pin_id); - } - - atoms_added.erase(blk_id); -} - -/* Set/Reset mode of rr nodes to the pb used. If set == true, then set all modes of the rr nodes affected by pb to the mode of the pb. - * Set all modes related to pb to 0 otherwise */ -void set_reset_pb_modes(t_lb_router_data* router_data, const t_pb* pb, const bool set) { - t_pb_type* pb_type; - t_pb_graph_node* pb_graph_node; - int mode = pb->mode; - int inode; - - VTR_ASSERT(mode >= 0); - - pb_graph_node = pb->pb_graph_node; - pb_type = pb_graph_node->pb_type; - - /* Input and clock pin modes are based on current pb mode */ - for (int iport = 0; iport < pb_graph_node->num_input_ports; iport++) { - for (int ipin = 0; ipin < pb_graph_node->num_input_pins[iport]; ipin++) { - inode = pb_graph_node->input_pins[iport][ipin].pin_count_in_cluster; - router_data->lb_rr_node_stats[inode].mode = (set == true) ? mode : -1; - } - } - for (int iport = 0; iport < pb_graph_node->num_clock_ports; iport++) { - for (int ipin = 0; ipin < pb_graph_node->num_clock_pins[iport]; ipin++) { - inode = pb_graph_node->clock_pins[iport][ipin].pin_count_in_cluster; - router_data->lb_rr_node_stats[inode].mode = (set == true) ? mode : -1; - } - } - - /* Output pin modes are based on parent pb, so set children to use new mode - * Output pin of top-level logic block is also set to mode 0 - */ - if (pb_type->num_modes != 0) { - for (int ichild_type = 0; ichild_type < pb_type->modes[mode].num_pb_type_children; ichild_type++) { - for (int ichild = 0; ichild < pb_type->modes[mode].pb_type_children[ichild_type].num_pb; ichild++) { - t_pb_graph_node* child_pb_graph_node = &pb_graph_node->child_pb_graph_nodes[mode][ichild_type][ichild]; - for (int iport = 0; iport < child_pb_graph_node->num_output_ports; iport++) { - for (int ipin = 0; ipin < child_pb_graph_node->num_output_pins[iport]; ipin++) { - inode = child_pb_graph_node->output_pins[iport][ipin].pin_count_in_cluster; - router_data->lb_rr_node_stats[inode].mode = (set == true) ? mode : -1; - } - } - } - } - } -} - -/* Expand all the nodes for a given lb_net */ -static bool try_expand_nodes(t_lb_router_data* router_data, - t_intra_lb_net* lb_net, - t_expansion_node* exp_node, - reservable_pq, compare_expansion_node>& pq, - int itarget, - bool try_other_modes, - int verbosity) { - bool is_impossible = false; - - do { - if (pq.empty()) { - /* No connection possible */ - is_impossible = true; - - if (verbosity > 3) { - //Print detailed debug info - auto& atom_nlist = g_vpr_ctx.atom().nlist; - AtomNetId net_id = lb_net->atom_net_id; - AtomPinId driver_pin = lb_net->atom_pins[0]; - AtomPinId sink_pin = lb_net->atom_pins[itarget]; - int driver_rr_node = lb_net->terminals[0]; - int sink_rr_node = lb_net->terminals[itarget]; - - VTR_LOG("\t\t\tNo possible routing path from %s to %s: needed for net '%s' from net pin '%s'", - describe_lb_type_rr_node(driver_rr_node, router_data).c_str(), - describe_lb_type_rr_node(sink_rr_node, router_data).c_str(), - atom_nlist.net_name(net_id).c_str(), - atom_nlist.pin_name(driver_pin).c_str()); - VTR_LOGV(sink_pin, " to net pin '%s'", atom_nlist.pin_name(sink_pin).c_str()); - VTR_LOG("\n"); - } - } else { - *exp_node = pq.top(); - pq.pop(); - int exp_inode = exp_node->node_index; - - if (router_data->explored_node_tb[exp_inode].explored_id != router_data->explore_id_index) { - /* First time node is popped implies path to this node is the lowest cost. - * If the node is popped a second time, then the path to that node is higher than this path so - * ignore. - */ - router_data->explored_node_tb[exp_inode].explored_id = router_data->explore_id_index; - router_data->explored_node_tb[exp_inode].prev_index = exp_node->prev_index; - if (exp_inode != lb_net->terminals[itarget]) { - if (!try_other_modes) { - expand_node(router_data, *exp_node, pq, lb_net->terminals.size() - 1); - } else { - expand_node_all_modes(router_data, *exp_node, pq, lb_net->terminals.size() - 1); - } - } - } - } - } while (exp_node->node_index != lb_net->terminals[itarget] && !is_impossible); - - return is_impossible; -} - -/* Attempt to route routing driver/targets on the current architecture - * Follows pathfinder negotiated congestion algorithm - */ -bool try_intra_lb_route(t_lb_router_data* router_data, - int verbosity, - t_mode_selection_status* mode_status) { - std::vector& lb_nets = *router_data->intra_lb_nets; - std::vector& lb_type_graph = *router_data->lb_type_graph; - bool is_routed = false; - bool is_impossible = false; - - mode_status->is_mode_conflict = false; - mode_status->try_expand_all_modes = false; - - t_expansion_node exp_node; - - /* Stores state info during route */ - reservable_pq, compare_expansion_node> pq; - - reset_explored_node_tb(router_data); - - /* Reset current routing */ - for (unsigned int inet = 0; inet < lb_nets.size(); inet++) { - free_lb_net_rt(lb_nets[inet].rt_tree); - lb_nets[inet].rt_tree = nullptr; - } - for (unsigned int inode = 0; inode < lb_type_graph.size(); inode++) { - router_data->lb_rr_node_stats[inode].historical_usage = 0; - router_data->lb_rr_node_stats[inode].occ = 0; - } - - std::unordered_map mode_map; - - /* Iteratively remove congestion until a successful route is found. - * Cap the total number of iterations tried so that if a solution does not exist, then the router won't run indefinitely */ - router_data->pres_con_fac = router_data->params.pres_fac; - for (int iter = 0; iter < router_data->params.max_iterations && !is_routed && !is_impossible; iter++) { - unsigned int inet; - /* Iterate across all nets internal to logic block */ - for (inet = 0; inet < lb_nets.size() && !is_impossible; inet++) { - int idx = inet; - if (is_skip_route_net(lb_nets[idx].rt_tree, router_data)) { - continue; - } - commit_remove_rt(lb_nets[idx].rt_tree, router_data, RT_REMOVE, &mode_map, mode_status); - free_lb_net_rt(lb_nets[idx].rt_tree); - lb_nets[idx].rt_tree = nullptr; - add_source_to_rt(router_data, idx); - - /* Route each sink of net */ - for (unsigned int itarget = 1; itarget < lb_nets[idx].terminals.size() && !is_impossible; itarget++) { - pq.clear(); - /* Get lowest cost next node, repeat until a path is found or if it is impossible to route */ - - expand_rt(router_data, idx, pq, idx); - - is_impossible = try_expand_nodes(router_data, &lb_nets[idx], &exp_node, pq, itarget, mode_status->expand_all_modes, verbosity); - - if (is_impossible && !mode_status->expand_all_modes) { - mode_status->try_expand_all_modes = true; - mode_status->expand_all_modes = true; - break; - } - - if (exp_node.node_index == lb_nets[idx].terminals[itarget]) { - /* Net terminal is routed, add this to the route tree, clear data structures, and keep going */ - is_impossible = add_to_rt(lb_nets[idx].rt_tree, exp_node.node_index, router_data, idx); - } - - if (verbosity > 5) { - VTR_LOG("Routing finished\n"); - VTR_LOG("\tS"); - print_trace(stdout, lb_nets[idx].rt_tree, router_data); - VTR_LOG("\n"); - } - - if (is_impossible) { - VTR_LOG("Routing was impossible!\n"); - } else if (mode_status->expand_all_modes) { - is_impossible = route_has_conflict(lb_nets[idx].rt_tree, router_data); - if (is_impossible) { - VTR_LOG("Routing was impossible due to modes!\n"); - } - } - - router_data->explore_id_index++; - if (router_data->explore_id_index > 2000000000) { - /* overflow protection */ - for (unsigned int id = 0; id < lb_type_graph.size(); id++) { - router_data->explored_node_tb[id].explored_id = OPEN; - router_data->explored_node_tb[id].enqueue_id = OPEN; - router_data->explore_id_index = 1; - } - } - } - - if (!is_impossible) { - commit_remove_rt(lb_nets[idx].rt_tree, router_data, RT_COMMIT, &mode_map, mode_status); - if (mode_status->is_mode_conflict) { - is_impossible = true; - } - } - } - - if (!is_impossible) { - is_routed = is_route_success(router_data); - } else { - --inet; - auto& atom_ctx = g_vpr_ctx.atom(); - VTR_LOGV(verbosity < 3, "Net '%s' is impossible to route within proposed %s cluster\n", - atom_ctx.nlist.net_name(lb_nets[inet].atom_net_id).c_str(), router_data->lb_type->name); - is_routed = false; - } - router_data->pres_con_fac *= router_data->params.pres_fac_mult; - } - - if (is_routed) { - save_and_reset_lb_route(router_data); - } else { - //Unroutable -#ifdef PRINT_INTRA_LB_ROUTE - print_route(getEchoFileName(E_ECHO_INTRA_LB_FAILED_ROUTE), router_data); -#endif - - if (verbosity > 3 && !is_impossible) { - //Report the congested nodes and associated nets - auto congested_rr_nodes = find_congested_rr_nodes(lb_type_graph, router_data->lb_rr_node_stats); - if (!congested_rr_nodes.empty()) { - VTR_LOG("%s\n", describe_congested_rr_nodes(congested_rr_nodes, router_data).c_str()); - } - } - - //Clean-up - for (unsigned int inet = 0; inet < lb_nets.size(); inet++) { - free_lb_net_rt(lb_nets[inet].rt_tree); - lb_nets[inet].rt_tree = nullptr; - } - } - return is_routed; -} - -/***************************************************************************************** - * Accessor Functions - ******************************************************************************************/ - -/* Creates an array [0..num_pb_graph_pins-1] lookup for intra-logic block routing. Given pb_graph_pin id for clb, lookup atom net that uses that pin. - * If pin is not used, stores OPEN at that pin location */ -t_pb_routes alloc_and_load_pb_route(const std::vector* intra_lb_nets, t_pb_graph_node* pb_graph_head) { - const std::vector& lb_nets = *intra_lb_nets; - int total_pins = pb_graph_head->total_pb_pins; - t_pb_routes pb_route; - - for (int inet = 0; inet < (int)lb_nets.size(); inet++) { - load_trace_to_pb_route(pb_route, total_pins, lb_nets[inet].atom_net_id, OPEN, lb_nets[inet].rt_tree); - } - - return pb_route; -} - -/* Free pin-to-atomic_net array lookup */ -void free_pb_route(t_pb_route* pb_route) { - if (pb_route != nullptr) { - delete[] pb_route; - } -} - -void free_intra_lb_nets(std::vector* intra_lb_nets) { - if (intra_lb_nets == nullptr) { - return; - } - std::vector& lb_nets = *intra_lb_nets; - for (unsigned int i = 0; i < lb_nets.size(); i++) { - lb_nets[i].terminals.clear(); - free_lb_net_rt(lb_nets[i].rt_tree); - lb_nets[i].rt_tree = nullptr; - } - delete intra_lb_nets; -} - -/*************************************************************************** - * Internal Functions - ****************************************************************************/ - -/* Recurse through route tree trace to populate pb pin to atom net lookup array */ -static void load_trace_to_pb_route(t_pb_routes& pb_route, const int total_pins, const AtomNetId net_id, const int prev_pin_id, const t_lb_trace* trace) { - int ipin = trace->current_node; - int driver_pb_pin_id = prev_pin_id; - int cur_pin_id = OPEN; - if (ipin < total_pins) { - /* This routing node corresponds with a pin. This node is virtual (ie. sink or source node) */ - cur_pin_id = ipin; - if (!pb_route.count(ipin)) { - pb_route.insert(std::make_pair(cur_pin_id, t_pb_route())); - pb_route[cur_pin_id].atom_net_id = net_id; - pb_route[cur_pin_id].driver_pb_pin_id = driver_pb_pin_id; - } else { - VTR_ASSERT(pb_route[cur_pin_id].atom_net_id == net_id); - } - } - for (int itrace = 0; itrace < (int)trace->next_nodes.size(); itrace++) { - load_trace_to_pb_route(pb_route, total_pins, net_id, cur_pin_id, &trace->next_nodes[itrace]); - } -} - -/* Free route tree for intra-logic block routing */ -static void free_lb_net_rt(t_lb_trace* lb_trace) { - if (lb_trace != nullptr) { - for (unsigned int i = 0; i < lb_trace->next_nodes.size(); i++) { - free_lb_trace(&lb_trace->next_nodes[i]); - } - lb_trace->next_nodes.clear(); - delete lb_trace; - } -} - -/* Free trace for intra-logic block routing */ -static void free_lb_trace(t_lb_trace* lb_trace) { - if (lb_trace != nullptr) { - for (unsigned int i = 0; i < lb_trace->next_nodes.size(); i++) { - free_lb_trace(&lb_trace->next_nodes[i]); - } - lb_trace->next_nodes.clear(); - } -} - -/* Given a pin of a net, assign route tree terminals for it - * Assumes that pin is not already assigned - */ -static void add_pin_to_rt_terminals(t_lb_router_data* router_data, const AtomPinId pin_id) { - std::vector& lb_nets = *router_data->intra_lb_nets; - std::vector& lb_type_graph = *router_data->lb_type_graph; - t_logical_block_type_ptr lb_type = router_data->lb_type; - bool found = false; - unsigned int ipos; - auto& atom_ctx = g_vpr_ctx.atom(); - - const t_pb_graph_pin* pb_graph_pin = find_pb_graph_pin(atom_ctx.nlist, atom_ctx.lookup, pin_id); - VTR_ASSERT(pb_graph_pin); - - AtomPortId port_id = atom_ctx.nlist.pin_port(pin_id); - AtomNetId net_id = atom_ctx.nlist.pin_net(pin_id); - - if (!net_id) { - //No net connected to this pin, so nothing to route - return; - } - - /* Find if current net is in route tree, if not, then add to rt. - * Code assumes that # of nets in cluster is small so a linear search through - * vector is faster than using more complex data structures - */ - for (ipos = 0; ipos < lb_nets.size(); ipos++) { - if (lb_nets[ipos].atom_net_id == net_id) { - found = true; - break; - } - } - if (found == false) { - struct t_intra_lb_net new_net; - - new_net.atom_net_id = net_id; - - ipos = lb_nets.size(); - lb_nets.push_back(new_net); - } - VTR_ASSERT(lb_nets[ipos].atom_net_id == net_id); - VTR_ASSERT(lb_nets[ipos].atom_pins.size() == lb_nets[ipos].terminals.size()); - - /* - * Determine whether or not this is a new intra lb net, if yes, then add to list of intra lb nets - */ - if (lb_nets[ipos].terminals.empty()) { - /* Add terminals */ - - //Default assumption is that the source is outside the current cluster (will be overriden later if required) - int source_terminal = get_lb_type_rr_graph_ext_source_index(lb_type); - lb_nets[ipos].terminals.push_back(source_terminal); - - AtomPinId net_driver_pin_id = atom_ctx.nlist.net_driver(net_id); - lb_nets[ipos].atom_pins.push_back(net_driver_pin_id); - - VTR_ASSERT_MSG(lb_type_graph[lb_nets[ipos].terminals[0]].type == LB_SOURCE, "Driver must be a source"); - } - - VTR_ASSERT(lb_nets[ipos].atom_pins.size() == lb_nets[ipos].terminals.size()); - - if (atom_ctx.nlist.port_type(port_id) == PortType::OUTPUT) { - //The current pin is the net driver, overwrite the default driver at index 0 - VTR_ASSERT_MSG(lb_nets[ipos].terminals[0] == get_lb_type_rr_graph_ext_source_index(lb_type), "Default driver must be external source"); - - VTR_ASSERT(atom_ctx.nlist.pin_type(pin_id) == PinType::DRIVER); - - //Override the default since this is the driver, and it is within the cluster - lb_nets[ipos].terminals[0] = pb_graph_pin->pin_count_in_cluster; - lb_nets[ipos].atom_pins[0] = pin_id; - - VTR_ASSERT_MSG(lb_type_graph[lb_nets[ipos].terminals[0]].type == LB_SOURCE, "Driver must be a source"); - - int sink_terminal = OPEN; - if (lb_nets[ipos].terminals.size() < atom_ctx.nlist.net_pins(net_id).size()) { - //Not all of the pins are within the cluster - if (lb_nets[ipos].terminals.size() == 1) { - //Only the source has been specified so far, must add cluster-external sink - sink_terminal = get_lb_type_rr_graph_ext_sink_index(lb_type); - lb_nets[ipos].terminals.push_back(sink_terminal); - lb_nets[ipos].atom_pins.push_back(AtomPinId::INVALID()); - } else { - VTR_ASSERT(lb_nets[ipos].terminals.size() > 1); - - //TODO: Figure out why we swap terminal 1 here (although it appears to work correctly, - // it's not clear why this is needed...) - - //Move current terminal 1 to end - sink_terminal = lb_nets[ipos].terminals[1]; - AtomPinId sink_atom_pin = lb_nets[ipos].atom_pins[1]; - lb_nets[ipos].terminals.push_back(sink_terminal); - lb_nets[ipos].atom_pins.push_back(sink_atom_pin); - - //Create external sink at terminal 1 - sink_terminal = get_lb_type_rr_graph_ext_sink_index(lb_type); - lb_nets[ipos].terminals[1] = sink_terminal; - lb_nets[ipos].atom_pins[1] = AtomPinId::INVALID(); - } - VTR_ASSERT(lb_type_graph[lb_nets[ipos].terminals[1]].type == LB_SINK); - } - } else { - //This is an input to a primitive - VTR_ASSERT(atom_ctx.nlist.port_type(port_id) == PortType::INPUT - || atom_ctx.nlist.port_type(port_id) == PortType::CLOCK); - VTR_ASSERT(atom_ctx.nlist.pin_type(pin_id) == PinType::SINK); - - //Get the rr node index associated with the pin - int pin_index = pb_graph_pin->pin_count_in_cluster; - VTR_ASSERT(lb_type_graph[pin_index].num_modes == 1); - VTR_ASSERT(lb_type_graph[pin_index].num_fanout[0] == 1); - - /* We actually route to the sink (to handle logically equivalent pins). - * The sink is one past the primitive input pin */ - int sink_index = lb_type_graph[pin_index].outedges[0][0].node_index; - VTR_ASSERT(lb_type_graph[sink_index].type == LB_SINK); - - if (lb_nets[ipos].terminals.size() == atom_ctx.nlist.net_pins(net_id).size() && lb_nets[ipos].terminals[1] == get_lb_type_rr_graph_ext_sink_index(lb_type)) { - /* If all sinks of net are all contained in the logic block, then the net does - * not need to route out of the logic block, so can replace the external sink - * with this last sink terminal */ - lb_nets[ipos].terminals[1] = sink_index; - lb_nets[ipos].atom_pins[1] = pin_id; - } else { - //Add as a regular sink - lb_nets[ipos].terminals.push_back(sink_index); - lb_nets[ipos].atom_pins.push_back(pin_id); - } - } - VTR_ASSERT(lb_nets[ipos].atom_pins.size() == lb_nets[ipos].terminals.size()); - - int num_lb_terminals = lb_nets[ipos].terminals.size(); - VTR_ASSERT(num_lb_terminals <= (int)atom_ctx.nlist.net_pins(net_id).size()); - VTR_ASSERT(num_lb_terminals >= 0); - -#ifdef VTR_ASSERT_SAFE_ENABLED - //Sanity checks - int num_extern_sources = 0; - int num_extern_sinks = 0; - for (size_t iterm = 0; iterm < lb_nets[ipos].terminals.size(); ++iterm) { - int inode = lb_nets[ipos].terminals[iterm]; - AtomPinId atom_pin = lb_nets[ipos].atom_pins[iterm]; - if (iterm == 0) { - //Net driver - VTR_ASSERT_SAFE_MSG(lb_type_graph[inode].type == LB_SOURCE, "Driver must be a source RR node"); - VTR_ASSERT_SAFE_MSG(atom_pin, "Driver have an associated atom pin"); - VTR_ASSERT_SAFE_MSG(atom_ctx.nlist.pin_type(atom_pin) == PinType::DRIVER, "Source RR must be associated with a driver pin in atom netlist"); - if (inode == get_lb_type_rr_graph_ext_source_index(lb_type)) { - ++num_extern_sources; - } - } else { - //Net sink - VTR_ASSERT_SAFE_MSG(lb_type_graph[inode].type == LB_SINK, "Non-driver must be a sink"); - - if (inode == get_lb_type_rr_graph_ext_sink_index(lb_type)) { - //External sink may have multiple potentially matching atom pins, so it's atom pin is left invalid - VTR_ASSERT_SAFE_MSG(!atom_pin, "Cluster external sink should have no valid atom pin"); - ++num_extern_sinks; - } else { - VTR_ASSERT_SAFE_MSG(atom_pin, "Intra-cluster sink must have an associated atom pin"); - VTR_ASSERT_SAFE_MSG(atom_ctx.nlist.pin_type(atom_pin) == PinType::SINK, "Intra-cluster Sink RR must be associated with a sink pin in atom netlist"); - } - } - } - VTR_ASSERT_SAFE_MSG(num_extern_sinks >= 0 && num_extern_sinks <= 1, "Net must have at most one external sink"); - VTR_ASSERT_SAFE_MSG(num_extern_sources >= 0 && num_extern_sources <= 1, "Net must have at most one external source"); -#endif -} - -/* Given a pin of a net, remove route tree terminals from it - */ -static void remove_pin_from_rt_terminals(t_lb_router_data* router_data, const AtomPinId pin_id) { - std::vector& lb_nets = *router_data->intra_lb_nets; - std::vector& lb_type_graph = *router_data->lb_type_graph; - t_logical_block_type_ptr lb_type = router_data->lb_type; - bool found = false; - unsigned int ipos; - auto& atom_ctx = g_vpr_ctx.atom(); - - const t_pb_graph_pin* pb_graph_pin = find_pb_graph_pin(atom_ctx.nlist, atom_ctx.lookup, pin_id); - - AtomPortId port_id = atom_ctx.nlist.pin_port(pin_id); - AtomNetId net_id = atom_ctx.nlist.pin_net(pin_id); - - if (!net_id) { - /* This is not a valid net */ - return; - } - - /* Find current net in route tree - * Code assumes that # of nets in cluster is small so a linear search through vector is faster than using more complex data structures - */ - for (ipos = 0; ipos < lb_nets.size(); ipos++) { - if (lb_nets[ipos].atom_net_id == net_id) { - found = true; - break; - } - } - VTR_ASSERT(found == true); - VTR_ASSERT(lb_nets[ipos].atom_net_id == net_id); - - VTR_ASSERT(lb_nets[ipos].atom_pins.size() == lb_nets[ipos].terminals.size()); - - auto port_type = atom_ctx.nlist.port_type(port_id); - if (port_type == PortType::OUTPUT) { - /* Net driver pin takes 0th position in terminals */ - int sink_terminal; - VTR_ASSERT(lb_nets[ipos].terminals[0] == pb_graph_pin->pin_count_in_cluster); - lb_nets[ipos].terminals[0] = get_lb_type_rr_graph_ext_source_index(lb_type); - - /* source terminal is now coming from outside logic block, do not need to route signal out of logic block */ - sink_terminal = get_lb_type_rr_graph_ext_sink_index(lb_type); - if (lb_nets[ipos].terminals[1] == sink_terminal) { - lb_nets[ipos].terminals[1] = lb_nets[ipos].terminals.back(); - lb_nets[ipos].terminals.pop_back(); - - lb_nets[ipos].atom_pins[1] = lb_nets[ipos].atom_pins.back(); - lb_nets[ipos].atom_pins.pop_back(); - } - } else { - VTR_ASSERT(port_type == PortType::INPUT || port_type == PortType::CLOCK); - - /* Remove sink from list of terminals */ - int pin_index = pb_graph_pin->pin_count_in_cluster; - unsigned int iterm; - - VTR_ASSERT(lb_type_graph[pin_index].num_modes == 1); - VTR_ASSERT(lb_type_graph[pin_index].num_fanout[0] == 1); - int sink_index = lb_type_graph[pin_index].outedges[0][0].node_index; - VTR_ASSERT(lb_type_graph[sink_index].type == LB_SINK); - - int target_index = -1; - //Search for the sink - found = false; - for (iterm = 0; iterm < lb_nets[ipos].terminals.size(); iterm++) { - if (lb_nets[ipos].terminals[iterm] == sink_index) { - target_index = sink_index; - found = true; - break; - } - } - if (!found) { - //Search for the pin - found = false; - for (iterm = 0; iterm < lb_nets[ipos].terminals.size(); iterm++) { - if (lb_nets[ipos].terminals[iterm] == pin_index) { - target_index = pin_index; - found = true; - break; - } - } - } - VTR_ASSERT(found == true); - VTR_ASSERT(lb_nets[ipos].terminals[iterm] == target_index); - VTR_ASSERT(iterm > 0); - - /* Drop terminal from list */ - lb_nets[ipos].terminals[iterm] = lb_nets[ipos].terminals.back(); - lb_nets[ipos].terminals.pop_back(); - - lb_nets[ipos].atom_pins[iterm] = lb_nets[ipos].atom_pins.back(); - lb_nets[ipos].atom_pins.pop_back(); - - if (lb_nets[ipos].terminals.size() == 1 && lb_nets[ipos].terminals[0] != get_lb_type_rr_graph_ext_source_index(lb_type)) { - /* The removed sink must be driven by an atom found in the cluster, add in special sink outside of cluster to represent this */ - lb_nets[ipos].terminals.push_back(get_lb_type_rr_graph_ext_sink_index(lb_type)); - lb_nets[ipos].atom_pins.push_back(AtomPinId::INVALID()); - } - - if (lb_nets[ipos].terminals.size() > 1 && lb_nets[ipos].terminals[1] != get_lb_type_rr_graph_ext_sink_index(lb_type) && lb_nets[ipos].terminals[0] != get_lb_type_rr_graph_ext_source_index(lb_type)) { - /* The removed sink must be driven by an atom found in the cluster, add in special sink outside of cluster to represent this */ - int terminal = lb_nets[ipos].terminals[1]; - lb_nets[ipos].terminals.push_back(terminal); - lb_nets[ipos].terminals[1] = get_lb_type_rr_graph_ext_sink_index(lb_type); - - AtomPinId pin = lb_nets[ipos].atom_pins[1]; - lb_nets[ipos].atom_pins.push_back(pin); - lb_nets[ipos].atom_pins[1] = AtomPinId::INVALID(); - } - } - VTR_ASSERT(lb_nets[ipos].atom_pins.size() == lb_nets[ipos].terminals.size()); - - if (lb_nets[ipos].terminals.size() == 1 && lb_nets[ipos].terminals[0] == get_lb_type_rr_graph_ext_source_index(lb_type)) { - /* This net is not routed, remove from list of nets in lb */ - lb_nets[ipos] = lb_nets.back(); - lb_nets.pop_back(); - } -} - -//It is possible that a net may connect multiple times to a logically equivalent set of primitive pins. -//The cluster router will only route one connection for a particular net to the common sink of the -//equivalent pins. -// -//To work around this, we fix all but one of these duplicate connections to route to specific pins, -//(instead of the common sink). This ensures a legal routing is produced and that the duplicate pins -//are not 'missing' in the clustered netlist. -static void fix_duplicate_equivalent_pins(t_lb_router_data* router_data) { - auto& atom_ctx = g_vpr_ctx.atom(); - - std::vector& lb_type_graph = *router_data->lb_type_graph; - std::vector& lb_nets = *router_data->intra_lb_nets; - - for (size_t ilb_net = 0; ilb_net < lb_nets.size(); ++ilb_net) { - //Collect all the sink terminals indicies which target a particular node - std::map> duplicate_terminals; - for (size_t iterm = 1; iterm < lb_nets[ilb_net].terminals.size(); ++iterm) { - int node = lb_nets[ilb_net].terminals[iterm]; - - duplicate_terminals[node].push_back(iterm); - } - - for (auto kv : duplicate_terminals) { - if (kv.second.size() < 2) continue; //Only process duplicates - - //Remap all the duplicate terminals so they target the pin instead of the sink - for (size_t idup_term = 0; idup_term < kv.second.size(); ++idup_term) { - int iterm = kv.second[idup_term]; //The index in terminals which is duplicated - - VTR_ASSERT(lb_nets[ilb_net].atom_pins.size() == lb_nets[ilb_net].terminals.size()); - AtomPinId atom_pin = lb_nets[ilb_net].atom_pins[iterm]; - VTR_ASSERT(atom_pin); - - const t_pb_graph_pin* pb_graph_pin = find_pb_graph_pin(atom_ctx.nlist, atom_ctx.lookup, atom_pin); - VTR_ASSERT(pb_graph_pin); - - if (pb_graph_pin->port->equivalent == PortEquivalence::NONE) continue; //Only need to remap equivalent ports - - //Remap this terminal to an explicit pin instead of the common sink - int pin_index = pb_graph_pin->pin_count_in_cluster; - - VTR_LOG_WARN( - "Found duplicate nets connected to logically equivalent pins. " - "Remapping intra lb net %d (atom net %zu '%s') from common sink " - "pb_route %d to fixed pin pb_route %d\n", - ilb_net, size_t(lb_nets[ilb_net].atom_net_id), atom_ctx.nlist.net_name(lb_nets[ilb_net].atom_net_id).c_str(), - kv.first, pin_index); - - VTR_ASSERT(lb_type_graph[pin_index].type == LB_INTERMEDIATE); - VTR_ASSERT(lb_type_graph[pin_index].num_fanout[0] == 1); - int sink_index = lb_type_graph[pin_index].outedges[0][0].node_index; - VTR_ASSERT(lb_type_graph[sink_index].type == LB_SINK); - VTR_ASSERT_MSG(sink_index == lb_nets[ilb_net].terminals[iterm], "Remapped pin must be connected to original sink"); - - //Change the target - lb_nets[ilb_net].terminals[iterm] = pin_index; - } - } - } -} - -/* Commit or remove route tree from currently routed solution */ -static void commit_remove_rt(t_lb_trace* rt, t_lb_router_data* router_data, e_commit_remove op, std::unordered_map* mode_map, t_mode_selection_status* mode_status) { - t_lb_rr_node_stats* lb_rr_node_stats; - t_explored_node_tb* explored_node_tb; - std::vector& lb_type_graph = *router_data->lb_type_graph; - int inode; - int incr; - - lb_rr_node_stats = router_data->lb_rr_node_stats; - explored_node_tb = router_data->explored_node_tb; - - if (rt == nullptr) { - return; - } - - inode = rt->current_node; - - /* Determine if node is being used or removed */ - if (op == RT_COMMIT) { - incr = 1; - if (lb_rr_node_stats[inode].occ >= lb_type_graph[inode].capacity) { - lb_rr_node_stats[inode].historical_usage += (lb_rr_node_stats[inode].occ - lb_type_graph[inode].capacity + 1); /* store historical overuse */ - } - } else { - incr = -1; - explored_node_tb[inode].inet = OPEN; - } - - lb_rr_node_stats[inode].occ += incr; - VTR_ASSERT(lb_rr_node_stats[inode].occ >= 0); - - auto& driver_node = lb_type_graph[inode]; - auto* driver_pin = driver_node.pb_graph_pin; - - /* Recursively update route tree */ - for (unsigned int i = 0; i < rt->next_nodes.size(); i++) { - // Check to see if there is no mode conflict between previous nets. - // A conflict is present if there are differing modes between a pb_graph_node - // and its children. - if (op == RT_COMMIT && mode_status->try_expand_all_modes) { - auto& node = lb_type_graph[rt->next_nodes[i].current_node]; - auto* pin = node.pb_graph_pin; - - if (check_edge_for_route_conflicts(mode_map, driver_pin, pin)) { - mode_status->is_mode_conflict = true; - } - } - - commit_remove_rt(&rt->next_nodes[i], router_data, op, mode_map, mode_status); - } -} - -/* Should net be skipped? If the net does not conflict with another net, then skip routing this net */ -static bool is_skip_route_net(t_lb_trace* rt, t_lb_router_data* router_data) { - t_lb_rr_node_stats* lb_rr_node_stats; - std::vector& lb_type_graph = *router_data->lb_type_graph; - int inode; - - lb_rr_node_stats = router_data->lb_rr_node_stats; - - if (rt == nullptr) { - return false; /* Net is not routed, therefore must route net */ - } - - inode = rt->current_node; - - /* Determine if node is overused */ - if (lb_rr_node_stats[inode].occ > lb_type_graph[inode].capacity) { - /* Conflict between this net and another net at this node, reroute net */ - return false; - } - - /* Recursively check that rest of route tree does not have a conflict */ - for (unsigned int i = 0; i < rt->next_nodes.size(); i++) { - if (!is_skip_route_net(&rt->next_nodes[i], router_data)) { - return false; - } - } - - /* No conflict, this net's current route is legal, skip routing this net */ - return true; -} - -/* At source mode as starting point to existing route tree */ -static void add_source_to_rt(t_lb_router_data* router_data, int inet) { - VTR_ASSERT((*router_data->intra_lb_nets)[inet].rt_tree == nullptr); - (*router_data->intra_lb_nets)[inet].rt_tree = new t_lb_trace; - (*router_data->intra_lb_nets)[inet].rt_tree->current_node = (*router_data->intra_lb_nets)[inet].terminals[0]; -} - -/* Expand all nodes found in route tree into priority queue */ -static void expand_rt(t_lb_router_data* router_data, int inet, reservable_pq, compare_expansion_node>& pq, int irt_net) { - std::vector& lb_nets = *router_data->intra_lb_nets; - - VTR_ASSERT(pq.empty()); - - expand_rt_rec(lb_nets[inet].rt_tree, OPEN, router_data->explored_node_tb, pq, irt_net, router_data->explore_id_index); -} - -/* Expand all nodes found in route tree into priority queue recursively */ -static void expand_rt_rec(t_lb_trace* rt, int prev_index, t_explored_node_tb* explored_node_tb, reservable_pq, compare_expansion_node>& pq, int irt_net, int explore_id_index) { - t_expansion_node enode; - - /* Perhaps should use a cost other than zero */ - enode.cost = 0; - enode.node_index = rt->current_node; - enode.prev_index = prev_index; - pq.push(enode); - explored_node_tb[enode.node_index].inet = irt_net; - explored_node_tb[enode.node_index].explored_id = OPEN; - explored_node_tb[enode.node_index].enqueue_id = explore_id_index; - explored_node_tb[enode.node_index].enqueue_cost = 0; - explored_node_tb[enode.node_index].prev_index = prev_index; - - for (unsigned int i = 0; i < rt->next_nodes.size(); i++) { - expand_rt_rec(&rt->next_nodes[i], rt->current_node, explored_node_tb, pq, irt_net, explore_id_index); - } -} - -/* Expand all edges of an expantion node */ -static void expand_edges(t_lb_router_data* router_data, - int mode, - int cur_inode, - float cur_cost, - int net_fanout, - reservable_pq, compare_expansion_node>& pq) { - std::vector& lb_type_graph = *router_data->lb_type_graph; - t_lb_rr_node_stats* lb_rr_node_stats = router_data->lb_rr_node_stats; - t_lb_router_params params = router_data->params; - t_expansion_node enode; - int usage; - float incr_cost; - - for (int iedge = 0; iedge < lb_type_graph[cur_inode].num_fanout[mode]; iedge++) { - /* Init new expansion node */ - enode.prev_index = cur_inode; - enode.node_index = lb_type_graph[cur_inode].outedges[mode][iedge].node_index; - enode.cost = cur_cost; - - /* Determine incremental cost of using expansion node */ - usage = lb_rr_node_stats[enode.node_index].occ + 1 - lb_type_graph[enode.node_index].capacity; - incr_cost = lb_type_graph[enode.node_index].intrinsic_cost; - incr_cost += lb_type_graph[cur_inode].outedges[mode][iedge].intrinsic_cost; - incr_cost += params.hist_fac * lb_rr_node_stats[enode.node_index].historical_usage; - if (usage > 0) { - incr_cost *= (usage * router_data->pres_con_fac); - } - - /* Adjust cost so that higher fanout nets prefer higher fanout routing nodes while lower fanout nets prefer lower fanout routing nodes */ - float fanout_factor = 1.0; - int next_mode = lb_rr_node_stats[enode.node_index].mode; - /* Assume first mode if a mode hasn't been forced. */ - if (next_mode == -1) { - next_mode = 0; - } - if (lb_type_graph[enode.node_index].num_fanout[next_mode] > 1) { - fanout_factor = 0.85 + (0.25 / net_fanout); - } else { - fanout_factor = 1.15 - (0.25 / net_fanout); - } - - incr_cost *= fanout_factor; - enode.cost = cur_cost + incr_cost; - - /* Add to queue if cost is lower than lowest cost path to this enode */ - if (router_data->explored_node_tb[enode.node_index].enqueue_id == router_data->explore_id_index) { - if (enode.cost < router_data->explored_node_tb[enode.node_index].enqueue_cost) { - pq.push(enode); - } - } else { - router_data->explored_node_tb[enode.node_index].enqueue_id = router_data->explore_id_index; - router_data->explored_node_tb[enode.node_index].enqueue_cost = enode.cost; - pq.push(enode); - } - } -} - -/* Expand all nodes found in route tree into priority queue */ -static void expand_node(t_lb_router_data* router_data, t_expansion_node exp_node, reservable_pq, compare_expansion_node>& pq, int net_fanout) { - int cur_node; - float cur_cost; - int mode; - t_expansion_node enode; - t_lb_rr_node_stats* lb_rr_node_stats = router_data->lb_rr_node_stats; - - cur_node = exp_node.node_index; - cur_cost = exp_node.cost; - mode = lb_rr_node_stats[cur_node].mode; - if (mode == -1) { - mode = 0; - } - - expand_edges(router_data, mode, cur_node, cur_cost, net_fanout, pq); -} - -/* Expand all nodes using all possible modes found in route tree into priority queue */ -static void expand_node_all_modes(t_lb_router_data* router_data, t_expansion_node exp_node, reservable_pq, compare_expansion_node>& pq, int net_fanout) { - std::vector& lb_type_graph = *router_data->lb_type_graph; - t_lb_rr_node_stats* lb_rr_node_stats = router_data->lb_rr_node_stats; - - int cur_inode = exp_node.node_index; - float cur_cost = exp_node.cost; - int cur_mode = lb_rr_node_stats[cur_inode].mode; - auto& node = lb_type_graph[cur_inode]; - auto* pin = node.pb_graph_pin; - - for (int mode = 0; mode < lb_type_graph[cur_inode].num_modes; mode++) { - /* If a mode has been forced, only add edges from that mode, otherwise add edges from all modes. */ - if (cur_mode != -1 && mode != cur_mode) { - continue; - } - - /* Check whether a mode is illegal. If it is then the node will not be expanded */ - bool is_illegal = false; - if (pin != nullptr) { - auto* pb_graph_node = pin->parent_node; - for (auto illegal_mode : pb_graph_node->illegal_modes) { - if (mode == illegal_mode) { - is_illegal = true; - break; - } - } - } - - if (is_illegal == true) { - continue; - } - - expand_edges(router_data, mode, cur_inode, cur_cost, net_fanout, pq); - } -} - -/* Add new path from existing route tree to target sink */ -static bool add_to_rt(t_lb_trace* rt, int node_index, t_lb_router_data* router_data, int irt_net) { - t_explored_node_tb* explored_node_tb = router_data->explored_node_tb; - std::vector trace_forward; - int rt_index, trace_index; - t_lb_trace* link_node; - t_lb_trace curr_node; - - /* Store path all the way back to route tree */ - rt_index = node_index; - while (explored_node_tb[rt_index].inet != irt_net) { - trace_forward.push_back(rt_index); - rt_index = explored_node_tb[rt_index].prev_index; - VTR_ASSERT(rt_index != OPEN); - } - - /* Find rt_index on the route tree */ - link_node = find_node_in_rt(rt, rt_index); - if (link_node == nullptr) { - VTR_LOG("Link node is nullptr. Routing impossible"); - return true; - } - - /* Add path to root tree */ - while (!trace_forward.empty()) { - trace_index = trace_forward.back(); - curr_node.current_node = trace_index; - link_node->next_nodes.push_back(curr_node); - link_node = &link_node->next_nodes.back(); - trace_forward.pop_back(); - } - - return false; -} - -/* Determine if a completed route is valid. A successful route has no congestion (ie. no routing resource is used by two nets). */ -static bool is_route_success(t_lb_router_data* router_data) { - std::vector& lb_type_graph = *router_data->lb_type_graph; - - for (unsigned int inode = 0; inode < lb_type_graph.size(); inode++) { - if (router_data->lb_rr_node_stats[inode].occ > lb_type_graph[inode].capacity) { - return false; - } - } - - return true; -} - -/* Given a route tree and an index of a node on the route tree, return a pointer to the trace corresponding to that index */ -static t_lb_trace* find_node_in_rt(t_lb_trace* rt, int rt_index) { - t_lb_trace* cur; - if (rt->current_node == rt_index) { - return rt; - } else { - for (unsigned int i = 0; i < rt->next_nodes.size(); i++) { - cur = find_node_in_rt(&rt->next_nodes[i], rt_index); - if (cur != nullptr) { - return cur; - } - } - } - return nullptr; -} - -#ifdef PRINT_INTRA_LB_ROUTE -/* Debug routine, print out current intra logic block route */ -static void print_route(const char* filename, t_lb_router_data* router_data) { - FILE* fp; - std::vector& lb_type_graph = *router_data->lb_type_graph; - - fp = fopen(filename, "w"); - for (unsigned int inode = 0; inode < lb_type_graph.size(); inode++) { - fprintf(fp, "node %d occ %d cap %d\n", inode, router_data->lb_rr_node_stats[inode].occ, lb_type_graph[inode].capacity); - } - - print_route(fp, router_data); - fclose(fp); -} - -static void print_route(FILE* fp, t_lb_router_data* router_data) { - std::vector& lb_nets = *router_data->intra_lb_nets; - fprintf(fp, "\n\n----------------------------------------------------\n\n"); - - auto& atom_ctx = g_vpr_ctx.atom(); - - for (unsigned int inet = 0; inet < lb_nets.size(); inet++) { - AtomNetId net_id = lb_nets[inet].atom_net_id; - fprintf(fp, "net %s num targets %d \n", atom_ctx.nlist.net_name(net_id).c_str(), (int)lb_nets[inet].terminals.size()); - fprintf(fp, "\tS"); - print_trace(fp, lb_nets[inet].rt_tree, router_data); - fprintf(fp, "\n\n"); - } -} -#endif - -/* Debug routine, print out trace of net */ -static void print_trace(FILE* fp, t_lb_trace* trace, t_lb_router_data* router_data) { - if (trace == NULL) { - fprintf(fp, "NULL"); - return; - } - for (unsigned int ibranch = 0; ibranch < trace->next_nodes.size(); ibranch++) { - auto current_node = trace->current_node; - auto current_str = describe_lb_type_rr_node(current_node, router_data); - auto next_node = trace->next_nodes[ibranch].current_node; - auto next_str = describe_lb_type_rr_node(next_node, router_data); - if (trace->next_nodes.size() > 1) { - fprintf(fp, "\n\tB"); - } - fprintf(fp, "(%d:%s-->%d:%s) ", current_node, current_str.c_str(), next_node, next_str.c_str()); - print_trace(fp, &trace->next_nodes[ibranch], router_data); - } -} - -static void reset_explored_node_tb(t_lb_router_data* router_data) { - std::vector& lb_type_graph = *router_data->lb_type_graph; - for (unsigned int inode = 0; inode < lb_type_graph.size(); inode++) { - router_data->explored_node_tb[inode].prev_index = OPEN; - router_data->explored_node_tb[inode].explored_id = OPEN; - router_data->explored_node_tb[inode].inet = OPEN; - router_data->explored_node_tb[inode].enqueue_id = OPEN; - router_data->explored_node_tb[inode].enqueue_cost = 0; - } -} - -/* Save last successful intra-logic block route and reset current traceback */ -static void save_and_reset_lb_route(t_lb_router_data* router_data) { - std::vector& lb_nets = *router_data->intra_lb_nets; - - /* Free old saved lb nets if exist */ - if (router_data->saved_lb_nets != nullptr) { - free_intra_lb_nets(router_data->saved_lb_nets); - router_data->saved_lb_nets = nullptr; - } - - /* Save current routed solution */ - router_data->saved_lb_nets = new std::vector(lb_nets.size()); - std::vector& saved_lb_nets = *router_data->saved_lb_nets; - - for (int inet = 0; inet < (int)saved_lb_nets.size(); inet++) { - /* - * Save and reset route tree data - */ - saved_lb_nets[inet].atom_net_id = lb_nets[inet].atom_net_id; - saved_lb_nets[inet].terminals.resize(lb_nets[inet].terminals.size()); - for (int iterm = 0; iterm < (int)lb_nets[inet].terminals.size(); iterm++) { - saved_lb_nets[inet].terminals[iterm] = lb_nets[inet].terminals[iterm]; - } - saved_lb_nets[inet].rt_tree = lb_nets[inet].rt_tree; - lb_nets[inet].rt_tree = nullptr; - } -} - -static std::vector find_congested_rr_nodes(const std::vector& lb_type_graph, - const t_lb_rr_node_stats* lb_rr_node_stats) { - std::vector congested_rr_nodes; - for (size_t inode = 0; inode < lb_type_graph.size(); ++inode) { - const t_lb_type_rr_node& rr_node = lb_type_graph[inode]; - const t_lb_rr_node_stats& rr_node_stats = lb_rr_node_stats[inode]; - - if (rr_node_stats.occ > rr_node.capacity) { - congested_rr_nodes.push_back(inode); - } - } - - return congested_rr_nodes; -} - -static std::string describe_lb_type_rr_node(int inode, - const t_lb_router_data* router_data) { - std::string description; - - const t_lb_type_rr_node& rr_node = (*router_data->lb_type_graph)[inode]; - t_logical_block_type_ptr lb_type = router_data->lb_type; - - const t_pb_graph_pin* pb_graph_pin = rr_node.pb_graph_pin; - - if (pb_graph_pin) { - description += "'" + pb_graph_pin->to_string(false) + "'"; - } else if (inode == get_lb_type_rr_graph_ext_source_index(lb_type)) { - VTR_ASSERT(rr_node.type == LB_SOURCE); - description = "cluster-external source (LB_SOURCE)"; - } else if (inode == get_lb_type_rr_graph_ext_sink_index(lb_type)) { - VTR_ASSERT(rr_node.type == LB_SINK); - description = "cluster-external sink (LB_SINK)"; - } else if (rr_node.type == LB_SINK) { - description = "cluster-internal sink (LB_SINK accessible via architecture pins: "; - - //To account for equivalent pins multiple pins may route to a single sink. - //As a result we need to fin all the nodes which connect to this sink in order - //to give user-friendly pin names - std::vector pin_descriptions; - std::vector pin_rrs = find_incoming_rr_nodes(inode, router_data); - for (int pin_rr_idx : pin_rrs) { - const t_pb_graph_pin* pin_pb_gpin = (*router_data->lb_type_graph)[pin_rr_idx].pb_graph_pin; - pin_descriptions.push_back(pin_pb_gpin->to_string()); - } - - description += vtr::join(pin_descriptions, ", "); - description += ")"; - - } else if (rr_node.type == LB_SOURCE) { - description = "cluster-internal source (LB_SOURCE)"; - } else if (rr_node.type == LB_INTERMEDIATE) { - description = "cluster-internal intermediate?"; - } else { - description = ""; - } - - return description; -} - -static std::vector find_incoming_rr_nodes(int dst_node, const t_lb_router_data* router_data) { - std::vector incoming_rr_nodes; - const auto& lb_rr_graph = *router_data->lb_type_graph; - for (size_t inode = 0; inode < lb_rr_graph.size(); ++inode) { - const t_lb_type_rr_node& rr_node = lb_rr_graph[inode]; - for (int mode = 0; mode < rr_node.num_modes; mode++) { - for (int iedge = 0; iedge < rr_node.num_fanout[mode]; ++iedge) { - const t_lb_type_rr_node_edge& rr_edge = rr_node.outedges[mode][iedge]; - - if (rr_edge.node_index == dst_node) { - //The current node connects to the destination node - incoming_rr_nodes.push_back(inode); - } - } - } - } - return incoming_rr_nodes; -} - -static std::string describe_congested_rr_nodes(const std::vector& congested_rr_nodes, - const t_lb_router_data* router_data) { - std::string description; - - const auto& lb_nets = *router_data->intra_lb_nets; - const auto& lb_type_graph = *router_data->lb_type_graph; - const auto& lb_rr_node_stats = router_data->lb_rr_node_stats; - - std::multimap congested_rr_node_to_nets; //From rr_node to net - for (unsigned int inet = 0; inet < lb_nets.size(); inet++) { - AtomNetId atom_net = lb_nets[inet].atom_net_id; - - //Walk the traceback to find congested RR nodes for each net - std::queue q; - - if (lb_nets[inet].rt_tree) { - q.push(*lb_nets[inet].rt_tree); - } - while (!q.empty()) { - t_lb_trace curr = q.front(); - q.pop(); - - for (const t_lb_trace& next_trace : curr.next_nodes) { - q.push(next_trace); - } - - int inode = curr.current_node; - const t_lb_type_rr_node& rr_node = lb_type_graph[inode]; - const t_lb_rr_node_stats& rr_node_stats = lb_rr_node_stats[inode]; - - if (rr_node_stats.occ > rr_node.capacity) { - //Congested - congested_rr_node_to_nets.insert({inode, atom_net}); - } - } - } - - VTR_ASSERT(!congested_rr_node_to_nets.empty()); - VTR_ASSERT(!congested_rr_nodes.empty()); - auto& atom_ctx = g_vpr_ctx.atom(); - for (int inode : congested_rr_nodes) { - const t_lb_type_rr_node& rr_node = lb_type_graph[inode]; - const t_lb_rr_node_stats& rr_node_stats = lb_rr_node_stats[inode]; - description += vtr::string_fmt("RR Node %d (%s) is congested (occ: %d > capacity: %d) with the following nets:\n", - inode, - describe_lb_type_rr_node(inode, router_data).c_str(), - rr_node_stats.occ, - rr_node.capacity); - auto range = congested_rr_node_to_nets.equal_range(inode); - for (auto itr = range.first; itr != range.second; ++itr) { - AtomNetId net = itr->second; - description += vtr::string_fmt("\tNet: %s\n", - atom_ctx.nlist.net_name(net).c_str()); - } - } - - return description; -} - -void reset_intra_lb_route(t_lb_router_data* router_data) { - for (auto& node : *router_data->lb_type_graph) { - auto* pin = node.pb_graph_pin; - if (pin == nullptr) { - continue; - } - VTR_ASSERT(pin->parent_node != nullptr); - pin->parent_node->illegal_modes.clear(); - } -} - -} /* end namespace openfpga */ diff --git a/openfpga/src/repack/lb_router.h b/openfpga/src/repack/lb_router.h index a27011df0..fe4c7d110 100644 --- a/openfpga/src/repack/lb_router.h +++ b/openfpga/src/repack/lb_router.h @@ -1,16 +1,20 @@ -/******************************************************************** - * Intra-logic block router determines if a candidate packing solution (or intermediate solution) can route. - * Adapted from original VPR cluster router to the use of LbRRGraph object - *******************************************************************/ #ifndef LB_ROUTER_H #define LB_ROUTER_H /******************************************************************** * Include header files that are required by function declaration *******************************************************************/ +#include +#include #include + +#include "vtr_vector.h" + +#include "arch_types.h" +#include "vpr_types.h" #include "atom_netlist_fwd.h" -#include "pack_types.h" + +#include "lb_rr_graph.h" /******************************************************************** * Function declaration @@ -19,21 +23,160 @@ /* begin namespace openfpga */ namespace openfpga { -/* Constructors/Destructors */ -t_lb_router_data* alloc_and_load_router_data(std::vector* lb_type_graph, t_logical_block_type_ptr type); -void free_router_data(t_lb_router_data* router_data); -void free_intra_lb_nets(std::vector* intra_lb_nets); +class LbRouter { + public: /* Intra-Logic Block Routing Data Structures (by instance) */ + /************************************************************************** + * A routing traceback data structure, provides a logic cluster_ctx.blocks + * instance specific trace lookup directly from the t_lb_type_rr_node array index + * After packing, routing info for each CLB will have an array of t_lb_traceback + * to store routing info within the CLB + ***************************************************************************/ + struct t_traceback { + int net; /* net of flat, technology-mapped, netlist using this node */ + LbRRNodeId prev_lb_rr_node; /* index of previous node that drives current node */ + LbRREdgeId prev_edge; /* index of previous edge that drives current node */ + }; -/* Routing Functions */ -void add_atom_as_target(t_lb_router_data* router_data, const AtomBlockId blk_id); -void remove_atom_from_target(t_lb_router_data* router_data, const AtomBlockId blk_id); -void set_reset_pb_modes(t_lb_router_data* router_data, const t_pb* pb, const bool set); -bool try_intra_lb_route(t_lb_router_data* router_data, int verbosity, t_mode_selection_status* mode_status); -void reset_intra_lb_route(t_lb_router_data* router_data); + /************************************************************************** + * Describes the status of a logic cluster_ctx.blocks routing resource node + * for a given logic cluster_ctx.blocks instance + ***************************************************************************/ + struct t_routing_stats { + int occ; /* Number of nets currently using this lb_rr_node */ + t_mode* mode; /* Mode that this rr_node is set to */ + + int historical_usage; /* Historical usage of using this node */ + + t_lb_rr_graph_stats() { + occ = 0; + mode = nullptr; + historical_usage = 0; + } + }; -/* Accessor Functions */ -t_pb_routes alloc_and_load_pb_route(const std::vector* intra_lb_nets, t_pb_graph_node* pb_graph_head); -void free_pb_route(t_pb_route* free_pb_route); + /************************************************************************** + * Data structure forming the route tree of a net within one logic cluster_ctx.blocks. + * A net is implemented using routing resource nodes. + * The t_lb_trace data structure records one of the nodes used by the net and the connections + * to other nodes + ***************************************************************************/ + struct t_trace { + LbRRNodeId current_node; /* current t_lb_type_rr_node used by net */ + std::vector next_nodes; /* index of previous edge that drives current node */ + }; + + /************************************************************************** + * Represents a net used inside a logic cluster_ctx.blocks and the + * physical nodes used by the net + ***************************************************************************/ + struct t_net { + AtomNetId atom_net_id; /* index of atom net this intra_lb_net represents */ + std::vector terminals; /* endpoints of the intra_lb_net, 0th position is the source, all others are sinks */ + std::vector atom_pins; /* AtomPin's associated with each terminal */ + std::vector fixed_terminals; /* Marks a terminal as having a fixed target (i.e. a pin not a sink) */ + t_lb_trace* rt_tree; /* Route tree head */ + + t_lb_rr_net() { + atom_net_id = AtomNetId::INVALID(); + rt_tree = nullptr; + } + }; + + /************************************************************************** + * Stores tuning parameters used by intra-logic cluster_ctx.blocks router + ***************************************************************************/ + struct t_option { + int max_iterations; + float pres_fac; + float pres_fac_mult; + float hist_fac; + }; + + /************************************************************************** + * Node expanded by router + ***************************************************************************/ + struct t_expansion_node { + LbRRNodeId node_index; /* Index of logic cluster_ctx.blocks rr node this expansion node represents */ + LbRRNodeId prev_index; /* Index of logic cluster_ctx.blocks rr node that drives this expansion node */ + float cost; + + t_expansion_node() { + node_index = LbRRNodeId::INVALID(); + prev_index = LbRRNodeId::INVALID(); + cost = 0; + } + }; + + class compare_expansion_node { + public: + /* Returns true if t1 is earlier than t2 */ + bool operator()(t_expansion_node& e1, t_expansion_node& e2) { + if (e1.cost > e2.cost) { + return true; + } + return false; + } + }; + + /************************************************************************** + * Stores explored nodes by router + ***************************************************************************/ + struct t_explored_node_stats { + LbRRNodeId prev_index; /* Prevous node that drives this one */ + int explored_id; /* ID used to determine if this node has been explored */ + int inet; /* net index of route tree */ + int enqueue_id; /* ID used ot determine if this node has been pushed on exploration priority queue */ + float enqueue_cost; /* cost of node pused on exploration priority queue */ + + t_explored_node_stats() { + prev_index = LbRRNodeId::INVALID(); + explored_id = OPEN; + enqueue_id = OPEN; + inet = OPEN; + enqueue_cost = 0; + } + }; + + /************************************************************************** + * Stores status of mode selection during clustering + ***************************************************************************/ + struct t_mode_selection_status { + bool is_mode_conflict = false; + bool try_expand_all_modes = false; + bool expand_all_modes = false; + + bool is_mode_issue() { + return is_mode_conflict || try_expand_all_modes; + } + }; + + private : /* Stores all data needed by intra-logic cluster_ctx.blocks router */ + /* Logical Netlist Info */ + std::vector intra_lb_nets_; /* Pointer to vector of intra logic cluster_ctx.blocks nets and their connections */ + + /* Saved nets */ + std::vector saved_lb_nets_; /* Save vector of intra logic cluster_ctx.blocks nets and their connections */ + + std::map atoms_added_; /* map that records which atoms are added to cluster router */ + + /* Logical-to-physical mapping info */ + vtr::vector lb_rr_node_stats_; /* [0..lb_type_graph->size()-1] Stats for each logic cluster_ctx.blocks rr node instance */ + + /* Stores state info during Pathfinder iterative routing */ + vtr::vector explored_node_tb_; /* [0..lb_type_graph->size()-1] Stores mode exploration and traceback info for nodes */ + int explore_id_index_; /* used in conjunction with node_traceback to determine whether or not a location has been explored. By using a unique identifier every route, I don't have to clear the previous route exploration */ + + /* Current type */ + t_logical_block_type_ptr lb_type_; + + /* Parameters used by router */ + t_option options_; + + bool is_routed_; /* Stores whether or not the current logical-to-physical mapping has a routed solution */ + + /* current congestion factor */ + float pres_con_fac_; +}; } /* end namespace openfpga */ diff --git a/openfpga/src/repack/lb_rr_graph.cpp b/openfpga/src/repack/lb_rr_graph.cpp index 99b0ecd1b..712acd50b 100644 --- a/openfpga/src/repack/lb_rr_graph.cpp +++ b/openfpga/src/repack/lb_rr_graph.cpp @@ -9,6 +9,14 @@ /* begin namespace openfpga */ namespace openfpga { +/************************************************** + * Public Constructors + *************************************************/ +LbRRGraph::LbRRGraph() { + ext_source_node_ = LbRRNodeId::INVALID(); + ext_sink_node_ = LbRRNodeId::INVALID(); +} + /************************************************** * Public Accessors: Aggregates *************************************************/ @@ -43,6 +51,11 @@ float LbRRGraph::node_intrinsic_cost(const LbRRNodeId& node) const { return node_intrinsic_costs_[node]; } +std::vector LbRRGraph::node_in_edges(const LbRRNodeId& node) const { + VTR_ASSERT(true == valid_node_id(node)); + return node_in_edges_[node]; +} + std::vector LbRRGraph::node_in_edges(const LbRRNodeId& node, t_mode* mode) const { std::vector in_edges; @@ -81,6 +94,24 @@ LbRRNodeId LbRRGraph::find_node(const e_lb_rr_type& type, t_pb_graph_pin* pb_gra return node_lookup_[size_t(type)].at(pb_graph_pin); } +LbRRNodeId ext_source_node() const { + return ext_source_node_; +} + +LbRRNodeId ext_sink_node() const { + return ext_sink_node_; +} + +std::vector find_edge(const LbRRNodeId& src_node, const LbRRNodeId& sink_node) const { + std::vector edges; + for (const LbRREdgeId& edge : node_out_edges_[src_node]) { + if (sink_node == edge_sink_node(edge)) { + edges.push_back(edge); + } + } + return edges; +} + LbRRNodeId LbRRGraph::edge_src_node(const LbRREdgeId& edge) const { VTR_ASSERT(true == valid_edge_id(edge)); return edge_src_nodes_[edge]; @@ -141,6 +172,16 @@ LbRRNodeId LbRRGraph::create_node(const e_lb_rr_type& type) { return node; } +LbRRNodeId LbRRGraph::create_ext_source_node(const e_lb_rr_type& type) { + LbRRNodeId ext_source_node = create_node(type); + ext_source_node_ = ext_source_node; +} + +LbRRNodeId LbRRGraph::create_ext_sink_node(const e_lb_rr_type& type) { + LbRRNodeId ext_sink_node = create_node(type); + ext_sink_node_ = ext_sink_node; +} + void LbRRGraph::set_node_type(const LbRRNodeId& node, const e_lb_rr_type& type) { VTR_ASSERT(true == valid_node_id(node)); node_types_[node] = type; diff --git a/openfpga/src/repack/lb_rr_graph.h b/openfpga/src/repack/lb_rr_graph.h index c6cfbd552..a85bcd4c5 100644 --- a/openfpga/src/repack/lb_rr_graph.h +++ b/openfpga/src/repack/lb_rr_graph.h @@ -168,6 +168,9 @@ class LbRRGraph { typedef vtr::Range node_range; typedef vtr::Range edge_range; + public: /* Constructors */ + LbRRGraph(); + public: /* Accessors */ /* Aggregates: create range-based loops for nodes/edges/switches/segments * To iterate over the nodes/edges/switches/segments in a RRGraph, @@ -195,13 +198,20 @@ class LbRRGraph { float node_intrinsic_cost(const LbRRNodeId& node) const; /* Get a list of edge ids, which are incoming edges to a node */ + std::vector node_in_edges(const LbRRNodeId& node) const; std::vector node_in_edges(const LbRRNodeId& node, t_mode* mode) const; /* Get a list of edge ids, which are outgoing edges from a node */ std::vector node_out_edges(const LbRRNodeId& node, t_mode* mode) const; + /* General method to look up a node with type and only pb_graph_pin information */ LbRRNodeId find_node(const e_lb_rr_type& type, t_pb_graph_pin* pb_graph_pin) const; + /* Method to find special node */ + LbRRNodeId ext_source_node() const; + LbRRNodeId ext_sink_node() const; + /* General method to look up a edge with source and sink nodes */ + std::vector find_edge(const LbRRNodeId& src_node, const LbRRNodeId& sink_node) const; /* Get the source node which drives a edge */ LbRRNodeId edge_src_node(const LbRREdgeId& edge) const; /* Get the sink node which a edge ends to */ @@ -234,6 +244,10 @@ class LbRRGraph { * set_node_xlow(node, 0); */ LbRRNodeId create_node(const e_lb_rr_type& type); + + /* Create special nodes */ + LbRRNodeId create_ext_source_node(const e_lb_rr_type& type); + LbRRNodeId create_ext_sink_node(const e_lb_rr_type& type); /* Set node-level information */ void set_node_type(const LbRRNodeId& node, const e_lb_rr_type& type); @@ -289,6 +303,10 @@ class LbRRGraph { */ typedef std::vector> NodeLookup; mutable NodeLookup node_lookup_; + + /* Special node look-up */ + LbRRNodeId ext_source_node_; + LbRRNodeId ext_sink_node_; }; } /* end namespace openfpga */ diff --git a/openfpga/src/repack/lb_rr_graph_utils.cpp b/openfpga/src/repack/lb_rr_graph_utils.cpp new file mode 100644 index 000000000..665dace21 --- /dev/null +++ b/openfpga/src/repack/lb_rr_graph_utils.cpp @@ -0,0 +1,59 @@ +/*************************************************************************************** + * This file includes most utilized functions for LbRRGraph object + ***************************************************************************************/ + +/* Headers from vtrutil library */ +#include "vtr_log.h" +#include "vtr_assert.h" + +#include "lb_rr_graph_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/*************************************************************************************** + * Generate a string to describe a node in a logical tile rr_graph + * in the context of logical tile + ***************************************************************************************/ +std::string describe_lb_rr_node(const LbRRGraph& lb_rr_graph, + const LbRRNodeId& inode) { + std::string description; + + const t_pb_graph_pin* pb_graph_pin = lb_rr_graph.node_pb_graph_pin(inode); + + if (pb_graph_pin) { + description += "'" + pb_graph_pin->to_string(false) + "'"; + } else if (inode == lb_rr_graph.ext_source_node()) { + VTR_ASSERT(LB_SOURCE == lb_rr_graph.node_type(inode)); + description = "cluster-external source (LB_SOURCE)"; + } else if (inode == lb_rr_graph.ext_sink_index()) { + VTR_ASSERT(LB_SINK == lb_rr_graph.node_type(inode)); + description = "cluster-external sink (LB_SINK)"; + } else if (LB_SINK == lb_rr_graph.node_type(inode)) { + description = "cluster-internal sink (LB_SINK accessible via architecture pins: "; + + //To account for equivalent pins multiple pins may route to a single sink. + //As a result we need to fin all the nodes which connect to this sink in order + //to give user-friendly pin names + std::vector pin_descriptions; + for (const LbRREdgeId edge : lb_rr_graph.node_in_edges(inode)) { + const LbRRNodeId pin_rr_idx = lb_rr_graph.edge_src_node(edge); + const t_pb_graph_pin* pin_pb_gpin = lb_rr_graph.node_pb_graph_pin(pin_rr_idx); + pin_descriptions.push_back(pin_pb_gpin->to_string()); + } + + description += vtr::join(pin_descriptions, ", "); + description += ")"; + + } else if (LB_SOURCE == lb_rr_graph.node_type(inode)) { + description = "cluster-internal source (LB_SOURCE)"; + } else if (LB_INTERMEDIATE == lb_rr_graph.node_type(inode)) { + description = "cluster-internal intermediate?"; + } else { + description = ""; + } + + return description; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/repack/lb_rr_graph_utils.h b/openfpga/src/repack/lb_rr_graph_utils.h new file mode 100644 index 000000000..392617f6a --- /dev/null +++ b/openfpga/src/repack/lb_rr_graph_utils.h @@ -0,0 +1,22 @@ +#ifndef LB_RR_GRAPH_UTILS_H +#define LB_RR_GRAPH_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include "lb_rr_graph.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +std::string describe_lb_rr_node(const LbRRGraph& lb_rr_graph, + const LbRRNodeId& inode); + +} /* end namespace openfpga */ + +#endif From 1799db810da4d9d6e8fa9991e881b98e4a6006f0 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 18 Feb 2020 17:04:36 -0700 Subject: [PATCH 172/645] compilation error fix --- openfpga/src/repack/lb_router.h | 8 ++++---- openfpga/src/repack/lb_rr_graph.cpp | 6 +++--- openfpga/src/repack/lb_rr_graph_utils.cpp | 3 ++- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/openfpga/src/repack/lb_router.h b/openfpga/src/repack/lb_router.h index fe4c7d110..c0e575324 100644 --- a/openfpga/src/repack/lb_router.h +++ b/openfpga/src/repack/lb_router.h @@ -10,8 +10,7 @@ #include "vtr_vector.h" -#include "arch_types.h" -#include "vpr_types.h" +#include "physical_types.h" #include "atom_netlist_fwd.h" #include "lb_rr_graph.h" @@ -41,7 +40,7 @@ class LbRouter { * Describes the status of a logic cluster_ctx.blocks routing resource node * for a given logic cluster_ctx.blocks instance ***************************************************************************/ - struct t_routing_stats { + struct t_routing_status { int occ; /* Number of nets currently using this lb_rr_node */ t_mode* mode; /* Mode that this rr_node is set to */ @@ -160,10 +159,11 @@ class LbRouter { std::map atoms_added_; /* map that records which atoms are added to cluster router */ /* Logical-to-physical mapping info */ - vtr::vector lb_rr_node_stats_; /* [0..lb_type_graph->size()-1] Stats for each logic cluster_ctx.blocks rr node instance */ + vtr::vector routing_status_; /* [0..lb_type_graph->size()-1] Stats for each logic cluster_ctx.blocks rr node instance */ /* Stores state info during Pathfinder iterative routing */ vtr::vector explored_node_tb_; /* [0..lb_type_graph->size()-1] Stores mode exploration and traceback info for nodes */ + int explore_id_index_; /* used in conjunction with node_traceback to determine whether or not a location has been explored. By using a unique identifier every route, I don't have to clear the previous route exploration */ /* Current type */ diff --git a/openfpga/src/repack/lb_rr_graph.cpp b/openfpga/src/repack/lb_rr_graph.cpp index 712acd50b..581edd905 100644 --- a/openfpga/src/repack/lb_rr_graph.cpp +++ b/openfpga/src/repack/lb_rr_graph.cpp @@ -94,15 +94,15 @@ LbRRNodeId LbRRGraph::find_node(const e_lb_rr_type& type, t_pb_graph_pin* pb_gra return node_lookup_[size_t(type)].at(pb_graph_pin); } -LbRRNodeId ext_source_node() const { +LbRRNodeId LbRRGraph::ext_source_node() const { return ext_source_node_; } -LbRRNodeId ext_sink_node() const { +LbRRNodeId LbRRGraph::ext_sink_node() const { return ext_sink_node_; } -std::vector find_edge(const LbRRNodeId& src_node, const LbRRNodeId& sink_node) const { +std::vector LbRRGraph::find_edge(const LbRRNodeId& src_node, const LbRRNodeId& sink_node) const { std::vector edges; for (const LbRREdgeId& edge : node_out_edges_[src_node]) { if (sink_node == edge_sink_node(edge)) { diff --git a/openfpga/src/repack/lb_rr_graph_utils.cpp b/openfpga/src/repack/lb_rr_graph_utils.cpp index 665dace21..351103647 100644 --- a/openfpga/src/repack/lb_rr_graph_utils.cpp +++ b/openfpga/src/repack/lb_rr_graph_utils.cpp @@ -5,6 +5,7 @@ /* Headers from vtrutil library */ #include "vtr_log.h" #include "vtr_assert.h" +#include "vtr_util.h" #include "lb_rr_graph_utils.h" @@ -26,7 +27,7 @@ std::string describe_lb_rr_node(const LbRRGraph& lb_rr_graph, } else if (inode == lb_rr_graph.ext_source_node()) { VTR_ASSERT(LB_SOURCE == lb_rr_graph.node_type(inode)); description = "cluster-external source (LB_SOURCE)"; - } else if (inode == lb_rr_graph.ext_sink_index()) { + } else if (inode == lb_rr_graph.ext_sink_node()) { VTR_ASSERT(LB_SINK == lb_rr_graph.node_type(inode)); description = "cluster-external sink (LB_SINK)"; } else if (LB_SINK == lb_rr_graph.node_type(inode)) { From 0310dafe423edf0bb363da6a8b96a9a17bd0ca2a Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 18 Feb 2020 18:35:00 -0700 Subject: [PATCH 173/645] add accessors to LBRouter --- openfpga/src/repack/lb_router.cpp | 26 ++++++++++++++++++++++++++ openfpga/src/repack/lb_router.h | 15 +++++++++++---- 2 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 openfpga/src/repack/lb_router.cpp diff --git a/openfpga/src/repack/lb_router.cpp b/openfpga/src/repack/lb_router.cpp new file mode 100644 index 000000000..be2cbe37f --- /dev/null +++ b/openfpga/src/repack/lb_router.cpp @@ -0,0 +1,26 @@ +/****************************************************************************** + * Memember functions for data structure LbRouter + ******************************************************************************/ +#include "vtr_assert.h" + +#include "lb_router.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/************************************************** + * Public Accessors + *************************************************/ +std::vector LbRouter::find_congested_rr_nodes(const LbRRGraph& lb_rr_graph) const { + std::vector congested_rr_nodes; + + for (const LbRRNodeId& inode : lb_rr_graph.nodes()) { + if (routing_status_[inode].occ > lb_rr_graph.node_capacity(inode)) { + congested_rr_nodes.push_back(inode); + } + } + + return congested_rr_nodes; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/repack/lb_router.h b/openfpga/src/repack/lb_router.h index c0e575324..ae45afba5 100644 --- a/openfpga/src/repack/lb_router.h +++ b/openfpga/src/repack/lb_router.h @@ -46,7 +46,7 @@ class LbRouter { int historical_usage; /* Historical usage of using this node */ - t_lb_rr_graph_stats() { + t_routing_status() { occ = 0; mode = nullptr; historical_usage = 0; @@ -61,7 +61,7 @@ class LbRouter { ***************************************************************************/ struct t_trace { LbRRNodeId current_node; /* current t_lb_type_rr_node used by net */ - std::vector next_nodes; /* index of previous edge that drives current node */ + std::vector next_nodes; /* index of previous edge that drives current node */ }; /************************************************************************** @@ -73,9 +73,9 @@ class LbRouter { std::vector terminals; /* endpoints of the intra_lb_net, 0th position is the source, all others are sinks */ std::vector atom_pins; /* AtomPin's associated with each terminal */ std::vector fixed_terminals; /* Marks a terminal as having a fixed target (i.e. a pin not a sink) */ - t_lb_trace* rt_tree; /* Route tree head */ + t_trace* rt_tree; /* Route tree head */ - t_lb_rr_net() { + t_net() { atom_net_id = AtomNetId::INVALID(); rt_tree = nullptr; } @@ -148,6 +148,13 @@ class LbRouter { return is_mode_conflict || try_expand_all_modes; } }; + + public : /* Public accessors */ + /** + * Find all the routing resource nodes that is congested, which they are used more than their capacity + * This function is call to collect the nodes and router can reroute these net + */ + std::vector find_congested_rr_nodes(const LbRRGraph& lb_rr_graph) const; private : /* Stores all data needed by intra-logic cluster_ctx.blocks router */ /* Logical Netlist Info */ From 11879d43b404db02c46a8903003b0ef529819a84 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 18 Feb 2020 19:22:36 -0700 Subject: [PATCH 174/645] add methods one by one to LbRouter from cluster_router.cpp --- openfpga/src/repack/lb_router.cpp | 106 ++++++++++++++++++++++++++++++ openfpga/src/repack/lb_router.h | 31 ++++++++- 2 files changed, 134 insertions(+), 3 deletions(-) diff --git a/openfpga/src/repack/lb_router.cpp b/openfpga/src/repack/lb_router.cpp index be2cbe37f..f362ce34a 100644 --- a/openfpga/src/repack/lb_router.cpp +++ b/openfpga/src/repack/lb_router.cpp @@ -2,16 +2,37 @@ * Memember functions for data structure LbRouter ******************************************************************************/ #include "vtr_assert.h" +#include "vtr_log.h" #include "lb_router.h" /* begin namespace openfpga */ namespace openfpga { +/************************************************** + * Public Constructors + *************************************************/ +LbRouter::LbRouter(const LbRRGraph& lb_rr_graph) { + routing_status_.resize(lb_rr_graph.nodes().size()); + explored_node_tb_.resize(lb_rr_graph.nodes().size()); + explore_id_index_ = 1; + + /* Default routing parameters */ + params_.max_iterations = 50; + params_.pres_fac = 1; + params_.pres_fac_mult = 2; + params_.hist_fac = 0.3; + + pres_con_fac_ = 1; +} + /************************************************** * Public Accessors *************************************************/ std::vector LbRouter::find_congested_rr_nodes(const LbRRGraph& lb_rr_graph) const { + /* Validate if the rr_graph is the one we used to initialize the router */ + VTR_ASSERT(true == matched_lb_rr_graph(lb_rr_graph)); + std::vector congested_rr_nodes; for (const LbRRNodeId& inode : lb_rr_graph.nodes()) { @@ -23,4 +44,89 @@ std::vector LbRouter::find_congested_rr_nodes(const LbRRGraph& lb_rr return congested_rr_nodes; } +bool LbRouter::is_route_success(const LbRRGraph& lb_rr_graph) const { + /* Validate if the rr_graph is the one we used to initialize the router */ + VTR_ASSERT(true == matched_lb_rr_graph(lb_rr_graph)); + + for (const LbRRNodeId& inode : lb_rr_graph.nodes()) { + if (routing_status_[inode].occ > lb_rr_graph.node_capacity(inode)) { + return false; + } + } + + return true; +} + +/************************************************** + * Private accessors + *************************************************/ +LbRouter::t_trace* LbRouter::find_node_in_rt(t_trace* rt, const LbRRNodeId& rt_index) { + t_trace* cur; + if (rt->current_node == rt_index) { + return rt; + } else { + for (unsigned int i = 0; i < rt->next_nodes.size(); i++) { + cur = find_node_in_rt(&rt->next_nodes[i], rt_index); + if (cur != nullptr) { + return cur; + } + } + } + return nullptr; +} + +/************************************************** + * Private mutators + *************************************************/ +void LbRouter::reset_explored_node_tb() { + for (t_explored_node_stats& explored_node : explored_node_tb_) { + explored_node.prev_index = LbRRNodeId::INVALID(); + explored_node.explored_id = OPEN; + explored_node.inet = OPEN; + explored_node.enqueue_id = OPEN; + explored_node.enqueue_cost = 0; + } +} + +bool LbRouter::add_to_rt(t_trace* rt, const LbRRNodeId& node_index, const int& irt_net) { + std::vector trace_forward; + t_trace* link_node; + t_trace curr_node; + + /* Store path all the way back to route tree */ + LbRRNodeId rt_index = node_index; + while (explored_node_tb_[rt_index].inet != irt_net) { + trace_forward.push_back(rt_index); + rt_index = explored_node_tb_[rt_index].prev_index; + VTR_ASSERT(rt_index != LbRRNodeId::INVALID()); + } + + /* Find rt_index on the route tree */ + link_node = find_node_in_rt(rt, rt_index); + if (link_node == nullptr) { + VTR_LOG("Link node is nullptr. Routing impossible"); + return true; + } + + /* Add path to root tree */ + LbRRNodeId trace_index; + while (!trace_forward.empty()) { + trace_index = trace_forward.back(); + curr_node.current_node = trace_index; + link_node->next_nodes.push_back(curr_node); + link_node = &link_node->next_nodes.back(); + trace_forward.pop_back(); + } + + return false; +} + +/************************************************** + * Private validators + *************************************************/ +bool LbRouter::matched_lb_rr_graph(const LbRRGraph& lb_rr_graph) const { + return ( (routing_status_.size() == lb_rr_graph.nodes().size()) + && (explored_node_tb_.size() == lb_rr_graph.nodes().size()) ); +} + } /* end namespace openfpga */ diff --git a/openfpga/src/repack/lb_router.h b/openfpga/src/repack/lb_router.h index ae45afba5..e5f1ba432 100644 --- a/openfpga/src/repack/lb_router.h +++ b/openfpga/src/repack/lb_router.h @@ -148,17 +148,42 @@ class LbRouter { return is_mode_conflict || try_expand_all_modes; } }; + + public : /* Public constructors */ + LbRouter(const LbRRGraph& lb_rr_graph); public : /* Public accessors */ /** - * Find all the routing resource nodes that is congested, which they are used more than their capacity + * Find all the routing resource nodes that are over-used, which they are used more than their capacity * This function is call to collect the nodes and router can reroute these net */ std::vector find_congested_rr_nodes(const LbRRGraph& lb_rr_graph) const; + /** + * Report if the routing is successfully done on a logical block routing resource graph + */ + bool is_route_success(const LbRRGraph& lb_rr_graph) const; + + private : /* Private accessors */ + /** + * Try to find a node in the routing traces recursively + * If not found, will return an empty pointer + */ + t_trace* find_node_in_rt(t_trace* rt, const LbRRNodeId& rt_index); + + private : /* Private mutators */ + void reset_explored_node_tb(); + bool add_to_rt(t_trace* rt, const LbRRNodeId& node_index, const int& irt_net); + + private : /* Private validators */ + /** + * Validate if the rr_graph is the one we used to initialize the router + */ + bool matched_lb_rr_graph(const LbRRGraph& lb_rr_graph) const; + private : /* Stores all data needed by intra-logic cluster_ctx.blocks router */ /* Logical Netlist Info */ - std::vector intra_lb_nets_; /* Pointer to vector of intra logic cluster_ctx.blocks nets and their connections */ + std::vector lb_nets_; /* Pointer to vector of intra logic cluster_ctx.blocks nets and their connections */ /* Saved nets */ std::vector saved_lb_nets_; /* Save vector of intra logic cluster_ctx.blocks nets and their connections */ @@ -177,7 +202,7 @@ class LbRouter { t_logical_block_type_ptr lb_type_; /* Parameters used by router */ - t_option options_; + t_option params_; bool is_routed_; /* Stores whether or not the current logical-to-physical mapping has a routed solution */ From c7ef14fc23517859a7b15cae30f518edb767bad7 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 18 Feb 2020 21:51:03 -0700 Subject: [PATCH 175/645] refactoring node expansion in LbRouter --- openfpga/src/repack/lb_router.cpp | 116 ++++++++++++++++++++++++++++ openfpga/src/repack/lb_router.h | 42 ++++++++++ openfpga/src/repack/lb_rr_graph.cpp | 9 +++ openfpga/src/repack/lb_rr_graph.h | 1 + 4 files changed, 168 insertions(+) diff --git a/openfpga/src/repack/lb_router.cpp b/openfpga/src/repack/lb_router.cpp index f362ce34a..63fc0105b 100644 --- a/openfpga/src/repack/lb_router.cpp +++ b/openfpga/src/repack/lb_router.cpp @@ -1,6 +1,8 @@ /****************************************************************************** * Memember functions for data structure LbRouter ******************************************************************************/ +#include + #include "vtr_assert.h" #include "vtr_log.h" @@ -121,6 +123,120 @@ bool LbRouter::add_to_rt(t_trace* rt, const LbRRNodeId& node_index, const int& i return false; } +void LbRouter::expand_edges(const LbRRGraph& lb_rr_graph, + t_mode* mode, + const LbRRNodeId& cur_inode, + float cur_cost, + int net_fanout, + reservable_pq, compare_expansion_node>& pq) { + /* Validate if the rr_graph is the one we used to initialize the router */ + VTR_ASSERT(true == matched_lb_rr_graph(lb_rr_graph)); + + t_expansion_node enode; + int usage; + float incr_cost; + + for (const LbRREdgeId& iedge : lb_rr_graph.node_out_edges(cur_inode, mode)) { + /* Init new expansion node */ + enode.prev_index = cur_inode; + enode.node_index = lb_rr_graph.edge_sink_node(iedge); + enode.cost = cur_cost; + + /* Determine incremental cost of using expansion node */ + usage = routing_status_[enode.node_index].occ + 1 - lb_rr_graph.node_capacity(enode.node_index); + incr_cost = lb_rr_graph.node_intrinsic_cost(enode.node_index); + incr_cost += lb_rr_graph.edge_intrinsic_cost(iedge); + incr_cost += params_.hist_fac * routing_status_[enode.node_index].historical_usage; + if (usage > 0) { + incr_cost *= (usage * pres_con_fac_); + } + + /* Adjust cost so that higher fanout nets prefer higher fanout routing nodes while lower fanout nets prefer lower fanout routing nodes */ + float fanout_factor = 1.0; + t_mode* next_mode = routing_status_[enode.node_index].mode; + /* Assume first mode if a mode hasn't been forced. */ + if (nullptr == next_mode) { + next_mode = &(lb_rr_graph.node_pb_graph_pin(enode.node_index)->parent_node->pb_type->modes[0]); + } + if (lb_rr_graph.node_out_edges(enode.node_index, next_mode).size() > 1) { + fanout_factor = 0.85 + (0.25 / net_fanout); + } else { + fanout_factor = 1.15 - (0.25 / net_fanout); + } + + incr_cost *= fanout_factor; + enode.cost = cur_cost + incr_cost; + + /* Add to queue if cost is lower than lowest cost path to this enode */ + if (explored_node_tb_[enode.node_index].enqueue_id == explore_id_index_) { + if (enode.cost < explored_node_tb_[enode.node_index].enqueue_cost) { + pq.push(enode); + } + } else { + explored_node_tb_[enode.node_index].enqueue_id = explore_id_index_; + explored_node_tb_[enode.node_index].enqueue_cost = enode.cost; + pq.push(enode); + } + } +} + +void LbRouter::expand_node(const LbRRGraph& lb_rr_graph, + const t_expansion_node& exp_node, + reservable_pq, compare_expansion_node>& pq, + const int& net_fanout) { + /* Validate if the rr_graph is the one we used to initialize the router */ + VTR_ASSERT(true == matched_lb_rr_graph(lb_rr_graph)); + + t_expansion_node enode; + + LbRRNodeId cur_node = exp_node.node_index; + float cur_cost = exp_node.cost; + t_mode* mode = routing_status_[cur_node].mode; + if (nullptr == mode) { + mode = &(lb_rr_graph.node_pb_graph_pin(cur_node)->parent_node->pb_type->modes[0]); + } + + expand_edges(lb_rr_graph, mode, cur_node, cur_cost, net_fanout, pq); +} + +void LbRouter::expand_node_all_modes(const LbRRGraph& lb_rr_graph, + const t_expansion_node& exp_node, + reservable_pq, compare_expansion_node>& pq, + const int& net_fanout) { + /* Validate if the rr_graph is the one we used to initialize the router */ + VTR_ASSERT(true == matched_lb_rr_graph(lb_rr_graph)); + + LbRRNodeId cur_inode = exp_node.node_index; + float cur_cost = exp_node.cost; + t_mode* cur_mode = routing_status_[cur_inode].mode; + auto* pin = lb_rr_graph.node_pb_graph_pin(cur_inode); + + for (const LbRREdgeId& edge : lb_rr_graph.node_out_edges(cur_inode)) { + t_mode* mode = lb_rr_graph.edge_mode(edge); + /* If a mode has been forced, only add edges from that mode, otherwise add edges from all modes. */ + if (cur_mode != nullptr && mode != cur_mode) { + continue; + } + + /* Check whether a mode is illegal. If it is then the node will not be expanded */ + bool is_illegal = false; + if (pin != nullptr) { + auto* pb_graph_node = pin->parent_node; + for (auto illegal_mode : pb_graph_node->illegal_modes) { + if (mode->index == illegal_mode) { + is_illegal = true; + break; + } + } + } + + if (is_illegal == true) { + continue; + } + expand_edges(lb_rr_graph, mode, cur_inode, cur_cost, net_fanout, pq); + } +} + /************************************************** * Private validators *************************************************/ diff --git a/openfpga/src/repack/lb_router.h b/openfpga/src/repack/lb_router.h index e5f1ba432..51778df91 100644 --- a/openfpga/src/repack/lb_router.h +++ b/openfpga/src/repack/lb_router.h @@ -149,6 +149,34 @@ class LbRouter { } }; + // TODO: check if this hacky class memory reserve thing is still necessary, if not, then delete + /* Packing uses a priority queue that requires a large number of elements. + * This backdoor + * allows me to use a priority queue where I can pre-allocate the # of elements + * in the underlying container + * for efficiency reasons. Note: Must use vector with this + */ + template + class reservable_pq : public std::priority_queue { + public: + typedef typename std::priority_queue::size_type size_type; + reservable_pq(size_type capacity = 0) { + reserve(capacity); + cur_cap = capacity; + } + void reserve(size_type capacity) { + this->c.reserve(capacity); + cur_cap = capacity; + } + void clear() { + this->c.clear(); + this->c.reserve(cur_cap); + } + + private: + size_type cur_cap; + }; + public : /* Public constructors */ LbRouter(const LbRRGraph& lb_rr_graph); @@ -174,6 +202,20 @@ class LbRouter { private : /* Private mutators */ void reset_explored_node_tb(); bool add_to_rt(t_trace* rt, const LbRRNodeId& node_index, const int& irt_net); + void expand_edges(const LbRRGraph& lb_rr_graph, + t_mode* mode, + const LbRRNodeId& cur_inode, + float cur_cost, + int net_fanout, + reservable_pq, compare_expansion_node>& pq); + void expand_node(const LbRRGraph& lb_rr_graph, + const t_expansion_node& exp_node, + reservable_pq, compare_expansion_node>& pq, + const int& net_fanout); + void expand_node_all_modes(const LbRRGraph& lb_rr_graph, + const t_expansion_node& exp_node, + reservable_pq, compare_expansion_node>& pq, + const int& net_fanout); private : /* Private validators */ /** diff --git a/openfpga/src/repack/lb_rr_graph.cpp b/openfpga/src/repack/lb_rr_graph.cpp index 581edd905..3645e60e6 100644 --- a/openfpga/src/repack/lb_rr_graph.cpp +++ b/openfpga/src/repack/lb_rr_graph.cpp @@ -69,6 +69,11 @@ std::vector LbRRGraph::node_in_edges(const LbRRNodeId& node, t_mode* return in_edges; } +std::vector LbRRGraph::node_out_edges(const LbRRNodeId& node) const { + VTR_ASSERT(true == valid_node_id(node)); + return node_out_edges_[node]; +} + std::vector LbRRGraph::node_out_edges(const LbRRNodeId& node, t_mode* mode) const { std::vector out_edges; @@ -175,11 +180,15 @@ LbRRNodeId LbRRGraph::create_node(const e_lb_rr_type& type) { LbRRNodeId LbRRGraph::create_ext_source_node(const e_lb_rr_type& type) { LbRRNodeId ext_source_node = create_node(type); ext_source_node_ = ext_source_node; + + return ext_source_node; } LbRRNodeId LbRRGraph::create_ext_sink_node(const e_lb_rr_type& type) { LbRRNodeId ext_sink_node = create_node(type); ext_sink_node_ = ext_sink_node; + + return ext_sink_node; } void LbRRGraph::set_node_type(const LbRRNodeId& node, const e_lb_rr_type& type) { diff --git a/openfpga/src/repack/lb_rr_graph.h b/openfpga/src/repack/lb_rr_graph.h index a85bcd4c5..e75d866b3 100644 --- a/openfpga/src/repack/lb_rr_graph.h +++ b/openfpga/src/repack/lb_rr_graph.h @@ -202,6 +202,7 @@ class LbRRGraph { std::vector node_in_edges(const LbRRNodeId& node, t_mode* mode) const; /* Get a list of edge ids, which are outgoing edges from a node */ + std::vector node_out_edges(const LbRRNodeId& node) const; std::vector node_out_edges(const LbRRNodeId& node, t_mode* mode) const; /* General method to look up a node with type and only pb_graph_pin information */ From 289c869caff4b7e97661e35a0ad38ac837153cb2 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 18 Feb 2020 22:01:22 -0700 Subject: [PATCH 176/645] refactored expand rt_node in LbRouter --- openfpga/src/repack/lb_router.cpp | 31 +++++++++++++++++++++++++++++++ openfpga/src/repack/lb_router.h | 8 ++++++++ 2 files changed, 39 insertions(+) diff --git a/openfpga/src/repack/lb_router.cpp b/openfpga/src/repack/lb_router.cpp index 63fc0105b..16c7f8f35 100644 --- a/openfpga/src/repack/lb_router.cpp +++ b/openfpga/src/repack/lb_router.cpp @@ -123,6 +123,37 @@ bool LbRouter::add_to_rt(t_trace* rt, const LbRRNodeId& node_index, const int& i return false; } +void LbRouter::expand_rt_rec(t_trace* rt, + const LbRRNodeId& prev_index, + reservable_pq, compare_expansion_node>& pq, + const int& irt_net, + const int& explore_id_index) { + t_expansion_node enode; + + /* Perhaps should use a cost other than zero */ + enode.cost = 0; + enode.node_index = rt->current_node; + enode.prev_index = prev_index; + pq.push(enode); + explored_node_tb_[enode.node_index].inet = irt_net; + explored_node_tb_[enode.node_index].explored_id = OPEN; + explored_node_tb_[enode.node_index].enqueue_id = explore_id_index; + explored_node_tb_[enode.node_index].enqueue_cost = 0; + explored_node_tb_[enode.node_index].prev_index = prev_index; + + for (unsigned int i = 0; i < rt->next_nodes.size(); i++) { + expand_rt_rec(&rt->next_nodes[i], rt->current_node, pq, irt_net, explore_id_index); + } +} + +void LbRouter::expand_rt(const int& inet, + reservable_pq, compare_expansion_node>& pq, + const int& irt_net) { + VTR_ASSERT(pq.empty()); + + expand_rt_rec(lb_nets_[inet].rt_tree, LbRRNodeId::INVALID(), pq, irt_net, explore_id_index_); +} + void LbRouter::expand_edges(const LbRRGraph& lb_rr_graph, t_mode* mode, const LbRRNodeId& cur_inode, diff --git a/openfpga/src/repack/lb_router.h b/openfpga/src/repack/lb_router.h index 51778df91..1d07954df 100644 --- a/openfpga/src/repack/lb_router.h +++ b/openfpga/src/repack/lb_router.h @@ -202,6 +202,14 @@ class LbRouter { private : /* Private mutators */ void reset_explored_node_tb(); bool add_to_rt(t_trace* rt, const LbRRNodeId& node_index, const int& irt_net); + void expand_rt_rec(t_trace* rt, + const LbRRNodeId& prev_index, + reservable_pq, compare_expansion_node>& pq, + const int& irt_net, + const int& explore_id_index); + void expand_rt(const int& inet, + reservable_pq, compare_expansion_node>& pq, + const int& irt_net); void expand_edges(const LbRRGraph& lb_rr_graph, t_mode* mode, const LbRRNodeId& cur_inode, From 80fa6f8a0a2994bec31a791679146f2a59620543 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 18 Feb 2020 22:08:51 -0700 Subject: [PATCH 177/645] refactored skip nets in LbRouter --- openfpga/src/repack/lb_router.cpp | 53 +++++++++++++++++++++++++++---- openfpga/src/repack/lb_router.h | 6 +++- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/openfpga/src/repack/lb_router.cpp b/openfpga/src/repack/lb_router.cpp index 16c7f8f35..267d8d3f9 100644 --- a/openfpga/src/repack/lb_router.cpp +++ b/openfpga/src/repack/lb_router.cpp @@ -80,14 +80,32 @@ LbRouter::t_trace* LbRouter::find_node_in_rt(t_trace* rt, const LbRRNodeId& rt_i /************************************************** * Private mutators *************************************************/ -void LbRouter::reset_explored_node_tb() { - for (t_explored_node_stats& explored_node : explored_node_tb_) { - explored_node.prev_index = LbRRNodeId::INVALID(); - explored_node.explored_id = OPEN; - explored_node.inet = OPEN; - explored_node.enqueue_id = OPEN; - explored_node.enqueue_cost = 0; +bool LbRouter::is_skip_route_net(const LbRRGraph& lb_rr_graph, + t_trace* rt) { + /* Validate if the rr_graph is the one we used to initialize the router */ + VTR_ASSERT(true == matched_lb_rr_graph(lb_rr_graph)); + + if (rt == nullptr) { + return false; /* Net is not routed, therefore must route net */ } + + LbRRNodeId inode = rt->current_node; + + /* Determine if node is overused */ + if (routing_status_[inode].occ > lb_rr_graph.node_capacity(inode)) { + /* Conflict between this net and another net at this node, reroute net */ + return false; + } + + /* Recursively check that rest of route tree does not have a conflict */ + for (unsigned int i = 0; i < rt->next_nodes.size(); i++) { + if (!is_skip_route_net(lb_rr_graph, &rt->next_nodes[i])) { + return false; + } + } + + /* No conflict, this net's current route is legal, skip routing this net */ + return true; } bool LbRouter::add_to_rt(t_trace* rt, const LbRRNodeId& node_index, const int& irt_net) { @@ -123,6 +141,13 @@ bool LbRouter::add_to_rt(t_trace* rt, const LbRRNodeId& node_index, const int& i return false; } +void LbRouter::add_source_to_rt(const int& inet) { + /* TODO: Validate net id */ + VTR_ASSERT(nullptr == lb_nets_[inet].rt_tree); + lb_nets_[inet].rt_tree = new t_trace; + lb_nets_[inet].rt_tree->current_node = lb_nets_[inet].terminals[0]; +} + void LbRouter::expand_rt_rec(t_trace* rt, const LbRRNodeId& prev_index, reservable_pq, compare_expansion_node>& pq, @@ -276,4 +301,18 @@ bool LbRouter::matched_lb_rr_graph(const LbRRGraph& lb_rr_graph) const { && (explored_node_tb_.size() == lb_rr_graph.nodes().size()) ); } +/************************************************** + * Private Initializer and cleaner + *************************************************/ +void LbRouter::reset_explored_node_tb() { + for (t_explored_node_stats& explored_node : explored_node_tb_) { + explored_node.prev_index = LbRRNodeId::INVALID(); + explored_node.explored_id = OPEN; + explored_node.inet = OPEN; + explored_node.enqueue_id = OPEN; + explored_node.enqueue_cost = 0; + } +} + + } /* end namespace openfpga */ diff --git a/openfpga/src/repack/lb_router.h b/openfpga/src/repack/lb_router.h index 1d07954df..92aca94c1 100644 --- a/openfpga/src/repack/lb_router.h +++ b/openfpga/src/repack/lb_router.h @@ -200,8 +200,9 @@ class LbRouter { t_trace* find_node_in_rt(t_trace* rt, const LbRRNodeId& rt_index); private : /* Private mutators */ - void reset_explored_node_tb(); + bool is_skip_route_net(const LbRRGraph& lb_rr_graph, t_trace* rt); bool add_to_rt(t_trace* rt, const LbRRNodeId& node_index, const int& irt_net); + void add_source_to_rt(const int& inet); void expand_rt_rec(t_trace* rt, const LbRRNodeId& prev_index, reservable_pq, compare_expansion_node>& pq, @@ -231,6 +232,9 @@ class LbRouter { */ bool matched_lb_rr_graph(const LbRRGraph& lb_rr_graph) const; + private : /* Private initializer and cleaner */ + void reset_explored_node_tb(); + private : /* Stores all data needed by intra-logic cluster_ctx.blocks router */ /* Logical Netlist Info */ std::vector lb_nets_; /* Pointer to vector of intra logic cluster_ctx.blocks nets and their connections */ From 3d5a15d41e0087a413722c7bd2752e726640d037 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 19 Feb 2020 00:07:36 -0700 Subject: [PATCH 178/645] refactored most functions except echo and try_route() in LbRouter --- openfpga/src/repack/lb_router.cpp | 226 +++++++++++++++++++++++++++- openfpga/src/repack/lb_router.h | 36 ++++- openfpga/src/repack/lb_rr_graph.cpp | 2 +- openfpga/src/repack/lb_rr_graph.h | 4 +- 4 files changed, 263 insertions(+), 5 deletions(-) diff --git a/openfpga/src/repack/lb_router.cpp b/openfpga/src/repack/lb_router.cpp index 267d8d3f9..fece00596 100644 --- a/openfpga/src/repack/lb_router.cpp +++ b/openfpga/src/repack/lb_router.cpp @@ -6,6 +6,11 @@ #include "vtr_assert.h" #include "vtr_log.h" +#include "physical_types.h" +#include "pb_type_graph.h" +#include "vpr_error.h" + +#include "lb_rr_graph_utils.h" #include "lb_router.h" /* begin namespace openfpga */ @@ -77,9 +82,172 @@ LbRouter::t_trace* LbRouter::find_node_in_rt(t_trace* rt, const LbRRNodeId& rt_i return nullptr; } +bool LbRouter::route_has_conflict(const LbRRGraph& lb_rr_graph, t_trace* rt) const { + t_mode* cur_mode = nullptr; + for (unsigned int i = 0; i < rt->next_nodes.size(); i++) { + std::vector edges = lb_rr_graph.find_edge(rt->current_node, rt->next_nodes[i].current_node); + VTR_ASSERT(0 == edges.size()); + t_mode* new_mode = lb_rr_graph.edge_mode(edges[0]); + if (cur_mode != nullptr && cur_mode != new_mode) { + return true; + } + if (route_has_conflict(lb_rr_graph, &rt->next_nodes[i]) == true) { + return true; + } + cur_mode = new_mode; + } + + return false; +} + /************************************************** * Private mutators *************************************************/ +void LbRouter::fix_duplicate_equivalent_pins(const AtomContext& atom_ctx, + const LbRRGraph& lb_rr_graph) { + for (size_t ilb_net = 0; ilb_net < lb_nets_.size(); ++ilb_net) { + //Collect all the sink terminals indicies which target a particular node + std::map> duplicate_terminals; + for (size_t iterm = 1; iterm < lb_nets_[ilb_net].terminals.size(); ++iterm) { + LbRRNodeId node = lb_nets_[ilb_net].terminals[iterm]; + + duplicate_terminals[node].push_back(iterm); + } + + for (auto kv : duplicate_terminals) { + if (kv.second.size() < 2) continue; //Only process duplicates + + //Remap all the duplicate terminals so they target the pin instead of the sink + for (size_t idup_term = 0; idup_term < kv.second.size(); ++idup_term) { + int iterm = kv.second[idup_term]; //The index in terminals which is duplicated + + VTR_ASSERT(lb_nets_[ilb_net].atom_pins.size() == lb_nets_[ilb_net].terminals.size()); + AtomPinId atom_pin = lb_nets_[ilb_net].atom_pins[iterm]; + VTR_ASSERT(atom_pin); + + const t_pb_graph_pin* pb_graph_pin = find_pb_graph_pin(atom_ctx.nlist, atom_ctx.lookup, atom_pin); + VTR_ASSERT(pb_graph_pin); + + if (pb_graph_pin->port->equivalent == PortEquivalence::NONE) continue; //Only need to remap equivalent ports + + //Remap this terminal to an explicit pin instead of the common sink + LbRRNodeId pin_index = lb_rr_graph.find_node(LB_INTERMEDIATE, pb_graph_pin); + VTR_ASSERT(true == lb_rr_graph.valid_node_id(pin_index)); + + VTR_LOG_WARN( + "Found duplicate nets connected to logically equivalent pins. " + "Remapping intra lb net %d (atom net %zu '%s') from common sink " + "pb_route %d to fixed pin pb_route %d\n", + ilb_net, size_t(lb_nets_[ilb_net].atom_net_id), atom_ctx.nlist.net_name(lb_nets_[ilb_net].atom_net_id).c_str(), + kv.first, size_t(pin_index)); + + VTR_ASSERT(1 == lb_rr_graph.node_out_edges(pin_index, &(pb_graph_pin->parent_node->pb_type->modes[0])).size()); + LbRRNodeId sink_index = lb_rr_graph.edge_sink_node(lb_rr_graph.node_out_edges(pin_index, &(pb_graph_pin->parent_node->pb_type->modes[0]))[0]); + VTR_ASSERT(LB_SINK == lb_rr_graph.node_type(sink_index)); + VTR_ASSERT_MSG(sink_index == lb_nets_[ilb_net].terminals[iterm], "Remapped pin must be connected to original sink"); + + //Change the target + lb_nets_[ilb_net].terminals[iterm] = pin_index; + } + } + } +} + +// Check one edge for mode conflict. +bool LbRouter::check_edge_for_route_conflicts(std::unordered_map& mode_map, + const t_pb_graph_pin* driver_pin, + const t_pb_graph_pin* pin) { + if (driver_pin == nullptr) { + return false; + } + + // Only check pins that are OUT_PORTs. + if (pin == nullptr || pin->port == nullptr || pin->port->type != OUT_PORT) { + return false; + } + VTR_ASSERT(!pin->port->is_clock); + + auto* pb_graph_node = pin->parent_node; + VTR_ASSERT(pb_graph_node->pb_type == pin->port->parent_pb_type); + + const t_pb_graph_edge* edge = get_edge_between_pins(driver_pin, pin); + VTR_ASSERT(edge != nullptr); + + auto mode_of_edge = edge->interconnect->parent_mode_index; + auto* mode = &pb_graph_node->pb_type->modes[mode_of_edge]; + + auto result = mode_map.insert(std::make_pair(pb_graph_node, mode)); + if (!result.second) { + if (result.first->second != mode) { + VTR_LOG("Differing modes for block. Got %s mode, while previously was %s for interconnect %s.\n", + mode->name, result.first->second->name, + edge->interconnect->name); + // The illegal mode is added to the pb_graph_node as it resulted in a conflict during atom-to-atom routing. This mode cannot be used in the consequent cluster + // generation try. + if (std::find(pb_graph_node->illegal_modes.begin(), pb_graph_node->illegal_modes.end(), result.first->second->index) == pb_graph_node->illegal_modes.end()) { + pb_graph_node->illegal_modes.push_back(result.first->second->index); + } + + // If the number of illegal modes equals the number of available mode for a specific pb_graph_node it means that no cluster can be generated. This resuts + // in a fatal error. + if ((int)pb_graph_node->illegal_modes.size() >= pb_graph_node->pb_type->num_modes) { + VPR_FATAL_ERROR(VPR_ERROR_PACK, "There are no more available modes to be used. Routing Failed!"); + } + + return true; + } + } + + return false; +} + +void LbRouter::commit_remove_rt(const LbRRGraph& lb_rr_graph, + t_trace* rt, + const e_commit_remove& op, + std::unordered_map& mode_map, + t_mode_selection_status& mode_status) { + int incr; + + if (nullptr == rt) { + return; + } + + LbRRNodeId inode = rt->current_node; + + /* Determine if node is being used or removed */ + if (op == RT_COMMIT) { + incr = 1; + if (routing_status_[inode].occ >= lb_rr_graph.node_capacity(inode)) { + routing_status_[inode].historical_usage += (routing_status_[inode].occ - lb_rr_graph.node_capacity(inode) + 1); /* store historical overuse */ + } + } else { + incr = -1; + explored_node_tb_[inode].inet = OPEN; + } + + routing_status_[inode].occ += incr; + VTR_ASSERT(routing_status_[inode].occ >= 0); + + t_pb_graph_pin* driver_pin = lb_rr_graph.node_pb_graph_pin(inode); + + /* Recursively update route tree */ + for (unsigned int i = 0; i < rt->next_nodes.size(); i++) { + // Check to see if there is no mode conflict between previous nets. + // A conflict is present if there are differing modes between a pb_graph_node + // and its children. + if (op == RT_COMMIT && mode_status.try_expand_all_modes) { + const LbRRNodeId& node = rt->next_nodes[i].current_node; + t_pb_graph_pin* pin = lb_rr_graph.node_pb_graph_pin(node); + + if (check_edge_for_route_conflicts(mode_map, driver_pin, pin)) { + mode_status.is_mode_conflict = true; + } + } + + commit_remove_rt(lb_rr_graph, &rt->next_nodes[i], op, mode_map, mode_status); + } +} + bool LbRouter::is_skip_route_net(const LbRRGraph& lb_rr_graph, t_trace* rt) { /* Validate if the rr_graph is the one we used to initialize the router */ @@ -293,6 +461,63 @@ void LbRouter::expand_node_all_modes(const LbRRGraph& lb_rr_graph, } } +bool LbRouter::try_expand_nodes(const AtomNetlist& atom_nlist, + const LbRRGraph& lb_rr_graph, + const t_net& lb_net, + t_expansion_node& exp_node, + reservable_pq, compare_expansion_node>& pq, + const int& itarget, + const bool& try_other_modes, + const int& verbosity) { + bool is_impossible = false; + + do { + if (pq.empty()) { + /* No connection possible */ + is_impossible = true; + + if (verbosity > 3) { + //Print detailed debug info + AtomNetId net_id = lb_net.atom_net_id; + AtomPinId driver_pin = lb_net.atom_pins[0]; + AtomPinId sink_pin = lb_net.atom_pins[itarget]; + LbRRNodeId driver_rr_node = lb_net.terminals[0]; + LbRRNodeId sink_rr_node = lb_net.terminals[itarget]; + + VTR_LOG("\t\t\tNo possible routing path from %s to %s: needed for net '%s' from net pin '%s'", + describe_lb_rr_node(lb_rr_graph, driver_rr_node).c_str(), + describe_lb_rr_node(lb_rr_graph, sink_rr_node).c_str(), + atom_nlist.net_name(net_id).c_str(), + atom_nlist.pin_name(driver_pin).c_str()); + VTR_LOGV(sink_pin, " to net pin '%s'", atom_nlist.pin_name(sink_pin).c_str()); + VTR_LOG("\n"); + } + } else { + exp_node = pq.top(); + pq.pop(); + LbRRNodeId exp_inode = exp_node.node_index; + + if (explored_node_tb_[exp_inode].explored_id != explore_id_index_) { + /* First time node is popped implies path to this node is the lowest cost. + * If the node is popped a second time, then the path to that node is higher + * than this path so ignore. + */ + explored_node_tb_[exp_inode].explored_id = explore_id_index_; + explored_node_tb_[exp_inode].prev_index = exp_node.prev_index; + if (exp_inode != lb_net.terminals[itarget]) { + if (!try_other_modes) { + expand_node(lb_rr_graph, exp_node, pq, lb_net.terminals.size() - 1); + } else { + expand_node_all_modes(lb_rr_graph, exp_node, pq, lb_net.terminals.size() - 1); + } + } + } + } + } while (exp_node.node_index != lb_net.terminals[itarget] && !is_impossible); + + return is_impossible; +} + /************************************************** * Private validators *************************************************/ @@ -314,5 +539,4 @@ void LbRouter::reset_explored_node_tb() { } } - } /* end namespace openfpga */ diff --git a/openfpga/src/repack/lb_router.h b/openfpga/src/repack/lb_router.h index 92aca94c1..8a4feac9c 100644 --- a/openfpga/src/repack/lb_router.h +++ b/openfpga/src/repack/lb_router.h @@ -11,7 +11,7 @@ #include "vtr_vector.h" #include "physical_types.h" -#include "atom_netlist_fwd.h" +#include "vpr_context.h" #include "lb_rr_graph.h" @@ -176,6 +176,11 @@ class LbRouter { private: size_type cur_cap; }; + + enum e_commit_remove { + RT_COMMIT, + RT_REMOVE + }; public : /* Public constructors */ LbRouter(const LbRRGraph& lb_rr_graph); @@ -199,7 +204,27 @@ class LbRouter { */ t_trace* find_node_in_rt(t_trace* rt, const LbRRNodeId& rt_index); + bool route_has_conflict(const LbRRGraph& lb_rr_graph, t_trace* rt) const; + private : /* Private mutators */ + /*It is possible that a net may connect multiple times to a logically equivalent set of primitive pins. + *The cluster router will only route one connection for a particular net to the common sink of the + *equivalent pins. + * + *To work around this, we fix all but one of these duplicate connections to route to specific pins, + *(instead of the common sink). This ensures a legal routing is produced and that the duplicate pins + *are not 'missing' in the clustered netlist. + */ + void fix_duplicate_equivalent_pins(const AtomContext& atom_ctx, + const LbRRGraph& lb_rr_graph); + bool check_edge_for_route_conflicts(std::unordered_map& mode_map, + const t_pb_graph_pin* driver_pin, + const t_pb_graph_pin* pin); + void commit_remove_rt(const LbRRGraph& lb_rr_graph, + t_trace* rt, + const e_commit_remove& op, + std::unordered_map& mode_map, + t_mode_selection_status& mode_status); bool is_skip_route_net(const LbRRGraph& lb_rr_graph, t_trace* rt); bool add_to_rt(t_trace* rt, const LbRRNodeId& node_index, const int& irt_net); void add_source_to_rt(const int& inet); @@ -225,6 +250,15 @@ class LbRouter { const t_expansion_node& exp_node, reservable_pq, compare_expansion_node>& pq, const int& net_fanout); + bool try_expand_nodes(const AtomNetlist& atom_nlist, + const LbRRGraph& lb_rr_graph, + const t_net& lb_net, + t_expansion_node& exp_node, + reservable_pq, compare_expansion_node>& pq, + const int& itarget, + const bool& try_other_modes, + const int& verbosity); + private : /* Private validators */ /** diff --git a/openfpga/src/repack/lb_rr_graph.cpp b/openfpga/src/repack/lb_rr_graph.cpp index 3645e60e6..434ac0b3e 100644 --- a/openfpga/src/repack/lb_rr_graph.cpp +++ b/openfpga/src/repack/lb_rr_graph.cpp @@ -87,7 +87,7 @@ std::vector LbRRGraph::node_out_edges(const LbRRNodeId& node, t_mode return out_edges; } -LbRRNodeId LbRRGraph::find_node(const e_lb_rr_type& type, t_pb_graph_pin* pb_graph_pin) const { +LbRRNodeId LbRRGraph::find_node(const e_lb_rr_type& type, const t_pb_graph_pin* pb_graph_pin) const { if (size_t(type) >= node_lookup_.size()) { return LbRRNodeId::INVALID(); } diff --git a/openfpga/src/repack/lb_rr_graph.h b/openfpga/src/repack/lb_rr_graph.h index e75d866b3..4500a504b 100644 --- a/openfpga/src/repack/lb_rr_graph.h +++ b/openfpga/src/repack/lb_rr_graph.h @@ -206,7 +206,7 @@ class LbRRGraph { std::vector node_out_edges(const LbRRNodeId& node, t_mode* mode) const; /* General method to look up a node with type and only pb_graph_pin information */ - LbRRNodeId find_node(const e_lb_rr_type& type, t_pb_graph_pin* pb_graph_pin) const; + LbRRNodeId find_node(const e_lb_rr_type& type, const t_pb_graph_pin* pb_graph_pin) const; /* Method to find special node */ LbRRNodeId ext_source_node() const; LbRRNodeId ext_sink_node() const; @@ -302,7 +302,7 @@ class LbRRGraph { /* Fast look-up to search a node by its type, coordinator and ptc_num * Indexing of fast look-up: [0..NUM_TYPES-1][t_pb_graph_pin*] */ - typedef std::vector> NodeLookup; + typedef std::vector> NodeLookup; mutable NodeLookup node_lookup_; /* Special node look-up */ From 5ccb4adb0856f417d9bc87ea37a27702013bbf3e Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 19 Feb 2020 11:09:24 -0700 Subject: [PATCH 179/645] refactored LB router main function --- openfpga/src/repack/lb_router.cpp | 197 ++++++++++++++++++++++++++---- openfpga/src/repack/lb_router.h | 38 ++++-- 2 files changed, 197 insertions(+), 38 deletions(-) diff --git a/openfpga/src/repack/lb_router.cpp b/openfpga/src/repack/lb_router.cpp index fece00596..a22d1d25e 100644 --- a/openfpga/src/repack/lb_router.cpp +++ b/openfpga/src/repack/lb_router.cpp @@ -19,11 +19,13 @@ namespace openfpga { /************************************************** * Public Constructors *************************************************/ -LbRouter::LbRouter(const LbRRGraph& lb_rr_graph) { +LbRouter::LbRouter(const LbRRGraph& lb_rr_graph, t_logical_block_type_ptr lb_type) { routing_status_.resize(lb_rr_graph.nodes().size()); explored_node_tb_.resize(lb_rr_graph.nodes().size()); explore_id_index_ = 1; + lb_type_ = lb_type; + /* Default routing parameters */ params_.max_iterations = 50; params_.pres_fac = 1; @@ -100,6 +102,115 @@ bool LbRouter::route_has_conflict(const LbRRGraph& lb_rr_graph, t_trace* rt) con return false; } +/************************************************** + * Public mutators + *************************************************/ +bool LbRouter::try_route(const LbRRGraph& lb_rr_graph, + const AtomNetlist& atom_nlist, + const int& verbosity) { + /* Validate if the rr_graph is the one we used to initialize the router */ + VTR_ASSERT(true == matched_lb_rr_graph(lb_rr_graph)); + + bool is_routed = false; + bool is_impossible = false; + + mode_status_.is_mode_conflict = false; + mode_status_.try_expand_all_modes = false; + + t_expansion_node exp_node; + + reset_explored_node_tb(); + + /* Reset current routing */ + reset_net_rt(); + reset_routing_status(); + + std::unordered_map mode_map; + + /* Iteratively remove congestion until a successful route is found. + * Cap the total number of iterations tried so that if a solution does not exist, then the router won't run indefinitely */ + pres_con_fac_ = params_.pres_fac; + for (int iter = 0; iter < params_.max_iterations && !is_routed && !is_impossible; iter++) { + unsigned int inet; + /* Iterate across all nets internal to logic block */ + for (inet = 0; inet < lb_nets_.size() && !is_impossible; inet++) { + int idx = inet; + if (is_skip_route_net(lb_rr_graph, lb_nets_[idx].rt_tree)) { + continue; + } + commit_remove_rt(lb_rr_graph, lb_nets_[idx].rt_tree, RT_REMOVE, mode_map); + free_net_rt(lb_nets_[idx].rt_tree); + lb_nets_[idx].rt_tree = nullptr; + add_source_to_rt(idx); + + /* Route each sink of net */ + for (unsigned int itarget = 1; itarget < lb_nets_[idx].terminals.size() && !is_impossible; itarget++) { + pq_.clear(); + /* Get lowest cost next node, repeat until a path is found or if it is impossible to route */ + + expand_rt(idx, idx); + + is_impossible = try_expand_nodes(atom_nlist, lb_rr_graph, lb_nets_[idx], exp_node, itarget, mode_status_.expand_all_modes, verbosity); + + if (is_impossible && !mode_status_.expand_all_modes) { + mode_status_.try_expand_all_modes = true; + mode_status_.expand_all_modes = true; + break; + } + + if (exp_node.node_index == lb_nets_[idx].terminals[itarget]) { + /* Net terminal is routed, add this to the route tree, clear data structures, and keep going */ + is_impossible = add_to_rt(lb_nets_[idx].rt_tree, exp_node.node_index, idx); + } + + if (is_impossible) { + VTR_LOG("Routing was impossible!\n"); + } else if (mode_status_.expand_all_modes) { + is_impossible = route_has_conflict(lb_rr_graph, lb_nets_[idx].rt_tree); + if (is_impossible) { + VTR_LOG("Routing was impossible due to modes!\n"); + } + } + + explore_id_index_++; + if (explore_id_index_ > 2000000000) { + /* overflow protection */ + for (const LbRRNodeId& id : lb_rr_graph.nodes()) { + explored_node_tb_[id].explored_id = OPEN; + explored_node_tb_[id].enqueue_id = OPEN; + explore_id_index_ = 1; + } + } + } + + if (!is_impossible) { + commit_remove_rt(lb_rr_graph, lb_nets_[idx].rt_tree, RT_COMMIT, mode_map); + if (mode_status_.is_mode_conflict) { + is_impossible = true; + } + } + } + + if (!is_impossible) { + is_routed_ = is_route_success(lb_rr_graph); + } else { + --inet; + VTR_LOGV(verbosity < 3, "Net '%s' is impossible to route within proposed %s cluster\n", + atom_nlist.net_name(lb_nets_[inet].atom_net_id).c_str(), lb_type_->name); + is_routed_ = false; + } + pres_con_fac_ *= params_.pres_fac_mult; + } + + /* TODO: + * Let user to decide to how proceed upon the routing results: + * - route success: save the results through public accessors to lb_nets_ + * print the route results to files + * - route fail: report all the congestion nodes + */ + return is_routed_; +} + /************************************************** * Private mutators *************************************************/ @@ -204,8 +315,7 @@ bool LbRouter::check_edge_for_route_conflicts(std::unordered_map& mode_map, - t_mode_selection_status& mode_status) { + std::unordered_map& mode_map) { int incr; if (nullptr == rt) { @@ -235,16 +345,16 @@ void LbRouter::commit_remove_rt(const LbRRGraph& lb_rr_graph, // Check to see if there is no mode conflict between previous nets. // A conflict is present if there are differing modes between a pb_graph_node // and its children. - if (op == RT_COMMIT && mode_status.try_expand_all_modes) { + if (op == RT_COMMIT && mode_status_.try_expand_all_modes) { const LbRRNodeId& node = rt->next_nodes[i].current_node; t_pb_graph_pin* pin = lb_rr_graph.node_pb_graph_pin(node); if (check_edge_for_route_conflicts(mode_map, driver_pin, pin)) { - mode_status.is_mode_conflict = true; + mode_status_.is_mode_conflict = true; } } - commit_remove_rt(lb_rr_graph, &rt->next_nodes[i], op, mode_map, mode_status); + commit_remove_rt(lb_rr_graph, &rt->next_nodes[i], op, mode_map); } } @@ -318,7 +428,6 @@ void LbRouter::add_source_to_rt(const int& inet) { void LbRouter::expand_rt_rec(t_trace* rt, const LbRRNodeId& prev_index, - reservable_pq, compare_expansion_node>& pq, const int& irt_net, const int& explore_id_index) { t_expansion_node enode; @@ -327,7 +436,7 @@ void LbRouter::expand_rt_rec(t_trace* rt, enode.cost = 0; enode.node_index = rt->current_node; enode.prev_index = prev_index; - pq.push(enode); + pq_.push(enode); explored_node_tb_[enode.node_index].inet = irt_net; explored_node_tb_[enode.node_index].explored_id = OPEN; explored_node_tb_[enode.node_index].enqueue_id = explore_id_index; @@ -335,24 +444,22 @@ void LbRouter::expand_rt_rec(t_trace* rt, explored_node_tb_[enode.node_index].prev_index = prev_index; for (unsigned int i = 0; i < rt->next_nodes.size(); i++) { - expand_rt_rec(&rt->next_nodes[i], rt->current_node, pq, irt_net, explore_id_index); + expand_rt_rec(&rt->next_nodes[i], rt->current_node, irt_net, explore_id_index); } } void LbRouter::expand_rt(const int& inet, - reservable_pq, compare_expansion_node>& pq, const int& irt_net) { - VTR_ASSERT(pq.empty()); + VTR_ASSERT(pq_.empty()); - expand_rt_rec(lb_nets_[inet].rt_tree, LbRRNodeId::INVALID(), pq, irt_net, explore_id_index_); + expand_rt_rec(lb_nets_[inet].rt_tree, LbRRNodeId::INVALID(), irt_net, explore_id_index_); } void LbRouter::expand_edges(const LbRRGraph& lb_rr_graph, t_mode* mode, const LbRRNodeId& cur_inode, float cur_cost, - int net_fanout, - reservable_pq, compare_expansion_node>& pq) { + int net_fanout) { /* Validate if the rr_graph is the one we used to initialize the router */ VTR_ASSERT(true == matched_lb_rr_graph(lb_rr_graph)); @@ -394,19 +501,18 @@ void LbRouter::expand_edges(const LbRRGraph& lb_rr_graph, /* Add to queue if cost is lower than lowest cost path to this enode */ if (explored_node_tb_[enode.node_index].enqueue_id == explore_id_index_) { if (enode.cost < explored_node_tb_[enode.node_index].enqueue_cost) { - pq.push(enode); + pq_.push(enode); } } else { explored_node_tb_[enode.node_index].enqueue_id = explore_id_index_; explored_node_tb_[enode.node_index].enqueue_cost = enode.cost; - pq.push(enode); + pq_.push(enode); } } } void LbRouter::expand_node(const LbRRGraph& lb_rr_graph, const t_expansion_node& exp_node, - reservable_pq, compare_expansion_node>& pq, const int& net_fanout) { /* Validate if the rr_graph is the one we used to initialize the router */ VTR_ASSERT(true == matched_lb_rr_graph(lb_rr_graph)); @@ -420,12 +526,11 @@ void LbRouter::expand_node(const LbRRGraph& lb_rr_graph, mode = &(lb_rr_graph.node_pb_graph_pin(cur_node)->parent_node->pb_type->modes[0]); } - expand_edges(lb_rr_graph, mode, cur_node, cur_cost, net_fanout, pq); + expand_edges(lb_rr_graph, mode, cur_node, cur_cost, net_fanout); } void LbRouter::expand_node_all_modes(const LbRRGraph& lb_rr_graph, const t_expansion_node& exp_node, - reservable_pq, compare_expansion_node>& pq, const int& net_fanout) { /* Validate if the rr_graph is the one we used to initialize the router */ VTR_ASSERT(true == matched_lb_rr_graph(lb_rr_graph)); @@ -457,7 +562,7 @@ void LbRouter::expand_node_all_modes(const LbRRGraph& lb_rr_graph, if (is_illegal == true) { continue; } - expand_edges(lb_rr_graph, mode, cur_inode, cur_cost, net_fanout, pq); + expand_edges(lb_rr_graph, mode, cur_inode, cur_cost, net_fanout); } } @@ -465,14 +570,13 @@ bool LbRouter::try_expand_nodes(const AtomNetlist& atom_nlist, const LbRRGraph& lb_rr_graph, const t_net& lb_net, t_expansion_node& exp_node, - reservable_pq, compare_expansion_node>& pq, const int& itarget, const bool& try_other_modes, const int& verbosity) { bool is_impossible = false; do { - if (pq.empty()) { + if (pq_.empty()) { /* No connection possible */ is_impossible = true; @@ -493,8 +597,8 @@ bool LbRouter::try_expand_nodes(const AtomNetlist& atom_nlist, VTR_LOG("\n"); } } else { - exp_node = pq.top(); - pq.pop(); + exp_node = pq_.top(); + pq_.pop(); LbRRNodeId exp_inode = exp_node.node_index; if (explored_node_tb_[exp_inode].explored_id != explore_id_index_) { @@ -506,9 +610,9 @@ bool LbRouter::try_expand_nodes(const AtomNetlist& atom_nlist, explored_node_tb_[exp_inode].prev_index = exp_node.prev_index; if (exp_inode != lb_net.terminals[itarget]) { if (!try_other_modes) { - expand_node(lb_rr_graph, exp_node, pq, lb_net.terminals.size() - 1); + expand_node(lb_rr_graph, exp_node, lb_net.terminals.size() - 1); } else { - expand_node_all_modes(lb_rr_graph, exp_node, pq, lb_net.terminals.size() - 1); + expand_node_all_modes(lb_rr_graph, exp_node, lb_net.terminals.size() - 1); } } } @@ -539,4 +643,45 @@ void LbRouter::reset_explored_node_tb() { } } +void LbRouter::reset_net_rt() { + for (unsigned int inet = 0; inet < lb_nets_.size(); inet++) { + free_net_rt(lb_nets_[inet].rt_tree); + lb_nets_[inet].rt_tree = nullptr; + } +} + +void LbRouter::reset_routing_status() { + for (t_routing_status& status : routing_status_) { + status.historical_usage = 0; + status.occ = 0; + } +} + +void LbRouter::clear_nets() { + reset_net_rt(); + for (unsigned int i = 0; i < lb_nets_.size(); i++) { + lb_nets_[i].terminals.clear(); + } + lb_nets_.clear(); +} + +void LbRouter::free_net_rt(t_trace* lb_trace) { + if (lb_trace != nullptr) { + for (unsigned int i = 0; i < lb_trace->next_nodes.size(); i++) { + free_lb_trace(&lb_trace->next_nodes[i]); + } + lb_trace->next_nodes.clear(); + delete lb_trace; + } +} + +void LbRouter::free_lb_trace(t_trace* lb_trace) { + if (lb_trace != nullptr) { + for (unsigned int i = 0; i < lb_trace->next_nodes.size(); i++) { + free_lb_trace(&lb_trace->next_nodes[i]); + } + lb_trace->next_nodes.clear(); + } +} + } /* end namespace openfpga */ diff --git a/openfpga/src/repack/lb_router.h b/openfpga/src/repack/lb_router.h index 8a4feac9c..40d8d5d93 100644 --- a/openfpga/src/repack/lb_router.h +++ b/openfpga/src/repack/lb_router.h @@ -183,7 +183,7 @@ class LbRouter { }; public : /* Public constructors */ - LbRouter(const LbRRGraph& lb_rr_graph); + LbRouter(const LbRRGraph& lb_rr_graph, t_logical_block_type_ptr lb_type); public : /* Public accessors */ /** @@ -206,6 +206,15 @@ class LbRouter { bool route_has_conflict(const LbRRGraph& lb_rr_graph, t_trace* rt) const; + public : /* Public mutators */ + /** + * Perform routing algorithm on a given logical tile routing resource graph + * Note: the lb_rr_graph must be the same as you initilized the router!!! + */ + bool try_route(const LbRRGraph& lb_rr_graph, + const AtomNetlist& atom_nlist, + const int& verbosity); + private : /* Private mutators */ /*It is possible that a net may connect multiple times to a logically equivalent set of primitive pins. *The cluster router will only route one connection for a particular net to the common sink of the @@ -223,43 +232,35 @@ class LbRouter { void commit_remove_rt(const LbRRGraph& lb_rr_graph, t_trace* rt, const e_commit_remove& op, - std::unordered_map& mode_map, - t_mode_selection_status& mode_status); + std::unordered_map& mode_map); bool is_skip_route_net(const LbRRGraph& lb_rr_graph, t_trace* rt); bool add_to_rt(t_trace* rt, const LbRRNodeId& node_index, const int& irt_net); void add_source_to_rt(const int& inet); void expand_rt_rec(t_trace* rt, const LbRRNodeId& prev_index, - reservable_pq, compare_expansion_node>& pq, const int& irt_net, const int& explore_id_index); void expand_rt(const int& inet, - reservable_pq, compare_expansion_node>& pq, const int& irt_net); void expand_edges(const LbRRGraph& lb_rr_graph, t_mode* mode, const LbRRNodeId& cur_inode, float cur_cost, - int net_fanout, - reservable_pq, compare_expansion_node>& pq); + int net_fanout); void expand_node(const LbRRGraph& lb_rr_graph, const t_expansion_node& exp_node, - reservable_pq, compare_expansion_node>& pq, const int& net_fanout); void expand_node_all_modes(const LbRRGraph& lb_rr_graph, const t_expansion_node& exp_node, - reservable_pq, compare_expansion_node>& pq, const int& net_fanout); bool try_expand_nodes(const AtomNetlist& atom_nlist, const LbRRGraph& lb_rr_graph, const t_net& lb_net, t_expansion_node& exp_node, - reservable_pq, compare_expansion_node>& pq, const int& itarget, const bool& try_other_modes, const int& verbosity); - private : /* Private validators */ /** * Validate if the rr_graph is the one we used to initialize the router @@ -268,6 +269,12 @@ class LbRouter { private : /* Private initializer and cleaner */ void reset_explored_node_tb(); + void reset_net_rt(); + void reset_routing_status(); + + void clear_nets(); + void free_net_rt(t_trace* lb_trace); + void free_lb_trace(t_trace* lb_trace); private : /* Stores all data needed by intra-logic cluster_ctx.blocks router */ /* Logical Netlist Info */ @@ -292,7 +299,14 @@ class LbRouter { /* Parameters used by router */ t_option params_; - bool is_routed_; /* Stores whether or not the current logical-to-physical mapping has a routed solution */ + /* Stores whether or not the current logical-to-physical mapping has a routed solution */ + bool is_routed_; + + /* Stores the mode selection status when expanding the edges */ + t_mode_selection_status mode_status_; + + /* Stores state info of the priority queue in expanding edges during route */ + reservable_pq, compare_expansion_node> pq_; /* current congestion factor */ float pres_con_fac_; From 2f1bcdd27d1f7e1e67ff75233abd6f24e2fff4e4 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 19 Feb 2020 14:53:35 -0700 Subject: [PATCH 180/645] use local data to store illegal modes for pb_graph_node inside LbRouter --- openfpga/src/repack/lb_router.cpp | 39 +++++++++++++++++++++++-------- openfpga/src/repack/lb_router.h | 11 +++++++-- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/openfpga/src/repack/lb_router.cpp b/openfpga/src/repack/lb_router.cpp index a22d1d25e..753ba2e32 100644 --- a/openfpga/src/repack/lb_router.cpp +++ b/openfpga/src/repack/lb_router.cpp @@ -31,6 +31,8 @@ LbRouter::LbRouter(const LbRRGraph& lb_rr_graph, t_logical_block_type_ptr lb_typ params_.pres_fac = 1; params_.pres_fac_mult = 2; params_.hist_fac = 0.3; + + is_routed_ = false; pres_con_fac_ = 1; } @@ -53,6 +55,13 @@ std::vector LbRouter::find_congested_rr_nodes(const LbRRGraph& lb_rr return congested_rr_nodes; } +bool LbRouter::is_routed() const { + return is_routed_; +} + +/************************************************** + * Private accessors + *************************************************/ bool LbRouter::is_route_success(const LbRRGraph& lb_rr_graph) const { /* Validate if the rr_graph is the one we used to initialize the router */ VTR_ASSERT(true == matched_lb_rr_graph(lb_rr_graph)); @@ -66,9 +75,6 @@ bool LbRouter::is_route_success(const LbRRGraph& lb_rr_graph) const { return true; } -/************************************************** - * Private accessors - *************************************************/ LbRouter::t_trace* LbRouter::find_node_in_rt(t_trace* rt, const LbRRNodeId& rt_index) { t_trace* cur; if (rt->current_node == rt_index) { @@ -111,7 +117,7 @@ bool LbRouter::try_route(const LbRRGraph& lb_rr_graph, /* Validate if the rr_graph is the one we used to initialize the router */ VTR_ASSERT(true == matched_lb_rr_graph(lb_rr_graph)); - bool is_routed = false; + is_routed_ = false; bool is_impossible = false; mode_status_.is_mode_conflict = false; @@ -130,7 +136,7 @@ bool LbRouter::try_route(const LbRRGraph& lb_rr_graph, /* Iteratively remove congestion until a successful route is found. * Cap the total number of iterations tried so that if a solution does not exist, then the router won't run indefinitely */ pres_con_fac_ = params_.pres_fac; - for (int iter = 0; iter < params_.max_iterations && !is_routed && !is_impossible; iter++) { + for (int iter = 0; iter < params_.max_iterations && !is_routed_ && !is_impossible; iter++) { unsigned int inet; /* Iterate across all nets internal to logic block */ for (inet = 0; inet < lb_nets_.size() && !is_impossible; inet++) { @@ -295,13 +301,18 @@ bool LbRouter::check_edge_for_route_conflicts(std::unordered_mapinterconnect->name); // The illegal mode is added to the pb_graph_node as it resulted in a conflict during atom-to-atom routing. This mode cannot be used in the consequent cluster // generation try. - if (std::find(pb_graph_node->illegal_modes.begin(), pb_graph_node->illegal_modes.end(), result.first->second->index) == pb_graph_node->illegal_modes.end()) { - pb_graph_node->illegal_modes.push_back(result.first->second->index); + auto it = illegal_modes_.find(pb_graph_node); + if (it == illegal_modes_.end()) { + illegal_modes_[pb_graph_node].push_back(result.first->second); + } else { + if (std::find(illegal_modes_.at(pb_graph_node).begin(), illegal_modes_.at(pb_graph_node).end(), result.first->second) == illegal_modes_.at(pb_graph_node).end()) { + it->second.push_back(result.first->second); + } } // If the number of illegal modes equals the number of available mode for a specific pb_graph_node it means that no cluster can be generated. This resuts // in a fatal error. - if ((int)pb_graph_node->illegal_modes.size() >= pb_graph_node->pb_type->num_modes) { + if ((int)illegal_modes_.at(pb_graph_node).size() >= pb_graph_node->pb_type->num_modes) { VPR_FATAL_ERROR(VPR_ERROR_PACK, "There are no more available modes to be used. Routing Failed!"); } @@ -551,8 +562,11 @@ void LbRouter::expand_node_all_modes(const LbRRGraph& lb_rr_graph, bool is_illegal = false; if (pin != nullptr) { auto* pb_graph_node = pin->parent_node; - for (auto illegal_mode : pb_graph_node->illegal_modes) { - if (mode->index == illegal_mode) { + if (0 == illegal_modes_.count(pb_graph_node)) { + continue; + } + for (auto illegal_mode : illegal_modes_.at(pb_graph_node)) { + if (mode == illegal_mode) { is_illegal = true; break; } @@ -684,4 +698,9 @@ void LbRouter::free_lb_trace(t_trace* lb_trace) { } } +void LbRouter::reset_illegal_modes() { + illegal_modes_.clear(); +} + + } /* end namespace openfpga */ diff --git a/openfpga/src/repack/lb_router.h b/openfpga/src/repack/lb_router.h index 40d8d5d93..9d7e76c02 100644 --- a/openfpga/src/repack/lb_router.h +++ b/openfpga/src/repack/lb_router.h @@ -192,12 +192,15 @@ class LbRouter { */ std::vector find_congested_rr_nodes(const LbRRGraph& lb_rr_graph) const; + /* Show if a valid routing solution has been founded or not */ + bool is_routed() const; + + private : /* Private accessors */ /** * Report if the routing is successfully done on a logical block routing resource graph */ bool is_route_success(const LbRRGraph& lb_rr_graph) const; - private : /* Private accessors */ /** * Try to find a node in the routing traces recursively * If not found, will return an empty pointer @@ -271,6 +274,7 @@ class LbRouter { void reset_explored_node_tb(); void reset_net_rt(); void reset_routing_status(); + void reset_illegal_modes(); void clear_nets(); void free_net_rt(t_trace* lb_trace); @@ -300,7 +304,7 @@ class LbRouter { t_option params_; /* Stores whether or not the current logical-to-physical mapping has a routed solution */ - bool is_routed_; + bool is_routed_; /* Stores the mode selection status when expanding the edges */ t_mode_selection_status mode_status_; @@ -308,6 +312,9 @@ class LbRouter { /* Stores state info of the priority queue in expanding edges during route */ reservable_pq, compare_expansion_node> pq_; + /* Store the illegal modes for each pb_graph_node that is involved in the routing resource graph */ + std::map> illegal_modes_; + /* current congestion factor */ float pres_con_fac_; }; From 2b37fcb296bb5ed057ec44550bc0609106c6159e Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 19 Feb 2020 15:09:25 -0700 Subject: [PATCH 181/645] use strong id for nets to be routed in LbRouter --- openfpga/src/repack/lb_router.cpp | 38 +++++++++++++++---------------- openfpga/src/repack/lb_router.h | 25 ++++++++++---------- 2 files changed, 31 insertions(+), 32 deletions(-) diff --git a/openfpga/src/repack/lb_router.cpp b/openfpga/src/repack/lb_router.cpp index 753ba2e32..0a13b9fa6 100644 --- a/openfpga/src/repack/lb_router.cpp +++ b/openfpga/src/repack/lb_router.cpp @@ -140,7 +140,7 @@ bool LbRouter::try_route(const LbRRGraph& lb_rr_graph, unsigned int inet; /* Iterate across all nets internal to logic block */ for (inet = 0; inet < lb_nets_.size() && !is_impossible; inet++) { - int idx = inet; + NetId idx = NetId(inet); if (is_skip_route_net(lb_rr_graph, lb_nets_[idx].rt_tree)) { continue; } @@ -202,7 +202,7 @@ bool LbRouter::try_route(const LbRRGraph& lb_rr_graph, } else { --inet; VTR_LOGV(verbosity < 3, "Net '%s' is impossible to route within proposed %s cluster\n", - atom_nlist.net_name(lb_nets_[inet].atom_net_id).c_str(), lb_type_->name); + atom_nlist.net_name(lb_nets_[NetId(inet)].atom_net_id).c_str(), lb_type_->name); is_routed_ = false; } pres_con_fac_ *= params_.pres_fac_mult; @@ -225,8 +225,8 @@ void LbRouter::fix_duplicate_equivalent_pins(const AtomContext& atom_ctx, for (size_t ilb_net = 0; ilb_net < lb_nets_.size(); ++ilb_net) { //Collect all the sink terminals indicies which target a particular node std::map> duplicate_terminals; - for (size_t iterm = 1; iterm < lb_nets_[ilb_net].terminals.size(); ++iterm) { - LbRRNodeId node = lb_nets_[ilb_net].terminals[iterm]; + for (size_t iterm = 1; iterm < lb_nets_[NetId(ilb_net)].terminals.size(); ++iterm) { + LbRRNodeId node = lb_nets_[NetId(ilb_net)].terminals[iterm]; duplicate_terminals[node].push_back(iterm); } @@ -238,8 +238,8 @@ void LbRouter::fix_duplicate_equivalent_pins(const AtomContext& atom_ctx, for (size_t idup_term = 0; idup_term < kv.second.size(); ++idup_term) { int iterm = kv.second[idup_term]; //The index in terminals which is duplicated - VTR_ASSERT(lb_nets_[ilb_net].atom_pins.size() == lb_nets_[ilb_net].terminals.size()); - AtomPinId atom_pin = lb_nets_[ilb_net].atom_pins[iterm]; + VTR_ASSERT(lb_nets_[NetId(ilb_net)].atom_pins.size() == lb_nets_[NetId(ilb_net)].terminals.size()); + AtomPinId atom_pin = lb_nets_[NetId(ilb_net)].atom_pins[iterm]; VTR_ASSERT(atom_pin); const t_pb_graph_pin* pb_graph_pin = find_pb_graph_pin(atom_ctx.nlist, atom_ctx.lookup, atom_pin); @@ -255,16 +255,16 @@ void LbRouter::fix_duplicate_equivalent_pins(const AtomContext& atom_ctx, "Found duplicate nets connected to logically equivalent pins. " "Remapping intra lb net %d (atom net %zu '%s') from common sink " "pb_route %d to fixed pin pb_route %d\n", - ilb_net, size_t(lb_nets_[ilb_net].atom_net_id), atom_ctx.nlist.net_name(lb_nets_[ilb_net].atom_net_id).c_str(), + ilb_net, size_t(lb_nets_[NetId(ilb_net)].atom_net_id), atom_ctx.nlist.net_name(lb_nets_[NetId(ilb_net)].atom_net_id).c_str(), kv.first, size_t(pin_index)); VTR_ASSERT(1 == lb_rr_graph.node_out_edges(pin_index, &(pb_graph_pin->parent_node->pb_type->modes[0])).size()); LbRRNodeId sink_index = lb_rr_graph.edge_sink_node(lb_rr_graph.node_out_edges(pin_index, &(pb_graph_pin->parent_node->pb_type->modes[0]))[0]); VTR_ASSERT(LB_SINK == lb_rr_graph.node_type(sink_index)); - VTR_ASSERT_MSG(sink_index == lb_nets_[ilb_net].terminals[iterm], "Remapped pin must be connected to original sink"); + VTR_ASSERT_MSG(sink_index == lb_nets_[NetId(ilb_net)].terminals[iterm], "Remapped pin must be connected to original sink"); //Change the target - lb_nets_[ilb_net].terminals[iterm] = pin_index; + lb_nets_[NetId(ilb_net)].terminals[iterm] = pin_index; } } } @@ -343,7 +343,7 @@ void LbRouter::commit_remove_rt(const LbRRGraph& lb_rr_graph, } } else { incr = -1; - explored_node_tb_[inode].inet = OPEN; + explored_node_tb_[inode].inet = NetId::INVALID(); } routing_status_[inode].occ += incr; @@ -397,7 +397,7 @@ bool LbRouter::is_skip_route_net(const LbRRGraph& lb_rr_graph, return true; } -bool LbRouter::add_to_rt(t_trace* rt, const LbRRNodeId& node_index, const int& irt_net) { +bool LbRouter::add_to_rt(t_trace* rt, const LbRRNodeId& node_index, const NetId& irt_net) { std::vector trace_forward; t_trace* link_node; t_trace curr_node; @@ -430,7 +430,7 @@ bool LbRouter::add_to_rt(t_trace* rt, const LbRRNodeId& node_index, const int& i return false; } -void LbRouter::add_source_to_rt(const int& inet) { +void LbRouter::add_source_to_rt(const NetId& inet) { /* TODO: Validate net id */ VTR_ASSERT(nullptr == lb_nets_[inet].rt_tree); lb_nets_[inet].rt_tree = new t_trace; @@ -439,7 +439,7 @@ void LbRouter::add_source_to_rt(const int& inet) { void LbRouter::expand_rt_rec(t_trace* rt, const LbRRNodeId& prev_index, - const int& irt_net, + const NetId& irt_net, const int& explore_id_index) { t_expansion_node enode; @@ -459,8 +459,8 @@ void LbRouter::expand_rt_rec(t_trace* rt, } } -void LbRouter::expand_rt(const int& inet, - const int& irt_net) { +void LbRouter::expand_rt(const NetId& inet, + const NetId& irt_net) { VTR_ASSERT(pq_.empty()); expand_rt_rec(lb_nets_[inet].rt_tree, LbRRNodeId::INVALID(), irt_net, explore_id_index_); @@ -651,7 +651,7 @@ void LbRouter::reset_explored_node_tb() { for (t_explored_node_stats& explored_node : explored_node_tb_) { explored_node.prev_index = LbRRNodeId::INVALID(); explored_node.explored_id = OPEN; - explored_node.inet = OPEN; + explored_node.inet = NetId::INVALID(); explored_node.enqueue_id = OPEN; explored_node.enqueue_cost = 0; } @@ -659,8 +659,8 @@ void LbRouter::reset_explored_node_tb() { void LbRouter::reset_net_rt() { for (unsigned int inet = 0; inet < lb_nets_.size(); inet++) { - free_net_rt(lb_nets_[inet].rt_tree); - lb_nets_[inet].rt_tree = nullptr; + free_net_rt(lb_nets_[NetId(inet)].rt_tree); + lb_nets_[NetId(inet)].rt_tree = nullptr; } } @@ -674,7 +674,7 @@ void LbRouter::reset_routing_status() { void LbRouter::clear_nets() { reset_net_rt(); for (unsigned int i = 0; i < lb_nets_.size(); i++) { - lb_nets_[i].terminals.clear(); + lb_nets_[NetId(i)].terminals.clear(); } lb_nets_.clear(); } diff --git a/openfpga/src/repack/lb_router.h b/openfpga/src/repack/lb_router.h index 9d7e76c02..44bd7b1b8 100644 --- a/openfpga/src/repack/lb_router.h +++ b/openfpga/src/repack/lb_router.h @@ -9,6 +9,7 @@ #include #include "vtr_vector.h" +#include "vtr_strong_id.h" #include "physical_types.h" #include "vpr_context.h" @@ -23,6 +24,9 @@ namespace openfpga { class LbRouter { + public: /* Strong ids */ + struct net_id_tag; + typedef vtr::StrongId NetId; public: /* Intra-Logic Block Routing Data Structures (by instance) */ /************************************************************************** * A routing traceback data structure, provides a logic cluster_ctx.blocks @@ -123,7 +127,7 @@ class LbRouter { struct t_explored_node_stats { LbRRNodeId prev_index; /* Prevous node that drives this one */ int explored_id; /* ID used to determine if this node has been explored */ - int inet; /* net index of route tree */ + NetId inet; /* net index of route tree */ int enqueue_id; /* ID used ot determine if this node has been pushed on exploration priority queue */ float enqueue_cost; /* cost of node pused on exploration priority queue */ @@ -131,7 +135,7 @@ class LbRouter { prev_index = LbRRNodeId::INVALID(); explored_id = OPEN; enqueue_id = OPEN; - inet = OPEN; + inet = NetId::INVALID(); enqueue_cost = 0; } }; @@ -237,14 +241,14 @@ class LbRouter { const e_commit_remove& op, std::unordered_map& mode_map); bool is_skip_route_net(const LbRRGraph& lb_rr_graph, t_trace* rt); - bool add_to_rt(t_trace* rt, const LbRRNodeId& node_index, const int& irt_net); - void add_source_to_rt(const int& inet); + bool add_to_rt(t_trace* rt, const LbRRNodeId& node_index, const NetId& irt_net); + void add_source_to_rt(const NetId& inet); void expand_rt_rec(t_trace* rt, const LbRRNodeId& prev_index, - const int& irt_net, + const NetId& irt_net, const int& explore_id_index); - void expand_rt(const int& inet, - const int& irt_net); + void expand_rt(const NetId& inet, + const NetId& irt_net); void expand_edges(const LbRRGraph& lb_rr_graph, t_mode* mode, const LbRRNodeId& cur_inode, @@ -282,12 +286,7 @@ class LbRouter { private : /* Stores all data needed by intra-logic cluster_ctx.blocks router */ /* Logical Netlist Info */ - std::vector lb_nets_; /* Pointer to vector of intra logic cluster_ctx.blocks nets and their connections */ - - /* Saved nets */ - std::vector saved_lb_nets_; /* Save vector of intra logic cluster_ctx.blocks nets and their connections */ - - std::map atoms_added_; /* map that records which atoms are added to cluster router */ + vtr::vector lb_nets_; /* Pointer to vector of intra logic cluster_ctx.blocks nets and their connections */ /* Logical-to-physical mapping info */ vtr::vector routing_status_; /* [0..lb_type_graph->size()-1] Stats for each logic cluster_ctx.blocks rr node instance */ From 444b994285fbeb8b2d6f4a0616b6e2c13a63d5f1 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 19 Feb 2020 15:37:22 -0700 Subject: [PATCH 182/645] flatten the t_net inside LbRouter into internal data --- openfpga/src/repack/lb_router.cpp | 86 ++++++++++++++++--------------- openfpga/src/repack/lb_router.h | 60 +++++++-------------- 2 files changed, 65 insertions(+), 81 deletions(-) diff --git a/openfpga/src/repack/lb_router.cpp b/openfpga/src/repack/lb_router.cpp index 0a13b9fa6..610501dde 100644 --- a/openfpga/src/repack/lb_router.cpp +++ b/openfpga/src/repack/lb_router.cpp @@ -139,24 +139,24 @@ bool LbRouter::try_route(const LbRRGraph& lb_rr_graph, for (int iter = 0; iter < params_.max_iterations && !is_routed_ && !is_impossible; iter++) { unsigned int inet; /* Iterate across all nets internal to logic block */ - for (inet = 0; inet < lb_nets_.size() && !is_impossible; inet++) { + for (inet = 0; inet < lb_net_ids_.size() && !is_impossible; inet++) { NetId idx = NetId(inet); - if (is_skip_route_net(lb_rr_graph, lb_nets_[idx].rt_tree)) { + if (is_skip_route_net(lb_rr_graph, lb_net_rt_trees_[idx])) { continue; } - commit_remove_rt(lb_rr_graph, lb_nets_[idx].rt_tree, RT_REMOVE, mode_map); - free_net_rt(lb_nets_[idx].rt_tree); - lb_nets_[idx].rt_tree = nullptr; + commit_remove_rt(lb_rr_graph, lb_net_rt_trees_[idx], RT_REMOVE, mode_map); + free_net_rt(lb_net_rt_trees_[idx]); + lb_net_rt_trees_[idx] = nullptr; add_source_to_rt(idx); /* Route each sink of net */ - for (unsigned int itarget = 1; itarget < lb_nets_[idx].terminals.size() && !is_impossible; itarget++) { + for (unsigned int itarget = 1; itarget < lb_net_terminals_[idx].size() && !is_impossible; itarget++) { pq_.clear(); /* Get lowest cost next node, repeat until a path is found or if it is impossible to route */ expand_rt(idx, idx); - is_impossible = try_expand_nodes(atom_nlist, lb_rr_graph, lb_nets_[idx], exp_node, itarget, mode_status_.expand_all_modes, verbosity); + is_impossible = try_expand_nodes(atom_nlist, lb_rr_graph, idx, exp_node, itarget, mode_status_.expand_all_modes, verbosity); if (is_impossible && !mode_status_.expand_all_modes) { mode_status_.try_expand_all_modes = true; @@ -164,15 +164,15 @@ bool LbRouter::try_route(const LbRRGraph& lb_rr_graph, break; } - if (exp_node.node_index == lb_nets_[idx].terminals[itarget]) { + if (exp_node.node_index == lb_net_terminals_[idx][itarget]) { /* Net terminal is routed, add this to the route tree, clear data structures, and keep going */ - is_impossible = add_to_rt(lb_nets_[idx].rt_tree, exp_node.node_index, idx); + is_impossible = add_to_rt(lb_net_rt_trees_[idx], exp_node.node_index, idx); } if (is_impossible) { VTR_LOG("Routing was impossible!\n"); } else if (mode_status_.expand_all_modes) { - is_impossible = route_has_conflict(lb_rr_graph, lb_nets_[idx].rt_tree); + is_impossible = route_has_conflict(lb_rr_graph, lb_net_rt_trees_[idx]); if (is_impossible) { VTR_LOG("Routing was impossible due to modes!\n"); } @@ -190,7 +190,7 @@ bool LbRouter::try_route(const LbRRGraph& lb_rr_graph, } if (!is_impossible) { - commit_remove_rt(lb_rr_graph, lb_nets_[idx].rt_tree, RT_COMMIT, mode_map); + commit_remove_rt(lb_rr_graph, lb_net_rt_trees_[idx], RT_COMMIT, mode_map); if (mode_status_.is_mode_conflict) { is_impossible = true; } @@ -202,7 +202,7 @@ bool LbRouter::try_route(const LbRRGraph& lb_rr_graph, } else { --inet; VTR_LOGV(verbosity < 3, "Net '%s' is impossible to route within proposed %s cluster\n", - atom_nlist.net_name(lb_nets_[NetId(inet)].atom_net_id).c_str(), lb_type_->name); + atom_nlist.net_name(lb_net_atom_net_ids_[NetId(inet)]).c_str(), lb_type_->name); is_routed_ = false; } pres_con_fac_ *= params_.pres_fac_mult; @@ -222,11 +222,11 @@ bool LbRouter::try_route(const LbRRGraph& lb_rr_graph, *************************************************/ void LbRouter::fix_duplicate_equivalent_pins(const AtomContext& atom_ctx, const LbRRGraph& lb_rr_graph) { - for (size_t ilb_net = 0; ilb_net < lb_nets_.size(); ++ilb_net) { + for (const NetId& ilb_net : lb_net_ids_) { //Collect all the sink terminals indicies which target a particular node std::map> duplicate_terminals; - for (size_t iterm = 1; iterm < lb_nets_[NetId(ilb_net)].terminals.size(); ++iterm) { - LbRRNodeId node = lb_nets_[NetId(ilb_net)].terminals[iterm]; + for (size_t iterm = 1; iterm < lb_net_terminals_[ilb_net].size(); ++iterm) { + LbRRNodeId node = lb_net_terminals_[ilb_net][iterm]; duplicate_terminals[node].push_back(iterm); } @@ -238,8 +238,8 @@ void LbRouter::fix_duplicate_equivalent_pins(const AtomContext& atom_ctx, for (size_t idup_term = 0; idup_term < kv.second.size(); ++idup_term) { int iterm = kv.second[idup_term]; //The index in terminals which is duplicated - VTR_ASSERT(lb_nets_[NetId(ilb_net)].atom_pins.size() == lb_nets_[NetId(ilb_net)].terminals.size()); - AtomPinId atom_pin = lb_nets_[NetId(ilb_net)].atom_pins[iterm]; + VTR_ASSERT(lb_net_atom_pins_[ilb_net].size() == lb_net_terminals_[ilb_net].size()); + AtomPinId atom_pin = lb_net_atom_pins_[ilb_net][iterm]; VTR_ASSERT(atom_pin); const t_pb_graph_pin* pb_graph_pin = find_pb_graph_pin(atom_ctx.nlist, atom_ctx.lookup, atom_pin); @@ -255,16 +255,16 @@ void LbRouter::fix_duplicate_equivalent_pins(const AtomContext& atom_ctx, "Found duplicate nets connected to logically equivalent pins. " "Remapping intra lb net %d (atom net %zu '%s') from common sink " "pb_route %d to fixed pin pb_route %d\n", - ilb_net, size_t(lb_nets_[NetId(ilb_net)].atom_net_id), atom_ctx.nlist.net_name(lb_nets_[NetId(ilb_net)].atom_net_id).c_str(), + size_t(ilb_net), size_t(lb_net_atom_net_ids_[ilb_net]), atom_ctx.nlist.net_name(lb_net_atom_net_ids_[ilb_net]).c_str(), kv.first, size_t(pin_index)); VTR_ASSERT(1 == lb_rr_graph.node_out_edges(pin_index, &(pb_graph_pin->parent_node->pb_type->modes[0])).size()); LbRRNodeId sink_index = lb_rr_graph.edge_sink_node(lb_rr_graph.node_out_edges(pin_index, &(pb_graph_pin->parent_node->pb_type->modes[0]))[0]); VTR_ASSERT(LB_SINK == lb_rr_graph.node_type(sink_index)); - VTR_ASSERT_MSG(sink_index == lb_nets_[NetId(ilb_net)].terminals[iterm], "Remapped pin must be connected to original sink"); + VTR_ASSERT_MSG(sink_index == lb_net_terminals_[ilb_net][iterm], "Remapped pin must be connected to original sink"); //Change the target - lb_nets_[NetId(ilb_net)].terminals[iterm] = pin_index; + lb_net_terminals_[ilb_net][iterm] = pin_index; } } } @@ -432,9 +432,9 @@ bool LbRouter::add_to_rt(t_trace* rt, const LbRRNodeId& node_index, const NetId& void LbRouter::add_source_to_rt(const NetId& inet) { /* TODO: Validate net id */ - VTR_ASSERT(nullptr == lb_nets_[inet].rt_tree); - lb_nets_[inet].rt_tree = new t_trace; - lb_nets_[inet].rt_tree->current_node = lb_nets_[inet].terminals[0]; + VTR_ASSERT(nullptr == lb_net_rt_trees_[inet]); + lb_net_rt_trees_[inet] = new t_trace; + lb_net_rt_trees_[inet]->current_node = lb_net_terminals_[inet][0]; } void LbRouter::expand_rt_rec(t_trace* rt, @@ -463,7 +463,7 @@ void LbRouter::expand_rt(const NetId& inet, const NetId& irt_net) { VTR_ASSERT(pq_.empty()); - expand_rt_rec(lb_nets_[inet].rt_tree, LbRRNodeId::INVALID(), irt_net, explore_id_index_); + expand_rt_rec(lb_net_rt_trees_[inet], LbRRNodeId::INVALID(), irt_net, explore_id_index_); } void LbRouter::expand_edges(const LbRRGraph& lb_rr_graph, @@ -582,7 +582,7 @@ void LbRouter::expand_node_all_modes(const LbRRGraph& lb_rr_graph, bool LbRouter::try_expand_nodes(const AtomNetlist& atom_nlist, const LbRRGraph& lb_rr_graph, - const t_net& lb_net, + const NetId& lb_net, t_expansion_node& exp_node, const int& itarget, const bool& try_other_modes, @@ -596,11 +596,11 @@ bool LbRouter::try_expand_nodes(const AtomNetlist& atom_nlist, if (verbosity > 3) { //Print detailed debug info - AtomNetId net_id = lb_net.atom_net_id; - AtomPinId driver_pin = lb_net.atom_pins[0]; - AtomPinId sink_pin = lb_net.atom_pins[itarget]; - LbRRNodeId driver_rr_node = lb_net.terminals[0]; - LbRRNodeId sink_rr_node = lb_net.terminals[itarget]; + AtomNetId net_id = lb_net_atom_net_ids_[lb_net]; + AtomPinId driver_pin = lb_net_atom_pins_[lb_net][0]; + AtomPinId sink_pin = lb_net_atom_pins_[lb_net][itarget]; + LbRRNodeId driver_rr_node = lb_net_terminals_[lb_net][0]; + LbRRNodeId sink_rr_node = lb_net_terminals_[lb_net][itarget]; VTR_LOG("\t\t\tNo possible routing path from %s to %s: needed for net '%s' from net pin '%s'", describe_lb_rr_node(lb_rr_graph, driver_rr_node).c_str(), @@ -622,16 +622,16 @@ bool LbRouter::try_expand_nodes(const AtomNetlist& atom_nlist, */ explored_node_tb_[exp_inode].explored_id = explore_id_index_; explored_node_tb_[exp_inode].prev_index = exp_node.prev_index; - if (exp_inode != lb_net.terminals[itarget]) { + if (exp_inode != lb_net_terminals_[lb_net][itarget]) { if (!try_other_modes) { - expand_node(lb_rr_graph, exp_node, lb_net.terminals.size() - 1); + expand_node(lb_rr_graph, exp_node, lb_net_terminals_[lb_net].size() - 1); } else { - expand_node_all_modes(lb_rr_graph, exp_node, lb_net.terminals.size() - 1); + expand_node_all_modes(lb_rr_graph, exp_node, lb_net_terminals_[lb_net].size() - 1); } } } } - } while (exp_node.node_index != lb_net.terminals[itarget] && !is_impossible); + } while (exp_node.node_index != lb_net_terminals_[lb_net][itarget] && !is_impossible); return is_impossible; } @@ -658,9 +658,9 @@ void LbRouter::reset_explored_node_tb() { } void LbRouter::reset_net_rt() { - for (unsigned int inet = 0; inet < lb_nets_.size(); inet++) { - free_net_rt(lb_nets_[NetId(inet)].rt_tree); - lb_nets_[NetId(inet)].rt_tree = nullptr; + for (const NetId& inet : lb_net_ids_) { + free_net_rt(lb_net_rt_trees_[inet]); + lb_net_rt_trees_[inet] = nullptr; } } @@ -672,11 +672,15 @@ void LbRouter::reset_routing_status() { } void LbRouter::clear_nets() { + /* TODO: Trace should no longer use pointers */ reset_net_rt(); - for (unsigned int i = 0; i < lb_nets_.size(); i++) { - lb_nets_[NetId(i)].terminals.clear(); - } - lb_nets_.clear(); + + lb_net_ids_.clear(); + lb_net_atom_net_ids_.clear(); + lb_net_atom_pins_.clear(); + lb_net_terminals_.clear(); + lb_net_fixed_terminals_.clear(); + lb_net_rt_trees_.clear(); } void LbRouter::free_net_rt(t_trace* lb_trace) { diff --git a/openfpga/src/repack/lb_router.h b/openfpga/src/repack/lb_router.h index 44bd7b1b8..a922d1fb4 100644 --- a/openfpga/src/repack/lb_router.h +++ b/openfpga/src/repack/lb_router.h @@ -28,18 +28,6 @@ class LbRouter { struct net_id_tag; typedef vtr::StrongId NetId; public: /* Intra-Logic Block Routing Data Structures (by instance) */ - /************************************************************************** - * A routing traceback data structure, provides a logic cluster_ctx.blocks - * instance specific trace lookup directly from the t_lb_type_rr_node array index - * After packing, routing info for each CLB will have an array of t_lb_traceback - * to store routing info within the CLB - ***************************************************************************/ - struct t_traceback { - int net; /* net of flat, technology-mapped, netlist using this node */ - LbRRNodeId prev_lb_rr_node; /* index of previous node that drives current node */ - LbRREdgeId prev_edge; /* index of previous edge that drives current node */ - }; - /************************************************************************** * Describes the status of a logic cluster_ctx.blocks routing resource node * for a given logic cluster_ctx.blocks instance @@ -68,23 +56,6 @@ class LbRouter { std::vector next_nodes; /* index of previous edge that drives current node */ }; - /************************************************************************** - * Represents a net used inside a logic cluster_ctx.blocks and the - * physical nodes used by the net - ***************************************************************************/ - struct t_net { - AtomNetId atom_net_id; /* index of atom net this intra_lb_net represents */ - std::vector terminals; /* endpoints of the intra_lb_net, 0th position is the source, all others are sinks */ - std::vector atom_pins; /* AtomPin's associated with each terminal */ - std::vector fixed_terminals; /* Marks a terminal as having a fixed target (i.e. a pin not a sink) */ - t_trace* rt_tree; /* Route tree head */ - - t_net() { - atom_net_id = AtomNetId::INVALID(); - rt_tree = nullptr; - } - }; - /************************************************************************** * Stores tuning parameters used by intra-logic cluster_ctx.blocks router ***************************************************************************/ @@ -199,6 +170,19 @@ class LbRouter { /* Show if a valid routing solution has been founded or not */ bool is_routed() const; + public : /* Public mutators */ + /** + * Add net to be routed + */ + + /** + * Perform routing algorithm on a given logical tile routing resource graph + * Note: the lb_rr_graph must be the same as you initilized the router!!! + */ + bool try_route(const LbRRGraph& lb_rr_graph, + const AtomNetlist& atom_nlist, + const int& verbosity); + private : /* Private accessors */ /** * Report if the routing is successfully done on a logical block routing resource graph @@ -213,15 +197,6 @@ class LbRouter { bool route_has_conflict(const LbRRGraph& lb_rr_graph, t_trace* rt) const; - public : /* Public mutators */ - /** - * Perform routing algorithm on a given logical tile routing resource graph - * Note: the lb_rr_graph must be the same as you initilized the router!!! - */ - bool try_route(const LbRRGraph& lb_rr_graph, - const AtomNetlist& atom_nlist, - const int& verbosity); - private : /* Private mutators */ /*It is possible that a net may connect multiple times to a logically equivalent set of primitive pins. *The cluster router will only route one connection for a particular net to the common sink of the @@ -262,7 +237,7 @@ class LbRouter { const int& net_fanout); bool try_expand_nodes(const AtomNetlist& atom_nlist, const LbRRGraph& lb_rr_graph, - const t_net& lb_net, + const NetId& lb_net, t_expansion_node& exp_node, const int& itarget, const bool& try_other_modes, @@ -286,7 +261,12 @@ class LbRouter { private : /* Stores all data needed by intra-logic cluster_ctx.blocks router */ /* Logical Netlist Info */ - vtr::vector lb_nets_; /* Pointer to vector of intra logic cluster_ctx.blocks nets and their connections */ + vtr::vector lb_net_ids_; /* Pointer to vector of intra logic cluster_ctx.blocks nets and their connections */ + vtr::vector lb_net_atom_net_ids_; /* index of atom net this intra_lb_net represents */ + vtr::vector> lb_net_atom_pins_; /* AtomPin's associated with each terminal */ + vtr::vector> lb_net_terminals_; /* endpoints of the intra_lb_net, 0th position is the source, all others are sinks */ + vtr::vector> lb_net_fixed_terminals_; /* Marks a terminal as having a fixed target (i.e. a pin not a sink) */ + vtr::vector lb_net_rt_trees_; /* Route tree head */ /* Logical-to-physical mapping info */ vtr::vector routing_status_; /* [0..lb_type_graph->size()-1] Stats for each logic cluster_ctx.blocks rr node instance */ From 43f15e4d6f4beb92a1922194e1b3cc8ad4584a08 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 19 Feb 2020 16:40:53 -0700 Subject: [PATCH 183/645] add methods to LbRouter for nets to be routed and access to routing traceback --- openfpga/src/repack/lb_router.cpp | 59 ++++++++++++++++++++++++++++++- openfpga/src/repack/lb_router.h | 15 +++++++- 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/openfpga/src/repack/lb_router.cpp b/openfpga/src/repack/lb_router.cpp index 610501dde..3ce17deaa 100644 --- a/openfpga/src/repack/lb_router.cpp +++ b/openfpga/src/repack/lb_router.cpp @@ -59,6 +59,22 @@ bool LbRouter::is_routed() const { return is_routed_; } +std::vector LbRouter::net_routed_nodes(const NetId& net) const { + VTR_ASSERT(true == is_routed()); + VTR_ASSERT(true == valid_net_id(net)); + + std::vector routed_nodes; + + t_trace* rt_tree = lb_net_rt_trees_[net]; + if (nullptr == rt_tree) { + return routed_nodes; + } + /* Walk through the routing tree of the net */ + rec_collect_trace_nodes(rt_tree, routed_nodes); + + return routed_nodes; +} + /************************************************** * Private accessors *************************************************/ @@ -108,9 +124,47 @@ bool LbRouter::route_has_conflict(const LbRRGraph& lb_rr_graph, t_trace* rt) con return false; } +void LbRouter::rec_collect_trace_nodes(const t_trace* trace, std::vector& routed_nodes) const { + if (routed_nodes.end() == std::find(routed_nodes.begin(), routed_nodes.end(), trace->current_node)) { + routed_nodes.push_back(trace->current_node); + } + + for (const t_trace& next : trace->next_nodes) { + rec_collect_trace_nodes(&next, routed_nodes); + } +} + /************************************************** * Public mutators *************************************************/ +LbRouter::NetId LbRouter::create_net_to_route(const LbRRNodeId& source, const std::vector& terminals) { + /* Create an new id */ + NetId net = NetId(lb_net_ids_.size()); + lb_net_ids_.push_back(net); + + /* Allocate other attributes */ + lb_net_atom_net_ids_.push_back(AtomNetId::INVALID()); + lb_net_atom_pins_.emplace_back(); + + std::vector net_terminals = terminals; + net_terminals.insert(net_terminals.begin(), source); + + lb_net_terminals_.push_back(net_terminals); + + return net; +} + +void LbRouter::add_net_atom_net_id(const NetId& net, const AtomNetId& atom_net) { + VTR_ASSERT(true == valid_net_id(net)); + lb_net_atom_net_ids_[net] = atom_net; +} + +void LbRouter::add_net_atom_pins(const NetId& net, const AtomPinId& src_pin, const std::vector& terminal_pins) { + VTR_ASSERT(true == valid_net_id(net)); + lb_net_atom_pins_[net] = terminal_pins; + lb_net_atom_pins_[net].insert(lb_net_atom_pins_[net].begin(), src_pin); +} + bool LbRouter::try_route(const LbRRGraph& lb_rr_graph, const AtomNetlist& atom_nlist, const int& verbosity) { @@ -644,6 +698,10 @@ bool LbRouter::matched_lb_rr_graph(const LbRRGraph& lb_rr_graph) const { && (explored_node_tb_.size() == lb_rr_graph.nodes().size()) ); } +bool LbRouter::valid_net_id(const NetId& net_id) const { + return ( size_t(net_id) < lb_net_ids_.size() ) && ( net_id == lb_net_ids_[net_id] ); +} + /************************************************** * Private Initializer and cleaner *************************************************/ @@ -679,7 +737,6 @@ void LbRouter::clear_nets() { lb_net_atom_net_ids_.clear(); lb_net_atom_pins_.clear(); lb_net_terminals_.clear(); - lb_net_fixed_terminals_.clear(); lb_net_rt_trees_.clear(); } diff --git a/openfpga/src/repack/lb_router.h b/openfpga/src/repack/lb_router.h index a922d1fb4..117ede40c 100644 --- a/openfpga/src/repack/lb_router.h +++ b/openfpga/src/repack/lb_router.h @@ -170,10 +170,19 @@ class LbRouter { /* Show if a valid routing solution has been founded or not */ bool is_routed() const; + /** + * Get the routing results for a Net + */ + std::vector net_routed_nodes(const NetId& net) const; + public : /* Public mutators */ /** * Add net to be routed */ + NetId create_net_to_route(const LbRRNodeId& source, const std::vector& terminals); + void add_net_atom_net_id(const NetId& net, const AtomNetId& atom_net); + void add_net_atom_pins(const NetId& net, const AtomPinId& src_pin, const std::vector& terminal_pins); + void set_net_fix_terminal(const NetId& net, const LbRRNodeId& terminal, const bool& fix); /** * Perform routing algorithm on a given logical tile routing resource graph @@ -197,6 +206,9 @@ class LbRouter { bool route_has_conflict(const LbRRGraph& lb_rr_graph, t_trace* rt) const; + /* Recursively find all the nodes in the trace */ + void rec_collect_trace_nodes(const t_trace* trace, std::vector& routed_nodes) const; + private : /* Private mutators */ /*It is possible that a net may connect multiple times to a logically equivalent set of primitive pins. *The cluster router will only route one connection for a particular net to the common sink of the @@ -249,6 +261,8 @@ class LbRouter { */ bool matched_lb_rr_graph(const LbRRGraph& lb_rr_graph) const; + bool valid_net_id(const NetId& net_id) const; + private : /* Private initializer and cleaner */ void reset_explored_node_tb(); void reset_net_rt(); @@ -265,7 +279,6 @@ class LbRouter { vtr::vector lb_net_atom_net_ids_; /* index of atom net this intra_lb_net represents */ vtr::vector> lb_net_atom_pins_; /* AtomPin's associated with each terminal */ vtr::vector> lb_net_terminals_; /* endpoints of the intra_lb_net, 0th position is the source, all others are sinks */ - vtr::vector> lb_net_fixed_terminals_; /* Marks a terminal as having a fixed target (i.e. a pin not a sink) */ vtr::vector lb_net_rt_trees_; /* Route tree head */ /* Logical-to-physical mapping info */ From bc27f9dd0c788376c770782ad21bef36c9025e5f Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 19 Feb 2020 20:34:30 -0700 Subject: [PATCH 184/645] add check codes for nets inside LbRouter --- openfpga/src/repack/lb_router.cpp | 36 +++++++++++++++++++++++++++++++ openfpga/src/repack/lb_router.h | 8 +++++++ 2 files changed, 44 insertions(+) diff --git a/openfpga/src/repack/lb_router.cpp b/openfpga/src/repack/lb_router.cpp index 3ce17deaa..1244143a0 100644 --- a/openfpga/src/repack/lb_router.cpp +++ b/openfpga/src/repack/lb_router.cpp @@ -171,7 +171,13 @@ bool LbRouter::try_route(const LbRRGraph& lb_rr_graph, /* Validate if the rr_graph is the one we used to initialize the router */ VTR_ASSERT(true == matched_lb_rr_graph(lb_rr_graph)); + /* Ensure each net to be routed is valid */ + for (const NetId& net : lb_net_ids_) { + VTR_ASSERT(true == check_net(lb_rr_graph, atom_nlist, net)); + } + is_routed_ = false; + bool is_impossible = false; mode_status_.is_mode_conflict = false; @@ -198,6 +204,7 @@ bool LbRouter::try_route(const LbRRGraph& lb_rr_graph, if (is_skip_route_net(lb_rr_graph, lb_net_rt_trees_[idx])) { continue; } + commit_remove_rt(lb_rr_graph, lb_net_rt_trees_[idx], RT_REMOVE, mode_map); free_net_rt(lb_net_rt_trees_[idx]); lb_net_rt_trees_[idx] = nullptr; @@ -702,6 +709,35 @@ bool LbRouter::valid_net_id(const NetId& net_id) const { return ( size_t(net_id) < lb_net_ids_.size() ) && ( net_id == lb_net_ids_[net_id] ); } +bool LbRouter::check_net(const LbRRGraph& lb_rr_graph, + const AtomNetlist& atom_nlist, + const NetId& net) const { + if (false == atom_nlist.valid_net_id(lb_net_atom_net_ids_[net])) { + return false; + } + if (lb_net_atom_pins_[net].size() != lb_net_terminals_[net].size()) { + return false; + } + /* We must have 1 source and >1 terminal */ + if (2 > lb_net_terminals_[net].size()) { + return false; + } + /* Each node must be valid */ + for (const LbRRNodeId& node : lb_net_terminals_[net]) { + if (false == lb_rr_graph.valid_node_id(node)) { + return false; + } + } + /* Each atom pin must be valid */ + for (const AtomPinId& pin : lb_net_atom_pins_[net]) { + if (false == atom_nlist.valid_pin_id(pin)) { + return false; + } + } + + return true; +} + /************************************************** * Private Initializer and cleaner *************************************************/ diff --git a/openfpga/src/repack/lb_router.h b/openfpga/src/repack/lb_router.h index 117ede40c..3a1f299af 100644 --- a/openfpga/src/repack/lb_router.h +++ b/openfpga/src/repack/lb_router.h @@ -263,6 +263,14 @@ class LbRouter { bool valid_net_id(const NetId& net_id) const; + /* Validate that all the nets have + * - valid source, terminal nodes in lb routing resource graph + * - valid atom net and pin ids in atom netlist + */ + bool check_net(const LbRRGraph& lb_rr_graph, + const AtomNetlist& atom_nlist, + const NetId& net) const; + private : /* Private initializer and cleaner */ void reset_explored_node_tb(); void reset_net_rt(); From ed5d83178f0a4b582a224d28afd0d73c72ec8f3d Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 19 Feb 2020 21:07:31 -0700 Subject: [PATCH 185/645] add fundamental check codes for LbRRGraph --- .../src/repack/build_physical_lb_rr_graph.cpp | 7 + openfpga/src/repack/lb_rr_graph.cpp | 191 ++++++++++++++++++ openfpga/src/repack/lb_rr_graph.h | 16 +- 3 files changed, 213 insertions(+), 1 deletion(-) diff --git a/openfpga/src/repack/build_physical_lb_rr_graph.cpp b/openfpga/src/repack/build_physical_lb_rr_graph.cpp index aca8cb0d1..e79985082 100644 --- a/openfpga/src/repack/build_physical_lb_rr_graph.cpp +++ b/openfpga/src/repack/build_physical_lb_rr_graph.cpp @@ -395,6 +395,13 @@ void build_physical_lb_rr_graphs(const DeviceContext& device_ctx, lb_type.pb_graph_head->pb_type->name); const LbRRGraph& lb_rr_graph = build_lb_type_physical_lb_rr_graph(lb_type.pb_graph_head, const_cast(device_annotation), verbose); + /* Check the rr_graph */ + if (false == lb_rr_graph.validate()) { + exit(1); + } + VTR_LOGV(verbose, + "Check routing resource graph for logical tile passed\n"); + device_annotation.add_physical_lb_rr_graph(lb_type.pb_graph_head, lb_rr_graph); } diff --git a/openfpga/src/repack/lb_rr_graph.cpp b/openfpga/src/repack/lb_rr_graph.cpp index 434ac0b3e..db43543da 100644 --- a/openfpga/src/repack/lb_rr_graph.cpp +++ b/openfpga/src/repack/lb_rr_graph.cpp @@ -262,4 +262,195 @@ bool LbRRGraph::valid_edge_id(const LbRREdgeId& edge_id) const { return ( size_t(edge_id) < edge_ids_.size() ) && ( edge_id == edge_ids_[edge_id] ); } +/* This function run fundamental checks on internal data + * Errors are thrown if fundamental checking fails + */ +bool LbRRGraph::validate() const { + size_t num_err = 0; + + /* Validate the sizes of nodes and node-related vectors + * Validate the sizes of edges and edge-related vectors + */ + if (false == validate_sizes()) { + VTR_LOG_WARN("Fail in validating node and edges sizes!\n"); + num_err++; + } + + /* Fundamental check */ + if (false == validate_nodes_edges()) { + VTR_LOG_WARN("Fail in validating edges connected to each node!\n"); + num_err++; + } + + /* Error out if there is any fatal errors found */ + if (0 < num_err) { + VTR_LOG_ERROR("Logical tile Routing Resource graph is not valid due to %d fatal errors !\n", + num_err); + } + + return (0 == num_err); +} + +/****************************************************************************** + * Private validators/invalidators + ******************************************************************************/ +bool LbRRGraph::validate_node_sizes() const { + size_t num_nodes = node_ids_.size(); + return node_ids_.size() == num_nodes + && node_types_.size() == num_nodes + && node_capacities_.size() == num_nodes + && node_pb_graph_pins_.size() == num_nodes + && node_intrinsic_costs_.size() == num_nodes + && node_in_edges_.size() == num_nodes + && node_out_edges_.size() == num_nodes; +} + +bool LbRRGraph::validate_edge_sizes() const { + size_t num_edges = edge_ids_.size(); + return edge_src_nodes_.size() == num_edges + && edge_sink_nodes_.size() == num_edges + && edge_intrinsic_costs_.size() == num_edges + && edge_modes_.size() == num_edges; +} + +bool LbRRGraph::validate_sizes() const { + return validate_node_sizes() && validate_edge_sizes(); +} + +/* Check if a node is in the list of source_nodes of a edge */ +bool LbRRGraph::validate_node_is_edge_src(const LbRRNodeId& node, const LbRREdgeId& edge) const { + /* Assure a valid node id */ + VTR_ASSERT_SAFE(valid_node_id(node)); + /* assure a valid edge id */ + VTR_ASSERT_SAFE(valid_edge_id(edge)); + /* find if the node is the src */ + if (node == edge_src_node(edge)) { + return true; /* confirmed source node*/ + } else { + return false; /* not a source */ + } +} + +/* Check if a node is in the list of sink_nodes of a edge */ +bool LbRRGraph::validate_node_is_edge_sink(const LbRRNodeId& node, const LbRREdgeId& edge) const { + /* Assure a valid node id */ + VTR_ASSERT_SAFE(valid_node_id(node)); + /* assure a valid edge id */ + VTR_ASSERT_SAFE(valid_edge_id(edge)); + /* find if the node is the sink */ + if (node == edge_sink_node(edge)) { + return true; /* confirmed source node*/ + } else { + return false; /* not a source */ + } +} +/* This function will check if a node has valid input edges + * 1. Check the edge ids are valid + * 2. Check the node is in the list of edge_sink_node + */ +bool LbRRGraph::validate_node_in_edges(const LbRRNodeId& node) const { + bool all_valid = true; + /* Assure a valid node id */ + VTR_ASSERT_SAFE(valid_node_id(node)); + /* Check each edge */ + for (auto edge : node_in_edges(node)) { + /* assure a valid edge id */ + VTR_ASSERT_SAFE(valid_edge_id(edge)); + /* check the node is in the list of edge_sink_node */ + if (true == validate_node_is_edge_sink(node, edge)) { + continue; + } + /* Reach here, it means there is something wrong! + * Print a warning + */ + VTR_LOG_WARN("Edge %d is in the input edge list of node %d while the node is not in edge's sink node list!\n", + size_t(edge), size_t(node)); + all_valid = false; + } + + return all_valid; +} + +/* This function will check if a node has valid output edges + * 1. Check the edge ids are valid + * 2. Check the node is in the list of edge_source_node + */ +bool LbRRGraph::validate_node_out_edges(const LbRRNodeId& node) const { + bool all_valid = true; + /* Assure a valid node id */ + VTR_ASSERT_SAFE(valid_node_id(node)); + /* Check each edge */ + for (auto edge : node_out_edges(node)) { + /* assure a valid edge id */ + VTR_ASSERT_SAFE(valid_edge_id(edge)); + /* check the node is in the list of edge_sink_node */ + if (true == validate_node_is_edge_src(node, edge)) { + continue; + } + /* Reach here, it means there is something wrong! + * Print a warning + */ + VTR_LOG_WARN("Edge %d is in the output edge list of node %d while the node is not in edge's source node list!\n", + size_t(edge), size_t(node)); + all_valid = false; + } + + return all_valid; +} + + +/* check if all the nodes' input edges are valid */ +bool LbRRGraph::validate_nodes_in_edges() const { + bool all_valid = true; + for (const LbRRNodeId& id : nodes()) { + /* Try to find if this is an invalid id or not */ + if (!valid_node_id(id)) { + /* Skip this id */ + continue; + } + if (true == validate_node_in_edges(id)) { + continue; + } + /* Reach here, it means there is something wrong! + * Print a warning + */ + all_valid = false; + } + return all_valid; +} + +/* check if all the nodes' output edges are valid */ +bool LbRRGraph::validate_nodes_out_edges() const { + bool all_valid = true; + for (const LbRRNodeId& id : nodes()) { + /* Try to find if this is an invalid id or not */ + if (!valid_node_id(id)) { + /* Skip this id */ + continue; + } + if (true == validate_node_out_edges(id)) { + continue; + } + /* Reach here, it means there is something wrong! + * Print a warning + */ + all_valid = false; + } + return all_valid; +} + +/* check all the edges of every node */ +bool LbRRGraph::validate_nodes_edges() const { + bool all_valid = true; + + if (false == validate_nodes_in_edges()) { + all_valid = false; + } + if (false == validate_nodes_out_edges()) { + all_valid = false; + } + + return all_valid; +} + } /* end namespace openfpga */ diff --git a/openfpga/src/repack/lb_rr_graph.h b/openfpga/src/repack/lb_rr_graph.h index 4500a504b..3aca85934 100644 --- a/openfpga/src/repack/lb_rr_graph.h +++ b/openfpga/src/repack/lb_rr_graph.h @@ -266,13 +266,27 @@ class LbRRGraph { LbRREdgeId create_edge(const LbRRNodeId& source, const LbRRNodeId& sink, t_mode* mode); void set_edge_intrinsic_cost(const LbRREdgeId& edge, const float& cost); - public: /* Validators */ + public: /* Public validators */ /* Validate is the node id does exist in the RRGraph */ bool valid_node_id(const LbRRNodeId& node) const; /* Validate is the edge id does exist in the RRGraph */ bool valid_edge_id(const LbRREdgeId& edge) const; + bool validate() const; + + private: /* Private Validators */ + bool validate_node_sizes() const; + bool validate_edge_sizes() const; + bool validate_sizes() const; + bool validate_node_is_edge_src(const LbRRNodeId& node, const LbRREdgeId& edge) const; + bool validate_node_is_edge_sink(const LbRRNodeId& node, const LbRREdgeId& edge) const; + bool validate_node_in_edges(const LbRRNodeId& node) const; + bool validate_node_out_edges(const LbRRNodeId& node) const; + bool validate_nodes_in_edges() const; + bool validate_nodes_out_edges() const; + bool validate_nodes_edges() const; + private: /* Internal Data */ /* Node related data */ vtr::vector node_ids_; From d8ab5536e110324343e02f6cf2d4718f0fee2f15 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 19 Feb 2020 21:41:05 -0700 Subject: [PATCH 186/645] add advanced check codes for lb_rr_graph --- .../src/repack/build_physical_lb_rr_graph.cpp | 4 + openfpga/src/repack/check_lb_rr_graph.cpp | 206 ++++++++++++++++++ openfpga/src/repack/check_lb_rr_graph.h | 21 ++ openfpga/src/repack/lb_rr_graph_utils.cpp | 13 ++ openfpga/src/repack/lb_rr_graph_utils.h | 3 + 5 files changed, 247 insertions(+) create mode 100644 openfpga/src/repack/check_lb_rr_graph.cpp create mode 100644 openfpga/src/repack/check_lb_rr_graph.h diff --git a/openfpga/src/repack/build_physical_lb_rr_graph.cpp b/openfpga/src/repack/build_physical_lb_rr_graph.cpp index e79985082..ae9fc4c65 100644 --- a/openfpga/src/repack/build_physical_lb_rr_graph.cpp +++ b/openfpga/src/repack/build_physical_lb_rr_graph.cpp @@ -10,6 +10,7 @@ #include "pb_type_utils.h" #include "build_physical_lb_rr_graph.h" +#include "check_lb_rr_graph.h" /* begin namespace openfpga */ namespace openfpga { @@ -399,6 +400,9 @@ void build_physical_lb_rr_graphs(const DeviceContext& device_ctx, if (false == lb_rr_graph.validate()) { exit(1); } + if (false == check_lb_rr_graph(lb_rr_graph)) { + exit(1); + } VTR_LOGV(verbose, "Check routing resource graph for logical tile passed\n"); diff --git a/openfpga/src/repack/check_lb_rr_graph.cpp b/openfpga/src/repack/check_lb_rr_graph.cpp new file mode 100644 index 000000000..9b3adbf8f --- /dev/null +++ b/openfpga/src/repack/check_lb_rr_graph.cpp @@ -0,0 +1,206 @@ +#include + +#include "vtr_log.h" + +#include "lb_rr_graph_utils.h" +#include "check_lb_rr_graph.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/*********************************************************************** + * This function aims at checking any duplicated edges (with same EdgeId) + * of a given node. + * We will walkthrough the input edges of a node and see if there is any duplication + **********************************************************************/ +static bool check_lb_rr_graph_node_duplicated_edges(const LbRRGraph& lb_rr_graph, + const LbRRNodeId& node) { + bool no_duplication = true; + + /* Create a map for each input edge */ + std::map> edge_counter; + + /* Check each input edges */ + for (const auto edge : lb_rr_graph.node_in_edges(node)) { + if (nullptr == lb_rr_graph.edge_mode(edge)) { + continue; + } + auto result = edge_counter[lb_rr_graph.edge_mode(edge)].insert(std::pair(edge, 1)); + if (false == result.second) { + result.first->second++; + } + } + + for (auto& edge_mode : edge_counter) { + for (auto& elem : edge_mode.second) { + if (elem.second > 1) { + /* Reach here it means we find some duplicated edges and report errors */ + /* Print a warning! */ + VTR_LOG_WARN("Node %d has duplicated input edges (id = %d)!\n", + size_t(node), size_t(elem.first)); + print_lb_rr_node(lb_rr_graph, node); + no_duplication = false; + } + } + } + + return no_duplication; +} + +/*********************************************************************** + * Check the whole Routing Resource Graph + * identify and report any duplicated edges between two nodes + **********************************************************************/ +static bool check_lb_rr_graph_duplicated_edges(const LbRRGraph& lb_rr_graph) { + bool no_duplication = true; + /* For each node: + * Search input edges, see there are two edges with same id or address + */ + for (const auto& node : lb_rr_graph.nodes()) { + if (false == check_lb_rr_graph_node_duplicated_edges(lb_rr_graph, node)) { + no_duplication = false; + } + } + + return no_duplication; +} + +/*********************************************************************** + * Identify and report any dangling node (nodes without any fan-in or fan-out) + * in the LbRRGraph + **********************************************************************/ +static bool check_lb_rr_graph_dangling_nodes(const LbRRGraph& lb_rr_graph) { + bool no_dangling = true; + /* For each node: + * check if the number of input edges and output edges are both 0 + * If so, this is a dangling nodes and report + */ + for (auto node : lb_rr_graph.nodes()) { + if ((0 == lb_rr_graph.node_in_edges(node).size()) + && (0 == lb_rr_graph.node_out_edges(node).size())) { + /* Print a warning! */ + VTR_LOG_WARN("Node %s is dangling (zero fan-in and zero fan-out)!\n", + node); + VTR_LOG_WARN("Node details for debugging:\n"); + print_lb_rr_node(lb_rr_graph, node); + no_dangling = false; + } + } + + return no_dangling; +} + +/*********************************************************************** + * check if all the source nodes are in the right condition: + * 1. zero fan-in and non-zero fanout + **********************************************************************/ +static bool check_lb_rr_graph_source_nodes(const LbRRGraph& lb_rr_graph) { + bool invalid_sources = false; + /* For each node: + * check if the number of input edges and output edges are both 0 + * If so, this is a dangling nodes and report + */ + for (auto node : lb_rr_graph.nodes()) { + /* Pass nodes whose types are not LB_SOURCE */ + if (LB_SOURCE != lb_rr_graph.node_type(node)) { + continue; + } + if ((0 != lb_rr_graph.node_in_edges(node).size()) + || (0 == lb_rr_graph.node_out_edges(node).size())) { + /* Print a warning! */ + VTR_LOG_WARN("Source node %d is invalid (should have zero fan-in and non-zero fan-out)!\n", + size_t(node)); + VTR_LOG_WARN("Node details for debugging:\n"); + print_lb_rr_node(lb_rr_graph, node); + invalid_sources = true; + } + } + + return !invalid_sources; +} + +/*********************************************************************** + * check if all the sink nodes are in the right condition: + * 1. non-zero fan-in and zero fanout + **********************************************************************/ +static bool check_lb_rr_graph_sink_nodes(const LbRRGraph& lb_rr_graph) { + bool invalid_sinks = false; + /* For each node: + * check if the number of input edges and output edges are both 0 + * If so, this is a dangling nodes and report + */ + for (auto node : lb_rr_graph.nodes()) { + /* Pass nodes whose types are not LB_SINK */ + if (LB_SINK != lb_rr_graph.node_type(node)) { + continue; + } + if ((0 == lb_rr_graph.node_in_edges(node).size()) + || (0 != lb_rr_graph.node_out_edges(node).size())) { + /* Print a warning! */ + VTR_LOG_WARN("Sink node %s is invalid (should have non-zero fan-in and zero fan-out)!\n", + node); + VTR_LOG_WARN("Node details for debugging:\n"); + print_lb_rr_node(lb_rr_graph, node); + invalid_sinks = true; + } + } + + return !invalid_sinks; +} + +/*********************************************************************** + * This is an advanced checker for LbRRGraph object + * Note that the checker try to report as many problems as it can. + * The problems may cause routing efficiency or even failures, depending + * on routing algorithms. + * It is strongly suggested to run this sanitizer before conducting + * routing algorithms + * + * For those who will develop customized lb_rr_graphs and routers: + * On the other hand, it is suggested that developers to create their + * own checking function for the lb_rr_graph, to guarantee their routers + * will work properly. + **********************************************************************/ +bool check_lb_rr_graph(const LbRRGraph& lb_rr_graph) { + size_t num_err = 0; + + if (false == check_lb_rr_graph_duplicated_edges(lb_rr_graph)) { + VTR_LOG_WARN("Fail in checking duplicated edges !\n"); + num_err++; + } + + if (false == check_lb_rr_graph_dangling_nodes(lb_rr_graph)) { + VTR_LOG_WARN("Fail in checking dangling nodes !\n"); + num_err++; + } + + if (false == check_lb_rr_graph_source_nodes(lb_rr_graph)) { + VTR_LOG_WARN("Fail in checking source nodes!\n"); + num_err++; + } + + if (false == check_lb_rr_graph_sink_nodes(lb_rr_graph)) { + VTR_LOG_WARN("Fail in checking sink nodes!\n"); + num_err++; + } + + if (false == check_lb_rr_graph_source_nodes(lb_rr_graph)) { + VTR_LOG_WARN("Fail in checking source nodes!\n"); + num_err++; + } + + if (false == check_lb_rr_graph_sink_nodes(lb_rr_graph)) { + VTR_LOG_WARN("Fail in checking sink nodes!\n"); + num_err++; + } + + /* Error out if there is any fatal errors found */ + if (0 < num_err) { + VTR_LOG_WARN("Checked Logical tile Routing Resource graph with %d errors !\n", + num_err); + } + + return (0 == num_err); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/repack/check_lb_rr_graph.h b/openfpga/src/repack/check_lb_rr_graph.h new file mode 100644 index 000000000..232fa597c --- /dev/null +++ b/openfpga/src/repack/check_lb_rr_graph.h @@ -0,0 +1,21 @@ +#ifndef CHECK_LB_RR_GRAPH_H +#define CHECK_LB_RR_GRAPH_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "lb_rr_graph.h" + + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +bool check_lb_rr_graph(const LbRRGraph& rr_graph); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/repack/lb_rr_graph_utils.cpp b/openfpga/src/repack/lb_rr_graph_utils.cpp index 351103647..a2e1df63a 100644 --- a/openfpga/src/repack/lb_rr_graph_utils.cpp +++ b/openfpga/src/repack/lb_rr_graph_utils.cpp @@ -57,4 +57,17 @@ std::string describe_lb_rr_node(const LbRRGraph& lb_rr_graph, return description; } +/* This function aims to print basic information about a node */ +void print_lb_rr_node(const LbRRGraph& lb_rr_graph, + const LbRRNodeId& node) { + VTR_LOG("Node id: %d\n", size_t(node)); + VTR_LOG("Node type: %s\n", lb_rr_type_str[lb_rr_graph.node_type(node)]); + VTR_LOG("Node capacity: %d\n", lb_rr_graph.node_capacity(node)); + VTR_LOG("Node pb_graph_pin: %s\n", lb_rr_graph.node_pb_graph_pin(node)->to_string().c_str()); + VTR_LOG("Node intrinsic_cost: %f\n", lb_rr_graph.node_intrinsic_cost(node)); + VTR_LOG("Node num in_edges: %ld\n", lb_rr_graph.node_in_edges(node).size()); + VTR_LOG("Node num out_edges: %ld\n", lb_rr_graph.node_out_edges(node).size()); +} + + } /* end namespace openfpga */ diff --git a/openfpga/src/repack/lb_rr_graph_utils.h b/openfpga/src/repack/lb_rr_graph_utils.h index 392617f6a..f493caa4f 100644 --- a/openfpga/src/repack/lb_rr_graph_utils.h +++ b/openfpga/src/repack/lb_rr_graph_utils.h @@ -17,6 +17,9 @@ namespace openfpga { std::string describe_lb_rr_node(const LbRRGraph& lb_rr_graph, const LbRRNodeId& inode); +void print_lb_rr_node(const LbRRGraph& lb_rr_graph, + const LbRRNodeId& node); + } /* end namespace openfpga */ #endif From fdb27c5a6b4822e8cea7cf64460ad34279ca977d Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 20 Feb 2020 13:24:34 -0700 Subject: [PATCH 187/645] move lb_rr_graph construction to repack command --- openfpga/src/base/openfpga_link_arch.cpp | 6 ------ openfpga/src/base/openfpga_repack.cpp | 2 +- openfpga/src/repack/repack.cpp | 10 ++++++++-- openfpga/src/repack/repack.h | 2 +- openfpga/test_script/s298_k6_frac.openfpga | 5 +++++ 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/openfpga/src/base/openfpga_link_arch.cpp b/openfpga/src/base/openfpga_link_arch.cpp index ac403592c..fcaa1f128 100644 --- a/openfpga/src/base/openfpga_link_arch.cpp +++ b/openfpga/src/base/openfpga_link_arch.cpp @@ -15,7 +15,6 @@ #include "annotate_rr_graph.h" #include "mux_library_builder.h" #include "build_tile_direct.h" -#include "build_physical_lb_rr_graph.h" #include "openfpga_link_arch.h" /* Include global variables of VPR */ @@ -112,11 +111,6 @@ void link_arch(OpenfpgaContext& openfpga_ctx, /* Build tile direct annotation */ openfpga_ctx.mutable_tile_direct() = build_device_tile_direct(g_vpr_ctx.device(), openfpga_ctx.arch().arch_direct); - - - build_physical_lb_rr_graphs(g_vpr_ctx.device(), - openfpga_ctx.mutable_vpr_device_annotation(), - cmd_context.option_enable(cmd, opt_verbose)); } } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_repack.cpp b/openfpga/src/base/openfpga_repack.cpp index c58e2b4ff..f605233da 100644 --- a/openfpga/src/base/openfpga_repack.cpp +++ b/openfpga/src/base/openfpga_repack.cpp @@ -24,7 +24,7 @@ void repack(OpenfpgaContext& openfpga_ctx, CommandOptionId opt_verbose = cmd.option("verbose"); pack_physical_pbs(g_vpr_ctx.device(), - openfpga_ctx.vpr_device_annotation(), + openfpga_ctx.mutable_vpr_device_annotation(), openfpga_ctx.mutable_vpr_clustering_annotation(), openfpga_ctx.vpr_routing_annotation(), cmd_context.option_enable(cmd, opt_verbose)); diff --git a/openfpga/src/repack/repack.cpp b/openfpga/src/repack/repack.cpp index 690dbaa23..ffb1ac631 100644 --- a/openfpga/src/repack/repack.cpp +++ b/openfpga/src/repack/repack.cpp @@ -7,6 +7,7 @@ #include "vtr_assert.h" #include "vtr_time.h" +#include "build_physical_lb_rr_graph.h" #include "repack.h" /* begin namespace openfpga */ @@ -16,17 +17,22 @@ namespace openfpga { * Top-level function to pack physical pb_graph * This function will do : * - create physical lb_rr_graph for each pb_graph considering physical modes only - * the lb_rr_graph willbe added to device annotation + * the lb_rr_graph will be added to device annotation * - annotate nets to be routed for each clustered block from operating modes of pb_graph * to physical modes of pb_graph * - rerun the routing for each clustered block * - store the packing results to clustering annotation ***************************************************************************************/ void pack_physical_pbs(const DeviceContext& device_ctx, - const VprDeviceAnnotation& device_annotation, + VprDeviceAnnotation& device_annotation, VprClusteringAnnotation& clustering_annotation, const VprRoutingAnnotation& routing_annotation, const bool& verbose) { + + build_physical_lb_rr_graphs(device_ctx, + device_annotation, + verbose); + } } /* end namespace openfpga */ diff --git a/openfpga/src/repack/repack.h b/openfpga/src/repack/repack.h index 2f8acd241..733a7d1ac 100644 --- a/openfpga/src/repack/repack.h +++ b/openfpga/src/repack/repack.h @@ -17,7 +17,7 @@ namespace openfpga { void pack_physical_pbs(const DeviceContext& device_ctx, - const VprDeviceAnnotation& device_annotation, + VprDeviceAnnotation& device_annotation, VprClusteringAnnotation& clustering_annotation, const VprRoutingAnnotation& routing_annotation, const bool& verbose); diff --git a/openfpga/test_script/s298_k6_frac.openfpga b/openfpga/test_script/s298_k6_frac.openfpga index 3c44fddae..f597475b9 100644 --- a/openfpga/test_script/s298_k6_frac.openfpga +++ b/openfpga/test_script/s298_k6_frac.openfpga @@ -21,6 +21,11 @@ lut_truth_table_fixup #--verbose # - Enable pin duplication on grid modules build_fabric --compress_routing --duplicate_grid_pin --verbose +# Repack the netlist to physical pbs +# This must be done before bitstream generator and testbench generation +# Strongly recommend it is done after all the fix-up have been applied +repack --verbose + # Write the Verilog netlit for FPGA fabric # - Enable the use of explicit port mapping in Verilog netlist write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose From 3e07d7d5e0b49f12740804fd1e73195a76653b5d Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 20 Feb 2020 20:26:20 -0700 Subject: [PATCH 188/645] finish net addition to LbRouter. Found a bug in pb pin fix-up. Need to consider clustered I/O block z offset --- openfpga/src/base/openfpga_pb_pin_fixup.cpp | 2 +- openfpga/src/base/openfpga_repack.cpp | 3 +- openfpga/src/fabric/build_grid_modules.cpp | 2 +- openfpga/src/repack/lb_router.cpp | 2 - openfpga/src/repack/lb_router.h | 2 +- openfpga/src/repack/lb_router_utils.cpp | 40 +++ openfpga/src/repack/lb_router_utils.h | 26 ++ openfpga/src/repack/lb_rr_graph.cpp | 4 + openfpga/src/repack/lb_rr_graph.h | 2 + openfpga/src/repack/repack.cpp | 286 +++++++++++++++++++- openfpga/src/repack/repack.h | 3 +- openfpga/src/utils/pb_type_utils.cpp | 14 +- openfpga/test_script/s298_k6_frac.openfpga | 6 +- 13 files changed, 378 insertions(+), 14 deletions(-) create mode 100644 openfpga/src/repack/lb_router_utils.cpp create mode 100644 openfpga/src/repack/lb_router_utils.h diff --git a/openfpga/src/base/openfpga_pb_pin_fixup.cpp b/openfpga/src/base/openfpga_pb_pin_fixup.cpp index 1c0beee27..54850c7c8 100644 --- a/openfpga/src/base/openfpga_pb_pin_fixup.cpp +++ b/openfpga/src/base/openfpga_pb_pin_fixup.cpp @@ -122,7 +122,7 @@ void update_cluster_pin_with_post_routing_results(const DeviceContext& device_ct /* If matched, we finish here */ if (routing_net_id == cluster_net_id) { - continue; + continue; } /* Add to net modification */ vpr_clustering_annotation.rename_net(blk_id, j, routing_net_id); diff --git a/openfpga/src/base/openfpga_repack.cpp b/openfpga/src/base/openfpga_repack.cpp index f605233da..f49321252 100644 --- a/openfpga/src/base/openfpga_repack.cpp +++ b/openfpga/src/base/openfpga_repack.cpp @@ -24,9 +24,10 @@ void repack(OpenfpgaContext& openfpga_ctx, CommandOptionId opt_verbose = cmd.option("verbose"); pack_physical_pbs(g_vpr_ctx.device(), + g_vpr_ctx.atom(), + g_vpr_ctx.clustering(), openfpga_ctx.mutable_vpr_device_annotation(), openfpga_ctx.mutable_vpr_clustering_annotation(), - openfpga_ctx.vpr_routing_annotation(), cmd_context.option_enable(cmd, opt_verbose)); } diff --git a/openfpga/src/fabric/build_grid_modules.cpp b/openfpga/src/fabric/build_grid_modules.cpp index fcfbfa0aa..39ca8e266 100644 --- a/openfpga/src/fabric/build_grid_modules.cpp +++ b/openfpga/src/fabric/build_grid_modules.cpp @@ -1053,7 +1053,7 @@ void build_physical_tile_module(ModuleManager& module_manager, sram_orgz_type, circuit_lib.design_tech_type(sram_model)); } - VTR_LOG("Done\n"); + VTR_LOGV(verbose, "Done\n"); } /***************************************************************************** diff --git a/openfpga/src/repack/lb_router.cpp b/openfpga/src/repack/lb_router.cpp index 1244143a0..9c83be476 100644 --- a/openfpga/src/repack/lb_router.cpp +++ b/openfpga/src/repack/lb_router.cpp @@ -1,8 +1,6 @@ /****************************************************************************** * Memember functions for data structure LbRouter ******************************************************************************/ -#include - #include "vtr_assert.h" #include "vtr_log.h" diff --git a/openfpga/src/repack/lb_router.h b/openfpga/src/repack/lb_router.h index 3a1f299af..0a8bb95fd 100644 --- a/openfpga/src/repack/lb_router.h +++ b/openfpga/src/repack/lb_router.h @@ -7,6 +7,7 @@ #include #include #include +#include #include "vtr_vector.h" #include "vtr_strong_id.h" @@ -182,7 +183,6 @@ class LbRouter { NetId create_net_to_route(const LbRRNodeId& source, const std::vector& terminals); void add_net_atom_net_id(const NetId& net, const AtomNetId& atom_net); void add_net_atom_pins(const NetId& net, const AtomPinId& src_pin, const std::vector& terminal_pins); - void set_net_fix_terminal(const NetId& net, const LbRRNodeId& terminal, const bool& fix); /** * Perform routing algorithm on a given logical tile routing resource graph diff --git a/openfpga/src/repack/lb_router_utils.cpp b/openfpga/src/repack/lb_router_utils.cpp new file mode 100644 index 000000000..961b61ff7 --- /dev/null +++ b/openfpga/src/repack/lb_router_utils.cpp @@ -0,0 +1,40 @@ +/*************************************************************************************** + * This file includes functions that are used to redo packing for physical pbs + ***************************************************************************************/ + +/* Headers from vtrutil library */ +#include "vtr_assert.h" + +#include "lb_router_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/*************************************************************************************** + * Add a net to route to a logical block router + * This function will automatically find the source and sink atom pins + * based on the given atom net + ***************************************************************************************/ +LbRouter::NetId add_lb_router_net_to_route(LbRouter& lb_router, + const LbRRNodeId& source_node, + const std::vector& sink_nodes, + const AtomNetlist& atom_nlist, + const AtomNetId& atom_net_id) { + VTR_ASSERT(0 < sink_nodes.size()); + + LbRouter::NetId lb_net = lb_router.create_net_to_route(source_node, sink_nodes); + + VTR_ASSERT(AtomNetId::INVALID() != atom_net_id); + lb_router.add_net_atom_net_id(lb_net, atom_net_id); + std::vector terminal_pins; + for (const AtomPinId& atom_pin : atom_nlist.net_sinks(atom_net_id)) { + VTR_ASSERT(AtomPinId::INVALID() != atom_pin); + terminal_pins.push_back(atom_pin); + } + VTR_ASSERT(AtomPinId::INVALID() != atom_nlist.net_driver(atom_net_id)); + lb_router.add_net_atom_pins(lb_net, atom_nlist.net_driver(atom_net_id), terminal_pins); + + return lb_net; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/repack/lb_router_utils.h b/openfpga/src/repack/lb_router_utils.h new file mode 100644 index 000000000..bcfc6004b --- /dev/null +++ b/openfpga/src/repack/lb_router_utils.h @@ -0,0 +1,26 @@ +#ifndef LB_ROUTER_UTILS_H +#define LB_ROUTER_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "atom_netlist.h" +#include "lb_rr_graph.h" +#include "lb_router.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +LbRouter::NetId add_lb_router_net_to_route(LbRouter& lb_router, + const LbRRNodeId& source_node, + const std::vector& sink_nodes, + const AtomNetlist& atom_nlist, + const AtomNetId& atom_net_id); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/repack/lb_rr_graph.cpp b/openfpga/src/repack/lb_rr_graph.cpp index db43543da..137b4a105 100644 --- a/openfpga/src/repack/lb_rr_graph.cpp +++ b/openfpga/src/repack/lb_rr_graph.cpp @@ -291,6 +291,10 @@ bool LbRRGraph::validate() const { return (0 == num_err); } +bool LbRRGraph::empty() const { + return (0 == nodes().size()) && (0 == edges().size()); +} + /****************************************************************************** * Private validators/invalidators ******************************************************************************/ diff --git a/openfpga/src/repack/lb_rr_graph.h b/openfpga/src/repack/lb_rr_graph.h index 3aca85934..b4d4a093a 100644 --- a/openfpga/src/repack/lb_rr_graph.h +++ b/openfpga/src/repack/lb_rr_graph.h @@ -275,6 +275,8 @@ class LbRRGraph { bool validate() const; + bool empty() const; + private: /* Private Validators */ bool validate_node_sizes() const; bool validate_edge_sizes() const; diff --git a/openfpga/src/repack/repack.cpp b/openfpga/src/repack/repack.cpp index ffb1ac631..f69d23e32 100644 --- a/openfpga/src/repack/repack.cpp +++ b/openfpga/src/repack/repack.cpp @@ -7,12 +7,290 @@ #include "vtr_assert.h" #include "vtr_time.h" +/* Headers from vpr library */ +#include "vpr_utils.h" + +#include "pb_type_utils.h" #include "build_physical_lb_rr_graph.h" +#include "lb_router.h" +#include "lb_router_utils.h" #include "repack.h" /* begin namespace openfpga */ namespace openfpga { +/*************************************************************************************** + * Try find the pin id which is mapped to a given atom net id in the context of pb route + ***************************************************************************************/ +static +std::vector find_routed_pb_graph_pins_atom_net(const t_pb* pb, + const AtomNetId& atom_net_id, + t_pb_graph_pin** pb_graph_pin_lookup_from_index) { + std::vector sink_pb_pins; + + /* Find the sink nodes from top-level node */ + for (int pin = 0; pin < pb->pb_graph_node->total_pb_pins; ++pin) { + /* Bypass unused pins */ + if ((0 == pb->pb_route.count(pin)) || (AtomNetId::INVALID() == pb->pb_route[pin].atom_net_id)) { + continue; + } + /* Get the driver pb pin id, it must be valid */ + if (atom_net_id != pb->pb_route[pin].atom_net_id) { + continue; + } + /* Check each sink nodes, if pin belongs to an input of a primitive pb_graph_node, it is what we want */ + for (const int& sink_pb_pin_id : pb->pb_route[pin].sink_pb_pin_ids) { + t_pb_graph_pin* sink_pb_pin = pb_graph_pin_lookup_from_index[sink_pb_pin_id]; + VTR_ASSERT(nullptr != sink_pb_pin); + /* We care only + * - input pins of primitive nodes + * - output pins of top node + */ + if ( (true == is_primitive_pb_type(sink_pb_pin->parent_node->pb_type)) + && (IN_PORT == sink_pb_pin->port->type)) { + sink_pb_pins.push_back(sink_pb_pin); + } + + if ( (true == sink_pb_pin->parent_node->is_root()) + && (OUT_PORT == sink_pb_pin->port->type)) { + sink_pb_pins.push_back(sink_pb_pin); + } + } + } + + return sink_pb_pins; +} + +/*************************************************************************************** + * Find the corresponding nodes in a logical block routing resource graph + * with a given list of sink pb_graph pins + * Note that these sink pins belong to operating pb_graph_node, + * we will find the associated physical pb_graph_node as well as physical pins + * and then spot the nodes in lb_rr_graph + ***************************************************************************************/ +static +std::vector find_lb_net_physical_sink_lb_rr_nodes(const LbRRGraph& lb_rr_graph, + const std::vector& sink_pins, + const VprDeviceAnnotation& device_annotation) { + std::vector sink_nodes; + + for (t_pb_graph_pin* sink_pin : sink_pins) { + /* Find the physical pin */ + t_pb_graph_pin* physical_sink_pin = nullptr; + if (true == sink_pin->parent_node->is_root()) { + physical_sink_pin = sink_pin; + } else { + physical_sink_pin = device_annotation.physical_pb_graph_pin(sink_pin); + } + + /* if this is the root node, the physical pin is its self */ + if (nullptr == physical_sink_pin) { + VTR_LOG("Fail to find a physical pin for operating pin '%s'!\n", + sink_pin->to_string().c_str()); + } + VTR_ASSERT(nullptr != physical_sink_pin); + LbRRNodeId sink_lb_rr_node = lb_rr_graph.find_node(LB_INTERMEDIATE, physical_sink_pin); + if (true != lb_rr_graph.valid_node_id(sink_lb_rr_node)) { + VTR_LOG("Try to find the lb_rr_node for pb_graph_pin '%s'\n", + physical_sink_pin->to_string().c_str()); + } + VTR_ASSERT(true == lb_rr_graph.valid_node_id(sink_lb_rr_node)); + sink_nodes.push_back(sink_lb_rr_node); + } + + return sink_nodes; +} + +/*************************************************************************************** + * Create nets to be routed, including the source nodes and terminals + * And add them to the logical block router + ***************************************************************************************/ +static +void add_lb_router_nets(LbRouter& lb_router, + t_logical_block_type_ptr lb_type, + const LbRRGraph& lb_rr_graph, + const AtomContext& atom_ctx, + const VprDeviceAnnotation& device_annotation, + const ClusteringContext& clustering_ctx, + const VprClusteringAnnotation& clustering_annotation, + const ClusterBlockId& block_id, + const bool& verbose) { + size_t net_counter = 0; + + /* Two spots to find source nodes for each nets + * - nets that appear in the inputs of a clustered block + * Note that these nets may be moved to another input of the same cluster block + * we will locate the final pin and consider its corresponding routing resource node as source + * - nets that appear in the outputs of a primitive pb_graph_node + * Note that these primitive pb_graph node are operating pb_graph_node + * while we are considering physical pb_graph node + * Therefore, we will find the outputs of physical pb_graph_node corresponding to the operating one + * and then consider the assoicated routing resource node as source + */ + t_pb* pb = clustering_ctx.clb_nlist.block_pb(block_id); + VTR_ASSERT(true == pb->pb_graph_node->is_root()); + + /* Build the fast look-up between pb_pin_id and pb_graph_pin pointer */ + t_pb_graph_pin** pb_graph_pin_lookup_from_index = alloc_and_load_pb_graph_pin_lookup_from_index(lb_type); + + /* Find the source nodes for the nets mapped to inputs of a clustered block */ + for (int j = 0; j < lb_type->pb_type->num_pins; j++) { + /* Find the net mapped to this pin in clustering results*/ + ClusterNetId cluster_net_id = clustering_ctx.clb_nlist.block_net(block_id, j); + /* Get the actual net id because it may be renamed during routing */ + if (true == clustering_annotation.is_net_renamed(block_id, j)) { + cluster_net_id = clustering_annotation.net(block_id, j); + } + /* Bypass unmapped pins */ + if (ClusterNetId::INVALID() == cluster_net_id) { + continue; + } + + /* Get the source pb_graph pin and find the rr_node in logical block routing resource graph */ + const t_pb_graph_pin* source_pb_pin = get_pb_graph_node_pin_from_block_pin(block_id, j); + VTR_ASSERT(source_pb_pin->parent_node == pb->pb_graph_node); + /* Bypass output pins */ + if (OUT_PORT == source_pb_pin->port->type) { + continue; + } + + /* The outputs of pb_graph_node is INTERMEDIATE node in the routing resource graph, + * they are all connected to a common source node + */ + LbRRNodeId source_lb_rr_node = lb_rr_graph.find_node(LB_INTERMEDIATE, source_pb_pin); + VTR_ASSERT(true == lb_rr_graph.valid_node_id(source_lb_rr_node)); + + AtomNetId atom_net_id = atom_ctx.lookup.atom_net(cluster_net_id); + VTR_ASSERT(AtomNetId::INVALID() != atom_net_id); + + /* Find all the sink pins in the pb_route, we walk through the input pins and find the pin */ + std::vector sink_pb_graph_pins = find_routed_pb_graph_pins_atom_net(pb, atom_net_id, pb_graph_pin_lookup_from_index); + std::vector sink_lb_rr_nodes = find_lb_net_physical_sink_lb_rr_nodes(lb_rr_graph, sink_pb_graph_pins, device_annotation); + VTR_ASSERT(sink_lb_rr_nodes.size() == sink_pb_graph_pins.size()); + + /* Add the net */ + add_lb_router_net_to_route(lb_router, + source_lb_rr_node, sink_lb_rr_nodes, + atom_ctx.nlist, atom_net_id); + net_counter++; + } + + /* Find the source nodes for the nets mapped to outputs of primitive pb_graph_node */ + for (int pin = 0; pin < pb->pb_graph_node->total_pb_pins; ++pin) { + /* Bypass unused pins */ + if ((0 == pb->pb_route.count(pin)) || (AtomNetId::INVALID() == pb->pb_route[pin].atom_net_id)) { + continue; + } + /* Get the driver pb pin id, it must be valid */ + int source_pb_pin_id = pb->pb_route[pin].driver_pb_pin_id; + if (OPEN == source_pb_pin_id) { + continue; + } + VTR_ASSERT(OPEN != source_pb_pin_id && source_pb_pin_id < pb->pb_graph_node->total_pb_pins); + /* Find the corresponding pb_graph_pin and its physical pb_graph_pin */ + t_pb_graph_pin* source_pb_pin = pb_graph_pin_lookup_from_index[source_pb_pin_id]; + /* Skip the pin from top-level pb_graph_node, they have been handled already */ + if (source_pb_pin->parent_node == pb->pb_graph_node) { + continue; + } + + /* The pin must be an output of a primitive pb_graph_node */ + if (OUT_PORT != source_pb_pin->port->type) { + continue; + } + if (true != is_primitive_pb_type(source_pb_pin->parent_node->pb_type)) { + continue; + } + + /* The outputs of pb_graph_node is SOURCE node in the routing resource graph */ + t_pb_graph_pin* physical_source_pb_pin = device_annotation.physical_pb_graph_pin(source_pb_pin); + LbRRNodeId source_lb_rr_node = lb_rr_graph.find_node(LB_SOURCE, physical_source_pb_pin); + VTR_ASSERT(true == lb_rr_graph.valid_node_id(source_lb_rr_node)); + + AtomNetId atom_net_id = pb->pb_route[pin].atom_net_id; + VTR_ASSERT(AtomNetId::INVALID() != atom_net_id); + + /* Find all the sink pins in the pb_route */ + std::vector sink_pb_graph_pins = find_routed_pb_graph_pins_atom_net(pb, atom_net_id, pb_graph_pin_lookup_from_index); + std::vector sink_lb_rr_nodes = find_lb_net_physical_sink_lb_rr_nodes(lb_rr_graph, sink_pb_graph_pins, device_annotation); + VTR_ASSERT(sink_lb_rr_nodes.size() == sink_pb_graph_pins.size()); + + /* Add the net */ + add_lb_router_net_to_route(lb_router, + source_lb_rr_node, sink_lb_rr_nodes, + atom_ctx.nlist, atom_net_id); + net_counter++; + } + + /* Free */ + free_pb_graph_pin_lookup_from_index(pb_graph_pin_lookup_from_index); + + VTR_LOGV(verbose, + "Added %lu nets to be routed.\n", + net_counter); +} + +/*************************************************************************************** + * Repack a clustered block in the physical mode + * This function will do + * - Find the lb_rr_graph that is affiliated to the clustered block + * and initilize the logcial tile router + * - Create nets to be routed, including the source nodes and terminals + * This should consider the net remapping in the clustering_annotation + * - Run the router to finish the repacking + * - Output routing results to data structure PhysicalPb and store it in clustering annotation + ***************************************************************************************/ +static +void repack_cluster(const DeviceContext& device_ctx, + const AtomContext& atom_ctx, + const ClusteringContext& clustering_ctx, + const VprDeviceAnnotation& device_annotation, + VprClusteringAnnotation& clustering_annotation, + const ClusterBlockId& block_id, + const bool& verbose) { + /* Get the pb graph that current clustered block is mapped to */ + t_logical_block_type_ptr lb_type = clustering_ctx.clb_nlist.block_type(block_id); + t_pb_graph_node* pb_graph_head = lb_type->pb_graph_head; + VTR_ASSERT(nullptr != pb_graph_head); + + /* We should get a non-empty graph */ + const LbRRGraph& lb_rr_graph = device_annotation.physical_lb_rr_graph(pb_graph_head); + VTR_ASSERT(!lb_rr_graph.empty()); + + VTR_LOG("Repack clustered block '%s'...", + clustering_ctx.clb_nlist.block_name(block_id).c_str()); + VTR_LOGV(verbose, "\n"); + + /* Initialize the router */ + LbRouter lb_router(lb_rr_graph, lb_type); + + /* Add nets to be routed with source and terminals */ + add_lb_router_nets(lb_router, lb_type, lb_rr_graph, atom_ctx, device_annotation, + clustering_ctx, const_cast(clustering_annotation), + block_id, verbose); + + VTR_LOG("Done\n"); +} + +/*************************************************************************************** + * Repack each clustered blocks in the clustering context + ***************************************************************************************/ +static +void repack_clusters(const DeviceContext& device_ctx, + const AtomContext& atom_ctx, + const ClusteringContext& clustering_ctx, + const VprDeviceAnnotation& device_annotation, + VprClusteringAnnotation& clustering_annotation, + const bool& verbose) { + vtr::ScopedStartFinishTimer timer("Repack clustered blocks to physical implementation of logical tile"); + + for (auto blk_id : clustering_ctx.clb_nlist.blocks()) { + repack_cluster(device_ctx, atom_ctx, clustering_ctx, + device_annotation, clustering_annotation, + blk_id, verbose); + } +} + /*************************************************************************************** * Top-level function to pack physical pb_graph * This function will do : @@ -24,15 +302,21 @@ namespace openfpga { * - store the packing results to clustering annotation ***************************************************************************************/ void pack_physical_pbs(const DeviceContext& device_ctx, + const AtomContext& atom_ctx, + const ClusteringContext& clustering_ctx, VprDeviceAnnotation& device_annotation, VprClusteringAnnotation& clustering_annotation, - const VprRoutingAnnotation& routing_annotation, const bool& verbose) { + /* build the routing resource graph for each logical tile */ build_physical_lb_rr_graphs(device_ctx, device_annotation, verbose); + /* Call the LbRouter to re-pack each clustered block to physical implementation */ + repack_clusters(device_ctx, atom_ctx, clustering_ctx, + const_cast(device_annotation), clustering_annotation, + verbose); } } /* end namespace openfpga */ diff --git a/openfpga/src/repack/repack.h b/openfpga/src/repack/repack.h index 733a7d1ac..8121a58bd 100644 --- a/openfpga/src/repack/repack.h +++ b/openfpga/src/repack/repack.h @@ -17,9 +17,10 @@ namespace openfpga { void pack_physical_pbs(const DeviceContext& device_ctx, + const AtomContext& atom_ctx, + const ClusteringContext& clustering_ctx, VprDeviceAnnotation& device_annotation, VprClusteringAnnotation& clustering_annotation, - const VprRoutingAnnotation& routing_annotation, const bool& verbose); } /* end namespace openfpga */ diff --git a/openfpga/src/utils/pb_type_utils.cpp b/openfpga/src/utils/pb_type_utils.cpp index 9f52c01da..13d7ccadb 100644 --- a/openfpga/src/utils/pb_type_utils.cpp +++ b/openfpga/src/utils/pb_type_utils.cpp @@ -25,9 +25,17 @@ namespace openfpga { ************************************************************************/ bool is_primitive_pb_type(t_pb_type* pb_type) { if (LUT_CLASS == pb_type->class_type) { - /* The first mode of LUT is wire, the second is the regular LUT */ - VTR_ASSERT(std::string("wire") == std::string(pb_type->modes[0].name)); - VTR_ASSERT(std::string(pb_type->name) == std::string(pb_type->modes[1].name)); + /* The only primitive LUT we recognize is the one which have + * a first mode of LUT is wire, the second is the regular LUT + * VPR contructed two modes under a regular LUT, and these children + * are labelled as LUT_CLASS as well. OpenFPGA does not consider + * them as primitive as they are for CAD usage only + */ + if (0 == pb_type->num_modes) { + return false; + } + VTR_ASSERT( (std::string("wire") == std::string(pb_type->modes[0].name)) + && (std::string(pb_type->name) == std::string(pb_type->modes[1].name))); return true; } return 0 == pb_type->num_modes; diff --git a/openfpga/test_script/s298_k6_frac.openfpga b/openfpga/test_script/s298_k6_frac.openfpga index f597475b9..91c5fa551 100644 --- a/openfpga/test_script/s298_k6_frac.openfpga +++ b/openfpga/test_script/s298_k6_frac.openfpga @@ -5,13 +5,13 @@ vpr ./test_vpr_arch/k6_frac_N10_40nm.xml ./test_blif/s298.blif --write_rr_graph read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml # Annotate the OpenFPGA architecture to VPR data base -link_openfpga_arch --verbose +link_openfpga_arch #--verbose # Check and correct any naming conflicts in the BLIF netlist check_netlist_naming_conflict --fix --report ./netlist_renaming.xml # Apply fix-up to clustering nets based on routing results -pb_pin_fixup #--verbose +pb_pin_fixup --verbose # Apply fix-up to Look-Up Table truth tables based on packing results lut_truth_table_fixup #--verbose @@ -19,7 +19,7 @@ lut_truth_table_fixup #--verbose # Build the module graph # - Enabled compression on routing architecture modules # - Enable pin duplication on grid modules -build_fabric --compress_routing --duplicate_grid_pin --verbose +build_fabric --compress_routing --duplicate_grid_pin #--verbose # Repack the netlist to physical pbs # This must be done before bitstream generator and testbench generation From 4abaef14b5abdae9959f0febdce217178d044527 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 20 Feb 2020 20:50:59 -0700 Subject: [PATCH 189/645] bug fixed in pb_pin fix-up. This is due to A CRITICAL BUG IN PHYSICAL_TILE PIN MAPPING!!! --- openfpga/src/base/openfpga_pb_pin_fixup.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/openfpga/src/base/openfpga_pb_pin_fixup.cpp b/openfpga/src/base/openfpga_pb_pin_fixup.cpp index 54850c7c8..901dbaa5f 100644 --- a/openfpga/src/base/openfpga_pb_pin_fixup.cpp +++ b/openfpga/src/base/openfpga_pb_pin_fixup.cpp @@ -60,14 +60,19 @@ void update_cluster_pin_with_post_routing_results(const DeviceContext& device_ct const vtr::Point& grid_coord, const ClusterBlockId& blk_id, const e_side& border_side, + const size_t& z, const bool& verbose) { /* Handle each pin */ auto logical_block = clustering_ctx.clb_nlist.block_type(blk_id); auto physical_tile = pick_best_physical_type(logical_block); for (int j = 0; j < logical_block->pb_type->num_pins; j++) { - /* Get the ptc num for the pin in rr_graph */ - int physical_pin = get_physical_pin(physical_tile, logical_block, j); + /* Get the ptc num for the pin in rr_graph, we need t consider the z offset here + * z offset is the location in the multiple-logic-tile tile + * Get physical pin does not consider THIS!!!! + */ + int physical_pin = z * logical_block->pb_type->num_pins + + get_physical_pin(physical_tile, logical_block, j); auto pin_class = physical_tile->pin_class[physical_pin]; auto class_inf = physical_tile->class_inf[pin_class]; @@ -179,8 +184,10 @@ void update_pb_pin_with_post_routing_results(const DeviceContext& device_ctx, /* We know the entrance to grid info and mapping results, do the fix-up for this block */ vtr::Point grid_coord(x, y); update_cluster_pin_with_post_routing_results(device_ctx, clustering_ctx, - vpr_routing_annotation, vpr_clustering_annotation, + vpr_routing_annotation, + vpr_clustering_annotation, grid_coord, cluster_blk_id, NUM_SIDES, + placement_ctx.block_locs[cluster_blk_id].loc.z, verbose); } } @@ -227,8 +234,10 @@ void update_pb_pin_with_post_routing_results(const DeviceContext& device_ctx, } /* Update on I/O grid */ update_cluster_pin_with_post_routing_results(device_ctx, clustering_ctx, - vpr_routing_annotation, vpr_clustering_annotation, + vpr_routing_annotation, + vpr_clustering_annotation, io_coord, cluster_blk_id, io_side, + placement_ctx.block_locs[cluster_blk_id].loc.z, verbose); } } From 0b0e00b5f426f2ce23bb28b8523b4a8531ee7ee4 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 20 Feb 2020 21:56:15 -0700 Subject: [PATCH 190/645] debugging the LbRouter --- openfpga/src/repack/lb_router.cpp | 37 ++++++++++++++++++- openfpga/src/repack/lb_router_utils.cpp | 41 ++++++++++++++++++--- openfpga/src/repack/lb_router_utils.h | 3 +- openfpga/src/repack/repack.cpp | 18 +++++++-- openfpga/test_vpr_arch/k6_frac_N10_40nm.xml | 3 +- 5 files changed, 88 insertions(+), 14 deletions(-) diff --git a/openfpga/src/repack/lb_router.cpp b/openfpga/src/repack/lb_router.cpp index 9c83be476..9ab0058ab 100644 --- a/openfpga/src/repack/lb_router.cpp +++ b/openfpga/src/repack/lb_router.cpp @@ -8,6 +8,7 @@ #include "pb_type_graph.h" #include "vpr_error.h" +#include "pb_type_utils.h" #include "lb_rr_graph_utils.h" #include "lb_router.h" @@ -148,6 +149,7 @@ LbRouter::NetId LbRouter::create_net_to_route(const LbRRNodeId& source, const st net_terminals.insert(net_terminals.begin(), source); lb_net_terminals_.push_back(net_terminals); + lb_net_rt_trees_.push_back(nullptr); return net; } @@ -260,8 +262,22 @@ bool LbRouter::try_route(const LbRRGraph& lb_rr_graph, is_routed_ = is_route_success(lb_rr_graph); } else { --inet; - VTR_LOGV(verbosity < 3, "Net '%s' is impossible to route within proposed %s cluster\n", + VTR_LOGV(verbosity < 3, + "Net '%s' is impossible to route within proposed %s cluster\n", atom_nlist.net_name(lb_net_atom_net_ids_[NetId(inet)]).c_str(), lb_type_->name); + VTR_LOGV(verbosity < 3, + "\tNet source pin '%s'\n", + lb_rr_graph.node_pb_graph_pin(lb_net_terminals_[NetId(inet)][0])->to_string().c_str()); + VTR_LOGV(verbosity < 3, + "\tNet sink pins:\n"); + for (size_t isink = 1; isink < lb_net_terminals_[NetId(inet)].size(); ++isink) { + VTR_LOGV(verbosity < 3, + "\t\t%s\n", + lb_rr_graph.node_pb_graph_pin(lb_net_terminals_[NetId(inet)][isink])->to_string().c_str()); + } + VTR_LOGV(verbosity < 3, + "Please check your architecture XML to see if it is routable\n"); + is_routed_ = false; } pres_con_fac_ *= params_.pres_fac_mult; @@ -557,7 +573,12 @@ void LbRouter::expand_edges(const LbRRGraph& lb_rr_graph, t_mode* next_mode = routing_status_[enode.node_index].mode; /* Assume first mode if a mode hasn't been forced. */ if (nullptr == next_mode) { - next_mode = &(lb_rr_graph.node_pb_graph_pin(enode.node_index)->parent_node->pb_type->modes[0]); + /* For primitive node, we give nullptr as default */ + if (true == is_primitive_pb_type(lb_rr_graph.node_pb_graph_pin(enode.node_index)->parent_node->pb_type)) { + next_mode = nullptr; + } else { + next_mode = &(lb_rr_graph.node_pb_graph_pin(enode.node_index)->parent_node->pb_type->modes[0]); + } } if (lb_rr_graph.node_out_edges(enode.node_index, next_mode).size() > 1) { fanout_factor = 0.85 + (0.25 / net_fanout); @@ -714,21 +735,33 @@ bool LbRouter::check_net(const LbRRGraph& lb_rr_graph, return false; } if (lb_net_atom_pins_[net].size() != lb_net_terminals_[net].size()) { + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Net '%lu' has unmatched atom pins and terminals.\n", + size_t(net)); return false; } /* We must have 1 source and >1 terminal */ if (2 > lb_net_terminals_[net].size()) { + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Net '%lu' has only %lu terminal.\n", + size_t(net), lb_net_terminals_[net].size()); return false; } /* Each node must be valid */ for (const LbRRNodeId& node : lb_net_terminals_[net]) { if (false == lb_rr_graph.valid_node_id(node)) { + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Net '%lu' has invalid terminal node in lb_rr_graph.\n", + size_t(net)); return false; } } /* Each atom pin must be valid */ for (const AtomPinId& pin : lb_net_atom_pins_[net]) { if (false == atom_nlist.valid_pin_id(pin)) { + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Net '%lu' has invalid atom pin.\n", + size_t(net)); return false; } } diff --git a/openfpga/src/repack/lb_router_utils.cpp b/openfpga/src/repack/lb_router_utils.cpp index 961b61ff7..221f5a640 100644 --- a/openfpga/src/repack/lb_router_utils.cpp +++ b/openfpga/src/repack/lb_router_utils.cpp @@ -16,9 +16,10 @@ namespace openfpga { * based on the given atom net ***************************************************************************************/ LbRouter::NetId add_lb_router_net_to_route(LbRouter& lb_router, + const LbRRGraph& lb_rr_graph, const LbRRNodeId& source_node, const std::vector& sink_nodes, - const AtomNetlist& atom_nlist, + const AtomContext& atom_ctx, const AtomNetId& atom_net_id) { VTR_ASSERT(0 < sink_nodes.size()); @@ -26,13 +27,41 @@ LbRouter::NetId add_lb_router_net_to_route(LbRouter& lb_router, VTR_ASSERT(AtomNetId::INVALID() != atom_net_id); lb_router.add_net_atom_net_id(lb_net, atom_net_id); + std::vector terminal_pins; - for (const AtomPinId& atom_pin : atom_nlist.net_sinks(atom_net_id)) { - VTR_ASSERT(AtomPinId::INVALID() != atom_pin); - terminal_pins.push_back(atom_pin); + AtomPinId atom_pin_outside_pb = AtomPinId::INVALID(); + + for (const LbRRNodeId& sink_node : sink_nodes) { + t_pb_graph_pin* sink_pb_pin = lb_rr_graph.node_pb_graph_pin(sink_node); + bool atom_pin_inside_pb = false; + for (const AtomPinId& atom_pin : atom_ctx.nlist.net_sinks(atom_net_id)) { + VTR_ASSERT(AtomPinId::INVALID() != atom_pin); + if (sink_pb_pin == find_pb_graph_pin(atom_ctx.nlist, atom_ctx.lookup, atom_pin)) { + terminal_pins.push_back(atom_pin); + atom_pin_inside_pb = true; + break; + } + if (AtomPinId::INVALID() == atom_pin_outside_pb) { + atom_pin_outside_pb = atom_pin; + } + } + /* Add a atom pin which is not inside the pb */ + if (false == atom_pin_inside_pb) { + VTR_ASSERT(AtomPinId::INVALID() != atom_pin_outside_pb); + terminal_pins.push_back(atom_pin_outside_pb); + } } - VTR_ASSERT(AtomPinId::INVALID() != atom_nlist.net_driver(atom_net_id)); - lb_router.add_net_atom_pins(lb_net, atom_nlist.net_driver(atom_net_id), terminal_pins); + VTR_ASSERT(AtomPinId::INVALID() != atom_ctx.nlist.net_driver(atom_net_id)); + if (sink_nodes.size() != terminal_pins.size()) { + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Net '%s' has %lu sink nodes while has %lu associated atom pins!\n", + atom_ctx.nlist.net_name(atom_net_id).c_str(), + sink_nodes.size(), + terminal_pins.size()); + } + VTR_ASSERT(sink_nodes.size() == terminal_pins.size()); + + lb_router.add_net_atom_pins(lb_net, atom_ctx.nlist.net_driver(atom_net_id), terminal_pins); return lb_net; } diff --git a/openfpga/src/repack/lb_router_utils.h b/openfpga/src/repack/lb_router_utils.h index bcfc6004b..6a92201eb 100644 --- a/openfpga/src/repack/lb_router_utils.h +++ b/openfpga/src/repack/lb_router_utils.h @@ -16,9 +16,10 @@ namespace openfpga { LbRouter::NetId add_lb_router_net_to_route(LbRouter& lb_router, + const LbRRGraph& lb_rr_graph, const LbRRNodeId& source_node, const std::vector& sink_nodes, - const AtomNetlist& atom_nlist, + const AtomContext& atom_ctx, const AtomNetId& atom_net_id); } /* end namespace openfpga */ diff --git a/openfpga/src/repack/repack.cpp b/openfpga/src/repack/repack.cpp index f69d23e32..eee2c9075 100644 --- a/openfpga/src/repack/repack.cpp +++ b/openfpga/src/repack/repack.cpp @@ -169,9 +169,9 @@ void add_lb_router_nets(LbRouter& lb_router, VTR_ASSERT(sink_lb_rr_nodes.size() == sink_pb_graph_pins.size()); /* Add the net */ - add_lb_router_net_to_route(lb_router, + add_lb_router_net_to_route(lb_router, lb_rr_graph, source_lb_rr_node, sink_lb_rr_nodes, - atom_ctx.nlist, atom_net_id); + atom_ctx, atom_net_id); net_counter++; } @@ -216,9 +216,9 @@ void add_lb_router_nets(LbRouter& lb_router, VTR_ASSERT(sink_lb_rr_nodes.size() == sink_pb_graph_pins.size()); /* Add the net */ - add_lb_router_net_to_route(lb_router, + add_lb_router_net_to_route(lb_router, lb_rr_graph, source_lb_rr_node, sink_lb_rr_nodes, - atom_ctx.nlist, atom_net_id); + atom_ctx, atom_net_id); net_counter++; } @@ -269,6 +269,16 @@ void repack_cluster(const DeviceContext& device_ctx, clustering_ctx, const_cast(clustering_annotation), block_id, verbose); + /* Run the router */ + bool route_success = lb_router.try_route(lb_rr_graph, atom_ctx.nlist, verbose); + + if (true == route_success) { + VTR_LOGV(verbose, "Reroute failed\n"); + exit(1); + } + VTR_ASSERT(true == route_success); + VTR_LOGV(verbose, "Reroute succeed\n"); + VTR_LOG("Done\n"); } diff --git a/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml index a090fd45b..018cf491f 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml @@ -236,7 +236,8 @@ - + + From 1b66e837bafe4cbd62866093e5cac6530b49e342 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 21 Feb 2020 11:29:00 -0700 Subject: [PATCH 191/645] bug fixing for lb router. Add physical mode to default node expanding settings --- openfpga/src/repack/lb_router.cpp | 49 +++++++++++++++++-- openfpga/src/repack/lb_router.h | 12 +++++ openfpga/src/repack/repack.cpp | 5 ++ .../k6_frac_N10_40nm_openfpga.xml | 2 +- openfpga/test_vpr_arch/k6_frac_N10_40nm.xml | 6 ++- 5 files changed, 66 insertions(+), 8 deletions(-) diff --git a/openfpga/src/repack/lb_router.cpp b/openfpga/src/repack/lb_router.cpp index 9ab0058ab..277548e18 100644 --- a/openfpga/src/repack/lb_router.cpp +++ b/openfpga/src/repack/lb_router.cpp @@ -109,7 +109,7 @@ bool LbRouter::route_has_conflict(const LbRRGraph& lb_rr_graph, t_trace* rt) con t_mode* cur_mode = nullptr; for (unsigned int i = 0; i < rt->next_nodes.size(); i++) { std::vector edges = lb_rr_graph.find_edge(rt->current_node, rt->next_nodes[i].current_node); - VTR_ASSERT(0 == edges.size()); + VTR_ASSERT(1 == edges.size()); t_mode* new_mode = lb_rr_graph.edge_mode(edges[0]); if (cur_mode != nullptr && cur_mode != new_mode) { return true; @@ -165,6 +165,34 @@ void LbRouter::add_net_atom_pins(const NetId& net, const AtomPinId& src_pin, con lb_net_atom_pins_[net].insert(lb_net_atom_pins_[net].begin(), src_pin); } +void LbRouter::set_physical_pb_modes(const LbRRGraph& lb_rr_graph, + const VprDeviceAnnotation& device_annotation) { + /* Go through each node in the routing resource graph + * Find the physical mode of each pb_graph_pin that is binded to the node + * For input pins, the physical mode is a mode of its parent pb_type + * For output pins, the physical mode is a mode of the parent pb_type of its parent + */ + for (const LbRRNodeId& node : lb_rr_graph.nodes()) { + t_pb_graph_pin* pb_pin = lb_rr_graph.node_pb_graph_pin(node); + if (nullptr == pb_pin) { + routing_status_[node].mode = nullptr; + } else { + if (IN_PORT == pb_pin->port->type) { + routing_status_[node].mode = device_annotation.physical_mode(pb_pin->parent_node->pb_type); + } else { + VTR_ASSERT(OUT_PORT == pb_pin->port->type); + /* For top-level pb_graph node, the physical mode is nullptr */ + if (true == pb_pin->parent_node->is_root()) { + routing_status_[node].mode = nullptr; + } else { + routing_status_[node].mode = device_annotation.physical_mode(pb_pin->parent_node->parent_pb_graph_node->pb_type); + /* TODO: need to think about how to handle INOUT ports !!! */ + } + } + } + } +} + bool LbRouter::try_route(const LbRRGraph& lb_rr_graph, const AtomNetlist& atom_nlist, const int& verbosity) { @@ -263,8 +291,10 @@ bool LbRouter::try_route(const LbRRGraph& lb_rr_graph, } else { --inet; VTR_LOGV(verbosity < 3, - "Net '%s' is impossible to route within proposed %s cluster\n", - atom_nlist.net_name(lb_net_atom_net_ids_[NetId(inet)]).c_str(), lb_type_->name); + "Net %lu '%s' is impossible to route within proposed %s cluster\n", + inet, + atom_nlist.net_name(lb_net_atom_net_ids_[NetId(inet)]).c_str(), + lb_type_->name); VTR_LOGV(verbosity < 3, "\tNet source pin '%s'\n", lb_rr_graph.node_pb_graph_pin(lb_net_terminals_[NetId(inet)][0])->to_string().c_str()); @@ -573,8 +603,11 @@ void LbRouter::expand_edges(const LbRRGraph& lb_rr_graph, t_mode* next_mode = routing_status_[enode.node_index].mode; /* Assume first mode if a mode hasn't been forced. */ if (nullptr == next_mode) { + /* If the node is mapped to a nullptr pb_graph_pin, this is a special SINK. Use nullptr mode */ + if (nullptr == lb_rr_graph.node_pb_graph_pin(enode.node_index)) { + next_mode = nullptr; + } else if (true == is_primitive_pb_type(lb_rr_graph.node_pb_graph_pin(enode.node_index)->parent_node->pb_type)) { /* For primitive node, we give nullptr as default */ - if (true == is_primitive_pb_type(lb_rr_graph.node_pb_graph_pin(enode.node_index)->parent_node->pb_type)) { next_mode = nullptr; } else { next_mode = &(lb_rr_graph.node_pb_graph_pin(enode.node_index)->parent_node->pb_type->modes[0]); @@ -614,7 +647,13 @@ void LbRouter::expand_node(const LbRRGraph& lb_rr_graph, float cur_cost = exp_node.cost; t_mode* mode = routing_status_[cur_node].mode; if (nullptr == mode) { - mode = &(lb_rr_graph.node_pb_graph_pin(cur_node)->parent_node->pb_type->modes[0]); + if (nullptr == lb_rr_graph.node_pb_graph_pin(cur_node)) { + mode = nullptr; + } else if (true == is_primitive_pb_type(lb_rr_graph.node_pb_graph_pin(cur_node)->parent_node->pb_type)) { + mode = nullptr; + } else { + mode = &(lb_rr_graph.node_pb_graph_pin(cur_node)->parent_node->pb_type->modes[0]); + } } expand_edges(lb_rr_graph, mode, cur_node, cur_cost, net_fanout); diff --git a/openfpga/src/repack/lb_router.h b/openfpga/src/repack/lb_router.h index 0a8bb95fd..bf96a5958 100644 --- a/openfpga/src/repack/lb_router.h +++ b/openfpga/src/repack/lb_router.h @@ -15,6 +15,7 @@ #include "physical_types.h" #include "vpr_context.h" +#include "vpr_device_annotation.h" #include "lb_rr_graph.h" /******************************************************************** @@ -184,6 +185,17 @@ class LbRouter { void add_net_atom_net_id(const NetId& net, const AtomNetId& atom_net); void add_net_atom_pins(const NetId& net, const AtomPinId& src_pin, const std::vector& terminal_pins); + /* TODO: Initialize all the modes in routing status with the mode set in pb + * This is function used for general purpose packing + */ + + /* Set all the modes in routing status with the physical mode defined in device annotation + * This method is used only in repacking for physical logical blocks + * Do NOT use it during the general purpose packing + */ + void set_physical_pb_modes(const LbRRGraph& lb_rr_graph, + const VprDeviceAnnotation& device_annotation); + /** * Perform routing algorithm on a given logical tile routing resource graph * Note: the lb_rr_graph must be the same as you initilized the router!!! diff --git a/openfpga/src/repack/repack.cpp b/openfpga/src/repack/repack.cpp index eee2c9075..02f943b1c 100644 --- a/openfpga/src/repack/repack.cpp +++ b/openfpga/src/repack/repack.cpp @@ -269,6 +269,11 @@ void repack_cluster(const DeviceContext& device_ctx, clustering_ctx, const_cast(clustering_annotation), block_id, verbose); + /* Initialize the modes to expand routing trees with the physical modes in device annotation + * This is a must-do before running the routeri in the purpose of repacking!!! + */ + lb_router.set_physical_pb_modes(lb_rr_graph, device_annotation); + /* Run the router */ bool route_success = lb_router.try_route(lb_rr_graph, atom_ctx.nlist, verbose); diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml index 829deb1a9..2f318cbaa 100644 --- a/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml @@ -226,7 +226,7 @@ - + diff --git a/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml index 018cf491f..97001c497 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml @@ -237,8 +237,9 @@ - - + + + @@ -251,6 +252,7 @@ + From b035b4c87f3317d5be332a86c0f9b148c9c02c13 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 21 Feb 2020 12:16:50 -0700 Subject: [PATCH 192/645] debugged with Lbrouter. Next step is to output routing traces to physical pb data structure --- .../libarchopenfpga/src/pb_type_annotation.cpp | 4 ++-- libopenfpga/libarchopenfpga/src/pb_type_annotation.h | 6 +++--- .../src/read_xml_pb_type_annotation.cpp | 2 +- openfpga/src/annotation/annotate_pb_graph.cpp | 1 + openfpga/src/annotation/vpr_device_annotation.cpp | 12 ++++++------ openfpga/src/annotation/vpr_device_annotation.h | 6 +++--- openfpga/src/repack/repack.cpp | 2 +- openfpga/test_script/s298_k6_frac.openfpga | 5 ++++- 8 files changed, 21 insertions(+), 17 deletions(-) diff --git a/libopenfpga/libarchopenfpga/src/pb_type_annotation.cpp b/libopenfpga/libarchopenfpga/src/pb_type_annotation.cpp index d04e39b72..b38796c9c 100644 --- a/libopenfpga/libarchopenfpga/src/pb_type_annotation.cpp +++ b/libopenfpga/libarchopenfpga/src/pb_type_annotation.cpp @@ -69,7 +69,7 @@ std::string PbTypeAnnotation::circuit_model_name() const { return circuit_model_name_; } -int PbTypeAnnotation::physical_pb_type_index_factor() const { +float PbTypeAnnotation::physical_pb_type_index_factor() const { return physical_pb_type_index_factor_; } @@ -164,7 +164,7 @@ void PbTypeAnnotation::set_circuit_model_name(const std::string& name) { circuit_model_name_ = name; } -void PbTypeAnnotation::set_physical_pb_type_index_factor(const int& value) { +void PbTypeAnnotation::set_physical_pb_type_index_factor(const float& value) { VTR_ASSERT(true == is_operating_pb_type()); physical_pb_type_index_factor_ = value; } diff --git a/libopenfpga/libarchopenfpga/src/pb_type_annotation.h b/libopenfpga/libarchopenfpga/src/pb_type_annotation.h index a969ee93d..e80acc3dc 100644 --- a/libopenfpga/libarchopenfpga/src/pb_type_annotation.h +++ b/libopenfpga/libarchopenfpga/src/pb_type_annotation.h @@ -45,7 +45,7 @@ class PbTypeAnnotation { std::string idle_mode_name() const; std::vector mode_bits() const; std::string circuit_model_name() const; - int physical_pb_type_index_factor() const; + float physical_pb_type_index_factor() const; int physical_pb_type_index_offset() const; std::vector port_names() const; BasicPort physical_pb_type_port(const std::string& port_name) const; @@ -63,7 +63,7 @@ class PbTypeAnnotation { void set_idle_mode_name(const std::string& name); void set_mode_bits(const std::vector& mode_bits); void set_circuit_model_name(const std::string& name); - void set_physical_pb_type_index_factor(const int& value); + void set_physical_pb_type_index_factor(const float& value); void set_physical_pb_type_index_offset(const int& value); void add_pb_type_port_pair(const std::string& operating_pb_port_name, const BasicPort& physical_pb_port); @@ -119,7 +119,7 @@ class PbTypeAnnotation { * operating pb_type adder[5] with a full path clb.fle[arith].adder[5] * to physical pb_type adder[10] with a full path clb.fle[physical].adder[10] */ - int physical_pb_type_index_factor_; + float physical_pb_type_index_factor_; /* The offset aims to align the indices for pb_type between operating and physical modes, * especially when an operating mode contains multiple pb_type (num_pb>1) diff --git a/libopenfpga/libarchopenfpga/src/read_xml_pb_type_annotation.cpp b/libopenfpga/libarchopenfpga/src/read_xml_pb_type_annotation.cpp index a17e970b7..743e23741 100644 --- a/libopenfpga/libarchopenfpga/src/read_xml_pb_type_annotation.cpp +++ b/libopenfpga/libarchopenfpga/src/read_xml_pb_type_annotation.cpp @@ -156,7 +156,7 @@ void read_xml_pb_type_annotation(pugi::xml_node& xml_pb_type, /* If this is an operating pb_type, index factor and offset may be optional needed */ if (true == pb_type_annotation.is_operating_pb_type()) { - pb_type_annotation.set_physical_pb_type_index_factor(get_attribute(xml_pb_type, "physical_pb_type_index_factor", loc_data, pugiutil::ReqOpt::OPTIONAL).as_int(1)); + pb_type_annotation.set_physical_pb_type_index_factor(get_attribute(xml_pb_type, "physical_pb_type_index_factor", loc_data, pugiutil::ReqOpt::OPTIONAL).as_float(1.)); pb_type_annotation.set_physical_pb_type_index_offset(get_attribute(xml_pb_type, "physical_pb_type_index_offset", loc_data, pugiutil::ReqOpt::OPTIONAL).as_int(0)); } diff --git a/openfpga/src/annotation/annotate_pb_graph.cpp b/openfpga/src/annotation/annotate_pb_graph.cpp index bf93cf28e..2b78f2f0e 100644 --- a/openfpga/src/annotation/annotate_pb_graph.cpp +++ b/openfpga/src/annotation/annotate_pb_graph.cpp @@ -405,6 +405,7 @@ void rec_build_vpr_physical_pb_graph_node_annotation(t_pb_graph_node* pb_graph_n * (size_t)vpr_device_annotation.pb_graph_node_unique_index(pb_graph_node) + vpr_device_annotation.physical_pb_type_index_offset(pb_graph_node->pb_type) ); + t_pb_graph_node* physical_pb_graph_node = vpr_device_annotation.pb_graph_node(physical_pb_type, physical_pb_graph_node_id); VTR_ASSERT(nullptr != physical_pb_graph_node); vpr_device_annotation.add_physical_pb_graph_node(pb_graph_node, physical_pb_graph_node); diff --git a/openfpga/src/annotation/vpr_device_annotation.cpp b/openfpga/src/annotation/vpr_device_annotation.cpp index 9be30591d..dfbcde112 100644 --- a/openfpga/src/annotation/vpr_device_annotation.cpp +++ b/openfpga/src/annotation/vpr_device_annotation.cpp @@ -165,12 +165,12 @@ t_pb_graph_node* VprDeviceAnnotation::physical_pb_graph_node(t_pb_graph_node* pb return physical_pb_graph_nodes_.at(pb_graph_node); } -int VprDeviceAnnotation::physical_pb_type_index_factor(t_pb_type* pb_type) const { +float VprDeviceAnnotation::physical_pb_type_index_factor(t_pb_type* pb_type) const { /* Ensure that the pb_type is in the list */ - std::map::const_iterator it = physical_pb_type_index_factors_.find(pb_type); + std::map::const_iterator it = physical_pb_type_index_factors_.find(pb_type); if (it == physical_pb_type_index_factors_.end()) { /* Default value is 1 */ - return 1; + return 1.; } return physical_pb_type_index_factors_.at(pb_type); } @@ -374,11 +374,11 @@ void VprDeviceAnnotation::add_physical_pb_graph_node(t_pb_graph_node* operating_ physical_pb_graph_nodes_[operating_pb_graph_node] = physical_pb_graph_node; } -void VprDeviceAnnotation::add_physical_pb_type_index_factor(t_pb_type* pb_type, const int& factor) { +void VprDeviceAnnotation::add_physical_pb_type_index_factor(t_pb_type* pb_type, const float& factor) { /* Warn any override attempt */ - std::map::const_iterator it = physical_pb_type_index_factors_.find(pb_type); + std::map::const_iterator it = physical_pb_type_index_factors_.find(pb_type); if (it != physical_pb_type_index_factors_.end()) { - VTR_LOG_WARN("Override the annotation between operating pb_type '%s' and it physical pb_type index factor '%d'!\n", + VTR_LOG_WARN("Override the annotation between operating pb_type '%s' and it physical pb_type index factor '%f'!\n", pb_type->name, factor); } diff --git a/openfpga/src/annotation/vpr_device_annotation.h b/openfpga/src/annotation/vpr_device_annotation.h index 0ecff4371..1cfdcd412 100644 --- a/openfpga/src/annotation/vpr_device_annotation.h +++ b/openfpga/src/annotation/vpr_device_annotation.h @@ -57,7 +57,7 @@ class VprDeviceAnnotation { /* Get the pointer to a pb_graph node using an unique index */ t_pb_graph_node* pb_graph_node(t_pb_type* pb_type, const PbGraphNodeId& unique_index) const; t_pb_graph_node* physical_pb_graph_node(t_pb_graph_node* pb_graph_node) const; - int physical_pb_type_index_factor(t_pb_type* pb_type) const; + float physical_pb_type_index_factor(t_pb_type* pb_type) const; int physical_pb_type_index_offset(t_pb_type* pb_type) const; int physical_pb_pin_rotate_offset(t_port* pb_port) const; @@ -88,7 +88,7 @@ class VprDeviceAnnotation { void add_pb_graph_node_unique_index(t_pb_graph_node* pb_graph_node); void add_physical_pb_graph_node(t_pb_graph_node* operating_pb_graph_node, t_pb_graph_node* physical_pb_graph_node); - void add_physical_pb_type_index_factor(t_pb_type* pb_type, const int& factor); + void add_physical_pb_type_index_factor(t_pb_type* pb_type, const float& factor); void add_physical_pb_type_index_offset(t_pb_type* pb_type, const int& offset); void add_physical_pb_pin_rotate_offset(t_port* pb_port, const int& offset); void add_physical_pb_graph_pin(t_pb_graph_pin* operating_pb_graph_pin, t_pb_graph_pin* physical_pb_graph_pin); @@ -99,7 +99,7 @@ class VprDeviceAnnotation { private: /* Internal data */ /* Pair a regular pb_type to its physical pb_type */ std::map physical_pb_types_; - std::map physical_pb_type_index_factors_; + std::map physical_pb_type_index_factors_; std::map physical_pb_type_index_offsets_; /* Pair a physical mode for a pb_type diff --git a/openfpga/src/repack/repack.cpp b/openfpga/src/repack/repack.cpp index 02f943b1c..e554a63ed 100644 --- a/openfpga/src/repack/repack.cpp +++ b/openfpga/src/repack/repack.cpp @@ -277,7 +277,7 @@ void repack_cluster(const DeviceContext& device_ctx, /* Run the router */ bool route_success = lb_router.try_route(lb_rr_graph, atom_ctx.nlist, verbose); - if (true == route_success) { + if (false == route_success) { VTR_LOGV(verbose, "Reroute failed\n"); exit(1); } diff --git a/openfpga/test_script/s298_k6_frac.openfpga b/openfpga/test_script/s298_k6_frac.openfpga index 91c5fa551..a985511c4 100644 --- a/openfpga/test_script/s298_k6_frac.openfpga +++ b/openfpga/test_script/s298_k6_frac.openfpga @@ -4,8 +4,11 @@ vpr ./test_vpr_arch/k6_frac_N10_40nm.xml ./test_blif/s298.blif --write_rr_graph # Read OpenFPGA architecture definition read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml +# Write out the architecture XML as a proof +#write_openfpga_arch -f ./arch_echo.xml + # Annotate the OpenFPGA architecture to VPR data base -link_openfpga_arch #--verbose +link_openfpga_arch --verbose # Check and correct any naming conflicts in the BLIF netlist check_netlist_naming_conflict --fix --report ./netlist_renaming.xml From 12f2888c7c063038ee06d34dee12cda0e219c187 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 21 Feb 2020 17:47:27 -0700 Subject: [PATCH 193/645] add physical pb data structure and basic allocator --- .../annotation/vpr_clustering_annotation.cpp | 20 +++ .../annotation/vpr_clustering_annotation.h | 7 ++ openfpga/src/repack/physical_pb.cpp | 96 ++++++++++++++ openfpga/src/repack/physical_pb.h | 71 +++++++++++ openfpga/src/repack/physical_pb_fwd.h | 23 ++++ openfpga/src/repack/repack.cpp | 9 ++ openfpga/src/utils/physical_pb_utils.cpp | 119 ++++++++++++++++++ openfpga/src/utils/physical_pb_utils.h | 31 +++++ 8 files changed, 376 insertions(+) create mode 100644 openfpga/src/repack/physical_pb.cpp create mode 100644 openfpga/src/repack/physical_pb.h create mode 100644 openfpga/src/repack/physical_pb_fwd.h create mode 100644 openfpga/src/utils/physical_pb_utils.cpp create mode 100644 openfpga/src/utils/physical_pb_utils.h diff --git a/openfpga/src/annotation/vpr_clustering_annotation.cpp b/openfpga/src/annotation/vpr_clustering_annotation.cpp index cd32fdeac..e0c7ab575 100644 --- a/openfpga/src/annotation/vpr_clustering_annotation.cpp +++ b/openfpga/src/annotation/vpr_clustering_annotation.cpp @@ -41,6 +41,14 @@ AtomNetlist::TruthTable VprClusteringAnnotation::truth_table(t_pb* pb) const { return block_truth_tables_.at(pb); } +PhysicalPb VprClusteringAnnotation::physical_pb(const ClusterBlockId& block_id) const { + if (physical_pbs_.end() == physical_pbs_.find(block_id)) { + return PhysicalPb(); + } + + return physical_pbs_.at(block_id); +} + /************************************************************************ * Public mutators ***********************************************************************/ @@ -67,4 +75,16 @@ void VprClusteringAnnotation::adapt_truth_table(t_pb* pb, block_truth_tables_[pb] = tt; } +void VprClusteringAnnotation::add_physical_pb(const ClusterBlockId& block_id, + const PhysicalPb& physical_pb) { + /* Warn any override attempt */ + if (physical_pbs_.end() != physical_pbs_.find(block_id)) { + VTR_LOG_WARN("Override the physical pb for clustered block %lu in clustering context annotation!\n", + size_t(block_id)); + } + + physical_pbs_[block_id] = physical_pb; +} + + } /* End namespace openfpga*/ diff --git a/openfpga/src/annotation/vpr_clustering_annotation.h b/openfpga/src/annotation/vpr_clustering_annotation.h index 2951ef226..b72173e1d 100644 --- a/openfpga/src/annotation/vpr_clustering_annotation.h +++ b/openfpga/src/annotation/vpr_clustering_annotation.h @@ -9,6 +9,8 @@ /* Header from vpr library */ #include "clustered_netlist.h" +#include "physical_pb.h" + /* Begin namespace openfpga */ namespace openfpga { @@ -33,14 +35,19 @@ class VprClusteringAnnotation { ClusterNetId net(const ClusterBlockId& block_id, const int& pin_index) const; bool is_truth_table_adapted(t_pb* pb) const; AtomNetlist::TruthTable truth_table(t_pb* pb) const; + PhysicalPb physical_pb(const ClusterBlockId& block_id) const; public: /* Public mutators */ void rename_net(const ClusterBlockId& block_id, const int& pin_index, const ClusterNetId& net_id); void adapt_truth_table(t_pb* pb, const AtomNetlist::TruthTable& tt); + void add_physical_pb(const ClusterBlockId& block_id, const PhysicalPb& physical_pb); private: /* Internal data */ /* Pair a regular pb_type to its physical pb_type */ std::map> net_names_; std::map block_truth_tables_; + + /* Link clustered blocks to physical pb (mapping results) */ + std::map physical_pbs_; }; } /* End namespace openfpga*/ diff --git a/openfpga/src/repack/physical_pb.cpp b/openfpga/src/repack/physical_pb.cpp new file mode 100644 index 000000000..1b3760dcc --- /dev/null +++ b/openfpga/src/repack/physical_pb.cpp @@ -0,0 +1,96 @@ +/****************************************************************************** + * Memember functions for data structure PhysicalPb + ******************************************************************************/ +#include "vtr_assert.h" +#include "vtr_log.h" + +#include "physical_pb.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/************************************************** + * Public Accessors + *************************************************/ +PhysicalPb::physical_pb_range PhysicalPb::pbs() const { + return vtr::make_range(pb_ids_.begin(), pb_ids_.end()); +} + +std::string PhysicalPb::name(const PhysicalPbId& pb) const { + VTR_ASSERT(true == valid_pb_id(pb)); + return names_[pb]; +} + +/* Find the module id by a given name, return invalid if not found */ +PhysicalPbId PhysicalPb::find_pb(const t_pb_graph_node* pb_graph_node) const { + if (type2id_map_.find(pb_graph_node) != type2id_map_.end()) { + /* Find it, return the id */ + return type2id_map_.at(pb_graph_node); + } + /* Not found, return an invalid id */ + return PhysicalPbId::INVALID(); +} + +PhysicalPbId PhysicalPb::parent(const PhysicalPbId& pb) const { + VTR_ASSERT(true == valid_pb_id(pb)); + return parent_pbs_[pb]; +} + +/****************************************************************************** + * Private Mutators + ******************************************************************************/ +PhysicalPbId PhysicalPb::create_pb(const t_pb_graph_node* pb_graph_node) { + /* Find if the name has been used. If used, return an invalid Id and report error! */ + std::map::iterator it = type2id_map_.find(pb_graph_node); + if (it != type2id_map_.end()) { + return PhysicalPbId::INVALID(); + } + + /* Create an new id */ + PhysicalPbId pb = PhysicalPbId(pb_ids_.size()); + pb_ids_.push_back(pb); + + /* Allocate other attributes */ + names_.emplace_back(); + pb_graph_nodes_.push_back(pb_graph_node); + mapped_atoms_.emplace_back(); + child_pbs_.emplace_back(); + parent_pbs_.emplace_back(); + mode_bits_.emplace_back(); + + /* Register in the name2id map */ + type2id_map_[pb_graph_node] = pb; + + return pb; +} + +void PhysicalPb::add_child(const PhysicalPbId& parent, + const PhysicalPbId& child, + const t_pb_type* child_type) { + VTR_ASSERT(true == valid_pb_id(parent)); + VTR_ASSERT(true == valid_pb_id(child)); + + child_pbs_[parent][child_type].push_back(child); + + if (PhysicalPbId::INVALID() != parent_pbs_[child]) { + VTR_LOGF_WARN(__FILE__, __LINE__, + "Overwrite parent '%s' for physical pb '%s' with a new one '%s'!\n", + pb_graph_nodes_[parent_pbs_[child]]->hierarchical_type_name().c_str(), + pb_graph_nodes_[child]->hierarchical_type_name().c_str(), + pb_graph_nodes_[parent]->hierarchical_type_name().c_str()); + } + parent_pbs_[child] = parent; +} + +/****************************************************************************** + * Private validators/invalidators + ******************************************************************************/ +bool PhysicalPb::valid_pb_id(const PhysicalPbId& pb_id) const { + return ( size_t(pb_id) < pb_ids_.size() ) && ( pb_id == pb_ids_[pb_id] ); +} + +bool PhysicalPb::empty() const { + return 0 == pb_ids_.size(); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/repack/physical_pb.h b/openfpga/src/repack/physical_pb.h new file mode 100644 index 000000000..8a9d3b20d --- /dev/null +++ b/openfpga/src/repack/physical_pb.h @@ -0,0 +1,71 @@ +#ifndef PHYSICAL_PB_H +#define PHYSICAL_PB_H + +/******************************************************************** + * Include header files required by the data structure definition + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_geometry.h" +#include "vtr_vector.h" + +/* Headers from readarch library */ +#include "physical_types.h" + +/* Headers from vpr library */ +#include "atom_netlist_fwd.h" + +#include "physical_pb_fwd.h" + +/* Begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * PhysicalPb object aims to store the mapped result for a programmable + * logical block like the VPR data structure t_pb does. + * Differently, it is tailored for the physical implementation of a + * programmable block. + * - It does not contain multi-mode for each child physical_pb while + * VPR t_pb does have multi-mode. This is because that the hardware + * implementation is unique + * - It contains mode-selection bits for each primitive physical_pb + * This is used to help bitstream generator to configure a primitive + * circuit in the correct mode + * - A primitive LUT can be mapped to various truth tables. + * This is true for any fracturable LUTs. + *******************************************************************/ +class PhysicalPb { + public: /* Types and ranges */ + typedef vtr::vector::const_iterator physical_pb_iterator; + typedef vtr::Range physical_pb_range; + public: /* Public aggregators */ + physical_pb_range pbs() const; + std::string name(const PhysicalPbId& pb) const; + PhysicalPbId find_pb(const t_pb_graph_node* name) const; + PhysicalPbId parent(const PhysicalPbId& pb) const; + public: /* Public mutators */ + PhysicalPbId create_pb(const t_pb_graph_node* pb_graph_node); + void add_child(const PhysicalPbId& parent, + const PhysicalPbId& child, + const t_pb_type* child_type); + public: /* Public validators/invalidators */ + bool valid_pb_id(const PhysicalPbId& pb_id) const; + bool empty() const; + private: /* Internal Data */ + vtr::vector pb_ids_; + vtr::vector pb_graph_nodes_; + vtr::vector names_; + vtr::vector> mapped_atoms_; + + /* Child pbs are organized as [0..num_child_pb_types-1][0..child_pb_type->num_pb-1] */ + vtr::vector>> child_pbs_; + vtr::vector parent_pbs_; + + vtr::vector> mode_bits_; + + /* Fast lookup */ + std::map type2id_map_; +}; + +} /* End namespace openfpga*/ + +#endif diff --git a/openfpga/src/repack/physical_pb_fwd.h b/openfpga/src/repack/physical_pb_fwd.h new file mode 100644 index 000000000..d601ab060 --- /dev/null +++ b/openfpga/src/repack/physical_pb_fwd.h @@ -0,0 +1,23 @@ +/************************************************** + * This file includes only declarations for + * the data structures for PhysicalPb + * Please refer to physical_pb.h for more details + *************************************************/ +#ifndef PHYSICAL_PB_FWD_H +#define PHYSICAL_PB_FWD_H + +#include "vtr_strong_id.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/* Strong Ids for ModuleManager */ +struct physical_pb_id_tag; + +typedef vtr::StrongId PhysicalPbId; + +class PhysicalPb; + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/repack/repack.cpp b/openfpga/src/repack/repack.cpp index e554a63ed..578efd91f 100644 --- a/openfpga/src/repack/repack.cpp +++ b/openfpga/src/repack/repack.cpp @@ -14,6 +14,7 @@ #include "build_physical_lb_rr_graph.h" #include "lb_router.h" #include "lb_router_utils.h" +#include "physical_pb_utils.h" #include "repack.h" /* begin namespace openfpga */ @@ -284,6 +285,14 @@ void repack_cluster(const DeviceContext& device_ctx, VTR_ASSERT(true == route_success); VTR_LOGV(verbose, "Reroute succeed\n"); + /* Annotate routing results to physical pb */ + PhysicalPb phy_pb; + alloc_physical_pb_from_pb_graph(phy_pb, pb_graph_head, device_annotation); + rec_update_physical_pb_from_operating_pb(phy_pb, clustering_ctx.clb_nlist.block_pb(block_id)); + + /* Add the pb to clustering context */ + clustering_annotation.add_physical_pb(block_id, phy_pb); + VTR_LOG("Done\n"); } diff --git a/openfpga/src/utils/physical_pb_utils.cpp b/openfpga/src/utils/physical_pb_utils.cpp new file mode 100644 index 000000000..9502df674 --- /dev/null +++ b/openfpga/src/utils/physical_pb_utils.cpp @@ -0,0 +1,119 @@ +/************************************************************************ + * Function to perform fundamental operation for the physical pb using + * data structures + ***********************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" + +#include "openfpga_naming.h" +#include "pb_type_utils.h" +#include "physical_pb_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/************************************************************************ + * Allocate an empty physical pb graph based on pb_graph + * This function should start with an empty physical pb object!!! + * Suggest to check this before executing this function + * VTR_ASSERT(true == phy_pb.empty()); + ***********************************************************************/ +static +void rec_alloc_physical_pb_from_pb_graph(PhysicalPb& phy_pb, + t_pb_graph_node* pb_graph_node, + const VprDeviceAnnotation& device_annotation) { + t_pb_type* pb_type = pb_graph_node->pb_type; + + t_mode* physical_mode = device_annotation.physical_mode(pb_type); + + PhysicalPbId cur_phy_pb_id = phy_pb.create_pb(pb_graph_node); + VTR_ASSERT(true == phy_pb.valid_pb_id(cur_phy_pb_id)); + + /* Finish for primitive node */ + if (true == is_primitive_pb_type(pb_type)) { + return; + } + + /* Find the physical mode */ + VTR_ASSERT(nullptr != physical_mode); + + /* Go to the leaf nodes first. This aims to build all the primitive nodes first + * and then we build the parents and create links + */ + for (int ipb = 0; ipb < physical_mode->num_pb_type_children; ++ipb) { + for (int jpb = 0; jpb < physical_mode->pb_type_children[ipb].num_pb; ++jpb) { + rec_alloc_physical_pb_from_pb_graph(phy_pb, + &(pb_graph_node->child_pb_graph_nodes[physical_mode->index][ipb][jpb]), + device_annotation); + } + } +} + +/************************************************************************ + * Build all the relationships between parent and children + * inside a physical pb graph + * This function must be executed after rec_alloc_physical_pb_from_pb_graph()!!! + ***********************************************************************/ +static +void rec_build_physical_pb_children_from_pb_graph(PhysicalPb& phy_pb, + t_pb_graph_node* pb_graph_node, + const VprDeviceAnnotation& device_annotation) { + t_pb_type* pb_type = pb_graph_node->pb_type; + + /* Finish for primitive node */ + if (true == is_primitive_pb_type(pb_type)) { + return; + } + + t_mode* physical_mode = device_annotation.physical_mode(pb_type); + VTR_ASSERT(nullptr != physical_mode); + + /* Please use the openfpga naming function so that you can build the link to module manager */ + PhysicalPbId parent_pb_id = phy_pb.find_pb(pb_graph_node); + VTR_ASSERT(true == phy_pb.valid_pb_id(parent_pb_id)); + + /* Add all the children */ + for (int ipb = 0; ipb < physical_mode->num_pb_type_children; ++ipb) { + for (int jpb = 0; jpb < physical_mode->pb_type_children[ipb].num_pb; ++jpb) { + PhysicalPbId child_pb_id = phy_pb.find_pb(&(pb_graph_node->child_pb_graph_nodes[physical_mode->index][ipb][jpb])); + VTR_ASSERT(true == phy_pb.valid_pb_id(child_pb_id)); + phy_pb.add_child(parent_pb_id, child_pb_id, &(physical_mode->pb_type_children[ipb])); + } + } + + /* Go to the leaf nodes first. This aims to build all the primitive nodes first + * and then we build the parents and create links + */ + for (int ipb = 0; ipb < physical_mode->num_pb_type_children; ++ipb) { + for (int jpb = 0; jpb < physical_mode->pb_type_children[ipb].num_pb; ++jpb) { + rec_build_physical_pb_children_from_pb_graph(phy_pb, + &(pb_graph_node->child_pb_graph_nodes[physical_mode->index][ipb][jpb]), + device_annotation); + } + } +} + +/************************************************************************ + * Allocate an empty physical pb graph based on pb_graph + * This function should start with an empty physical pb object!!! + * Suggest to check this before executing this function + * VTR_ASSERT(true == phy_pb.empty()); + ***********************************************************************/ +void alloc_physical_pb_from_pb_graph(PhysicalPb& phy_pb, + t_pb_graph_node* pb_graph_head, + const VprDeviceAnnotation& device_annotation) { + VTR_ASSERT(true == phy_pb.empty()); + + rec_alloc_physical_pb_from_pb_graph(phy_pb, pb_graph_head, device_annotation); + rec_build_physical_pb_children_from_pb_graph(phy_pb, pb_graph_head, device_annotation); +} + +/************************************************************************ + * Synchronize mapping results from an operating pb to a physical pb + ***********************************************************************/ +void rec_update_physical_pb_from_operating_pb(PhysicalPb& phy_pb, + t_pb* op_pb) { +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/utils/physical_pb_utils.h b/openfpga/src/utils/physical_pb_utils.h new file mode 100644 index 000000000..bdd37dc81 --- /dev/null +++ b/openfpga/src/utils/physical_pb_utils.h @@ -0,0 +1,31 @@ +/******************************************************************** + * Header file for circuit_library_utils.cpp + *******************************************************************/ +#ifndef PHYSICAL_PB_UTILS_H +#define PHYSICAL_PB_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include "physical_types.h" +#include "vpr_device_annotation.h" +#include "physical_pb.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void alloc_physical_pb_from_pb_graph(PhysicalPb& phy_pb, + t_pb_graph_node* pb_graph_head, + const VprDeviceAnnotation& device_annotation); + +void rec_update_physical_pb_from_operating_pb(PhysicalPb& phy_pb, + t_pb* op_pb); + +} /* end namespace openfpga */ + +#endif From 926e429374632096b76c7e04265c951ccfeab007 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 21 Feb 2020 20:39:49 -0700 Subject: [PATCH 194/645] add save repacking results in physical pb --- openfpga/src/repack/physical_pb.cpp | 44 ++++++- openfpga/src/repack/physical_pb.h | 12 +- openfpga/src/repack/repack.cpp | 18 ++- openfpga/src/utils/physical_pb_utils.cpp | 161 ++++++++++++++++++++++- openfpga/src/utils/physical_pb_utils.h | 8 +- 5 files changed, 228 insertions(+), 15 deletions(-) diff --git a/openfpga/src/repack/physical_pb.cpp b/openfpga/src/repack/physical_pb.cpp index 1b3760dcc..647c527af 100644 --- a/openfpga/src/repack/physical_pb.cpp +++ b/openfpga/src/repack/physical_pb.cpp @@ -36,6 +36,17 @@ PhysicalPbId PhysicalPb::parent(const PhysicalPbId& pb) const { return parent_pbs_[pb]; } +AtomNetId PhysicalPb::pb_graph_pin_atom_net(const PhysicalPbId& pb, + const t_pb_graph_pin* pb_graph_pin) const { + VTR_ASSERT(true == valid_pb_id(pb)); + if (pin_atom_nets_[pb].find(pb_graph_pin) != pin_atom_nets_[pb].end()) { + /* Find it, return the id */ + return pin_atom_nets_[pb].at(pb_graph_pin); + } + /* Not found, return an invalid id */ + return AtomNetId::INVALID(); +} + /****************************************************************************** * Private Mutators ******************************************************************************/ @@ -53,9 +64,12 @@ PhysicalPbId PhysicalPb::create_pb(const t_pb_graph_node* pb_graph_node) { /* Allocate other attributes */ names_.emplace_back(); pb_graph_nodes_.push_back(pb_graph_node); - mapped_atoms_.emplace_back(); + atom_blocks_.emplace_back(); + pin_atom_nets_.emplace_back(); + child_pbs_.emplace_back(); parent_pbs_.emplace_back(); + mode_bits_.emplace_back(); /* Register in the name2id map */ @@ -82,6 +96,34 @@ void PhysicalPb::add_child(const PhysicalPbId& parent, parent_pbs_[child] = parent; } +void PhysicalPb::set_mode_bits(const PhysicalPbId& pb, + const std::vector& mode_bits) { + VTR_ASSERT(true == valid_pb_id(pb)); + + mode_bits_[pb] = mode_bits; +} + +void PhysicalPb::add_atom_block(const PhysicalPbId& pb, + const AtomBlockId& atom_block) { + VTR_ASSERT(true == valid_pb_id(pb)); + + atom_blocks_[pb].push_back(atom_block); +} + +void PhysicalPb::set_pb_graph_pin_atom_net(const PhysicalPbId& pb, + const t_pb_graph_pin* pb_graph_pin, + const AtomNetId& atom_net) { + VTR_ASSERT(true == valid_pb_id(pb)); + if (pin_atom_nets_[pb].end() != pin_atom_nets_[pb].find(pb_graph_pin)) { + VTR_LOG_WARN("Overwrite pb_graph_pin '%s[%d]' atom net '%lu' with '%lu'\n", + pb_graph_pin->port->name, pb_graph_pin->pin_number, + size_t(pin_atom_nets_[pb][pb_graph_pin]), + size_t(atom_net)); + } + + pin_atom_nets_[pb][pb_graph_pin] = atom_net; +} + /****************************************************************************** * Private validators/invalidators ******************************************************************************/ diff --git a/openfpga/src/repack/physical_pb.h b/openfpga/src/repack/physical_pb.h index 8a9d3b20d..9e4dff206 100644 --- a/openfpga/src/repack/physical_pb.h +++ b/openfpga/src/repack/physical_pb.h @@ -42,11 +42,20 @@ class PhysicalPb { std::string name(const PhysicalPbId& pb) const; PhysicalPbId find_pb(const t_pb_graph_node* name) const; PhysicalPbId parent(const PhysicalPbId& pb) const; + AtomNetId pb_graph_pin_atom_net(const PhysicalPbId& pb, + const t_pb_graph_pin* pb_graph_pin) const; public: /* Public mutators */ PhysicalPbId create_pb(const t_pb_graph_node* pb_graph_node); void add_child(const PhysicalPbId& parent, const PhysicalPbId& child, const t_pb_type* child_type); + void add_atom_block(const PhysicalPbId& pb, + const AtomBlockId& atom_block); + void set_mode_bits(const PhysicalPbId& pb, + const std::vector& mode_bits); + void set_pb_graph_pin_atom_net(const PhysicalPbId& pb, + const t_pb_graph_pin* pb_graph_pin, + const AtomNetId& atom_net); public: /* Public validators/invalidators */ bool valid_pb_id(const PhysicalPbId& pb_id) const; bool empty() const; @@ -54,7 +63,8 @@ class PhysicalPb { vtr::vector pb_ids_; vtr::vector pb_graph_nodes_; vtr::vector names_; - vtr::vector> mapped_atoms_; + vtr::vector> atom_blocks_; + vtr::vector> pin_atom_nets_; /* Child pbs are organized as [0..num_child_pb_types-1][0..child_pb_type->num_pb-1] */ vtr::vector>> child_pbs_; diff --git a/openfpga/src/repack/repack.cpp b/openfpga/src/repack/repack.cpp index 578efd91f..e4df2986d 100644 --- a/openfpga/src/repack/repack.cpp +++ b/openfpga/src/repack/repack.cpp @@ -242,8 +242,7 @@ void add_lb_router_nets(LbRouter& lb_router, * - Output routing results to data structure PhysicalPb and store it in clustering annotation ***************************************************************************************/ static -void repack_cluster(const DeviceContext& device_ctx, - const AtomContext& atom_ctx, +void repack_cluster(const AtomContext& atom_ctx, const ClusteringContext& clustering_ctx, const VprDeviceAnnotation& device_annotation, VprClusteringAnnotation& clustering_annotation, @@ -288,7 +287,13 @@ void repack_cluster(const DeviceContext& device_ctx, /* Annotate routing results to physical pb */ PhysicalPb phy_pb; alloc_physical_pb_from_pb_graph(phy_pb, pb_graph_head, device_annotation); - rec_update_physical_pb_from_operating_pb(phy_pb, clustering_ctx.clb_nlist.block_pb(block_id)); + rec_update_physical_pb_from_operating_pb(phy_pb, + clustering_ctx.clb_nlist.block_pb(block_id), + clustering_ctx.clb_nlist.block_pb(block_id)->pb_route, + atom_ctx, + device_annotation); + /* TODO: save routing results */ + VTR_LOGV(verbose, "Saved results in physical pb\n"); /* Add the pb to clustering context */ clustering_annotation.add_physical_pb(block_id, phy_pb); @@ -300,8 +305,7 @@ void repack_cluster(const DeviceContext& device_ctx, * Repack each clustered blocks in the clustering context ***************************************************************************************/ static -void repack_clusters(const DeviceContext& device_ctx, - const AtomContext& atom_ctx, +void repack_clusters(const AtomContext& atom_ctx, const ClusteringContext& clustering_ctx, const VprDeviceAnnotation& device_annotation, VprClusteringAnnotation& clustering_annotation, @@ -309,7 +313,7 @@ void repack_clusters(const DeviceContext& device_ctx, vtr::ScopedStartFinishTimer timer("Repack clustered blocks to physical implementation of logical tile"); for (auto blk_id : clustering_ctx.clb_nlist.blocks()) { - repack_cluster(device_ctx, atom_ctx, clustering_ctx, + repack_cluster(atom_ctx, clustering_ctx, device_annotation, clustering_annotation, blk_id, verbose); } @@ -338,7 +342,7 @@ void pack_physical_pbs(const DeviceContext& device_ctx, verbose); /* Call the LbRouter to re-pack each clustered block to physical implementation */ - repack_clusters(device_ctx, atom_ctx, clustering_ctx, + repack_clusters(atom_ctx, clustering_ctx, const_cast(device_annotation), clustering_annotation, verbose); } diff --git a/openfpga/src/utils/physical_pb_utils.cpp b/openfpga/src/utils/physical_pb_utils.cpp index 9502df674..ec668a7d3 100644 --- a/openfpga/src/utils/physical_pb_utils.cpp +++ b/openfpga/src/utils/physical_pb_utils.cpp @@ -21,7 +21,7 @@ namespace openfpga { ***********************************************************************/ static void rec_alloc_physical_pb_from_pb_graph(PhysicalPb& phy_pb, - t_pb_graph_node* pb_graph_node, + const t_pb_graph_node* pb_graph_node, const VprDeviceAnnotation& device_annotation) { t_pb_type* pb_type = pb_graph_node->pb_type; @@ -57,7 +57,7 @@ void rec_alloc_physical_pb_from_pb_graph(PhysicalPb& phy_pb, ***********************************************************************/ static void rec_build_physical_pb_children_from_pb_graph(PhysicalPb& phy_pb, - t_pb_graph_node* pb_graph_node, + const t_pb_graph_node* pb_graph_node, const VprDeviceAnnotation& device_annotation) { t_pb_type* pb_type = pb_graph_node->pb_type; @@ -101,7 +101,7 @@ void rec_build_physical_pb_children_from_pb_graph(PhysicalPb& phy_pb, * VTR_ASSERT(true == phy_pb.empty()); ***********************************************************************/ void alloc_physical_pb_from_pb_graph(PhysicalPb& phy_pb, - t_pb_graph_node* pb_graph_head, + const t_pb_graph_node* pb_graph_head, const VprDeviceAnnotation& device_annotation) { VTR_ASSERT(true == phy_pb.empty()); @@ -109,11 +109,164 @@ void alloc_physical_pb_from_pb_graph(PhysicalPb& phy_pb, rec_build_physical_pb_children_from_pb_graph(phy_pb, pb_graph_head, device_annotation); } +/************************************************************************ + * Update a mapping net from a pin of an operating primitive pb to a + * physical pb data base + ***********************************************************************/ +static +void update_primitive_physical_pb_pin_atom_net(PhysicalPb& phy_pb, + const PhysicalPbId& primitive_pb, + const t_pb_graph_pin* pb_graph_pin, + const t_pb_routes& pb_route, + const VprDeviceAnnotation& device_annotation) { + int node_index = pb_graph_pin->pin_count_in_cluster; + if (pb_route.count(node_index)) { + /* The pin is mapped to a net, find the original pin in the atom netlist */ + AtomNetId atom_net = pb_route[node_index].atom_net_id; + + VTR_ASSERT(atom_net); + + /* Find the physical pb_graph_pin */ + t_pb_graph_pin* physical_pb_graph_pin = device_annotation.physical_pb_graph_pin((t_pb_graph_pin*)pb_graph_pin); + VTR_ASSERT(nullptr != physical_pb_graph_pin); + + /* Check if the pin has been mapped to a net. + * If yes, the atom net must be the same + */ + if (AtomNetId::INVALID() == phy_pb.pb_graph_pin_atom_net(primitive_pb, physical_pb_graph_pin)) { + phy_pb.set_pb_graph_pin_atom_net(primitive_pb, physical_pb_graph_pin, atom_net); + } else { + VTR_ASSERT(atom_net == phy_pb.pb_graph_pin_atom_net(primitive_pb, physical_pb_graph_pin)); + } + } +} + +/************************************************************************ + * Synchronize mapping nets from an operating primitive pb to a physical pb + ***********************************************************************/ +static +void synchronize_primitive_physical_pb_atom_nets(PhysicalPb& phy_pb, + const PhysicalPbId& primitive_pb, + const t_pb_graph_node* pb_graph_node, + const t_pb_routes& pb_route, + const AtomContext& atom_ctx, + const AtomBlockId& atom_blk, + const VprDeviceAnnotation& device_annotation) { + /* Iterate over all the ports: input, output and clock */ + for (int iport = 0; iport < pb_graph_node->num_input_ports; ++iport) { + for (int ipin = 0; ipin < pb_graph_node->num_input_pins[iport]; ++ipin) { + /* Port exists (some LUTs may have no input and hence no port in the atom netlist) */ + t_model_ports* model_port = pb_graph_node->input_pins[iport][ipin].port->model_port; + if (nullptr == model_port) { + continue; + } + + AtomPortId atom_port = atom_ctx.nlist.find_atom_port(atom_blk, model_port); + if (!atom_port) { + continue; + } + /* Find the atom nets mapped to the pin + * Note that some inputs may not be used, we set them to be open by default + */ + update_primitive_physical_pb_pin_atom_net(phy_pb, primitive_pb, + &(pb_graph_node->input_pins[iport][ipin]), + pb_route, device_annotation); + } + } + + for (int iport = 0; iport < pb_graph_node->num_output_ports; ++iport) { + for (int ipin = 0; ipin < pb_graph_node->num_output_pins[iport]; ++ipin) { + /* Port exists (some LUTs may have no input and hence no port in the atom netlist) */ + t_model_ports* model_port = pb_graph_node->output_pins[iport][ipin].port->model_port; + if (nullptr == model_port) { + continue; + } + + AtomPortId atom_port = atom_ctx.nlist.find_atom_port(atom_blk, model_port); + if (!atom_port) { + continue; + } + /* Find the atom nets mapped to the pin + * Note that some inputs may not be used, we set them to be open by default + */ + update_primitive_physical_pb_pin_atom_net(phy_pb, primitive_pb, + &(pb_graph_node->output_pins[iport][ipin]), + pb_route, device_annotation); + } + } + + for (int iport = 0; iport < pb_graph_node->num_clock_ports; ++iport) { + for (int ipin = 0; ipin < pb_graph_node->num_clock_pins[iport]; ++ipin) { + /* Port exists (some LUTs may have no input and hence no port in the atom netlist) */ + t_model_ports* model_port = pb_graph_node->clock_pins[iport][ipin].port->model_port; + if (nullptr == model_port) { + continue; + } + + AtomPortId atom_port = atom_ctx.nlist.find_atom_port(atom_blk, model_port); + if (!atom_port) { + continue; + } + /* Find the atom nets mapped to the pin + * Note that some inputs may not be used, we set them to be open by default + */ + update_primitive_physical_pb_pin_atom_net(phy_pb, primitive_pb, + &(pb_graph_node->clock_pins[iport][ipin]), + pb_route, device_annotation); + } + } +} + /************************************************************************ * Synchronize mapping results from an operating pb to a physical pb ***********************************************************************/ void rec_update_physical_pb_from_operating_pb(PhysicalPb& phy_pb, - t_pb* op_pb) { + const t_pb* op_pb, + const t_pb_routes& pb_route, + const AtomContext& atom_ctx, + const VprDeviceAnnotation& device_annotation) { + t_pb_graph_node* pb_graph_node = op_pb->pb_graph_node; + t_pb_type* pb_type = pb_graph_node->pb_type; + + if (true == is_primitive_pb_type(pb_type)) { + t_pb_graph_node* physical_pb_graph_node = device_annotation.physical_pb_graph_node(pb_graph_node); + VTR_ASSERT(nullptr != physical_pb_graph_node); + /* Find the physical pb */ + const PhysicalPbId& physical_pb = phy_pb.find_pb(physical_pb_graph_node); + VTR_ASSERT(true == phy_pb.valid_pb_id(physical_pb)); + + /* Set the mode bits */ + phy_pb.set_mode_bits(physical_pb, device_annotation.pb_type_mode_bits(physical_pb_graph_node->pb_type)); + + /* Find mapped atom block and add to this physical pb */ + AtomBlockId atom_blk = atom_ctx.nlist.find_block(op_pb->name); + VTR_ASSERT(atom_blk); + + phy_pb.add_atom_block(physical_pb, atom_blk); + + /* TODO: Iterate over ports and annotate the atom pins */ + synchronize_primitive_physical_pb_atom_nets(phy_pb, physical_pb, + pb_graph_node, + pb_route, + atom_ctx, atom_blk, + device_annotation); + return; + } + + /* Walk through the pb recursively but only visit the mapped modes and child pbs */ + t_mode* mapped_mode = &(pb_graph_node->pb_type->modes[op_pb->mode]); + for (int ipb = 0; ipb < mapped_mode->num_pb_type_children; ++ipb) { + /* Each child may exist multiple times in the hierarchy*/ + for (int jpb = 0; jpb < mapped_mode->pb_type_children[ipb].num_pb; ++jpb) { + if ((nullptr != op_pb->child_pbs[ipb]) && (nullptr != op_pb->child_pbs[ipb][jpb].name)) { + rec_update_physical_pb_from_operating_pb(phy_pb, + &(op_pb->child_pbs[ipb][jpb]), + pb_route, + atom_ctx, + device_annotation); + } + } + } } } /* end namespace openfpga */ diff --git a/openfpga/src/utils/physical_pb_utils.h b/openfpga/src/utils/physical_pb_utils.h index bdd37dc81..0e8bb71df 100644 --- a/openfpga/src/utils/physical_pb_utils.h +++ b/openfpga/src/utils/physical_pb_utils.h @@ -10,6 +10,7 @@ #include #include "physical_types.h" #include "vpr_device_annotation.h" +#include "vpr_context.h" #include "physical_pb.h" /******************************************************************** @@ -20,11 +21,14 @@ namespace openfpga { void alloc_physical_pb_from_pb_graph(PhysicalPb& phy_pb, - t_pb_graph_node* pb_graph_head, + const t_pb_graph_node* pb_graph_head, const VprDeviceAnnotation& device_annotation); void rec_update_physical_pb_from_operating_pb(PhysicalPb& phy_pb, - t_pb* op_pb); + const t_pb* op_pb, + const t_pb_routes& pb_route, + const AtomContext& atom_ctx, + const VprDeviceAnnotation& device_annotation); } /* end namespace openfpga */ From 921bf7dd7bac1f4303936cc41562e4d8a063b530 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 21 Feb 2020 20:45:22 -0700 Subject: [PATCH 195/645] use constant in device annotation --- openfpga/src/annotation/vpr_device_annotation.cpp | 8 ++++---- openfpga/src/annotation/vpr_device_annotation.h | 6 +++--- openfpga/src/utils/physical_pb_utils.cpp | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/openfpga/src/annotation/vpr_device_annotation.cpp b/openfpga/src/annotation/vpr_device_annotation.cpp index dfbcde112..0787e0225 100644 --- a/openfpga/src/annotation/vpr_device_annotation.cpp +++ b/openfpga/src/annotation/vpr_device_annotation.cpp @@ -206,9 +206,9 @@ int VprDeviceAnnotation::physical_pb_pin_offset(t_port* pb_port) const { } -t_pb_graph_pin* VprDeviceAnnotation::physical_pb_graph_pin(t_pb_graph_pin* pb_graph_pin) const { +t_pb_graph_pin* VprDeviceAnnotation::physical_pb_graph_pin(const t_pb_graph_pin* pb_graph_pin) const { /* Ensure that the pb_type is in the list */ - std::map::const_iterator it = physical_pb_graph_pins_.find(pb_graph_pin); + std::map::const_iterator it = physical_pb_graph_pins_.find(pb_graph_pin); if (it == physical_pb_graph_pins_.end()) { return nullptr; } @@ -409,10 +409,10 @@ void VprDeviceAnnotation::add_physical_pb_pin_rotate_offset(t_port* pb_port, con physical_pb_pin_offsets_[pb_port] = 0; } -void VprDeviceAnnotation::add_physical_pb_graph_pin(t_pb_graph_pin* operating_pb_graph_pin, +void VprDeviceAnnotation::add_physical_pb_graph_pin(const t_pb_graph_pin* operating_pb_graph_pin, t_pb_graph_pin* physical_pb_graph_pin) { /* Warn any override attempt */ - std::map::const_iterator it = physical_pb_graph_pins_.find(operating_pb_graph_pin); + std::map::const_iterator it = physical_pb_graph_pins_.find(operating_pb_graph_pin); if (it != physical_pb_graph_pins_.end()) { VTR_LOG_WARN("Override the annotation between operating pb_graph_pin '%s' and it physical pb_graph_pin '%s'!\n", operating_pb_graph_pin->port->name, physical_pb_graph_pin->port->name); diff --git a/openfpga/src/annotation/vpr_device_annotation.h b/openfpga/src/annotation/vpr_device_annotation.h index 1cfdcd412..00209df5c 100644 --- a/openfpga/src/annotation/vpr_device_annotation.h +++ b/openfpga/src/annotation/vpr_device_annotation.h @@ -70,7 +70,7 @@ class VprDeviceAnnotation { * The accumulated offset will be reset to 0 when it exceeds the msb() of the physical port */ int physical_pb_pin_offset(t_port* pb_port) const; - t_pb_graph_pin* physical_pb_graph_pin(t_pb_graph_pin* pb_graph_pin) const; + t_pb_graph_pin* physical_pb_graph_pin(const t_pb_graph_pin* pb_graph_pin) const; CircuitModelId rr_switch_circuit_model(const RRSwitchId& rr_switch) const; CircuitModelId rr_segment_circuit_model(const RRSegmentId& rr_segment) const; ArchDirectId direct_annotation(const size_t& direct) const; @@ -91,7 +91,7 @@ class VprDeviceAnnotation { void add_physical_pb_type_index_factor(t_pb_type* pb_type, const float& factor); void add_physical_pb_type_index_offset(t_pb_type* pb_type, const int& offset); void add_physical_pb_pin_rotate_offset(t_port* pb_port, const int& offset); - void add_physical_pb_graph_pin(t_pb_graph_pin* operating_pb_graph_pin, t_pb_graph_pin* physical_pb_graph_pin); + void add_physical_pb_graph_pin(const t_pb_graph_pin* operating_pb_graph_pin, t_pb_graph_pin* physical_pb_graph_pin); void add_rr_switch_circuit_model(const RRSwitchId& rr_switch, const CircuitModelId& circuit_model); void add_rr_segment_circuit_model(const RRSegmentId& rr_segment, const CircuitModelId& circuit_model); void add_direct_annotation(const size_t& direct, const ArchDirectId& arch_direct_id); @@ -169,7 +169,7 @@ class VprDeviceAnnotation { std::map physical_pb_graph_nodes_; /* Pair a pb_graph_pin to a physical pb_graph_pin */ - std::map physical_pb_graph_pins_; + std::map physical_pb_graph_pins_; /* Pair a Routing Resource Switch (rr_switch) to a circuit model */ std::map rr_switch_circuit_models_; diff --git a/openfpga/src/utils/physical_pb_utils.cpp b/openfpga/src/utils/physical_pb_utils.cpp index ec668a7d3..777f2ad0c 100644 --- a/openfpga/src/utils/physical_pb_utils.cpp +++ b/openfpga/src/utils/physical_pb_utils.cpp @@ -127,7 +127,7 @@ void update_primitive_physical_pb_pin_atom_net(PhysicalPb& phy_pb, VTR_ASSERT(atom_net); /* Find the physical pb_graph_pin */ - t_pb_graph_pin* physical_pb_graph_pin = device_annotation.physical_pb_graph_pin((t_pb_graph_pin*)pb_graph_pin); + t_pb_graph_pin* physical_pb_graph_pin = device_annotation.physical_pb_graph_pin(pb_graph_pin); VTR_ASSERT(nullptr != physical_pb_graph_pin); /* Check if the pin has been mapped to a net. From 9583731531c1d038f2f18c084936923e6a7f3c83 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 22 Feb 2020 22:10:32 -0700 Subject: [PATCH 196/645] add results saver for lb router --- openfpga/src/repack/lb_router.cpp | 9 ++++++++ openfpga/src/repack/lb_router.h | 9 ++++++++ openfpga/src/repack/lb_router_utils.cpp | 30 +++++++++++++++++++++++++ openfpga/src/repack/lb_router_utils.h | 5 +++++ openfpga/src/repack/repack.cpp | 1 + 5 files changed, 54 insertions(+) diff --git a/openfpga/src/repack/lb_router.cpp b/openfpga/src/repack/lb_router.cpp index 277548e18..c1b67b804 100644 --- a/openfpga/src/repack/lb_router.cpp +++ b/openfpga/src/repack/lb_router.cpp @@ -39,6 +39,15 @@ LbRouter::LbRouter(const LbRRGraph& lb_rr_graph, t_logical_block_type_ptr lb_typ /************************************************** * Public Accessors *************************************************/ +LbRouter::net_range LbRouter::nets() const { + return vtr::make_range(lb_net_ids_.begin(), lb_net_ids_.end()); +} + +AtomNetId LbRouter::net_atom_net_id(const NetId& net) const { + VTR_ASSERT(true == valid_net_id(net)); + return lb_net_atom_net_ids_[net]; +} + std::vector LbRouter::find_congested_rr_nodes(const LbRRGraph& lb_rr_graph) const { /* Validate if the rr_graph is the one we used to initialize the router */ VTR_ASSERT(true == matched_lb_rr_graph(lb_rr_graph)); diff --git a/openfpga/src/repack/lb_router.h b/openfpga/src/repack/lb_router.h index bf96a5958..c1d82ca1c 100644 --- a/openfpga/src/repack/lb_router.h +++ b/openfpga/src/repack/lb_router.h @@ -29,6 +29,9 @@ class LbRouter { public: /* Strong ids */ struct net_id_tag; typedef vtr::StrongId NetId; + public: /* Types and ranges */ + typedef vtr::vector::const_iterator net_iterator; + typedef vtr::Range net_range; public: /* Intra-Logic Block Routing Data Structures (by instance) */ /************************************************************************** * Describes the status of a logic cluster_ctx.blocks routing resource node @@ -163,6 +166,12 @@ class LbRouter { LbRouter(const LbRRGraph& lb_rr_graph, t_logical_block_type_ptr lb_type); public : /* Public accessors */ + /* Return the ids for all the nets to be routed */ + net_range nets() const; + + /* Return the atom net id for a net to be routed */ + AtomNetId net_atom_net_id(const NetId& net) const; + /** * Find all the routing resource nodes that are over-used, which they are used more than their capacity * This function is call to collect the nodes and router can reroute these net diff --git a/openfpga/src/repack/lb_router_utils.cpp b/openfpga/src/repack/lb_router_utils.cpp index 221f5a640..c106cd9f7 100644 --- a/openfpga/src/repack/lb_router_utils.cpp +++ b/openfpga/src/repack/lb_router_utils.cpp @@ -66,4 +66,34 @@ LbRouter::NetId add_lb_router_net_to_route(LbRouter& lb_router, return lb_net; } +/*************************************************************************************** + * Load the routing results (routing tree) from lb router to + * a physical pb data structure + ***************************************************************************************/ +void save_lb_router_results_to_physical_pb(PhysicalPb& phy_pb, + const LbRouter& lb_router, + const LbRRGraph& lb_rr_graph) { + /* Get mapping routing nodes per net */ + for (const LbRouter::NetId& net : lb_router.nets()) { + std::vector routed_nodes = lb_router.net_routed_nodes(net); + for (const LbRRNodeId& node : routed_nodes) { + t_pb_graph_pin* pb_graph_pin = lb_rr_graph.node_pb_graph_pin(node); + if (nullptr == pb_graph_pin) { + continue; + } + /* Find the pb id */ + const PhysicalPbId& pb_id = phy_pb.find_pb(pb_graph_pin->parent_node); + VTR_ASSERT(true == phy_pb.valid_pb_id(pb_id)); + + const AtomNetId& atom_net = lb_router.net_atom_net_id(net); + + if (AtomNetId::INVALID() == phy_pb.pb_graph_pin_atom_net(pb_id, pb_graph_pin)) { + phy_pb.set_pb_graph_pin_atom_net(pb_id, pb_graph_pin, atom_net); + } else { + VTR_ASSERT(atom_net == phy_pb.pb_graph_pin_atom_net(pb_id, pb_graph_pin)); + } + } + } +} + } /* end namespace openfpga */ diff --git a/openfpga/src/repack/lb_router_utils.h b/openfpga/src/repack/lb_router_utils.h index 6a92201eb..129796e7d 100644 --- a/openfpga/src/repack/lb_router_utils.h +++ b/openfpga/src/repack/lb_router_utils.h @@ -7,6 +7,7 @@ #include "atom_netlist.h" #include "lb_rr_graph.h" #include "lb_router.h" +#include "physical_pb.h" /******************************************************************** * Function declaration @@ -22,6 +23,10 @@ LbRouter::NetId add_lb_router_net_to_route(LbRouter& lb_router, const AtomContext& atom_ctx, const AtomNetId& atom_net_id); +void save_lb_router_results_to_physical_pb(PhysicalPb& phy_pb, + const LbRouter& lb_router, + const LbRRGraph& lb_rr_graph); + } /* end namespace openfpga */ #endif diff --git a/openfpga/src/repack/repack.cpp b/openfpga/src/repack/repack.cpp index e4df2986d..371d4ab46 100644 --- a/openfpga/src/repack/repack.cpp +++ b/openfpga/src/repack/repack.cpp @@ -293,6 +293,7 @@ void repack_cluster(const AtomContext& atom_ctx, atom_ctx, device_annotation); /* TODO: save routing results */ + save_lb_router_results_to_physical_pb(phy_pb, lb_router, lb_rr_graph); VTR_LOGV(verbose, "Saved results in physical pb\n"); /* Add the pb to clustering context */ From 2d17395e13f5bb87717c1d898a705009d697b360 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 22 Feb 2020 23:04:42 -0700 Subject: [PATCH 197/645] start integrating fpga_bitstream. Bring data structures online --- openfpga/src/base/openfpga_bitstream.cpp | 28 +++ openfpga/src/base/openfpga_bitstream.h | 23 +++ .../src/base/openfpga_bitstream_command.cpp | 20 ++ openfpga/src/base/openfpga_context.h | 6 + .../src/fpga_bitstream/bitstream_manager.cpp | 187 ++++++++++++++++++ .../src/fpga_bitstream/bitstream_manager.h | 133 +++++++++++++ .../fpga_bitstream/bitstream_manager_fwd.h | 25 +++ .../bitstream_manager_utils.cpp | 58 ++++++ .../fpga_bitstream/bitstream_manager_utils.h | 24 +++ .../fpga_bitstream/build_device_bitstream.cpp | 69 +++++++ .../fpga_bitstream/build_device_bitstream.h | 24 +++ 11 files changed, 597 insertions(+) create mode 100644 openfpga/src/base/openfpga_bitstream.cpp create mode 100644 openfpga/src/base/openfpga_bitstream.h create mode 100644 openfpga/src/fpga_bitstream/bitstream_manager.cpp create mode 100644 openfpga/src/fpga_bitstream/bitstream_manager.h create mode 100644 openfpga/src/fpga_bitstream/bitstream_manager_fwd.h create mode 100644 openfpga/src/fpga_bitstream/bitstream_manager_utils.cpp create mode 100644 openfpga/src/fpga_bitstream/bitstream_manager_utils.h create mode 100644 openfpga/src/fpga_bitstream/build_device_bitstream.cpp create mode 100644 openfpga/src/fpga_bitstream/build_device_bitstream.h diff --git a/openfpga/src/base/openfpga_bitstream.cpp b/openfpga/src/base/openfpga_bitstream.cpp new file mode 100644 index 000000000..8a51899e0 --- /dev/null +++ b/openfpga/src/base/openfpga_bitstream.cpp @@ -0,0 +1,28 @@ +/******************************************************************** + * This file includes functions to build bitstream database + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_time.h" +#include "vtr_log.h" + +#include "build_device_bitstream.h" +#include "openfpga_bitstream.h" + +/* Include global variables of VPR */ +#include "globals.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * A wrapper function to call the fabric_verilog function of FPGA-Verilog + *******************************************************************/ +void fpga_bitstream(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context) { + + CommandOptionId opt_verbose = cmd.option("verbose"); + + openfpga_ctx.mutable_bitstream_manager() = build_device_bitstream(g_vpr_ctx, openfpga_ctx, cmd_context.option_enable(cmd, opt_verbose)); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_bitstream.h b/openfpga/src/base/openfpga_bitstream.h new file mode 100644 index 000000000..1411dc090 --- /dev/null +++ b/openfpga/src/base/openfpga_bitstream.h @@ -0,0 +1,23 @@ +#ifndef OPENFPGA_BITSTREAM_H +#define OPENFPGA_BITSTREAM_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "command.h" +#include "command_context.h" +#include "openfpga_context.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void fpga_bitstream(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/base/openfpga_bitstream_command.cpp b/openfpga/src/base/openfpga_bitstream_command.cpp index 827dd1eca..af06cd60b 100644 --- a/openfpga/src/base/openfpga_bitstream_command.cpp +++ b/openfpga/src/base/openfpga_bitstream_command.cpp @@ -5,6 +5,7 @@ * - repack : create physical pbs and redo packing *******************************************************************/ #include "openfpga_repack.h" +#include "openfpga_bitstream.h" #include "openfpga_bitstream_command.h" /* begin namespace openfpga */ @@ -33,6 +34,25 @@ void add_openfpga_bitstream_commands(openfpga::Shell& shell) { std::vector cmd_dependency_repack; cmd_dependency_repack.push_back(shell_cmd_build_fabric_id); shell.set_command_dependency(shell_cmd_repack_id, cmd_dependency_repack); + + /******************************** + * Command 'fpga_bitstream' + */ + Command shell_cmd_fpga_bitstream("fpga_bitstream"); + /* Add an option '--verbose' */ + shell_cmd_fpga_bitstream.add_option("fabric_dependent", false, "Enable the bitstream construction for the FPGA fabric"); + shell_cmd_fpga_bitstream.add_option("verbose", false, "Enable verbose output"); + + /* Add command 'fpga_bitstream' to the Shell */ + ShellCommandId shell_cmd_fpga_bitstream_id = shell.add_command(shell_cmd_fpga_bitstream, "Build bitstream database"); + shell.set_command_class(shell_cmd_fpga_bitstream_id, openfpga_bitstream_cmd_class); + shell.set_command_execute_function(shell_cmd_fpga_bitstream_id, fpga_bitstream); + + /* The 'fpga_bitstream' command should NOT be executed before 'repack' */ + std::vector cmd_dependency_fpga_bitstream; + cmd_dependency_fpga_bitstream.push_back(shell_cmd_repack_id); + shell.set_command_dependency(shell_cmd_fpga_bitstream_id, cmd_dependency_fpga_bitstream); + } } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_context.h b/openfpga/src/base/openfpga_context.h index 5726cbba9..06c70cd24 100644 --- a/openfpga/src/base/openfpga_context.h +++ b/openfpga/src/base/openfpga_context.h @@ -11,6 +11,7 @@ #include "tile_direct.h" #include "module_manager.h" #include "openfpga_flow_manager.h" +#include "bitstream_manager.h" #include "device_rr_gsb.h" /******************************************************************** @@ -52,6 +53,7 @@ class OpenfpgaContext : public Context { const openfpga::TileDirect& tile_direct() const { return tile_direct_; } const openfpga::ModuleManager& module_graph() const { return module_graph_; } const openfpga::FlowManager& flow_manager() const { return flow_manager_; } + const openfpga::BitstreamManager& bitstream_manager() const { return bitstream_manager_; } public: /* Public mutators */ openfpga::Arch& mutable_arch() { return arch_; } openfpga::VprDeviceAnnotation& mutable_vpr_device_annotation() { return vpr_device_annotation_; } @@ -63,6 +65,7 @@ class OpenfpgaContext : public Context { openfpga::TileDirect& mutable_tile_direct() { return tile_direct_; } openfpga::ModuleManager& mutable_module_graph() { return module_graph_; } openfpga::FlowManager& mutable_flow_manager() { return flow_manager_; } + openfpga::BitstreamManager& mutable_bitstream_manager() { return bitstream_manager_; } private: /* Internal data */ /* Data structure to store information from read_openfpga_arch library */ openfpga::Arch arch_; @@ -90,6 +93,9 @@ class OpenfpgaContext : public Context { /* Fabric module graph */ openfpga::ModuleManager module_graph_; + + /* Bitstream database */ + openfpga::BitstreamManager bitstream_manager_; /* Flow status */ openfpga::FlowManager flow_manager_; diff --git a/openfpga/src/fpga_bitstream/bitstream_manager.cpp b/openfpga/src/fpga_bitstream/bitstream_manager.cpp new file mode 100644 index 000000000..2a96b1113 --- /dev/null +++ b/openfpga/src/fpga_bitstream/bitstream_manager.cpp @@ -0,0 +1,187 @@ +/****************************************************************************** + * This file includes member functions for data structure BitstreamManager + ******************************************************************************/ +#include + +#include "vtr_assert.h" +#include "bitstream_manager.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/************************************************** + * Public Accessors : Aggregates + *************************************************/ +/* Find all the configuration bits */ +BitstreamManager::config_bit_range BitstreamManager::bits() const { + return vtr::make_range(bit_ids_.begin(), bit_ids_.end()); +} + +/* Find all the configuration blocks */ +BitstreamManager::config_block_range BitstreamManager::blocks() const { + return vtr::make_range(block_ids_.begin(), block_ids_.end()); +} + +/****************************************************************************** + * Public Accessors + ******************************************************************************/ +bool BitstreamManager::bit_value(const ConfigBitId& bit_id) const { + /* Ensure a valid id */ + VTR_ASSERT(true == valid_bit_id(bit_id)); + + return bit_values_[bit_id]; +} + +std::string BitstreamManager::block_name(const ConfigBlockId& block_id) const { + /* Ensure the input ids are valid */ + VTR_ASSERT(true == valid_block_id(block_id)); + + return block_names_[block_id]; +} + +ConfigBlockId BitstreamManager::block_parent(const ConfigBlockId& block_id) const { + /* Ensure the input ids are valid */ + VTR_ASSERT(true == valid_block_id(block_id)); + + return parent_block_ids_[block_id]; +} + +std::vector BitstreamManager::block_children(const ConfigBlockId& block_id) const { + /* Ensure the input ids are valid */ + VTR_ASSERT(true == valid_block_id(block_id)); + + return child_block_ids_[block_id]; +} + +std::vector BitstreamManager::block_bits(const ConfigBlockId& block_id) const { + /* Ensure the input ids are valid */ + VTR_ASSERT(true == valid_block_id(block_id)); + + return block_bit_ids_[block_id]; +} + +ConfigBlockId BitstreamManager::bit_parent_block(const ConfigBitId& bit_id) const { + /* Ensure the input ids are valid */ + VTR_ASSERT(true == valid_bit_id(bit_id)); + + return bit_parent_block_ids_[bit_id]; +} + +size_t BitstreamManager::bit_index_in_parent_block(const ConfigBitId& bit_id) const { + /* Ensure the input ids are valid */ + VTR_ASSERT(true == valid_bit_id(bit_id)); + + ConfigBlockId bit_parent_block = bit_parent_block_ids_[bit_id]; + + VTR_ASSERT(true == valid_block_id(bit_parent_block)); + + for (size_t index = 0; index < block_bits(bit_parent_block).size(); ++index) { + if (bit_id == block_bits(bit_parent_block)[index]) { + return index; + } + } + + /* Not found, return in valid value */ + return size_t(-1); +} + +/* Find the child block in a bitstream manager with a given name */ +ConfigBlockId BitstreamManager::find_child_block(const ConfigBlockId& block_id, + const std::string& child_block_name) const { + /* Ensure the input ids are valid */ + VTR_ASSERT(true == valid_block_id(block_id)); + + std::vector candidates; + + for (const ConfigBlockId& child : block_children(block_id)) { + if (0 == child_block_name.compare(block_name(child))) { + candidates.push_back(child); + } + } + + /* We should have 0 or 1 candidate! */ + VTR_ASSERT(0 == candidates.size() || 1 == candidates.size()); + if (0 == candidates.size()) { + /* Not found, return an invalid value */ + return ConfigBlockId::INVALID(); + } + return candidates[0]; +} + +/****************************************************************************** + * Public Mutators + ******************************************************************************/ +ConfigBitId BitstreamManager::add_bit(const bool& bit_value) { + ConfigBitId bit = ConfigBitId(bit_ids_.size()); + /* Add a new bit, and allocate associated data structures */ + bit_ids_.push_back(bit); + bit_values_.push_back(bit_value); + shared_config_bit_values_.emplace_back(); + bit_parent_block_ids_.push_back(ConfigBlockId::INVALID()); + + return bit; +} + +ConfigBlockId BitstreamManager::add_block(const std::string& block_name) { + ConfigBlockId block = ConfigBlockId(block_ids_.size()); + /* Add a new bit, and allocate associated data structures */ + block_ids_.push_back(block); + block_names_.push_back(block_name); + block_bit_ids_.emplace_back(); + parent_block_ids_.push_back(ConfigBlockId::INVALID()); + child_block_ids_.emplace_back(); + + return block; +} + +void BitstreamManager::add_child_block(const ConfigBlockId& parent_block, const ConfigBlockId& child_block) { + /* Ensure the input ids are valid */ + VTR_ASSERT(true == valid_block_id(parent_block)); + VTR_ASSERT(true == valid_block_id(child_block)); + + /* We should have only a parent block for each block! */ + VTR_ASSERT(ConfigBlockId::INVALID() == parent_block_ids_[child_block]); + + /* Ensure the child block is not in the list of children of the parent block */ + std::vector::iterator it = std::find(child_block_ids_[parent_block].begin(), child_block_ids_[parent_block].end(), child_block); + VTR_ASSERT(it == child_block_ids_[parent_block].end()); + + /* Add the child_block to the parent_block */ + child_block_ids_[parent_block].push_back(child_block); + /* Register the block in the parent of the block */ + parent_block_ids_[child_block] = parent_block; +} + +void BitstreamManager::add_bit_to_block(const ConfigBlockId& block, const ConfigBitId& bit) { + /* Ensure the input ids are valid */ + VTR_ASSERT(true == valid_block_id(block)); + VTR_ASSERT(true == valid_bit_id(bit)); + + /* We should have only a parent block for each bit! */ + VTR_ASSERT(ConfigBlockId::INVALID() == bit_parent_block_ids_[bit]); + + /* Add the bit to the block */ + block_bit_ids_[block].push_back(bit); + /* Register the block in the parent of the bit */ + bit_parent_block_ids_[bit] = block; +} + +void BitstreamManager::add_shared_config_bit_values(const ConfigBitId& bit, const std::vector& shared_config_bits) { + /* Ensure the input ids are valid */ + VTR_ASSERT(true == valid_bit_id(bit)); + + shared_config_bit_values_[bit] = shared_config_bits; +} + +/****************************************************************************** + * Public Validators + ******************************************************************************/ +bool BitstreamManager::valid_bit_id(const ConfigBitId& bit_id) const { + return (size_t(bit_id) < bit_ids_.size()) && (bit_id == bit_ids_[bit_id]); +} + +bool BitstreamManager::valid_block_id(const ConfigBlockId& block_id) const { + return (size_t(block_id) < block_ids_.size()) && (block_id == block_ids_[block_id]); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_bitstream/bitstream_manager.h b/openfpga/src/fpga_bitstream/bitstream_manager.h new file mode 100644 index 000000000..640b5a325 --- /dev/null +++ b/openfpga/src/fpga_bitstream/bitstream_manager.h @@ -0,0 +1,133 @@ +/****************************************************************************** + * This file introduces a data structure to store bitstream-related information + * + * General concept + * --------------- + * The idea is to create a unified data structure that stores all the configuration bits + * with proper annotation to which modules in FPGA fabric it belongs to. + * 1. It can be easily organized in fabric-dependent representation + * (generate a sequence of bitstream which exactly fit the configuration protocol of FPGA fabric) + * 2. Or it can be easily organized in fabric-independent representation (think about XML file) + * + * Cross-reference + * --------------- + * May be used only when you want to bind the bitstream to a specific FPGA fabric! + * If you do so, please make sure the block name is exactly same as the instance name + * of a child module in ModuleManager!!! + * The configurable modules/instances in module manager are arranged + * in the sequence to fit different configuration protocol. + * By using the link between ModuleManager and BitstreamManager, + * we can build a sequence of configuration bits to fit different configuration protocols. + * + * +------------------+ +-----------------+ + * | | block_name == instance_name | | + * | BitstreamManager |-------------------------------->| ModuleManager | + * | | | | + * +------------------+ +-----------------+ + * + * Restrictions: + * 1. Each block inside BitstreamManager should have only 1 parent block + * and multiple child block + * 2. Each bit inside BitstreamManager should have only 1 parent block + * + ******************************************************************************/ +#ifndef BITSTREAM_MANAGER_H +#define BITSTREAM_MANAGER_H + +#include +#include +#include "vtr_vector.h" + +#include "bitstream_manager_fwd.h" + +/* begin namespace openfpga */ +namespace openfpga { + +class BitstreamManager { + public: /* Types and ranges */ + typedef vtr::vector::const_iterator config_bit_iterator; + typedef vtr::vector::const_iterator config_block_iterator; + + typedef vtr::Range config_bit_range; + typedef vtr::Range config_block_range; + + public: /* Public aggregators */ + /* Find all the configuration bits */ + config_bit_range bits() const; + + config_block_range blocks() const; + + public: /* Public Accessors */ + /* Find the value of bitstream */ + bool bit_value(const ConfigBitId& bit_id) const; + + /* Find a name of a block */ + std::string block_name(const ConfigBlockId& block_id) const; + + /* Find the parent of a block */ + ConfigBlockId block_parent(const ConfigBlockId& block_id) const; + + /* Find the children of a block */ + std::vector block_children(const ConfigBlockId& block_id) const; + + /* Find all the bits that belong to a block */ + std::vector block_bits(const ConfigBlockId& block_id) const; + + /* Find the parent block of a bit */ + ConfigBlockId bit_parent_block(const ConfigBitId& bit_id) const; + + /* Find the index of a configuration bit in its parent block */ + size_t bit_index_in_parent_block(const ConfigBitId& bit_id) const; + + /* Find the child block in a bitstream manager with a given name */ + ConfigBlockId find_child_block(const ConfigBlockId& block_id, const std::string& child_block_name) const; + + public: /* Public Mutators */ + /* Add a new configuration bit to the bitstream manager */ + ConfigBitId add_bit(const bool& bit_value); + + /* Add a new block of configuration bits to the bitstream manager */ + ConfigBlockId add_block(const std::string& block_name); + + /* Set a block as a child block of another */ + void add_child_block(const ConfigBlockId& parent_block, const ConfigBlockId& child_block); + + /* Add a configuration bit to a block */ + void add_bit_to_block(const ConfigBlockId& block, const ConfigBitId& bit); + + /* Add share configuration bits to a configuration bit */ + void add_shared_config_bit_values(const ConfigBitId& bit, const std::vector& shared_config_bits); + + public: /* Public Validators */ + bool valid_bit_id(const ConfigBitId& bit_id) const; + + bool valid_block_id(const ConfigBlockId& block_id) const; + + private: /* Internal data */ + /* Unique id of a block of bits in the Bitstream */ + vtr::vector block_ids_; + vtr::vector> block_bit_ids_; + + /* Back-annotation for the bits */ + /* Parent block of a bit in the Bitstream + * For each bit, the block name can be designed to be same as the instance name in a module + * to reflect its position in the module tree (ModuleManager) + * Note that the blocks here all unique, unlike ModuleManager where modules can be instanciated + * Therefore, this block graph can be considered as a flattened graph of ModuleGraph + */ + vtr::vector block_names_; + vtr::vector parent_block_ids_; + vtr::vector> child_block_ids_; + + /* Unique id of a bit in the Bitstream */ + vtr::vector bit_ids_; + vtr::vector bit_parent_block_ids_; + /* value of a bit in the Bitstream */ + vtr::vector bit_values_; + /* value of a shared configuration bits in the Bitstream */ + vtr::vector> shared_config_bit_values_; +}; + +#endif + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_bitstream/bitstream_manager_fwd.h b/openfpga/src/fpga_bitstream/bitstream_manager_fwd.h new file mode 100644 index 000000000..55a596633 --- /dev/null +++ b/openfpga/src/fpga_bitstream/bitstream_manager_fwd.h @@ -0,0 +1,25 @@ +/************************************************** + * This file includes only declarations for + * the data structures for bitstream database + * Please refer to bitstream_manager.h for more details + *************************************************/ +#ifndef BITSTREAM_MANAGER_FWD_H +#define BITSTREAM_MANAGER_FWD_H + +#include "vtr_strong_id.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/* Strong Ids for BitstreamContext */ +struct config_block_id_tag; +struct config_bit_id_tag; + +typedef vtr::StrongId ConfigBlockId; +typedef vtr::StrongId ConfigBitId; + +class BitstreamManager; + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_bitstream/bitstream_manager_utils.cpp b/openfpga/src/fpga_bitstream/bitstream_manager_utils.cpp new file mode 100644 index 000000000..1225602ef --- /dev/null +++ b/openfpga/src/fpga_bitstream/bitstream_manager_utils.cpp @@ -0,0 +1,58 @@ +/******************************************************************** + * This file includes most utilized functions for data structure + * BitstreamManager + * + * Note: These functions are not generic enough so that they + * should NOT be a member function! + *******************************************************************/ +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" + +#include "bitstream_manager_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Recursively find the hierarchy of a block of bitstream manager + * Return a vector of the block ids, where the top-level block + * locates in the head, while the leaf block locates in the tail + * top, next, ... , block + *******************************************************************/ +std::vector find_bitstream_manager_block_hierarchy(const BitstreamManager& bitstream_manager, + const ConfigBlockId& block) { + std::vector block_hierarchy; + ConfigBlockId temp_block = block; + + /* Generate a tree of parent block */ + while (true == bitstream_manager.valid_block_id(temp_block)) { + block_hierarchy.push_back(temp_block); + /* Go to upper level */ + temp_block = bitstream_manager.block_parent(temp_block); + } + + /* Reverse the vector, so that top block stay in the first */ + std::reverse(block_hierarchy.begin(), block_hierarchy.end()); + + return block_hierarchy; +} + +/******************************************************************** + * Find all the top-level blocks in a bitstream manager, + * which have no parents + *******************************************************************/ +std::vector find_bitstream_manager_top_blocks(const BitstreamManager& bitstream_manager) { + std::vector top_blocks; + for (const ConfigBlockId& blk : bitstream_manager.blocks()) { + if (ConfigBlockId::INVALID() != bitstream_manager.block_parent(blk)) { + continue; + } + top_blocks.push_back(blk); + } + + return top_blocks; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_bitstream/bitstream_manager_utils.h b/openfpga/src/fpga_bitstream/bitstream_manager_utils.h new file mode 100644 index 000000000..4c8b26ad2 --- /dev/null +++ b/openfpga/src/fpga_bitstream/bitstream_manager_utils.h @@ -0,0 +1,24 @@ +#ifndef BITSTREAM_MANAGER_UTILS_H +#define BITSTREAM_MANAGER_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include "bitstream_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +std::vector find_bitstream_manager_block_hierarchy(const BitstreamManager& bitstream_manager, + const ConfigBlockId& block); + +std::vector find_bitstream_manager_top_blocks(const BitstreamManager& bitstream_manager); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_bitstream/build_device_bitstream.cpp b/openfpga/src/fpga_bitstream/build_device_bitstream.cpp new file mode 100644 index 000000000..997c83fb6 --- /dev/null +++ b/openfpga/src/fpga_bitstream/build_device_bitstream.cpp @@ -0,0 +1,69 @@ +/******************************************************************** + * This file includes functions to build bitstream from a mapped + * FPGA fabric. + * We decode the bitstream from configuration of routing multiplexers + * and Look-Up Tables (LUTs) which locate in CLBs and global routing architecture + *******************************************************************/ +#include + +/* Headers from vtrutil library */ +#include "vtr_log.h" +#include "vtr_assert.h" +#include "vtr_time.h" + +#include "openfpga_naming.h" + +//#include "build_grid_bitstream.h" +//#include "build_routing_bitstream.h" +#include "build_device_bitstream.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * A top-level function to build a bistream from the FPGA device + * 1. It will organize the bitstream w.r.t. the hierarchy of module graphs + * describing the FPGA fabric + * 2. It will decode configuration bits from routing multiplexers used in + * global routing architecture + * 3. It will decode configuration bits from routing multiplexers and LUTs + * used in CLBs + * + * Note: this function create a bitstream which is binding to the module graphs + * of the FPGA fabric that FPGA-X2P generates! + * But it can be used to output a generic bitstream for VPR mapping FPGA + *******************************************************************/ +BitstreamManager build_device_bitstream(const VprContext& vpr_ctx, + const OpenfpgaContext& openfpga_ctx, + const bool& verbose) { + + std::string timer_message = std::string("\nBuild fabric-independent bitstream for implementation '") + vpr_ctx.atom().nlist.netlist_name() + std::string("'\n"); + vtr::ScopedStartFinishTimer timer(timer_message); + + /* Bitstream manager to be built */ + BitstreamManager bitstream_manager; + + /* Assign the SRAM model applied to the FPGA fabric */ + CircuitModelId sram_model = openfpga_ctx.arch().config_protocol.memory_model(); + VTR_ASSERT(true == openfpga_ctx.arch().circuit_lib.valid_model_id(sram_model)); + + /* Create the top-level block for bitstream + * This is related to the top-level module of fpga + */ + std::string top_block_name = generate_fpga_top_module_name(); + ConfigBlockId top_block = bitstream_manager.add_block(top_block_name); + + /* Create bitstream from grids */ + VTR_LOGV(verbose, "Building grid bitstream...\n"); + //build_grid_bitstream(bitstream_manager, top_block, module_manager, circuit_lib, mux_lib, device_size, grids); + VTR_LOGV(verbose, "Done\n"); + + /* Create bitstream from routing architectures */ + VTR_LOGV(verbose, "Building routing bitstream...\n"); + //build_routing_bitstream(bitstream_manager, top_block, module_manager, circuit_lib, mux_lib, rr_switches, L_rr_node, L_device_rr_gsb); + VTR_LOGV(verbose, "Done\n"); + + return bitstream_manager; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_bitstream/build_device_bitstream.h b/openfpga/src/fpga_bitstream/build_device_bitstream.h new file mode 100644 index 000000000..3926f2733 --- /dev/null +++ b/openfpga/src/fpga_bitstream/build_device_bitstream.h @@ -0,0 +1,24 @@ +#ifndef BUILD_DEVICE_BITSTREAM_H +#define BUILD_DEVICE_BITSTREAM_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include "vpr_context.h" +#include "openfpga_context.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +BitstreamManager build_device_bitstream(const VprContext& vpr_ctx, + const OpenfpgaContext& openfpga_ctx, + const bool& verbose); + +} /* end namespace openfpga */ + +#endif From 51439ba3b4af63a19772d1a61c356c612f1339f1 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 23 Feb 2020 20:40:18 -0700 Subject: [PATCH 198/645] add bitstream writer to be integrated --- .../src/fpga_bitstream/bitstream_manager.h | 4 +- .../src/fpga_bitstream/bitstream_writer.cpp | 140 ++++++++++++++++++ .../src/fpga_bitstream/bitstream_writer.h | 22 +++ openfpga/test_script/s298_k6_frac.openfpga | 5 +- 4 files changed, 168 insertions(+), 3 deletions(-) create mode 100644 openfpga/src/fpga_bitstream/bitstream_writer.cpp create mode 100644 openfpga/src/fpga_bitstream/bitstream_writer.h diff --git a/openfpga/src/fpga_bitstream/bitstream_manager.h b/openfpga/src/fpga_bitstream/bitstream_manager.h index 640b5a325..58a2af8ee 100644 --- a/openfpga/src/fpga_bitstream/bitstream_manager.h +++ b/openfpga/src/fpga_bitstream/bitstream_manager.h @@ -128,6 +128,6 @@ class BitstreamManager { vtr::vector> shared_config_bit_values_; }; -#endif - } /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_bitstream/bitstream_writer.cpp b/openfpga/src/fpga_bitstream/bitstream_writer.cpp new file mode 100644 index 000000000..f19e3819f --- /dev/null +++ b/openfpga/src/fpga_bitstream/bitstream_writer.cpp @@ -0,0 +1,140 @@ +/******************************************************************** + * This file includes functions that output bitstream database + * to files in different formats + *******************************************************************/ +#include +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" +#include "vtr_time.h" + +/* Headers from openfpgautil library */ +#include "openfpga_digest.h" + +#include "openfpga_naming.h" + +#include "bitstream_manager_utils.h" +#include "bitstream_writer.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * This function write header information to a bitstream file + *******************************************************************/ +static +void write_bitstream_xml_file_head(std::fstream& fp) { + valid_file_stream(fp); + + auto end = std::chrono::system_clock::now(); + std::time_t end_time = std::chrono::system_clock::to_time_t(end); + + fp << "" << std::endl; + fp << std::endl; +} + +/******************************************************************** + * Recursively write the bitstream of a block to a xml file + * This function will use a Depth-First Search in outputting bitstream + * for each block + * 1. For block with bits as children, we will output the XML lines + * 2. For block without bits/child blocks, we can return + * 3. For block with child blocks, we visit each child recursively + *******************************************************************/ +static +void rec_write_block_bitstream_to_xml_file(std::fstream& fp, + const BitstreamManager& bitstream_manager, + const ConfigBlockId& block) { + valid_file_stream(fp); + + /* Dive to child blocks if this block has any */ + for (const ConfigBlockId& child_block : bitstream_manager.block_children(block)) { + rec_write_block_bitstream_to_xml_file(fp, bitstream_manager, child_block); + } + + if (0 == bitstream_manager.block_bits(block).size()) { + return; + } + + /* Write the bits of this block */ + fp << "" << std::endl; + + std::vector block_hierarchy = find_bitstream_manager_block_hierarchy(bitstream_manager, block); + + /* Output hierarchy of this parent*/ + fp << "\t" << std::endl; + size_t hierarchy_counter = 0; + for (const ConfigBlockId& temp_block : block_hierarchy) { + fp << "\t\t" << std::endl; + hierarchy_counter++; + } + fp << "\t" << std::endl; + + /* Output child bits under this block */ + size_t bit_counter = 0; + fp << "\t" << std::endl; + for (const ConfigBitId& child_bit : bitstream_manager.block_bits(block)) { + fp << "\t\t" << std::endl; + bit_counter++; + } + fp << "\t" << std::endl; + + fp << "" < top_block = find_bitstream_manager_top_blocks(bitstream_manager); + /* Make sure we have only 1 top block and its name matches the top module */ + VTR_ASSERT(1 == top_block.size()); + VTR_ASSERT(0 == top_block_name.compare(bitstream_manager.block_name(top_block[0]))); + + /* Write bitstream, block by block, in a recursive way */ + rec_write_block_bitstream_to_xml_file(fp, bitstream_manager, top_block[0]); + + /* Close file handler */ + fp.close(); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_bitstream/bitstream_writer.h b/openfpga/src/fpga_bitstream/bitstream_writer.h new file mode 100644 index 000000000..338bfde35 --- /dev/null +++ b/openfpga/src/fpga_bitstream/bitstream_writer.h @@ -0,0 +1,22 @@ +#ifndef BITSTREAM_WRITER_H +#define BITSTREAM_WRITER_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include "bitstream_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void write_arch_independent_bitstream_to_xml_file(const BitstreamManager& bitstream_manager, + const std::string& fname); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/test_script/s298_k6_frac.openfpga b/openfpga/test_script/s298_k6_frac.openfpga index a985511c4..490284fe4 100644 --- a/openfpga/test_script/s298_k6_frac.openfpga +++ b/openfpga/test_script/s298_k6_frac.openfpga @@ -8,7 +8,7 @@ read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml #write_openfpga_arch -f ./arch_echo.xml # Annotate the OpenFPGA architecture to VPR data base -link_openfpga_arch --verbose +link_openfpga_arch #--verbose # Check and correct any naming conflicts in the BLIF netlist check_netlist_naming_conflict --fix --report ./netlist_renaming.xml @@ -29,6 +29,9 @@ build_fabric --compress_routing --duplicate_grid_pin #--verbose # Strongly recommend it is done after all the fix-up have been applied repack --verbose +# Build the bitstream +fpga_bitstream --verbose + # Write the Verilog netlit for FPGA fabric # - Enable the use of explicit port mapping in Verilog netlist write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose From 8723007f6855163060bc519ea62f8fe70f131eb8 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 23 Feb 2020 20:53:24 -0700 Subject: [PATCH 199/645] Bring mux bitstream generation online --- .../fpga_bitstream/build_mux_bitstream.cpp | 178 ++++++++++++++++++ .../src/fpga_bitstream/build_mux_bitstream.h | 30 +++ 2 files changed, 208 insertions(+) create mode 100644 openfpga/src/fpga_bitstream/build_mux_bitstream.cpp create mode 100644 openfpga/src/fpga_bitstream/build_mux_bitstream.h diff --git a/openfpga/src/fpga_bitstream/build_mux_bitstream.cpp b/openfpga/src/fpga_bitstream/build_mux_bitstream.cpp new file mode 100644 index 000000000..3b7e06354 --- /dev/null +++ b/openfpga/src/fpga_bitstream/build_mux_bitstream.cpp @@ -0,0 +1,178 @@ +/******************************************************************** + * This file includes functions to build bitstream from routing multiplexers + * which are based on different technology + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_log.h" +#include "vtr_assert.h" +#include "vtr_vector.h" + +/* Headers from openfpgautil library */ +#include "openfpga_decode.h" + +#include "mux_utils.h" +#include "decoder_library_utils.h" + +#include "build_mux_bitstream.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/* Default path ID of a unused multiplexer */ +#define DEFAULT_PATH_ID -1 + +/* Default path ID of a unused multiplexer when there are no constant inputs*/ +#define DEFAULT_MUX_PATH_ID 0 + +/******************************************************************** + * Find the default path id of a MUX + * This is applied when the path id specified is DEFAULT_PATH_ID, + * which is not correlated to the MUX implementation + * This function is binding the default path id to the implemented structure + * 1. If the MUX has a constant input, the default path id will be + * directed to the last input of the MUX, which is the constant input + * 2. If the MUX does not have a constant input, the default path id + * will the first input of the MUX. + * + * Restriction: + * we assume the default path is the first input of the MUX + * Change if this is not what you want + *******************************************************************/ +size_t find_mux_default_path_id(const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const size_t& mux_size) { + size_t default_path_id; + + if (true == circuit_lib.mux_add_const_input(mux_model)) { + default_path_id = mux_size - 1; /* When there is a constant input, use the last path */ + } else { + default_path_id = DEFAULT_MUX_PATH_ID; /* When there is no constant input, use the default one */ + } + + return default_path_id; +} + +/******************************************************************** + * This function generates bitstream for a CMOS routing multiplexer + * Thanks to MuxGraph object has already describe the internal multiplexing + * structure, bitstream generation is simply done by routing the signal + * to from a given input to the output + * All the memory bits can be generated by an API of MuxGraph + * + * To be generic, this function only returns a vector bit values + * without touching an bitstream-relate data structure + *******************************************************************/ +static +std::vector build_cmos_mux_bitstream(const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const MuxLibrary& mux_lib, + const size_t& mux_size, + const int& path_id) { + /* Note that the size of implemented mux could be different than the mux size we see here, + * due to the constant inputs + * We will find the input size of implemented MUX and fetch the graph-based representation in MUX library + */ + size_t implemented_mux_size = find_mux_implementation_num_inputs(circuit_lib, mux_model, mux_size); + /* Note that the mux graph is indexed using datapath MUX size!!!! */ + MuxId mux_graph_id = mux_lib.mux_graph(mux_model, mux_size); + const MuxGraph mux_graph = mux_lib.mux_graph(mux_graph_id); + + size_t datapath_id = path_id; + + /* Find the path_id related to the implementation */ + if (DEFAULT_PATH_ID == path_id) { + datapath_id = find_mux_default_path_id(circuit_lib, mux_model, implemented_mux_size); + } else { + VTR_ASSERT( datapath_id < mux_size); + } + /* Path id should makes sense */ + VTR_ASSERT(datapath_id < mux_graph.inputs().size()); + /* We should have only one output for this MUX! */ + VTR_ASSERT(1 == mux_graph.outputs().size()); + + /* Generate the memory bits */ + vtr::vector raw_bitstream = mux_graph.decode_memory_bits(MuxInputId(datapath_id), mux_graph.output_id(mux_graph.outputs()[0])); + + std::vector mux_bitstream; + for (const bool& bit : raw_bitstream) { + mux_bitstream.push_back(bit); + } + + /* Consider local encoder support, we need further encode the bitstream */ + if (false == circuit_lib.mux_use_local_encoder(mux_model)) { + return mux_bitstream; + } + + /* Clear the mux_bitstream, we need to apply encoding */ + mux_bitstream.clear(); + + /* Encode the memory bits level by level, + * One local encoder is used for each level of multiplexers + */ + for (const size_t& level : mux_graph.levels()) { + /* The encoder will convert the path_id to a binary number + * For example: when path_id=3 (use the 4th input), using a 2-input encoder + * the sram_bits will be the 2-digit binary number of 3: 10 + */ + std::vector encoder_data; + + /* Exception: there is only 1 memory at this level, bitstream will not be changed!!! */ + if (1 == mux_graph.memories_at_level(level).size()) { + mux_bitstream.push_back(raw_bitstream[mux_graph.memories_at_level(level)[0]]); + continue; + } + + /* Otherwise: we follow a regular recipe */ + for (size_t mem_index = 0; mem_index < mux_graph.memories_at_level(level).size(); ++mem_index) { + /* Conversion rule: true = 1, false = 0 */ + if (true == raw_bitstream[mux_graph.memories_at_level(level)[mem_index]]) { + encoder_data.push_back(mem_index); + } + } + /* There should be at most one '1' */ + VTR_ASSERT( (0 == encoder_data.size()) || (1 == encoder_data.size())); + /* Convert to encoded bits */ + std::vector encoder_addr; + if (0 == encoder_data.size()) { + encoder_addr = itobin_vec(0, find_mux_local_decoder_addr_size(mux_graph.memories_at_level(level).size())); + } else { + VTR_ASSERT(1 == encoder_data.size()); + encoder_addr = itobin_vec(encoder_data[0], find_mux_local_decoder_addr_size(mux_graph.memories_at_level(level).size())); + } + /* Build final mux bitstream */ + for (const size_t& bit : encoder_addr) { + mux_bitstream.push_back(1 == bit); + } + } + + return mux_bitstream; +} + +/******************************************************************** + * This function generates bitstream for a routing multiplexer + * supporting both CMOS and ReRAM multiplexer designs + *******************************************************************/ +std::vector build_mux_bitstream(const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const MuxLibrary& mux_lib, + const size_t& mux_size, + const int& path_id) { + std::vector mux_bitstream; + + switch (circuit_lib.design_tech_type(mux_model)) { + case CIRCUIT_MODEL_DESIGN_CMOS: + mux_bitstream = build_cmos_mux_bitstream(circuit_lib, mux_model, mux_lib, mux_size, path_id); + break; + case CIRCUIT_MODEL_DESIGN_RRAM: + /* TODO: ReRAM MUX needs a different bitstream generation strategy */ + break; + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid design technology for circuit model '%s'!\n", + circuit_lib.model_name(mux_model).c_str()); + exit(1); + } + return mux_bitstream; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_bitstream/build_mux_bitstream.h b/openfpga/src/fpga_bitstream/build_mux_bitstream.h new file mode 100644 index 000000000..639e086bc --- /dev/null +++ b/openfpga/src/fpga_bitstream/build_mux_bitstream.h @@ -0,0 +1,30 @@ +#ifndef BUILD_MUX_BITSTREAM_H +#define BUILD_MUX_BITSTREAM_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include "circuit_library.h" +#include "mux_library.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +size_t find_mux_default_path_id(const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const size_t& mux_size); + +std::vector build_mux_bitstream(const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const MuxLibrary& mux_lib, + const size_t& mux_size, + const int& path_id); + +} /* end namespace openfpga */ + +#endif From 86c7c24701816ac888d754e0d050bd0b40412b23 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 23 Feb 2020 20:58:17 -0700 Subject: [PATCH 200/645] add fabric bitstream generation online --- .../fpga_bitstream/build_fabric_bitstream.cpp | 131 ++++++++++++++++++ .../fpga_bitstream/build_fabric_bitstream.h | 23 +++ 2 files changed, 154 insertions(+) create mode 100644 openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp create mode 100644 openfpga/src/fpga_bitstream/build_fabric_bitstream.h diff --git a/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp b/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp new file mode 100644 index 000000000..241aee04d --- /dev/null +++ b/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp @@ -0,0 +1,131 @@ +/******************************************************************** + * This file includes functions to build fabric dependent bitstream + *******************************************************************/ +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" +#include "vtr_time.h" + +#include "openfpga_naming.h" + +#include "bitstream_manager_utils.h" +#include "build_fabric_bitstream.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * This function will walk through all the configurable children under a module + * in a recursive way, following a Depth-First Search (DFS) strategy + * For each configuration child, we use its instance name as a key to spot the + * configuration bits in bitstream manager. + * Note that it is guarentee that the instance name in module manager is + * consistent with the block names in bitstream manager + * We use this link to reorganize the bitstream in the sequence of memories as we stored + * in the configurable_children) and configurable_child_instances() of each module of module manager + *******************************************************************/ +static +void rec_build_module_fabric_dependent_bitstream(const BitstreamManager& bitstream_manager, + const ConfigBlockId& parent_block, + const ModuleManager& module_manager, + const ModuleId& parent_module, + std::vector& fabric_bitstream) { + + /* Depth-first search: if we have any children in the parent_block, + * we dive to the next level first! + */ + if (0 < bitstream_manager.block_children(parent_block).size()) { + for (size_t child_id = 0; child_id < module_manager.configurable_children(parent_module).size(); ++child_id) { + ModuleId child_module = module_manager.configurable_children(parent_module)[child_id]; + size_t child_instance = module_manager.configurable_child_instances(parent_module)[child_id]; + /* Get the instance name and ensure it is not empty */ + std::string instance_name = module_manager.instance_name(parent_module, child_module, child_instance); + + /* Find the child block that matches the instance name! */ + ConfigBlockId child_block = bitstream_manager.find_child_block(parent_block, instance_name); + /* We must have one valid block id! */ + if (true != bitstream_manager.valid_block_id(child_block)) + VTR_ASSERT(true == bitstream_manager.valid_block_id(child_block)); + + /* Go recursively */ + rec_build_module_fabric_dependent_bitstream(bitstream_manager, child_block, + module_manager, child_module, + fabric_bitstream); + } + /* Ensure that there should be no configuration bits in the parent block */ + VTR_ASSERT(0 == bitstream_manager.block_bits(parent_block).size()); + } + + /* Note that, reach here, it means that this is a leaf node. + * We add the configuration bits to the fabric_bitstream, + * And then, we can return + */ + for (const ConfigBitId& config_bit : bitstream_manager.block_bits(parent_block)) { + fabric_bitstream.push_back(config_bit); + } +} + +/******************************************************************** + * A top-level function re-organizes the bitstream for a specific + * FPGA fabric, where configuration bits are organized in the sequence + * that can be directly loaded to the FPGA configuration protocol. + * Support: + * 1. Configuration chain + * 2. Memory decoders + * This function does NOT modify the bitstream database + * Instead, it builds a vector of ids for configuration bits in bitstream manager + * + * This function can be called ONLY after the function build_device_bitstream() + * Note that this function does NOT decode bitstreams from circuit implementation + * It was done in the function build_device_bitstream() + *******************************************************************/ +std::vector build_fabric_dependent_bitstream(const BitstreamManager& bitstream_manager, + const ModuleManager& module_manager) { + std::vector fabric_bitstream; + + vtr::ScopedStartFinishTimer timer("\nBuild fabric dependent bitstream\n"); + + /* Get the top module name in module manager, which is our starting point */ + std::string top_module_name = generate_fpga_top_module_name(); + ModuleId top_module = module_manager.find_module(top_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(top_module)); + + /* Find the top block in bitstream manager, which has not parents */ + std::vector top_block = find_bitstream_manager_top_blocks(bitstream_manager); + /* Make sure we have only 1 top block and its name matches the top module */ + VTR_ASSERT(1 == top_block.size()); + VTR_ASSERT(0 == top_module_name.compare(bitstream_manager.block_name(top_block[0]))); + + rec_build_module_fabric_dependent_bitstream(bitstream_manager, top_block[0], + module_manager, top_module, + fabric_bitstream); + + /* Time-consuming sanity check: Uncomment these codes only for debugging!!! + * Check which configuration bits are not touched + */ + /* + for (const ConfigBitId& config_bit : bitstream_manager.bits()) { + std::vector::iterator it = std::find(fabric_bitstream.begin(), fabric_bitstream.end(), config_bit); + if (it == fabric_bitstream.end()) { + std::vector block_hierarchy = find_bitstream_manager_block_hierarchy(bitstream_manager, bitstream_manager.bit_parent_block(config_bit)); + std::string block_hierarchy_name; + for (const ConfigBlockId& temp_block : block_hierarchy) { + block_hierarchy_name += std::string("/") + bitstream_manager.block_name(temp_block); + } + vpr_printf(TIO_MESSAGE_INFO, + "bit (parent_block = %s) is not touched!\n", + block_hierarchy_name.c_str()); + } + } + */ + + /* Ensure our fabric bitstream is in the same size as device bistream */ + VTR_ASSERT(bitstream_manager.bits().size() == fabric_bitstream.size()); + + return fabric_bitstream; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_bitstream/build_fabric_bitstream.h b/openfpga/src/fpga_bitstream/build_fabric_bitstream.h new file mode 100644 index 000000000..47d46084c --- /dev/null +++ b/openfpga/src/fpga_bitstream/build_fabric_bitstream.h @@ -0,0 +1,23 @@ +#ifndef BUILD_FABRIC_BITSTREAM_H +#define BUILD_FABRIC_BITSTREAM_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include "bitstream_manager.h" +#include "module_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +std::vector build_fabric_dependent_bitstream(const BitstreamManager& bitstream_manager, + const ModuleManager& module_manager); + +} /* end namespace openfpga */ + +#endif From 712eeb13402bdf163ad0952e9ed898aff0bdb732 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 23 Feb 2020 22:09:46 -0700 Subject: [PATCH 201/645] bring bitstream generator for routing modules online --- .../fpga_bitstream/build_device_bitstream.cpp | 13 +- .../fpga_bitstream/build_mux_bitstream.cpp | 7 +- .../build_routing_bitstream.cpp | 442 ++++++++++++++++++ .../fpga_bitstream/build_routing_bitstream.h | 39 ++ .../fpga_bitstream/mux_bitstream_constants.h | 15 + 5 files changed, 508 insertions(+), 8 deletions(-) create mode 100644 openfpga/src/fpga_bitstream/build_routing_bitstream.cpp create mode 100644 openfpga/src/fpga_bitstream/build_routing_bitstream.h create mode 100644 openfpga/src/fpga_bitstream/mux_bitstream_constants.h diff --git a/openfpga/src/fpga_bitstream/build_device_bitstream.cpp b/openfpga/src/fpga_bitstream/build_device_bitstream.cpp index 997c83fb6..0b6d01d39 100644 --- a/openfpga/src/fpga_bitstream/build_device_bitstream.cpp +++ b/openfpga/src/fpga_bitstream/build_device_bitstream.cpp @@ -14,7 +14,7 @@ #include "openfpga_naming.h" //#include "build_grid_bitstream.h" -//#include "build_routing_bitstream.h" +#include "build_routing_bitstream.h" #include "build_device_bitstream.h" /* begin namespace openfpga */ @@ -60,9 +60,18 @@ BitstreamManager build_device_bitstream(const VprContext& vpr_ctx, /* Create bitstream from routing architectures */ VTR_LOGV(verbose, "Building routing bitstream...\n"); - //build_routing_bitstream(bitstream_manager, top_block, module_manager, circuit_lib, mux_lib, rr_switches, L_rr_node, L_device_rr_gsb); + build_routing_bitstream(bitstream_manager, top_block, + openfpga_ctx.module_graph(), + openfpga_ctx.arch().circuit_lib, + openfpga_ctx.mux_lib(), + openfpga_ctx.vpr_device_annotation(), + openfpga_ctx.vpr_routing_annotation(), + vpr_ctx.device().rr_graph, + openfpga_ctx.device_rr_gsb()); VTR_LOGV(verbose, "Done\n"); + VTR_LOGV(verbose, "Decoded %lu configuration bits\n", bitstream_manager.bits().size()); + return bitstream_manager; } diff --git a/openfpga/src/fpga_bitstream/build_mux_bitstream.cpp b/openfpga/src/fpga_bitstream/build_mux_bitstream.cpp index 3b7e06354..5de0e3d0e 100644 --- a/openfpga/src/fpga_bitstream/build_mux_bitstream.cpp +++ b/openfpga/src/fpga_bitstream/build_mux_bitstream.cpp @@ -13,17 +13,12 @@ #include "mux_utils.h" #include "decoder_library_utils.h" +#include "mux_bitstream_constants.h" #include "build_mux_bitstream.h" /* begin namespace openfpga */ namespace openfpga { -/* Default path ID of a unused multiplexer */ -#define DEFAULT_PATH_ID -1 - -/* Default path ID of a unused multiplexer when there are no constant inputs*/ -#define DEFAULT_MUX_PATH_ID 0 - /******************************************************************** * Find the default path id of a MUX * This is applied when the path id specified is DEFAULT_PATH_ID, diff --git a/openfpga/src/fpga_bitstream/build_routing_bitstream.cpp b/openfpga/src/fpga_bitstream/build_routing_bitstream.cpp new file mode 100644 index 000000000..e0dd75c2a --- /dev/null +++ b/openfpga/src/fpga_bitstream/build_routing_bitstream.cpp @@ -0,0 +1,442 @@ +/******************************************************************** + * This file includes functions to build bitstream from global routing + * architecture of a mapped FPGA fabric + * We decode the bitstream from configuration of routing multiplexers + * which locate in global routing architecture + *******************************************************************/ +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" + +/* Headers from openfpgautil library */ +#include "openfpga_side_manager.h" + +#include "mux_utils.h" +#include "rr_gsb_utils.h" +#include "openfpga_reserved_words.h" +#include "openfpga_naming.h" +#include "openfpga_rr_graph_utils.h" + +#include "mux_bitstream_constants.h" +#include "build_mux_bitstream.h" +#include "build_routing_bitstream.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * This function generates bitstream for a routing multiplexer + * This function will identify if a node indicates a routing multiplexer + * If not a routing multiplexer, no bitstream is needed here + * If yes, we will generate the bitstream for the routing multiplexer + *******************************************************************/ +static +void build_switch_block_mux_bitstream(BitstreamManager& bitstream_manager, + const ConfigBlockId& mux_mem_block, + const ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const MuxLibrary& mux_lib, + const RRGraph& rr_graph, + const RRNodeId& cur_rr_node, + const std::vector& drive_rr_nodes, + const VprDeviceAnnotation& device_annotation, + const VprRoutingAnnotation& routing_annotation) { + /* Check current rr_node is CHANX or CHANY*/ + VTR_ASSERT( (CHANX == rr_graph.node_type(cur_rr_node)) + || (CHANY == rr_graph.node_type(cur_rr_node))); + + /* Find the input size of the implementation of a routing multiplexer */ + size_t datapath_mux_size = drive_rr_nodes.size(); + + /* Find out which routing path is used in this MUX */ + int path_id = DEFAULT_PATH_ID; + for (size_t inode = 0; inode < drive_rr_nodes.size(); ++inode) { + if (routing_annotation.rr_node_net(drive_rr_nodes[inode]) == routing_annotation.rr_node_net(cur_rr_node)) { + path_id = (int)inode; + break; + } + } + + /* Ensure that our path id makes sense! */ + VTR_ASSERT( (DEFAULT_PATH_ID == path_id) + || ( (DEFAULT_PATH_ID < path_id) && (path_id < (int)datapath_mux_size) ) + ); + + /* Find the circuit model id of the mux, we need its design technology which matters the bitstream generation */ + std::vector driver_switches = get_rr_graph_driver_switches(rr_graph, cur_rr_node); + VTR_ASSERT(1 == driver_switches.size()); + CircuitModelId mux_model = device_annotation.rr_switch_circuit_model(driver_switches[0]); + + /* Generate bitstream depend on both technology and structure of this MUX */ + std::vector mux_bitstream = build_mux_bitstream(circuit_lib, mux_model, mux_lib, datapath_mux_size, path_id); + + /* Find the module in module manager and ensure the bitstream size matches! */ + std::string mem_module_name = generate_mux_subckt_name(circuit_lib, mux_model, datapath_mux_size, std::string(MEMORY_MODULE_POSTFIX)); + ModuleId mux_mem_module = module_manager.find_module(mem_module_name); + VTR_ASSERT (true == module_manager.valid_module_id(mux_mem_module)); + ModulePortId mux_mem_out_port_id = module_manager.find_module_port(mux_mem_module, generate_configuration_chain_data_out_name()); + VTR_ASSERT(mux_bitstream.size() == module_manager.module_port(mux_mem_module, mux_mem_out_port_id).get_width()); + + /* Add the bistream to the bitstream manager */ + for (const bool& bit : mux_bitstream) { + ConfigBitId config_bit = bitstream_manager.add_bit(bit); + /* Link the memory bits to the mux mem block */ + bitstream_manager.add_bit_to_block(mux_mem_block, config_bit); + } +} + +/******************************************************************** + * This function generates bitstream for an interconnection, + * i.e., a routing multiplexer, in a Switch Block + * This function will identify if a node indicates a routing multiplexer + * If not a routing multiplexer, no bitstream is needed here + * If yes, we will generate the bitstream for the routing multiplexer + *******************************************************************/ +static +void build_switch_block_interc_bitstream(BitstreamManager& bitstream_manager, + const ConfigBlockId& sb_configurable_block, + const ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const MuxLibrary& mux_lib, + const RRGraph& rr_graph, + const VprDeviceAnnotation& device_annotation, + const VprRoutingAnnotation& routing_annotation, + const RRGSB& rr_gsb, + const e_side& chan_side, + const size_t& chan_node_id) { + + std::vector driver_rr_nodes; + + /* Get the node */ + const RRNodeId& cur_rr_node = rr_gsb.get_chan_node(chan_side, chan_node_id); + + /* Determine if the interc lies inside a channel wire, that is interc between segments */ + if (false == rr_gsb.is_sb_node_passing_wire(rr_graph, chan_side, chan_node_id)) { + driver_rr_nodes = get_rr_graph_configurable_driver_nodes(rr_graph, cur_rr_node); + /* Special: if there are zero-driver nodes. We skip here */ + if (0 == driver_rr_nodes.size()) { + return; + } + } + + if ( (0 == driver_rr_nodes.size()) + || (0 == driver_rr_nodes.size()) ) { + /* No bitstream generation required by a special direct connection*/ + return; + } else if (1 < driver_rr_nodes.size()) { + /* Create the block denoting the memory instances that drives this node in Switch Block */ + std::string mem_block_name = generate_sb_memory_instance_name(SWITCH_BLOCK_MEM_INSTANCE_PREFIX, chan_side, chan_node_id, std::string("")); + ConfigBlockId mux_mem_block = bitstream_manager.add_block(mem_block_name); + bitstream_manager.add_child_block(sb_configurable_block, mux_mem_block); + /* This is a routing multiplexer! Generate bitstream */ + build_switch_block_mux_bitstream(bitstream_manager, mux_mem_block, module_manager, + circuit_lib, mux_lib, rr_graph, + cur_rr_node, driver_rr_nodes, + device_annotation, routing_annotation); + } /*Nothing should be done else*/ +} + +/******************************************************************** + * This function generates bitstream for a Switch Block + * and add it to the bitstream manager + * This function will spot all the routing multiplexers in a Switch Block + * using a simple but effective rule: + * The fan-in of each output node. + * If there are more than 2 fan-in, there is a routing multiplexer + * + * Note that the output nodes typically spread over all the sides of a Switch Block + * So, we will iterate over that. + *******************************************************************/ +static +void build_switch_block_bitstream(BitstreamManager& bitstream_manager, + const ConfigBlockId& sb_config_block, + const ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const MuxLibrary& mux_lib, + const VprDeviceAnnotation& device_annotation, + const VprRoutingAnnotation& routing_annotation, + const RRGraph& rr_graph, + const RRGSB& rr_gsb) { + + /* Iterate over all the multiplexers */ + for (size_t side = 0; side < rr_gsb.get_num_sides(); ++side) { + SideManager side_manager(side); + for (size_t itrack = 0; itrack < rr_gsb.get_chan_width(side_manager.get_side()); ++itrack) { + VTR_ASSERT( (CHANX == rr_graph.node_type(rr_gsb.get_chan_node(side_manager.get_side(), itrack))) + || (CHANY == rr_graph.node_type(rr_gsb.get_chan_node(side_manager.get_side(), itrack))) ); + /* Only output port indicates a routing multiplexer */ + if (OUT_PORT != rr_gsb.get_chan_node_direction(side_manager.get_side(), itrack)) { + continue; + } + build_switch_block_interc_bitstream(bitstream_manager, sb_config_block, + module_manager, + circuit_lib, mux_lib, rr_graph, + device_annotation, routing_annotation, + rr_gsb, side_manager.get_side(), itrack); + } + } +} + +/******************************************************************** + * This function generates bitstream for a routing multiplexer + * in a Connection block + * This function will identify if a node indicates a routing multiplexer + * If not a routing multiplexer, no bitstream is needed here + * If yes, we will generate the bitstream for the routing multiplexer + *******************************************************************/ +static +void build_connection_block_mux_bitstream(BitstreamManager& bitstream_manager, + const ConfigBlockId& mux_mem_block, + const ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const MuxLibrary& mux_lib, + const VprDeviceAnnotation& device_annotation, + const VprRoutingAnnotation& routing_annotation, + const RRGraph& rr_graph, + const RRNodeId& src_rr_node) { + + /* Find drive_rr_nodes*/ + size_t datapath_mux_size = rr_graph.node_fan_in(src_rr_node); + + /* Configuration bits for MUX*/ + int path_id = DEFAULT_PATH_ID; + int edge_index = 0; + for (const RREdgeId& edge : rr_graph.node_in_edges(src_rr_node)) { + RRNodeId driver_node = rr_graph.edge_src_node(edge); + if (routing_annotation.rr_node_net(driver_node) == routing_annotation.rr_node_net(src_rr_node)) { + path_id = edge_index; + break; + } + edge_index++; + } + + /* Ensure that our path id makes sense! */ + VTR_ASSERT( (DEFAULT_PATH_ID == path_id) + || ( (DEFAULT_PATH_ID < path_id) && (path_id < (int)datapath_mux_size) ) + ); + + + /* Find the circuit model id of the mux, we need its design technology which matters the bitstream generation */ + std::vector driver_switches = get_rr_graph_driver_switches(rr_graph, src_rr_node); + VTR_ASSERT(1 == driver_switches.size()); + CircuitModelId mux_model = device_annotation.rr_switch_circuit_model(driver_switches[0]); + + /* Generate bitstream depend on both technology and structure of this MUX */ + std::vector mux_bitstream = build_mux_bitstream(circuit_lib, mux_model, mux_lib, datapath_mux_size, path_id); + + /* Find the module in module manager and ensure the bitstream size matches! */ + std::string mem_module_name = generate_mux_subckt_name(circuit_lib, mux_model, datapath_mux_size, std::string(MEMORY_MODULE_POSTFIX)); + ModuleId mux_mem_module = module_manager.find_module(mem_module_name); + VTR_ASSERT (true == module_manager.valid_module_id(mux_mem_module)); + ModulePortId mux_mem_out_port_id = module_manager.find_module_port(mux_mem_module, generate_configuration_chain_data_out_name()); + VTR_ASSERT(mux_bitstream.size() == module_manager.module_port(mux_mem_module, mux_mem_out_port_id).get_width()); + + /* Add the bistream to the bitstream manager */ + for (const bool& bit : mux_bitstream) { + ConfigBitId config_bit = bitstream_manager.add_bit(bit); + /* Link the memory bits to the mux mem block */ + bitstream_manager.add_bit_to_block(mux_mem_block, config_bit); + } +} + + +/******************************************************************** + * This function generates bitstream for an interconnection, + * i.e., a routing multiplexer, in a Connection Block + * This function will identify if a node indicates a routing multiplexer + * If not a routing multiplexer, no bitstream is needed here + * If yes, we will generate the bitstream for the routing multiplexer + *******************************************************************/ +static +void build_connection_block_interc_bitstream(BitstreamManager& bitstream_manager, + const ConfigBlockId& cb_configurable_block, + const ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const MuxLibrary& mux_lib, + const VprDeviceAnnotation& device_annotation, + const VprRoutingAnnotation& routing_annotation, + const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const e_side& cb_ipin_side, + const size_t& ipin_index) { + + RRNodeId src_rr_node = rr_gsb.get_ipin_node(cb_ipin_side, ipin_index); + + /* Consider configurable edges only */ + std::vector driver_rr_nodes = get_rr_graph_configurable_driver_nodes(rr_graph, src_rr_node); + + if (1 == driver_rr_nodes.size()) { + /* No bitstream generation required by a special direct connection*/ + } else if (1 < driver_rr_nodes.size()) { + /* Create the block denoting the memory instances that drives this node in Switch Block */ + std::string mem_block_name = generate_cb_memory_instance_name(CONNECTION_BLOCK_MEM_INSTANCE_PREFIX, rr_graph.node_side(src_rr_node), ipin_index, std::string("")); + ConfigBlockId mux_mem_block = bitstream_manager.add_block(mem_block_name); + bitstream_manager.add_child_block(cb_configurable_block, mux_mem_block); + /* This is a routing multiplexer! Generate bitstream */ + build_connection_block_mux_bitstream(bitstream_manager, mux_mem_block, + module_manager, circuit_lib, mux_lib, + device_annotation, routing_annotation, + rr_graph, src_rr_node); + } /*Nothing should be done else*/ +} + +/******************************************************************** + * This function generates bitstream for a Connection Block + * and add it to the bitstream manager + * This function will spot all the routing multiplexers in a Connection Block + * using a simple but effective rule: + * The fan-in of each output node. + * If there are more than 2 fan-in, there is a routing multiplexer + * + * Note that the output nodes are the IPIN rr node in a Connection Block + * So, we will iterate over that. + *******************************************************************/ +static +void build_connection_block_bitstream(BitstreamManager& bitstream_manager, + const ConfigBlockId& cb_configurable_block, + const ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const MuxLibrary& mux_lib, + const VprDeviceAnnotation& device_annotation, + const VprRoutingAnnotation& routing_annotation, + const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const t_rr_type& cb_type) { + + /* Find routing multiplexers on the sides of a Connection block where IPIN nodes locate */ + std::vector cb_sides = rr_gsb.get_cb_ipin_sides(cb_type); + + for (size_t side = 0; side < cb_sides.size(); ++side) { + enum e_side cb_ipin_side = cb_sides[side]; + SideManager side_manager(cb_ipin_side); + for (size_t inode = 0; inode < rr_gsb.get_num_ipin_nodes(cb_ipin_side); ++inode) { + build_connection_block_interc_bitstream(bitstream_manager, cb_configurable_block, + module_manager, circuit_lib, mux_lib, + device_annotation, routing_annotation, + rr_graph, rr_gsb, + cb_ipin_side, inode); + } + } +} + +/******************************************************************** + * Create bitstream for a X-direction or Y-direction Connection Blocks + *******************************************************************/ +static +void build_connection_block_bitstreams(BitstreamManager& bitstream_manager, + const ConfigBlockId& top_configurable_block, + const ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const MuxLibrary& mux_lib, + const VprDeviceAnnotation& device_annotation, + const VprRoutingAnnotation& routing_annotation, + const RRGraph& rr_graph, + const DeviceRRGSB& device_rr_gsb, + const t_rr_type& cb_type) { + + vtr::Point cb_range = device_rr_gsb.get_gsb_range(); + + for (size_t ix = 0; ix < cb_range.x(); ++ix) { + for (size_t iy = 0; iy < cb_range.y(); ++iy) { + const RRGSB& rr_gsb = device_rr_gsb.get_gsb(ix, iy); + /* Check if the connection block exists in the device! + * Some of them do NOT exist due to heterogeneous blocks (height > 1) + * We will skip those modules + */ + if (false == rr_gsb.is_cb_exist(cb_type)) { + continue; + } + /* Skip if the cb does not contain any configuration bits! */ + if (true == connection_block_contain_only_routing_tracks(rr_gsb, cb_type)) { + continue; + } + /* Create a block for the bitstream which corresponds to the Switch block */ + vtr::Point cb_coord(rr_gsb.get_cb_x(cb_type), rr_gsb.get_cb_y(cb_type)); + ConfigBlockId cb_configurable_block = bitstream_manager.add_block(generate_connection_block_module_name(cb_type, cb_coord)); + /* Set switch block as a child of top block */ + bitstream_manager.add_child_block(top_configurable_block, cb_configurable_block); + + build_connection_block_bitstream(bitstream_manager, cb_configurable_block, module_manager, + circuit_lib, mux_lib, + device_annotation, routing_annotation, + rr_graph, + rr_gsb, cb_type); + } + } +} + +/******************************************************************** + * Top-level function to create bitstream for global routing architecture + * Two major tasks: + * 1. Generate bitstreams for Switch Blocks + * 2. Generate bitstreams for both X-direction and Y-direction Connection Blocks + *******************************************************************/ +void build_routing_bitstream(BitstreamManager& bitstream_manager, + const ConfigBlockId& top_configurable_block, + const ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const MuxLibrary& mux_lib, + const VprDeviceAnnotation& device_annotation, + const VprRoutingAnnotation& routing_annotation, + const RRGraph& rr_graph, + const DeviceRRGSB& device_rr_gsb) { + + /* Generate bitstream for each switch blocks + * To organize the bitstream in blocks, we create a block for each switch block + * and give names which are same as they are in top-level module managers + */ + VTR_LOG("Generating bitstream for Switch blocks..."); + vtr::Point sb_range = device_rr_gsb.get_gsb_range(); + for (size_t ix = 0; ix < sb_range.x(); ++ix) { + for (size_t iy = 0; iy < sb_range.y(); ++iy) { + const RRGSB& rr_gsb = device_rr_gsb.get_gsb(ix, iy); + /* Check if the switch block exists in the device! + * Some of them do NOT exist due to heterogeneous blocks (width > 1) + * We will skip those modules + */ + if (false == rr_gsb.is_sb_exist()) { + continue; + } + + /* Create a block for the bitstream which corresponds to the Switch block */ + vtr::Point sb_coord(rr_gsb.get_sb_x(), rr_gsb.get_sb_y()); + ConfigBlockId sb_configurable_block = bitstream_manager.add_block(generate_switch_block_module_name(sb_coord)); + /* Set switch block as a child of top block */ + bitstream_manager.add_child_block(top_configurable_block, sb_configurable_block); + + build_switch_block_bitstream(bitstream_manager, sb_configurable_block, module_manager, + circuit_lib, mux_lib, + device_annotation, routing_annotation, + rr_graph, + rr_gsb); + } + } + VTR_LOG("Done\n"); + + /* Generate bitstream for each connection blocks + * To organize the bitstream in blocks, we create a block for each connection block + * and give names which are same as they are in top-level module managers + */ + VTR_LOG("Generating bitstream for X-direction Connection blocks ..."); + + build_connection_block_bitstreams(bitstream_manager, top_configurable_block, module_manager, + circuit_lib, mux_lib, + device_annotation, routing_annotation, + rr_graph, + device_rr_gsb, CHANX); + VTR_LOG("Done\n"); + + VTR_LOG("Generating bitstream for Y-direction Connection blocks ..."); + + build_connection_block_bitstreams(bitstream_manager, top_configurable_block, module_manager, + circuit_lib, mux_lib, + device_annotation, routing_annotation, + rr_graph, + device_rr_gsb, CHANY); + VTR_LOG("Done"); + +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_bitstream/build_routing_bitstream.h b/openfpga/src/fpga_bitstream/build_routing_bitstream.h new file mode 100644 index 000000000..2337601fe --- /dev/null +++ b/openfpga/src/fpga_bitstream/build_routing_bitstream.h @@ -0,0 +1,39 @@ +/******************************************************************** + * Header file for build_routing_bitstream.cpp + *******************************************************************/ +#ifndef BUILD_ROUTING_BITSTREAM_H +#define BUILD_ROUTING_BITSTREAM_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include "bitstream_manager.h" +#include "vpr_context.h" +#include "module_manager.h" +#include "circuit_library.h" +#include "mux_library.h" +#include "device_rr_gsb.h" +#include "vpr_device_annotation.h" +#include "vpr_routing_annotation.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void build_routing_bitstream(BitstreamManager& bitstream_manager, + const ConfigBlockId& top_configurable_block, + const ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const MuxLibrary& mux_lib, + const VprDeviceAnnotation& device_annotation, + const VprRoutingAnnotation& routing_annotation, + const RRGraph& rr_graph, + const DeviceRRGSB& device_rr_gsb); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_bitstream/mux_bitstream_constants.h b/openfpga/src/fpga_bitstream/mux_bitstream_constants.h new file mode 100644 index 000000000..73d707ad5 --- /dev/null +++ b/openfpga/src/fpga_bitstream/mux_bitstream_constants.h @@ -0,0 +1,15 @@ +#ifndef MUX_BITSTREAM_CONSTANTS_H +#define MUX_BITSTREAM_CONSTANTS_H + +/* begin namespace openfpga */ +namespace openfpga { + +/* Default path ID of a unused multiplexer */ +#define DEFAULT_PATH_ID -1 + +/* Default path ID of a unused multiplexer when there are no constant inputs*/ +#define DEFAULT_MUX_PATH_ID 0 + +} /* end namespace openfpga */ + +#endif From 8e9660b816238c76f6269aafe819be3e573ece7c Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 24 Feb 2020 16:09:29 -0700 Subject: [PATCH 202/645] add mapped block fast look-up as placement annotation --- .../src/annotation/annotate_placement.cpp | 34 +++++++++++++ openfpga/src/annotation/annotate_placement.h | 24 +++++++++ .../annotation/vpr_placement_annotation.cpp | 49 +++++++++++++++++++ .../src/annotation/vpr_placement_annotation.h | 48 ++++++++++++++++++ openfpga/src/base/openfpga_context.h | 6 +++ openfpga/src/base/openfpga_link_arch.cpp | 11 ++++- openfpga/src/fabric/build_grid_modules.cpp | 34 +++---------- 7 files changed, 177 insertions(+), 29 deletions(-) create mode 100644 openfpga/src/annotation/annotate_placement.cpp create mode 100644 openfpga/src/annotation/annotate_placement.h create mode 100644 openfpga/src/annotation/vpr_placement_annotation.cpp create mode 100644 openfpga/src/annotation/vpr_placement_annotation.h diff --git a/openfpga/src/annotation/annotate_placement.cpp b/openfpga/src/annotation/annotate_placement.cpp new file mode 100644 index 000000000..adc83d5ff --- /dev/null +++ b/openfpga/src/annotation/annotate_placement.cpp @@ -0,0 +1,34 @@ +/******************************************************************** + * This file includes functions that are used to annotate pb_graph_node + * and pb_graph_pins from VPR to OpenFPGA + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" +#include "vtr_geometry.h" + +#include "annotate_placement.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Assign mapped blocks to grid locations + * This is used by bitstream generator mainly as a fast look-up to + * get mapped blocks with a given coordinate + *******************************************************************/ +void annotate_mapped_blocks(const DeviceContext& device_ctx, + const ClusteringContext& cluster_ctx, + const PlacementContext& place_ctx, + VprPlacementAnnotation& place_annotation) { + VTR_LOG("Building annotation for mapped blocks on grid locations..."); + + place_annotation.init_mapped_blocks(device_ctx.grid); + for (const ClusterBlockId& blk_id : cluster_ctx.clb_nlist.blocks()) { + vtr::Point grid_coord(place_ctx.block_locs[blk_id].loc.x, place_ctx.block_locs[blk_id].loc.y); + place_annotation.add_mapped_block(grid_coord, place_ctx.block_locs[blk_id].loc.z, blk_id); + } + VTR_LOG("Done\n"); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/annotation/annotate_placement.h b/openfpga/src/annotation/annotate_placement.h new file mode 100644 index 000000000..fd47bb56d --- /dev/null +++ b/openfpga/src/annotation/annotate_placement.h @@ -0,0 +1,24 @@ +#ifndef ANNOTATE_PLACEMENT_H +#define ANNOTATE_PLACEMENT_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "vpr_context.h" +#include "vpr_placement_annotation.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void annotate_mapped_blocks(const DeviceContext& device_ctx, + const ClusteringContext& cluster_ctx, + const PlacementContext& place_ctx, + VprPlacementAnnotation& place_annotation); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/annotation/vpr_placement_annotation.cpp b/openfpga/src/annotation/vpr_placement_annotation.cpp new file mode 100644 index 000000000..779707250 --- /dev/null +++ b/openfpga/src/annotation/vpr_placement_annotation.cpp @@ -0,0 +1,49 @@ +/************************************************************************ + * Member functions for class VprPlacementAnnotation + ***********************************************************************/ +#include "vtr_log.h" +#include "vtr_assert.h" +#include "vpr_placement_annotation.h" + +/* namespace openfpga begins */ +namespace openfpga { + +/************************************************************************ + * Constructors + ***********************************************************************/ + +/************************************************************************ + * Public accessors + ***********************************************************************/ +std::vector VprPlacementAnnotation::grid_blocks(const vtr::Point& grid_coord) const { + return blocks_[grid_coord.x()][grid_coord.y()]; +} + +/************************************************************************ + * Public mutators + ***********************************************************************/ +void VprPlacementAnnotation::init_mapped_blocks(const DeviceGrid& grids) { + /* Size the block array with grid sizes */ + blocks_.resize({grids.width(), grids.height()}); + + /* Resize the number of blocks allowed per grid by the capacity of the type */ + for (size_t x = 0; x < grids.width(); ++x) { + for (size_t y = 0; y < grids.height(); ++y) { + /* Deposit invalid ids and we will fill later */ + blocks_[x][y].resize(grids[x][y].type->capacity, ClusterBlockId::INVALID()); + } + } +} + +void VprPlacementAnnotation::add_mapped_block(const vtr::Point& grid_coord, + const size_t& z, + const ClusterBlockId& mapped_block) { + VTR_ASSERT(z < grid_blocks(grid_coord).size()); + if (ClusterBlockId::INVALID() != blocks_[grid_coord.x()][grid_coord.y()][z]) { + VTR_LOG("Override mapped blocks at grid[%lu][%lu][%lu]!\n", + grid_coord.x(), grid_coord.y(), z); + } + blocks_[grid_coord.x()][grid_coord.y()][z] = mapped_block; +} + +} /* End namespace openfpga*/ diff --git a/openfpga/src/annotation/vpr_placement_annotation.h b/openfpga/src/annotation/vpr_placement_annotation.h new file mode 100644 index 000000000..6a5e9c134 --- /dev/null +++ b/openfpga/src/annotation/vpr_placement_annotation.h @@ -0,0 +1,48 @@ +#ifndef VPR_PLACEMENT_ANNOTATION_H +#define VPR_PLACEMENT_ANNOTATION_H + +/******************************************************************** + * Include header files required by the data structure definition + *******************************************************************/ +#include + +/* Header from vtrutil library */ +#include "vtr_geometry.h" + +/* Header from vpr library */ +#include "device_grid.h" +#include "clustered_netlist.h" + +/* Begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * This is the critical data structure to annotate placement results + * in VPR context + *******************************************************************/ +class VprPlacementAnnotation { + public: /* Public accessors */ + std::vector grid_blocks(const vtr::Point& grid_coord) const; + public: /* Public mutators */ + void init_mapped_blocks(const DeviceGrid& grids); + void add_mapped_block(const vtr::Point& grid_coord, + const size_t& z, const ClusterBlockId& mapped_block); + private: /* Internal data */ + /* A direct mapping show each mapped/unmapped blocks in grids + * The blocks_ array represents each grid on the FPGA fabric + * For example, block_[x][y] showed the mapped/unmapped blocks + * at grid[x][y]. The third coordinate 'z' is the index of the same + * type of blocks in the grids. This is mainly applied to I/O + * blocks where you may have >1 I/O in a grid + * + * Note that this is different from the grid blocks in PlacementContext + * VPR considers only mapped blocks while this annotation + * considers both unmapped and mapped blocks + * Unmapped blocks will be labelled as an invalid id in the vector + */ + vtr::Matrix> blocks_; +}; + +} /* End namespace openfpga*/ + +#endif diff --git a/openfpga/src/base/openfpga_context.h b/openfpga/src/base/openfpga_context.h index 06c70cd24..9c685b247 100644 --- a/openfpga/src/base/openfpga_context.h +++ b/openfpga/src/base/openfpga_context.h @@ -6,6 +6,7 @@ #include "vpr_netlist_annotation.h" #include "vpr_device_annotation.h" #include "vpr_clustering_annotation.h" +#include "vpr_placement_annotation.h" #include "vpr_routing_annotation.h" #include "mux_library.h" #include "tile_direct.h" @@ -47,6 +48,7 @@ class OpenfpgaContext : public Context { const openfpga::VprDeviceAnnotation& vpr_device_annotation() const { return vpr_device_annotation_; } const openfpga::VprNetlistAnnotation& vpr_netlist_annotation() const { return vpr_netlist_annotation_; } const openfpga::VprClusteringAnnotation& vpr_clustering_annotation() const { return vpr_clustering_annotation_; } + const openfpga::VprPlacementAnnotation& vpr_placement_annotation() const { return vpr_placement_annotation_; } const openfpga::VprRoutingAnnotation& vpr_routing_annotation() const { return vpr_routing_annotation_; } const openfpga::DeviceRRGSB& device_rr_gsb() const { return device_rr_gsb_; } const openfpga::MuxLibrary& mux_lib() const { return mux_lib_; } @@ -59,6 +61,7 @@ class OpenfpgaContext : public Context { openfpga::VprDeviceAnnotation& mutable_vpr_device_annotation() { return vpr_device_annotation_; } openfpga::VprNetlistAnnotation& mutable_vpr_netlist_annotation() { return vpr_netlist_annotation_; } openfpga::VprClusteringAnnotation& mutable_vpr_clustering_annotation() { return vpr_clustering_annotation_; } + openfpga::VprPlacementAnnotation& mutable_vpr_placement_annotation() { return vpr_placement_annotation_; } openfpga::VprRoutingAnnotation& mutable_vpr_routing_annotation() { return vpr_routing_annotation_; } openfpga::DeviceRRGSB& mutable_device_rr_gsb() { return device_rr_gsb_; } openfpga::MuxLibrary& mutable_mux_lib() { return mux_lib_; } @@ -79,6 +82,9 @@ class OpenfpgaContext : public Context { /* Pin net fix to cluster results */ openfpga::VprClusteringAnnotation vpr_clustering_annotation_; + /* Placement results */ + openfpga::VprPlacementAnnotation vpr_placement_annotation_; + /* Routing results annotation */ openfpga::VprRoutingAnnotation vpr_routing_annotation_; diff --git a/openfpga/src/base/openfpga_link_arch.cpp b/openfpga/src/base/openfpga_link_arch.cpp index fcaa1f128..9dc1e38c7 100644 --- a/openfpga/src/base/openfpga_link_arch.cpp +++ b/openfpga/src/base/openfpga_link_arch.cpp @@ -15,6 +15,7 @@ #include "annotate_rr_graph.h" #include "mux_library_builder.h" #include "build_tile_direct.h" +#include "annotate_placement.h" #include "openfpga_link_arch.h" /* Include global variables of VPR */ @@ -109,8 +110,14 @@ void link_arch(OpenfpgaContext& openfpga_ctx, const_cast(openfpga_ctx)); /* Build tile direct annotation */ - openfpga_ctx.mutable_tile_direct() = build_device_tile_direct(g_vpr_ctx.device(), - openfpga_ctx.arch().arch_direct); + openfpga_ctx.mutable_tile_direct() = build_device_tile_direct(g_vpr_ctx.device(), + openfpga_ctx.arch().arch_direct); + + /* Annotate placement results */ + annotate_mapped_blocks(g_vpr_ctx.device(), + g_vpr_ctx.clustering(), + g_vpr_ctx.placement(), + openfpga_ctx.mutable_vpr_placement_annotation()); } } /* end namespace openfpga */ diff --git a/openfpga/src/fabric/build_grid_modules.cpp b/openfpga/src/fabric/build_grid_modules.cpp index 39ca8e266..17ecc67e2 100644 --- a/openfpga/src/fabric/build_grid_modules.cpp +++ b/openfpga/src/fabric/build_grid_modules.cpp @@ -914,35 +914,15 @@ void build_physical_tile_module(ModuleManager& module_manager, ModuleId grid_module = module_manager.add_module(grid_module_name); VTR_ASSERT(true == module_manager.valid_module_id(grid_module)); - /* Now each physical tile may have a number of diffrent logical blocks - * We assume the following organization: - * - * Physical tile - * +----------------------- - * | - * | pb_typeA[0] - from equivalent site[A] - * | +-------------------- - * | | - * | +-------------------- - * | - * | pb_typeB[0] - from equivalent site[B] - * | +-------------------- - * | | - * | +-------------------- - * ... ... - * | pb_typeA[capacity - 1] - from equivalent site[A] - * | +-------------------- - * | | - * | +-------------------- - * | - * | pb_typeB[capacity - 1] - from equivalent site[B] - * | +-------------------- - * | | - * | +-------------------- - * | - * +----------------------- + /* Now each physical tile may have a number of logical blocks + * OpenFPGA only considers the physical implementation of the tiles. + * So, we do not allow multiple equivalent sites to be defined + * under a physical tile type. + * If you need different equivalent sites, you can always define + * it as a mode under a */ for (int iz = 0; iz < phy_block_type->capacity; ++iz) { + VTR_ASSERT(1 == phy_block_type->equivalent_sites.size()); for (t_logical_block_type_ptr lb_type : phy_block_type->equivalent_sites) { /* Bypass empty pb_graph */ if (nullptr == lb_type->pb_graph_head) { From 04c69d30c287f702df15eaae9e69cbbc39ffc697 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 24 Feb 2020 19:38:02 -0700 Subject: [PATCH 203/645] start adding grid bitstream builder. TODO: lut and interconnect bitstream decoding --- .../fpga_bitstream/build_grid_bitstream.cpp | 367 ++++++++++++++++++ .../src/fpga_bitstream/build_grid_bitstream.h | 37 ++ openfpga/src/repack/physical_pb.cpp | 22 ++ openfpga/src/repack/physical_pb.h | 5 + 4 files changed, 431 insertions(+) create mode 100644 openfpga/src/fpga_bitstream/build_grid_bitstream.cpp create mode 100644 openfpga/src/fpga_bitstream/build_grid_bitstream.h diff --git a/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp b/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp new file mode 100644 index 000000000..0ed29be12 --- /dev/null +++ b/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp @@ -0,0 +1,367 @@ +/******************************************************************** + * This file includes functions that are used for building bitstreams + * for grids (CLBs, heterogenerous blocks, I/Os, etc.) + *******************************************************************/ +#include + +/* Headers from vtrutil library */ +#include "vtr_log.h" +#include "vtr_assert.h" +#include "vtr_time.h" + +/* Headers from vpr library */ +#include "vpr_utils.h" + +#include "mux_utils.h" + +#include "circuit_library_utils.h" + +#include "openfpga_reserved_words.h" +#include "openfpga_naming.h" +#include "mux_bitstream_constants.h" +#include "pb_type_utils.h" + +#include "build_mux_bitstream.h" +//#include "build_lut_bitstream.h" +#include "build_grid_bitstream.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Decode mode bits "01..." to a bitstream vector + *******************************************************************/ +static +std::vector generate_mode_select_bitstream(const std::vector& mode_bits) { + std::vector mode_select_bitstream; + + for (const size_t& mode_bit : mode_bits) { + /* Error out for unexpected bits */ + VTR_ASSERT((0 == mode_bit) || (1 == mode_bit)); + mode_select_bitstream.push_back(1 == mode_bit); + } + + return mode_select_bitstream; +} + +/******************************************************************** + * Generate bitstream for a primitive node and add it to bitstream manager + *******************************************************************/ +static +void build_primitive_bitstream(BitstreamManager& bitstream_manager, + const ConfigBlockId& parent_configurable_block, + const ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const VprDeviceAnnotation& device_annotation, + const PhysicalPb& physical_pb, + const PhysicalPbId& primitive_pb_id, + t_pb_type* primitive_pb_type) { + + /* Ensure a valid physical pritimive pb */ + if (nullptr == primitive_pb_type) { + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid primitive_pb_type!\n"); + exit(1); + } + + CircuitModelId primitive_model = device_annotation.pb_type_circuit_model(primitive_pb_type); + VTR_ASSERT(CircuitModelId::INVALID() != primitive_model); + VTR_ASSERT( (CIRCUIT_MODEL_IOPAD == circuit_lib.model_type(primitive_model)) + || (CIRCUIT_MODEL_HARDLOGIC == circuit_lib.model_type(primitive_model)) + || (CIRCUIT_MODEL_FF == circuit_lib.model_type(primitive_model)) ); + + /* Find SRAM ports for mode-selection */ + std::vector primitive_mode_select_ports = find_circuit_mode_select_sram_ports(circuit_lib, primitive_model); + + /* We may have a port for mode select or not. */ + VTR_ASSERT( (0 == primitive_mode_select_ports.size()) + || (1 == primitive_mode_select_ports.size()) ); + + /* Generate bitstream for mode-select ports */ + if (0 == primitive_mode_select_ports.size()) { + return; /* Nothing to do, return directly */ + } + + std::vector mode_select_bitstream; + if (true == physical_pb.valid_pb_id(primitive_pb_id)) { + mode_select_bitstream = generate_mode_select_bitstream(physical_pb.mode_bits(primitive_pb_id)); + } else { /* get default mode_bits */ + mode_select_bitstream = generate_mode_select_bitstream(device_annotation.pb_type_mode_bits(primitive_pb_type)); + } + + /* Ensure the length of bitstream matches the side of memory circuits */ + std::vector sram_models = find_circuit_sram_models(circuit_lib, primitive_model); + VTR_ASSERT(1 == sram_models.size()); + std::string mem_block_name = generate_memory_module_name(circuit_lib, primitive_model, sram_models[0], std::string(MEMORY_MODULE_POSTFIX)); + ModuleId mem_module = module_manager.find_module(mem_block_name); + VTR_ASSERT (true == module_manager.valid_module_id(mem_module)); + ModulePortId mem_out_port_id = module_manager.find_module_port(mem_module, generate_configuration_chain_data_out_name()); + VTR_ASSERT(mode_select_bitstream.size() == module_manager.module_port(mem_module, mem_out_port_id).get_width()); + + /* Create a block for the bitstream which corresponds to the memory module associated to the LUT */ + ConfigBlockId mem_block = bitstream_manager.add_block(mem_block_name); + bitstream_manager.add_child_block(parent_configurable_block, mem_block); + + /* Add the bitstream to the bitstream manager */ + for (const bool& bit : mode_select_bitstream) { + ConfigBitId config_bit = bitstream_manager.add_bit(bit); + /* Link the memory bits to the mux mem block */ + bitstream_manager.add_bit_to_block(mem_block, config_bit); + } +} + +/******************************************************************** + * This function generates bitstream for a physical block, which is + * a child block of a grid + * This function will follow a recursive way in generating bitstreams + * It will follow the same sequence in visiting all the sub blocks + * in a physical as we did during module generation + * + * Note: if you want to bind your bitstream with a FPGA fabric generated by FPGA-X2P + * Please follow the same sequence in visiting pb_graph nodes!!! + * For more details, you may refer to function rec_build_physical_block_modules() + *******************************************************************/ +static +void rec_build_physical_block_bitstream(BitstreamManager& bitstream_manager, + const ConfigBlockId& parent_configurable_block, + const ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const MuxLibrary& mux_lib, + const VprDeviceAnnotation& device_annotation, + const e_side& border_side, + const PhysicalPb& physical_pb, + const PhysicalPbId& pb_id, + t_pb_graph_node* physical_pb_graph_node, + const size_t& pb_graph_node_index) { + /* Get the physical pb_type that is linked to the pb_graph node */ + t_pb_type* physical_pb_type = physical_pb_graph_node->pb_type; + + /* Find the mode that define_idle_mode*/ + t_mode* physical_mode = device_annotation.physical_mode(physical_pb_type); + + /* Create a block for the physical block under the grid block in bitstream manager */ + std::string pb_block_name = generate_physical_block_instance_name(physical_pb_type, pb_graph_node_index); + ConfigBlockId pb_configurable_block = bitstream_manager.add_block(pb_block_name); + bitstream_manager.add_child_block(parent_configurable_block, pb_configurable_block); + + /* Recursively finish all the child pb_types*/ + if (false == is_primitive_pb_type(physical_pb_type)) { + for (int ipb = 0; ipb < physical_mode->num_pb_type_children; ++ipb) { + for (int jpb = 0; jpb < physical_mode->pb_type_children[ipb].num_pb; ++jpb) { + PhysicalPbId child_pb = PhysicalPbId::INVALID(); + /* Find the child pb that is mapped, and the mapping info is not stored in the physical mode ! */ + if (true == physical_pb.valid_pb_id(pb_id)) { + child_pb = physical_pb.child(pb_id, &(physical_mode->pb_type_children[ipb]), jpb); + VTR_ASSERT(true == physical_pb.valid_pb_id(child_pb)); + } + /* Go recursively */ + rec_build_physical_block_bitstream(bitstream_manager, pb_configurable_block, + module_manager, circuit_lib, mux_lib, + device_annotation, + border_side, + physical_pb, child_pb, + &(physical_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ipb][jpb]), + jpb); + } + } + } + + /* Check if this has defined a circuit_model*/ + if (true == is_primitive_pb_type(physical_pb_type)) { + switch (physical_pb_type->class_type) { + case LUT_CLASS: + /* Special case for LUT !!! + * Mapped logical block information is stored in child_pbs of this pb!!! + */ + //build_lut_bitstream(bitstream_manager, pb_configurable_block, + // module_manager, circuit_lib, mux_lib, + // physical_pb, pb_id, physical_pb_type); + break; + case LATCH_CLASS: + case UNKNOWN_CLASS: + case MEMORY_CLASS: + /* For other types of blocks, we can apply a generic therapy */ + build_primitive_bitstream(bitstream_manager, pb_configurable_block, + module_manager, circuit_lib, device_annotation, + physical_pb, pb_id, physical_pb_type); + break; + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Unknown class type of pb_type '%s'!\n", + physical_pb_type->name); + exit(1); + } + /* Finish for primitive node, return */ + return; + } + + /* Generate the bitstream for the interconnection in this physical block */ + //build_physical_block_interc_bitstream(bitstream_manager, pb_configurable_block, + // module_manager, circuit_lib, mux_lib, + // physical_pb_graph_node, physical_pb, + // pb_id, physical_mode_index); +} + +/******************************************************************** + * This function generates bitstream for a grid, which could be a + * CLB, a heterogenerous block, an I/O, etc. + * Note that each grid may contain a number of physical blocks, + * this function will iterate over them + *******************************************************************/ +static +void build_physical_block_bitstream(BitstreamManager& bitstream_manager, + const ConfigBlockId& top_block, + const ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const MuxLibrary& mux_lib, + const VprDeviceAnnotation& device_annotation, + const VprClusteringAnnotation& cluster_annotation, + const VprPlacementAnnotation& place_annotation, + const DeviceGrid& grids, + const vtr::Point& grid_coord, + const e_side& border_side) { + /* Create a block for the grid in bitstream manager */ + t_physical_tile_type_ptr grid_type = grids[grid_coord.x()][grid_coord.y()].type; + std::string grid_module_name_prefix(GRID_MODULE_NAME_PREFIX); + std::string grid_block_name = generate_grid_block_instance_name(grid_module_name_prefix, std::string(grid_type->name), + is_io_type(grid_type), border_side, grid_coord); + ConfigBlockId grid_configurable_block = bitstream_manager.add_block(grid_block_name); + bitstream_manager.add_child_block(top_block, grid_configurable_block); + + /* Iterate over the capacity of the grid + * Now each physical tile may have a number of logical blocks + * OpenFPGA only considers the physical implementation of the tiles. + * So, we do not allow multiple equivalent sites to be defined + * under a physical tile type. + * If you need different equivalent sites, you can always define + * it as a mode under a + */ + for (size_t z = 0; z < place_annotation.grid_blocks(grid_coord).size(); ++z) { + VTR_ASSERT(1 == grid_type->equivalent_sites.size()); + for (t_logical_block_type_ptr lb_type : grid_type->equivalent_sites) { + /* Bypass empty pb_graph */ + if (nullptr == lb_type->pb_graph_head) { + continue; + } + + if (ClusterBlockId::INVALID() == place_annotation.grid_blocks(grid_coord)[z]) { + /* Recursively traverse the pb_graph and generate bitstream */ + rec_build_physical_block_bitstream(bitstream_manager, grid_configurable_block, + module_manager, circuit_lib, mux_lib, + device_annotation, border_side, + PhysicalPb(), PhysicalPbId::INVALID(), + lb_type->pb_graph_head, z); + } else { + const PhysicalPb& phy_pb = cluster_annotation.physical_pb(place_annotation.grid_blocks(grid_coord)[z]); + + /* Get the top-level node of the pb_graph */ + t_pb_graph_node* pb_graph_head = lb_type->pb_graph_head; + VTR_ASSERT(nullptr != pb_graph_head); + const PhysicalPbId& top_pb_id = phy_pb.find_pb(pb_graph_head); + + /* Recursively traverse the pb_graph and generate bitstream */ + rec_build_physical_block_bitstream(bitstream_manager, grid_configurable_block, + module_manager, circuit_lib, mux_lib, + device_annotation, border_side, + phy_pb, top_pb_id, pb_graph_head, z); + } + } + } +} + + +/******************************************************************** + * Top-level function of this file: + * Generate bitstreams for all the grids, including + * 1. core grids that sit in the center of the fabric + * 2. side grids (I/O grids) that sit in the borders for the fabric + *******************************************************************/ +void build_grid_bitstream(BitstreamManager& bitstream_manager, + const ConfigBlockId& top_block, + const ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const MuxLibrary& mux_lib, + const DeviceGrid& grids, + const VprDeviceAnnotation& device_annotation, + const VprClusteringAnnotation& cluster_annotation, + const VprPlacementAnnotation& place_annotation, + const bool& verbose) { + + VTR_LOGV(verbose, "Generating bitstream for core grids..."); + + /* Generate bitstream for the core logic block one by one */ + for (size_t ix = 1; ix < grids.width() - 1; ++ix) { + for (size_t iy = 1; iy < grids.height() - 1; ++iy) { + /* Bypass EMPTY grid */ + if (true == is_empty_type(grids[ix][iy].type)) { + continue; + } + /* Skip width > 1 or height > 1 tiles (mostly heterogeneous blocks) */ + if ( (0 < grids[ix][iy].width_offset) + || (0 < grids[ix][iy].height_offset) ) { + continue; + } + /* We should not meet any I/O grid */ + VTR_ASSERT(true != is_io_type(grids[ix][iy].type)); + /* Add a grid module to top_module*/ + vtr::Point grid_coord(ix, iy); + build_physical_block_bitstream(bitstream_manager, top_block, module_manager, + circuit_lib, mux_lib, + device_annotation, cluster_annotation, + place_annotation, + grids, grid_coord, NUM_SIDES); + } + } + VTR_LOGV(verbose, "Done\n"); + + VTR_LOGV(verbose, "Generating bitstream for I/O grids..."); + + /* Create the coordinate range for each side of FPGA fabric */ + std::vector io_sides{TOP, RIGHT, BOTTOM, LEFT}; + std::map>> io_coordinates; + + /* TOP side*/ + for (size_t ix = 1; ix < grids.width() - 1; ++ix) { + io_coordinates[TOP].push_back(vtr::Point(ix, grids.height() - 1)); + } + + /* RIGHT side */ + for (size_t iy = 1; iy < grids.height() - 1; ++iy) { + io_coordinates[RIGHT].push_back(vtr::Point(grids.width() - 1, iy)); + } + + /* BOTTOM side*/ + for (size_t ix = 1; ix < grids.width() - 1; ++ix) { + io_coordinates[BOTTOM].push_back(vtr::Point(ix, 0)); + } + + /* LEFT side */ + for (size_t iy = 1; iy < grids.height() - 1; ++iy) { + io_coordinates[LEFT].push_back(vtr::Point(0, iy)); + } + + /* Add instances of I/O grids to top_module */ + for (const e_side& io_side : io_sides) { + for (const vtr::Point& io_coordinate : io_coordinates[io_side]) { + /* Bypass EMPTY grid */ + if (true == is_empty_type(grids[io_coordinate.x()][io_coordinate.y()].type)) { + continue; + } + /* Skip height > 1 tiles (mostly heterogeneous blocks) */ + if ( (0 < grids[io_coordinate.x()][io_coordinate.y()].width_offset) + || (0 < grids[io_coordinate.x()][io_coordinate.y()].height_offset) ) { + continue; + } + /* We should not meet any I/O grid */ + VTR_ASSERT(true == is_io_type(grids[io_coordinate.x()][io_coordinate.y()].type)); + build_physical_block_bitstream(bitstream_manager, top_block, module_manager, + circuit_lib, mux_lib, + device_annotation, cluster_annotation, + place_annotation, + grids, io_coordinate, io_side); + } + } + VTR_LOGV(verbose, "Done\n"); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_bitstream/build_grid_bitstream.h b/openfpga/src/fpga_bitstream/build_grid_bitstream.h new file mode 100644 index 000000000..6f192a9b7 --- /dev/null +++ b/openfpga/src/fpga_bitstream/build_grid_bitstream.h @@ -0,0 +1,37 @@ +#ifndef BUILD_GRID_BITSTREAM_H +#define BUILD_GRID_BITSTREAM_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include "device_grid.h" +#include "bitstream_manager.h" +#include "module_manager.h" +#include "circuit_library.h" +#include "mux_library.h" +#include "vpr_device_annotation.h" +#include "vpr_clustering_annotation.h" +#include "vpr_placement_annotation.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void build_grid_bitstream(BitstreamManager& bitstream_manager, + const ConfigBlockId& top_block, + const ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const MuxLibrary& mux_lib, + const DeviceGrid& grids, + const VprDeviceAnnotation& device_annotation, + const VprClusteringAnnotation& cluster_annotation, + const VprPlacementAnnotation& place_annotation, + const bool& verbose); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/repack/physical_pb.cpp b/openfpga/src/repack/physical_pb.cpp index 647c527af..f64a3d29a 100644 --- a/openfpga/src/repack/physical_pb.cpp +++ b/openfpga/src/repack/physical_pb.cpp @@ -36,6 +36,23 @@ PhysicalPbId PhysicalPb::parent(const PhysicalPbId& pb) const { return parent_pbs_[pb]; } +PhysicalPbId PhysicalPb::child(const PhysicalPbId& pb, + const t_pb_type* pb_type, + const size_t& index) const { + VTR_ASSERT(true == valid_pb_id(pb)); + if (0 < child_pbs_[pb].count(pb_type)) { + if (index < child_pbs_[pb].at(pb_type).size()) { + return child_pbs_[pb].at(pb_type)[index]; + } + } + return PhysicalPbId::INVALID(); +} + +std::vector PhysicalPb::atom_blocks(const PhysicalPbId& pb) const { + VTR_ASSERT(true == valid_pb_id(pb)); + return atom_blocks_[pb]; +} + AtomNetId PhysicalPb::pb_graph_pin_atom_net(const PhysicalPbId& pb, const t_pb_graph_pin* pb_graph_pin) const { VTR_ASSERT(true == valid_pb_id(pb)); @@ -47,6 +64,11 @@ AtomNetId PhysicalPb::pb_graph_pin_atom_net(const PhysicalPbId& pb, return AtomNetId::INVALID(); } +std::vector PhysicalPb::mode_bits(const PhysicalPbId& pb) const { + VTR_ASSERT(true == valid_pb_id(pb)); + return mode_bits_[pb]; +} + /****************************************************************************** * Private Mutators ******************************************************************************/ diff --git a/openfpga/src/repack/physical_pb.h b/openfpga/src/repack/physical_pb.h index 9e4dff206..2ad29c2cd 100644 --- a/openfpga/src/repack/physical_pb.h +++ b/openfpga/src/repack/physical_pb.h @@ -42,8 +42,13 @@ class PhysicalPb { std::string name(const PhysicalPbId& pb) const; PhysicalPbId find_pb(const t_pb_graph_node* name) const; PhysicalPbId parent(const PhysicalPbId& pb) const; + PhysicalPbId child(const PhysicalPbId& pb, + const t_pb_type* pb_type, + const size_t& index) const; + std::vector atom_blocks(const PhysicalPbId& pb) const; AtomNetId pb_graph_pin_atom_net(const PhysicalPbId& pb, const t_pb_graph_pin* pb_graph_pin) const; + std::vector mode_bits(const PhysicalPbId& pb) const; public: /* Public mutators */ PhysicalPbId create_pb(const t_pb_graph_node* pb_graph_node); void add_child(const PhysicalPbId& parent, From 2c44c70557cb17ff9509b7e5eb4ba3e6795c387d Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 25 Feb 2020 00:28:06 -0700 Subject: [PATCH 204/645] bring pb interconnection bitstream generation online --- .../fpga_bitstream/build_device_bitstream.cpp | 12 +- .../fpga_bitstream/build_grid_bitstream.cpp | 248 +++++++++++++++++- .../build_routing_bitstream.cpp | 2 +- 3 files changed, 249 insertions(+), 13 deletions(-) diff --git a/openfpga/src/fpga_bitstream/build_device_bitstream.cpp b/openfpga/src/fpga_bitstream/build_device_bitstream.cpp index 0b6d01d39..a5cde4853 100644 --- a/openfpga/src/fpga_bitstream/build_device_bitstream.cpp +++ b/openfpga/src/fpga_bitstream/build_device_bitstream.cpp @@ -13,7 +13,7 @@ #include "openfpga_naming.h" -//#include "build_grid_bitstream.h" +#include "build_grid_bitstream.h" #include "build_routing_bitstream.h" #include "build_device_bitstream.h" @@ -55,7 +55,15 @@ BitstreamManager build_device_bitstream(const VprContext& vpr_ctx, /* Create bitstream from grids */ VTR_LOGV(verbose, "Building grid bitstream...\n"); - //build_grid_bitstream(bitstream_manager, top_block, module_manager, circuit_lib, mux_lib, device_size, grids); + build_grid_bitstream(bitstream_manager, top_block, + openfpga_ctx.module_graph(), + openfpga_ctx.arch().circuit_lib, + openfpga_ctx.mux_lib(), + vpr_ctx.device().grid, + openfpga_ctx.vpr_device_annotation(), + openfpga_ctx.vpr_clustering_annotation(), + openfpga_ctx.vpr_placement_annotation(), + verbose); VTR_LOGV(verbose, "Done\n"); /* Create bitstream from routing architectures */ diff --git a/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp b/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp index 0ed29be12..d344d8880 100644 --- a/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp +++ b/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp @@ -12,10 +12,12 @@ /* Headers from vpr library */ #include "vpr_utils.h" +#include "pb_graph_utils.h" #include "mux_utils.h" #include "circuit_library_utils.h" +#include "openfpga_interconnect_types.h" #include "openfpga_reserved_words.h" #include "openfpga_naming.h" #include "mux_bitstream_constants.h" @@ -110,6 +112,229 @@ void build_primitive_bitstream(BitstreamManager& bitstream_manager, } } +/******************************************************************** + * This function generates bitstream for a programmable routing + * multiplexer which drives an output pin of physical_pb_graph_node and its the input_edges + * + * src_pb_graph_node.[in|out]_pins -----------------> des_pb_graph_node.[in|out]pins + * /|\ + * | + * input_pins, edges, output_pins + *******************************************************************/ +static +void build_physical_block_pin_interc_bitstream(BitstreamManager& bitstream_manager, + const ConfigBlockId& parent_configurable_block, + const ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const MuxLibrary& mux_lib, + const VprDeviceAnnotation& device_annotation, + const PhysicalPb& physical_pb, + t_pb_graph_pin* des_pb_graph_pin, + t_mode* physical_mode) { + /* Identify the number of fan-in (Consider interconnection edges of only selected mode) */ + t_interconnect* cur_interc = pb_graph_pin_interc(des_pb_graph_pin, physical_mode); + size_t fan_in = pb_graph_pin_inputs(des_pb_graph_pin, cur_interc).size(); + + if ((nullptr == cur_interc) || (0 == fan_in)) { + /* No interconnection matched */ + return; + } + + /* Identify pin interconnection type */ + enum e_interconnect interc_type = device_annotation.interconnect_physical_type(cur_interc); + switch (interc_type) { + case DIRECT_INTERC: + /* Nothing to do, return */ + break; + case COMPLETE_INTERC: + case MUX_INTERC: { + /* Find the circuit model id of the mux, we need its design technology which matters the bitstream generation */ + CircuitModelId mux_model = device_annotation.interconnect_circuit_model(cur_interc); + VTR_ASSERT(CIRCUIT_MODEL_MUX == circuit_lib.model_type(mux_model)); + + /* Find the input size of the implementation of a routing multiplexer */ + size_t datapath_mux_size = fan_in; + VTR_ASSERT(true == valid_mux_implementation_num_inputs(datapath_mux_size)); + + /* Find the path id: + * - if des pb is not valid, this is an unmapped pb, we can set a default path_id + */ + const PhysicalPbId& des_pb_id = physical_pb.find_pb(des_pb_graph_pin->parent_node); + size_t mux_input_pin_id = 0; + if (true != physical_pb.valid_pb_id(des_pb_id)) { + mux_input_pin_id = DEFAULT_PATH_ID; + } else { + for (t_pb_graph_pin* src_pb_graph_pin : pb_graph_pin_inputs(des_pb_graph_pin, cur_interc)) { + const PhysicalPbId& src_pb_id = physical_pb.find_pb(src_pb_graph_pin->parent_node); + /* If the src pb id is not valid, we bypass it */ + if ( (true != physical_pb.valid_pb_id(src_pb_id)) + && (physical_pb.pb_graph_pin_atom_net(src_pb_id, src_pb_graph_pin) == physical_pb.pb_graph_pin_atom_net(des_pb_id, des_pb_graph_pin))) { + break; + } + mux_input_pin_id++; + } + VTR_ASSERT (mux_input_pin_id <= fan_in); + /* Unmapped pin, use default path id */ + if (fan_in == mux_input_pin_id) { + mux_input_pin_id = DEFAULT_PATH_ID; + } + } + + /* Generate bitstream depend on both technology and structure of this MUX */ + std::vector mux_bitstream = build_mux_bitstream(circuit_lib, mux_model, mux_lib, datapath_mux_size, mux_input_pin_id); + + /* Create the block denoting the memory instances that drives this node in physical_block */ + std::string mem_block_name = generate_pb_memory_instance_name(GRID_MEM_INSTANCE_PREFIX, des_pb_graph_pin, std::string("")); + ConfigBlockId mux_mem_block = bitstream_manager.add_block(mem_block_name); + bitstream_manager.add_child_block(parent_configurable_block, mux_mem_block); + + /* Find the module in module manager and ensure the bitstream size matches! */ + std::string mem_module_name = generate_mux_subckt_name(circuit_lib, mux_model, datapath_mux_size, std::string(MEMORY_MODULE_POSTFIX)); + ModuleId mux_mem_module = module_manager.find_module(mem_module_name); + VTR_ASSERT (true == module_manager.valid_module_id(mux_mem_module)); + ModulePortId mux_mem_out_port_id = module_manager.find_module_port(mux_mem_module, generate_configuration_chain_data_out_name()); + VTR_ASSERT(mux_bitstream.size() == module_manager.module_port(mux_mem_module, mux_mem_out_port_id).get_width()); + + /* Add the bistream to the bitstream manager */ + for (const bool& bit : mux_bitstream) { + ConfigBitId config_bit = bitstream_manager.add_bit(bit); + /* Link the memory bits to the mux mem block */ + bitstream_manager.add_bit_to_block(mux_mem_block, config_bit); + } + break; + } + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid interconnection type for %s (Arch[LINE%d])!\n", + cur_interc->name, cur_interc->line_num); + exit(1); + } +} + +/******************************************************************** + * This function generates bitstream for the programmable routing + * multiplexers in a pb_graph node + *******************************************************************/ +static +void build_physical_block_interc_port_bitstream(BitstreamManager& bitstream_manager, + const ConfigBlockId& parent_configurable_block, + const ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const MuxLibrary& mux_lib, + const VprDeviceAnnotation& device_annotation, + t_pb_graph_node* physical_pb_graph_node, + const PhysicalPb& physical_pb, + const e_circuit_pb_port_type& pb_port_type, + t_mode* physical_mode) { + switch (pb_port_type) { + case CIRCUIT_PB_PORT_INPUT: + for (int iport = 0; iport < physical_pb_graph_node->num_input_ports; ++iport) { + for (int ipin = 0; ipin < physical_pb_graph_node->num_input_pins[iport]; ++ipin) { + build_physical_block_pin_interc_bitstream(bitstream_manager, parent_configurable_block, + module_manager, circuit_lib, mux_lib, + device_annotation, + physical_pb, + &(physical_pb_graph_node->input_pins[iport][ipin]), + physical_mode); + } + } + break; + case CIRCUIT_PB_PORT_OUTPUT: + for (int iport = 0; iport < physical_pb_graph_node->num_output_ports; ++iport) { + for (int ipin = 0; ipin < physical_pb_graph_node->num_output_pins[iport]; ++ipin) { + build_physical_block_pin_interc_bitstream(bitstream_manager, parent_configurable_block, + module_manager, circuit_lib, mux_lib, + device_annotation, + physical_pb, + &(physical_pb_graph_node->output_pins[iport][ipin]), + physical_mode); + } + } + break; + case CIRCUIT_PB_PORT_CLOCK: + for (int iport = 0; iport < physical_pb_graph_node->num_clock_ports; ++iport) { + for (int ipin = 0; ipin < physical_pb_graph_node->num_clock_pins[iport]; ++ipin) { + build_physical_block_pin_interc_bitstream(bitstream_manager, parent_configurable_block, + module_manager, circuit_lib, mux_lib, + device_annotation, + physical_pb, + &(physical_pb_graph_node->clock_pins[iport][ipin]), + physical_mode); + + } + } + break; + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid pb port type!\n"); + exit(1); + } +} + +/******************************************************************** + * This function generates bitstream for the programmable routing + * multiplexers in a pb_graph node + *******************************************************************/ +static +void build_physical_block_interc_bitstream(BitstreamManager& bitstream_manager, + const ConfigBlockId& parent_configurable_block, + const ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const MuxLibrary& mux_lib, + const VprDeviceAnnotation& device_annotation, + t_pb_graph_node* physical_pb_graph_node, + const PhysicalPb& physical_pb, + t_mode* physical_mode) { + /* Check if the pb_graph node is valid or not */ + if (nullptr == physical_pb_graph_node) { + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid physical_pb_graph_node.\n"); + exit(1); + } + + /* We check output_pins of physical_pb_graph_node and its the input_edges + * Iterate over the interconnections between outputs of physical_pb_graph_node + * and outputs of child_pb_graph_node + * child_pb_graph_node.output_pins -----------------> physical_pb_graph_node.outpins + * /|\ + * | + * input_pins, edges, output_pins + * Note: it is not applied to primitive pb_type! + */ + build_physical_block_interc_port_bitstream(bitstream_manager, parent_configurable_block, + module_manager, circuit_lib, mux_lib, + device_annotation, + physical_pb_graph_node, physical_pb, + CIRCUIT_PB_PORT_OUTPUT, physical_mode); + + /* We check input_pins of child_pb_graph_node and its the input_edges + * Iterate over the interconnections between inputs of physical_pb_graph_node + * and inputs of child_pb_graph_node + * physical_pb_graph_node.input_pins -----------------> child_pb_graph_node.input_pins + * /|\ + * | + * input_pins, edges, output_pins + */ + for (int ipb = 0; ipb < physical_mode->num_pb_type_children; ipb++) { + for (int jpb = 0; jpb < physical_mode->pb_type_children[ipb].num_pb; jpb++) { + t_pb_graph_node* child_pb_graph_node = &(physical_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ipb][jpb]); + + /* For each child_pb_graph_node input pins*/ + build_physical_block_interc_port_bitstream(bitstream_manager, parent_configurable_block, + module_manager, circuit_lib, mux_lib, + device_annotation, + child_pb_graph_node, physical_pb, + CIRCUIT_PB_PORT_INPUT, physical_mode); + /* For clock pins, we should do the same work */ + build_physical_block_interc_port_bitstream(bitstream_manager, parent_configurable_block, + module_manager, circuit_lib, mux_lib, + device_annotation, + child_pb_graph_node, physical_pb, + CIRCUIT_PB_PORT_CLOCK, physical_mode); + } + } +} + /******************************************************************** * This function generates bitstream for a physical block, which is * a child block of a grid @@ -168,8 +393,10 @@ void rec_build_physical_block_bitstream(BitstreamManager& bitstream_manager, /* Check if this has defined a circuit_model*/ if (true == is_primitive_pb_type(physical_pb_type)) { - switch (physical_pb_type->class_type) { - case LUT_CLASS: + CircuitModelId primitive_circuit_model = device_annotation.pb_type_circuit_model(physical_pb_type); + VTR_ASSERT(CircuitModelId::INVALID() != primitive_circuit_model); + switch (circuit_lib.model_type(primitive_circuit_model)) { + case CIRCUIT_MODEL_LUT: /* Special case for LUT !!! * Mapped logical block information is stored in child_pbs of this pb!!! */ @@ -177,9 +404,9 @@ void rec_build_physical_block_bitstream(BitstreamManager& bitstream_manager, // module_manager, circuit_lib, mux_lib, // physical_pb, pb_id, physical_pb_type); break; - case LATCH_CLASS: - case UNKNOWN_CLASS: - case MEMORY_CLASS: + case CIRCUIT_MODEL_FF: + case CIRCUIT_MODEL_HARDLOGIC: + case CIRCUIT_MODEL_IOPAD: /* For other types of blocks, we can apply a generic therapy */ build_primitive_bitstream(bitstream_manager, pb_configurable_block, module_manager, circuit_lib, device_annotation, @@ -187,7 +414,7 @@ void rec_build_physical_block_bitstream(BitstreamManager& bitstream_manager, break; default: VTR_LOGF_ERROR(__FILE__, __LINE__, - "Unknown class type of pb_type '%s'!\n", + "Unknown circuit model type of pb_type '%s'!\n", physical_pb_type->name); exit(1); } @@ -196,10 +423,11 @@ void rec_build_physical_block_bitstream(BitstreamManager& bitstream_manager, } /* Generate the bitstream for the interconnection in this physical block */ - //build_physical_block_interc_bitstream(bitstream_manager, pb_configurable_block, - // module_manager, circuit_lib, mux_lib, - // physical_pb_graph_node, physical_pb, - // pb_id, physical_mode_index); + build_physical_block_interc_bitstream(bitstream_manager, pb_configurable_block, + module_manager, circuit_lib, mux_lib, + device_annotation, + physical_pb_graph_node, physical_pb, + physical_mode); } /******************************************************************** diff --git a/openfpga/src/fpga_bitstream/build_routing_bitstream.cpp b/openfpga/src/fpga_bitstream/build_routing_bitstream.cpp index e0dd75c2a..5b4fadb09 100644 --- a/openfpga/src/fpga_bitstream/build_routing_bitstream.cpp +++ b/openfpga/src/fpga_bitstream/build_routing_bitstream.cpp @@ -435,7 +435,7 @@ void build_routing_bitstream(BitstreamManager& bitstream_manager, device_annotation, routing_annotation, rr_graph, device_rr_gsb, CHANY); - VTR_LOG("Done"); + VTR_LOG("Done\n"); } From 2d86a02358278e85c6d2cc4cd550ffd7927dfe95 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 25 Feb 2020 12:45:13 -0700 Subject: [PATCH 205/645] refactored LUT bitstream generation to use vtr logic --- libs/libvtrutil/src/vtr_logic.h | 7 +- openfpga/src/utils/lut_utils.cpp | 395 ++++++++++++++++++++++++++++++- openfpga/src/utils/lut_utils.h | 19 ++ 3 files changed, 419 insertions(+), 2 deletions(-) diff --git a/libs/libvtrutil/src/vtr_logic.h b/libs/libvtrutil/src/vtr_logic.h index 2287f3383..c1ab29462 100644 --- a/libs/libvtrutil/src/vtr_logic.h +++ b/libs/libvtrutil/src/vtr_logic.h @@ -1,15 +1,20 @@ #ifndef VTR_LOGIC_H #define VTR_LOGIC_H +#include + namespace vtr { enum class LogicValue { FALSE = 0, TRUE = 1, DONT_CARE = 2, - UNKOWN = 3 + UNKOWN = 3, + NUM_LOGIC_VALUE_TYPES }; +constexpr std::array LOGIC_VALUE_STRING = {{"false", "true", "don't care", "unknown"}}; + } // namespace vtr #endif diff --git a/openfpga/src/utils/lut_utils.cpp b/openfpga/src/utils/lut_utils.cpp index e2b01edb5..b8b96f74b 100644 --- a/openfpga/src/utils/lut_utils.cpp +++ b/openfpga/src/utils/lut_utils.cpp @@ -2,10 +2,15 @@ * This file includes most utilized functions to manipulate LUTs, * especially their truth tables, in the OpenFPGA context *******************************************************************/ +#include + /* Headers from vtrutil library */ #include "vtr_assert.h" #include "vtr_log.h" +/* Headers from openfpgautil library */ +#include "openfpga_decode.h" + #include "lut_utils.h" /* begin namespace openfpga */ @@ -29,6 +34,21 @@ namespace openfpga { * Truth table line: 00111 * rotated_pin_map: 2310 * Adapt truth table line: 11001 + * + * An illustrative example: + * + * Original Truth Table Post VPR Truth Table + * + * +-------+ +-------+ + * net0 --->| | net1--->| | + * net1 --->| LUT | net0--->| LUT | + * ... | | ... | | + * +-------+ +-------+ + * + * Truth table line Truth table line + * .names net0 net1 out .names net1 net0 out + * 01 1 10 1 + * *******************************************************************/ AtomNetlist::TruthTable lut_truth_table_adaption(const AtomNetlist::TruthTable& orig_tt, const std::vector& rotated_pin_map) { @@ -89,5 +109,378 @@ std::vector truth_table_to_string(const AtomNetlist::TruthTable& tt return tt_str; } -} /* end namespace openfpga */ +/******************************************************************** + * Adapt the truth table from the short-wire connection + * from the input nets of a LUT to an output of a LUT + * + * LUT + * +-------------+ + * lut_input--->|----+ | + * | | | + * | +------->|---> lut_output + * | | + * +-------------+ + * + * In this case, LUT is configured as a wiring module + * This function will generate a truth for the wiring LUT + * + * For example: + * The truth table of the case where the 3rd input of + * a 4-input LUT is wired to output + * + * --1- 1 + * + ********************************************************************/ +AtomNetlist::TruthTable build_post_routing_wired_lut_truth_table(const size_t& lut_size, + const size_t& wire_input_id) { + AtomNetlist::TruthTable tt; + + /* There is always only one line in this truth table */ + tt.resize(1); + /* Pre-allocate the truth table: + * Each truth table line is organized in BLIF format: + * |<---LUT size--->| + * < a string of 0 or 1> <0 or 1> + * The first of characters represent the input values of each LUT input + * Here, we add 2 characters, which denote the space and a digit (0|1) + * By default, we set all the inputs as don't care value '-' + * + * For more details, please refer to the BLIF format documentation + */ + tt[0].resize(lut_size, vtr::LogicValue::DONT_CARE); + /* Fill the truth table !!! */ + VTR_ASSERT(wire_input_id < lut_size); + tt[0][wire_input_id] = vtr::LogicValue::TRUE; + tt[0].push_back(vtr::LogicValue::TRUE); + + return tt; +} + +/******************************************************************** + * Adapt truth table for a fracturable LUT + * Determine fixed input bits for this truth table: + * 1. input bits within frac_level (all '-' if not specified) + * 2. input bits outside frac_level, decoded to its output mask (0 -> first part -> all '1') + * + * For example: + * A 4-input function is mapped to input[0..3] of a 6-input fracturable LUT + * Plus, it uses the 2nd output of the fracturable LUT + * The truth table of the 4-input function is + * 1001 1 + * while truth table of a 6-input LUT requires 6 characters + * Therefore, it must be adapted by adding mask bits, which are + * a number of fixed digits to configure the fracturable LUT to + * operate in a 4-input LUT mode + * The mask bits can be decoded from the index of output used in the fracturable LUT + * For the 2nd output, it will be '01', the binary representation of index '1' + * Now the truth table will be adapt to + * 100101 1 + * where the first 4 digits come from the original truth table + * the 2 following digits are mask bits + * + ********************************************************************/ +AtomNetlist::TruthTable adapt_truth_table_for_frac_lut(const size_t& lut_frac_level, + const size_t& lut_output_mask, + const AtomNetlist::TruthTable& truth_table) { + /* No adaption required for when the lut_frac_level is not set */ + if (size_t(OPEN) == lut_frac_level) { + return truth_table; + } + + AtomNetlist::TruthTable adapt_truth_table; + + /* Apply modification to the truth table */ + for (const std::vector& tt_line : truth_table) { + /* Last element is the output */ + size_t lut_size = tt_line.size() - 1; + /* Get the number of bits to be masked (modified) */ + int num_mask_bits = lut_size - lut_frac_level; + /* Check if we need to modify any bits */ + VTR_ASSERT(0 <= num_mask_bits); + if ( 0 == num_mask_bits ) { + /* No modification needed, push to adapted truth table */ + adapt_truth_table.push_back(tt_line); + continue; + } + /* Modify bits starting from lut_frac_level */ + /* Decode the lut_output_mask to LUT input codes */ + int temp = pow(2., num_mask_bits) - 1 - lut_output_mask; + VTR_ASSERT(0 <= temp); + std::vector mask_bits_vec = itobin_vec(temp, num_mask_bits); + /* Copy the bits to the truth table line */ + std::vector adapt_tt_line = tt_line; + for (size_t itt = lut_frac_level; itt < lut_frac_level + mask_bits_vec.size(); ++itt) { + + vtr::LogicValue logic_val = vtr::LogicValue::FALSE; + VTR_ASSERT( (1 == mask_bits_vec[itt - lut_frac_level]) + || (0 == mask_bits_vec[itt - lut_frac_level]) ); + if (1 == mask_bits_vec[itt - lut_frac_level]) { + logic_val = vtr::LogicValue::TRUE; + } + adapt_tt_line[itt] = logic_val; + } + + /* Push to adapted truth table */ + adapt_truth_table.push_back(adapt_tt_line); + } + + return adapt_truth_table; +} + +/******************************************************************** + * Determine if the truth table of a LUT is a on-set or a off-set + * - An on-set is defined as the truth table lines are ended with logic '1' + * - An off-set is defined as the truth table lines are ended with logic '0' + *******************************************************************/ +bool lut_truth_table_use_on_set(const AtomNetlist::TruthTable& truth_table) { + bool on_set = false; + bool off_set = false; + + for (const std::vector& tt_line : truth_table) { + switch (tt_line.back()) { + case vtr::LogicValue::TRUE : + on_set = true; + break; + case vtr::LogicValue::FALSE : + off_set = true; + break; + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid truth_table_line ending '%s'!\n", + vtr::LOGIC_VALUE_STRING[size_t(tt_line.back())]); + exit(1); + } + } + + /* Prefer on_set if both are true */ + if (true == on_set && true == off_set) { + on_set = true; + off_set = false; + } + VTR_ASSERT(on_set == !off_set); + + return on_set; +} + +/******************************************************************** + * Complete a line in truth table with a given lut size + * Due to the size of truth table may be less than the lut size. + * i.e. in LUT-6 architecture, there exists LUT1-6 in technology-mapped netlists + * So, in truth table line, there may be 10- 1 + * In this case, we should complete it by --10- 1 + *******************************************************************/ +static +std::vector complete_truth_table_line(const size_t& lut_size, + const std::vector& tt_line) { + std::vector ret; + + VTR_ASSERT(0 < tt_line.size()); + + /* Complete the truth table line*/ + size_t cover_len = tt_line.size() - 1; + VTR_ASSERT(cover_len <= lut_size); + + /* Copy the original truth table line */ + ret = tt_line; + /* Kick out the last value for now as it is the output value */ + ret.pop_back(); + + /* Add the number of '-' we should add in the back !!! */ + for (size_t j = cover_len; j < lut_size; ++j) { + ret.push_back(vtr::LogicValue::DONT_CARE); + } + + /* Copy the original truth table line */ + ret.push_back(tt_line.back()); + + /* Check if the size of ret matches our expectation */ + VTR_ASSERT(lut_size + 1 == ret.size()); + + return ret; +} + +/******************************************************************** + * For each lut_bit_lines, we should recover the truth table, + * and then set the sram bits to "1" if the truth table defines so. + * Start_point: the position we start converting don't care sign '-' + * to explicit '0' or '1' + *******************************************************************/ +static +void rec_build_lut_bitstream_per_line(std::vector& lut_bitstream, + const size_t& lut_size, + const std::vector& tt_line, + const size_t& start_point) { + std::vector temp_line = tt_line; + + /* Check the length of sram bits and truth table line */ + VTR_ASSERT(lut_size + 1 == tt_line.size()); /* lut_size + '1|0' */ + + /* End of truth_table_line should be "space" and "1" */ + VTR_ASSERT( (vtr::LogicValue::TRUE == tt_line.back()) + || (vtr::LogicValue::FALSE == tt_line.back()) ); + + /* Make sure before start point there is no '-' */ + VTR_ASSERT(start_point < tt_line.size()); + for (size_t i = 0; i < start_point; ++i) { + VTR_ASSERT(vtr::LogicValue::DONT_CARE != tt_line[i]); + } + + /* Configure sram bits recursively */ + for (size_t i = start_point; i < lut_size; ++i) { + if (vtr::LogicValue::DONT_CARE == tt_line[i]) { + /* if we find a dont_care, we don't do configure now but recursively*/ + /* '0' branch */ + temp_line[i] = vtr::LogicValue::FALSE; + rec_build_lut_bitstream_per_line(lut_bitstream, lut_size, temp_line, start_point + 1); + /* '1' branch */ + temp_line[i] = vtr::LogicValue::TRUE; + rec_build_lut_bitstream_per_line(lut_bitstream, lut_size, temp_line, start_point + 1); + return; + } + } + + /* TODO: Use MuxGraph to decode this!!! */ + /* Decode bitstream only when there are only 0 or 1 in the truth table */ + size_t sram_id = 0; + for (size_t i = 0; i < lut_size; ++i) { + /* Should be either '0' or '1' */ + switch (tt_line[i]) { + case vtr::LogicValue::FALSE : + /* We assume the 1-lut pass sram1 when input = 0 */ + sram_id += (size_t)pow(2., (double)(i)); + break; + case vtr::LogicValue::TRUE : + /* We assume the 1-lut pass sram0 when input = 1 */ + break; + case vtr::LogicValue::DONT_CARE : + default : + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid truth_table bit '%s', should be [0|1|]!\n", + vtr::LOGIC_VALUE_STRING[size_t(tt_line[i])]); + exit(1); + } + } + /* Set the sram bit to '1'*/ + VTR_ASSERT(sram_id < lut_bitstream.size()); + if (vtr::LogicValue::TRUE == tt_line.back()) { + lut_bitstream[sram_id] = true; /* on set*/ + } else if (vtr::LogicValue::FALSE == tt_line.back()) { + lut_bitstream[sram_id] = false; /* off set */ + } else { + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid truth_table_line ending '%s'!\n", + vtr::LOGIC_VALUE_STRING[size_t(tt_line.back())]); + exit(1); + } +} + +/******************************************************************** + * Generate the bitstream for a single-output LUT with a given truth table + * As truth tables may come from different logic blocks, truth tables could be in on and off sets + * We first build a base SRAM bits, where different parts are set to tbe on/off sets + * Then, we can decode SRAM bits as regular process + *******************************************************************/ +static +std::vector build_single_output_lut_bitstream(const AtomNetlist::TruthTable& truth_table, + const MuxGraph& lut_mux_graph, + const size_t& default_sram_bit_value) { + size_t lut_size = lut_mux_graph.num_memory_bits(); + size_t bitstream_size = lut_mux_graph.num_inputs(); + std::vector lut_bitstream(bitstream_size, false); + AtomNetlist::TruthTable completed_truth_table; + bool on_set = false; + bool off_set = false; + + /* if No truth_table, do default*/ + if (0 == truth_table.size()) { + switch (default_sram_bit_value) { + case 0: + on_set = true; + off_set = false; + break; + case 1: + on_set = false; + off_set = true; + break; + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid default_signal_init_value '%lu'!\n", + default_sram_bit_value); + exit(1); + } + } else { + on_set = lut_truth_table_use_on_set(truth_table); + off_set = !on_set; + } + + /* Read in truth table lines, decode one by one */ + for (const std::vector& tt_line : truth_table) { + /* Complete the truth table line by line*/ + completed_truth_table.push_back(complete_truth_table_line(lut_size, tt_line)); + } + + /* Initial all the bits in the bitstream */ + if (true == off_set) { + /* By default, the lut_bitstream is initialize for on_set + * For off set, it should be flipped + */ + lut_bitstream.clear(); + lut_bitstream.resize(bitstream_size, true); + } + + for (const std::vector& tt_line : completed_truth_table) { + /* Update the truth table, sram_bits */ + rec_build_lut_bitstream_per_line(lut_bitstream, lut_size, tt_line, 0); + } + + return lut_bitstream; +} + +/******************************************************************** + * Generate bitstream for a fracturable LUT (also applicable to single-output LUT) + * Check type of truth table of each mapped logical block + * if it is on-set, we give a all 0 base bitstream + * if it is off-set, we give a all 1 base bitstream + *******************************************************************/ +std::vector build_frac_lut_bitstream(const CircuitLibrary& circuit_lib, + const MuxGraph& lut_mux_graph, + const VprDeviceAnnotation& device_annotation, + const std::map& truth_tables, + const size_t& default_sram_bit_value) { + /* Initialization */ + std::vector lut_bitstream(lut_mux_graph.num_inputs(), default_sram_bit_value); + + for (const std::pair& element : truth_tables) { + /* Find the corresponding circuit model output port and assoicated lut_output_mask */ + CircuitPortId lut_model_output_port = device_annotation.pb_circuit_port(element.first->port); + size_t lut_frac_level = circuit_lib.port_lut_frac_level(lut_model_output_port); + /* By default, lut_frac_level will be the lut_size, i.e., number of levels of the mux graph */ + if (size_t(-1) == lut_frac_level) { + lut_frac_level = lut_mux_graph.num_levels(); + } + + /* Find the corresponding circuit model output port and assoicated lut_output_mask */ + size_t lut_output_mask = circuit_lib.port_lut_output_mask(lut_model_output_port)[element.first->pin_number]; + + /* Decode lut sram bits */ + std::vector temp_bitstream = build_single_output_lut_bitstream(element.second, lut_mux_graph, default_sram_bit_value); + + /* Depending on the frac-level, we get the location(starting/end points) of sram bits */ + size_t length_of_temp_bitstream_to_copy = (size_t)pow(2., (double)(lut_frac_level)); + size_t bitstream_offset = length_of_temp_bitstream_to_copy * lut_output_mask; + /* Ensure the offset is in range */ + VTR_ASSERT(bitstream_offset < lut_bitstream.size()); + VTR_ASSERT(bitstream_offset + length_of_temp_bitstream_to_copy <= lut_bitstream.size()); + + /* Copy to the segment of bitstream */ + for (size_t bit = bitstream_offset; bit < bitstream_offset + length_of_temp_bitstream_to_copy; ++bit) { + lut_bitstream[bit] = temp_bitstream[bit]; + } + } + + return lut_bitstream; +} + + +} /* end namespace openfpga */ diff --git a/openfpga/src/utils/lut_utils.h b/openfpga/src/utils/lut_utils.h index 800c1fc59..a1d713f25 100644 --- a/openfpga/src/utils/lut_utils.h +++ b/openfpga/src/utils/lut_utils.h @@ -6,7 +6,11 @@ *******************************************************************/ #include #include +#include #include "atom_netlist.h" +#include "mux_graph.h" +#include "physical_types.h" +#include "vpr_device_annotation.h" /******************************************************************** * Function declaration @@ -20,6 +24,21 @@ AtomNetlist::TruthTable lut_truth_table_adaption(const AtomNetlist::TruthTable& std::vector truth_table_to_string(const AtomNetlist::TruthTable& tt); +AtomNetlist::TruthTable build_post_routing_wired_lut_truth_table(const size_t& lut_size, + const size_t& wire_input_id); + +AtomNetlist::TruthTable adapt_truth_table_for_frac_lut(const size_t& lut_frac_level, + const size_t& lut_output_mask, + const AtomNetlist::TruthTable& truth_table); + +bool lut_truth_table_use_on_set(const AtomNetlist::TruthTable& truth_table); + +std::vector build_frac_lut_bitstream(const CircuitLibrary& circuit_lib, + const MuxGraph& lut_mux_graph, + const VprDeviceAnnotation& device_annotation, + const std::map& truth_tables, + const size_t& default_sram_bit_value); + } /* end namespace openfpga */ #endif From ca038857d3b5988e439150d4b43be83dc4c6ce59 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 25 Feb 2020 13:34:13 -0700 Subject: [PATCH 206/645] add lut physical truth table to physical pb --- openfpga/src/repack/physical_pb.cpp | 15 ++++++++++++++- openfpga/src/repack/physical_pb.h | 10 +++++++++- openfpga/src/utils/lut_utils.cpp | 4 ++-- openfpga/src/utils/lut_utils.h | 2 +- openfpga/src/utils/physical_pb_utils.cpp | 2 +- 5 files changed, 27 insertions(+), 6 deletions(-) diff --git a/openfpga/src/repack/physical_pb.cpp b/openfpga/src/repack/physical_pb.cpp index f64a3d29a..a29b6d401 100644 --- a/openfpga/src/repack/physical_pb.cpp +++ b/openfpga/src/repack/physical_pb.cpp @@ -50,6 +50,7 @@ PhysicalPbId PhysicalPb::child(const PhysicalPbId& pb, std::vector PhysicalPb::atom_blocks(const PhysicalPbId& pb) const { VTR_ASSERT(true == valid_pb_id(pb)); + return atom_blocks_[pb]; } @@ -64,6 +65,11 @@ AtomNetId PhysicalPb::pb_graph_pin_atom_net(const PhysicalPbId& pb, return AtomNetId::INVALID(); } +AtomNetlist::TruthTable PhysicalPb::truth_table(const PhysicalPbId& pb) const { + VTR_ASSERT(true == valid_pb_id(pb)); + return truth_tables_[pb]; +} + std::vector PhysicalPb::mode_bits(const PhysicalPbId& pb) const { VTR_ASSERT(true == valid_pb_id(pb)); return mode_bits_[pb]; @@ -118,6 +124,13 @@ void PhysicalPb::add_child(const PhysicalPbId& parent, parent_pbs_[child] = parent; } +void PhysicalPb::set_truth_table(const PhysicalPbId& pb, + const AtomNetlist::TruthTable& truth_table) { + VTR_ASSERT(true == valid_pb_id(pb)); + + truth_tables_[pb] = truth_table; +} + void PhysicalPb::set_mode_bits(const PhysicalPbId& pb, const std::vector& mode_bits) { VTR_ASSERT(true == valid_pb_id(pb)); @@ -128,7 +141,7 @@ void PhysicalPb::set_mode_bits(const PhysicalPbId& pb, void PhysicalPb::add_atom_block(const PhysicalPbId& pb, const AtomBlockId& atom_block) { VTR_ASSERT(true == valid_pb_id(pb)); - + atom_blocks_[pb].push_back(atom_block); } diff --git a/openfpga/src/repack/physical_pb.h b/openfpga/src/repack/physical_pb.h index 2ad29c2cd..d5f5b00a3 100644 --- a/openfpga/src/repack/physical_pb.h +++ b/openfpga/src/repack/physical_pb.h @@ -12,7 +12,7 @@ #include "physical_types.h" /* Headers from vpr library */ -#include "atom_netlist_fwd.h" +#include "atom_netlist.h" #include "physical_pb_fwd.h" @@ -48,6 +48,7 @@ class PhysicalPb { std::vector atom_blocks(const PhysicalPbId& pb) const; AtomNetId pb_graph_pin_atom_net(const PhysicalPbId& pb, const t_pb_graph_pin* pb_graph_pin) const; + AtomNetlist::TruthTable truth_table(const PhysicalPbId& pb) const; std::vector mode_bits(const PhysicalPbId& pb) const; public: /* Public mutators */ PhysicalPbId create_pb(const t_pb_graph_node* pb_graph_node); @@ -56,6 +57,8 @@ class PhysicalPb { const t_pb_type* child_type); void add_atom_block(const PhysicalPbId& pb, const AtomBlockId& atom_block); + void set_truth_table(const PhysicalPbId& pb, + const AtomNetlist::TruthTable& truth_table); void set_mode_bits(const PhysicalPbId& pb, const std::vector& mode_bits); void set_pb_graph_pin_atom_net(const PhysicalPbId& pb, @@ -75,6 +78,11 @@ class PhysicalPb { vtr::vector>> child_pbs_; vtr::vector parent_pbs_; + /* configuration bits + * Truth tables and mode selection + */ + vtr::vector truth_tables_; + vtr::vector> mode_bits_; /* Fast lookup */ diff --git a/openfpga/src/utils/lut_utils.cpp b/openfpga/src/utils/lut_utils.cpp index b8b96f74b..16bb8c3f2 100644 --- a/openfpga/src/utils/lut_utils.cpp +++ b/openfpga/src/utils/lut_utils.cpp @@ -446,12 +446,12 @@ std::vector build_single_output_lut_bitstream(const AtomNetlist::TruthTabl std::vector build_frac_lut_bitstream(const CircuitLibrary& circuit_lib, const MuxGraph& lut_mux_graph, const VprDeviceAnnotation& device_annotation, - const std::map& truth_tables, + const std::map& truth_tables, const size_t& default_sram_bit_value) { /* Initialization */ std::vector lut_bitstream(lut_mux_graph.num_inputs(), default_sram_bit_value); - for (const std::pair& element : truth_tables) { + for (const std::pair& element : truth_tables) { /* Find the corresponding circuit model output port and assoicated lut_output_mask */ CircuitPortId lut_model_output_port = device_annotation.pb_circuit_port(element.first->port); size_t lut_frac_level = circuit_lib.port_lut_frac_level(lut_model_output_port); diff --git a/openfpga/src/utils/lut_utils.h b/openfpga/src/utils/lut_utils.h index a1d713f25..21f638175 100644 --- a/openfpga/src/utils/lut_utils.h +++ b/openfpga/src/utils/lut_utils.h @@ -36,7 +36,7 @@ bool lut_truth_table_use_on_set(const AtomNetlist::TruthTable& truth_table); std::vector build_frac_lut_bitstream(const CircuitLibrary& circuit_lib, const MuxGraph& lut_mux_graph, const VprDeviceAnnotation& device_annotation, - const std::map& truth_tables, + const std::map& truth_tables, const size_t& default_sram_bit_value); } /* end namespace openfpga */ diff --git a/openfpga/src/utils/physical_pb_utils.cpp b/openfpga/src/utils/physical_pb_utils.cpp index 777f2ad0c..71cf98677 100644 --- a/openfpga/src/utils/physical_pb_utils.cpp +++ b/openfpga/src/utils/physical_pb_utils.cpp @@ -244,7 +244,7 @@ void rec_update_physical_pb_from_operating_pb(PhysicalPb& phy_pb, phy_pb.add_atom_block(physical_pb, atom_blk); - /* TODO: Iterate over ports and annotate the atom pins */ + /* Iterate over ports and annotate the atom pins */ synchronize_primitive_physical_pb_atom_nets(phy_pb, physical_pb, pb_graph_node, pb_route, From 2dd80e4830dcb2fdcae458574b3788c3789b33b6 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 25 Feb 2020 21:21:44 -0700 Subject: [PATCH 207/645] add more methods to acquire physical truth table from physical pb --- openfpga/src/annotation/vpr_clustering_annotation.cpp | 5 +++++ openfpga/src/annotation/vpr_clustering_annotation.h | 1 + openfpga/src/repack/physical_pb.cpp | 11 +++++++++++ openfpga/src/repack/physical_pb.h | 1 + 4 files changed, 18 insertions(+) diff --git a/openfpga/src/annotation/vpr_clustering_annotation.cpp b/openfpga/src/annotation/vpr_clustering_annotation.cpp index e0c7ab575..8dbc2d6f4 100644 --- a/openfpga/src/annotation/vpr_clustering_annotation.cpp +++ b/openfpga/src/annotation/vpr_clustering_annotation.cpp @@ -86,5 +86,10 @@ void VprClusteringAnnotation::add_physical_pb(const ClusterBlockId& block_id, physical_pbs_[block_id] = physical_pb; } +PhysicalPb& VprClusteringAnnotation::mutable_physical_pb(const ClusterBlockId& block_id) { + VTR_ASSERT(physical_pbs_.end() != physical_pbs_.find(block_id)); + + return physical_pbs_.at(block_id); +} } /* End namespace openfpga*/ diff --git a/openfpga/src/annotation/vpr_clustering_annotation.h b/openfpga/src/annotation/vpr_clustering_annotation.h index b72173e1d..f8e84bb02 100644 --- a/openfpga/src/annotation/vpr_clustering_annotation.h +++ b/openfpga/src/annotation/vpr_clustering_annotation.h @@ -41,6 +41,7 @@ class VprClusteringAnnotation { const ClusterNetId& net_id); void adapt_truth_table(t_pb* pb, const AtomNetlist::TruthTable& tt); void add_physical_pb(const ClusterBlockId& block_id, const PhysicalPb& physical_pb); + PhysicalPb& mutable_physical_pb(const ClusterBlockId& block_id); private: /* Internal data */ /* Pair a regular pb_type to its physical pb_type */ std::map> net_names_; diff --git a/openfpga/src/repack/physical_pb.cpp b/openfpga/src/repack/physical_pb.cpp index a29b6d401..59417080e 100644 --- a/openfpga/src/repack/physical_pb.cpp +++ b/openfpga/src/repack/physical_pb.cpp @@ -16,6 +16,17 @@ PhysicalPb::physical_pb_range PhysicalPb::pbs() const { return vtr::make_range(pb_ids_.begin(), pb_ids_.end()); } +std::vector PhysicalPb::primitive_pbs() const { + std::vector results; + /* The primitive pbs are those without any children */ + for (auto pb : pbs()) { + if (true == child_pbs_[pb].empty()) { + results.push_back(pb); + } + } + return results; +} + std::string PhysicalPb::name(const PhysicalPbId& pb) const { VTR_ASSERT(true == valid_pb_id(pb)); return names_[pb]; diff --git a/openfpga/src/repack/physical_pb.h b/openfpga/src/repack/physical_pb.h index d5f5b00a3..caea0a0b9 100644 --- a/openfpga/src/repack/physical_pb.h +++ b/openfpga/src/repack/physical_pb.h @@ -39,6 +39,7 @@ class PhysicalPb { typedef vtr::Range physical_pb_range; public: /* Public aggregators */ physical_pb_range pbs() const; + std::vector primitive_pbs() const; std::string name(const PhysicalPbId& pb) const; PhysicalPbId find_pb(const t_pb_graph_node* name) const; PhysicalPbId parent(const PhysicalPbId& pb) const; From 4024ed63cb4c87f3ba0b2786aff8df8937d72857 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 25 Feb 2020 22:39:42 -0700 Subject: [PATCH 208/645] add truth table build up for physical LUTs --- openfpga/src/base/openfpga_repack.cpp | 8 +- .../src/repack/build_physical_truth_table.cpp | 170 ++++++++++++++++++ .../src/repack/build_physical_truth_table.h | 27 +++ openfpga/src/repack/physical_pb.cpp | 16 +- openfpga/src/repack/physical_pb.h | 6 +- openfpga/src/utils/lut_utils.cpp | 4 +- openfpga/src/utils/lut_utils.h | 4 +- 7 files changed, 226 insertions(+), 9 deletions(-) create mode 100644 openfpga/src/repack/build_physical_truth_table.cpp create mode 100644 openfpga/src/repack/build_physical_truth_table.h diff --git a/openfpga/src/base/openfpga_repack.cpp b/openfpga/src/base/openfpga_repack.cpp index f49321252..91c6b4798 100644 --- a/openfpga/src/base/openfpga_repack.cpp +++ b/openfpga/src/base/openfpga_repack.cpp @@ -5,7 +5,7 @@ #include "vtr_time.h" #include "vtr_log.h" -#include "verilog_api.h" +#include "build_physical_truth_table.h" #include "repack.h" #include "openfpga_repack.h" @@ -29,6 +29,12 @@ void repack(OpenfpgaContext& openfpga_ctx, openfpga_ctx.mutable_vpr_device_annotation(), openfpga_ctx.mutable_vpr_clustering_annotation(), cmd_context.option_enable(cmd, opt_verbose)); + + build_physical_lut_truth_tables(openfpga_ctx.mutable_vpr_clustering_annotation(), + g_vpr_ctx.atom(), + g_vpr_ctx.clustering(), + openfpga_ctx.vpr_device_annotation(), + openfpga_ctx.arch().circuit_lib); } } /* end namespace openfpga */ diff --git a/openfpga/src/repack/build_physical_truth_table.cpp b/openfpga/src/repack/build_physical_truth_table.cpp new file mode 100644 index 000000000..c749ae206 --- /dev/null +++ b/openfpga/src/repack/build_physical_truth_table.cpp @@ -0,0 +1,170 @@ +/*************************************************************************************** + * This file includes functions that are used to build truth tables of + * the physical implementation of LUTs + ***************************************************************************************/ + +/* Headers from vtrutil library */ +#include "vtr_log.h" +#include "vtr_assert.h" +#include "vtr_time.h" + +#include "openfpga_naming.h" + +#include "lut_utils.h" +#include "physical_pb.h" +#include "build_physical_truth_table.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/*************************************************************************************** + * Identify if LUT is used as wiring + * In this case, LUT functions as a buffer + * +------+ + * in0 -->|--- | + * | \ | + * in1 -->| --|--->out + * ... + * + * Note that this function judge the LUT operating mode from the input nets and output + * nets that are mapped to inputs and outputs. + * If the output net appear in the list of input nets, this LUT is used as a wire + ***************************************************************************************/ +static +bool is_wired_lut(const std::vector& input_nets, + const AtomNetId& output_net) { + for (const AtomNetId& input_net : input_nets) { + if (input_net == output_net) { + return true; + } + } + + return false; +} + +/*************************************************************************************** + * Create pin rotation map for a LUT + ***************************************************************************************/ +static +std::vector generate_lut_rotated_input_pin_map(const std::vector& input_nets, + const AtomContext& atom_ctx, + const AtomBlockId& atom_blk, + const t_pb_graph_node* pb_graph_node) { + /* Find the pin rotation status and record it , + * Note that some LUT inputs may not be used, we set them to be open by default + */ + std::vector rotated_pin_map(input_nets.size(), -1); + + VTR_ASSERT(1 == pb_graph_node->num_input_ports); + for (int ipin = 0; ipin < pb_graph_node->num_input_pins[0]; ++ipin) { + /* Port exists (some LUTs may have no input and hence no port in the atom netlist) */ + AtomPortId atom_port = atom_ctx.nlist.find_atom_port(atom_blk, pb_graph_node->input_pins[0][ipin].port->model_port); + if (!atom_port) { + continue; + } + + for (AtomPinId atom_pin : atom_ctx.nlist.port_pins(atom_port)) { + AtomNetId atom_pin_net = atom_ctx.nlist.pin_net(atom_pin); + if (atom_pin_net == input_nets[ipin]) { + rotated_pin_map[ipin] = atom_ctx.nlist.pin_port_bit(atom_pin); + break; + } + } + } + return rotated_pin_map; +} + +/*************************************************************************************** + * This function will iterate over all the inputs and outputs of the LUT pb + * and find truth tables that are mapped to each output pins + * Note that a physical LUT may have multiple truth tables to be considered + * as they may be fracturable + ***************************************************************************************/ +static +void build_physical_pb_lut_truth_tables(PhysicalPb& physical_pb, + const PhysicalPbId& lut_pb_id, + const AtomContext& atom_ctx, + const VprDeviceAnnotation& device_annotation, + const CircuitLibrary& circuit_lib) { + const t_pb_graph_node* pb_graph_node = physical_pb.pb_graph_node(lut_pb_id); + + CircuitModelId lut_model = device_annotation.pb_type_circuit_model(physical_pb.pb_graph_node(lut_pb_id)->pb_type); + VTR_ASSERT(CIRCUIT_MODEL_LUT == circuit_lib.model_type(lut_model)); + + /* Find all the nets mapped to each inputs */ + std::vector input_nets; + VTR_ASSERT(1 == pb_graph_node->num_input_ports); + for (int ipin = 0; ipin < pb_graph_node->num_input_pins[0]; ++ipin) { + input_nets.push_back(physical_pb.pb_graph_pin_atom_net(lut_pb_id, &(pb_graph_node->input_pins[0][ipin]))); + } + + + /* Find all the nets mapped to each outputs */ + for (int iport = 0; iport < pb_graph_node->num_input_ports; ++iport) { + for (int ipin = 0; ipin < pb_graph_node->num_input_pins[iport]; ++ipin) { + const t_pb_graph_pin* output_pin = &(pb_graph_node->output_pins[iport][ipin]); + AtomNetId output_net = physical_pb.pb_graph_pin_atom_net(lut_pb_id, output_pin); + /* Bypass unmapped pins */ + if (AtomNetId::INVALID() == output_net) { + continue; + } + /* Check if this is a LUT used as wiring */ + if (true == is_wired_lut(input_nets, output_net)) { + AtomNetlist::TruthTable wire_tt = build_wired_lut_truth_table(input_nets.size(), std::find(input_nets.begin(), input_nets.end(), output_net) - input_nets.begin()); + physical_pb.set_truth_table(lut_pb_id, output_pin, wire_tt); + continue; + } + + /* Find the truth table from atom block which drives the atom net */ + const AtomBlockId& atom_blk = atom_ctx.nlist.net_driver_block(output_net); + VTR_ASSERT(true == atom_ctx.nlist.valid_block_id(atom_blk)); + const AtomNetlist::TruthTable& orig_tt = atom_ctx.nlist.block_truth_table(atom_blk); + + std::vector rotated_pin_map = generate_lut_rotated_input_pin_map(input_nets, atom_ctx, atom_blk, pb_graph_node); + const AtomNetlist::TruthTable& adapt_tt = lut_truth_table_adaption(orig_tt, rotated_pin_map); + + /* Adapt the truth table for fracturable lut implementation and add to physical pb */ + CircuitPortId lut_model_output_port = device_annotation.pb_circuit_port(output_pin->port); + size_t lut_frac_level = circuit_lib.port_lut_frac_level(lut_model_output_port); + if (size_t(-1) == lut_frac_level) { + lut_frac_level = input_nets.size(); + } + size_t lut_output_mask = circuit_lib.port_lut_output_mask(lut_model_output_port)[output_pin->pin_number]; + const AtomNetlist::TruthTable& frac_lut_tt = adapt_truth_table_for_frac_lut(lut_frac_level, lut_output_mask, adapt_tt); + physical_pb.set_truth_table(lut_pb_id, output_pin, frac_lut_tt); + } + } +} + +/*************************************************************************************** + * This function will iterate over all the physical pb that are + * binded to clustered blocks and build the truth tables for the + * physical Look-Up Table (LUT) implementations. + * Note that the truth table built here is different from the atom + * netlists in VPR context. We consider fracturable LUT features + * and LUTs operating as wires + ***************************************************************************************/ +void build_physical_lut_truth_tables(VprClusteringAnnotation& cluster_annotation, + const AtomContext& atom_ctx, + const ClusteringContext& cluster_ctx, + const VprDeviceAnnotation& device_annotation, + const CircuitLibrary& circuit_lib) { + vtr::ScopedStartFinishTimer timer("Build truth tables for physical LUTs"); + + for (auto blk_id : cluster_ctx.clb_nlist.blocks()) { + PhysicalPb& physical_pb = cluster_annotation.mutable_physical_pb(blk_id); + /* Find the LUT physical pb id */ + for (const PhysicalPbId& primitive_pb : physical_pb.primitive_pbs()) { + CircuitModelId circuit_model = device_annotation.pb_type_circuit_model(physical_pb.pb_graph_node(primitive_pb)->pb_type); + VTR_ASSERT(true == circuit_lib.valid_model_id(circuit_model)); + if (CIRCUIT_MODEL_LUT != circuit_lib.model_type(circuit_model)) { + continue; + } + + /* Reach here, we have a LUT to deal with. Find the truth tables that mapped to the LUT */ + build_physical_pb_lut_truth_tables(physical_pb, primitive_pb, atom_ctx, device_annotation, circuit_lib); + } + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/repack/build_physical_truth_table.h b/openfpga/src/repack/build_physical_truth_table.h new file mode 100644 index 000000000..c5d14059f --- /dev/null +++ b/openfpga/src/repack/build_physical_truth_table.h @@ -0,0 +1,27 @@ +#ifndef BUILD_PHYSICAL_TRUTH_TABLE_H +#define BUILD_PHYSICAL_TRUTH_TABLE_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "vpr_context.h" +#include "vpr_device_annotation.h" +#include "vpr_clustering_annotation.h" +#include "circuit_library.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void build_physical_lut_truth_tables(VprClusteringAnnotation& cluster_annotation, + const AtomContext& atom_ctx, + const ClusteringContext& cluster_ctx, + const VprDeviceAnnotation& device_annotation, + const CircuitLibrary& circuit_lib); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/repack/physical_pb.cpp b/openfpga/src/repack/physical_pb.cpp index 59417080e..74894293a 100644 --- a/openfpga/src/repack/physical_pb.cpp +++ b/openfpga/src/repack/physical_pb.cpp @@ -32,6 +32,11 @@ std::string PhysicalPb::name(const PhysicalPbId& pb) const { return names_[pb]; } +const t_pb_graph_node* PhysicalPb::pb_graph_node(const PhysicalPbId& pb) const { + VTR_ASSERT(true == valid_pb_id(pb)); + return pb_graph_nodes_[pb]; +} + /* Find the module id by a given name, return invalid if not found */ PhysicalPbId PhysicalPb::find_pb(const t_pb_graph_node* pb_graph_node) const { if (type2id_map_.find(pb_graph_node) != type2id_map_.end()) { @@ -76,7 +81,7 @@ AtomNetId PhysicalPb::pb_graph_pin_atom_net(const PhysicalPbId& pb, return AtomNetId::INVALID(); } -AtomNetlist::TruthTable PhysicalPb::truth_table(const PhysicalPbId& pb) const { +std::map PhysicalPb::truth_tables(const PhysicalPbId& pb) const { VTR_ASSERT(true == valid_pb_id(pb)); return truth_tables_[pb]; } @@ -109,6 +114,7 @@ PhysicalPbId PhysicalPb::create_pb(const t_pb_graph_node* pb_graph_node) { child_pbs_.emplace_back(); parent_pbs_.emplace_back(); + truth_tables_.emplace_back(); mode_bits_.emplace_back(); /* Register in the name2id map */ @@ -136,10 +142,16 @@ void PhysicalPb::add_child(const PhysicalPbId& parent, } void PhysicalPb::set_truth_table(const PhysicalPbId& pb, + const t_pb_graph_pin* pb_graph_pin, const AtomNetlist::TruthTable& truth_table) { VTR_ASSERT(true == valid_pb_id(pb)); + + if (0 < truth_tables_[pb].count(pb_graph_pin)) { + VTR_LOG_WARN("Overwrite truth tables mapped to pb_graph_pin '%s[%ld]!\n", + pb_graph_pin->port->name, pb_graph_pin->pin_number); + } - truth_tables_[pb] = truth_table; + truth_tables_[pb][pb_graph_pin] = truth_table; } void PhysicalPb::set_mode_bits(const PhysicalPbId& pb, diff --git a/openfpga/src/repack/physical_pb.h b/openfpga/src/repack/physical_pb.h index caea0a0b9..11559da6b 100644 --- a/openfpga/src/repack/physical_pb.h +++ b/openfpga/src/repack/physical_pb.h @@ -41,6 +41,7 @@ class PhysicalPb { physical_pb_range pbs() const; std::vector primitive_pbs() const; std::string name(const PhysicalPbId& pb) const; + const t_pb_graph_node* pb_graph_node(const PhysicalPbId& pb) const; PhysicalPbId find_pb(const t_pb_graph_node* name) const; PhysicalPbId parent(const PhysicalPbId& pb) const; PhysicalPbId child(const PhysicalPbId& pb, @@ -49,7 +50,7 @@ class PhysicalPb { std::vector atom_blocks(const PhysicalPbId& pb) const; AtomNetId pb_graph_pin_atom_net(const PhysicalPbId& pb, const t_pb_graph_pin* pb_graph_pin) const; - AtomNetlist::TruthTable truth_table(const PhysicalPbId& pb) const; + std::map truth_tables(const PhysicalPbId& pb) const; std::vector mode_bits(const PhysicalPbId& pb) const; public: /* Public mutators */ PhysicalPbId create_pb(const t_pb_graph_node* pb_graph_node); @@ -59,6 +60,7 @@ class PhysicalPb { void add_atom_block(const PhysicalPbId& pb, const AtomBlockId& atom_block); void set_truth_table(const PhysicalPbId& pb, + const t_pb_graph_pin* pb_graph_pin, const AtomNetlist::TruthTable& truth_table); void set_mode_bits(const PhysicalPbId& pb, const std::vector& mode_bits); @@ -82,7 +84,7 @@ class PhysicalPb { /* configuration bits * Truth tables and mode selection */ - vtr::vector truth_tables_; + vtr::vector> truth_tables_; vtr::vector> mode_bits_; diff --git a/openfpga/src/utils/lut_utils.cpp b/openfpga/src/utils/lut_utils.cpp index 16bb8c3f2..ca3ac343e 100644 --- a/openfpga/src/utils/lut_utils.cpp +++ b/openfpga/src/utils/lut_utils.cpp @@ -131,8 +131,8 @@ std::vector truth_table_to_string(const AtomNetlist::TruthTable& tt * --1- 1 * ********************************************************************/ -AtomNetlist::TruthTable build_post_routing_wired_lut_truth_table(const size_t& lut_size, - const size_t& wire_input_id) { +AtomNetlist::TruthTable build_wired_lut_truth_table(const size_t& lut_size, + const size_t& wire_input_id) { AtomNetlist::TruthTable tt; /* There is always only one line in this truth table */ diff --git a/openfpga/src/utils/lut_utils.h b/openfpga/src/utils/lut_utils.h index 21f638175..4b2f8bd58 100644 --- a/openfpga/src/utils/lut_utils.h +++ b/openfpga/src/utils/lut_utils.h @@ -24,8 +24,8 @@ AtomNetlist::TruthTable lut_truth_table_adaption(const AtomNetlist::TruthTable& std::vector truth_table_to_string(const AtomNetlist::TruthTable& tt); -AtomNetlist::TruthTable build_post_routing_wired_lut_truth_table(const size_t& lut_size, - const size_t& wire_input_id); +AtomNetlist::TruthTable build_wired_lut_truth_table(const size_t& lut_size, + const size_t& wire_input_id); AtomNetlist::TruthTable adapt_truth_table_for_frac_lut(const size_t& lut_frac_level, const size_t& lut_output_mask, From 075264e3e38b4310fa7eb8942a0b761c2e36bde9 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 25 Feb 2020 23:29:16 -0700 Subject: [PATCH 209/645] debugging LUT bitstream generation --- .../fpga_bitstream/build_grid_bitstream.cpp | 116 +++++++++++++++++- openfpga/src/utils/physical_pb_utils.cpp | 2 +- 2 files changed, 113 insertions(+), 5 deletions(-) diff --git a/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp b/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp index d344d8880..bce9f8271 100644 --- a/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp +++ b/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp @@ -2,6 +2,7 @@ * This file includes functions that are used for building bitstreams * for grids (CLBs, heterogenerous blocks, I/Os, etc.) *******************************************************************/ +#include #include /* Headers from vtrutil library */ @@ -22,9 +23,9 @@ #include "openfpga_naming.h" #include "mux_bitstream_constants.h" #include "pb_type_utils.h" +#include "lut_utils.h" #include "build_mux_bitstream.h" -//#include "build_lut_bitstream.h" #include "build_grid_bitstream.h" /* begin namespace openfpga */ @@ -335,6 +336,112 @@ void build_physical_block_interc_bitstream(BitstreamManager& bitstream_manager, } } +/******************************************************************** + * Generate bitstream for a LUT and add it to bitstream manager + * This function supports both single-output and fracturable LUTs + *******************************************************************/ +static +void build_lut_bitstream(BitstreamManager& bitstream_manager, + const ConfigBlockId& parent_configurable_block, + const VprDeviceAnnotation& device_annotation, + const ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const MuxLibrary& mux_lib, + const PhysicalPb& physical_pb, + const PhysicalPbId& lut_pb_id, + t_pb_type* lut_pb_type) { + + /* Ensure a valid physical pritimive pb */ + if (nullptr == lut_pb_type) { + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid lut_pb_type!\n"); + exit(1); + } + + CircuitModelId lut_model = device_annotation.pb_type_circuit_model(lut_pb_type); + VTR_ASSERT(CircuitModelId::INVALID() != lut_model); + VTR_ASSERT(CIRCUIT_MODEL_LUT == circuit_lib.model_type(lut_model)); + + /* Find the input ports for LUT size, this is used to decode the LUT memory bits! */ + std::vector model_input_ports = circuit_lib.model_ports_by_type(lut_model, CIRCUIT_MODEL_PORT_INPUT, true); + VTR_ASSERT(1 == model_input_ports.size()); + size_t lut_size = circuit_lib.port_size(model_input_ports[0]); + + /* Find SRAM ports for truth tables and mode-selection */ + std::vector lut_regular_sram_ports = find_circuit_regular_sram_ports(circuit_lib, lut_model); + std::vector lut_mode_select_ports = find_circuit_mode_select_sram_ports(circuit_lib, lut_model); + /* We should always 1 regular sram port, where truth table is loaded to */ + VTR_ASSERT(1 == lut_regular_sram_ports.size()); + /* We may have a port for mode select or not. This depends on if the LUT is fracturable or not */ + VTR_ASSERT( (0 == lut_mode_select_ports.size()) + || (1 == lut_mode_select_ports.size()) ); + + std::vector lut_bitstream; + /* Generate bitstream for the LUT */ + if (false == physical_pb.valid_pb_id(lut_pb_id)) { + /* An empty pb means that this is an unused LUT, + * we give an empty truth table, which are full of default values (defined by users) + */ + for (size_t i = 0; i < circuit_lib.port_size(lut_regular_sram_ports[0]); ++i) { + VTR_ASSERT( (0 == circuit_lib.port_default_value(lut_regular_sram_ports[0])) + || (1 == circuit_lib.port_default_value(lut_regular_sram_ports[0])) ); + lut_bitstream.push_back(1 == circuit_lib.port_default_value(lut_regular_sram_ports[0])); + } + } else { + VTR_ASSERT(true == physical_pb.valid_pb_id(lut_pb_id)); + + /* Find MUX graph correlated to the LUT */ + MuxId lut_mux_id = mux_lib.mux_graph(lut_model, (size_t)pow(2., lut_size)); + const MuxGraph& mux_graph = mux_lib.mux_graph(lut_mux_id); + /* Ensure the LUT MUX has the expected input and SRAM port sizes */ + VTR_ASSERT(mux_graph.num_memory_bits() == lut_size); + VTR_ASSERT(mux_graph.num_inputs() == (size_t)pow(2., lut_size)); + /* Generate LUT bitstream */ + lut_bitstream = build_frac_lut_bitstream(circuit_lib, mux_graph, + device_annotation, + physical_pb.truth_tables(lut_pb_id), + circuit_lib.port_default_value(lut_regular_sram_ports[0])); + } + + /* Generate bitstream for mode-select ports */ + if (0 != lut_mode_select_ports.size()) { + std::vector mode_select_bitstream; + /* TODO: Xifan Tang: find out why some lut_pb has no mode bits!!! + * I suspect that wire LUTs are not mapped to any pb + */ + if ( (true == physical_pb.valid_pb_id(lut_pb_id)) + && (0 != physical_pb.mode_bits(lut_pb_id).size()) ) { + mode_select_bitstream = generate_mode_select_bitstream(physical_pb.mode_bits(lut_pb_id)); + } else { /* get default mode_bits */ + mode_select_bitstream = generate_mode_select_bitstream(device_annotation.pb_type_mode_bits(lut_pb_type)); + } + /* Conjunct the mode-select bitstream to the lut bitstream */ + for (const bool& bit : mode_select_bitstream) { + lut_bitstream.push_back(bit); + } + } + + /* Ensure the length of bitstream matches the side of memory circuits */ + std::vector sram_models = find_circuit_sram_models(circuit_lib, lut_model); + VTR_ASSERT(1 == sram_models.size()); + std::string mem_block_name = generate_memory_module_name(circuit_lib, lut_model, sram_models[0], std::string(MEMORY_MODULE_POSTFIX)); + ModuleId mem_module = module_manager.find_module(mem_block_name); + VTR_ASSERT (true == module_manager.valid_module_id(mem_module)); + ModulePortId mem_out_port_id = module_manager.find_module_port(mem_module, generate_configuration_chain_data_out_name()); + VTR_ASSERT(lut_bitstream.size() == module_manager.module_port(mem_module, mem_out_port_id).get_width()); + + /* Create a block for the bitstream which corresponds to the memory module associated to the LUT */ + ConfigBlockId mem_block = bitstream_manager.add_block(mem_block_name); + bitstream_manager.add_child_block(parent_configurable_block, mem_block); + + /* Add the bitstream to the bitstream manager */ + for (const bool& bit : lut_bitstream) { + ConfigBitId config_bit = bitstream_manager.add_bit(bit); + /* Link the memory bits to the mux mem block */ + bitstream_manager.add_bit_to_block(mem_block, config_bit); + } +} + /******************************************************************** * This function generates bitstream for a physical block, which is * a child block of a grid @@ -400,9 +507,10 @@ void rec_build_physical_block_bitstream(BitstreamManager& bitstream_manager, /* Special case for LUT !!! * Mapped logical block information is stored in child_pbs of this pb!!! */ - //build_lut_bitstream(bitstream_manager, pb_configurable_block, - // module_manager, circuit_lib, mux_lib, - // physical_pb, pb_id, physical_pb_type); + build_lut_bitstream(bitstream_manager, pb_configurable_block, + device_annotation, + module_manager, circuit_lib, mux_lib, + physical_pb, pb_id, physical_pb_type); break; case CIRCUIT_MODEL_FF: case CIRCUIT_MODEL_HARDLOGIC: diff --git a/openfpga/src/utils/physical_pb_utils.cpp b/openfpga/src/utils/physical_pb_utils.cpp index 71cf98677..9fe61ef89 100644 --- a/openfpga/src/utils/physical_pb_utils.cpp +++ b/openfpga/src/utils/physical_pb_utils.cpp @@ -236,7 +236,7 @@ void rec_update_physical_pb_from_operating_pb(PhysicalPb& phy_pb, VTR_ASSERT(true == phy_pb.valid_pb_id(physical_pb)); /* Set the mode bits */ - phy_pb.set_mode_bits(physical_pb, device_annotation.pb_type_mode_bits(physical_pb_graph_node->pb_type)); + phy_pb.set_mode_bits(physical_pb, device_annotation.pb_type_mode_bits(pb_type)); /* Find mapped atom block and add to this physical pb */ AtomBlockId atom_blk = atom_ctx.nlist.find_block(op_pb->name); From 759758421d92babfe17dd65669581abc520e82a2 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 25 Feb 2020 23:45:49 -0700 Subject: [PATCH 210/645] found the bug in physical pb mode bits and fixed --- openfpga/src/fpga_bitstream/build_grid_bitstream.cpp | 3 +-- openfpga/src/utils/physical_pb_utils.cpp | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp b/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp index bce9f8271..b50d05f85 100644 --- a/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp +++ b/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp @@ -409,8 +409,7 @@ void build_lut_bitstream(BitstreamManager& bitstream_manager, /* TODO: Xifan Tang: find out why some lut_pb has no mode bits!!! * I suspect that wire LUTs are not mapped to any pb */ - if ( (true == physical_pb.valid_pb_id(lut_pb_id)) - && (0 != physical_pb.mode_bits(lut_pb_id).size()) ) { + if (true == physical_pb.valid_pb_id(lut_pb_id)) { mode_select_bitstream = generate_mode_select_bitstream(physical_pb.mode_bits(lut_pb_id)); } else { /* get default mode_bits */ mode_select_bitstream = generate_mode_select_bitstream(device_annotation.pb_type_mode_bits(lut_pb_type)); diff --git a/openfpga/src/utils/physical_pb_utils.cpp b/openfpga/src/utils/physical_pb_utils.cpp index 9fe61ef89..93fbba992 100644 --- a/openfpga/src/utils/physical_pb_utils.cpp +++ b/openfpga/src/utils/physical_pb_utils.cpp @@ -32,6 +32,8 @@ void rec_alloc_physical_pb_from_pb_graph(PhysicalPb& phy_pb, /* Finish for primitive node */ if (true == is_primitive_pb_type(pb_type)) { + /* Deposite mode bits here */ + phy_pb.set_mode_bits(cur_phy_pb_id, device_annotation.pb_type_mode_bits(pb_type)); return; } From a26d31b87f411c82e59e96fbe5c79c2dcace4177 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 26 Feb 2020 11:09:23 -0700 Subject: [PATCH 211/645] make write bitstream online --- openfpga/src/base/openfpga_bitstream.cpp | 27 +++++++++++++++++-- openfpga/src/base/openfpga_bitstream.h | 3 +++ .../src/base/openfpga_bitstream_command.cpp | 22 ++++++++++++++- openfpga/src/base/openfpga_context.h | 4 +++ .../src/fpga_bitstream/bitstream_writer.cpp | 7 ++++- .../fpga_bitstream/build_fabric_bitstream.cpp | 7 ++++- .../fpga_bitstream/build_fabric_bitstream.h | 3 ++- openfpga/test_script/s298_k6_frac.openfpga | 3 ++- 8 files changed, 69 insertions(+), 7 deletions(-) diff --git a/openfpga/src/base/openfpga_bitstream.cpp b/openfpga/src/base/openfpga_bitstream.cpp index 8a51899e0..08fa7bf16 100644 --- a/openfpga/src/base/openfpga_bitstream.cpp +++ b/openfpga/src/base/openfpga_bitstream.cpp @@ -6,6 +6,8 @@ #include "vtr_log.h" #include "build_device_bitstream.h" +#include "bitstream_writer.h" +#include "build_fabric_bitstream.h" #include "openfpga_bitstream.h" /* Include global variables of VPR */ @@ -15,14 +17,35 @@ namespace openfpga { /******************************************************************** - * A wrapper function to call the fabric_verilog function of FPGA-Verilog + * A wrapper function to call the build_device_bitstream() in FPGA bitstream *******************************************************************/ void fpga_bitstream(OpenfpgaContext& openfpga_ctx, const Command& cmd, const CommandContext& cmd_context) { CommandOptionId opt_verbose = cmd.option("verbose"); + CommandOptionId opt_file = cmd.option("file"); - openfpga_ctx.mutable_bitstream_manager() = build_device_bitstream(g_vpr_ctx, openfpga_ctx, cmd_context.option_enable(cmd, opt_verbose)); + openfpga_ctx.mutable_bitstream_manager() = build_device_bitstream(g_vpr_ctx, + openfpga_ctx, + cmd_context.option_enable(cmd, opt_verbose)); + + if (true == cmd_context.option_enable(cmd, opt_file)) { + write_arch_independent_bitstream_to_xml_file(openfpga_ctx.bitstream_manager(), + cmd_context.option_value(cmd, opt_file)); + } +} + +/******************************************************************** + * A wrapper function to call the build_fabric_bitstream() in FPGA bitstream + *******************************************************************/ +void build_fabric_bitstream(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context) { + + CommandOptionId opt_verbose = cmd.option("verbose"); + + openfpga_ctx.mutable_fabric_bitstream() = build_fabric_dependent_bitstream(openfpga_ctx.bitstream_manager(), + openfpga_ctx.module_graph(), + cmd_context.option_enable(cmd, opt_verbose)); } } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_bitstream.h b/openfpga/src/base/openfpga_bitstream.h index 1411dc090..9dc2ad471 100644 --- a/openfpga/src/base/openfpga_bitstream.h +++ b/openfpga/src/base/openfpga_bitstream.h @@ -18,6 +18,9 @@ namespace openfpga { void fpga_bitstream(OpenfpgaContext& openfpga_ctx, const Command& cmd, const CommandContext& cmd_context); +void build_fabric_bitstream(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context); + } /* end namespace openfpga */ #endif diff --git a/openfpga/src/base/openfpga_bitstream_command.cpp b/openfpga/src/base/openfpga_bitstream_command.cpp index af06cd60b..96201b6c7 100644 --- a/openfpga/src/base/openfpga_bitstream_command.cpp +++ b/openfpga/src/base/openfpga_bitstream_command.cpp @@ -39,8 +39,12 @@ void add_openfpga_bitstream_commands(openfpga::Shell& shell) { * Command 'fpga_bitstream' */ Command shell_cmd_fpga_bitstream("fpga_bitstream"); + + /* Add an option '--file' in short '-f'*/ + CommandOptionId fpga_bitstream_opt_file = shell_cmd_fpga_bitstream.add_option("file", true, "file path to output the bitstream database"); + shell_cmd_fpga_bitstream.set_option_short_name(fpga_bitstream_opt_file, "f"); + shell_cmd_fpga_bitstream.set_option_require_value(fpga_bitstream_opt_file, openfpga::OPT_STRING); /* Add an option '--verbose' */ - shell_cmd_fpga_bitstream.add_option("fabric_dependent", false, "Enable the bitstream construction for the FPGA fabric"); shell_cmd_fpga_bitstream.add_option("verbose", false, "Enable verbose output"); /* Add command 'fpga_bitstream' to the Shell */ @@ -53,6 +57,22 @@ void add_openfpga_bitstream_commands(openfpga::Shell& shell) { cmd_dependency_fpga_bitstream.push_back(shell_cmd_repack_id); shell.set_command_dependency(shell_cmd_fpga_bitstream_id, cmd_dependency_fpga_bitstream); + /******************************** + * Command 'build_fabric_bitstream' + */ + Command shell_cmd_fabric_bitstream("build_fabric_bitstream"); + /* Add an option '--verbose' */ + shell_cmd_fabric_bitstream.add_option("verbose", false, "Enable verbose output"); + + /* Add command 'fabric_bitstream' to the Shell */ + ShellCommandId shell_cmd_fabric_bitstream_id = shell.add_command(shell_cmd_fabric_bitstream, "Reorganize the fabric-independent bitstream for the FPGA fabric created by FPGA-Verilog"); + shell.set_command_class(shell_cmd_fabric_bitstream_id, openfpga_bitstream_cmd_class); + shell.set_command_execute_function(shell_cmd_fabric_bitstream_id, build_fabric_bitstream); + + /* The 'fabric_bitstream' command should NOT be executed before 'fpga_bitstream' */ + std::vector cmd_dependency_fabric_bitstream; + cmd_dependency_fabric_bitstream.push_back(shell_cmd_fpga_bitstream_id); + shell.set_command_dependency(shell_cmd_fabric_bitstream_id, cmd_dependency_fabric_bitstream); } } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_context.h b/openfpga/src/base/openfpga_context.h index 9c685b247..521a2cf6a 100644 --- a/openfpga/src/base/openfpga_context.h +++ b/openfpga/src/base/openfpga_context.h @@ -1,6 +1,7 @@ #ifndef OPENFPGA_CONTEXT_H #define OPENFPGA_CONTEXT_H +#include #include "vpr_context.h" #include "openfpga_arch.h" #include "vpr_netlist_annotation.h" @@ -56,6 +57,7 @@ class OpenfpgaContext : public Context { const openfpga::ModuleManager& module_graph() const { return module_graph_; } const openfpga::FlowManager& flow_manager() const { return flow_manager_; } const openfpga::BitstreamManager& bitstream_manager() const { return bitstream_manager_; } + const std::vector& fabric_bitstream() const { return fabric_bitstream_; } public: /* Public mutators */ openfpga::Arch& mutable_arch() { return arch_; } openfpga::VprDeviceAnnotation& mutable_vpr_device_annotation() { return vpr_device_annotation_; } @@ -69,6 +71,7 @@ class OpenfpgaContext : public Context { openfpga::ModuleManager& mutable_module_graph() { return module_graph_; } openfpga::FlowManager& mutable_flow_manager() { return flow_manager_; } openfpga::BitstreamManager& mutable_bitstream_manager() { return bitstream_manager_; } + std::vector& mutable_fabric_bitstream() { return fabric_bitstream_; } private: /* Internal data */ /* Data structure to store information from read_openfpga_arch library */ openfpga::Arch arch_; @@ -102,6 +105,7 @@ class OpenfpgaContext : public Context { /* Bitstream database */ openfpga::BitstreamManager bitstream_manager_; + std::vector fabric_bitstream_; /* Flow status */ openfpga::FlowManager flow_manager_; diff --git a/openfpga/src/fpga_bitstream/bitstream_writer.cpp b/openfpga/src/fpga_bitstream/bitstream_writer.cpp index f19e3819f..6f8539d3f 100644 --- a/openfpga/src/fpga_bitstream/bitstream_writer.cpp +++ b/openfpga/src/fpga_bitstream/bitstream_writer.cpp @@ -111,7 +111,12 @@ void rec_write_block_bitstream_to_xml_file(std::fstream& fp, *******************************************************************/ void write_arch_independent_bitstream_to_xml_file(const BitstreamManager& bitstream_manager, const std::string& fname) { - std::string timer_message = std::string("Writing ") + std::to_string(bitstream_manager.bits().size()) + std::string(" architecture independent bitstream into XML file '") + fname + std::string("'\n"); + /* Ensure that we have a valid file name */ + if (true == fname.empty()) { + VTR_LOG_ERROR("Received empty file name to output bitstream!\n\tPlease specify a valid file name.\n"); + } + + std::string timer_message = std::string("Write ") + std::to_string(bitstream_manager.bits().size()) + std::string(" architecture independent bitstream into XML file '") + fname + std::string("'"); vtr::ScopedStartFinishTimer timer(timer_message); /* Create the file stream */ diff --git a/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp b/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp index 241aee04d..0a15f6930 100644 --- a/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp +++ b/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp @@ -83,7 +83,8 @@ void rec_build_module_fabric_dependent_bitstream(const BitstreamManager& bitstre * It was done in the function build_device_bitstream() *******************************************************************/ std::vector build_fabric_dependent_bitstream(const BitstreamManager& bitstream_manager, - const ModuleManager& module_manager) { + const ModuleManager& module_manager, + const bool& verbose) { std::vector fabric_bitstream; vtr::ScopedStartFinishTimer timer("\nBuild fabric dependent bitstream\n"); @@ -125,6 +126,10 @@ std::vector build_fabric_dependent_bitstream(const BitstreamManager /* Ensure our fabric bitstream is in the same size as device bistream */ VTR_ASSERT(bitstream_manager.bits().size() == fabric_bitstream.size()); + VTR_LOGV(verbose, + "Built %lu configuration bits for fabric\n", + fabric_bitstream.size()); + return fabric_bitstream; } diff --git a/openfpga/src/fpga_bitstream/build_fabric_bitstream.h b/openfpga/src/fpga_bitstream/build_fabric_bitstream.h index 47d46084c..507454059 100644 --- a/openfpga/src/fpga_bitstream/build_fabric_bitstream.h +++ b/openfpga/src/fpga_bitstream/build_fabric_bitstream.h @@ -16,7 +16,8 @@ namespace openfpga { std::vector build_fabric_dependent_bitstream(const BitstreamManager& bitstream_manager, - const ModuleManager& module_manager); + const ModuleManager& module_manager, + const bool& verbose); } /* end namespace openfpga */ diff --git a/openfpga/test_script/s298_k6_frac.openfpga b/openfpga/test_script/s298_k6_frac.openfpga index 490284fe4..5caf1d24c 100644 --- a/openfpga/test_script/s298_k6_frac.openfpga +++ b/openfpga/test_script/s298_k6_frac.openfpga @@ -30,7 +30,8 @@ build_fabric --compress_routing --duplicate_grid_pin #--verbose repack --verbose # Build the bitstream -fpga_bitstream --verbose +# - Output the fabric-independent bitstream to a file +fpga_bitstream --verbose --file /var/tmp/xtang/openfpga_test_src/fabric_indepenent_bitstream.xml # Write the Verilog netlit for FPGA fabric # - Enable the use of explicit port mapping in Verilog netlist From 410dcf6ab6d194aebc0f16e2f8f6b759b8e66c3d Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 26 Feb 2020 11:42:18 -0700 Subject: [PATCH 212/645] debugged LUT bitstream --- openfpga/src/fpga_bitstream/build_grid_bitstream.cpp | 3 --- openfpga/src/repack/build_physical_truth_table.cpp | 5 ++--- openfpga/src/repack/repack.cpp | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp b/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp index b50d05f85..253798c86 100644 --- a/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp +++ b/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp @@ -406,9 +406,6 @@ void build_lut_bitstream(BitstreamManager& bitstream_manager, /* Generate bitstream for mode-select ports */ if (0 != lut_mode_select_ports.size()) { std::vector mode_select_bitstream; - /* TODO: Xifan Tang: find out why some lut_pb has no mode bits!!! - * I suspect that wire LUTs are not mapped to any pb - */ if (true == physical_pb.valid_pb_id(lut_pb_id)) { mode_select_bitstream = generate_mode_select_bitstream(physical_pb.mode_bits(lut_pb_id)); } else { /* get default mode_bits */ diff --git a/openfpga/src/repack/build_physical_truth_table.cpp b/openfpga/src/repack/build_physical_truth_table.cpp index c749ae206..5e291d88b 100644 --- a/openfpga/src/repack/build_physical_truth_table.cpp +++ b/openfpga/src/repack/build_physical_truth_table.cpp @@ -98,10 +98,9 @@ void build_physical_pb_lut_truth_tables(PhysicalPb& physical_pb, input_nets.push_back(physical_pb.pb_graph_pin_atom_net(lut_pb_id, &(pb_graph_node->input_pins[0][ipin]))); } - /* Find all the nets mapped to each outputs */ - for (int iport = 0; iport < pb_graph_node->num_input_ports; ++iport) { - for (int ipin = 0; ipin < pb_graph_node->num_input_pins[iport]; ++ipin) { + for (int iport = 0; iport < pb_graph_node->num_output_ports; ++iport) { + for (int ipin = 0; ipin < pb_graph_node->num_output_pins[iport]; ++ipin) { const t_pb_graph_pin* output_pin = &(pb_graph_node->output_pins[iport][ipin]); AtomNetId output_net = physical_pb.pb_graph_pin_atom_net(lut_pb_id, output_pin); /* Bypass unmapped pins */ diff --git a/openfpga/src/repack/repack.cpp b/openfpga/src/repack/repack.cpp index 371d4ab46..6f312c0b7 100644 --- a/openfpga/src/repack/repack.cpp +++ b/openfpga/src/repack/repack.cpp @@ -292,7 +292,7 @@ void repack_cluster(const AtomContext& atom_ctx, clustering_ctx.clb_nlist.block_pb(block_id)->pb_route, atom_ctx, device_annotation); - /* TODO: save routing results */ + /* Save routing results */ save_lb_router_results_to_physical_pb(phy_pb, lb_router, lb_rr_graph); VTR_LOGV(verbose, "Saved results in physical pb\n"); From 25e0583636b4893fbdab8f13bba41d50a06af805 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 26 Feb 2020 17:10:57 -0700 Subject: [PATCH 213/645] add io location map data structure and start porting verilog testbench generator --- openfpga/src/base/io_location_map.cpp | 46 ++ openfpga/src/base/io_location_map.h | 39 ++ openfpga/src/base/openfpga_context.h | 4 + .../fpga_verilog/verilog_testbench_utils.cpp | 576 ++++++++++++++++++ .../fpga_verilog/verilog_testbench_utils.h | 86 +++ .../src/fpga_verilog/verilog_writer_utils.h | 1 + openfpga/test_script/s298_k6_frac.openfpga | 2 +- 7 files changed, 753 insertions(+), 1 deletion(-) create mode 100644 openfpga/src/base/io_location_map.cpp create mode 100644 openfpga/src/base/io_location_map.h create mode 100644 openfpga/src/fpga_verilog/verilog_testbench_utils.cpp create mode 100644 openfpga/src/fpga_verilog/verilog_testbench_utils.h diff --git a/openfpga/src/base/io_location_map.cpp b/openfpga/src/base/io_location_map.cpp new file mode 100644 index 000000000..7e0c07dfa --- /dev/null +++ b/openfpga/src/base/io_location_map.cpp @@ -0,0 +1,46 @@ +/****************************************************************************** + * Memember functions for data structure IoLocationMap + ******************************************************************************/ +#include "vtr_assert.h" + +#include "io_location_map.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/************************************************** + * Public Accessors + *************************************************/ +size_t IoLocationMap::io_index(const size_t& x, const size_t& y, const size_t& z) const { + if (x >= io_indices_.size()) { + return size_t(-1); + } + + if (y >= io_indices_[x].size()) { + return size_t(-1); + } + + if (z >= io_indices_[x][y].size()) { + return size_t(-1); + } + + return io_indices_[x][y][z]; +} + +void IoLocationMap::set_io_index(const size_t& x, const size_t& y, const size_t& z, const size_t& io_index) { + if (x >= io_indices_.size()) { + io_indices_.resize(x); + } + + if (y >= io_indices_[x].size()) { + io_indices_[x].resize(y); + } + + if (z >= io_indices_[x][y].size()) { + io_indices_[x][y].resize(z); + } + + io_indices_[x][y][z] = io_index; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/base/io_location_map.h b/openfpga/src/base/io_location_map.h new file mode 100644 index 000000000..de6422d23 --- /dev/null +++ b/openfpga/src/base/io_location_map.h @@ -0,0 +1,39 @@ +#ifndef IO_LOCATION_MAP_H +#define IO_LOCATION_MAP_H + +/******************************************************************** + * Include header files required by the data structure definition + *******************************************************************/ +#include + +/* Begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * I/O location map is a data structure to bridge the index of I/Os + * in the FPGA fabric, i.e., the module graph, and logical location + * of the I/O in VPR coordinate system + * + * For example + * io[0] io[1] io[2] + * +-----------------+ +--------+ + * | | | | | + * | I/O | I/O | | I/O | + * | [0][y] | [0][y] | | [1][y] | + * | [0] | [1] | | [0] | + * +-----------------+ +--------+ + * + *******************************************************************/ +class IoLocationMap { + public: /* Public aggregators */ + size_t io_index(const size_t& x, const size_t& y, const size_t& z) const; + public: /* Public mutators */ + void set_io_index(const size_t& x, const size_t& y, const size_t& z, const size_t& io_index); + private: /* Internal Data */ + /* I/O index fast lookup by [x][y][z] location */ + std::vector>> io_indices_; +}; + +} /* End namespace openfpga*/ + +#endif diff --git a/openfpga/src/base/openfpga_context.h b/openfpga/src/base/openfpga_context.h index 521a2cf6a..4b6cdf2d2 100644 --- a/openfpga/src/base/openfpga_context.h +++ b/openfpga/src/base/openfpga_context.h @@ -15,6 +15,7 @@ #include "openfpga_flow_manager.h" #include "bitstream_manager.h" #include "device_rr_gsb.h" +#include "io_location_map.h" /******************************************************************** * This file includes the declaration of the date structure @@ -58,6 +59,7 @@ class OpenfpgaContext : public Context { const openfpga::FlowManager& flow_manager() const { return flow_manager_; } const openfpga::BitstreamManager& bitstream_manager() const { return bitstream_manager_; } const std::vector& fabric_bitstream() const { return fabric_bitstream_; } + const openfpga::IoLocationMap& io_location_map() { return io_location_map_; } public: /* Public mutators */ openfpga::Arch& mutable_arch() { return arch_; } openfpga::VprDeviceAnnotation& mutable_vpr_device_annotation() { return vpr_device_annotation_; } @@ -72,6 +74,7 @@ class OpenfpgaContext : public Context { openfpga::FlowManager& mutable_flow_manager() { return flow_manager_; } openfpga::BitstreamManager& mutable_bitstream_manager() { return bitstream_manager_; } std::vector& mutable_fabric_bitstream() { return fabric_bitstream_; } + openfpga::IoLocationMap& mutable_io_location_map() { return io_location_map_; } private: /* Internal data */ /* Data structure to store information from read_openfpga_arch library */ openfpga::Arch arch_; @@ -102,6 +105,7 @@ class OpenfpgaContext : public Context { /* Fabric module graph */ openfpga::ModuleManager module_graph_; + openfpga::IoLocationMap io_location_map_; /* Bitstream database */ openfpga::BitstreamManager bitstream_manager_; diff --git a/openfpga/src/fpga_verilog/verilog_testbench_utils.cpp b/openfpga/src/fpga_verilog/verilog_testbench_utils.cpp new file mode 100644 index 000000000..e73fe5ee4 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_testbench_utils.cpp @@ -0,0 +1,576 @@ +/******************************************************************** + * This file includes most utilized functions that are used to create + * Verilog testbenches + * + * Note: please try to avoid using global variables in this file + * so that we can make it free to use anywhere + *******************************************************************/ +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" + +/* Headers from openfpgautil library */ +#include "openfpga_port.h" +#include "openfpga_digest.h" + +#include "verilog_port_types.h" + +#include "verilog_constants.h" +#include "verilog_writer_utils.h" +#include "verilog_testbench_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Print an instance of the FPGA top-level module + *******************************************************************/ +void print_verilog_testbench_fpga_instance(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module, + const std::string& top_instance_name) { + /* Validate the file stream */ + valid_file_stream(fp); + + /* Include defined top-level module */ + print_verilog_comment(fp, std::string("----- FPGA top-level module to be capsulated -----")); + + /* Create an empty port-to-port name mapping, because we use default names */ + std::map port2port_name_map; + + /* Use explicit port mapping for a clean instanciation */ + print_verilog_module_instance(fp, module_manager, top_module, + top_instance_name, + port2port_name_map, true); + + /* Add an empty line as a splitter */ + fp << std::endl; +} + +/******************************************************************** + * Instanciate the input benchmark module + *******************************************************************/ +void print_verilog_testbench_benchmark_instance(std::fstream& fp, + const std::string& module_name, + const std::string& instance_name, + const std::string& module_input_port_postfix, + const std::string& module_output_port_postfix, + const std::string& output_port_postfix, + const AtomContext& atom_ctx, + const bool& use_explicit_port_map) { + /* Validate the file stream */ + valid_file_stream(fp); + + fp << "\t" << module_name << " " << instance_name << "(" << std::endl; + + size_t port_counter = 0; + for (const AtomBlockId& atom_blk : atom_ctx.nlist.blocks()) { + /* Bypass non-I/O atom blocks ! */ + if ( (AtomBlockType::INPAD != atom_ctx.nlist.block_type(atom_blk)) + && (AtomBlockType::OUTPAD != atom_ctx.nlist.block_type(atom_blk)) ) { + continue; + } + /* The first port does not need a comma */ + if(0 < port_counter){ + fp << "," << std::endl; + } + /* Input port follows the logical block name while output port requires a special postfix */ + if (AtomBlockType::INPAD == atom_ctx.nlist.block_type(atom_blk)) { + fp << "\t\t"; + if (true == use_explicit_port_map) { + fp << "." << atom_ctx.nlist.block_name(atom_blk) << module_input_port_postfix << "("; + } + fp << atom_ctx.nlist.block_name(atom_blk); + if (true == use_explicit_port_map) { + fp << ")"; + } + } else { + VTR_ASSERT_SAFE(AtomBlockType::OUTPAD == atom_ctx.nlist.block_type(atom_blk)); + fp << "\t\t"; + if (true == use_explicit_port_map) { + fp << "." << atom_ctx.nlist.block_name(atom_blk) << module_output_port_postfix << "("; + } + fp << atom_ctx.nlist.block_name(atom_blk) << output_port_postfix; + if (true == use_explicit_port_map) { + fp << ")"; + } + } + /* Update the counter */ + port_counter++; + } + fp << "\t);" << std::endl; +} + +/******************************************************************** + * This function adds stimuli to I/Os of FPGA fabric + * 1. For mapped I/Os, this function will wire them to the input ports + * of the pre-configured FPGA top module + * 2. For unmapped I/Os, this function will assign a constant value + * by default + *******************************************************************/ +void print_verilog_testbench_connect_fpga_ios(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module, + const AtomContext& atom_ctx, + const PlacementContext& place_ctx, + const IoLocationMap& io_location_map, + const std::string& io_input_port_name_postfix, + const std::string& io_output_port_name_postfix, + const size_t& unused_io_value) { + /* Validate the file stream */ + valid_file_stream(fp); + + /* In this function, we support only 1 type of I/Os */ + VTR_ASSERT(1 == module_manager.module_ports_by_type(top_module, ModuleManager::MODULE_GPIO_PORT).size()); + BasicPort module_io_port = module_manager.module_ports_by_type(top_module, ModuleManager::MODULE_GPIO_PORT)[0]; + + /* Keep tracking which I/Os have been used */ + std::vector io_used(module_io_port.get_width(), false); + + /* See if this I/O should be wired to a benchmark input/output */ + /* Add signals from blif benchmark and short-wire them to FPGA I/O PADs + * This brings convenience to checking functionality + */ + print_verilog_comment(fp, std::string("----- Link BLIF Benchmark I/Os to FPGA I/Os -----")); + + for (const AtomBlockId& atom_blk : atom_ctx.nlist.blocks()) { + /* Bypass non-I/O atom blocks ! */ + if ( (AtomBlockType::INPAD != atom_ctx.nlist.block_type(atom_blk)) + && (AtomBlockType::OUTPAD != atom_ctx.nlist.block_type(atom_blk)) ) { + continue; + } + + /* Find the index of the mapped GPIO in top-level FPGA fabric */ + size_t io_index = io_location_map.io_index(place_ctx.block_locs[atom_ctx.lookup.atom_clb(atom_blk)].loc.x, + place_ctx.block_locs[atom_ctx.lookup.atom_clb(atom_blk)].loc.y, + place_ctx.block_locs[atom_ctx.lookup.atom_clb(atom_blk)].loc.z); + + /* Ensure that IO index is in range */ + BasicPort module_mapped_io_port = module_io_port; + /* Set the port pin index */ + VTR_ASSERT(io_index < module_mapped_io_port.get_width()); + module_mapped_io_port.set_width(io_index, io_index); + + /* Create the port for benchmark I/O, due to BLIF benchmark, each I/O always has a size of 1 + * In addition, the input and output ports may have different postfix in naming + * due to verification context! Here, we give full customization on naming + */ + BasicPort benchmark_io_port; + if (AtomBlockType::INPAD == atom_ctx.nlist.block_type(atom_blk)) { + benchmark_io_port.set_name(std::string(atom_ctx.nlist.block_name(atom_blk) + io_input_port_name_postfix)); + benchmark_io_port.set_width(1); + print_verilog_comment(fp, std::string("----- Blif Benchmark input " + atom_ctx.nlist.block_name(atom_blk) + " is mapped to FPGA IOPAD " + module_mapped_io_port.get_name() + "[" + std::to_string(io_index) + "] -----")); + print_verilog_wire_connection(fp, module_mapped_io_port, benchmark_io_port, false); + } else { + VTR_ASSERT(AtomBlockType::OUTPAD == atom_ctx.nlist.block_type(atom_blk)); + benchmark_io_port.set_name(std::string(atom_ctx.nlist.block_name(atom_blk) + io_output_port_name_postfix)); + benchmark_io_port.set_width(1); + print_verilog_comment(fp, std::string("----- Blif Benchmark output " + atom_ctx.nlist.block_name(atom_blk) + " is mapped to FPGA IOPAD " + module_mapped_io_port.get_name() + "[" + std::to_string(io_index) + "] -----")); + print_verilog_wire_connection(fp, benchmark_io_port, module_mapped_io_port, false); + } + + /* Mark this I/O has been used/wired */ + io_used[io_index] = true; + } + + /* Add an empty line as a splitter */ + fp << std::endl; + + /* Wire the unused iopads to a constant */ + print_verilog_comment(fp, std::string("----- Wire unused FPGA I/Os to constants -----")); + for (size_t io_index = 0; io_index < io_used.size(); ++io_index) { + /* Bypass used iopads */ + if (true == io_used[io_index]) { + continue; + } + + /* Wire to a contant */ + BasicPort module_unused_io_port = module_io_port; + /* Set the port pin index */ + module_unused_io_port.set_width(io_index, io_index); + + std::vector default_values(module_unused_io_port.get_width(), unused_io_value); + print_verilog_wire_constant_values(fp, module_unused_io_port, default_values); + } + + /* Add an empty line as a splitter */ + fp << std::endl; +} + +/******************************************************************** + * Print Verilog codes to set up a timeout for the simulation + * and dump the waveform to VCD files + * + * Note that: these codes are tuned for Icarus simulator!!! + *******************************************************************/ +void print_verilog_timeout_and_vcd(std::fstream& fp, + const std::string& icarus_preprocessing_flag, + const std::string& module_name, + const std::string& vcd_fname, + const std::string& simulation_start_counter_name, + const std::string& error_counter_name, + const int& simulation_time) { + /* Validate the file stream */ + valid_file_stream(fp); + + /* The following verilog codes are tuned for Icarus */ + print_verilog_preprocessing_flag(fp, icarus_preprocessing_flag); + + print_verilog_comment(fp, std::string("----- Begin Icarus requirement -------")); + + fp << "\tinitial begin" << std::endl; + fp << "\t\t$dumpfile(\"" << vcd_fname << "\");" << std::endl; + fp << "\t\t$dumpvars(1, " << module_name << ");" << std::endl; + fp << "\tend" << std::endl; + + /* Condition ends for the Icarus requirement */ + print_verilog_endif(fp); + + print_verilog_comment(fp, std::string("----- END Icarus requirement -------")); + + /* Add an empty line as splitter */ + fp << std::endl; + + BasicPort sim_start_port(simulation_start_counter_name, 1); + + fp << "initial begin" << std::endl; + fp << "\t" << generate_verilog_port(VERILOG_PORT_CONKT, sim_start_port) << " <= 1'b1;" << std::endl; + fp << "\t$timeformat(-9, 2, \"ns\", 20);" << std::endl; + fp << "\t$display(\"Simulation start\");" << std::endl; + print_verilog_comment(fp, std::string("----- Can be changed by the user for his/her need -------")); + fp << "\t#" << simulation_time << std::endl; + fp << "\tif(" << error_counter_name << " == 0) begin" << std::endl; + fp << "\t\t$display(\"Simulation Succeed\");" << std::endl; + fp << "\tend else begin" << std::endl; + fp << "\t\t$display(\"Simulation Failed with " << std::string("%d") << " error(s)\", " << error_counter_name << ");" << std::endl; + fp << "\tend" << std::endl; + fp << "\t$finish;" << std::endl; + fp << "end" << std::endl; + + /* Add an empty line as splitter */ + fp << std::endl; +} + +/******************************************************************** + * Generate the clock port name to be used in this testbench + * + * Restrictions: + * Assume this is a single clock benchmark + *******************************************************************/ +BasicPort generate_verilog_testbench_clock_port(const std::vector& clock_port_names, + const std::string& default_clock_name) { + if (0 == clock_port_names.size()) { + return BasicPort(default_clock_name, 1); + } + + VTR_ASSERT(1 == clock_port_names.size()); + return BasicPort(clock_port_names[0], 1); +} + +/******************************************************************** + * Print Verilog codes to check the equivalence of output vectors + * + * Restriction: this function only supports single clock benchmarks! + *******************************************************************/ +void print_verilog_testbench_check(std::fstream& fp, + const std::string& autochecked_preprocessing_flag, + const std::string& simulation_start_counter_name, + const std::string& benchmark_port_postfix, + const std::string& fpga_port_postfix, + const std::string& check_flag_port_postfix, + const std::string& error_counter_name, + const AtomContext& atom_ctx, + const std::vector& clock_port_names, + const std::string& default_clock_name) { + + /* Validate the file stream */ + valid_file_stream(fp); + + /* Add output autocheck conditionally: only when a preprocessing flag is enable */ + print_verilog_preprocessing_flag(fp, autochecked_preprocessing_flag); + + print_verilog_comment(fp, std::string("----- Begin checking output vectors -------")); + + BasicPort clock_port = generate_verilog_testbench_clock_port(clock_port_names, default_clock_name); + + print_verilog_comment(fp, std::string("----- Skip the first falling edge of clock, it is for initialization -------")); + + BasicPort sim_start_port(simulation_start_counter_name, 1); + + fp << "\t" << generate_verilog_port(VERILOG_PORT_REG, sim_start_port) << ";" << std::endl; + fp << std::endl; + + fp << "\talways@(negedge " << generate_verilog_port(VERILOG_PORT_CONKT, clock_port) << ") begin" << std::endl; + fp << "\t\tif (1'b1 == " << generate_verilog_port(VERILOG_PORT_CONKT, sim_start_port) << ") begin" << std::endl; + fp << "\t\t"; + print_verilog_register_connection(fp, sim_start_port, sim_start_port, true); + fp << "\t\tend else begin" << std::endl; + + for (const AtomBlockId& atom_blk : atom_ctx.nlist.blocks()) { + /* Bypass non-I/O atom blocks ! */ + if ( (AtomBlockType::INPAD != atom_ctx.nlist.block_type(atom_blk)) + && (AtomBlockType::OUTPAD != atom_ctx.nlist.block_type(atom_blk)) ) { + continue; + } + + if (AtomBlockType::OUTPAD == atom_ctx.nlist.block_type(atom_blk)) { + fp << "\t\t\tif(!(" << atom_ctx.nlist.block_name(atom_blk) << fpga_port_postfix; + fp << " === " << atom_ctx.nlist.block_name(atom_blk) << benchmark_port_postfix; + fp << ") && !(" << atom_ctx.nlist.block_name(atom_blk) << benchmark_port_postfix; + fp << " === 1'bx)) begin" << std::endl; + fp << "\t\t\t\t" << atom_ctx.nlist.block_name(atom_blk) << check_flag_port_postfix << " <= 1'b1;" << std::endl; + fp << "\t\t\tend else begin" << std::endl; + fp << "\t\t\t\t" << atom_ctx.nlist.block_name(atom_blk) << check_flag_port_postfix << "<= 1'b0;" << std::endl; + fp << "\t\t\tend" << std::endl; + } + } + fp << "\t\tend" << std::endl; + fp << "\tend" << std::endl; + + /* Add an empty line as splitter */ + fp << std::endl; + + for (const AtomBlockId& atom_blk : atom_ctx.nlist.blocks()) { + /* Only care about output atom blocks ! */ + if (AtomBlockType::OUTPAD != atom_ctx.nlist.block_type(atom_blk)) { + continue; + } + + fp << "\talways@(posedge " << atom_ctx.nlist.block_name(atom_blk) << check_flag_port_postfix << ") begin" << std::endl; + fp << "\t\tif(" << atom_ctx.nlist.block_name(atom_blk) << check_flag_port_postfix << ") begin" << std::endl; + fp << "\t\t\t" << error_counter_name << " = " << error_counter_name << " + 1;" << std::endl; + fp << "\t\t\t$display(\"Mismatch on " << atom_ctx.nlist.block_name(atom_blk) << fpga_port_postfix << " at time = " << std::string("%t") << "\", $realtime);" << std::endl; + fp << "\t\tend" << std::endl; + fp << "\tend" << std::endl; + + /* Add an empty line as splitter */ + fp << std::endl; + } + + /* Condition ends */ + print_verilog_endif(fp); + + /* Add an empty line as splitter */ + fp << std::endl; +} + +/******************************************************************** + * Generate random stimulus for the clock port + * This function is designed to drive the clock port of a benchmark module + * If there is no clock port found, we will give a default clock name + * In such case, this clock will not be wired to the benchmark module + * but be only used as a synchronizer in verification + *******************************************************************/ +void print_verilog_testbench_clock_stimuli(std::fstream& fp, + const SimulationSetting& simulation_parameters, + const BasicPort& clock_port) { + /* Validate the file stream */ + valid_file_stream(fp); + + print_verilog_comment(fp, std::string("----- Clock Initialization -------")); + + fp << "\tinitial begin" << std::endl; + /* Create clock stimuli */ + fp << "\t\t" << generate_verilog_port(VERILOG_PORT_CONKT, clock_port) << " <= 1'b0;" << std::endl; + fp << "\t\twhile(1) begin" << std::endl; + fp << "\t\t\t#" << std::setprecision(10) << ((0.5/simulation_parameters.operating_clock_frequency())/VERILOG_SIM_TIMESCALE) << std::endl; + fp << "\t\t\t" << generate_verilog_port(VERILOG_PORT_CONKT, clock_port); + fp << " <= !"; + fp << generate_verilog_port(VERILOG_PORT_CONKT, clock_port); + fp << ";" << std::endl; + fp << "\t\tend" << std::endl; + + fp << "\tend" << std::endl; + + /* Add an empty line as splitter */ + fp << std::endl; +} + +/******************************************************************** + * Generate random stimulus for the input ports (non-clock signals) + * For clock signals, please use print_verilog_testbench_clock_stimuli + *******************************************************************/ +void print_verilog_testbench_random_stimuli(std::fstream& fp, + const AtomContext& atom_ctx, + const std::string& check_flag_port_postfix, + const BasicPort& clock_port) { + /* Validate the file stream */ + valid_file_stream(fp); + + print_verilog_comment(fp, std::string("----- Input Initialization -------")); + + fp << "\tinitial begin" << std::endl; + + for (const AtomBlockId& atom_blk : atom_ctx.nlist.blocks()) { + /* Bypass non-I/O atom blocks ! */ + if ( (AtomBlockType::INPAD != atom_ctx.nlist.block_type(atom_blk)) + && (AtomBlockType::OUTPAD != atom_ctx.nlist.block_type(atom_blk)) ) { + continue; + } + + /* TODO: find the clock inputs will be initialized later */ + if (AtomBlockType::INPAD == atom_ctx.nlist.block_type(atom_blk)) { + fp << "\t\t" << atom_ctx.nlist.block_name(atom_blk) << " <= 1'b0;" << std::endl; + } + } + + /* Add an empty line as splitter */ + fp << std::endl; + + /* Set 0 to registers for checking flags */ + for (const AtomBlockId& atom_blk : atom_ctx.nlist.blocks()) { + /* Bypass non-I/O atom blocks ! */ + if (AtomBlockType::OUTPAD != atom_ctx.nlist.block_type(atom_blk)) { + continue; + } + + /* Each logical block assumes a single-width port */ + BasicPort output_port(std::string(atom_ctx.nlist.block_name(atom_blk) + check_flag_port_postfix), 1); + fp << "\t\t" << generate_verilog_port(VERILOG_PORT_CONKT, output_port) << " <= 1'b0;" << std::endl; + } + + fp << "\tend" << std::endl; + /* Finish initialization */ + + /* Add an empty line as splitter */ + fp << std::endl; + + // Not ready yet to determine if input is reset +/* + fprintf(fp, "//----- Reset Stimulis\n"); + fprintf(fp, " initial begin\n"); + fprintf(fp, " #%.3f\n",(rand() % 10) + 0.001); + fprintf(fp, " %s <= !%s;\n", reset_input_name, reset_input_name); + fprintf(fp, " #%.3f\n",(rand() % 10) + 0.001); + fprintf(fp, " %s <= !%s;\n", reset_input_name, reset_input_name); + fprintf(fp, " while(1) begin\n"); + fprintf(fp, " #%.3f\n", (rand() % 15) + 0.5); + fprintf(fp, " %s <= !%s;\n", reset_input_name, reset_input_name); + fprintf(fp, " #%.3f\n", (rand() % 10000) + 200); + fprintf(fp, " %s <= !%s;\n", reset_input_name, reset_input_name); + fprintf(fp, " end\n"); + fprintf(fp, " end\n\n"); +*/ + + print_verilog_comment(fp, std::string("----- Input Stimulus -------")); + fp << "\talways@(negedge " << generate_verilog_port(VERILOG_PORT_CONKT, clock_port) << ") begin" << std::endl; + + for (const AtomBlockId& atom_blk : atom_ctx.nlist.blocks()) { + /* Bypass non-I/O atom blocks ! */ + if ( (AtomBlockType::INPAD != atom_ctx.nlist.block_type(atom_blk)) + && (AtomBlockType::OUTPAD != atom_ctx.nlist.block_type(atom_blk)) ) { + continue; + } + + /* TODO: find the clock inputs will be initialized later */ + if (AtomBlockType::INPAD != atom_ctx.nlist.block_type(atom_blk)) { + fp << "\t\t" << atom_ctx.nlist.block_name(atom_blk) << " <= $random;" << std::endl; + } + } + + fp << "\tend" << std::endl; + + /* Add an empty line as splitter */ + fp << std::endl; +} + +/******************************************************************** + * Print Verilog declaration of shared ports appear in testbenches + * which are + * 1. the shared input ports (registers) to drive both + * FPGA fabric and benchmark instance + * 2. the output ports (wires) for both FPGA fabric and benchmark instance + * 3. the checking flag ports to evaluate if outputs matches under the + * same input vectors + *******************************************************************/ +void print_verilog_testbench_shared_ports(std::fstream& fp, + const AtomContext& atom_ctx, + const std::string& benchmark_output_port_postfix, + const std::string& fpga_output_port_postfix, + const std::string& check_flag_port_postfix, + const std::string& autocheck_preprocessing_flag) { + /* Validate the file stream */ + valid_file_stream(fp); + + /* Instantiate register for inputs stimulis */ + print_verilog_comment(fp, std::string("----- Shared inputs -------")); + for (const AtomBlockId& atom_blk : atom_ctx.nlist.blocks()) { + /* We care only those logic blocks which are input I/Os */ + if (AtomBlockType::INPAD != atom_ctx.nlist.block_type(atom_blk)) { + continue; + } + + /* TODO: Skip clocks because they are handled in another function */ + + /* Each logical block assumes a single-width port */ + BasicPort input_port(atom_ctx.nlist.block_name(atom_blk), 1); + fp << "\t" << generate_verilog_port(VERILOG_PORT_REG, input_port) << ";" << std::endl; + } + + /* Add an empty line as splitter */ + fp << std::endl; + + /* Instantiate wires for FPGA fabric outputs */ + print_verilog_comment(fp, std::string("----- FPGA fabric outputs -------")); + + for (const AtomBlockId& atom_blk : atom_ctx.nlist.blocks()) { + /* We care only those logic blocks which are output I/Os */ + if (AtomBlockType::OUTPAD != atom_ctx.nlist.block_type(atom_blk)) { + continue; + } + + /* Each logical block assumes a single-width port */ + BasicPort output_port(std::string(atom_ctx.nlist.block_name(atom_blk) + fpga_output_port_postfix), 1); + fp << "\t" << generate_verilog_port(VERILOG_PORT_WIRE, output_port) << ";" << std::endl; + } + + /* Add an empty line as splitter */ + fp << std::endl; + + /* Benchmark is instanciated conditionally: only when a preprocessing flag is enable */ + print_verilog_preprocessing_flag(fp, std::string(autocheck_preprocessing_flag)); + + /* Add an empty line as splitter */ + fp << std::endl; + + /* Instantiate wire for benchmark output */ + print_verilog_comment(fp, std::string("----- Benchmark outputs -------")); + for (const AtomBlockId& atom_blk : atom_ctx.nlist.blocks()) { + /* We care only those logic blocks which are output I/Os */ + if (AtomBlockType::OUTPAD != atom_ctx.nlist.block_type(atom_blk)) { + continue; + } + + /* Each logical block assumes a single-width port */ + BasicPort output_port(std::string(atom_ctx.nlist.block_name(atom_blk) + benchmark_output_port_postfix), 1); + fp << "\t" << generate_verilog_port(VERILOG_PORT_WIRE, output_port) << ";" << std::endl; + } + + /* Add an empty line as splitter */ + fp << std::endl; + + /* Instantiate register for output comparison */ + print_verilog_comment(fp, std::string("----- Output vectors checking flags -------")); + for (const AtomBlockId& atom_blk : atom_ctx.nlist.blocks()) { + /* We care only those logic blocks which are output I/Os */ + if (AtomBlockType::OUTPAD != atom_ctx.nlist.block_type(atom_blk)) { + continue; + } + + /* Each logical block assumes a single-width port */ + BasicPort output_port(std::string(atom_ctx.nlist.block_name(atom_blk) + check_flag_port_postfix), 1); + fp << "\t" << generate_verilog_port(VERILOG_PORT_REG, output_port) << ";" << std::endl; + } + + /* Add an empty line as splitter */ + fp << std::endl; + + /* Condition ends for the benchmark instanciation */ + print_verilog_endif(fp); + + /* Add an empty line as splitter */ + fp << std::endl; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_testbench_utils.h b/openfpga/src/fpga_verilog/verilog_testbench_utils.h new file mode 100644 index 000000000..7de04deeb --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_testbench_utils.h @@ -0,0 +1,86 @@ +#ifndef VERILOG_TESTBENCH_UTILS_H +#define VERILOG_TESTBENCH_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include +#include "module_manager.h" +#include "vpr_context.h" +#include "io_location_map.h" +#include "simulation_setting.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_verilog_testbench_fpga_instance(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module, + const std::string& top_instance_name); + +void print_verilog_testbench_benchmark_instance(std::fstream& fp, + const std::string& module_name, + const std::string& instance_name, + const std::string& module_input_port_postfix, + const std::string& module_output_port_postfix, + const std::string& output_port_postfix, + const AtomContext& atom_ctx, + const bool& use_explicit_port_map); + +void print_verilog_testbench_connect_fpga_ios(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module, + const AtomContext& atom_ctx, + const PlacementContext& place_ctx, + const IoLocationMap& io_location_map, + const std::string& io_input_port_name_postfix, + const std::string& io_output_port_name_postfix, + const size_t& unused_io_value); + +void print_verilog_timeout_and_vcd(std::fstream& fp, + const std::string& icarus_preprocessing_flag, + const std::string& module_name, + const std::string& vcd_fname, + const std::string& simulation_start_counter_name, + const std::string& error_counter_name, + const int& simulation_time); + +BasicPort generate_verilog_testbench_clock_port(const std::vector& clock_port_names, + const std::string& default_clock_name); + +void print_verilog_testbench_check(std::fstream& fp, + const std::string& autochecked_preprocessing_flag, + const std::string& simulation_start_counter_name, + const std::string& benchmark_port_postfix, + const std::string& fpga_port_postfix, + const std::string& check_flag_port_postfix, + const std::string& error_counter_name, + const AtomContext& atom_ctx, + const std::vector& clock_port_names, + const std::string& default_clock_name); + +void print_verilog_testbench_clock_stimuli(std::fstream& fp, + const SimulationSetting& simulation_parameters, + const BasicPort& clock_port); + +void print_verilog_testbench_random_stimuli(std::fstream& fp, + const AtomContext& atom_ctx, + const std::string& check_flag_port_postfix, + const BasicPort& clock_port); + +void print_verilog_testbench_shared_ports(std::fstream& fp, + const AtomContext& atom_ctx, + const std::string& benchmark_output_port_postfix, + const std::string& fpga_output_port_postfix, + const std::string& check_flag_port_postfix, + const std::string& autocheck_preprocessing_flag); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_verilog/verilog_writer_utils.h b/openfpga/src/fpga_verilog/verilog_writer_utils.h index b145d8be6..8e6cddaaa 100644 --- a/openfpga/src/fpga_verilog/verilog_writer_utils.h +++ b/openfpga/src/fpga_verilog/verilog_writer_utils.h @@ -14,6 +14,7 @@ #include #include "openfpga_port.h" #include "verilog_port_types.h" +#include "circuit_library.h" #include "module_manager.h" /******************************************************************** diff --git a/openfpga/test_script/s298_k6_frac.openfpga b/openfpga/test_script/s298_k6_frac.openfpga index 5caf1d24c..0b2e3cc13 100644 --- a/openfpga/test_script/s298_k6_frac.openfpga +++ b/openfpga/test_script/s298_k6_frac.openfpga @@ -27,7 +27,7 @@ build_fabric --compress_routing --duplicate_grid_pin #--verbose # Repack the netlist to physical pbs # This must be done before bitstream generator and testbench generation # Strongly recommend it is done after all the fix-up have been applied -repack --verbose +repack #--verbose # Build the bitstream # - Output the fabric-independent bitstream to a file From b3796b08181a74eb5dcb35c34d0c65d761aba7cc Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 26 Feb 2020 19:58:18 -0700 Subject: [PATCH 214/645] build io location map --- openfpga/src/base/io_location_map.cpp | 6 +-- openfpga/src/base/openfpga_build_fabric.cpp | 43 +++++++++++---------- openfpga/src/base/openfpga_build_fabric.h | 2 +- openfpga/src/fabric/build_device_module.cpp | 6 ++- openfpga/src/fabric/build_device_module.h | 3 +- openfpga/src/fabric/build_top_module.cpp | 20 +++++++++- openfpga/src/fabric/build_top_module.h | 2 + 7 files changed, 53 insertions(+), 29 deletions(-) diff --git a/openfpga/src/base/io_location_map.cpp b/openfpga/src/base/io_location_map.cpp index 7e0c07dfa..460ba080d 100644 --- a/openfpga/src/base/io_location_map.cpp +++ b/openfpga/src/base/io_location_map.cpp @@ -29,15 +29,15 @@ size_t IoLocationMap::io_index(const size_t& x, const size_t& y, const size_t& z void IoLocationMap::set_io_index(const size_t& x, const size_t& y, const size_t& z, const size_t& io_index) { if (x >= io_indices_.size()) { - io_indices_.resize(x); + io_indices_.resize(x + 1); } if (y >= io_indices_[x].size()) { - io_indices_[x].resize(y); + io_indices_[x].resize(y + 1); } if (z >= io_indices_[x][y].size()) { - io_indices_[x][y].resize(z); + io_indices_[x][y].resize(z + 1); } io_indices_[x][y][z] = io_index; diff --git a/openfpga/src/base/openfpga_build_fabric.cpp b/openfpga/src/base/openfpga_build_fabric.cpp index 5c3d8a69d..a1e90055b 100644 --- a/openfpga/src/base/openfpga_build_fabric.cpp +++ b/openfpga/src/base/openfpga_build_fabric.cpp @@ -21,43 +21,43 @@ namespace openfpga { * This function should only be called after the GSB builder is done *******************************************************************/ static -void compress_routing_hierarchy(OpenfpgaContext& openfpga_context, +void compress_routing_hierarchy(OpenfpgaContext& openfpga_ctx, const bool& verbose_output) { vtr::ScopedStartFinishTimer timer("Identify unique General Switch Blocks (GSBs)"); /* Build unique module lists */ - openfpga_context.mutable_device_rr_gsb().build_unique_module(g_vpr_ctx.device().rr_graph); + openfpga_ctx.mutable_device_rr_gsb().build_unique_module(g_vpr_ctx.device().rr_graph); /* Report the stats */ VTR_LOGV(verbose_output, "Detected %lu unique X-direction connection blocks from a total of %d (compression rate=%d%)\n", - openfpga_context.device_rr_gsb().get_num_cb_unique_module(CHANX), - find_device_rr_gsb_num_cb_modules(openfpga_context.device_rr_gsb(), CHANX), - 100 * (openfpga_context.device_rr_gsb().get_num_cb_unique_module(CHANX) / find_device_rr_gsb_num_cb_modules(openfpga_context.device_rr_gsb(), CHANX) - 1)); + openfpga_ctx.device_rr_gsb().get_num_cb_unique_module(CHANX), + find_device_rr_gsb_num_cb_modules(openfpga_ctx.device_rr_gsb(), CHANX), + 100 * (openfpga_ctx.device_rr_gsb().get_num_cb_unique_module(CHANX) / find_device_rr_gsb_num_cb_modules(openfpga_ctx.device_rr_gsb(), CHANX) - 1)); VTR_LOGV(verbose_output, "Detected %lu unique Y-direction connection blocks from a total of %d (compression rate=%d%)\n", - openfpga_context.device_rr_gsb().get_num_cb_unique_module(CHANY), - find_device_rr_gsb_num_cb_modules(openfpga_context.device_rr_gsb(), CHANY), - 100 * (openfpga_context.device_rr_gsb().get_num_cb_unique_module(CHANY) / find_device_rr_gsb_num_cb_modules(openfpga_context.device_rr_gsb(), CHANY) - 1)); + openfpga_ctx.device_rr_gsb().get_num_cb_unique_module(CHANY), + find_device_rr_gsb_num_cb_modules(openfpga_ctx.device_rr_gsb(), CHANY), + 100 * (openfpga_ctx.device_rr_gsb().get_num_cb_unique_module(CHANY) / find_device_rr_gsb_num_cb_modules(openfpga_ctx.device_rr_gsb(), CHANY) - 1)); VTR_LOGV(verbose_output, "Detected %lu unique switch blocks from a total of %d (compression rate=%d%)\n", - openfpga_context.device_rr_gsb().get_num_sb_unique_module(), - find_device_rr_gsb_num_sb_modules(openfpga_context.device_rr_gsb()), - 100 * (openfpga_context.device_rr_gsb().get_num_sb_unique_module() / find_device_rr_gsb_num_sb_modules(openfpga_context.device_rr_gsb()) - 1)); + openfpga_ctx.device_rr_gsb().get_num_sb_unique_module(), + find_device_rr_gsb_num_sb_modules(openfpga_ctx.device_rr_gsb()), + 100 * (openfpga_ctx.device_rr_gsb().get_num_sb_unique_module() / find_device_rr_gsb_num_sb_modules(openfpga_ctx.device_rr_gsb()) - 1)); VTR_LOGV(verbose_output, "Detected %lu unique general switch blocks from a total of %d (compression rate=%d%)\n", - openfpga_context.device_rr_gsb().get_num_gsb_unique_module(), - find_device_rr_gsb_num_gsb_modules(openfpga_context.device_rr_gsb()), - 100 * (openfpga_context.device_rr_gsb().get_num_gsb_unique_module() / find_device_rr_gsb_num_gsb_modules(openfpga_context.device_rr_gsb()) - 1)); + openfpga_ctx.device_rr_gsb().get_num_gsb_unique_module(), + find_device_rr_gsb_num_gsb_modules(openfpga_ctx.device_rr_gsb()), + 100 * (openfpga_ctx.device_rr_gsb().get_num_gsb_unique_module() / find_device_rr_gsb_num_gsb_modules(openfpga_ctx.device_rr_gsb()) - 1)); } /******************************************************************** * Build the module graph for FPGA device *******************************************************************/ -void build_fabric(OpenfpgaContext& openfpga_context, +void build_fabric(OpenfpgaContext& openfpga_ctx, const Command& cmd, const CommandContext& cmd_context) { CommandOptionId opt_compress_routing = cmd.option("compress_routing"); @@ -65,16 +65,17 @@ void build_fabric(OpenfpgaContext& openfpga_context, CommandOptionId opt_verbose = cmd.option("verbose"); if (true == cmd_context.option_enable(cmd, opt_compress_routing)) { - compress_routing_hierarchy(openfpga_context, cmd_context.option_enable(cmd, opt_verbose)); + compress_routing_hierarchy(openfpga_ctx, cmd_context.option_enable(cmd, opt_verbose)); } VTR_LOG("\n"); - openfpga_context.mutable_module_graph() = build_device_module_graph(g_vpr_ctx.device(), - const_cast(openfpga_context), - cmd_context.option_enable(cmd, opt_compress_routing), - cmd_context.option_enable(cmd, opt_duplicate_grid_pin), - cmd_context.option_enable(cmd, opt_verbose)); + openfpga_ctx.mutable_module_graph() = build_device_module_graph(openfpga_ctx.mutable_io_location_map(), + g_vpr_ctx.device(), + const_cast(openfpga_ctx), + cmd_context.option_enable(cmd, opt_compress_routing), + cmd_context.option_enable(cmd, opt_duplicate_grid_pin), + cmd_context.option_enable(cmd, opt_verbose)); } } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_build_fabric.h b/openfpga/src/base/openfpga_build_fabric.h index 44aa3632c..824ed63a1 100644 --- a/openfpga/src/base/openfpga_build_fabric.h +++ b/openfpga/src/base/openfpga_build_fabric.h @@ -15,7 +15,7 @@ /* begin namespace openfpga */ namespace openfpga { -void build_fabric(OpenfpgaContext& openfpga_context, +void build_fabric(OpenfpgaContext& openfpga_ctx, const Command& cmd, const CommandContext& cmd_context); } /* end namespace openfpga */ diff --git a/openfpga/src/fabric/build_device_module.cpp b/openfpga/src/fabric/build_device_module.cpp index a137407ed..9680285fa 100644 --- a/openfpga/src/fabric/build_device_module.cpp +++ b/openfpga/src/fabric/build_device_module.cpp @@ -26,7 +26,8 @@ namespace openfpga { * The main function to be called for building module graphs * for a FPGA fabric *******************************************************************/ -ModuleManager build_device_module_graph(const DeviceContext& vpr_device_ctx, +ModuleManager build_device_module_graph(IoLocationMap& io_location_map, + const DeviceContext& vpr_device_ctx, const OpenfpgaContext& openfpga_ctx, const bool& compress_routing, const bool& duplicate_grid_pin, @@ -97,7 +98,8 @@ ModuleManager build_device_module_graph(const DeviceContext& vpr_device_ctx, } /* Build FPGA fabric top-level module */ - build_top_module(module_manager, openfpga_ctx.arch().circuit_lib, + build_top_module(module_manager, io_location_map, + openfpga_ctx.arch().circuit_lib, vpr_device_ctx.grid, vpr_device_ctx.rr_graph, openfpga_ctx.device_rr_gsb(), diff --git a/openfpga/src/fabric/build_device_module.h b/openfpga/src/fabric/build_device_module.h index fd585d986..b8f8c0ac8 100644 --- a/openfpga/src/fabric/build_device_module.h +++ b/openfpga/src/fabric/build_device_module.h @@ -14,7 +14,8 @@ /* begin namespace openfpga */ namespace openfpga { -ModuleManager build_device_module_graph(const DeviceContext& vpr_device_ctx, +ModuleManager build_device_module_graph(IoLocationMap& io_location_map, + const DeviceContext& vpr_device_ctx, const OpenfpgaContext& openfpga_ctx, const bool& compress_routing, const bool& duplicate_grid_pin, diff --git a/openfpga/src/fabric/build_top_module.cpp b/openfpga/src/fabric/build_top_module.cpp index 3357b2b95..7af79d46c 100644 --- a/openfpga/src/fabric/build_top_module.cpp +++ b/openfpga/src/fabric/build_top_module.cpp @@ -89,6 +89,7 @@ size_t add_top_module_grid_instance(ModuleManager& module_manager, static vtr::Matrix add_top_module_grid_instances(ModuleManager& module_manager, const ModuleId& top_module, + IoLocationMap& io_location_map, const DeviceGrid& grids) { /* Reserve an array for the instance ids */ vtr::Matrix grid_instance_ids({grids.width(), grids.height()}); @@ -142,6 +143,7 @@ vtr::Matrix add_top_module_grid_instances(ModuleManager& module_manager, } /* Add instances of I/O grids to top_module */ + size_t io_counter = 0; for (const e_side& io_side : io_sides) { for (const vtr::Point& io_coordinate : io_coordinates[io_side]) { /* Bypass EMPTY grid */ @@ -157,6 +159,21 @@ vtr::Matrix add_top_module_grid_instances(ModuleManager& module_manager, VTR_ASSERT(true == is_io_type(grids[io_coordinate.x()][io_coordinate.y()].type)); /* Add a grid module to top_module*/ grid_instance_ids[io_coordinate.x()][io_coordinate.y()] = add_top_module_grid_instance(module_manager, top_module, grids[io_coordinate.x()][io_coordinate.y()].type, io_side, io_coordinate); + + /* MUST DO: register in io location mapping! + * I/O location mapping is a critical look-up for testbench generators + * As we add the I/O grid instances to top module by following order: + * TOP -> RIGHT -> BOTTOM -> LEFT + * The I/O index will increase in this way as well. + * This organization I/O indices is also consistent to the way + * that GPIOs are wired in function connect_gpio_module() + * + * Note: if you change the GPIO function, you should update here as well! + */ + for (int z = 0; z < grids[io_coordinate.x()][io_coordinate.y()].type->capacity; ++z) { + io_location_map.set_io_index(io_coordinate.x(), io_coordinate.y(), z, io_counter); + } + io_counter++; } } @@ -280,6 +297,7 @@ vtr::Matrix add_top_module_connection_block_instances(ModuleManager& mod * 5. Add module nets/submodules to connect configuration ports *******************************************************************/ void build_top_module(ModuleManager& module_manager, + IoLocationMap& io_location_map, const CircuitLibrary& circuit_lib, const DeviceGrid& grids, const RRGraph& rr_graph, @@ -301,7 +319,7 @@ void build_top_module(ModuleManager& module_manager, /* Add sub modules, which are grid, SB and CBX/CBY modules as instances */ /* Add all the grids across the fabric */ - vtr::Matrix grid_instance_ids = add_top_module_grid_instances(module_manager, top_module, grids); + vtr::Matrix grid_instance_ids = add_top_module_grid_instances(module_manager, top_module, io_location_map, grids); /* Add all the SBs across the fabric */ vtr::Matrix sb_instance_ids = add_top_module_switch_block_instances(module_manager, top_module, device_rr_gsb, compact_routing_hierarchy); /* Add all the CBX and CBYs across the fabric */ diff --git a/openfpga/src/fabric/build_top_module.h b/openfpga/src/fabric/build_top_module.h index 3b6badfb2..4ced0735b 100644 --- a/openfpga/src/fabric/build_top_module.h +++ b/openfpga/src/fabric/build_top_module.h @@ -14,6 +14,7 @@ #include "tile_direct.h" #include "arch_direct.h" #include "module_manager.h" +#include "io_location_map.h" /******************************************************************** * Function declaration @@ -23,6 +24,7 @@ namespace openfpga { void build_top_module(ModuleManager& module_manager, + IoLocationMap& io_location_map, const CircuitLibrary& circuit_lib, const DeviceGrid& grids, const RRGraph& rr_graph, From e9adb4fdbc243d89a1fb85cbcf1768a5ce4ef2f7 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 26 Feb 2020 20:38:01 -0700 Subject: [PATCH 215/645] add preconfig top module Verilog generation --- openfpga/src/fpga_verilog/verilog_constants.h | 6 + .../verilog_preconfig_top_module.cpp | 442 ++++++++++++++++++ .../verilog_preconfig_top_module.h | 34 ++ .../src/utils/openfpga_atom_netlist_utils.cpp | 34 ++ .../src/utils/openfpga_atom_netlist_utils.h | 22 + 5 files changed, 538 insertions(+) create mode 100644 openfpga/src/fpga_verilog/verilog_preconfig_top_module.cpp create mode 100644 openfpga/src/fpga_verilog/verilog_preconfig_top_module.h create mode 100644 openfpga/src/utils/openfpga_atom_netlist_utils.cpp create mode 100644 openfpga/src/utils/openfpga_atom_netlist_utils.h diff --git a/openfpga/src/fpga_verilog/verilog_constants.h b/openfpga/src/fpga_verilog/verilog_constants.h index e355b7a5f..c8a01d375 100644 --- a/openfpga/src/fpga_verilog/verilog_constants.h +++ b/openfpga/src/fpga_verilog/verilog_constants.h @@ -48,4 +48,10 @@ constexpr char* SB_VERILOG_FILE_NAME_PREFIX = "sb_"; constexpr char* LOGICAL_MODULE_VERILOG_FILE_NAME_PREFIX = "logical_tile_"; constexpr char* GRID_VERILOG_FILE_NAME_PREFIX = "grid_"; +constexpr char* FORMAL_VERIFICATION_TOP_MODULE_POSTFIX = "_top_formal_verification"; +constexpr char* FORMAL_VERIFICATION_TOP_MODULE_PORT_POSTFIX = "_fm"; +constexpr char* FORMAL_VERIFICATION_TOP_MODULE_UUT_NAME = "U0_formal_verification"; + +#define VERILOG_DEFAULT_SIGNAL_INIT_VALUE 0 + #endif diff --git a/openfpga/src/fpga_verilog/verilog_preconfig_top_module.cpp b/openfpga/src/fpga_verilog/verilog_preconfig_top_module.cpp new file mode 100644 index 000000000..e2bceb75d --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_preconfig_top_module.cpp @@ -0,0 +1,442 @@ +/******************************************************************** + * This file includes functions that are used to generate + * a Verilog module of a pre-configured FPGA fabric + *******************************************************************/ +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" +#include "vtr_time.h" + +/* Headers from openfpgautil library */ +#include "openfpga_port.h" +#include "openfpga_digest.h" + +#include "bitstream_manager_utils.h" +#include "openfpga_atom_netlist_utils.h" + +#include "openfpga_naming.h" + +#include "verilog_constants.h" +#include "verilog_writer_utils.h" +#include "verilog_testbench_utils.h" +#include "verilog_preconfig_top_module.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Print module declaration and ports for the pre-configured + * FPGA top module + * The module ports do exactly match the input benchmark + *******************************************************************/ +static +void print_verilog_preconfig_top_module_ports(std::fstream& fp, + const std::string& circuit_name, + const AtomContext& atom_ctx) { + + /* Validate the file stream */ + valid_file_stream(fp); + + /* Module declaration */ + fp << "module " << circuit_name << std::string(FORMAL_VERIFICATION_TOP_MODULE_POSTFIX); + fp << " (" << std::endl; + + /* Add module ports */ + size_t port_counter = 0; + + /* Port type-to-type mapping */ + std::map port_type2type_map; + port_type2type_map[AtomBlockType::INPAD] = VERILOG_PORT_INPUT; + port_type2type_map[AtomBlockType::OUTPAD] = VERILOG_PORT_OUTPUT; + + /* Print all the I/Os of the circuit implementation to be tested*/ + for (const AtomBlockId& atom_blk : atom_ctx.nlist.blocks()) { + /* We only care I/O logical blocks !*/ + if ( (AtomBlockType::INPAD != atom_ctx.nlist.block_type(atom_blk)) + && (AtomBlockType::OUTPAD != atom_ctx.nlist.block_type(atom_blk)) ) { + continue; + } + if (0 < port_counter) { + fp << "," << std::endl; + } + /* Both input and output ports have only size of 1 */ + BasicPort module_port(std::string(atom_ctx.nlist.block_name(atom_blk) + std::string(FORMAL_VERIFICATION_TOP_MODULE_PORT_POSTFIX)), 1); + fp << generate_verilog_port(port_type2type_map[atom_ctx.nlist.block_type(atom_blk)], module_port); + + /* Update port counter */ + port_counter++; + } + + fp << ");" << std::endl; + + /* Add an empty line as a splitter */ + fp << std::endl; +} + +/******************************************************************** + * Print internal wires for the pre-configured FPGA top module + * The internal wires are tailored for the ports of FPGA top module + * which will be different in various configuration protocols + *******************************************************************/ +static +void print_verilog_preconfig_top_module_internal_wires(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module) { + /* Validate the file stream */ + valid_file_stream(fp); + + /* Global ports of top-level module */ + print_verilog_comment(fp, std::string("----- Local wires for FPGA fabric -----")); + for (const ModulePortId& module_port_id : module_manager.module_ports(top_module)) { + BasicPort module_port = module_manager.module_port(top_module, module_port_id); + fp << generate_verilog_port(VERILOG_PORT_WIRE, module_port) << ";" << std::endl; + } + /* Add an empty line as a splitter */ + fp << std::endl; +} + +/******************************************************************** + * Connect global ports of FPGA top module to constants except: + * 1. operating clock, which should be wired to the clock port of + * this pre-configured FPGA top module + *******************************************************************/ +static +void print_verilog_preconfig_top_module_connect_global_ports(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module, + const CircuitLibrary& circuit_lib, + const std::vector& global_ports, + const std::vector& benchmark_clock_port_names) { + /* Validate the file stream */ + valid_file_stream(fp); + + print_verilog_comment(fp, std::string("----- Begin Connect Global ports of FPGA top module -----")); + + /* Global ports of the top module in module manager do not carry any attributes, + * such as is_clock, is_set, etc. + * Therefore, for each global port in the top module, we find the circuit port in the circuit library + * which share the same name. We can access to the attributes. + * To gurantee the correct link between global ports in module manager and those in circuit library + * We have performed some critical check in check_circuit_library() for global ports, + * where we guarantee all the global ports share the same name must have the same attributes. + * So that each global port with the same name is unique! + */ + for (const BasicPort& module_global_port : module_manager.module_ports_by_type(top_module, ModuleManager::MODULE_GLOBAL_PORT)) { + CircuitPortId linked_circuit_port_id = CircuitPortId::INVALID(); + /* Find the circuit port with the same name */ + for (const CircuitPortId& circuit_port_id : global_ports) { + if (0 != module_global_port.get_name().compare(circuit_lib.port_prefix(circuit_port_id))) { + continue; + } + linked_circuit_port_id = circuit_port_id; + break; + } + /* Must find one valid circuit port */ + VTR_ASSERT(CircuitPortId::INVALID() != linked_circuit_port_id); + /* Port size should match! */ + VTR_ASSERT(module_global_port.get_width() == circuit_lib.port_size(linked_circuit_port_id)); + /* Now, for operating clock port, we should wire it to the clock of benchmark! */ + if ( (CIRCUIT_MODEL_PORT_CLOCK == circuit_lib.port_type(linked_circuit_port_id)) + && (false == circuit_lib.port_is_prog(linked_circuit_port_id)) ) { + /* Wiring to each pin of the global port: benchmark clock is always 1-bit */ + for (const size_t& pin : module_global_port.pins()) { + for (const std::string& clock_port_name : benchmark_clock_port_names) { + BasicPort module_clock_pin(module_global_port.get_name(), pin, pin); + BasicPort benchmark_clock_pin(clock_port_name + std::string(FORMAL_VERIFICATION_TOP_MODULE_PORT_POSTFIX), 1); + print_verilog_wire_connection(fp, module_clock_pin, benchmark_clock_pin, false); + } + } + /* Finish, go to the next */ + continue; + } + + /* For other ports, give an default value */ + std::vector default_values(module_global_port.get_width(), circuit_lib.port_default_value(linked_circuit_port_id)); + print_verilog_wire_constant_values(fp, module_global_port, default_values); + } + + print_verilog_comment(fp, std::string("----- End Connect Global ports of FPGA top module -----")); + + /* Add an empty line as a splitter */ + fp << std::endl; +} + +/******************************************************************** + * Impose the bitstream on the configuration memories + * This function uses 'assign' syntax to impost the bitstream at mem port + * while uses 'force' syntax to impost the bitstream at mem_inv port + *******************************************************************/ +static +void print_verilog_preconfig_top_module_assign_bitstream(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module, + const BitstreamManager& bitstream_manager) { + /* Validate the file stream */ + valid_file_stream(fp); + + print_verilog_comment(fp, std::string("----- Begin assign bitstream to configuration memories -----")); + + for (const ConfigBlockId& config_block_id : bitstream_manager.blocks()) { + /* We only cares blocks with configuration bits */ + if (0 == bitstream_manager.block_bits(config_block_id).size()) { + continue; + } + /* Build the hierarchical path of the configuration bit in modules */ + std::vector block_hierarchy = find_bitstream_manager_block_hierarchy(bitstream_manager, config_block_id); + /* Drop the first block, which is the top module, it should be replaced by the instance name here */ + /* Ensure that this is the module we want to drop! */ + VTR_ASSERT(0 == module_manager.module_name(top_module).compare(bitstream_manager.block_name(block_hierarchy[0]))); + block_hierarchy.erase(block_hierarchy.begin()); + /* Build the full hierarchy path */ + std::string bit_hierarchy_path(FORMAL_VERIFICATION_TOP_MODULE_UUT_NAME); + for (const ConfigBlockId& temp_block : block_hierarchy) { + bit_hierarchy_path += std::string("."); + bit_hierarchy_path += bitstream_manager.block_name(temp_block); + } + bit_hierarchy_path += std::string("."); + + /* Find the bit index in the parent block */ + BasicPort config_data_port(bit_hierarchy_path + generate_configuration_chain_data_out_name(), + bitstream_manager.block_bits(config_block_id).size()); + + /* Wire it to the configuration bit: access both data out and data outb ports */ + std::vector config_data_values; + for (const ConfigBitId config_bit : bitstream_manager.block_bits(config_block_id)) { + config_data_values.push_back(bitstream_manager.bit_value(config_bit)); + } + print_verilog_wire_constant_values(fp, config_data_port, config_data_values); + } + + fp << "initial begin" << std::endl; + + for (const ConfigBlockId& config_block_id : bitstream_manager.blocks()) { + /* We only cares blocks with configuration bits */ + if (0 == bitstream_manager.block_bits(config_block_id).size()) { + continue; + } + /* Build the hierarchical path of the configuration bit in modules */ + std::vector block_hierarchy = find_bitstream_manager_block_hierarchy(bitstream_manager, config_block_id); + /* Drop the first block, which is the top module, it should be replaced by the instance name here */ + /* Ensure that this is the module we want to drop! */ + VTR_ASSERT(0 == module_manager.module_name(top_module).compare(bitstream_manager.block_name(block_hierarchy[0]))); + block_hierarchy.erase(block_hierarchy.begin()); + /* Build the full hierarchy path */ + std::string bit_hierarchy_path(FORMAL_VERIFICATION_TOP_MODULE_UUT_NAME); + for (const ConfigBlockId& temp_block : block_hierarchy) { + bit_hierarchy_path += std::string("."); + bit_hierarchy_path += bitstream_manager.block_name(temp_block); + } + bit_hierarchy_path += std::string("."); + + /* Find the bit index in the parent block */ + BasicPort config_datab_port(bit_hierarchy_path + generate_configuration_chain_inverted_data_out_name(), + bitstream_manager.block_bits(config_block_id).size()); + + std::vector config_datab_values; + for (const ConfigBitId config_bit : bitstream_manager.block_bits(config_block_id)) { + config_datab_values.push_back(!bitstream_manager.bit_value(config_bit)); + } + print_verilog_force_wire_constant_values(fp, config_datab_port, config_datab_values); + } + + fp << "end" << std::endl; + + print_verilog_comment(fp, std::string("----- End assign bitstream to configuration memories -----")); +} + +/******************************************************************** + * Impose the bitstream on the configuration memories + * This function uses '$deposit' syntax to do so + *******************************************************************/ +static +void print_verilog_preconfig_top_module_deposit_bitstream(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module, + const BitstreamManager& bitstream_manager) { + /* Validate the file stream */ + valid_file_stream(fp); + + print_verilog_comment(fp, std::string("----- Begin deposit bitstream to configuration memories -----")); + + fp << "initial begin" << std::endl; + + for (const ConfigBlockId& config_block_id : bitstream_manager.blocks()) { + /* We only cares blocks with configuration bits */ + if (0 == bitstream_manager.block_bits(config_block_id).size()) { + continue; + } + /* Build the hierarchical path of the configuration bit in modules */ + std::vector block_hierarchy = find_bitstream_manager_block_hierarchy(bitstream_manager, config_block_id); + /* Drop the first block, which is the top module, it should be replaced by the instance name here */ + /* Ensure that this is the module we want to drop! */ + VTR_ASSERT(0 == module_manager.module_name(top_module).compare(bitstream_manager.block_name(block_hierarchy[0]))); + block_hierarchy.erase(block_hierarchy.begin()); + /* Build the full hierarchy path */ + std::string bit_hierarchy_path(FORMAL_VERIFICATION_TOP_MODULE_UUT_NAME); + for (const ConfigBlockId& temp_block : block_hierarchy) { + bit_hierarchy_path += std::string("."); + bit_hierarchy_path += bitstream_manager.block_name(temp_block); + } + bit_hierarchy_path += std::string("."); + + /* Find the bit index in the parent block */ + BasicPort config_data_port(bit_hierarchy_path + generate_configuration_chain_data_out_name(), + bitstream_manager.block_bits(config_block_id).size()); + + BasicPort config_datab_port(bit_hierarchy_path + generate_configuration_chain_inverted_data_out_name(), + bitstream_manager.block_bits(config_block_id).size()); + + /* Wire it to the configuration bit: access both data out and data outb ports */ + std::vector config_data_values; + for (const ConfigBitId config_bit : bitstream_manager.block_bits(config_block_id)) { + config_data_values.push_back(bitstream_manager.bit_value(config_bit)); + } + print_verilog_deposit_wire_constant_values(fp, config_data_port, config_data_values); + + std::vector config_datab_values; + for (const ConfigBitId config_bit : bitstream_manager.block_bits(config_block_id)) { + config_datab_values.push_back(!bitstream_manager.bit_value(config_bit)); + } + print_verilog_deposit_wire_constant_values(fp, config_datab_port, config_datab_values); + } + + fp << "end" << std::endl; + + print_verilog_comment(fp, std::string("----- End deposit bitstream to configuration memories -----")); +} + +/******************************************************************** + * Impose the bitstream on the configuration memories + * We branch here for different simulators: + * 1. iVerilog Icarus prefers using 'assign' syntax to force the values + * 2. Mentor Modelsim prefers using '$deposit' syntax to do so + *******************************************************************/ +static +void print_verilog_preconfig_top_module_load_bitstream(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module, + const BitstreamManager& bitstream_manager) { + print_verilog_comment(fp, std::string("----- Begin load bitstream to configuration memories -----")); + + print_verilog_preprocessing_flag(fp, std::string(ICARUS_SIMULATOR_FLAG)); + + /* Use assign syntax for Icarus simulator */ + print_verilog_preconfig_top_module_assign_bitstream(fp, module_manager, top_module, bitstream_manager); + + fp << "`else" << std::endl; + + /* Use assign syntax for Icarus simulator */ + print_verilog_preconfig_top_module_deposit_bitstream(fp, module_manager, top_module, bitstream_manager); + + print_verilog_endif(fp); + + print_verilog_comment(fp, std::string("----- End load bitstream to configuration memories -----")); +} + + +/******************************************************************** + * Top-level function to generate a Verilog module of + * a pre-configured FPGA fabric. + * + * Pre-configured FPGA fabric + * +-------------------------------------------- + * | + * | FPGA fabric + * | +-------------------------------+ + * | | | + * | 0/1---->|FPGA global ports | + * | | | + * benchmark_clock----->|--------->|FPGA_clock | + * | | | + * benchmark_inputs---->|--------->|FPGA mapped I/Os | + * | | | + * benchmark_outputs<---|<---------|FPGA mapped I/Os | + * | | | + * | 0/1---->|FPGA unmapped I/Os | + * | | | + * fabric_bitstream---->|--------->|Internal_configuration_ports | + * | +-------------------------------+ + * | + * +------------------------------------------- + * + * Note: we do NOT put this module in the module manager. + * Because, it is not a standard module, where we force configuration signals + * This module is a wrapper for the FPGA fabric to be compatible in + * the port map of input benchmark. + * It includes wires to force constant values to part of FPGA datapath I/Os + * All these are hard to implement as a module in module manager + *******************************************************************/ +void print_verilog_preconfig_top_module(const ModuleManager& module_manager, + const BitstreamManager& bitstream_manager, + const CircuitLibrary& circuit_lib, + const std::vector& global_ports, + const AtomContext& atom_ctx, + const PlacementContext& place_ctx, + const IoLocationMap& io_location_map, + const std::string& circuit_name, + const std::string& verilog_fname, + const std::string& verilog_dir) { + std::string timer_message = std::string("Write pre-configured FPGA top-level Verilog netlist for design '") + circuit_name + std::string("'"); + + /* Start time count */ + vtr::ScopedStartFinishTimer timer(timer_message); + + /* Create the file stream */ + std::fstream fp; + fp.open(verilog_fname, std::fstream::out | std::fstream::trunc); + + /* Validate the file stream */ + check_file_stream(verilog_fname.c_str(), fp); + + /* Generate a brief description on the Verilog file*/ + std::string title = std::string("Verilog netlist for pre-configured FPGA fabric by design: ") + circuit_name; + print_verilog_file_header(fp, title); + + /* Print preprocessing flags and external netlists */ + print_verilog_include_defines_preproc_file(fp, verilog_dir); + + print_verilog_include_netlist(fp, std::string(verilog_dir + std::string(DEFINES_VERILOG_SIMULATION_FILE_NAME))); + + /* Print module declaration and ports */ + print_verilog_preconfig_top_module_ports(fp, circuit_name, atom_ctx); + + /* Find the top_module */ + ModuleId top_module = module_manager.find_module(generate_fpga_top_module_name()); + VTR_ASSERT(true == module_manager.valid_module_id(top_module)); + + /* Print internal wires */ + print_verilog_preconfig_top_module_internal_wires(fp, module_manager, top_module); + + /* Instanciate FPGA top-level module */ + print_verilog_testbench_fpga_instance(fp, module_manager, top_module, + std::string(FORMAL_VERIFICATION_TOP_MODULE_UUT_NAME)); + + /* Find clock ports in benchmark */ + std::vector benchmark_clock_port_names = find_atom_netlist_clock_port_names(atom_ctx.nlist); + + /* Connect FPGA top module global ports to constant or benchmark global signals! */ + print_verilog_preconfig_top_module_connect_global_ports(fp, module_manager, top_module, + circuit_lib, global_ports, + benchmark_clock_port_names); + + /* Connect I/Os to benchmark I/Os or constant driver */ + print_verilog_testbench_connect_fpga_ios(fp, module_manager, top_module, + atom_ctx, place_ctx, io_location_map, + std::string(FORMAL_VERIFICATION_TOP_MODULE_PORT_POSTFIX), + std::string(FORMAL_VERIFICATION_TOP_MODULE_PORT_POSTFIX), + (size_t)VERILOG_DEFAULT_SIGNAL_INIT_VALUE); + + /* Assign FPGA internal SRAM/Memory ports to bitstream values */ + print_verilog_preconfig_top_module_load_bitstream(fp, module_manager, top_module, + bitstream_manager); + + /* Testbench ends*/ + print_verilog_module_end(fp, std::string(circuit_name) + std::string(FORMAL_VERIFICATION_TOP_MODULE_POSTFIX)); + + /* Close the file stream */ + fp.close(); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_preconfig_top_module.h b/openfpga/src/fpga_verilog/verilog_preconfig_top_module.h new file mode 100644 index 000000000..0c8e54708 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_preconfig_top_module.h @@ -0,0 +1,34 @@ +#ifndef VERILOG_PRECONFIG_TOP_MODULE_H +#define VERILOG_PRECONFIG_TOP_MODULE_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "circuit_types.h" +#include "vpr_types.h" +#include "module_manager.h" +#include "bitstream_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_verilog_preconfig_top_module(const ModuleManager& module_manager, + const BitstreamManager& bitstream_manager, + const CircuitLibrary& circuit_lib, + const std::vector& global_ports, + const AtomContext& atom_ctx, + const PlacementContext& place_ctx, + const IoLocationMap& io_location_map, + const std::string& circuit_name, + const std::string& verilog_fname, + const std::string& verilog_dir); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/utils/openfpga_atom_netlist_utils.cpp b/openfpga/src/utils/openfpga_atom_netlist_utils.cpp new file mode 100644 index 000000000..d2786e88d --- /dev/null +++ b/openfpga/src/utils/openfpga_atom_netlist_utils.cpp @@ -0,0 +1,34 @@ +/*************************************************************************************** + * This file includes most utilized functions that are used to acquire data from + * VPR atom netlist (users' netlist to implement) + ***************************************************************************************/ + +/* Headers from vtrutil library */ +#include "vtr_log.h" +#include "vtr_assert.h" +#include "vtr_time.h" + +/* Headers from vtrutil library */ +#include "atom_netlist_utils.h" + +#include "openfpga_atom_netlist_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/*************************************************************************************** + * Find the names of all the atom blocks that drive clock nets + ***************************************************************************************/ +std::vector find_atom_netlist_clock_port_names(const AtomNetlist& atom_nlist) { + std::vector clock_names; + + std::set clock_pins = find_netlist_logical_clock_drivers(atom_nlist); + for (const AtomPinId& clock_pin : clock_pins) { + const AtomBlockId& atom_blk = atom_nlist.port_block(atom_nlist.pin_port(clock_pin)); + clock_names.push_back(atom_nlist.block_name(atom_blk)); + } + + return clock_names; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/utils/openfpga_atom_netlist_utils.h b/openfpga/src/utils/openfpga_atom_netlist_utils.h new file mode 100644 index 000000000..180181687 --- /dev/null +++ b/openfpga/src/utils/openfpga_atom_netlist_utils.h @@ -0,0 +1,22 @@ +#ifndef OPENFPGA_ATOM_NETLIST_UTILS_H +#define OPENFPGA_ATOM_NETLIST_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "atom_netlist.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +std::vector find_atom_netlist_clock_port_names(const AtomNetlist& atom_nlist); + +} /* end namespace openfpga */ + +#endif From bb671acac3bf1d8aaad193a2aa9c50a1e1845d24 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 26 Feb 2020 20:58:16 -0700 Subject: [PATCH 216/645] add formal random Verilog testbench generation --- .../verilog_formal_random_top_testbench.cpp | 259 ++++++++++++++++++ .../verilog_formal_random_top_testbench.h | 26 ++ openfpga/src/utils/simulation_utils.cpp | 47 ++++ openfpga/src/utils/simulation_utils.h | 28 ++ 4 files changed, 360 insertions(+) create mode 100644 openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.cpp create mode 100644 openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.h create mode 100644 openfpga/src/utils/simulation_utils.cpp create mode 100644 openfpga/src/utils/simulation_utils.h diff --git a/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.cpp b/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.cpp new file mode 100644 index 000000000..71f7df873 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.cpp @@ -0,0 +1,259 @@ +/******************************************************************** + * This file includes functions that are used to generate a Verilog + * testbench for the top-level module (FPGA fabric), in purpose of + * running formal verification with random input vectors + *******************************************************************/ +#include +#include +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" +#include "vtr_time.h" + +/* Headers from openfpgautil library */ +#include "openfpga_port.h" +#include "openfpga_digest.h" + +#include "openfpga_atom_netlist_utils.h" +#include "simulation_utils.h" + +#include "verilog_constants.h" +#include "verilog_writer_utils.h" +#include "verilog_testbench_utils.h" +#include "verilog_formal_random_top_testbench.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Local variables used only in this file + *******************************************************************/ +constexpr char* FORMAL_RANDOM_TOP_TESTBENCH_POSTFIX = "_top_formal_verification_random_tb"; +constexpr char* FPGA_PORT_POSTFIX = "_gfpga"; +constexpr char* BENCHMARK_PORT_POSTFIX = "_bench"; +constexpr char* CHECKFLAG_PORT_POSTFIX = "_flag"; +constexpr char* DEFAULT_CLOCK_NAME = "clk"; +constexpr char* BENCHMARK_INSTANCE_NAME = "REF_DUT"; +constexpr char* FPGA_INSTANCE_NAME = "FPGA_DUT"; +constexpr char* ERROR_COUNTER = "nb_error"; +constexpr char* FORMAL_TB_SIM_START_PORT_NAME = "sim_start"; +constexpr int MAGIC_NUMBER_FOR_SIMULATION_TIME = 200; + +/******************************************************************** + * Print the module ports for the Verilog testbench + * using random vectors + * This function generates + * 1. the input ports to drive both input benchmark module and FPGA fabric module + * 2. the output ports for input benchmark module + * 3. the output ports for FPGA fabric module + * 4. the error checking ports + *******************************************************************/ +static +void print_verilog_top_random_testbench_ports(std::fstream& fp, + const std::string& circuit_name, + const std::vector& clock_port_names, + const AtomContext& atom_ctx) { + /* Validate the file stream */ + valid_file_stream(fp); + + /* Print the declaration for the module */ + fp << "module " << circuit_name << FORMAL_RANDOM_TOP_TESTBENCH_POSTFIX << ";" << std::endl; + + /* Create a clock port if the benchmark does not have one! + * The clock is used for counting and synchronizing input stimulus + */ + BasicPort clock_port = generate_verilog_testbench_clock_port(clock_port_names, std::string(DEFAULT_CLOCK_NAME)); + print_verilog_comment(fp, std::string("----- Default clock port is added here since benchmark does not contain one -------")); + fp << "\t" << generate_verilog_port(VERILOG_PORT_REG, clock_port) << ";" << std::endl; + + /* Add an empty line as splitter */ + fp << std::endl; + + print_verilog_testbench_shared_ports(fp, atom_ctx, + std::string(BENCHMARK_PORT_POSTFIX), + std::string(FPGA_PORT_POSTFIX), + std::string(CHECKFLAG_PORT_POSTFIX), + std::string(AUTOCHECKED_SIMULATION_FLAG)); + + /* Instantiate an integer to count the number of error + * and determine if the simulation succeed or failed + */ + print_verilog_comment(fp, std::string("----- Error counter -------")); + fp << "\tinteger " << ERROR_COUNTER << "= 0;" << std::endl; + + /* Add an empty line as splitter */ + fp << std::endl; +} + +/******************************************************************** + * Instanciate the input benchmark module + *******************************************************************/ +static +void print_verilog_top_random_testbench_benchmark_instance(std::fstream& fp, + const std::string& reference_verilog_top_name, + const AtomContext& atom_ctx) { + /* Validate the file stream */ + valid_file_stream(fp); + + /* Benchmark is instanciated conditionally: only when a preprocessing flag is enable */ + print_verilog_preprocessing_flag(fp, std::string(AUTOCHECKED_SIMULATION_FLAG)); + + print_verilog_comment(fp, std::string("----- Reference Benchmark Instanication -------")); + + /* Do NOT use explicit port mapping here: + * VPR added a prefix of "out_" to the output ports of input benchmark + */ + print_verilog_testbench_benchmark_instance(fp, reference_verilog_top_name, + std::string(BENCHMARK_INSTANCE_NAME), + std::string(), + std::string(), + std::string(BENCHMARK_PORT_POSTFIX), + atom_ctx, + false); + + print_verilog_comment(fp, std::string("----- End reference Benchmark Instanication -------")); + + /* Add an empty line as splitter */ + fp << std::endl; + + /* Condition ends for the benchmark instanciation */ + print_verilog_endif(fp); + + /* Add an empty line as splitter */ + fp << std::endl; +} + +/******************************************************************** + * Instanciate the FPGA fabric module + *******************************************************************/ +static +void print_verilog_random_testbench_fpga_instance(std::fstream& fp, + const std::string& circuit_name, + const AtomContext& atom_ctx) { + /* Validate the file stream */ + valid_file_stream(fp); + + print_verilog_comment(fp, std::string("----- FPGA fabric instanciation -------")); + + /* Always use explicit port mapping */ + print_verilog_testbench_benchmark_instance(fp, std::string(circuit_name + std::string(FORMAL_VERIFICATION_TOP_MODULE_POSTFIX)), + std::string(FPGA_INSTANCE_NAME), + std::string(FORMAL_VERIFICATION_TOP_MODULE_PORT_POSTFIX), + std::string(FORMAL_VERIFICATION_TOP_MODULE_PORT_POSTFIX), + std::string(FPGA_PORT_POSTFIX), + atom_ctx, + true); + + print_verilog_comment(fp, std::string("----- End FPGA Fabric Instanication -------")); + + /* Add an empty line as splitter */ + fp << std::endl; +} + +/********************************************************************* + * Top-level function in this file: + * Create a Verilog testbench using random input vectors + * The testbench consists of two modules, i.e., Design Under Test (DUT) + * 1. top-level module of FPGA fabric + * 2. top-level module of users' benchmark, + * i.e., the input benchmark of VPR flow + * +----------+ + * | FPGA | +------------+ + * +----->| Fabric |------>| | + * | | | | | + * | +----------+ | | + * | | Output | + * random_input_vectors -----+ | Vector |---->Functional correct? + * | | Comparator | + * | +-----------+ | | + * | | Input | | | + * +----->| Benchmark |----->| | + * +-----------+ +------------+ + * + * Same input vectors are given to drive both DUTs. + * The output vectors of the DUTs are compared to verify if they + * have the same functionality. + * A flag will be raised to indicate the result + ********************************************************************/ +void print_verilog_random_top_testbench(const std::string& circuit_name, + const std::string& verilog_fname, + const std::string& verilog_dir, + const AtomContext& atom_ctx, + const SimulationSetting& simulation_parameters) { + std::string timer_message = std::string("Write configuration-skip testbench for FPGA top-level Verilog netlist implemented by '") + circuit_name.c_str() + std::string("'"); + + /* Start time count */ + vtr::ScopedStartFinishTimer timer(timer_message); + + /* Create the file stream */ + std::fstream fp; + fp.open(verilog_fname, std::fstream::out | std::fstream::trunc); + + /* Validate the file stream */ + check_file_stream(verilog_fname.c_str(), fp); + + /* Generate a brief description on the Verilog file*/ + std::string title = std::string("FPGA Verilog Testbench for Formal Top-level netlist of Design: ") + circuit_name; + print_verilog_file_header(fp, title); + + /* Print preprocessing flags and external netlists */ + print_verilog_include_defines_preproc_file(fp, verilog_dir); + + print_verilog_include_netlist(fp, std::string(verilog_dir + std::string(DEFINES_VERILOG_SIMULATION_FILE_NAME))); + + /* Preparation: find all the clock ports */ + std::vector clock_port_names = find_atom_netlist_clock_port_names(atom_ctx.nlist); + + /* Start of testbench */ + print_verilog_top_random_testbench_ports(fp, circuit_name, clock_port_names, atom_ctx); + + /* Call defined top-level module */ + print_verilog_random_testbench_fpga_instance(fp, circuit_name, atom_ctx); + + /* Call defined benchmark */ + print_verilog_top_random_testbench_benchmark_instance(fp, circuit_name, atom_ctx); + + /* Find clock port to be used */ + BasicPort clock_port = generate_verilog_testbench_clock_port(clock_port_names, std::string(DEFAULT_CLOCK_NAME)); + + /* Add stimuli for reset, set, clock and iopad signals */ + print_verilog_testbench_clock_stimuli(fp, simulation_parameters, + clock_port); + print_verilog_testbench_random_stimuli(fp, atom_ctx, + std::string(CHECKFLAG_PORT_POSTFIX), clock_port); + + print_verilog_testbench_check(fp, + std::string(AUTOCHECKED_SIMULATION_FLAG), + std::string(FORMAL_TB_SIM_START_PORT_NAME), + std::string(BENCHMARK_PORT_POSTFIX), + std::string(FPGA_PORT_POSTFIX), + std::string(CHECKFLAG_PORT_POSTFIX), + std::string(ERROR_COUNTER), + atom_ctx, + clock_port_names, std::string(DEFAULT_CLOCK_NAME)); + + int simulation_time = find_operating_phase_simulation_time(MAGIC_NUMBER_FOR_SIMULATION_TIME, + simulation_parameters.num_clock_cycles(), + 1./simulation_parameters.operating_clock_frequency(), + VERILOG_SIM_TIMESCALE); + + /* Add Icarus requirement */ + print_verilog_timeout_and_vcd(fp, + std::string(ICARUS_SIMULATOR_FLAG), + std::string(circuit_name + std::string(FORMAL_RANDOM_TOP_TESTBENCH_POSTFIX)), + std::string(circuit_name + std::string("_formal.vcd")), + std::string(FORMAL_TB_SIM_START_PORT_NAME), + std::string(ERROR_COUNTER), + simulation_time); + + /* Testbench ends*/ + print_verilog_module_end(fp, std::string(circuit_name) + std::string(FORMAL_RANDOM_TOP_TESTBENCH_POSTFIX)); + + /* Close the file stream */ + fp.close(); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.h b/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.h new file mode 100644 index 000000000..f73b49fc1 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.h @@ -0,0 +1,26 @@ +#ifndef VERILOG_FORMAL_RANDOM_TOP_TESTBENCH +#define VERILOG_FORMAL_RANDOM_TOP_TESTBENCH + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include "vpr_context.h" +#include "simulation_setting.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_verilog_random_top_testbench(const std::string& circuit_name, + const std::string& verilog_fname, + const std::string& verilog_dir, + const AtomContext& atom_ctx, + const SimulationSetting& simulation_parameters); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/utils/simulation_utils.cpp b/openfpga/src/utils/simulation_utils.cpp new file mode 100644 index 000000000..e7d8d33bf --- /dev/null +++ b/openfpga/src/utils/simulation_utils.cpp @@ -0,0 +1,47 @@ +/******************************************************************** + * This file include most utilized functions in generating simulations + * Note: function placed here MUST be generic enough for both SPICE + * and Verilog simulations! + *******************************************************************/ +#include + +#include "simulation_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + + +/******************************************************************** + * Compute the time period for the simulation + *******************************************************************/ +int find_operating_phase_simulation_time(const int& factor, + const int& num_op_clock_cycles, + const float& op_clock_period, + const float& timescale) { + /* Take into account the prog_reset and reset cycles + * 1e9 is to change the unit to ns rather than second + */ + return (factor * num_op_clock_cycles * op_clock_period) / timescale; +} + +/******************************************************************** + * Find the the full time period of a simulation, including + * both the programming time and operating time + * This is a generic function that can be used to generate simulation + * time period for SPICE/Verilog simulators + *******************************************************************/ +float find_simulation_time_period(const float &time_unit, + const int &num_prog_clock_cycles, + const float &prog_clock_period, + const int &num_op_clock_cycles, + const float &op_clock_period) { + float total_time_period = 0.; + + /* Take into account the prog_reset and reset cycles */ + total_time_period = (num_prog_clock_cycles + 2) * prog_clock_period + num_op_clock_cycles * op_clock_period; + total_time_period = total_time_period / time_unit; + + return total_time_period; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/utils/simulation_utils.h b/openfpga/src/utils/simulation_utils.h new file mode 100644 index 000000000..d99a6226f --- /dev/null +++ b/openfpga/src/utils/simulation_utils.h @@ -0,0 +1,28 @@ +#ifndef SIMULATION_UTILS_H +#define SIMULATION_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +int find_operating_phase_simulation_time(const int& factor, + const int& num_op_clock_cycles, + const float& op_clock_period, + const float& timescale); + +float find_simulation_time_period(const float& time_unit, + const int& num_prog_clock_cycles, + const float& prog_clock_period, + const int& num_op_clock_cycles, + const float& op_clock_period); + +} /* end namespace openfpga */ + +#endif From 77529f49574f4d4b085ed02c9599ed44fcf2a2c9 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 26 Feb 2020 21:30:21 -0700 Subject: [PATCH 217/645] adapt top Verilog testbench generation --- .../fpga_verilog/verilog_top_testbench.cpp | 889 ++++++++++++++++++ .../src/fpga_verilog/verilog_top_testbench.h | 39 + 2 files changed, 928 insertions(+) create mode 100644 openfpga/src/fpga_verilog/verilog_top_testbench.cpp create mode 100644 openfpga/src/fpga_verilog/verilog_top_testbench.h diff --git a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp new file mode 100644 index 000000000..6159d1751 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp @@ -0,0 +1,889 @@ +/******************************************************************** + * This file includes functions that are used to create + * an auto-check top-level testbench for a FPGA fabric + *******************************************************************/ +#include +#include +#include + + +/* Headers from vtrutil library */ +#include "vtr_log.h" +#include "vtr_assert.h" +#include "vtr_time.h" + +/* Headers from openfpgautil library */ +#include "openfpga_port.h" +#include "openfpga_digest.h" + +#include "bitstream_manager_utils.h" + +#include "openfpga_naming.h" +#include "simulation_utils.h" +#include "openfpga_atom_netlist_utils.h" + +#include "verilog_constants.h" +#include "verilog_writer_utils.h" +#include "verilog_testbench_utils.h" +#include "verilog_top_testbench.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Local variables used only in this file + *******************************************************************/ +constexpr char* TOP_TESTBENCH_REFERENCE_INSTANCE_NAME = "REF_DUT"; +constexpr char* TOP_TESTBENCH_FPGA_INSTANCE_NAME = "FPGA_DUT"; +constexpr char* TOP_TESTBENCH_REFERENCE_OUTPUT_POSTFIX = "_benchmark"; +constexpr char* TOP_TESTBENCH_FPGA_OUTPUT_POSTFIX = "_fpga"; + +constexpr char* TOP_TESTBENCH_CHECKFLAG_PORT_POSTFIX = "_flag"; + +constexpr char* TOP_TESTBENCH_CC_PROG_TASK_NAME = "prog_cycle_task"; + +constexpr char* TOP_TESTBENCH_SIM_START_PORT_NAME = "sim_start"; + +constexpr int TOP_TESTBENCH_MAGIC_NUMBER_FOR_SIMULATION_TIME = 200; +constexpr char* TOP_TESTBENCH_ERROR_COUNTER = "nb_error"; + +constexpr char* TOP_TB_RESET_PORT_NAME = "greset"; +constexpr char* TOP_TB_SET_PORT_NAME = "gset"; +constexpr char* TOP_TB_PROG_RESET_PORT_NAME = "prog_reset"; +constexpr char* TOP_TB_PROG_SET_PORT_NAME = "prog_set"; +constexpr char* TOP_TB_CONFIG_DONE_PORT_NAME = "config_done"; +constexpr char* TOP_TB_OP_CLOCK_PORT_NAME = "op_clock"; +constexpr char* TOP_TB_PROG_CLOCK_PORT_NAME = "prog_clock"; +constexpr char* TOP_TB_INOUT_REG_POSTFIX = "_reg"; +constexpr char* TOP_TB_CLOCK_REG_POSTFIX = "_reg"; + +constexpr char* AUTOCHECK_TOP_TESTBENCH_VERILOG_MODULE_POSTFIX = "_autocheck_top_tb"; + +/******************************************************************** + * Print local wires for configuration chain protocols + *******************************************************************/ +static +void print_verilog_top_testbench_config_chain_port(std::fstream& fp) { + /* Validate the file stream */ + valid_file_stream(fp); + + /* Print the head of configuraion-chains here */ + print_verilog_comment(fp, std::string("---- Configuration-chain head -----")); + BasicPort config_chain_head_port(generate_configuration_chain_head_name(), 1); + fp << generate_verilog_port(VERILOG_PORT_REG, config_chain_head_port) << ";" << std::endl; + + /* Print the tail of configuration-chains here */ + print_verilog_comment(fp, std::string("---- Configuration-chain tail -----")); + BasicPort config_chain_tail_port(generate_configuration_chain_tail_name(), 1); + fp << generate_verilog_port(VERILOG_PORT_WIRE, config_chain_tail_port) << ";" << std::endl; +} + +/******************************************************************** + * Print local wires for different types of configuration protocols + *******************************************************************/ +static +void print_verilog_top_testbench_config_protocol_port(std::fstream& fp, + const e_config_protocol_type& sram_orgz_type) { + switch(sram_orgz_type) { + case CONFIG_MEM_STANDALONE: + /* TODO */ + break; + case CONFIG_MEM_SCAN_CHAIN: + print_verilog_top_testbench_config_chain_port(fp); + break; + case CONFIG_MEM_MEMORY_BANK: + /* TODO */ + break; + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid type of SRAM organization!\n"); + exit(1); + } +} + +/******************************************************************** + * Wire the global ports of FPGA fabric to local wires + *******************************************************************/ +static +void print_verilog_top_testbench_global_ports_stimuli(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module, + const CircuitLibrary& circuit_lib, + const std::vector& global_ports) { + /* Validate the file stream */ + valid_file_stream(fp); + + print_verilog_comment(fp, std::string("----- Begin connecting global ports of FPGA fabric to stimuli -----")); + + /* Connect global clock ports to operating or programming clock signal */ + for (const CircuitPortId& model_global_port : global_ports) { + if (CIRCUIT_MODEL_PORT_CLOCK != circuit_lib.port_type(model_global_port)) { + continue; + } + /* Reach here, it means we have a global clock to deal with: + * 1. if the port is identified as a programming clock, + * connect it to the local wire of programming clock + * 2. if the port is identified as an operating clock + * connect it to the local wire of operating clock + */ + /* Find the module port */ + ModulePortId module_global_port = module_manager.find_module_port(top_module, circuit_lib.port_prefix(model_global_port)); + VTR_ASSERT(true == module_manager.valid_module_port_id(top_module, module_global_port)); + + BasicPort stimuli_clock_port; + if (true == circuit_lib.port_is_prog(model_global_port)) { + stimuli_clock_port.set_name(std::string(TOP_TB_PROG_CLOCK_PORT_NAME)); + stimuli_clock_port.set_width(1); + } else { + VTR_ASSERT_SAFE(false == circuit_lib.port_is_prog(model_global_port)); + stimuli_clock_port.set_name(std::string(TOP_TB_OP_CLOCK_PORT_NAME)); + stimuli_clock_port.set_width(1); + } + /* Wire the port to the input stimuli: + * The wiring will be inverted if the default value of the global port is 1 + * Otherwise, the wiring will not be inverted! + */ + print_verilog_wire_connection(fp, module_manager.module_port(top_module, module_global_port), + stimuli_clock_port, + 1 == circuit_lib.port_default_value(model_global_port)); + } + + /* Connect global configuration done ports to configuration done signal */ + for (const CircuitPortId& model_global_port : global_ports) { + /* Bypass clock signals, they have been processed */ + if (CIRCUIT_MODEL_PORT_CLOCK == circuit_lib.port_type(model_global_port)) { + continue; + } + if (false == circuit_lib.port_is_config_enable(model_global_port)) { + continue; + } + /* Reach here, it means we have a configuration done port to deal with */ + /* Find the module port */ + ModulePortId module_global_port = module_manager.find_module_port(top_module, circuit_lib.port_prefix(model_global_port)); + VTR_ASSERT(true == module_manager.valid_module_port_id(top_module, module_global_port)); + + BasicPort stimuli_config_done_port(std::string(TOP_TB_CONFIG_DONE_PORT_NAME), 1); + /* Wire the port to the input stimuli: + * The wiring will be inverted if the default value of the global port is 1 + * Otherwise, the wiring will not be inverted! + */ + print_verilog_wire_connection(fp, module_manager.module_port(top_module, module_global_port), + stimuli_config_done_port, + 1 == circuit_lib.port_default_value(model_global_port)); + } + + /* Connect global reset ports to operating or programming reset signal */ + for (const CircuitPortId& model_global_port : global_ports) { + /* Bypass clock signals, they have been processed */ + if (CIRCUIT_MODEL_PORT_CLOCK == circuit_lib.port_type(model_global_port)) { + continue; + } + /* Bypass config_done signals, they have been processed */ + if (true == circuit_lib.port_is_config_enable(model_global_port)) { + continue; + } + + if (false == circuit_lib.port_is_reset(model_global_port)) { + continue; + } + /* Reach here, it means we have a reset port to deal with */ + /* Find the module port */ + ModulePortId module_global_port = module_manager.find_module_port(top_module, circuit_lib.port_prefix(model_global_port)); + VTR_ASSERT(true == module_manager.valid_module_port_id(top_module, module_global_port)); + + BasicPort stimuli_reset_port; + if (true == circuit_lib.port_is_prog(model_global_port)) { + stimuli_reset_port.set_name(std::string(TOP_TB_PROG_RESET_PORT_NAME)); + stimuli_reset_port.set_width(1); + } else { + VTR_ASSERT_SAFE(false == circuit_lib.port_is_prog(model_global_port)); + stimuli_reset_port.set_name(std::string(TOP_TB_RESET_PORT_NAME)); + stimuli_reset_port.set_width(1); + } + /* Wire the port to the input stimuli: + * The wiring will be inverted if the default value of the global port is 1 + * Otherwise, the wiring will not be inverted! + */ + print_verilog_wire_connection(fp, module_manager.module_port(top_module, module_global_port), + stimuli_reset_port, + 1 == circuit_lib.port_default_value(model_global_port)); + } + + /* Connect global set ports to operating or programming set signal */ + for (const CircuitPortId& model_global_port : global_ports) { + /* Bypass clock signals, they have been processed */ + if (CIRCUIT_MODEL_PORT_CLOCK == circuit_lib.port_type(model_global_port)) { + continue; + } + /* Bypass config_done signals, they have been processed */ + if (true == circuit_lib.port_is_config_enable(model_global_port)) { + continue; + } + + /* Bypass reset signals, they have been processed */ + if (true == circuit_lib.port_is_reset(model_global_port)) { + continue; + } + + if (false == circuit_lib.port_is_set(model_global_port)) { + continue; + } + /* Reach here, it means we have a set port to deal with */ + /* Find the module port */ + ModulePortId module_global_port = module_manager.find_module_port(top_module, circuit_lib.port_prefix(model_global_port)); + VTR_ASSERT(true == module_manager.valid_module_port_id(top_module, module_global_port)); + + BasicPort stimuli_set_port; + if (true == circuit_lib.port_is_prog(model_global_port)) { + stimuli_set_port.set_name(std::string(TOP_TB_PROG_SET_PORT_NAME)); + stimuli_set_port.set_width(1); + } else { + VTR_ASSERT_SAFE(false == circuit_lib.port_is_prog(model_global_port)); + stimuli_set_port.set_name(std::string(TOP_TB_SET_PORT_NAME)); + stimuli_set_port.set_width(1); + } + /* Wire the port to the input stimuli: + * The wiring will be inverted if the default value of the global port is 1 + * Otherwise, the wiring will not be inverted! + */ + print_verilog_wire_connection(fp, module_manager.module_port(top_module, module_global_port), + stimuli_set_port, + 1 == circuit_lib.port_default_value(model_global_port)); + } + + /* For the rest of global ports, wire them to constant signals */ + for (const CircuitPortId& model_global_port : global_ports) { + /* Bypass clock signals, they have been processed */ + if (CIRCUIT_MODEL_PORT_CLOCK == circuit_lib.port_type(model_global_port)) { + continue; + } + /* Bypass config_done signals, they have been processed */ + if (true == circuit_lib.port_is_config_enable(model_global_port)) { + continue; + } + + /* Bypass reset signals, they have been processed */ + if (true == circuit_lib.port_is_reset(model_global_port)) { + continue; + } + + /* Bypass set signals, they have been processed */ + if (true == circuit_lib.port_is_set(model_global_port)) { + continue; + } + + /* Reach here, it means we have a port to deal with */ + /* Find the module port and wire it to constant values */ + ModulePortId module_global_port = module_manager.find_module_port(top_module, circuit_lib.port_prefix(model_global_port)); + VTR_ASSERT(true == module_manager.valid_module_port_id(top_module, module_global_port)); + + BasicPort module_port = module_manager.module_port(top_module, module_global_port); + std::vector default_values(module_port.get_width(), circuit_lib.port_default_value(model_global_port)); + print_verilog_wire_constant_values(fp, module_port, default_values); + } + + print_verilog_comment(fp, std::string("----- End connecting global ports of FPGA fabric to stimuli -----")); +} + +/******************************************************************** + * This function prints the top testbench module declaration + * and internal wires/port declaration + * Ports can be classified in two categories: + * 1. General-purpose ports, which are datapath I/Os, clock signals + * for the FPGA fabric and input benchmark + * 2. Fabric-featured ports, which are required by configuration + * protocols. + * Due the difference in configuration protocols, the internal + * wires and ports will be different: + * (a) configuration-chain: we will have two ports, + * a head and a tail for the configuration chain, + * in addition to the regular ports. + * (b) memory-decoders: we will have a few ports to drive + * address lines for decoders and a bit input port to feed + * configuration bits + *******************************************************************/ +static +void print_verilog_top_testbench_ports(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module, + const AtomContext& atom_ctx, + const std::vector& clock_port_names, + const e_config_protocol_type& sram_orgz_type, + const std::string& circuit_name){ + /* Validate the file stream */ + valid_file_stream(fp); + + /* Print module definition */ + fp << "module " << circuit_name << std::string(AUTOCHECK_TOP_TESTBENCH_VERILOG_MODULE_POSTFIX); + fp << ";" << std::endl; + + /* Print regular local wires: + * 1. global ports, i.e., reset, set and clock signals + * 2. datapath I/O signals + */ + /* Global ports of top-level module */ + print_verilog_comment(fp, std::string("----- Local wires for global ports of FPGA fabric -----")); + for (const BasicPort& module_port : module_manager.module_ports_by_type(top_module, ModuleManager::MODULE_GLOBAL_PORT)) { + fp << generate_verilog_port(VERILOG_PORT_WIRE, module_port) << ";" << std::endl; + } + /* Add an empty line as a splitter */ + fp << std::endl; + + /* Datapath I/Os of top-level module */ + print_verilog_comment(fp, std::string("----- Local wires for I/Os of FPGA fabric -----")); + for (const BasicPort& module_port : module_manager.module_ports_by_type(top_module, ModuleManager::MODULE_GPIO_PORT)) { + fp << generate_verilog_port(VERILOG_PORT_WIRE, module_port) << ";" << std::endl; + } + /* Add an empty line as a splitter */ + fp << std::endl; + + /* Add local wires/registers that drive stimulus + * We create these general purpose ports here, + * and then wire them to the ports of FPGA fabric depending on their usage + */ + /* Configuration done port */ + BasicPort config_done_port(std::string(TOP_TB_CONFIG_DONE_PORT_NAME), 1); + fp << generate_verilog_port(VERILOG_PORT_REG, config_done_port) << ";" << std::endl; + + /* Programming clock */ + BasicPort prog_clock_port(std::string(TOP_TB_PROG_CLOCK_PORT_NAME), 1); + fp << generate_verilog_port(VERILOG_PORT_WIRE, prog_clock_port) << ";" << std::endl; + BasicPort prog_clock_register_port(std::string(std::string(TOP_TB_PROG_CLOCK_PORT_NAME) + std::string(TOP_TB_CLOCK_REG_POSTFIX)), 1); + fp << generate_verilog_port(VERILOG_PORT_REG, prog_clock_register_port) << ";" << std::endl; + + /* Operating clock */ + BasicPort op_clock_port(std::string(TOP_TB_OP_CLOCK_PORT_NAME), 1); + fp << generate_verilog_port(VERILOG_PORT_WIRE, op_clock_port) << ";" << std::endl; + BasicPort op_clock_register_port(std::string(std::string(TOP_TB_OP_CLOCK_PORT_NAME) + std::string(TOP_TB_CLOCK_REG_POSTFIX)), 1); + fp << generate_verilog_port(VERILOG_PORT_REG, op_clock_register_port) << ";" << std::endl; + + /* Programming set and reset */ + BasicPort prog_reset_port(std::string(TOP_TB_PROG_RESET_PORT_NAME), 1); + fp << generate_verilog_port(VERILOG_PORT_REG, prog_reset_port) << ";" << std::endl; + BasicPort prog_set_port(std::string(TOP_TB_PROG_SET_PORT_NAME), 1); + fp << generate_verilog_port(VERILOG_PORT_REG, prog_set_port) << ";" << std::endl; + + /* Global set and reset */ + BasicPort reset_port(std::string(TOP_TB_RESET_PORT_NAME), 1); + fp << generate_verilog_port(VERILOG_PORT_REG, reset_port) << ";" << std::endl; + BasicPort set_port(std::string(TOP_TB_SET_PORT_NAME), 1); + fp << generate_verilog_port(VERILOG_PORT_REG, set_port) << ";" << std::endl; + + /* Configuration ports depend on the organization of SRAMs */ + print_verilog_top_testbench_config_protocol_port(fp, sram_orgz_type); + + /* Create a clock port if the benchmark have one but not in the default name! + * We will wire the clock directly to the operating clock directly + */ + for (const std::string clock_port_name : clock_port_names) { + if (0 == clock_port_name.compare(op_clock_port.get_name())) { + continue; + } + /* Ensure the clock port name is not a duplication of global ports of the FPGA module */ + bool print_clock_port = true; + for (const BasicPort& module_port : module_manager.module_ports_by_type(top_module, ModuleManager::MODULE_GLOBAL_PORT)) { + if (0 == clock_port_name.compare(module_port.get_name())) { + print_clock_port = false; + } + } + if (false == print_clock_port) { + continue; + } + + /* Print the clock and wire it to op_clock */ + print_verilog_comment(fp, std::string("----- Create a clock for benchmark and wire it to op_clock -------")); + BasicPort clock_port(clock_port_name, 1); + fp << "\t" << generate_verilog_port(VERILOG_PORT_WIRE, clock_port) << ";" << std::endl; + print_verilog_wire_connection(fp, clock_port, op_clock_port, false); + } + + print_verilog_testbench_shared_ports(fp, atom_ctx, + std::string(TOP_TESTBENCH_REFERENCE_OUTPUT_POSTFIX), + std::string(TOP_TESTBENCH_FPGA_OUTPUT_POSTFIX), + std::string(TOP_TESTBENCH_CHECKFLAG_PORT_POSTFIX), + std::string(AUTOCHECKED_SIMULATION_FLAG)); + + /* Instantiate an integer to count the number of error and + * determine if the simulation succeed or failed + */ + print_verilog_comment(fp, std::string("----- Error counter -----")); + fp << "\tinteger " << TOP_TESTBENCH_ERROR_COUNTER << "= 0;" << std::endl; +} + +/******************************************************************** + * Instanciate the input benchmark module + *******************************************************************/ +static +void print_verilog_top_testbench_benchmark_instance(std::fstream& fp, + const std::string& reference_verilog_top_name, + const AtomContext& atom_ctx) { + /* Validate the file stream */ + valid_file_stream(fp); + + /* Benchmark is instanciated conditionally: only when a preprocessing flag is enable */ + print_verilog_preprocessing_flag(fp, std::string(AUTOCHECKED_SIMULATION_FLAG)); + + print_verilog_comment(fp, std::string("----- Reference Benchmark Instanication -------")); + + /* Do NOT use explicit port mapping here: + * VPR added a prefix of "out_" to the output ports of input benchmark + */ + print_verilog_testbench_benchmark_instance(fp, reference_verilog_top_name, + std::string(TOP_TESTBENCH_REFERENCE_INSTANCE_NAME), + std::string(), + std::string(), + std::string(TOP_TESTBENCH_REFERENCE_OUTPUT_POSTFIX), + atom_ctx, + false); + + print_verilog_comment(fp, std::string("----- End reference Benchmark Instanication -------")); + + /* Add an empty line as splitter */ + fp << std::endl; + + /* Condition ends for the benchmark instanciation */ + print_verilog_endif(fp); + + /* Add an empty line as splitter */ + fp << std::endl; +} + +/******************************************************************** + * Print tasks (processes) in Verilog format, + * which is very useful in generating stimuli for each clock cycle + * This function is tuned for configuration-chain manipulation: + * During each programming cycle, we feed the input of scan chain with a memory bit + *******************************************************************/ +static +void print_verilog_top_testbench_load_bitstream_task_configuration_chain(std::fstream& fp) { + + /* Validate the file stream */ + valid_file_stream(fp); + + BasicPort prog_clock_port(std::string(TOP_TB_PROG_CLOCK_PORT_NAME), 1); + BasicPort cc_head_port(generate_configuration_chain_head_name(), 1); + BasicPort cc_head_value(generate_configuration_chain_head_name() + std::string("_val"), 1); + + /* Add an empty line as splitter */ + fp << std::endl; + + /* Feed the scan-chain input at each falling edge of programming clock + * It aims at avoid racing the programming clock (scan-chain data changes at the rising edge). + */ + print_verilog_comment(fp, std::string("----- Task: input values during a programming clock cycle -----")); + fp << "task " << std::string(TOP_TESTBENCH_CC_PROG_TASK_NAME) << ";" << std::endl; + fp << generate_verilog_port(VERILOG_PORT_INPUT, cc_head_value) << ";" << std::endl; + fp << "\tbegin" << std::endl; + fp << "\t\t@(negedge " << generate_verilog_port(VERILOG_PORT_CONKT, prog_clock_port) << ");" << std::endl; + fp << "\t\t\t"; + fp << generate_verilog_port(VERILOG_PORT_CONKT, cc_head_port); + fp << " = "; + fp << generate_verilog_port(VERILOG_PORT_CONKT, cc_head_value); + fp << ";" << std::endl; + + fp << "\tend" << std::endl; + fp << "endtask" << std::endl; + + /* Add an empty line as splitter */ + fp << std::endl; +} + +/******************************************************************** + * Print tasks, which is very useful in generating stimuli for each clock cycle + *******************************************************************/ +static +void print_verilog_top_testbench_load_bitstream_task(std::fstream& fp, + const e_config_protocol_type& sram_orgz_type) { + switch (sram_orgz_type) { + case CONFIG_MEM_STANDALONE: + break; + case CONFIG_MEM_SCAN_CHAIN: + print_verilog_top_testbench_load_bitstream_task_configuration_chain(fp); + break; + case CONFIG_MEM_MEMORY_BANK: + /* TODO: + dump_verilog_top_testbench_stimuli_serial_version_tasks_memory_bank(cur_sram_orgz_info, fp); + */ + break; + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid type of SRAM organization!\n"); + exit(1); + } +} + +/******************************************************************** + * Print generatic input stimuli for the top testbench + * include: + * 1. configuration done signal + * 2. programming clock + * 3. operating clock + * 4. programming reset signal + * 5. programming set signal + * 6. reset signal + * 7. set signal + *******************************************************************/ +static +void print_verilog_top_testbench_generic_stimulus(std::fstream& fp, + const size_t& num_config_clock_cycles, + const float& prog_clock_period, + const float& op_clock_period, + const float& timescale) { + /* Validate the file stream */ + valid_file_stream(fp); + + print_verilog_comment(fp, std::string("----- Number of clock cycles in configuration phase: " + std::to_string(num_config_clock_cycles) + " -----")); + + BasicPort config_done_port(std::string(TOP_TB_CONFIG_DONE_PORT_NAME), 1); + + BasicPort op_clock_port(std::string(TOP_TB_OP_CLOCK_PORT_NAME), 1); + BasicPort op_clock_register_port(std::string(std::string(TOP_TB_OP_CLOCK_PORT_NAME) + std::string(TOP_TB_CLOCK_REG_POSTFIX)), 1); + + BasicPort prog_clock_port(std::string(TOP_TB_PROG_CLOCK_PORT_NAME), 1); + BasicPort prog_clock_register_port(std::string(std::string(TOP_TB_PROG_CLOCK_PORT_NAME) + std::string(TOP_TB_CLOCK_REG_POSTFIX)), 1); + + BasicPort prog_reset_port(std::string(TOP_TB_PROG_RESET_PORT_NAME), 1); + BasicPort prog_set_port(std::string(TOP_TB_PROG_SET_PORT_NAME), 1); + + BasicPort reset_port(std::string(TOP_TB_RESET_PORT_NAME), 1); + BasicPort set_port(std::string(TOP_TB_SET_PORT_NAME), 1); + + /* Generate stimuli waveform for configuration done signals */ + print_verilog_comment(fp, "----- Begin configuration done signal generation -----"); + print_verilog_pulse_stimuli(fp, config_done_port, + 0, /* Initial value */ + num_config_clock_cycles * prog_clock_period / timescale, 0); + print_verilog_comment(fp, "----- End configuration done signal generation -----"); + fp << std::endl; + + /* Generate stimuli waveform for programming clock signals */ + print_verilog_comment(fp, "----- Begin raw programming clock signal generation -----"); + print_verilog_clock_stimuli(fp, prog_clock_register_port, + 0, /* Initial value */ + 0.5 * prog_clock_period / timescale, + std::string()); + print_verilog_comment(fp, "----- End raw programming clock signal generation -----"); + fp << std::endl; + + /* Programming clock should be only enabled during programming phase. + * When configuration is done (config_done is enabled), programming clock should be always zero. + */ + print_verilog_comment(fp, std::string("----- Actual programming clock is triggered only when " + config_done_port.get_name() + " and " + prog_reset_port.get_name() + " are disabled -----")); + fp << "\tassign " << generate_verilog_port(VERILOG_PORT_CONKT, prog_clock_port); + fp << " = " << generate_verilog_port(VERILOG_PORT_CONKT, prog_clock_register_port); + fp << " & (~" << generate_verilog_port(VERILOG_PORT_CONKT, config_done_port) << ")"; + fp << " & (~" << generate_verilog_port(VERILOG_PORT_CONKT, prog_reset_port) << ")"; + fp << ";" << std::endl; + + fp << std::endl; + + /* Generate stimuli waveform for operating clock signals */ + print_verilog_comment(fp, "----- Begin raw operating clock signal generation -----"); + print_verilog_clock_stimuli(fp, op_clock_register_port, + 0, /* Initial value */ + 0.5 * op_clock_period / timescale, + std::string("~" + reset_port.get_name())); + print_verilog_comment(fp, "----- End raw operating clock signal generation -----"); + + /* Operation clock should be enabled after programming phase finishes. + * Before configuration is done (config_done is enabled), operation clock should be always zero. + */ + print_verilog_comment(fp, std::string("----- Actual operating clock is triggered only when " + config_done_port.get_name() + " is enabled -----")); + fp << "\tassign " << generate_verilog_port(VERILOG_PORT_CONKT, op_clock_port); + fp << " = " << generate_verilog_port(VERILOG_PORT_CONKT, op_clock_register_port); + fp << " & " << generate_verilog_port(VERILOG_PORT_CONKT, config_done_port); + fp << ";" << std::endl; + + fp << std::endl; + + /* Reset signal for configuration circuit: + * only enable during the first clock cycle in programming phase + */ + print_verilog_comment(fp, "----- Begin programming reset signal generation -----"); + print_verilog_pulse_stimuli(fp, prog_reset_port, + 1, /* Initial value */ + prog_clock_period / timescale, 0); + print_verilog_comment(fp, "----- End programming reset signal generation -----"); + + fp << std::endl; + + /* Programming set signal for configuration circuit : always disabled */ + print_verilog_comment(fp, "----- Begin programming set signal generation: always disabled -----"); + print_verilog_pulse_stimuli(fp, prog_set_port, + 0, /* Initial value */ + prog_clock_period / timescale, 0); + print_verilog_comment(fp, "----- End programming set signal generation: always disabled -----"); + + fp << std::endl; + + /* Operating reset signals: only enabled during the first clock cycle in operation phase */ + std::vector reset_pulse_widths; + reset_pulse_widths.push_back(op_clock_period / timescale); + reset_pulse_widths.push_back(2 * op_clock_period / timescale); + + std::vector reset_flip_values; + reset_flip_values.push_back(1); + reset_flip_values.push_back(0); + + print_verilog_comment(fp, "----- Begin operating reset signal generation -----"); + print_verilog_comment(fp, "----- Reset signal is enabled until the first clock cycle in operation phase -----"); + print_verilog_pulse_stimuli(fp, reset_port, + 1, + reset_pulse_widths, + reset_flip_values, + config_done_port.get_name()); + print_verilog_comment(fp, "----- End operating reset signal generation -----"); + + /* Operating set signal for configuration circuit : always disabled */ + print_verilog_comment(fp, "----- Begin operating set signal generation: always disabled -----"); + print_verilog_pulse_stimuli(fp, set_port, + 0, /* Initial value */ + op_clock_period / timescale, 0); + print_verilog_comment(fp, "----- End operating set signal generation: always disabled -----"); + + fp << std::endl; +} + +/******************************************************************** + * Print stimulus for a FPGA fabric with a configuration chain protocol + * where configuration bits are programming in serial (one by one) + * Task list: + * 1. For clock signal, we should create voltage waveforms for two types of clock signals: + * a. operation clock + * b. programming clock + * 2. For Set/Reset, we reset the chip after programming phase ends + * and before operation phase starts + * 3. For input/output clb nets (mapped to I/O grids), + * we should create voltage waveforms only after programming phase + *******************************************************************/ +static +void print_verilog_top_testbench_configuration_chain_bitstream(std::fstream& fp, + const BitstreamManager& bitstream_manager, + const std::vector& fabric_bitstream) { + /* Validate the file stream */ + valid_file_stream(fp); + + /* Initial value should be the first configuration bits + * In the rest of programming cycles, + * configuration bits are fed at the falling edge of programming clock. + * We do not care the value of scan_chain head during the first programming cycle + * It is reset anyway + */ + BasicPort config_chain_head_port(generate_configuration_chain_head_name(), 1); + std::vector initial_values(config_chain_head_port.get_width(), 0); + + print_verilog_comment(fp, "----- Begin bitstream loading during configuration phase -----"); + fp << "initial" << std::endl; + fp << "\tbegin" << std::endl; + print_verilog_comment(fp, "----- Configuration chain default input -----"); + fp << "\t\t"; + fp << generate_verilog_port_constant_values(config_chain_head_port, initial_values); + fp << ";"; + + fp << std::endl; + + /* Attention: the configuration chain protcol requires the last configuration bit is fed first + * We will visit the fabric bitstream in a reverse way + */ + std::vector cc_bitstream = fabric_bitstream; + std::reverse(cc_bitstream.begin(), cc_bitstream.end()); + for (const ConfigBitId& bit_id : cc_bitstream) { + fp << "\t\t" << std::string(TOP_TESTBENCH_CC_PROG_TASK_NAME); + fp << "(1'b" << (size_t)bitstream_manager.bit_value(bit_id) << ");" << std::endl; + } + + /* Raise the flag of configuration done when bitstream loading is complete */ + BasicPort prog_clock_port(std::string(TOP_TB_PROG_CLOCK_PORT_NAME), 1); + fp << "\t\t@(negedge " << generate_verilog_port(VERILOG_PORT_CONKT, prog_clock_port) << ");" << std::endl; + + BasicPort config_done_port(std::string(TOP_TB_CONFIG_DONE_PORT_NAME), 1); + fp << "\t\t\t"; + fp << generate_verilog_port(VERILOG_PORT_CONKT, config_done_port); + fp << " <= "; + std::vector config_done_enable_values(config_done_port.get_width(), 1); + fp << generate_verilog_constant_values(config_done_enable_values); + fp << ";" << std::endl; + + fp << "\tend" << std::endl; + print_verilog_comment(fp, "----- End bitstream loading during configuration phase -----"); +} + +/******************************************************************** + * Generate the stimuli for the top-level testbench + * The simulation consists of two phases: configuration phase and operation phase + * Configuration bits are loaded serially. + * This is actually what we do for a physical FPGA + *******************************************************************/ +static +void print_verilog_top_testbench_bitstream(std::fstream& fp, + const e_config_protocol_type& sram_orgz_type, + const BitstreamManager& bitstream_manager, + const std::vector& fabric_bitstream) { + /* Branch on the type of configuration protocol */ + switch (sram_orgz_type) { + case CONFIG_MEM_STANDALONE: + /* TODO */ + break; + case CONFIG_MEM_SCAN_CHAIN: + print_verilog_top_testbench_configuration_chain_bitstream(fp, bitstream_manager, fabric_bitstream); + break; + case CONFIG_MEM_MEMORY_BANK: + /* TODO */ + break; + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid SRAM organization type!\n"); + exit(1); + } +} + +/******************************************************************** + * The top-level function to generate a testbench, in order to verify: + * 1. Configuration phase of the FPGA fabric, where the bitstream is + * loaded to the configuration protocol of the FPGA fabric + * 2. Operating phase of the FPGA fabric, where input stimuli are + * fed to the I/Os of the FPGA fabric + * +----------+ + * | FPGA | +------------+ + * +----->| Fabric |------>| | + * | | | | | + * | +----------+ | | + * | | Output | + * random_input_vectors -----+ | Vector |---->Functional correct? + * | | Comparator | + * | +-----------+ | | + * | | Input | | | + * +----->| Benchmark |----->| | + * +-----------+ +------------+ + * + *******************************************************************/ +void print_verilog_top_testbench(const ModuleManager& module_manager, + const BitstreamManager& bitstream_manager, + const std::vector& fabric_bitstream, + const e_config_protocol_type& sram_orgz_type, + const CircuitLibrary& circuit_lib, + const std::vector& global_ports, + const AtomContext& atom_ctx, + const PlacementContext& place_ctx, + const IoLocationMap& io_location_map, + const std::string& circuit_name, + const std::string& verilog_fname, + const std::string& verilog_dir, + const SimulationSetting& simulation_parameters) { + + std::string timer_message = std::string("Write autocheck testbench for FPGA top-level Verilog netlist for '") + circuit_name + std::string("'"); + + /* Start time count */ + vtr::ScopedStartFinishTimer timer(timer_message); + + /* Create the file stream */ + std::fstream fp; + fp.open(verilog_fname, std::fstream::out | std::fstream::trunc); + + /* Validate the file stream */ + check_file_stream(verilog_fname.c_str(), fp); + + /* Generate a brief description on the Verilog file*/ + std::string title = std::string("FPGA Verilog Testbench for Top-level netlist of Design: ") + circuit_name; + print_verilog_file_header(fp, title); + + /* Print preprocessing flags and external netlists */ + print_verilog_include_defines_preproc_file(fp, verilog_dir); + + /* Find the top_module */ + ModuleId top_module = module_manager.find_module(generate_fpga_top_module_name()); + VTR_ASSERT(true == module_manager.valid_module_id(top_module)); + + /* Preparation: find all the clock ports */ + std::vector clock_port_names = find_atom_netlist_clock_port_names(atom_ctx.nlist); + + /* Start of testbench */ + print_verilog_top_testbench_ports(fp, module_manager, top_module, + atom_ctx, clock_port_names, + sram_orgz_type, circuit_name); + + /* Find the clock period */ + float prog_clock_period = (1./simulation_parameters.programming_clock_frequency()); + float op_clock_period = (1./simulation_parameters.operating_clock_frequency()); + /* Estimate the number of configuration clock cycles + * by traversing the linked-list and count the number of SRAM=1 or BL=1&WL=1 in it. + * We plus 1 additional config clock cycle here because we need to reset everything during the first clock cycle + */ + size_t num_config_clock_cycles = 1 + fabric_bitstream.size(); + + /* Generate stimuli for general control signals */ + print_verilog_top_testbench_generic_stimulus(fp, + num_config_clock_cycles, + prog_clock_period, + op_clock_period, + VERILOG_SIM_TIMESCALE); + + /* Generate stimuli for global ports or connect them to existed signals */ + print_verilog_top_testbench_global_ports_stimuli(fp, + module_manager, top_module, + circuit_lib, global_ports); + + /* Instanciate FPGA top-level module */ + print_verilog_testbench_fpga_instance(fp, module_manager, top_module, + std::string(TOP_TESTBENCH_FPGA_INSTANCE_NAME)); + + /* Connect I/Os to benchmark I/Os or constant driver */ + print_verilog_testbench_connect_fpga_ios(fp, module_manager, top_module, + atom_ctx, place_ctx, io_location_map, + std::string(), + std::string(TOP_TESTBENCH_FPGA_OUTPUT_POSTFIX), + (size_t)VERILOG_DEFAULT_SIGNAL_INIT_VALUE); + + /* Instanciate input benchmark */ + print_verilog_top_testbench_benchmark_instance(fp, + circuit_name, + atom_ctx); + + /* Print tasks used for loading bitstreams */ + print_verilog_top_testbench_load_bitstream_task(fp, sram_orgz_type); + + /* load bitstream to FPGA fabric in a configuration phase */ + print_verilog_top_testbench_bitstream(fp, sram_orgz_type, + bitstream_manager, fabric_bitstream); + + /* Add stimuli for reset, set, clock and iopad signals */ + print_verilog_testbench_random_stimuli(fp, atom_ctx, + std::string(TOP_TESTBENCH_CHECKFLAG_PORT_POSTFIX), + BasicPort(std::string(TOP_TB_OP_CLOCK_PORT_NAME), 1)); + + /* Add output autocheck */ + print_verilog_testbench_check(fp, + std::string(AUTOCHECKED_SIMULATION_FLAG), + std::string(TOP_TESTBENCH_SIM_START_PORT_NAME), + std::string(TOP_TESTBENCH_REFERENCE_OUTPUT_POSTFIX), + std::string(TOP_TESTBENCH_FPGA_OUTPUT_POSTFIX), + std::string(TOP_TESTBENCH_CHECKFLAG_PORT_POSTFIX), + std::string(TOP_TESTBENCH_ERROR_COUNTER), + atom_ctx, clock_port_names, std::string(TOP_TB_OP_CLOCK_PORT_NAME)); + + /* Find simulation time */ + float simulation_time = find_simulation_time_period(VERILOG_SIM_TIMESCALE, + num_config_clock_cycles, + 1./simulation_parameters.programming_clock_frequency(), + simulation_parameters.num_clock_cycles(), + 1./simulation_parameters.operating_clock_frequency()); + + + /* Add Icarus requirement */ + print_verilog_timeout_and_vcd(fp, + std::string(ICARUS_SIMULATOR_FLAG), + std::string(circuit_name + std::string(AUTOCHECK_TOP_TESTBENCH_VERILOG_MODULE_POSTFIX)), + std::string(circuit_name + std::string("_formal.vcd")), + std::string(TOP_TESTBENCH_SIM_START_PORT_NAME), + std::string(TOP_TESTBENCH_ERROR_COUNTER), + (int)simulation_time); + + + /* Testbench ends*/ + print_verilog_module_end(fp, std::string(circuit_name) + std::string(AUTOCHECK_TOP_TESTBENCH_VERILOG_MODULE_POSTFIX)); + + /* Close the file stream */ + fp.close(); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_top_testbench.h b/openfpga/src/fpga_verilog/verilog_top_testbench.h new file mode 100644 index 000000000..e2e652eb8 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_top_testbench.h @@ -0,0 +1,39 @@ +#ifndef VERILOG_TOP_TESTBENCH +#define VERILOG_TOP_TESTBENCH + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "module_manager.h" +#include "bitstream_manager.h" +#include "circuit_library.h" +#include "vpr_context.h" +#include "io_location_map.h" +#include "simulation_setting.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_verilog_top_testbench(const ModuleManager& module_manager, + const BitstreamManager& bitstream_manager, + const std::vector& fabric_bitstream, + const e_config_protocol_type& sram_orgz_type, + const CircuitLibrary& circuit_lib, + const std::vector& global_ports, + const AtomContext& atom_ctx, + const PlacementContext& place_ctx, + const IoLocationMap& io_location_map, + const std::string& circuit_name, + const std::string& verilog_fname, + const std::string& verilog_dir, + const SimulationSetting& simulation_parameters); + +} /* end namespace openfpga */ + +#endif From f558405887418a25d233fe1d4ec4afeb7197c68e Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 27 Feb 2020 12:33:09 -0700 Subject: [PATCH 218/645] ported verilog testbench generator online. Split from fabric generator. Testing to be done --- openfpga/src/base/openfpga_verilog.cpp | 46 +++++- openfpga/src/base/openfpga_verilog.h | 3 + .../src/base/openfpga_verilog_command.cpp | 137 +++++++++++++----- ...options.cpp => fabric_verilog_options.cpp} | 30 +--- ...log_options.h => fabric_verilog_options.h} | 10 +- openfpga/src/fpga_verilog/verilog_api.cpp | 130 +++++++++++++++-- openfpga/src/fpga_verilog/verilog_api.h | 19 ++- .../verilog_auxiliary_netlists.cpp | 23 ++- .../fpga_verilog/verilog_auxiliary_netlists.h | 7 +- .../verilog_preconfig_top_module.h | 5 +- openfpga/src/fpga_verilog/verilog_submodule.h | 2 +- .../verilog_testbench_options.cpp | 97 +++++++++++++ .../fpga_verilog/verilog_testbench_options.h | 62 ++++++++ 13 files changed, 459 insertions(+), 112 deletions(-) rename openfpga/src/fpga_verilog/{verilog_options.cpp => fabric_verilog_options.cpp} (75%) rename openfpga/src/fpga_verilog/{verilog_options.h => fabric_verilog_options.h} (89%) create mode 100644 openfpga/src/fpga_verilog/verilog_testbench_options.cpp create mode 100644 openfpga/src/fpga_verilog/verilog_testbench_options.h diff --git a/openfpga/src/base/openfpga_verilog.cpp b/openfpga/src/base/openfpga_verilog.cpp index 72b8fb100..2a6c88241 100644 --- a/openfpga/src/base/openfpga_verilog.cpp +++ b/openfpga/src/base/openfpga_verilog.cpp @@ -15,7 +15,7 @@ namespace openfpga { /******************************************************************** - * A wrapper function to call the fabric_verilog function of FPGA-Verilog + * A wrapper function to call the fabric Verilog generator of FPGA-Verilog *******************************************************************/ void write_fabric_verilog(OpenfpgaContext& openfpga_ctx, const Command& cmd, const CommandContext& cmd_context) { @@ -26,9 +26,6 @@ void write_fabric_verilog(OpenfpgaContext& openfpga_ctx, CommandOptionId opt_include_signal_init = cmd.option("include_signal_init"); CommandOptionId opt_support_icarus_simulator = cmd.option("support_icarus_simulator"); CommandOptionId opt_print_user_defined_template = cmd.option("print_user_defined_template"); - CommandOptionId opt_print_top_testbench = cmd.option("print_top_testbench"); - CommandOptionId opt_print_formal_verification_top_netlist = cmd.option("print_formal_verification_top_netlist"); - CommandOptionId opt_print_autocheck_top_testbench = cmd.option("print_autocheck_top_testbench"); CommandOptionId opt_verbose = cmd.option("verbose"); /* This is an intermediate data structure which is designed to modularize the FPGA-Verilog @@ -41,9 +38,6 @@ void write_fabric_verilog(OpenfpgaContext& openfpga_ctx, options.set_include_signal_init(cmd_context.option_enable(cmd, opt_include_signal_init)); options.set_support_icarus_simulator(cmd_context.option_enable(cmd, opt_support_icarus_simulator)); options.set_print_user_defined_template(cmd_context.option_enable(cmd, opt_print_user_defined_template)); - options.set_print_top_testbench(cmd_context.option_enable(cmd, opt_print_top_testbench)); - options.set_print_formal_verification_top_netlist(cmd_context.option_enable(cmd, opt_print_formal_verification_top_netlist)); - options.set_print_autocheck_top_testbench(cmd_context.option_value(cmd, opt_print_autocheck_top_testbench)); options.set_verbose_output(cmd_context.option_enable(cmd, opt_verbose)); options.set_compress_routing(openfpga_ctx.flow_manager().compress_routing()); @@ -56,4 +50,42 @@ void write_fabric_verilog(OpenfpgaContext& openfpga_ctx, options); } +/******************************************************************** + * A wrapper function to call the Verilog testbench generator of FPGA-Verilog + *******************************************************************/ +void write_verilog_testbench(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context) { + + CommandOptionId opt_output_dir = cmd.option("file"); + CommandOptionId opt_reference_benchmark = cmd.option("reference_verilog_file_path"); + CommandOptionId opt_print_top_testbench = cmd.option("print_top_testbench"); + CommandOptionId opt_print_formal_verification_top_netlist = cmd.option("print_formal_verification_top_netlist"); + CommandOptionId opt_print_preconfig_top_testbench = cmd.option("print_preconfig_top_testbench"); + CommandOptionId opt_print_simulation_ini = cmd.option("print_simulation_ini"); + CommandOptionId opt_verbose = cmd.option("verbose"); + + /* This is an intermediate data structure which is designed to modularize the FPGA-Verilog + * Keep it independent from any other outside data structures + */ + VerilogTestbenchOption options; + options.set_output_directory(cmd_context.option_value(cmd, opt_output_dir)); + options.set_reference_verilog_file_path(cmd_context.option_value(cmd, opt_reference_benchmark)); + options.set_print_formal_verification_top_netlist(cmd_context.option_enable(cmd, opt_print_formal_verification_top_netlist)); + options.set_print_preconfig_top_testbench(cmd_context.option_enable(cmd, opt_print_preconfig_top_testbench)); + options.set_print_top_testbench(cmd_context.option_enable(cmd, opt_print_top_testbench)); + options.set_print_simulation_ini(cmd_context.option_value(cmd, opt_print_simulation_ini)); + options.set_verbose_output(cmd_context.option_enable(cmd, opt_verbose)); + + fpga_verilog_testbench(openfpga_ctx.module_graph(), + openfpga_ctx.bitstream_manager(), + openfpga_ctx.fabric_bitstream(), + g_vpr_ctx.atom(), + g_vpr_ctx.placement(), + openfpga_ctx.io_location_map(), + openfpga_ctx.arch().circuit_lib, + openfpga_ctx.arch().sim_setting, + openfpga_ctx.arch().config_protocol.type(), + options); +} + } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_verilog.h b/openfpga/src/base/openfpga_verilog.h index a62d19411..faf8cf6b0 100644 --- a/openfpga/src/base/openfpga_verilog.h +++ b/openfpga/src/base/openfpga_verilog.h @@ -18,6 +18,9 @@ namespace openfpga { void write_fabric_verilog(OpenfpgaContext& openfpga_ctx, const Command& cmd, const CommandContext& cmd_context); +void write_verilog_testbench(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context); + } /* end namespace openfpga */ #endif diff --git a/openfpga/src/base/openfpga_verilog_command.cpp b/openfpga/src/base/openfpga_verilog_command.cpp index 7eb7674b9..29c0ee73a 100644 --- a/openfpga/src/base/openfpga_verilog_command.cpp +++ b/openfpga/src/base/openfpga_verilog_command.cpp @@ -11,6 +11,99 @@ /* begin namespace openfpga */ namespace openfpga { +/******************************************************************** + * - Add a command to Shell environment: generate fabric Verilog + * - Add associated options + * - Add command dependency + *******************************************************************/ +static +void add_openfpga_write_fabric_verilog_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id, + const ShellCommandId& shell_cmd_build_fabric_id) { + Command shell_cmd("write_fabric_verilog"); + + /* Add an option '--file' in short '-f'*/ + CommandOptionId output_opt = shell_cmd.add_option("file", true, "Specify the output directory for Verilog netlists"); + shell_cmd.set_option_short_name(output_opt, "f"); + shell_cmd.set_option_require_value(output_opt, openfpga::OPT_STRING); + + /* Add an option '--explicit_port_mapping' */ + shell_cmd.add_option("explicit_port_mapping", false, "Use explicit port mapping in Verilog netlists"); + + /* Add an option '--include_timing' */ + shell_cmd.add_option("include_timing", false, "Enable timing annotation in Verilog netlists"); + + /* Add an option '--include_signal_init' */ + shell_cmd.add_option("include_signal_init", false, "Initialize all the signals in Verilog netlists"); + + /* Add an option '--support_icarus_simulator' */ + shell_cmd.add_option("support_icarus_simulator", false, "Fine-tune Verilog netlists to support icarus simulator"); + + /* Add an option '--print_user_defined_template' */ + shell_cmd.add_option("print_user_defined_template", false, "Generate a template Verilog files for user-defined circuit models"); + + /* Add an option '--verbose' */ + shell_cmd.add_option("verbose", false, "Enable verbose output"); + + /* Add command 'write_fabric_verilog' to the Shell */ + ShellCommandId shell_cmd_id = shell.add_command(shell_cmd, "generate Verilog netlists modeling full FPGA fabric"); + shell.set_command_class(shell_cmd_id, cmd_class_id); + shell.set_command_execute_function(shell_cmd_id, write_verilog_testbench); + + /* The 'build_fabric' command should NOT be executed before 'link_openfpga_arch' */ + std::vector cmd_dependency; + cmd_dependency.push_back(shell_cmd_build_fabric_id); + shell.set_command_dependency(shell_cmd_id, cmd_dependency); +} + +/******************************************************************** + * - Add a command to Shell environment: write Verilog testbench + * - Add associated options + * - Add command dependency + *******************************************************************/ +static +void add_openfpga_write_verilog_testbench_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id, + const ShellCommandId& shell_cmd_build_fabric_id) { + Command shell_cmd("write_verilog_testbench"); + + /* Add an option '--file' in short '-f'*/ + CommandOptionId output_opt = shell_cmd.add_option("file", true, "Specify the output directory for Verilog netlists"); + shell_cmd.set_option_short_name(output_opt, "f"); + shell_cmd.set_option_require_value(output_opt, openfpga::OPT_STRING); + + /* Add an option '--reference_benchmark_file_path'*/ + CommandOptionId ref_bm_opt = shell_cmd.add_option("reference_benchmark_file_path", true, "Specify the file path to the reference Verilog netlist"); + shell_cmd.set_option_require_value(ref_bm_opt, openfpga::OPT_STRING); + + /* Add an option '--print_top_testbench' */ + shell_cmd.add_option("print_top_testbench", false, "Generate a full testbench for top-level fabric module with autocheck capability"); + + /* Add an option '--print_formal_verification_top_netlist' */ + shell_cmd.add_option("print_formal_verification_top_netlist", false, "Generate a top-level module which can be used in formal verification"); + + /* Add an option '--print_preconfig_top_testbench' */ + CommandOptionId preconfig_tb_opt = shell_cmd.add_option("print_preconfig_top_testbench", false, "Generate a pre-configured testbench for top-level fabric module with autocheck capability"); + shell_cmd.set_option_require_value(preconfig_tb_opt, openfpga::OPT_STRING); + + /* Add an option '--print_simulation_ini' */ + CommandOptionId sim_ini_opt = shell_cmd.add_option("print_simulation_ini", false, "Generate a .ini file as an exchangeable file to enable HDL simulations"); + shell_cmd.set_option_require_value(sim_ini_opt, openfpga::OPT_STRING); + + /* Add an option '--verbose' */ + shell_cmd.add_option("verbose", false, "Enable verbose output"); + + /* Add command to the Shell */ + ShellCommandId shell_cmd_id = shell.add_command(shell_cmd, "generate Verilog testbenches for full FPGA fabric"); + shell.set_command_class(shell_cmd_id, cmd_class_id); + shell.set_command_execute_function(shell_cmd_id, write_fabric_verilog); + + /* The command should NOT be executed before 'build_fabric' */ + std::vector cmd_dependency; + cmd_dependency.push_back(shell_cmd_build_fabric_id); + shell.set_command_dependency(shell_cmd_id, cmd_dependency); +} + void add_openfpga_verilog_commands(openfpga::Shell& shell) { /* Get the unique id of 'build_fabric' command which is to be used in creating the dependency graph */ const ShellCommandId& shell_cmd_build_fabric_id = shell.command(std::string("build_fabric")); @@ -19,42 +112,18 @@ void add_openfpga_verilog_commands(openfpga::Shell& shell) { ShellCommandClassId openfpga_verilog_cmd_class = shell.add_command_class("FPGA-Verilog"); /******************************** - * Command 'wirte_fabric_verilog' + * Command 'write_fabric_verilog' */ - Command shell_cmd_write_fabric_verilog("write_fabric_verilog"); - /* Add an option '--file' in short '-f'*/ - CommandOptionId fabric_verilog_output_opt = shell_cmd_write_fabric_verilog.add_option("file", true, "Specify the output directory for Verilog netlists"); - shell_cmd_write_fabric_verilog.set_option_short_name(fabric_verilog_output_opt, "f"); - shell_cmd_write_fabric_verilog.set_option_require_value(fabric_verilog_output_opt, openfpga::OPT_STRING); - /* Add an option '--explicit_port_mapping' */ - shell_cmd_write_fabric_verilog.add_option("explicit_port_mapping", false, "Use explicit port mapping in Verilog netlists"); - /* Add an option '--include_timing' */ - shell_cmd_write_fabric_verilog.add_option("include_timing", false, "Enable timing annotation in Verilog netlists"); - /* Add an option '--include_signal_init' */ - shell_cmd_write_fabric_verilog.add_option("include_signal_init", false, "Initialize all the signals in Verilog netlists"); - /* Add an option '--support_icarus_simulator' */ - shell_cmd_write_fabric_verilog.add_option("support_icarus_simulator", false, "Fine-tune Verilog netlists to support icarus simulator"); - /* Add an option '--print_user_defined_template' */ - shell_cmd_write_fabric_verilog.add_option("print_user_defined_template", false, "Generate a template Verilog files for user-defined circuit models"); - /* Add an option '--print_top_testbench' */ - shell_cmd_write_fabric_verilog.add_option("print_top_testbench", false, "Generate a testbench for top-level fabric module"); - /* Add an option '--print_formal_verification_top_netlist' */ - shell_cmd_write_fabric_verilog.add_option("print_formal_verification_top_netlist", false, "Generate a top-level module which can be used in formal verification"); - /* Add an option '--print_autocheck_top_testbench' */ - CommandOptionId fabric_verilog_autocheck_tb_opt = shell_cmd_write_fabric_verilog.add_option("print_autocheck_top_testbench", false, "Generate a testbench for top-level fabric module with autocheck capability"); - shell_cmd_write_fabric_verilog.set_option_require_value(fabric_verilog_autocheck_tb_opt, openfpga::OPT_STRING); - /* Add an option '--verbose' */ - shell_cmd_write_fabric_verilog.add_option("verbose", false, "Enable verbose output"); - - /* Add command 'write_fabric_verilog' to the Shell */ - ShellCommandId shell_cmd_write_fabric_verilog_id = shell.add_command(shell_cmd_write_fabric_verilog, "generate Verilog netlists modeling full FPGA fabric"); - shell.set_command_class(shell_cmd_write_fabric_verilog_id, openfpga_verilog_cmd_class); - shell.set_command_execute_function(shell_cmd_write_fabric_verilog_id, write_fabric_verilog); + add_openfpga_write_fabric_verilog_command(shell, + openfpga_verilog_cmd_class, + shell_cmd_build_fabric_id); - /* The 'build_fabric' command should NOT be executed before 'link_openfpga_arch' */ - std::vector cmd_dependency_write_fabric_verilog; - cmd_dependency_write_fabric_verilog.push_back(shell_cmd_build_fabric_id); - shell.set_command_dependency(shell_cmd_write_fabric_verilog_id, cmd_dependency_write_fabric_verilog); + /******************************** + * Command 'write_verilog_testbench' + */ + add_openfpga_write_verilog_testbench_command(shell, + openfpga_verilog_cmd_class, + shell_cmd_build_fabric_id); } } /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_options.cpp b/openfpga/src/fpga_verilog/fabric_verilog_options.cpp similarity index 75% rename from openfpga/src/fpga_verilog/verilog_options.cpp rename to openfpga/src/fpga_verilog/fabric_verilog_options.cpp index b3b398dd7..ba843ef20 100644 --- a/openfpga/src/fpga_verilog/verilog_options.cpp +++ b/openfpga/src/fpga_verilog/fabric_verilog_options.cpp @@ -3,7 +3,7 @@ ******************************************************************************/ #include "vtr_assert.h" -#include "verilog_options.h" +#include "fabric_verilog_options.h" /* begin namespace openfpga */ namespace openfpga { @@ -52,22 +52,6 @@ bool FabricVerilogOption::compress_routing() const { return compress_routing_; } -bool FabricVerilogOption::print_top_testbench() const { - return print_top_testbench_; -} - -bool FabricVerilogOption::print_formal_verification_top_netlist() const { - return print_formal_verification_top_netlist_; -} - -bool FabricVerilogOption::print_autocheck_top_testbench() const { - return false == reference_verilog_file_path_.empty(); -} - -std::string FabricVerilogOption::reference_verilog_file_path() const { - return reference_verilog_file_path_; -} - bool FabricVerilogOption::print_user_defined_template() const { return print_user_defined_template_; } @@ -103,18 +87,6 @@ void FabricVerilogOption::set_compress_routing(const bool& enabled) { compress_routing_ = enabled; } -void FabricVerilogOption::set_print_top_testbench(const bool& enabled) { - print_top_testbench_ = enabled; -} - -void FabricVerilogOption::set_print_formal_verification_top_netlist(const bool& enabled) { - print_formal_verification_top_netlist_ = enabled; -} - -void FabricVerilogOption::set_print_autocheck_top_testbench(const std::string& reference_verilog_file_path) { - reference_verilog_file_path_ = reference_verilog_file_path; -} - void FabricVerilogOption::set_print_user_defined_template(const bool& enabled) { print_user_defined_template_ = enabled; } diff --git a/openfpga/src/fpga_verilog/verilog_options.h b/openfpga/src/fpga_verilog/fabric_verilog_options.h similarity index 89% rename from openfpga/src/fpga_verilog/verilog_options.h rename to openfpga/src/fpga_verilog/fabric_verilog_options.h index 48e32086b..38ef05f9a 100644 --- a/openfpga/src/fpga_verilog/verilog_options.h +++ b/openfpga/src/fpga_verilog/fabric_verilog_options.h @@ -1,5 +1,5 @@ -#ifndef VERILOG_OPTIONS_H -#define VERILOG_OPTIONS_H +#ifndef FABRIC_VERILOG_OPTIONS_H +#define FABRIC_VERILOG_OPTIONS_H /******************************************************************** * Include header files required by the data structure definition @@ -10,11 +10,7 @@ namespace openfpga { /******************************************************************** - * FlowManager aims to resolve the dependency between OpenFPGA functional - * code blocks - * It can provide flags for downstream modules about if the data structures - * they require have already been constructed - * + * Options for Fabric Verilog generator *******************************************************************/ class FabricVerilogOption { public: /* Public constructor */ diff --git a/openfpga/src/fpga_verilog/verilog_api.cpp b/openfpga/src/fpga_verilog/verilog_api.cpp index 645033d65..527ec5a56 100644 --- a/openfpga/src/fpga_verilog/verilog_api.cpp +++ b/openfpga/src/fpga_verilog/verilog_api.cpp @@ -20,6 +20,10 @@ #include "verilog_grid.h" #include "verilog_top_module.h" +#include "verilog_preconfig_top_module.h" +#include "verilog_formal_random_top_testbench.h" +#include "verilog_top_testbench.h" + /* Header file for this source file */ #include "verilog_api.h" @@ -27,17 +31,18 @@ namespace openfpga { /******************************************************************** - * Top-level function of FPGA-Verilog + * A top-level function of FPGA-Verilog which focuses on fabric Verilog generation * This function will generate - * 1. primitive modules required by the full fabric - * which are LUTs, routing multiplexer, logic gates, transmission-gates etc. - * 2. Routing modules, which are Switch Blocks (SBs) and Connection Blocks (CBs) - * 3. Logic block modules, which are Configuration Logic Blocks (CLBs) - * 4. FPGA module, which are the full FPGA fabric with configuration protocol - * 5. A wrapper module, which encapsulate the FPGA module in a Verilog module which have the same port as the input benchmark - * 6. Testbench, where a FPGA module is configured with a bitstream and then driven by input vectors - * 7. Pre-configured testbench, which can skip the configuration phase and pre-configure the FPGA module. This testbench is created for quick verification and formal verification purpose. - * 8. Verilog netlist including preprocessing flags and all the Verilog netlists that have been generated + * - primitive modules required by the full fabric + * - which are LUTs, routing multiplexer, logic gates, transmission-gates etc. + * - Routing modules, which are Switch Blocks (SBs) and Connection Blocks (CBs) + * - Logic block modules, which are Configuration Logic Blocks (CLBs) + * - FPGA module, which are the full FPGA fabric with configuration protocol + * + * Note: + * - Please do NOT include ANY testbench generation in this function!!! + * It is about the fabric itself, independent from any implementation + * All the testbench generation should be in the function fpga_testbench_verilog() * * TODO: We should use module manager as a constant here. * All the modification should be done before this writer! @@ -75,9 +80,6 @@ void fpga_fabric_verilog(ModuleManager& module_manager, print_verilog_preprocessing_flags_netlist(std::string(src_dir_path), options); - print_verilog_simulation_preprocessing_flags(std::string(src_dir_path), - options); - /* Generate primitive Verilog modules, which are corner stones of FPGA fabric * Note that this function MUST be called before Verilog generation of * core logic (i.e., logic blocks and routing resources) !!! @@ -121,4 +123,106 @@ void fpga_fabric_verilog(ModuleManager& module_manager, module_manager.num_modules()); } + +/******************************************************************** + * A top-level function of FPGA-Verilog which focuses on fabric Verilog generation + * This function will generate + * - A wrapper module, which encapsulate the FPGA module in a Verilog module which have the same port as the input benchmark + * - Testbench, where a FPGA module is configured with a bitstream and then driven by input vectors + * - Pre-configured testbench, which can skip the configuration phase and pre-configure the FPGA module. + * This testbench is created for quick verification and formal verification purpose. + * - Verilog netlist including preprocessing flags and all the Verilog netlists that have been generated + ********************************************************************/ +void fpga_verilog_testbench(const ModuleManager& module_manager, + const BitstreamManager& bitstream_manager, + const std::vector& fabric_bitstream, + const AtomContext& atom_ctx, + const PlacementContext& place_ctx, + const IoLocationMap& io_location_map, + const CircuitLibrary& circuit_lib, + const SimulationSetting& simulation_setting, + const e_config_protocol_type& config_protocol_type, + const VerilogTestbenchOption& options) { + + vtr::ScopedStartFinishTimer timer("Write Verilog testbenches for FPGA fabric\n"); + + std::string src_dir_path = format_dir_path(options.output_directory()); + + std::string netlist_name = atom_ctx.nlist.netlist_name(); + + /* Create directories */ + create_dir_path(src_dir_path.c_str()); + + /* TODO: check if this works here. This function was in fabric generator */ + print_verilog_simulation_preprocessing_flags(std::string(src_dir_path), + options); + + /* Collect global ports from the circuit library: + * TODO: should we place this in the OpenFPGA context? + */ + std::vector global_ports = find_circuit_library_global_ports(circuit_lib); + + /* Generate wrapper module for FPGA fabric (mapped by the input benchmark and pre-configured testbench for verification */ + if (true == options.print_formal_verification_top_netlist()) { + std::string formal_verification_top_netlist_file_path = src_dir_path + netlist_name + + std::string(FORMAL_VERIFICATION_VERILOG_FILE_POSTFIX); + print_verilog_preconfig_top_module(module_manager, bitstream_manager, + circuit_lib, global_ports, + atom_ctx, place_ctx, io_location_map, + netlist_name, + formal_verification_top_netlist_file_path, + src_dir_path); + } + + if (true == options.print_preconfig_top_testbench()) { + /* Generate top-level testbench using random vectors */ + std::string random_top_testbench_file_path = src_dir_path + netlist_name + + std::string(RANDOM_TOP_TESTBENCH_VERILOG_FILE_POSTFIX); + print_verilog_random_top_testbench(netlist_name, + random_top_testbench_file_path, + src_dir_path, + atom_ctx, + simulation_setting); + } + + /* Generate full testbench for verification, including configuration phase and operating phase */ + if (true == options.print_top_testbench()) { + std::string top_testbench_file_path = src_dir_path + netlist_name + + std::string(AUTOCHECK_TOP_TESTBENCH_VERILOG_FILE_POSTFIX); + print_verilog_top_testbench(module_manager, + bitstream_manager, fabric_bitstream, + config_protocol_type, + circuit_lib, global_ports, + atom_ctx, place_ctx, io_location_map, + netlist_name, + top_testbench_file_path, + src_dir_path, + simulation_setting); + } + + /* TODO: Generate exchangeable files which contains simulation settings + if (true == options.print_simulation_ini()) { + std::string simulation_ini_file_name; + if (true != options.simulation_ini_path()) { + simulation_ini_file_name = options.simulation_ini_path(); + } + print_verilog_simulation_info(simulation_ini_file_name, + format_dir_path(chomped_parent_dir), + netlist_name, + src_dir_path, + bitstream_manager.bits().size(), + simulation_setting.num_clock_cycle(), + simulation_setting.programming_clock_frequency(), + simulation_setting.operating_clock_frequency()); + } + */ + + /* Generate a Verilog file including all the netlists that have been generated */ + print_include_netlists(src_dir_path, + netlist_name, + options.reference_verilog_file_path(), + circuit_lib); + +} + } /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_api.h b/openfpga/src/fpga_verilog/verilog_api.h index 7494a5a8f..587987425 100644 --- a/openfpga/src/fpga_verilog/verilog_api.h +++ b/openfpga/src/fpga_verilog/verilog_api.h @@ -7,14 +7,17 @@ #include #include -#include "vpr_types.h" #include "mux_library.h" #include "circuit_library.h" #include "vpr_context.h" #include "vpr_device_annotation.h" #include "device_rr_gsb.h" #include "module_manager.h" -#include "verilog_options.h" +#include "bitstream_manager.h" +#include "simulation_setting.h" +#include "io_location_map.h" +#include "fabric_verilog_options.h" +#include "verilog_testbench_options.h" /******************************************************************** * Function declaration @@ -31,6 +34,18 @@ void fpga_fabric_verilog(ModuleManager& module_manager, const DeviceRRGSB& device_rr_gsb, const FabricVerilogOption& options); +void fpga_verilog_testbench(const ModuleManager& module_manager, + const BitstreamManager& bitstream_manager, + const std::vector& fabric_bitstream, + const AtomContext& atom_ctx, + const PlacementContext& place_ctx, + const IoLocationMap& io_location_map, + const CircuitLibrary& circuit_lib, + const SimulationSetting& simulation_parameters, + const e_config_protocol_type& config_protocol_type, + const VerilogTestbenchOption& options); + + } /* end namespace openfpga */ #endif diff --git a/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.cpp b/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.cpp index f1a12798d..20552c29f 100644 --- a/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.cpp +++ b/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.cpp @@ -115,7 +115,7 @@ void print_include_netlists(const std::string& src_dir, * which are used enable/disable some features in FPGA Verilog modules *******************************************************************/ void print_verilog_preprocessing_flags_netlist(const std::string& src_dir, - const FabricVerilogOption& fpga_verilog_opts) { + const FabricVerilogOption& fabric_verilog_opts) { std::string verilog_fname = src_dir + std::string(DEFINES_VERILOG_FILE_NAME); @@ -130,25 +130,19 @@ void print_verilog_preprocessing_flags_netlist(const std::string& src_dir, print_verilog_file_header(fp, std::string("Preprocessing flags to enable/disable features in FPGA Verilog modules")); /* To enable timing */ - if (true == fpga_verilog_opts.include_timing()) { + if (true == fabric_verilog_opts.include_timing()) { print_verilog_define_flag(fp, std::string(VERILOG_TIMING_PREPROC_FLAG), 1); fp << std::endl; } /* To enable timing */ - if (true == fpga_verilog_opts.include_signal_init()) { + if (true == fabric_verilog_opts.include_signal_init()) { print_verilog_define_flag(fp, std::string(VERILOG_SIGNAL_INIT_PREPROC_FLAG), 1); fp << std::endl; } - /* To enable formal verfication flag */ - if (true == fpga_verilog_opts.print_formal_verification_top_netlist()) { - print_verilog_define_flag(fp, std::string(VERILOG_FORMAL_VERIFICATION_PREPROC_FLAG), 1); - fp << std::endl; - } - /* To enable functional verfication with Icarus */ - if (true == fpga_verilog_opts.support_icarus_simulator()) { + if (true == fabric_verilog_opts.support_icarus_simulator()) { print_verilog_define_flag(fp, std::string(ICARUS_SIMULATOR_FLAG), 1); fp << std::endl; } @@ -161,7 +155,7 @@ void print_verilog_preprocessing_flags_netlist(const std::string& src_dir, * Print a Verilog file containing simulation-related preprocessing flags *******************************************************************/ void print_verilog_simulation_preprocessing_flags(const std::string& src_dir, - const FabricVerilogOption& fpga_verilog_opts) { + const VerilogTestbenchOption& verilog_testbench_opts) { std::string verilog_fname = src_dir + std::string(DEFINES_VERILOG_SIMULATION_FILE_NAME); @@ -176,19 +170,20 @@ void print_verilog_simulation_preprocessing_flags(const std::string& src_dir, print_verilog_file_header(fp, std::string("Preprocessing flags to enable/disable simulation features")); /* To enable manualy checked simulation */ - if (true == fpga_verilog_opts.print_top_testbench()) { + if (true == verilog_testbench_opts.print_top_testbench()) { print_verilog_define_flag(fp, std::string(INITIAL_SIMULATION_FLAG), 1); fp << std::endl; } /* To enable auto-checked simulation */ - if (true == fpga_verilog_opts.print_autocheck_top_testbench()) { + if ( (true == verilog_testbench_opts.print_preconfig_top_testbench()) + || (true == verilog_testbench_opts.print_top_testbench()) ) { print_verilog_define_flag(fp, std::string(AUTOCHECKED_SIMULATION_FLAG), 1); fp << std::endl; } /* To enable pre-configured FPGA simulation */ - if (true == fpga_verilog_opts.print_formal_verification_top_netlist()) { + if (true == verilog_testbench_opts.print_formal_verification_top_netlist()) { print_verilog_define_flag(fp, std::string(FORMAL_SIMULATION_FLAG), 1); fp << std::endl; } diff --git a/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.h b/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.h index 42f3b0042..291ca8915 100644 --- a/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.h +++ b/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.h @@ -6,7 +6,8 @@ *******************************************************************/ #include #include "circuit_library.h" -#include "verilog_options.h" +#include "fabric_verilog_options.h" +#include "verilog_testbench_options.h" /******************************************************************** * Function declaration @@ -21,10 +22,10 @@ void print_include_netlists(const std::string& src_dir, const CircuitLibrary& circuit_lib); void print_verilog_preprocessing_flags_netlist(const std::string& src_dir, - const FabricVerilogOption& fpga_verilog_opts); + const FabricVerilogOption& fabric_verilog_opts); void print_verilog_simulation_preprocessing_flags(const std::string& src_dir, - const FabricVerilogOption& fpga_verilog_opts); + const VerilogTestbenchOption& verilog_testbench_opts); } /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_preconfig_top_module.h b/openfpga/src/fpga_verilog/verilog_preconfig_top_module.h index 0c8e54708..59f777a6c 100644 --- a/openfpga/src/fpga_verilog/verilog_preconfig_top_module.h +++ b/openfpga/src/fpga_verilog/verilog_preconfig_top_module.h @@ -6,10 +6,11 @@ *******************************************************************/ #include #include -#include "circuit_types.h" -#include "vpr_types.h" +#include "circuit_library.h" +#include "vpr_context.h" #include "module_manager.h" #include "bitstream_manager.h" +#include "io_location_map.h" /******************************************************************** * Function declaration diff --git a/openfpga/src/fpga_verilog/verilog_submodule.h b/openfpga/src/fpga_verilog/verilog_submodule.h index 4f7cb511c..88e1ce965 100644 --- a/openfpga/src/fpga_verilog/verilog_submodule.h +++ b/openfpga/src/fpga_verilog/verilog_submodule.h @@ -6,7 +6,7 @@ *******************************************************************/ #include "module_manager.h" #include "mux_library.h" -#include "verilog_options.h" +#include "fabric_verilog_options.h" /******************************************************************** * Function declaration diff --git a/openfpga/src/fpga_verilog/verilog_testbench_options.cpp b/openfpga/src/fpga_verilog/verilog_testbench_options.cpp new file mode 100644 index 000000000..f1ef64daa --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_testbench_options.cpp @@ -0,0 +1,97 @@ +/****************************************************************************** + * Memember functions for data structure VerilogTestbenchOption + ******************************************************************************/ +#include "vtr_assert.h" + +#include "verilog_testbench_options.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/************************************************** + * Public Constructors + *************************************************/ +VerilogTestbenchOption::VerilogTestbenchOption() { + output_directory_.clear(); + reference_verilog_file_path_.clear(); + print_preconfig_top_testbench_ = false; + print_formal_verification_top_netlist_ = false; + print_top_testbench_ = false; + simulation_ini_path_.clear(); + verbose_output_ = false; +} + +/************************************************** + * Public Accessors + *************************************************/ +std::string VerilogTestbenchOption::output_directory() const { + return output_directory_; +} + +std::string VerilogTestbenchOption::reference_verilog_file_path() const { + return reference_verilog_file_path_; +} + +bool VerilogTestbenchOption::print_formal_verification_top_netlist() const { + return print_formal_verification_top_netlist_; +} + +bool VerilogTestbenchOption::print_preconfig_top_testbench() const { + return print_preconfig_top_testbench_; +} + +bool VerilogTestbenchOption::print_top_testbench() const { + return print_top_testbench_; +} + +bool VerilogTestbenchOption::print_simulation_ini() const { + return !simulation_ini_path_.empty(); +} + +std::string VerilogTestbenchOption::simulation_ini_path() const { + return simulation_ini_path_; +} + +bool VerilogTestbenchOption::verbose_output() const { + return verbose_output_; +} + +/****************************************************************************** + * Private Mutators + ******************************************************************************/ +void VerilogTestbenchOption::set_output_directory(const std::string& output_dir) { + output_directory_ = output_dir; +} + +void VerilogTestbenchOption::set_reference_verilog_file_path(const std::string& reference_verilog_file_path) { + reference_verilog_file_path_ = reference_verilog_file_path; + /* Chain effect on other options: + * Enable/disable the print_preconfig_top_testbench and print_top_testbench + */ + set_print_preconfig_top_testbench(print_preconfig_top_testbench_); + set_print_top_testbench(print_top_testbench_); +} + +void VerilogTestbenchOption::set_print_formal_verification_top_netlist(const bool& enabled) { + print_formal_verification_top_netlist_ = enabled; +} + +void VerilogTestbenchOption::set_print_preconfig_top_testbench(const bool& enabled) { + print_preconfig_top_testbench_ = enabled + && print_formal_verification_top_netlist_ + && (!reference_verilog_file_path_.empty()); +} + +void VerilogTestbenchOption::set_print_top_testbench(const bool& enabled) { + print_top_testbench_ = enabled && (!reference_verilog_file_path_.empty()); +} + +void VerilogTestbenchOption::set_print_simulation_ini(const std::string& simulation_ini_path) { + simulation_ini_path_ = simulation_ini_path; +} + +void VerilogTestbenchOption::set_verbose_output(const bool& enabled) { + verbose_output_ = enabled; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_testbench_options.h b/openfpga/src/fpga_verilog/verilog_testbench_options.h new file mode 100644 index 000000000..d393b11c8 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_testbench_options.h @@ -0,0 +1,62 @@ +#ifndef VERILOG_TESTBENCH_OPTIONS_H +#define VERILOG_TESTBENCH_OPTIONS_H + +/******************************************************************** + * Include header files required by the data structure definition + *******************************************************************/ +#include + +/* Begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Options for Verilog Testbench generator + * Typicall usage: + * VerilogTestbench verilog_tb_opt(); + * // Set options + * ... + * + *******************************************************************/ +class VerilogTestbenchOption { + public: /* Public constructor */ + /* Set default options */ + VerilogTestbenchOption(); + public: /* Public accessors */ + std::string output_directory() const; + std::string reference_verilog_file_path() const; + bool print_formal_verification_top_netlist() const; + bool print_preconfig_top_testbench() const; + bool print_top_testbench() const; + bool print_simulation_ini() const; + std::string simulation_ini_path() const; + bool verbose_output() const; + public: /* Public validator */ + bool validate() const; + public: /* Public mutators */ + void set_output_directory(const std::string& output_dir); + /* The reference verilog file path is the key parameters that will have an impact on other options: + * - print_preconfig_top_testbench + * - print_top_testbench + * If the file path is empty, the above testbench generation will not be enabled + */ + void set_reference_verilog_file_path(const std::string& reference_verilog_file_path); + void set_print_formal_verification_top_netlist(const bool& enabled); + /* The preconfig top testbench generation can be enabled only when formal verification top netlist is enabled */ + void set_print_preconfig_top_testbench(const bool& enabled); + void set_print_top_testbench(const bool& enabled); + void set_print_simulation_ini(const std::string& simulation_ini_path); + void set_verbose_output(const bool& enabled); + private: /* Internal Data */ + std::string output_directory_; + std::string reference_verilog_file_path_; + bool print_formal_verification_top_netlist_; + bool print_preconfig_top_testbench_; + bool print_top_testbench_; + /* Print simulation ini is enabled only when the path is not empty */ + std::string simulation_ini_path_; + bool verbose_output_; +}; + +} /* End namespace openfpga*/ + +#endif From 078f72320fb7979243d5c8eea895afa7a0908b07 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 27 Feb 2020 13:24:26 -0700 Subject: [PATCH 219/645] debugging Verilog testbench generator. Bug spotted in using renamed atom_block and clock ports --- openfpga/src/base/openfpga_verilog.cpp | 4 ++-- .../src/base/openfpga_verilog_command.cpp | 7 +++---- openfpga/src/fpga_verilog/verilog_api.cpp | 2 +- .../verilog_testbench_options.cpp | 19 +++++++++++-------- .../fpga_verilog/verilog_testbench_options.h | 6 +++--- openfpga/test_script/s298_k6_frac.openfpga | 5 ++++- 6 files changed, 24 insertions(+), 19 deletions(-) diff --git a/openfpga/src/base/openfpga_verilog.cpp b/openfpga/src/base/openfpga_verilog.cpp index 2a6c88241..41057ad34 100644 --- a/openfpga/src/base/openfpga_verilog.cpp +++ b/openfpga/src/base/openfpga_verilog.cpp @@ -57,7 +57,7 @@ void write_verilog_testbench(OpenfpgaContext& openfpga_ctx, const Command& cmd, const CommandContext& cmd_context) { CommandOptionId opt_output_dir = cmd.option("file"); - CommandOptionId opt_reference_benchmark = cmd.option("reference_verilog_file_path"); + CommandOptionId opt_reference_benchmark = cmd.option("reference_benchmark_file_path"); CommandOptionId opt_print_top_testbench = cmd.option("print_top_testbench"); CommandOptionId opt_print_formal_verification_top_netlist = cmd.option("print_formal_verification_top_netlist"); CommandOptionId opt_print_preconfig_top_testbench = cmd.option("print_preconfig_top_testbench"); @@ -69,7 +69,7 @@ void write_verilog_testbench(OpenfpgaContext& openfpga_ctx, */ VerilogTestbenchOption options; options.set_output_directory(cmd_context.option_value(cmd, opt_output_dir)); - options.set_reference_verilog_file_path(cmd_context.option_value(cmd, opt_reference_benchmark)); + options.set_reference_benchmark_file_path(cmd_context.option_value(cmd, opt_reference_benchmark)); options.set_print_formal_verification_top_netlist(cmd_context.option_enable(cmd, opt_print_formal_verification_top_netlist)); options.set_print_preconfig_top_testbench(cmd_context.option_enable(cmd, opt_print_preconfig_top_testbench)); options.set_print_top_testbench(cmd_context.option_enable(cmd, opt_print_top_testbench)); diff --git a/openfpga/src/base/openfpga_verilog_command.cpp b/openfpga/src/base/openfpga_verilog_command.cpp index 29c0ee73a..3d4f7784b 100644 --- a/openfpga/src/base/openfpga_verilog_command.cpp +++ b/openfpga/src/base/openfpga_verilog_command.cpp @@ -48,7 +48,7 @@ void add_openfpga_write_fabric_verilog_command(openfpga::Shell& /* Add command 'write_fabric_verilog' to the Shell */ ShellCommandId shell_cmd_id = shell.add_command(shell_cmd, "generate Verilog netlists modeling full FPGA fabric"); shell.set_command_class(shell_cmd_id, cmd_class_id); - shell.set_command_execute_function(shell_cmd_id, write_verilog_testbench); + shell.set_command_execute_function(shell_cmd_id, write_fabric_verilog); /* The 'build_fabric' command should NOT be executed before 'link_openfpga_arch' */ std::vector cmd_dependency; @@ -83,8 +83,7 @@ void add_openfpga_write_verilog_testbench_command(openfpga::Shell cmd_dependency; diff --git a/openfpga/src/fpga_verilog/verilog_api.cpp b/openfpga/src/fpga_verilog/verilog_api.cpp index 527ec5a56..ed4b28982 100644 --- a/openfpga/src/fpga_verilog/verilog_api.cpp +++ b/openfpga/src/fpga_verilog/verilog_api.cpp @@ -220,7 +220,7 @@ void fpga_verilog_testbench(const ModuleManager& module_manager, /* Generate a Verilog file including all the netlists that have been generated */ print_include_netlists(src_dir_path, netlist_name, - options.reference_verilog_file_path(), + options.reference_benchmark_file_path(), circuit_lib); } diff --git a/openfpga/src/fpga_verilog/verilog_testbench_options.cpp b/openfpga/src/fpga_verilog/verilog_testbench_options.cpp index f1ef64daa..0214fe6e5 100644 --- a/openfpga/src/fpga_verilog/verilog_testbench_options.cpp +++ b/openfpga/src/fpga_verilog/verilog_testbench_options.cpp @@ -13,7 +13,7 @@ namespace openfpga { *************************************************/ VerilogTestbenchOption::VerilogTestbenchOption() { output_directory_.clear(); - reference_verilog_file_path_.clear(); + reference_benchmark_file_path_.clear(); print_preconfig_top_testbench_ = false; print_formal_verification_top_netlist_ = false; print_top_testbench_ = false; @@ -28,8 +28,8 @@ std::string VerilogTestbenchOption::output_directory() const { return output_directory_; } -std::string VerilogTestbenchOption::reference_verilog_file_path() const { - return reference_verilog_file_path_; +std::string VerilogTestbenchOption::reference_benchmark_file_path() const { + return reference_benchmark_file_path_; } bool VerilogTestbenchOption::print_formal_verification_top_netlist() const { @@ -63,8 +63,8 @@ void VerilogTestbenchOption::set_output_directory(const std::string& output_dir) output_directory_ = output_dir; } -void VerilogTestbenchOption::set_reference_verilog_file_path(const std::string& reference_verilog_file_path) { - reference_verilog_file_path_ = reference_verilog_file_path; +void VerilogTestbenchOption::set_reference_benchmark_file_path(const std::string& reference_benchmark_file_path) { + reference_benchmark_file_path_ = reference_benchmark_file_path; /* Chain effect on other options: * Enable/disable the print_preconfig_top_testbench and print_top_testbench */ @@ -78,12 +78,15 @@ void VerilogTestbenchOption::set_print_formal_verification_top_netlist(const boo void VerilogTestbenchOption::set_print_preconfig_top_testbench(const bool& enabled) { print_preconfig_top_testbench_ = enabled - && print_formal_verification_top_netlist_ - && (!reference_verilog_file_path_.empty()); + && (!reference_benchmark_file_path_.empty()); + /* Enable print formal verification top_netlist if this is enabled */ + if (true == print_preconfig_top_testbench_) { + print_formal_verification_top_netlist_ = true; + } } void VerilogTestbenchOption::set_print_top_testbench(const bool& enabled) { - print_top_testbench_ = enabled && (!reference_verilog_file_path_.empty()); + print_top_testbench_ = enabled && (!reference_benchmark_file_path_.empty()); } void VerilogTestbenchOption::set_print_simulation_ini(const std::string& simulation_ini_path) { diff --git a/openfpga/src/fpga_verilog/verilog_testbench_options.h b/openfpga/src/fpga_verilog/verilog_testbench_options.h index d393b11c8..d18bfa224 100644 --- a/openfpga/src/fpga_verilog/verilog_testbench_options.h +++ b/openfpga/src/fpga_verilog/verilog_testbench_options.h @@ -23,7 +23,7 @@ class VerilogTestbenchOption { VerilogTestbenchOption(); public: /* Public accessors */ std::string output_directory() const; - std::string reference_verilog_file_path() const; + std::string reference_benchmark_file_path() const; bool print_formal_verification_top_netlist() const; bool print_preconfig_top_testbench() const; bool print_top_testbench() const; @@ -39,7 +39,7 @@ class VerilogTestbenchOption { * - print_top_testbench * If the file path is empty, the above testbench generation will not be enabled */ - void set_reference_verilog_file_path(const std::string& reference_verilog_file_path); + void set_reference_benchmark_file_path(const std::string& reference_benchmark_file_path); void set_print_formal_verification_top_netlist(const bool& enabled); /* The preconfig top testbench generation can be enabled only when formal verification top netlist is enabled */ void set_print_preconfig_top_testbench(const bool& enabled); @@ -48,7 +48,7 @@ class VerilogTestbenchOption { void set_verbose_output(const bool& enabled); private: /* Internal Data */ std::string output_directory_; - std::string reference_verilog_file_path_; + std::string reference_benchmark_file_path_; bool print_formal_verification_top_netlist_; bool print_preconfig_top_testbench_; bool print_top_testbench_; diff --git a/openfpga/test_script/s298_k6_frac.openfpga b/openfpga/test_script/s298_k6_frac.openfpga index 0b2e3cc13..eb09da42e 100644 --- a/openfpga/test_script/s298_k6_frac.openfpga +++ b/openfpga/test_script/s298_k6_frac.openfpga @@ -33,9 +33,12 @@ repack #--verbose # - Output the fabric-independent bitstream to a file fpga_bitstream --verbose --file /var/tmp/xtang/openfpga_test_src/fabric_indepenent_bitstream.xml -# Write the Verilog netlit for FPGA fabric +# Write the Verilog netlist for FPGA fabric # - Enable the use of explicit port mapping in Verilog netlist write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose +# Write the Verilog testbench for FPGA fabric +write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src --reference_benchmark_file_path /var/tmp/xtang/s298.v --print_top_testbench --print_preconfig_top_testbench + # Finish and exit OpenFPGA exit From b010fc1983c1686ba0885e9b1bcef9ed37389c14 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 27 Feb 2020 13:28:21 -0700 Subject: [PATCH 220/645] add warning to force formal_verification_top_netlist enabled --- openfpga/src/fpga_verilog/verilog_testbench_options.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openfpga/src/fpga_verilog/verilog_testbench_options.cpp b/openfpga/src/fpga_verilog/verilog_testbench_options.cpp index 0214fe6e5..8cd2969f2 100644 --- a/openfpga/src/fpga_verilog/verilog_testbench_options.cpp +++ b/openfpga/src/fpga_verilog/verilog_testbench_options.cpp @@ -2,6 +2,7 @@ * Memember functions for data structure VerilogTestbenchOption ******************************************************************************/ #include "vtr_assert.h" +#include "vtr_log.h" #include "verilog_testbench_options.h" @@ -81,7 +82,10 @@ void VerilogTestbenchOption::set_print_preconfig_top_testbench(const bool& enabl && (!reference_benchmark_file_path_.empty()); /* Enable print formal verification top_netlist if this is enabled */ if (true == print_preconfig_top_testbench_) { - print_formal_verification_top_netlist_ = true; + if (false == print_formal_verification_top_netlist_) { + VTR_LOG_WARN("Forcely enable to print top-level Verilog netlist in formal verification purpose as print pre-configured top-level Verilog testbench is enabled\n"); + print_formal_verification_top_netlist_ = true; + } } } From 9b769cd8e4c839aee6f4e67e9a5bd41ca496a8e2 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 27 Feb 2020 16:37:20 -0700 Subject: [PATCH 221/645] bug fix for using renamed i/o names --- openfpga/src/base/openfpga_verilog.cpp | 1 + openfpga/src/fpga_verilog/verilog_api.cpp | 4 + openfpga/src/fpga_verilog/verilog_api.h | 2 + .../verilog_formal_random_top_testbench.cpp | 32 +++-- .../verilog_formal_random_top_testbench.h | 1 + .../verilog_preconfig_top_module.cpp | 18 ++- .../verilog_preconfig_top_module.h | 2 + .../fpga_verilog/verilog_testbench_utils.cpp | 118 ++++++++++++++---- .../fpga_verilog/verilog_testbench_utils.h | 6 + .../fpga_verilog/verilog_top_testbench.cpp | 23 ++-- .../src/fpga_verilog/verilog_top_testbench.h | 2 + 11 files changed, 163 insertions(+), 46 deletions(-) diff --git a/openfpga/src/base/openfpga_verilog.cpp b/openfpga/src/base/openfpga_verilog.cpp index 41057ad34..1ce23a1d4 100644 --- a/openfpga/src/base/openfpga_verilog.cpp +++ b/openfpga/src/base/openfpga_verilog.cpp @@ -82,6 +82,7 @@ void write_verilog_testbench(OpenfpgaContext& openfpga_ctx, g_vpr_ctx.atom(), g_vpr_ctx.placement(), openfpga_ctx.io_location_map(), + openfpga_ctx.vpr_netlist_annotation(), openfpga_ctx.arch().circuit_lib, openfpga_ctx.arch().sim_setting, openfpga_ctx.arch().config_protocol.type(), diff --git a/openfpga/src/fpga_verilog/verilog_api.cpp b/openfpga/src/fpga_verilog/verilog_api.cpp index ed4b28982..286188a75 100644 --- a/openfpga/src/fpga_verilog/verilog_api.cpp +++ b/openfpga/src/fpga_verilog/verilog_api.cpp @@ -139,6 +139,7 @@ void fpga_verilog_testbench(const ModuleManager& module_manager, const AtomContext& atom_ctx, const PlacementContext& place_ctx, const IoLocationMap& io_location_map, + const VprNetlistAnnotation& netlist_annotation, const CircuitLibrary& circuit_lib, const SimulationSetting& simulation_setting, const e_config_protocol_type& config_protocol_type, @@ -169,6 +170,7 @@ void fpga_verilog_testbench(const ModuleManager& module_manager, print_verilog_preconfig_top_module(module_manager, bitstream_manager, circuit_lib, global_ports, atom_ctx, place_ctx, io_location_map, + netlist_annotation, netlist_name, formal_verification_top_netlist_file_path, src_dir_path); @@ -182,6 +184,7 @@ void fpga_verilog_testbench(const ModuleManager& module_manager, random_top_testbench_file_path, src_dir_path, atom_ctx, + netlist_annotation, simulation_setting); } @@ -194,6 +197,7 @@ void fpga_verilog_testbench(const ModuleManager& module_manager, config_protocol_type, circuit_lib, global_ports, atom_ctx, place_ctx, io_location_map, + netlist_annotation, netlist_name, top_testbench_file_path, src_dir_path, diff --git a/openfpga/src/fpga_verilog/verilog_api.h b/openfpga/src/fpga_verilog/verilog_api.h index 587987425..66eb069ba 100644 --- a/openfpga/src/fpga_verilog/verilog_api.h +++ b/openfpga/src/fpga_verilog/verilog_api.h @@ -16,6 +16,7 @@ #include "bitstream_manager.h" #include "simulation_setting.h" #include "io_location_map.h" +#include "vpr_netlist_annotation.h" #include "fabric_verilog_options.h" #include "verilog_testbench_options.h" @@ -40,6 +41,7 @@ void fpga_verilog_testbench(const ModuleManager& module_manager, const AtomContext& atom_ctx, const PlacementContext& place_ctx, const IoLocationMap& io_location_map, + const VprNetlistAnnotation& netlist_annotation, const CircuitLibrary& circuit_lib, const SimulationSetting& simulation_parameters, const e_config_protocol_type& config_protocol_type, diff --git a/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.cpp b/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.cpp index 71f7df873..2154a8513 100644 --- a/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.cpp +++ b/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.cpp @@ -55,7 +55,8 @@ static void print_verilog_top_random_testbench_ports(std::fstream& fp, const std::string& circuit_name, const std::vector& clock_port_names, - const AtomContext& atom_ctx) { + const AtomContext& atom_ctx, + const VprNetlistAnnotation& netlist_annotation) { /* Validate the file stream */ valid_file_stream(fp); @@ -72,7 +73,7 @@ void print_verilog_top_random_testbench_ports(std::fstream& fp, /* Add an empty line as splitter */ fp << std::endl; - print_verilog_testbench_shared_ports(fp, atom_ctx, + print_verilog_testbench_shared_ports(fp, atom_ctx, netlist_annotation, std::string(BENCHMARK_PORT_POSTFIX), std::string(FPGA_PORT_POSTFIX), std::string(CHECKFLAG_PORT_POSTFIX), @@ -94,7 +95,8 @@ void print_verilog_top_random_testbench_ports(std::fstream& fp, static void print_verilog_top_random_testbench_benchmark_instance(std::fstream& fp, const std::string& reference_verilog_top_name, - const AtomContext& atom_ctx) { + const AtomContext& atom_ctx, + const VprNetlistAnnotation& netlist_annotation) { /* Validate the file stream */ valid_file_stream(fp); @@ -111,7 +113,7 @@ void print_verilog_top_random_testbench_benchmark_instance(std::fstream& fp, std::string(), std::string(), std::string(BENCHMARK_PORT_POSTFIX), - atom_ctx, + atom_ctx, netlist_annotation, false); print_verilog_comment(fp, std::string("----- End reference Benchmark Instanication -------")); @@ -132,7 +134,8 @@ void print_verilog_top_random_testbench_benchmark_instance(std::fstream& fp, static void print_verilog_random_testbench_fpga_instance(std::fstream& fp, const std::string& circuit_name, - const AtomContext& atom_ctx) { + const AtomContext& atom_ctx, + const VprNetlistAnnotation& netlist_annotation) { /* Validate the file stream */ valid_file_stream(fp); @@ -144,7 +147,7 @@ void print_verilog_random_testbench_fpga_instance(std::fstream& fp, std::string(FORMAL_VERIFICATION_TOP_MODULE_PORT_POSTFIX), std::string(FORMAL_VERIFICATION_TOP_MODULE_PORT_POSTFIX), std::string(FPGA_PORT_POSTFIX), - atom_ctx, + atom_ctx, netlist_annotation, true); print_verilog_comment(fp, std::string("----- End FPGA Fabric Instanication -------")); @@ -182,6 +185,7 @@ void print_verilog_random_top_testbench(const std::string& circuit_name, const std::string& verilog_fname, const std::string& verilog_dir, const AtomContext& atom_ctx, + const VprNetlistAnnotation& netlist_annotation, const SimulationSetting& simulation_parameters) { std::string timer_message = std::string("Write configuration-skip testbench for FPGA top-level Verilog netlist implemented by '") + circuit_name.c_str() + std::string("'"); @@ -208,13 +212,13 @@ void print_verilog_random_top_testbench(const std::string& circuit_name, std::vector clock_port_names = find_atom_netlist_clock_port_names(atom_ctx.nlist); /* Start of testbench */ - print_verilog_top_random_testbench_ports(fp, circuit_name, clock_port_names, atom_ctx); + print_verilog_top_random_testbench_ports(fp, circuit_name, clock_port_names, atom_ctx, netlist_annotation); /* Call defined top-level module */ - print_verilog_random_testbench_fpga_instance(fp, circuit_name, atom_ctx); + print_verilog_random_testbench_fpga_instance(fp, circuit_name, atom_ctx, netlist_annotation); /* Call defined benchmark */ - print_verilog_top_random_testbench_benchmark_instance(fp, circuit_name, atom_ctx); + print_verilog_top_random_testbench_benchmark_instance(fp, circuit_name, atom_ctx, netlist_annotation); /* Find clock port to be used */ BasicPort clock_port = generate_verilog_testbench_clock_port(clock_port_names, std::string(DEFAULT_CLOCK_NAME)); @@ -222,8 +226,10 @@ void print_verilog_random_top_testbench(const std::string& circuit_name, /* Add stimuli for reset, set, clock and iopad signals */ print_verilog_testbench_clock_stimuli(fp, simulation_parameters, clock_port); - print_verilog_testbench_random_stimuli(fp, atom_ctx, - std::string(CHECKFLAG_PORT_POSTFIX), clock_port); + print_verilog_testbench_random_stimuli(fp, atom_ctx, + netlist_annotation, + std::string(CHECKFLAG_PORT_POSTFIX), + clock_port); print_verilog_testbench_check(fp, std::string(AUTOCHECKED_SIMULATION_FLAG), @@ -233,7 +239,9 @@ void print_verilog_random_top_testbench(const std::string& circuit_name, std::string(CHECKFLAG_PORT_POSTFIX), std::string(ERROR_COUNTER), atom_ctx, - clock_port_names, std::string(DEFAULT_CLOCK_NAME)); + netlist_annotation, + clock_port_names, + std::string(DEFAULT_CLOCK_NAME)); int simulation_time = find_operating_phase_simulation_time(MAGIC_NUMBER_FOR_SIMULATION_TIME, simulation_parameters.num_clock_cycles(), diff --git a/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.h b/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.h index f73b49fc1..88692ae4f 100644 --- a/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.h +++ b/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.h @@ -19,6 +19,7 @@ void print_verilog_random_top_testbench(const std::string& circuit_name, const std::string& verilog_fname, const std::string& verilog_dir, const AtomContext& atom_ctx, + const VprNetlistAnnotation& netlist_annotation, const SimulationSetting& simulation_parameters); } /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_preconfig_top_module.cpp b/openfpga/src/fpga_verilog/verilog_preconfig_top_module.cpp index e2bceb75d..000879230 100644 --- a/openfpga/src/fpga_verilog/verilog_preconfig_top_module.cpp +++ b/openfpga/src/fpga_verilog/verilog_preconfig_top_module.cpp @@ -34,7 +34,8 @@ namespace openfpga { static void print_verilog_preconfig_top_module_ports(std::fstream& fp, const std::string& circuit_name, - const AtomContext& atom_ctx) { + const AtomContext& atom_ctx, + const VprNetlistAnnotation& netlist_annotation) { /* Validate the file stream */ valid_file_stream(fp); @@ -58,11 +59,18 @@ void print_verilog_preconfig_top_module_ports(std::fstream& fp, && (AtomBlockType::OUTPAD != atom_ctx.nlist.block_type(atom_blk)) ) { continue; } + + /* The block may be renamed as it contains special characters which violate Verilog syntax */ + std::string block_name = atom_ctx.nlist.block_name(atom_blk); + if (true == netlist_annotation.is_block_renamed(atom_blk)) { + block_name = netlist_annotation.block_name(atom_blk); + } + if (0 < port_counter) { fp << "," << std::endl; } /* Both input and output ports have only size of 1 */ - BasicPort module_port(std::string(atom_ctx.nlist.block_name(atom_blk) + std::string(FORMAL_VERIFICATION_TOP_MODULE_PORT_POSTFIX)), 1); + BasicPort module_port(std::string(block_name + std::string(FORMAL_VERIFICATION_TOP_MODULE_PORT_POSTFIX)), 1); fp << generate_verilog_port(port_type2type_map[atom_ctx.nlist.block_type(atom_blk)], module_port); /* Update port counter */ @@ -375,6 +383,7 @@ void print_verilog_preconfig_top_module(const ModuleManager& module_manager, const AtomContext& atom_ctx, const PlacementContext& place_ctx, const IoLocationMap& io_location_map, + const VprNetlistAnnotation& netlist_annotation, const std::string& circuit_name, const std::string& verilog_fname, const std::string& verilog_dir) { @@ -400,7 +409,7 @@ void print_verilog_preconfig_top_module(const ModuleManager& module_manager, print_verilog_include_netlist(fp, std::string(verilog_dir + std::string(DEFINES_VERILOG_SIMULATION_FILE_NAME))); /* Print module declaration and ports */ - print_verilog_preconfig_top_module_ports(fp, circuit_name, atom_ctx); + print_verilog_preconfig_top_module_ports(fp, circuit_name, atom_ctx, netlist_annotation); /* Find the top_module */ ModuleId top_module = module_manager.find_module(generate_fpga_top_module_name()); @@ -423,7 +432,8 @@ void print_verilog_preconfig_top_module(const ModuleManager& module_manager, /* Connect I/Os to benchmark I/Os or constant driver */ print_verilog_testbench_connect_fpga_ios(fp, module_manager, top_module, - atom_ctx, place_ctx, io_location_map, + atom_ctx, place_ctx, io_location_map, + netlist_annotation, std::string(FORMAL_VERIFICATION_TOP_MODULE_PORT_POSTFIX), std::string(FORMAL_VERIFICATION_TOP_MODULE_PORT_POSTFIX), (size_t)VERILOG_DEFAULT_SIGNAL_INIT_VALUE); diff --git a/openfpga/src/fpga_verilog/verilog_preconfig_top_module.h b/openfpga/src/fpga_verilog/verilog_preconfig_top_module.h index 59f777a6c..499cafea9 100644 --- a/openfpga/src/fpga_verilog/verilog_preconfig_top_module.h +++ b/openfpga/src/fpga_verilog/verilog_preconfig_top_module.h @@ -11,6 +11,7 @@ #include "module_manager.h" #include "bitstream_manager.h" #include "io_location_map.h" +#include "vpr_netlist_annotation.h" /******************************************************************** * Function declaration @@ -26,6 +27,7 @@ void print_verilog_preconfig_top_module(const ModuleManager& module_manager, const AtomContext& atom_ctx, const PlacementContext& place_ctx, const IoLocationMap& io_location_map, + const VprNetlistAnnotation& netlist_annotation, const std::string& circuit_name, const std::string& verilog_fname, const std::string& verilog_dir); diff --git a/openfpga/src/fpga_verilog/verilog_testbench_utils.cpp b/openfpga/src/fpga_verilog/verilog_testbench_utils.cpp index e73fe5ee4..395d80493 100644 --- a/openfpga/src/fpga_verilog/verilog_testbench_utils.cpp +++ b/openfpga/src/fpga_verilog/verilog_testbench_utils.cpp @@ -58,6 +58,7 @@ void print_verilog_testbench_benchmark_instance(std::fstream& fp, const std::string& module_output_port_postfix, const std::string& output_port_postfix, const AtomContext& atom_ctx, + const VprNetlistAnnotation& netlist_annotation, const bool& use_explicit_port_map) { /* Validate the file stream */ valid_file_stream(fp); @@ -71,6 +72,13 @@ void print_verilog_testbench_benchmark_instance(std::fstream& fp, && (AtomBlockType::OUTPAD != atom_ctx.nlist.block_type(atom_blk)) ) { continue; } + + /* The block may be renamed as it contains special characters which violate Verilog syntax */ + std::string block_name = atom_ctx.nlist.block_name(atom_blk); + if (true == netlist_annotation.is_block_renamed(atom_blk)) { + block_name = netlist_annotation.block_name(atom_blk); + } + /* The first port does not need a comma */ if(0 < port_counter){ fp << "," << std::endl; @@ -79,9 +87,9 @@ void print_verilog_testbench_benchmark_instance(std::fstream& fp, if (AtomBlockType::INPAD == atom_ctx.nlist.block_type(atom_blk)) { fp << "\t\t"; if (true == use_explicit_port_map) { - fp << "." << atom_ctx.nlist.block_name(atom_blk) << module_input_port_postfix << "("; + fp << "." << block_name << module_input_port_postfix << "("; } - fp << atom_ctx.nlist.block_name(atom_blk); + fp << block_name; if (true == use_explicit_port_map) { fp << ")"; } @@ -89,9 +97,9 @@ void print_verilog_testbench_benchmark_instance(std::fstream& fp, VTR_ASSERT_SAFE(AtomBlockType::OUTPAD == atom_ctx.nlist.block_type(atom_blk)); fp << "\t\t"; if (true == use_explicit_port_map) { - fp << "." << atom_ctx.nlist.block_name(atom_blk) << module_output_port_postfix << "("; + fp << "." << block_name << module_output_port_postfix << "("; } - fp << atom_ctx.nlist.block_name(atom_blk) << output_port_postfix; + fp << block_name << output_port_postfix; if (true == use_explicit_port_map) { fp << ")"; } @@ -115,6 +123,7 @@ void print_verilog_testbench_connect_fpga_ios(std::fstream& fp, const AtomContext& atom_ctx, const PlacementContext& place_ctx, const IoLocationMap& io_location_map, + const VprNetlistAnnotation& netlist_annotation, const std::string& io_input_port_name_postfix, const std::string& io_output_port_name_postfix, const size_t& unused_io_value) { @@ -152,21 +161,27 @@ void print_verilog_testbench_connect_fpga_ios(std::fstream& fp, VTR_ASSERT(io_index < module_mapped_io_port.get_width()); module_mapped_io_port.set_width(io_index, io_index); + /* The block may be renamed as it contains special characters which violate Verilog syntax */ + std::string block_name = atom_ctx.nlist.block_name(atom_blk); + if (true == netlist_annotation.is_block_renamed(atom_blk)) { + block_name = netlist_annotation.block_name(atom_blk); + } + /* Create the port for benchmark I/O, due to BLIF benchmark, each I/O always has a size of 1 * In addition, the input and output ports may have different postfix in naming * due to verification context! Here, we give full customization on naming */ BasicPort benchmark_io_port; if (AtomBlockType::INPAD == atom_ctx.nlist.block_type(atom_blk)) { - benchmark_io_port.set_name(std::string(atom_ctx.nlist.block_name(atom_blk) + io_input_port_name_postfix)); + benchmark_io_port.set_name(std::string(block_name + io_input_port_name_postfix)); benchmark_io_port.set_width(1); - print_verilog_comment(fp, std::string("----- Blif Benchmark input " + atom_ctx.nlist.block_name(atom_blk) + " is mapped to FPGA IOPAD " + module_mapped_io_port.get_name() + "[" + std::to_string(io_index) + "] -----")); + print_verilog_comment(fp, std::string("----- Blif Benchmark input " + block_name + " is mapped to FPGA IOPAD " + module_mapped_io_port.get_name() + "[" + std::to_string(io_index) + "] -----")); print_verilog_wire_connection(fp, module_mapped_io_port, benchmark_io_port, false); } else { VTR_ASSERT(AtomBlockType::OUTPAD == atom_ctx.nlist.block_type(atom_blk)); - benchmark_io_port.set_name(std::string(atom_ctx.nlist.block_name(atom_blk) + io_output_port_name_postfix)); + benchmark_io_port.set_name(std::string(block_name + io_output_port_name_postfix)); benchmark_io_port.set_width(1); - print_verilog_comment(fp, std::string("----- Blif Benchmark output " + atom_ctx.nlist.block_name(atom_blk) + " is mapped to FPGA IOPAD " + module_mapped_io_port.get_name() + "[" + std::to_string(io_index) + "] -----")); + print_verilog_comment(fp, std::string("----- Blif Benchmark output " + block_name + " is mapped to FPGA IOPAD " + module_mapped_io_port.get_name() + "[" + std::to_string(io_index) + "] -----")); print_verilog_wire_connection(fp, benchmark_io_port, module_mapped_io_port, false); } @@ -281,6 +296,7 @@ void print_verilog_testbench_check(std::fstream& fp, const std::string& check_flag_port_postfix, const std::string& error_counter_name, const AtomContext& atom_ctx, + const VprNetlistAnnotation& netlist_annotation, const std::vector& clock_port_names, const std::string& default_clock_name) { @@ -314,14 +330,20 @@ void print_verilog_testbench_check(std::fstream& fp, continue; } + /* The block may be renamed as it contains special characters which violate Verilog syntax */ + std::string block_name = atom_ctx.nlist.block_name(atom_blk); + if (true == netlist_annotation.is_block_renamed(atom_blk)) { + block_name = netlist_annotation.block_name(atom_blk); + } + if (AtomBlockType::OUTPAD == atom_ctx.nlist.block_type(atom_blk)) { - fp << "\t\t\tif(!(" << atom_ctx.nlist.block_name(atom_blk) << fpga_port_postfix; - fp << " === " << atom_ctx.nlist.block_name(atom_blk) << benchmark_port_postfix; - fp << ") && !(" << atom_ctx.nlist.block_name(atom_blk) << benchmark_port_postfix; + fp << "\t\t\tif(!(" << block_name << fpga_port_postfix; + fp << " === " << block_name << benchmark_port_postfix; + fp << ") && !(" << block_name << benchmark_port_postfix; fp << " === 1'bx)) begin" << std::endl; - fp << "\t\t\t\t" << atom_ctx.nlist.block_name(atom_blk) << check_flag_port_postfix << " <= 1'b1;" << std::endl; + fp << "\t\t\t\t" << block_name << check_flag_port_postfix << " <= 1'b1;" << std::endl; fp << "\t\t\tend else begin" << std::endl; - fp << "\t\t\t\t" << atom_ctx.nlist.block_name(atom_blk) << check_flag_port_postfix << "<= 1'b0;" << std::endl; + fp << "\t\t\t\t" << block_name << check_flag_port_postfix << "<= 1'b0;" << std::endl; fp << "\t\t\tend" << std::endl; } } @@ -337,10 +359,16 @@ void print_verilog_testbench_check(std::fstream& fp, continue; } - fp << "\talways@(posedge " << atom_ctx.nlist.block_name(atom_blk) << check_flag_port_postfix << ") begin" << std::endl; - fp << "\t\tif(" << atom_ctx.nlist.block_name(atom_blk) << check_flag_port_postfix << ") begin" << std::endl; + /* The block may be renamed as it contains special characters which violate Verilog syntax */ + std::string block_name = atom_ctx.nlist.block_name(atom_blk); + if (true == netlist_annotation.is_block_renamed(atom_blk)) { + block_name = netlist_annotation.block_name(atom_blk); + } + + fp << "\talways@(posedge " << block_name << check_flag_port_postfix << ") begin" << std::endl; + fp << "\t\tif(" << block_name << check_flag_port_postfix << ") begin" << std::endl; fp << "\t\t\t" << error_counter_name << " = " << error_counter_name << " + 1;" << std::endl; - fp << "\t\t\t$display(\"Mismatch on " << atom_ctx.nlist.block_name(atom_blk) << fpga_port_postfix << " at time = " << std::string("%t") << "\", $realtime);" << std::endl; + fp << "\t\t\t$display(\"Mismatch on " << block_name << fpga_port_postfix << " at time = " << std::string("%t") << "\", $realtime);" << std::endl; fp << "\t\tend" << std::endl; fp << "\tend" << std::endl; @@ -393,6 +421,7 @@ void print_verilog_testbench_clock_stimuli(std::fstream& fp, *******************************************************************/ void print_verilog_testbench_random_stimuli(std::fstream& fp, const AtomContext& atom_ctx, + const VprNetlistAnnotation& netlist_annotation, const std::string& check_flag_port_postfix, const BasicPort& clock_port) { /* Validate the file stream */ @@ -409,9 +438,15 @@ void print_verilog_testbench_random_stimuli(std::fstream& fp, continue; } + /* The block may be renamed as it contains special characters which violate Verilog syntax */ + std::string block_name = atom_ctx.nlist.block_name(atom_blk); + if (true == netlist_annotation.is_block_renamed(atom_blk)) { + block_name = netlist_annotation.block_name(atom_blk); + } + /* TODO: find the clock inputs will be initialized later */ if (AtomBlockType::INPAD == atom_ctx.nlist.block_type(atom_blk)) { - fp << "\t\t" << atom_ctx.nlist.block_name(atom_blk) << " <= 1'b0;" << std::endl; + fp << "\t\t" << block_name << " <= 1'b0;" << std::endl; } } @@ -425,8 +460,14 @@ void print_verilog_testbench_random_stimuli(std::fstream& fp, continue; } + /* The block may be renamed as it contains special characters which violate Verilog syntax */ + std::string block_name = atom_ctx.nlist.block_name(atom_blk); + if (true == netlist_annotation.is_block_renamed(atom_blk)) { + block_name = netlist_annotation.block_name(atom_blk); + } + /* Each logical block assumes a single-width port */ - BasicPort output_port(std::string(atom_ctx.nlist.block_name(atom_blk) + check_flag_port_postfix), 1); + BasicPort output_port(std::string(block_name + check_flag_port_postfix), 1); fp << "\t\t" << generate_verilog_port(VERILOG_PORT_CONKT, output_port) << " <= 1'b0;" << std::endl; } @@ -463,9 +504,15 @@ void print_verilog_testbench_random_stimuli(std::fstream& fp, continue; } + /* The block may be renamed as it contains special characters which violate Verilog syntax */ + std::string block_name = atom_ctx.nlist.block_name(atom_blk); + if (true == netlist_annotation.is_block_renamed(atom_blk)) { + block_name = netlist_annotation.block_name(atom_blk); + } + /* TODO: find the clock inputs will be initialized later */ if (AtomBlockType::INPAD != atom_ctx.nlist.block_type(atom_blk)) { - fp << "\t\t" << atom_ctx.nlist.block_name(atom_blk) << " <= $random;" << std::endl; + fp << "\t\t" << block_name << " <= $random;" << std::endl; } } @@ -486,6 +533,7 @@ void print_verilog_testbench_random_stimuli(std::fstream& fp, *******************************************************************/ void print_verilog_testbench_shared_ports(std::fstream& fp, const AtomContext& atom_ctx, + const VprNetlistAnnotation& netlist_annotation, const std::string& benchmark_output_port_postfix, const std::string& fpga_output_port_postfix, const std::string& check_flag_port_postfix, @@ -501,10 +549,16 @@ void print_verilog_testbench_shared_ports(std::fstream& fp, continue; } + /* The block may be renamed as it contains special characters which violate Verilog syntax */ + std::string block_name = atom_ctx.nlist.block_name(atom_blk); + if (true == netlist_annotation.is_block_renamed(atom_blk)) { + block_name = netlist_annotation.block_name(atom_blk); + } + /* TODO: Skip clocks because they are handled in another function */ /* Each logical block assumes a single-width port */ - BasicPort input_port(atom_ctx.nlist.block_name(atom_blk), 1); + BasicPort input_port(block_name, 1); fp << "\t" << generate_verilog_port(VERILOG_PORT_REG, input_port) << ";" << std::endl; } @@ -520,8 +574,14 @@ void print_verilog_testbench_shared_ports(std::fstream& fp, continue; } + /* The block may be renamed as it contains special characters which violate Verilog syntax */ + std::string block_name = atom_ctx.nlist.block_name(atom_blk); + if (true == netlist_annotation.is_block_renamed(atom_blk)) { + block_name = netlist_annotation.block_name(atom_blk); + } + /* Each logical block assumes a single-width port */ - BasicPort output_port(std::string(atom_ctx.nlist.block_name(atom_blk) + fpga_output_port_postfix), 1); + BasicPort output_port(std::string(block_name + fpga_output_port_postfix), 1); fp << "\t" << generate_verilog_port(VERILOG_PORT_WIRE, output_port) << ";" << std::endl; } @@ -542,8 +602,14 @@ void print_verilog_testbench_shared_ports(std::fstream& fp, continue; } + /* The block may be renamed as it contains special characters which violate Verilog syntax */ + std::string block_name = atom_ctx.nlist.block_name(atom_blk); + if (true == netlist_annotation.is_block_renamed(atom_blk)) { + block_name = netlist_annotation.block_name(atom_blk); + } + /* Each logical block assumes a single-width port */ - BasicPort output_port(std::string(atom_ctx.nlist.block_name(atom_blk) + benchmark_output_port_postfix), 1); + BasicPort output_port(std::string(block_name + benchmark_output_port_postfix), 1); fp << "\t" << generate_verilog_port(VERILOG_PORT_WIRE, output_port) << ";" << std::endl; } @@ -558,8 +624,14 @@ void print_verilog_testbench_shared_ports(std::fstream& fp, continue; } + /* The block may be renamed as it contains special characters which violate Verilog syntax */ + std::string block_name = atom_ctx.nlist.block_name(atom_blk); + if (true == netlist_annotation.is_block_renamed(atom_blk)) { + block_name = netlist_annotation.block_name(atom_blk); + } + /* Each logical block assumes a single-width port */ - BasicPort output_port(std::string(atom_ctx.nlist.block_name(atom_blk) + check_flag_port_postfix), 1); + BasicPort output_port(std::string(block_name + check_flag_port_postfix), 1); fp << "\t" << generate_verilog_port(VERILOG_PORT_REG, output_port) << ";" << std::endl; } diff --git a/openfpga/src/fpga_verilog/verilog_testbench_utils.h b/openfpga/src/fpga_verilog/verilog_testbench_utils.h index 7de04deeb..e9a481380 100644 --- a/openfpga/src/fpga_verilog/verilog_testbench_utils.h +++ b/openfpga/src/fpga_verilog/verilog_testbench_utils.h @@ -10,6 +10,7 @@ #include "module_manager.h" #include "vpr_context.h" #include "io_location_map.h" +#include "vpr_netlist_annotation.h" #include "simulation_setting.h" /******************************************************************** @@ -31,6 +32,7 @@ void print_verilog_testbench_benchmark_instance(std::fstream& fp, const std::string& module_output_port_postfix, const std::string& output_port_postfix, const AtomContext& atom_ctx, + const VprNetlistAnnotation& netlist_annotation, const bool& use_explicit_port_map); void print_verilog_testbench_connect_fpga_ios(std::fstream& fp, @@ -39,6 +41,7 @@ void print_verilog_testbench_connect_fpga_ios(std::fstream& fp, const AtomContext& atom_ctx, const PlacementContext& place_ctx, const IoLocationMap& io_location_map, + const VprNetlistAnnotation& netlist_annotation, const std::string& io_input_port_name_postfix, const std::string& io_output_port_name_postfix, const size_t& unused_io_value); @@ -62,6 +65,7 @@ void print_verilog_testbench_check(std::fstream& fp, const std::string& check_flag_port_postfix, const std::string& error_counter_name, const AtomContext& atom_ctx, + const VprNetlistAnnotation& netlist_annotation, const std::vector& clock_port_names, const std::string& default_clock_name); @@ -71,11 +75,13 @@ void print_verilog_testbench_clock_stimuli(std::fstream& fp, void print_verilog_testbench_random_stimuli(std::fstream& fp, const AtomContext& atom_ctx, + const VprNetlistAnnotation& netlist_annotation, const std::string& check_flag_port_postfix, const BasicPort& clock_port); void print_verilog_testbench_shared_ports(std::fstream& fp, const AtomContext& atom_ctx, + const VprNetlistAnnotation& netlist_annotation, const std::string& benchmark_output_port_postfix, const std::string& fpga_output_port_postfix, const std::string& check_flag_port_postfix, diff --git a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp index 6159d1751..76bc1ea49 100644 --- a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp +++ b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp @@ -307,6 +307,7 @@ void print_verilog_top_testbench_ports(std::fstream& fp, const ModuleManager& module_manager, const ModuleId& top_module, const AtomContext& atom_ctx, + const VprNetlistAnnotation& netlist_annotation, const std::vector& clock_port_names, const e_config_protocol_type& sram_orgz_type, const std::string& circuit_name){ @@ -397,7 +398,7 @@ void print_verilog_top_testbench_ports(std::fstream& fp, print_verilog_wire_connection(fp, clock_port, op_clock_port, false); } - print_verilog_testbench_shared_ports(fp, atom_ctx, + print_verilog_testbench_shared_ports(fp, atom_ctx, netlist_annotation, std::string(TOP_TESTBENCH_REFERENCE_OUTPUT_POSTFIX), std::string(TOP_TESTBENCH_FPGA_OUTPUT_POSTFIX), std::string(TOP_TESTBENCH_CHECKFLAG_PORT_POSTFIX), @@ -416,7 +417,8 @@ void print_verilog_top_testbench_ports(std::fstream& fp, static void print_verilog_top_testbench_benchmark_instance(std::fstream& fp, const std::string& reference_verilog_top_name, - const AtomContext& atom_ctx) { + const AtomContext& atom_ctx, + const VprNetlistAnnotation& netlist_annotation) { /* Validate the file stream */ valid_file_stream(fp); @@ -433,7 +435,7 @@ void print_verilog_top_testbench_benchmark_instance(std::fstream& fp, std::string(), std::string(), std::string(TOP_TESTBENCH_REFERENCE_OUTPUT_POSTFIX), - atom_ctx, + atom_ctx, netlist_annotation, false); print_verilog_comment(fp, std::string("----- End reference Benchmark Instanication -------")); @@ -766,6 +768,7 @@ void print_verilog_top_testbench(const ModuleManager& module_manager, const AtomContext& atom_ctx, const PlacementContext& place_ctx, const IoLocationMap& io_location_map, + const VprNetlistAnnotation& netlist_annotation, const std::string& circuit_name, const std::string& verilog_fname, const std::string& verilog_dir, @@ -799,7 +802,7 @@ void print_verilog_top_testbench(const ModuleManager& module_manager, /* Start of testbench */ print_verilog_top_testbench_ports(fp, module_manager, top_module, - atom_ctx, clock_port_names, + atom_ctx, netlist_annotation, clock_port_names, sram_orgz_type, circuit_name); /* Find the clock period */ @@ -829,7 +832,8 @@ void print_verilog_top_testbench(const ModuleManager& module_manager, /* Connect I/Os to benchmark I/Os or constant driver */ print_verilog_testbench_connect_fpga_ios(fp, module_manager, top_module, - atom_ctx, place_ctx, io_location_map, + atom_ctx, place_ctx, io_location_map, + netlist_annotation, std::string(), std::string(TOP_TESTBENCH_FPGA_OUTPUT_POSTFIX), (size_t)VERILOG_DEFAULT_SIGNAL_INIT_VALUE); @@ -837,7 +841,8 @@ void print_verilog_top_testbench(const ModuleManager& module_manager, /* Instanciate input benchmark */ print_verilog_top_testbench_benchmark_instance(fp, circuit_name, - atom_ctx); + atom_ctx, + netlist_annotation); /* Print tasks used for loading bitstreams */ print_verilog_top_testbench_load_bitstream_task(fp, sram_orgz_type); @@ -848,6 +853,7 @@ void print_verilog_top_testbench(const ModuleManager& module_manager, /* Add stimuli for reset, set, clock and iopad signals */ print_verilog_testbench_random_stimuli(fp, atom_ctx, + netlist_annotation, std::string(TOP_TESTBENCH_CHECKFLAG_PORT_POSTFIX), BasicPort(std::string(TOP_TB_OP_CLOCK_PORT_NAME), 1)); @@ -859,7 +865,10 @@ void print_verilog_top_testbench(const ModuleManager& module_manager, std::string(TOP_TESTBENCH_FPGA_OUTPUT_POSTFIX), std::string(TOP_TESTBENCH_CHECKFLAG_PORT_POSTFIX), std::string(TOP_TESTBENCH_ERROR_COUNTER), - atom_ctx, clock_port_names, std::string(TOP_TB_OP_CLOCK_PORT_NAME)); + atom_ctx, + netlist_annotation, + clock_port_names, + std::string(TOP_TB_OP_CLOCK_PORT_NAME)); /* Find simulation time */ float simulation_time = find_simulation_time_period(VERILOG_SIM_TIMESCALE, diff --git a/openfpga/src/fpga_verilog/verilog_top_testbench.h b/openfpga/src/fpga_verilog/verilog_top_testbench.h index e2e652eb8..5684b37e2 100644 --- a/openfpga/src/fpga_verilog/verilog_top_testbench.h +++ b/openfpga/src/fpga_verilog/verilog_top_testbench.h @@ -11,6 +11,7 @@ #include "circuit_library.h" #include "vpr_context.h" #include "io_location_map.h" +#include "vpr_netlist_annotation.h" #include "simulation_setting.h" /******************************************************************** @@ -29,6 +30,7 @@ void print_verilog_top_testbench(const ModuleManager& module_manager, const AtomContext& atom_ctx, const PlacementContext& place_ctx, const IoLocationMap& io_location_map, + const VprNetlistAnnotation& netlist_annotation, const std::string& circuit_name, const std::string& verilog_fname, const std::string& verilog_dir, From ae899f3b11a3516146b3ae1a74953eb4261e4ee0 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 27 Feb 2020 16:51:55 -0700 Subject: [PATCH 222/645] bug fixed for clock names --- .../verilog_formal_random_top_testbench.cpp | 3 ++- .../src/fpga_verilog/verilog_preconfig_top_module.cpp | 2 +- openfpga/src/fpga_verilog/verilog_testbench_utils.cpp | 4 ++++ openfpga/src/fpga_verilog/verilog_testbench_utils.h | 1 + openfpga/src/fpga_verilog/verilog_top_testbench.cpp | 3 ++- openfpga/src/utils/openfpga_atom_netlist_utils.cpp | 11 +++++++++-- openfpga/src/utils/openfpga_atom_netlist_utils.h | 4 +++- .../SRC/fpga_x2p/verilog/verilog_testbench_utils.cpp | 1 - 8 files changed, 22 insertions(+), 7 deletions(-) diff --git a/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.cpp b/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.cpp index 2154a8513..4e2d129c5 100644 --- a/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.cpp +++ b/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.cpp @@ -74,6 +74,7 @@ void print_verilog_top_random_testbench_ports(std::fstream& fp, fp << std::endl; print_verilog_testbench_shared_ports(fp, atom_ctx, netlist_annotation, + clock_port_names, std::string(BENCHMARK_PORT_POSTFIX), std::string(FPGA_PORT_POSTFIX), std::string(CHECKFLAG_PORT_POSTFIX), @@ -209,7 +210,7 @@ void print_verilog_random_top_testbench(const std::string& circuit_name, print_verilog_include_netlist(fp, std::string(verilog_dir + std::string(DEFINES_VERILOG_SIMULATION_FILE_NAME))); /* Preparation: find all the clock ports */ - std::vector clock_port_names = find_atom_netlist_clock_port_names(atom_ctx.nlist); + std::vector clock_port_names = find_atom_netlist_clock_port_names(atom_ctx.nlist, netlist_annotation); /* Start of testbench */ print_verilog_top_random_testbench_ports(fp, circuit_name, clock_port_names, atom_ctx, netlist_annotation); diff --git a/openfpga/src/fpga_verilog/verilog_preconfig_top_module.cpp b/openfpga/src/fpga_verilog/verilog_preconfig_top_module.cpp index 000879230..695d9275f 100644 --- a/openfpga/src/fpga_verilog/verilog_preconfig_top_module.cpp +++ b/openfpga/src/fpga_verilog/verilog_preconfig_top_module.cpp @@ -423,7 +423,7 @@ void print_verilog_preconfig_top_module(const ModuleManager& module_manager, std::string(FORMAL_VERIFICATION_TOP_MODULE_UUT_NAME)); /* Find clock ports in benchmark */ - std::vector benchmark_clock_port_names = find_atom_netlist_clock_port_names(atom_ctx.nlist); + std::vector benchmark_clock_port_names = find_atom_netlist_clock_port_names(atom_ctx.nlist, netlist_annotation); /* Connect FPGA top module global ports to constant or benchmark global signals! */ print_verilog_preconfig_top_module_connect_global_ports(fp, module_manager, top_module, diff --git a/openfpga/src/fpga_verilog/verilog_testbench_utils.cpp b/openfpga/src/fpga_verilog/verilog_testbench_utils.cpp index 395d80493..8d3e856c7 100644 --- a/openfpga/src/fpga_verilog/verilog_testbench_utils.cpp +++ b/openfpga/src/fpga_verilog/verilog_testbench_utils.cpp @@ -534,6 +534,7 @@ void print_verilog_testbench_random_stimuli(std::fstream& fp, void print_verilog_testbench_shared_ports(std::fstream& fp, const AtomContext& atom_ctx, const VprNetlistAnnotation& netlist_annotation, + const std::vector& clock_port_names, const std::string& benchmark_output_port_postfix, const std::string& fpga_output_port_postfix, const std::string& check_flag_port_postfix, @@ -556,6 +557,9 @@ void print_verilog_testbench_shared_ports(std::fstream& fp, } /* TODO: Skip clocks because they are handled in another function */ + if (clock_port_names.end() != std::find(clock_port_names.begin(), clock_port_names.end(), block_name)) { + continue; + } /* Each logical block assumes a single-width port */ BasicPort input_port(block_name, 1); diff --git a/openfpga/src/fpga_verilog/verilog_testbench_utils.h b/openfpga/src/fpga_verilog/verilog_testbench_utils.h index e9a481380..61d5929c8 100644 --- a/openfpga/src/fpga_verilog/verilog_testbench_utils.h +++ b/openfpga/src/fpga_verilog/verilog_testbench_utils.h @@ -82,6 +82,7 @@ void print_verilog_testbench_random_stimuli(std::fstream& fp, void print_verilog_testbench_shared_ports(std::fstream& fp, const AtomContext& atom_ctx, const VprNetlistAnnotation& netlist_annotation, + const std::vector& clock_port_names, const std::string& benchmark_output_port_postfix, const std::string& fpga_output_port_postfix, const std::string& check_flag_port_postfix, diff --git a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp index 76bc1ea49..f64558dda 100644 --- a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp +++ b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp @@ -399,6 +399,7 @@ void print_verilog_top_testbench_ports(std::fstream& fp, } print_verilog_testbench_shared_ports(fp, atom_ctx, netlist_annotation, + clock_port_names, std::string(TOP_TESTBENCH_REFERENCE_OUTPUT_POSTFIX), std::string(TOP_TESTBENCH_FPGA_OUTPUT_POSTFIX), std::string(TOP_TESTBENCH_CHECKFLAG_PORT_POSTFIX), @@ -798,7 +799,7 @@ void print_verilog_top_testbench(const ModuleManager& module_manager, VTR_ASSERT(true == module_manager.valid_module_id(top_module)); /* Preparation: find all the clock ports */ - std::vector clock_port_names = find_atom_netlist_clock_port_names(atom_ctx.nlist); + std::vector clock_port_names = find_atom_netlist_clock_port_names(atom_ctx.nlist, netlist_annotation); /* Start of testbench */ print_verilog_top_testbench_ports(fp, module_manager, top_module, diff --git a/openfpga/src/utils/openfpga_atom_netlist_utils.cpp b/openfpga/src/utils/openfpga_atom_netlist_utils.cpp index d2786e88d..8d1876f90 100644 --- a/openfpga/src/utils/openfpga_atom_netlist_utils.cpp +++ b/openfpga/src/utils/openfpga_atom_netlist_utils.cpp @@ -18,14 +18,21 @@ namespace openfpga { /*************************************************************************************** * Find the names of all the atom blocks that drive clock nets + * This function will find if the block has been renamed due to contain sensitive characters + * that violates the Verilog syntax ***************************************************************************************/ -std::vector find_atom_netlist_clock_port_names(const AtomNetlist& atom_nlist) { +std::vector find_atom_netlist_clock_port_names(const AtomNetlist& atom_nlist, + const VprNetlistAnnotation& netlist_annotation) { std::vector clock_names; std::set clock_pins = find_netlist_logical_clock_drivers(atom_nlist); for (const AtomPinId& clock_pin : clock_pins) { const AtomBlockId& atom_blk = atom_nlist.port_block(atom_nlist.pin_port(clock_pin)); - clock_names.push_back(atom_nlist.block_name(atom_blk)); + std::string block_name = atom_nlist.block_name(atom_blk); + if (true == netlist_annotation.is_block_renamed(atom_blk)) { + block_name = netlist_annotation.block_name(atom_blk); + } + clock_names.push_back(block_name); } return clock_names; diff --git a/openfpga/src/utils/openfpga_atom_netlist_utils.h b/openfpga/src/utils/openfpga_atom_netlist_utils.h index 180181687..dfa5743a2 100644 --- a/openfpga/src/utils/openfpga_atom_netlist_utils.h +++ b/openfpga/src/utils/openfpga_atom_netlist_utils.h @@ -7,6 +7,7 @@ #include #include #include "atom_netlist.h" +#include "vpr_netlist_annotation.h" /******************************************************************** * Function declaration @@ -15,7 +16,8 @@ /* begin namespace openfpga */ namespace openfpga { -std::vector find_atom_netlist_clock_port_names(const AtomNetlist& atom_nlist); +std::vector find_atom_netlist_clock_port_names(const AtomNetlist& atom_nlist, + const VprNetlistAnnotation& netlist_annotation); } /* end namespace openfpga */ diff --git a/vpr7_x2p/vpr/SRC/fpga_x2p/verilog/verilog_testbench_utils.cpp b/vpr7_x2p/vpr/SRC/fpga_x2p/verilog/verilog_testbench_utils.cpp index ee2a80ea6..9293c7cf6 100644 --- a/vpr7_x2p/vpr/SRC/fpga_x2p/verilog/verilog_testbench_utils.cpp +++ b/vpr7_x2p/vpr/SRC/fpga_x2p/verilog/verilog_testbench_utils.cpp @@ -479,7 +479,6 @@ void print_verilog_testbench_shared_ports(std::fstream& fp, /* Validate the file stream */ check_file_handler(fp); - /* Instantiate register for inputs stimulis */ print_verilog_comment(fp, std::string("----- Shared inputs -------")); for (const t_logical_block& lb : L_logical_blocks) { From 65c81e14b2281ef2f366f9aa1379ed9baf2dd50e Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 27 Feb 2020 18:01:47 -0700 Subject: [PATCH 223/645] add simulation ini file writer --- openfpga/CMakeLists.txt | 1 + .../fpga_verilog/simulation_info_writer.cpp | 67 +++++++++++++++++++ .../src/fpga_verilog/simulation_info_writer.h | 26 +++++++ openfpga/src/fpga_verilog/verilog_api.cpp | 13 ++-- .../verilog_auxiliary_netlists.cpp | 1 - openfpga/src/fpga_verilog/verilog_constants.h | 3 + .../verilog_formal_random_top_testbench.cpp | 1 - openfpga/test_script/s298_k6_frac.openfpga | 2 +- 8 files changed, 103 insertions(+), 11 deletions(-) create mode 100644 openfpga/src/fpga_verilog/simulation_info_writer.cpp create mode 100644 openfpga/src/fpga_verilog/simulation_info_writer.h diff --git a/openfpga/CMakeLists.txt b/openfpga/CMakeLists.txt index 1e3ae7f13..1cdd56a32 100644 --- a/openfpga/CMakeLists.txt +++ b/openfpga/CMakeLists.txt @@ -22,6 +22,7 @@ target_link_libraries(libopenfpga libarchopenfpga libopenfpgashell libopenfpgautil + libini libvtrutil libvpr8) diff --git a/openfpga/src/fpga_verilog/simulation_info_writer.cpp b/openfpga/src/fpga_verilog/simulation_info_writer.cpp new file mode 100644 index 000000000..fbbb7e83c --- /dev/null +++ b/openfpga/src/fpga_verilog/simulation_info_writer.cpp @@ -0,0 +1,67 @@ +/********************************************************************* + * This function includes the writer for generating exchangeable + * information, in order to interface different simulators + ********************************************************************/ +#include +#include +#include +#define MINI_CASE_SENSITIVE +#include "ini.h" + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_time.h" + +#include "simulation_utils.h" + +#include "verilog_constants.h" +#include "simulation_info_writer.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/********************************************************************* + * Top-level function to write an ini file which contains exchangeable + * information, in order to interface different Verilog simulators + ********************************************************************/ +void print_verilog_simulation_info(const std::string& ini_fname, + const std::string& circuit_name, + const std::string& src_dir, + const size_t& num_program_clock_cycles, + const int& num_operating_clock_cycles, + const float& prog_clock_freq, + const float& op_clock_freq) { + + std::string timer_message = std::string("Write exchangeable file containing simulation information '") + ini_fname + std::string("'"); + + /* Start time count */ + vtr::ScopedStartFinishTimer timer(timer_message); + + /* Use default name if user does not provide one */ + VTR_ASSERT(true != ini_fname.empty()); + + mINI::INIStructure ini; + // std::map units_map; + // units_map['s']=1; // units_map['ms']=1E-3; // units_map['us']=1E-6; + // units_map['ns']=1E-9; // units_map['ps']=1E-12; // units_map['fs']=1E-15; + + /* Compute simulation time period */ + float simulation_time_period = find_simulation_time_period(1E-3, + num_program_clock_cycles, + 1. / prog_clock_freq, + num_operating_clock_cycles, + 1. / op_clock_freq); + ini["SIMULATION_DECK"]["PROJECTNAME "] = "ModelSimProject"; + ini["SIMULATION_DECK"]["BENCHMARK "] = circuit_name; + ini["SIMULATION_DECK"]["TOP_TB"] = circuit_name + std::string(FORMAL_RANDOM_TOP_TESTBENCH_POSTFIX); + ini["SIMULATION_DECK"]["SIMTIME "] = std::to_string(simulation_time_period); + ini["SIMULATION_DECK"]["UNIT "] = "ms"; + ini["SIMULATION_DECK"]["VERILOG_PATH "] = std::string(src_dir); + ini["SIMULATION_DECK"]["VERILOG_FILE1"] = std::string(DEFINES_VERILOG_FILE_NAME); + ini["SIMULATION_DECK"]["VERILOG_FILE2"] = std::string(circuit_name + std::string(TOP_INCLUDE_NETLIST_FILE_NAME_POSTFIX)); + + mINI::INIFile file(ini_fname); + file.generate(ini, true); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/simulation_info_writer.h b/openfpga/src/fpga_verilog/simulation_info_writer.h new file mode 100644 index 000000000..a9a8bdcb1 --- /dev/null +++ b/openfpga/src/fpga_verilog/simulation_info_writer.h @@ -0,0 +1,26 @@ +#ifndef SIMULATION_INFO_WRITER_H +#define SIMULATION_INFO_WRITER_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_verilog_simulation_info(const std::string& ini_fname, + const std::string& circuit_name, + const std::string& src_dir, + const size_t& num_program_clock_cycles, + const int& num_operating_clock_cycles, + const float& prog_clock_freq, + const float& op_clock_freq); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_verilog/verilog_api.cpp b/openfpga/src/fpga_verilog/verilog_api.cpp index 286188a75..dc332db58 100644 --- a/openfpga/src/fpga_verilog/verilog_api.cpp +++ b/openfpga/src/fpga_verilog/verilog_api.cpp @@ -23,6 +23,7 @@ #include "verilog_preconfig_top_module.h" #include "verilog_formal_random_top_testbench.h" #include "verilog_top_testbench.h" +#include "simulation_info_writer.h" /* Header file for this source file */ #include "verilog_api.h" @@ -204,22 +205,18 @@ void fpga_verilog_testbench(const ModuleManager& module_manager, simulation_setting); } - /* TODO: Generate exchangeable files which contains simulation settings + /* Generate exchangeable files which contains simulation settings */ if (true == options.print_simulation_ini()) { - std::string simulation_ini_file_name; - if (true != options.simulation_ini_path()) { - simulation_ini_file_name = options.simulation_ini_path(); - } + std::string simulation_ini_file_name = options.simulation_ini_path(); + VTR_ASSERT(true != options.simulation_ini_path().empty()); print_verilog_simulation_info(simulation_ini_file_name, - format_dir_path(chomped_parent_dir), netlist_name, src_dir_path, bitstream_manager.bits().size(), - simulation_setting.num_clock_cycle(), + simulation_setting.num_clock_cycles(), simulation_setting.programming_clock_frequency(), simulation_setting.operating_clock_frequency()); } - */ /* Generate a Verilog file including all the netlists that have been generated */ print_include_netlists(src_dir_path, diff --git a/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.cpp b/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.cpp index 20552c29f..9419f4172 100644 --- a/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.cpp +++ b/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.cpp @@ -23,7 +23,6 @@ namespace openfpga { /******************************************************************** * Local constant variables *******************************************************************/ -constexpr char* TOP_INCLUDE_NETLIST_FILE_NAME_POSTFIX = "_include_netlists.v"; /******************************************************************** * Print a file that includes all the netlists that have been generated diff --git a/openfpga/src/fpga_verilog/verilog_constants.h b/openfpga/src/fpga_verilog/verilog_constants.h index c8a01d375..797a00574 100644 --- a/openfpga/src/fpga_verilog/verilog_constants.h +++ b/openfpga/src/fpga_verilog/verilog_constants.h @@ -22,6 +22,7 @@ constexpr char* MODELSIM_SIMULATION_TIME_UNIT = "ms"; constexpr char* ICARUS_SIMULATOR_FLAG = "ICARUS_SIMULATOR"; // the flag to enable specific Verilog code in testbenches // End of Icarus variables and flag +constexpr char* TOP_INCLUDE_NETLIST_FILE_NAME_POSTFIX = "_include_netlists.v"; constexpr char* VERILOG_TOP_POSTFIX = "_top.v"; constexpr char* FORMAL_VERIFICATION_VERILOG_FILE_POSTFIX = "_top_formal_verification.v"; constexpr char* TOP_TESTBENCH_VERILOG_FILE_POSTFIX = "_top_tb.v"; /* !!! must be consist with the modelsim_testbench_module_postfix */ @@ -52,6 +53,8 @@ constexpr char* FORMAL_VERIFICATION_TOP_MODULE_POSTFIX = "_top_formal_verificati constexpr char* FORMAL_VERIFICATION_TOP_MODULE_PORT_POSTFIX = "_fm"; constexpr char* FORMAL_VERIFICATION_TOP_MODULE_UUT_NAME = "U0_formal_verification"; +constexpr char* FORMAL_RANDOM_TOP_TESTBENCH_POSTFIX = "_top_formal_verification_random_tb"; + #define VERILOG_DEFAULT_SIGNAL_INIT_VALUE 0 #endif diff --git a/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.cpp b/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.cpp index 4e2d129c5..7aef8a72f 100644 --- a/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.cpp +++ b/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.cpp @@ -31,7 +31,6 @@ namespace openfpga { /******************************************************************** * Local variables used only in this file *******************************************************************/ -constexpr char* FORMAL_RANDOM_TOP_TESTBENCH_POSTFIX = "_top_formal_verification_random_tb"; constexpr char* FPGA_PORT_POSTFIX = "_gfpga"; constexpr char* BENCHMARK_PORT_POSTFIX = "_bench"; constexpr char* CHECKFLAG_PORT_POSTFIX = "_flag"; diff --git a/openfpga/test_script/s298_k6_frac.openfpga b/openfpga/test_script/s298_k6_frac.openfpga index eb09da42e..d79fc3b9b 100644 --- a/openfpga/test_script/s298_k6_frac.openfpga +++ b/openfpga/test_script/s298_k6_frac.openfpga @@ -38,7 +38,7 @@ fpga_bitstream --verbose --file /var/tmp/xtang/openfpga_test_src/fabric_indepene write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose # Write the Verilog testbench for FPGA fabric -write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src --reference_benchmark_file_path /var/tmp/xtang/s298.v --print_top_testbench --print_preconfig_top_testbench +write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src --reference_benchmark_file_path /var/tmp/xtang/s298.v --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini /var/tmp/xtang/openfpga_test_src/simulation_deck.ini # Finish and exit OpenFPGA exit From 8322b1623d3e60709e147e73413e824a0e5b237e Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 27 Feb 2020 19:30:36 -0700 Subject: [PATCH 224/645] start porting SDC generator --- openfpga/src/fpga_sdc/sdc_option.cpp | 125 +++++++++++++++++++++++++++ openfpga/src/fpga_sdc/sdc_option.h | 53 ++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 openfpga/src/fpga_sdc/sdc_option.cpp create mode 100644 openfpga/src/fpga_sdc/sdc_option.h diff --git a/openfpga/src/fpga_sdc/sdc_option.cpp b/openfpga/src/fpga_sdc/sdc_option.cpp new file mode 100644 index 000000000..4b8c4666f --- /dev/null +++ b/openfpga/src/fpga_sdc/sdc_option.cpp @@ -0,0 +1,125 @@ +/******************************************************************** + * Member functions for a data structure which includes all the options for the SDC generator + ********************************************************************/ +#include "sdc_option.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Public Constructors + ********************************************************************/ +SdcOption::SdcOption(const std::string& sdc_dir) { + sdc_dir_ = sdc_dir; + constrain_global_port_ = false; + constrain_grid_ = false; + constrain_sb_ = false; + constrain_cb_ = false; + constrain_configurable_memory_outputs_ = false; + constrain_routing_multiplexer_outputs_ = false; + constrain_switch_block_outputs_ = false; +} + +/******************************************************************** + * Public accessors + ********************************************************************/ +std::string SdcOption::sdc_dir() const { + return sdc_dir_; +} + +bool SdcOption::generate_sdc() const { + return generate_sdc_pnr() && generate_sdc_analysis_; +} + +bool SdcOption::generate_sdc_pnr() const { + return constrain_global_port_ + || constrain_grid_ + || constrain_sb_ + || constrain_cb_ + || constrain_configurable_memory_outputs_ + || constrain_routing_multiplexer_outputs_ + || constrain_switch_block_outputs_; +} + +bool SdcOption::generate_sdc_analysis() const { + return generate_sdc_analysis_; +} + +bool SdcOption::constrain_global_port() const { + return constrain_global_port_; +} + +bool SdcOption::constrain_grid() const { + return constrain_grid_; +} + +bool SdcOption::constrain_sb() const { + return constrain_sb_; +} + +bool SdcOption::constrain_cb() const { + return constrain_cb_; +} + +bool SdcOption::constrain_configurable_memory_outputs() const { + return constrain_configurable_memory_outputs_; +} + +bool SdcOption::constrain_routing_multiplexer_outputs() const { + return constrain_routing_multiplexer_outputs_; +} + +bool SdcOption::constrain_switch_block_outputs() const { + return constrain_switch_block_outputs_; +} + +/******************************************************************** + * Public mutators + ********************************************************************/ +void SdcOption::set_sdc_dir(const std::string& sdc_dir) { + sdc_dir_ = sdc_dir; +} + +void SdcOption::set_generate_sdc_pnr(const bool& generate_sdc_pnr) { + constrain_global_port_ = generate_sdc_pnr; + constrain_grid_ = generate_sdc_pnr; + constrain_sb_ = generate_sdc_pnr; + constrain_cb_ = generate_sdc_pnr; + constrain_configurable_memory_outputs_ = generate_sdc_pnr; + constrain_routing_multiplexer_outputs_ = generate_sdc_pnr; + constrain_switch_block_outputs_ = generate_sdc_pnr; +} + +void SdcOption::set_generate_sdc_analysis(const bool& generate_sdc_analysis) { + generate_sdc_analysis_ = generate_sdc_analysis; +} + +void SdcOption::set_constrain_global_port(const bool& constrain_global_port) { + constrain_global_port_ = constrain_global_port; +} + +void SdcOption::set_constrain_grid(const bool& constrain_grid) { + constrain_grid_ = constrain_grid; +} + +void SdcOption::set_constrain_sb(const bool& constrain_sb) { + constrain_sb_ = constrain_sb; +} + +void SdcOption::set_constrain_cb(const bool& constrain_cb) { + constrain_cb_ = constrain_cb; +} + +void SdcOption::set_constrain_configurable_memory_outputs(const bool& constrain_config_mem_outputs) { + constrain_configurable_memory_outputs_ = constrain_config_mem_outputs; +} + +void SdcOption::set_constrain_routing_multiplexer_outputs(const bool& constrain_routing_mux_outputs) { + constrain_routing_multiplexer_outputs_ = constrain_routing_mux_outputs; +} + +void SdcOption::set_constrain_switch_block_outputs(const bool& constrain_sb_outputs) { + constrain_switch_block_outputs_ = constrain_sb_outputs; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/sdc_option.h b/openfpga/src/fpga_sdc/sdc_option.h new file mode 100644 index 000000000..d53c08e94 --- /dev/null +++ b/openfpga/src/fpga_sdc/sdc_option.h @@ -0,0 +1,53 @@ +#ifndef SDC_OPTION_H +#define SDC_OPTION_H + +/******************************************************************** + * A data structure to include all the options for the SDC generator + ********************************************************************/ + +#include + +/* begin namespace openfpga */ +namespace openfpga { + +class SdcOption { + public: /* Public Constructors */ + SdcOption(const std::string& sdc_dir); + public: /* Public accessors */ + std::string sdc_dir() const; + bool generate_sdc() const; + bool generate_sdc_pnr() const; + bool generate_sdc_analysis() const; + bool constrain_global_port() const; + bool constrain_grid() const; + bool constrain_sb() const; + bool constrain_cb() const; + bool constrain_configurable_memory_outputs() const; + bool constrain_routing_multiplexer_outputs() const; + bool constrain_switch_block_outputs() const; + public: /* Public mutators */ + void set_sdc_dir(const std::string& sdc_dir); + void set_generate_sdc_pnr(const bool& generate_sdc_pnr); + void set_generate_sdc_analysis(const bool& generate_sdc_analysis); + void set_constrain_global_port(const bool& constrain_global_port); + void set_constrain_grid(const bool& constrain_grid); + void set_constrain_sb(const bool& constrain_sb); + void set_constrain_cb(const bool& constrain_cb); + void set_constrain_configurable_memory_outputs(const bool& constrain_config_mem_outputs); + void set_constrain_routing_multiplexer_outputs(const bool& constrain_routing_mux_outputs); + void set_constrain_switch_block_outputs(const bool& constrain_sb_outputs); + private: /* Internal data */ + std::string sdc_dir_; + bool constrain_global_port_; + bool constrain_grid_; + bool constrain_sb_; + bool constrain_cb_; + bool constrain_configurable_memory_outputs_; + bool constrain_routing_multiplexer_outputs_; + bool constrain_switch_block_outputs_; + bool generate_sdc_analysis_; +}; + +} /* end namespace openfpga */ + +#endif From 78476ca7749c146f17950875aa04edf49d893188 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 27 Feb 2020 19:36:28 -0700 Subject: [PATCH 225/645] adapt sdc writer utils --- openfpga/src/fpga_sdc/sdc_writer_naming.h | 20 ++ openfpga/src/fpga_sdc/sdc_writer_utils.cpp | 202 +++++++++++++++++++++ openfpga/src/fpga_sdc/sdc_writer_utils.h | 62 +++++++ 3 files changed, 284 insertions(+) create mode 100644 openfpga/src/fpga_sdc/sdc_writer_naming.h create mode 100644 openfpga/src/fpga_sdc/sdc_writer_utils.cpp create mode 100644 openfpga/src/fpga_sdc/sdc_writer_utils.h diff --git a/openfpga/src/fpga_sdc/sdc_writer_naming.h b/openfpga/src/fpga_sdc/sdc_writer_naming.h new file mode 100644 index 000000000..9783e29f2 --- /dev/null +++ b/openfpga/src/fpga_sdc/sdc_writer_naming.h @@ -0,0 +1,20 @@ +#ifndef SDC_WRITER_NAMING_H +#define SDC_WRITER_NAMING_H + +/* begin namespace openfpga */ +namespace openfpga { + +constexpr char* SDC_FILE_NAME_POSTFIX = ".sdc"; + +constexpr char* SDC_GLOBAL_PORTS_FILE_NAME = "global_ports.sdc"; +constexpr char* SDC_BENCHMARK_ANALYSIS_FILE_NAME= "fpga_top_analysis.sdc"; +constexpr char* SDC_DISABLE_CONFIG_MEM_OUTPUTS_FILE_NAME = "disable_configurable_memory_outputs.sdc"; +constexpr char* SDC_DISABLE_MUX_OUTPUTS_FILE_NAME = "disable_routing_multiplexer_outputs.sdc"; +constexpr char* SDC_DISABLE_SB_OUTPUTS_FILE_NAME = "disable_sb_outputs.sdc"; +constexpr char* SDC_CB_FILE_NAME = "cb.sdc"; + +constexpr char* SDC_ANALYSIS_FILE_NAME = "fpga_top_analysis.sdc"; + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_sdc/sdc_writer_utils.cpp b/openfpga/src/fpga_sdc/sdc_writer_utils.cpp new file mode 100644 index 000000000..328f87ce6 --- /dev/null +++ b/openfpga/src/fpga_sdc/sdc_writer_utils.cpp @@ -0,0 +1,202 @@ +/******************************************************************** + * This file include most utilized functions to be used in SDC writers + *******************************************************************/ +#include +#include +#include + +/* Headers from openfpgautil library */ +#include "openfpga_digest.h" + +#include "sdc_writer_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Write a head (description) in SDC file + *******************************************************************/ +void print_sdc_file_header(std::fstream& fp, + const std::string& usage) { + + valid_file_stream(fp); + + auto end = std::chrono::system_clock::now(); + std::time_t end_time = std::chrono::system_clock::to_time_t(end); + + fp << "#############################################" << std::endl; + fp << "#\tSynopsys Design Constraints (SDC)" << std::endl; + fp << "#\tFor FPGA fabric " << std::endl; + fp << "#\tDescription: " << usage << std::endl; + fp << "#\tAuthor: Xifan TANG " << std::endl; + fp << "#\tOrganization: University of Utah " << std::endl; + fp << "#\tDate: " << std::ctime(&end_time); + fp << "#############################################" << std::endl; + fp << std::endl; +} + +/******************************************************************** + * Write a port in SDC format + *******************************************************************/ +std::string generate_sdc_port(const BasicPort& port) { + std::string sdc_line; + + std::string size_str = "[" + std::to_string(port.get_lsb()) + ":" + std::to_string(port.get_msb()) + "]"; + + /* Only connection require a format of [:] + * others require a format of [:] + */ + /* When LSB == MSB, we can use a simplified format []*/ + if ( 1 == port.get_width()) { + size_str = "[" + std::to_string(port.get_lsb()) + "]"; + } + sdc_line = port.get_name() + size_str; + + return sdc_line; +} + +/******************************************************************** + * Constrain a path between two ports of a module with a given maximum timing value + *******************************************************************/ +void print_pnr_sdc_constrain_max_delay(std::fstream& fp, + const std::string& src_instance_name, + const std::string& src_port_name, + const std::string& des_instance_name, + const std::string& des_port_name, + const float& delay) { + /* Validate file stream */ + valid_file_stream(fp); + + fp << "set_max_delay"; + + fp << " -from "; + if (!src_instance_name.empty()) { + fp << src_instance_name << "/"; + } + fp << src_port_name; + + fp << " -to "; + + if (!des_instance_name.empty()) { + fp << des_instance_name << "/"; + } + fp << des_port_name; + + fp << " " << std::setprecision(10) << delay; + + fp << std::endl; +} + +/******************************************************************** + * Constrain a path between two ports of a module with a given timing value + * Note: this function uses set_max_delay !!! + *******************************************************************/ +void print_pnr_sdc_constrain_module_port2port_timing(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& input_parent_module_id, + const ModulePortId& module_input_port_id, + const ModuleId& output_parent_module_id, + const ModulePortId& module_output_port_id, + const float& tmax) { + print_pnr_sdc_constrain_max_delay(fp, + module_manager.module_name(input_parent_module_id), + generate_sdc_port(module_manager.module_port(input_parent_module_id, module_input_port_id)), + module_manager.module_name(output_parent_module_id), + generate_sdc_port(module_manager.module_port(output_parent_module_id, module_output_port_id)), + tmax); + +} + +/******************************************************************** + * Constrain a path between two ports of a module with a given timing value + * This function will NOT output the module name + * Note: this function uses set_max_delay !!! + *******************************************************************/ +void print_pnr_sdc_constrain_port2port_timing(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& input_parent_module_id, + const ModulePortId& module_input_port_id, + const ModuleId& output_parent_module_id, + const ModulePortId& module_output_port_id, + const float& tmax) { + print_pnr_sdc_constrain_max_delay(fp, + std::string(), + generate_sdc_port(module_manager.module_port(input_parent_module_id, module_input_port_id)), + std::string(), + generate_sdc_port(module_manager.module_port(output_parent_module_id, module_output_port_id)), + tmax); + +} + +/******************************************************************** + * Disable timing for a port + *******************************************************************/ +void print_sdc_disable_port_timing(std::fstream& fp, + const BasicPort& port) { + /* Validate file stream */ + valid_file_stream(fp); + + fp << "set_disable_timing "; + + fp << generate_sdc_port(port); + + fp << std::endl; +} + +/******************************************************************** + * Set the input delay for a port in SDC format + * Note that the input delay will be bounded by a clock port + *******************************************************************/ +void print_sdc_set_port_input_delay(std::fstream& fp, + const BasicPort& port, + const BasicPort& clock_port, + const float& delay) { + /* Validate file stream */ + valid_file_stream(fp); + + fp << "set_input_delay "; + + fp << "-clock "; + + fp << generate_sdc_port(clock_port); + + fp << " -max "; + + fp << std::setprecision(10) << delay; + + fp << " "; + + fp << generate_sdc_port(port); + + fp << std::endl; +} + +/******************************************************************** + * Set the output delay for a port in SDC format + * Note that the output delay will be bounded by a clock port + *******************************************************************/ +void print_sdc_set_port_output_delay(std::fstream& fp, + const BasicPort& port, + const BasicPort& clock_port, + const float& delay) { + /* Validate file stream */ + valid_file_stream(fp); + + fp << "set_output_delay "; + + fp << "-clock "; + + fp << generate_sdc_port(clock_port); + + fp << " -max "; + + fp << std::setprecision(10) << delay; + + fp << " "; + + fp << generate_sdc_port(port); + + fp << std::endl; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/sdc_writer_utils.h b/openfpga/src/fpga_sdc/sdc_writer_utils.h new file mode 100644 index 000000000..4e0d86839 --- /dev/null +++ b/openfpga/src/fpga_sdc/sdc_writer_utils.h @@ -0,0 +1,62 @@ +#ifndef SDC_WRITER_UTILS_H +#define SDC_WRITER_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "openfpga_port.h" +#include "module_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_sdc_file_header(std::fstream& fp, + const std::string& usage); + +std::string generate_sdc_port(const BasicPort& port); + +void print_pnr_sdc_constrain_max_delay(std::fstream& fp, + const std::string& src_instance_name, + const std::string& src_port_name, + const std::string& des_instance_name, + const std::string& des_port_name, + const float& delay); + +void print_pnr_sdc_constrain_module_port2port_timing(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& input_parent_module_id, + const ModulePortId& module_input_port_id, + const ModuleId& output_parent_module_id, + const ModulePortId& module_output_port_id, + const float& tmax); + +void print_pnr_sdc_constrain_port2port_timing(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& input_parent_module_id, + const ModulePortId& module_input_port_id, + const ModuleId& output_parent_module_id, + const ModulePortId& module_output_port_id, + const float& tmax); + +void print_sdc_disable_port_timing(std::fstream& fp, + const BasicPort& port); + +void print_sdc_set_port_input_delay(std::fstream& fp, + const BasicPort& port, + const BasicPort& clock_port, + const float& delay); + +void print_sdc_set_port_output_delay(std::fstream& fp, + const BasicPort& port, + const BasicPort& clock_port, + const float& delay); + +} /* end namespace openfpga */ + +#endif From d136ac236f6f831b4f0167e6023389d856085a1a Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 27 Feb 2020 19:39:57 -0700 Subject: [PATCH 226/645] adapt sdc memory utils --- openfpga/src/fpga_sdc/sdc_memory_utils.cpp | 69 ++++++++++++++++++++++ openfpga/src/fpga_sdc/sdc_memory_utils.h | 25 ++++++++ 2 files changed, 94 insertions(+) create mode 100644 openfpga/src/fpga_sdc/sdc_memory_utils.cpp create mode 100644 openfpga/src/fpga_sdc/sdc_memory_utils.h diff --git a/openfpga/src/fpga_sdc/sdc_memory_utils.cpp b/openfpga/src/fpga_sdc/sdc_memory_utils.cpp new file mode 100644 index 000000000..1910b4197 --- /dev/null +++ b/openfpga/src/fpga_sdc/sdc_memory_utils.cpp @@ -0,0 +1,69 @@ +/******************************************************************** + * Most utilized function used to constrain memory cells in FPGA + * fabric using SDC commands + *******************************************************************/ + +/* Headers from openfpgautil library */ +#include "openfpga_digest.h" + +#include "sdc_writer_utils.h" + +#include "sdc_memory_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Print SDC commands to disable outputs of all the configurable memory modules + * in a given module + * This function will be executed in a recursive way, + * using a Depth-First Search (DFS) strategy + * It will iterate over all the configurable children under each module + * and print a SDC command to disable its outputs + *******************************************************************/ +void rec_print_pnr_sdc_disable_configurable_memory_module_output(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const std::string& parent_module_path) { + + /* For each configurable child, we will go one level down in priority */ + for (size_t child_index = 0; child_index < module_manager.configurable_children(parent_module).size(); ++child_index) { + std::string child_module_path = parent_module_path; + ModuleId child_module_id = module_manager.configurable_children(parent_module)[child_index]; + size_t child_instance_id = module_manager.configurable_child_instances(parent_module)[child_index]; + if (true == module_manager.instance_name(parent_module, child_module_id, child_instance_id).empty()) { + /* Give a default name __ */ + child_module_path += module_manager.module_name(child_module_id); + child_module_path += "_"; + child_module_path += std::to_string(child_instance_id); + child_module_path += "_"; + } else { + child_module_path += module_manager.instance_name(parent_module, child_module_id, child_instance_id); + } + child_module_path = format_dir_path(child_module_path); + + rec_print_pnr_sdc_disable_configurable_memory_module_output(fp, module_manager, + child_module_id, + child_module_path); + } + + /* If there is no configurable children any more, this is a leaf module, print a SDC command for disable timing */ + if (0 < module_manager.configurable_children(parent_module).size()) { + return; + } + + /* Validate file stream */ + valid_file_stream(fp); + + /* Disable timing for each output port of this module */ + for (const BasicPort& output_port : module_manager.module_ports_by_type(parent_module, ModuleManager::MODULE_OUTPUT_PORT)) { + for (const size_t& pin : output_port.pins()) { + BasicPort output_pin(output_port.get_name(), pin, pin); + fp << "set_disable_timing "; + fp << parent_module_path << generate_sdc_port(output_pin); + fp << std::endl; + } + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/sdc_memory_utils.h b/openfpga/src/fpga_sdc/sdc_memory_utils.h new file mode 100644 index 000000000..7fbf92294 --- /dev/null +++ b/openfpga/src/fpga_sdc/sdc_memory_utils.h @@ -0,0 +1,25 @@ +#ifndef SDC_MEMORY_UTILS_H +#define SDC_MEMORY_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "module_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void rec_print_pnr_sdc_disable_configurable_memory_module_output(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const std::string& parent_module_path); + +} /* end namespace openfpga */ + +#endif From b4ed931ac6903824fd1567006de0e7b508ae3a53 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 27 Feb 2020 20:35:56 -0700 Subject: [PATCH 227/645] adapt sdc routing writer --- .../src/fpga_sdc/pnr_sdc_routing_writer.cpp | 411 ++++++++++++++++++ .../src/fpga_sdc/pnr_sdc_routing_writer.h | 42 ++ 2 files changed, 453 insertions(+) create mode 100644 openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp create mode 100644 openfpga/src/fpga_sdc/pnr_sdc_routing_writer.h diff --git a/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp b/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp new file mode 100644 index 000000000..348ee3dac --- /dev/null +++ b/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp @@ -0,0 +1,411 @@ +/******************************************************************** + * This file includes functions that print SDC (Synopsys Design Constraint) + * files in physical design tools, i.e., Place & Route (PnR) tools + * The SDC files are used to constrain the physical design for each routing modules + * in FPGA fabric, such as Switch Blocks (SBs) and Connection Blocks (CBs) + * + * Note that this is different from the SDC to constrain VPR Place&Route + * engine! These SDCs are designed for PnR to generate FPGA layouts!!! + *******************************************************************/ +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_log.h" +#include "vtr_assert.h" +#include "vtr_time.h" + +/* Headers from openfpgautil library */ +#include "openfpga_port.h" +#include "openfpga_side_manager.h" +#include "openfpga_digest.h" + +#include "mux_utils.h" + +#include "openfpga_naming.h" + +#include "openfpga_rr_graph_utils.h" +#include "build_routing_module_utils.h" + +#include "sdc_writer_naming.h" +#include "sdc_writer_utils.h" +#include "pnr_sdc_routing_writer.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Find the timing constraints between the inputs and outputs of a routing + * multiplexer in a Switch Block + *******************************************************************/ +static +float find_pnr_sdc_switch_tmax(const t_rr_switch_inf& switch_inf) { + return switch_inf.R * switch_inf.Cout + switch_inf.Tdel; +} + +/******************************************************************** + * Set timing constraints between the inputs and outputs of a routing + * multiplexer in a Switch Block + *******************************************************************/ +static +void print_pnr_sdc_constrain_sb_mux_timing(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& sb_module, + const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const e_side& output_node_side, + const RRNodeId& output_rr_node) { + /* Validate file stream */ + valid_file_stream(fp); + + VTR_ASSERT( ( CHANX == rr_graph.node_type(output_rr_node) ) + || ( CHANY == rr_graph.node_type(output_rr_node) )); + + /* Find the module port corresponding to the output rr_node */ + ModulePortId module_output_port = find_switch_block_module_chan_port(module_manager, + sb_module, + rr_graph, + rr_gsb, + output_node_side, + output_rr_node, + OUT_PORT); + + /* Find the module port corresponding to the fan-in rr_nodes of the output rr_node */ + std::vector module_input_ports = find_switch_block_module_input_ports(module_manager, + sb_module, + rr_graph, + rr_gsb, + get_rr_graph_configurable_driver_nodes(rr_graph, output_rr_node)); + + /* Find timing constraints for each path (edge) */ + std::map switch_delays; + size_t edge_counter = 0; + for (const RREdgeId& edge : rr_graph.node_configurable_in_edges(output_rr_node)) { + /* Get the switch delay */ + const RRSwitchId& driver_switch = rr_graph.edge_switch(edge); + switch_delays[module_input_ports[edge_counter]] = find_pnr_sdc_switch_tmax(rr_graph.get_switch(driver_switch)); + edge_counter++; + } + + /* Find the starting points */ + for (const ModulePortId& module_input_port : module_input_ports) { + /* Constrain a path */ + print_pnr_sdc_constrain_port2port_timing(fp, + module_manager, + sb_module, module_input_port, + sb_module, module_output_port, + switch_delays[module_input_port]); + } +} + +/******************************************************************** + * Set timing constraints between the inputs and outputs of SBs, + * which are connected by routing multiplexers with the given delays + * specified in architectural XML file + * + * To enable block by block timing constraining, we generate the SDC + * file for each unique SB module + *******************************************************************/ +static +void print_pnr_sdc_constrain_sb_timing(const std::string& sdc_dir, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const RRGSB& rr_gsb) { + + /* Create the file name for Verilog netlist */ + vtr::Point gsb_coordinate(rr_gsb.get_sb_x(), rr_gsb.get_sb_y()); + std::string sdc_fname(sdc_dir + generate_switch_block_module_name(gsb_coordinate) + std::string(SDC_FILE_NAME_POSTFIX)); + + /* Create the file stream */ + std::fstream fp; + fp.open(sdc_fname, std::fstream::out | std::fstream::trunc); + + /* Validate file stream */ + check_file_stream(sdc_fname.c_str(), fp); + + std::string sb_module_name = generate_switch_block_module_name(gsb_coordinate); + ModuleId sb_module = module_manager.find_module(sb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(sb_module)); + + /* Generate the descriptions*/ + print_sdc_file_header(fp, std::string("Constrain timing of Switch Block " + sb_module_name + " for PnR")); + + for (size_t side = 0; side < rr_gsb.get_num_sides(); ++side) { + SideManager side_manager(side); + for (size_t itrack = 0; itrack < rr_gsb.get_chan_width(side_manager.get_side()); ++itrack) { + const RRNodeId& chan_rr_node = rr_gsb.get_chan_node(side_manager.get_side(), itrack); + /* We only care the output port and it should indicate a SB mux */ + if (OUT_PORT != rr_gsb.get_chan_node_direction(side_manager.get_side(), itrack)) { + continue; + } + /* Constrain thru wires */ + if (false != rr_gsb.is_sb_node_passing_wire(rr_graph, side_manager.get_side(), itrack)) { + continue; + } + /* This is a MUX, constrain all the paths from an input to an output */ + print_pnr_sdc_constrain_sb_mux_timing(fp, + module_manager, sb_module, + rr_graph, + rr_gsb, + side_manager.get_side(), + chan_rr_node); + } + } + + /* Close file handler */ + fp.close(); +} + +/******************************************************************** + * Print SDC timing constraints for Switch blocks + * This function is designed for flatten routing hierarchy + *******************************************************************/ +void print_pnr_sdc_flatten_routing_constrain_sb_timing(const std::string& sdc_dir, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const DeviceRRGSB& device_rr_gsb) { + + /* Start time count */ + vtr::ScopedStartFinishTimer timer("Generating SDC for constrain Switch Block timing for P&R flow"); + + /* Get the range of SB array */ + vtr::Point sb_range = device_rr_gsb.get_gsb_range(); + /* Go for each SB */ + for (size_t ix = 0; ix < sb_range.x(); ++ix) { + for (size_t iy = 0; iy < sb_range.y(); ++iy) { + const RRGSB& rr_gsb = device_rr_gsb.get_gsb(ix, iy); + if (false == rr_gsb.is_sb_exist()) { + continue; + } + print_pnr_sdc_constrain_sb_timing(sdc_dir, + module_manager, + rr_graph, + rr_gsb); + } + } +} + +/******************************************************************** + * Print SDC timing constraints for Switch blocks + * This function is designed for compact routing hierarchy + *******************************************************************/ +void print_pnr_sdc_compact_routing_constrain_sb_timing(const std::string& sdc_dir, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const DeviceRRGSB& device_rr_gsb) { + + /* Start time count */ + vtr::ScopedStartFinishTimer timer("Generating SDC for constrain Switch Block timing for P&R flow"); + + for (size_t isb = 0; isb < device_rr_gsb.get_num_sb_unique_module(); ++isb) { + const RRGSB& rr_gsb = device_rr_gsb.get_sb_unique_module(isb); + if (false == rr_gsb.is_sb_exist()) { + continue; + } + print_pnr_sdc_constrain_sb_timing(sdc_dir, + module_manager, + rr_graph, + rr_gsb); + } +} + +/******************************************************************** + * Set timing constraints between the inputs and outputs of a routing + * multiplexer in a Connection Block + *******************************************************************/ +static +void print_pnr_sdc_constrain_cb_mux_timing(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& cb_module, + const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const t_rr_type& cb_type, + const RRNodeId& output_rr_node) { + /* Validate file stream */ + valid_file_stream(fp); + + VTR_ASSERT(IPIN == rr_graph.node_type(output_rr_node)); + + /* We have OPINs since we may have direct connections: + * These connections should be handled by other functions in the compact_netlist.c + * So we just return here for OPINs + */ + if (0 == get_rr_graph_configurable_driver_nodes(rr_graph, output_rr_node).size()) { + return; + } + + /* Find the module port corresponding to the output rr_node */ + ModulePortId module_output_port = find_connection_block_module_ipin_port(module_manager, + cb_module, + rr_graph, + rr_gsb, + output_rr_node); + + /* Find the module port corresponding to the fan-in rr_nodes of the output rr_node */ + std::vector module_input_ports = find_connection_block_module_input_ports(module_manager, + cb_module, + rr_graph, + rr_gsb, + cb_type, + get_rr_graph_configurable_driver_nodes(rr_graph, output_rr_node)); + + /* Find timing constraints for each path (edge) */ + std::map switch_delays; + size_t edge_counter = 0; + for (const RREdgeId& edge : rr_graph.node_configurable_in_edges(output_rr_node)) { + /* Get the switch delay */ + const RRSwitchId& driver_switch = rr_graph.edge_switch(edge); + switch_delays[module_input_ports[edge_counter]] = find_pnr_sdc_switch_tmax(rr_graph.get_switch(driver_switch)); + edge_counter++; + } + + /* Find the starting points */ + for (const ModulePortId& module_input_port : module_input_ports) { + /* Constrain a path */ + print_pnr_sdc_constrain_port2port_timing(fp, + module_manager, + cb_module, module_input_port, + cb_module, module_output_port, + switch_delays[module_input_port]); + } +} + + +/******************************************************************** + * Print SDC timing constraints for a Connection block + * This function is designed for compact routing hierarchy + *******************************************************************/ +static +void print_pnr_sdc_constrain_cb_timing(const std::string& sdc_dir, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const t_rr_type& cb_type) { + /* Create the netlist */ + vtr::Point gsb_coordinate(rr_gsb.get_cb_x(cb_type), rr_gsb.get_cb_y(cb_type)); + + /* Find the module name and create a SDC file for it */ + std::string sdc_fname(sdc_dir + generate_connection_block_module_name(cb_type, gsb_coordinate) + std::string(SDC_FILE_NAME_POSTFIX)); + + /* Create the file stream */ + std::fstream fp; + fp.open(sdc_fname, std::fstream::out | std::fstream::trunc); + + /* Validate file stream */ + check_file_stream(sdc_fname.c_str(), fp); + + std::string cb_module_name = generate_connection_block_module_name(cb_type, gsb_coordinate); + ModuleId cb_module = module_manager.find_module(cb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(cb_module)); + + /* Generate the descriptions*/ + print_sdc_file_header(fp, std::string("Constrain timing of Connection Block " + cb_module_name + " for PnR")); + + std::vector cb_sides = rr_gsb.get_cb_ipin_sides(cb_type); + + for (size_t side = 0; side < cb_sides.size(); ++side) { + enum e_side cb_ipin_side = cb_sides[side]; + SideManager side_manager(cb_ipin_side); + for (size_t inode = 0; inode < rr_gsb.get_num_ipin_nodes(cb_ipin_side); ++inode) { + const RRNodeId& ipin_rr_node = rr_gsb.get_ipin_node(cb_ipin_side, inode); + print_pnr_sdc_constrain_cb_mux_timing(fp, + module_manager, cb_module, + rr_graph, rr_gsb, cb_type, + ipin_rr_node); + } + } + + /* Close file handler */ + fp.close(); +} + +/******************************************************************** + * Iterate over all the connection blocks in a device + * and print SDC file for each of them + *******************************************************************/ +static +void print_pnr_sdc_flatten_routing_constrain_cb_timing(const std::string& sdc_dir, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const DeviceRRGSB& device_rr_gsb, + const t_rr_type& cb_type) { + /* Build unique X-direction connection block modules */ + vtr::Point cb_range = device_rr_gsb.get_gsb_range(); + + for (size_t ix = 0; ix < cb_range.x(); ++ix) { + for (size_t iy = 0; iy < cb_range.y(); ++iy) { + /* Check if the connection block exists in the device! + * Some of them do NOT exist due to heterogeneous blocks (height > 1) + * We will skip those modules + */ + const RRGSB& rr_gsb = device_rr_gsb.get_gsb(ix, iy); + if (false == rr_gsb.is_cb_exist(cb_type)) { + continue; + } + print_pnr_sdc_constrain_cb_timing(sdc_dir, + module_manager, + rr_graph, + rr_gsb, + cb_type); + + } + } +} + +/******************************************************************** + * Iterate over all the connection blocks in a device + * and print SDC file for each of them + *******************************************************************/ +void print_pnr_sdc_flatten_routing_constrain_cb_timing(const std::string& sdc_dir, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const DeviceRRGSB& device_rr_gsb) { + + /* Start time count */ + vtr::ScopedStartFinishTimer timer("Generating SDC for constrain Connection Block timing for P&R flow"); + + print_pnr_sdc_flatten_routing_constrain_cb_timing(sdc_dir, module_manager, + rr_graph, + device_rr_gsb, + CHANX); + + print_pnr_sdc_flatten_routing_constrain_cb_timing(sdc_dir, module_manager, + rr_graph, + device_rr_gsb, + CHANY); +} + +/******************************************************************** + * Print SDC timing constraints for Connection blocks + * This function is designed for compact routing hierarchy + *******************************************************************/ +void print_pnr_sdc_compact_routing_constrain_cb_timing(const std::string& sdc_dir, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const DeviceRRGSB& device_rr_gsb) { + + /* Start time count */ + vtr::ScopedStartFinishTimer timer("Generating SDC for constrain Connection Block timing for P&R flow"); + + /* Print SDC for unique X-direction connection block modules */ + for (size_t icb = 0; icb < device_rr_gsb.get_num_cb_unique_module(CHANX); ++icb) { + const RRGSB& unique_mirror = device_rr_gsb.get_cb_unique_module(CHANX, icb); + print_pnr_sdc_constrain_cb_timing(sdc_dir, + module_manager, + rr_graph, + unique_mirror, + CHANX); + } + + /* Print SDC for unique Y-direction connection block modules */ + for (size_t icb = 0; icb < device_rr_gsb.get_num_cb_unique_module(CHANY); ++icb) { + const RRGSB& unique_mirror = device_rr_gsb.get_cb_unique_module(CHANY, icb); + print_pnr_sdc_constrain_cb_timing(sdc_dir, + module_manager, + rr_graph, + unique_mirror, + CHANY); + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.h b/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.h new file mode 100644 index 000000000..65ce60ca8 --- /dev/null +++ b/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.h @@ -0,0 +1,42 @@ +#ifndef PNR_SDC_ROUTING_WRITER_H +#define PNR_SDC_ROUTING_WRITER_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "module_manager.h" +#include "device_rr_gsb.h" +#include "rr_graph_obj.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_pnr_sdc_flatten_routing_constrain_sb_timing(const std::string& sdc_dir, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const DeviceRRGSB& device_rr_gsb); + +void print_pnr_sdc_compact_routing_constrain_sb_timing(const std::string& sdc_dir, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const DeviceRRGSB& device_rr_gsb); + +void print_pnr_sdc_flatten_routing_constrain_cb_timing(const std::string& sdc_dir, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const DeviceRRGSB& device_rr_gsb); + +void print_pnr_sdc_compact_routing_constrain_cb_timing(const std::string& sdc_dir, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const DeviceRRGSB& device_rr_gsb); + +} /* end namespace openfpga */ + +#endif From fdcb9829037c83be39d3e3328fc3fa0678a2e6bb Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 27 Feb 2020 21:06:33 -0700 Subject: [PATCH 228/645] adapt pnr sdc grid writer --- openfpga/src/fabric/build_grid_modules.cpp | 1 - openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp | 346 ++++++++++++++++++ openfpga/src/fpga_sdc/pnr_sdc_grid_writer.h | 27 ++ 3 files changed, 373 insertions(+), 1 deletion(-) create mode 100644 openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp create mode 100644 openfpga/src/fpga_sdc/pnr_sdc_grid_writer.h diff --git a/openfpga/src/fabric/build_grid_modules.cpp b/openfpga/src/fabric/build_grid_modules.cpp index 17ecc67e2..a5c346025 100644 --- a/openfpga/src/fabric/build_grid_modules.cpp +++ b/openfpga/src/fabric/build_grid_modules.cpp @@ -1103,7 +1103,6 @@ void build_grid_modules(ModuleManager& module_manager, duplicate_grid_pin, verbose); } - continue; } else { /* For CLB and heterogenenous blocks */ build_physical_tile_module(module_manager, circuit_lib, diff --git a/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp b/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp new file mode 100644 index 000000000..ea7702a12 --- /dev/null +++ b/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp @@ -0,0 +1,346 @@ +/******************************************************************** + * This file includes functions that print SDC (Synopsys Design Constraint) + * files in physical design tools, i.e., Place & Route (PnR) tools + * The SDC files are used to constrain the physical design for each grid + * (CLBs, heterogeneous blocks etc.) + * + * Note that this is different from the SDC to constrain VPR Place&Route + * engine! These SDCs are designed for PnR to generate FPGA layouts!!! + *******************************************************************/ +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_log.h" +#include "vtr_assert.h" +#include "vtr_time.h" + +/* Headers from openfpgautil library */ +#include "openfpga_port.h" +#include "openfpga_digest.h" +#include "openfpga_side_manager.h" + + +#include "openfpga_interconnect_types.h" +#include "vpr_utils.h" +#include "mux_utils.h" + +#include "openfpga_reserved_words.h" +#include "openfpga_naming.h" +#include "pb_type_utils.h" +#include "pb_graph_utils.h" + +#include "sdc_writer_naming.h" +#include "sdc_writer_utils.h" +#include "pnr_sdc_grid_writer.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Print pin-to-pin timing constraints for a given interconnection + * at an output port of a pb_graph node + *******************************************************************/ +static +void print_pnr_sdc_constrain_pb_pin_interc_timing(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& parent_module, + t_pb_graph_pin* des_pb_graph_pin, + t_mode* physical_mode) { + + /* Validate file stream */ + valid_file_stream(fp); + + /* 1. identify pin interconnection type, + * 2. Identify the number of fan-in (Consider interconnection edges of only selected mode) + * 3. Print SDC timing constraints + */ + t_interconnect* cur_interc = pb_graph_pin_interc(des_pb_graph_pin, physical_mode); + size_t fan_in = pb_graph_pin_inputs(des_pb_graph_pin, cur_interc).size(); + if ((nullptr == cur_interc) || (0 == fan_in)) { + /* No interconnection matched */ + return; + } + + /* Print pin-to-pin SDC contraint here */ + /* For more than one mode defined, the direct interc has more than one input_edge , + * We need to find which edge is connected the pin we want + */ + for (int iedge = 0; iedge < des_pb_graph_pin->num_input_edges; iedge++) { + if (cur_interc != des_pb_graph_pin->input_edges[iedge]->interconnect) { + continue; + } + + /* Source pin, node, pb_type*/ + t_pb_graph_pin* src_pb_graph_pin = des_pb_graph_pin->input_edges[iedge]->input_pins[0]; + t_pb_graph_node* src_pb_graph_node = src_pb_graph_pin->parent_node; + /* Des pin, node, pb_type */ + t_pb_graph_node* des_pb_graph_node = des_pb_graph_pin->parent_node; + + /* Find the src module in module manager */ + std::string src_module_name = generate_physical_block_module_name(src_pb_graph_pin->parent_node->pb_type); + ModuleId src_module = module_manager.find_module(src_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(src_module)); + + ModulePortId src_module_port_id = module_manager.find_module_port(src_module, generate_pb_type_port_name(src_pb_graph_pin->port)); + VTR_ASSERT(true == module_manager.valid_module_port_id(src_module, src_module_port_id)); + + /* Generate the name of the des instance name + * If des module is not the parent module, it is a child module. + * We should find the instance id + */ + std::string src_instance_name = src_module_name; + if (parent_module != src_module) { + src_instance_name = module_manager.module_name(parent_module) + std::string("/"); + /* Instance id is actually the placement index */ + size_t instance_id = src_pb_graph_node->placement_index; + if (true == module_manager.instance_name(parent_module, src_module, instance_id).empty()) { + src_instance_name += src_module_name; + src_instance_name += "_"; + src_instance_name += std::to_string(instance_id); + src_instance_name += "_"; + } else { + src_instance_name += module_manager.instance_name(parent_module, src_module, instance_id); + } + } + + /* Generate src port information */ + BasicPort src_port = module_manager.module_port(src_module, src_module_port_id); + src_port.set_width(src_pb_graph_pin->pin_number, src_pb_graph_pin->pin_number); + + /* Find the des module in module manager */ + std::string des_module_name = generate_physical_block_module_name(des_pb_graph_pin->parent_node->pb_type); + ModuleId des_module = module_manager.find_module(des_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(des_module)); + ModulePortId des_module_port_id = module_manager.find_module_port(des_module, generate_pb_type_port_name(des_pb_graph_pin->port)); + VTR_ASSERT(true == module_manager.valid_module_port_id(des_module, des_module_port_id)); + + /* Generate the name of the des instance name + * If des module is not the parent module, it is a child module. + * We should find the instance id + */ + std::string des_instance_name = des_module_name; + if (parent_module != des_module) { + des_instance_name = module_manager.module_name(parent_module) + std::string("/"); + /* Instance id is actually the placement index */ + size_t instance_id = des_pb_graph_node->placement_index; + if (true == module_manager.instance_name(parent_module, des_module, instance_id).empty()) { + des_instance_name += des_module_name; + des_instance_name += "_"; + des_instance_name += std::to_string(instance_id); + des_instance_name += "_"; + } else { + des_instance_name += module_manager.instance_name(parent_module, des_module, instance_id); + } + } + + /* Generate des port information */ + BasicPort des_port = module_manager.module_port(des_module, des_module_port_id); + des_port.set_width(des_pb_graph_pin->pin_number, des_pb_graph_pin->pin_number); + + /* Print a SDC timing constraint */ + print_pnr_sdc_constrain_max_delay(fp, + src_instance_name, + generate_sdc_port(src_port), + des_instance_name, + generate_sdc_port(des_port), + des_pb_graph_pin->input_edges[iedge]->delay_max); + } +} + +/******************************************************************** + * Print port-to-port timing constraints which source from + * an output port of a pb_graph node + *******************************************************************/ +static +void print_pnr_sdc_constrain_pb_interc_timing(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& parent_module, + t_pb_graph_node* des_pb_graph_node, + const e_circuit_pb_port_type& pb_port_type, + t_mode* physical_mode) { + /* Validate file stream */ + valid_file_stream(fp); + + switch (pb_port_type) { + case CIRCUIT_PB_PORT_INPUT: { + for (int iport = 0; iport < des_pb_graph_node->num_input_ports; ++iport) { + for (int ipin = 0; ipin < des_pb_graph_node->num_input_pins[iport]; ++ipin) { + /* If this is a idle block, we set 0 to the selected edge*/ + /* Get the selected edge of current pin*/ + print_pnr_sdc_constrain_pb_pin_interc_timing(fp, + module_manager, parent_module, + &(des_pb_graph_node->input_pins[iport][ipin]), + physical_mode); + } + } + break; + } + case CIRCUIT_PB_PORT_OUTPUT: { + for (int iport = 0; iport < des_pb_graph_node->num_output_ports; ++iport) { + for (int ipin = 0; ipin < des_pb_graph_node->num_output_pins[iport]; ++ipin) { + print_pnr_sdc_constrain_pb_pin_interc_timing(fp, + module_manager, parent_module, + &(des_pb_graph_node->output_pins[iport][ipin]), + physical_mode); + } + } + break; + } + case CIRCUIT_PB_PORT_CLOCK: { + /* Do NOT constrain clock here, it should be handled by Clock Tree Synthesis */ + break; + } + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid pb port type!\n"); + exit(1); + } +} + +/******************************************************************** + * This function will generate a SDC file for each pb_type, + * constraining the pin-to-pin timing between + * 1. input port of parent_pb_graph_node and input port of child_pb_graph_nodes + * 2. output port of parent_pb_graph_node and output port of child_pb_graph_nodes + * 3. output port of child_pb_graph_node and input port of child_pb_graph_nodes + *******************************************************************/ +static +void print_pnr_sdc_constrain_pb_graph_node_timing(const std::string& sdc_dir, + const ModuleManager& module_manager, + t_pb_graph_node* parent_pb_graph_node, + t_mode* physical_mode) { + + /* Get the pb_type definition related to the node */ + t_pb_type* physical_pb_type = parent_pb_graph_node->pb_type; + std::string pb_module_name = generate_physical_block_module_name(physical_pb_type); + + /* Find the pb module in module manager */ + ModuleId pb_module = module_manager.find_module(pb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(pb_module)); + + /* Create the file name for SDC */ + std::string sdc_fname(sdc_dir + pb_module_name + std::string(SDC_FILE_NAME_POSTFIX)); + + /* Create the file stream */ + std::fstream fp; + fp.open(sdc_fname, std::fstream::out | std::fstream::trunc); + + check_file_stream(sdc_fname.c_str(), fp); + + /* Generate the descriptions*/ + print_sdc_file_header(fp, std::string("Timing constraints for Grid " + pb_module_name + " in PnR")); + + /* We check output_pins of cur_pb_graph_node and its the input_edges + * Built the interconnections between outputs of cur_pb_graph_node and outputs of child_pb_graph_node + * child_pb_graph_node.output_pins -----------------> cur_pb_graph_node.outpins + * /|\ + * | + * input_pins, edges, output_pins + */ + print_pnr_sdc_constrain_pb_interc_timing(fp, + module_manager, pb_module, + parent_pb_graph_node, + CIRCUIT_PB_PORT_OUTPUT, + physical_mode); + + /* We check input_pins of child_pb_graph_node and its the input_edges + * Built the interconnections between inputs of cur_pb_graph_node and inputs of child_pb_graph_node + * cur_pb_graph_node.input_pins -----------------> child_pb_graph_node.input_pins + * /|\ + * | + * input_pins, edges, output_pins + */ + for (int ipb = 0; ipb < physical_mode->num_pb_type_children; ++ipb) { + for (int jpb = 0; jpb < physical_mode->pb_type_children[ipb].num_pb; ++jpb) { + t_pb_graph_node* child_pb_graph_node = &(parent_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ipb][jpb]); + /* For each child_pb_graph_node input pins*/ + print_pnr_sdc_constrain_pb_interc_timing(fp, + module_manager, pb_module, + child_pb_graph_node, + CIRCUIT_PB_PORT_INPUT, + physical_mode); + /* Do NOT constrain clock here, it should be handled by Clock Tree Synthesis */ + } + } + + /* Close file handler */ + fp.close(); +} + +/******************************************************************** + * Recursively print SDC timing constraints for a pb_type + * This function will generate a SDC file for each pb_type, + * constraining the pin-to-pin timing + *******************************************************************/ +static +void rec_print_pnr_sdc_constrain_pb_graph_timing(const std::string& sdc_dir, + const ModuleManager& module_manager, + const VprDeviceAnnotation& device_annotation, + t_pb_graph_node* parent_pb_graph_node) { + /* Validate pb_graph node */ + if (nullptr == parent_pb_graph_node) { + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid parent_pb_graph_node.\n"); + exit(1); + } + + /* Get the pb_type */ + t_pb_type* parent_pb_type = parent_pb_graph_node->pb_type; + + /* No need to constrain the primitive node */ + if (true == is_primitive_pb_type(parent_pb_type)) { + return; + } + + /* Note we only go through the graph through the physical modes. + * which we build the modules + */ + t_mode* physical_mode = device_annotation.physical_mode(parent_pb_type); + + /* Write a SDC file for this pb_type */ + print_pnr_sdc_constrain_pb_graph_node_timing(sdc_dir, + module_manager, + parent_pb_graph_node, + physical_mode); + + /* Go recursively to the lower level in the pb_graph + * Note that we assume a full hierarchical P&R, we will only visit pb_graph_node of unique pb_type + */ + for (int ipb = 0; ipb < physical_mode->num_pb_type_children; ++ipb) { + rec_print_pnr_sdc_constrain_pb_graph_timing(sdc_dir, module_manager, + device_annotation, + &(parent_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ipb][0])); + } +} + +/******************************************************************** + * Top-level function to print timing constraints for pb_types + *******************************************************************/ +void print_pnr_sdc_constrain_grid_timing(const std::string& sdc_dir, + const DeviceContext& device_ctx, + const VprDeviceAnnotation& device_annotation, + const ModuleManager& module_manager) { + + /* Start time count */ + vtr::ScopedStartFinishTimer timer("Generating SDC for constraining grid timing for P&R flow"); + + for (const t_physical_tile_type& physical_tile : device_ctx.physical_tile_types) { + /* Bypass empty type or nullptr */ + if (true == is_empty_type(&physical_tile)) { + continue; + } else { + VTR_ASSERT(1 == physical_tile.equivalent_sites.size()); + t_pb_graph_node* pb_graph_head = physical_tile.equivalent_sites[0]->pb_graph_head; + if (nullptr == pb_graph_head) { + continue; + } + /* Special for I/O block, generate one module for each border side */ + rec_print_pnr_sdc_constrain_pb_graph_timing(sdc_dir, module_manager, + device_annotation, + pb_graph_head); + } + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.h b/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.h new file mode 100644 index 000000000..91c1b7030 --- /dev/null +++ b/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.h @@ -0,0 +1,27 @@ +#ifndef PNR_SDC_GRID_WRITER_H +#define PNR_SDC_GRID_WRITER_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "vpr_context.h" +#include "vpr_device_annotation.h" +#include "module_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_pnr_sdc_constrain_grid_timing(const std::string& sdc_dir, + const DeviceContext& device_ctx, + const VprDeviceAnnotation& device_annotation, + const ModuleManager& module_manager); + +} /* end namespace openfpga */ + +#endif From 89c51b70e353543ccbcd5d477988a5516e2685f0 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 28 Feb 2020 09:48:58 -0700 Subject: [PATCH 229/645] split sdc option into two categories which will be called by different commands --- openfpga/src/fpga_sdc/analysis_sdc_option.cpp | 39 ++++++++++++++ openfpga/src/fpga_sdc/analysis_sdc_option.h | 30 +++++++++++ .../{sdc_option.cpp => pnr_sdc_option.cpp} | 52 +++++++------------ .../{sdc_option.h => pnr_sdc_option.h} | 13 ++--- 4 files changed, 94 insertions(+), 40 deletions(-) create mode 100644 openfpga/src/fpga_sdc/analysis_sdc_option.cpp create mode 100644 openfpga/src/fpga_sdc/analysis_sdc_option.h rename openfpga/src/fpga_sdc/{sdc_option.cpp => pnr_sdc_option.cpp} (60%) rename openfpga/src/fpga_sdc/{sdc_option.h => pnr_sdc_option.h} (85%) diff --git a/openfpga/src/fpga_sdc/analysis_sdc_option.cpp b/openfpga/src/fpga_sdc/analysis_sdc_option.cpp new file mode 100644 index 000000000..444e73fb4 --- /dev/null +++ b/openfpga/src/fpga_sdc/analysis_sdc_option.cpp @@ -0,0 +1,39 @@ +/******************************************************************** + * Member functions for a data structure which includes all the options for the SDC generator + ********************************************************************/ +#include "analysis_sdc_option.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Public Constructors + ********************************************************************/ +AnalysisSdcOption::AnalysisSdcOption(const std::string& sdc_dir) { + sdc_dir_ = sdc_dir; + generate_sdc_analysis_ = false; +} + +/******************************************************************** + * Public accessors + ********************************************************************/ +std::string AnalysisSdcOption::sdc_dir() const { + return sdc_dir_; +} + +bool AnalysisSdcOption::generate_sdc_analysis() const { + return generate_sdc_analysis_; +} + +/******************************************************************** + * Public mutators + ********************************************************************/ +void AnalysisSdcOption::set_sdc_dir(const std::string& sdc_dir) { + sdc_dir_ = sdc_dir; +} + +void AnalysisSdcOption::set_generate_sdc_analysis(const bool& generate_sdc_analysis) { + generate_sdc_analysis_ = generate_sdc_analysis; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/analysis_sdc_option.h b/openfpga/src/fpga_sdc/analysis_sdc_option.h new file mode 100644 index 000000000..514e5e81c --- /dev/null +++ b/openfpga/src/fpga_sdc/analysis_sdc_option.h @@ -0,0 +1,30 @@ +#ifndef ANALYSIS_SDC_OPTION_H +#define ANALYSIS_SDC_OPTION_H + +/******************************************************************** + * A data structure to include all the options for the SDC generator + * in purpose of analyzing users' implementations + ********************************************************************/ + +#include + +/* begin namespace openfpga */ +namespace openfpga { + +class AnalysisSdcOption { + public: /* Public Constructors */ + AnalysisSdcOption(const std::string& sdc_dir); + public: /* Public accessors */ + std::string sdc_dir() const; + bool generate_sdc_analysis() const; + public: /* Public mutators */ + void set_sdc_dir(const std::string& sdc_dir); + void set_generate_sdc_analysis(const bool& generate_sdc_analysis); + private: /* Internal data */ + std::string sdc_dir_; + bool generate_sdc_analysis_; +}; + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_sdc/sdc_option.cpp b/openfpga/src/fpga_sdc/pnr_sdc_option.cpp similarity index 60% rename from openfpga/src/fpga_sdc/sdc_option.cpp rename to openfpga/src/fpga_sdc/pnr_sdc_option.cpp index 4b8c4666f..3a3906b50 100644 --- a/openfpga/src/fpga_sdc/sdc_option.cpp +++ b/openfpga/src/fpga_sdc/pnr_sdc_option.cpp @@ -1,7 +1,7 @@ /******************************************************************** * Member functions for a data structure which includes all the options for the SDC generator ********************************************************************/ -#include "sdc_option.h" +#include "pnr_sdc_option.h" /* begin namespace openfpga */ namespace openfpga { @@ -9,7 +9,7 @@ namespace openfpga { /******************************************************************** * Public Constructors ********************************************************************/ -SdcOption::SdcOption(const std::string& sdc_dir) { +PnrSdcOption::PnrSdcOption(const std::string& sdc_dir) { sdc_dir_ = sdc_dir; constrain_global_port_ = false; constrain_grid_ = false; @@ -23,15 +23,11 @@ SdcOption::SdcOption(const std::string& sdc_dir) { /******************************************************************** * Public accessors ********************************************************************/ -std::string SdcOption::sdc_dir() const { +std::string PnrSdcOption::sdc_dir() const { return sdc_dir_; } -bool SdcOption::generate_sdc() const { - return generate_sdc_pnr() && generate_sdc_analysis_; -} - -bool SdcOption::generate_sdc_pnr() const { +bool PnrSdcOption::generate_sdc_pnr() const { return constrain_global_port_ || constrain_grid_ || constrain_sb_ @@ -41,46 +37,42 @@ bool SdcOption::generate_sdc_pnr() const { || constrain_switch_block_outputs_; } -bool SdcOption::generate_sdc_analysis() const { - return generate_sdc_analysis_; -} - -bool SdcOption::constrain_global_port() const { +bool PnrSdcOption::constrain_global_port() const { return constrain_global_port_; } -bool SdcOption::constrain_grid() const { +bool PnrSdcOption::constrain_grid() const { return constrain_grid_; } -bool SdcOption::constrain_sb() const { +bool PnrSdcOption::constrain_sb() const { return constrain_sb_; } -bool SdcOption::constrain_cb() const { +bool PnrSdcOption::constrain_cb() const { return constrain_cb_; } -bool SdcOption::constrain_configurable_memory_outputs() const { +bool PnrSdcOption::constrain_configurable_memory_outputs() const { return constrain_configurable_memory_outputs_; } -bool SdcOption::constrain_routing_multiplexer_outputs() const { +bool PnrSdcOption::constrain_routing_multiplexer_outputs() const { return constrain_routing_multiplexer_outputs_; } -bool SdcOption::constrain_switch_block_outputs() const { +bool PnrSdcOption::constrain_switch_block_outputs() const { return constrain_switch_block_outputs_; } /******************************************************************** * Public mutators ********************************************************************/ -void SdcOption::set_sdc_dir(const std::string& sdc_dir) { +void PnrSdcOption::set_sdc_dir(const std::string& sdc_dir) { sdc_dir_ = sdc_dir; } -void SdcOption::set_generate_sdc_pnr(const bool& generate_sdc_pnr) { +void PnrSdcOption::set_generate_sdc_pnr(const bool& generate_sdc_pnr) { constrain_global_port_ = generate_sdc_pnr; constrain_grid_ = generate_sdc_pnr; constrain_sb_ = generate_sdc_pnr; @@ -90,35 +82,31 @@ void SdcOption::set_generate_sdc_pnr(const bool& generate_sdc_pnr) { constrain_switch_block_outputs_ = generate_sdc_pnr; } -void SdcOption::set_generate_sdc_analysis(const bool& generate_sdc_analysis) { - generate_sdc_analysis_ = generate_sdc_analysis; -} - -void SdcOption::set_constrain_global_port(const bool& constrain_global_port) { +void PnrSdcOption::set_constrain_global_port(const bool& constrain_global_port) { constrain_global_port_ = constrain_global_port; } -void SdcOption::set_constrain_grid(const bool& constrain_grid) { +void PnrSdcOption::set_constrain_grid(const bool& constrain_grid) { constrain_grid_ = constrain_grid; } -void SdcOption::set_constrain_sb(const bool& constrain_sb) { +void PnrSdcOption::set_constrain_sb(const bool& constrain_sb) { constrain_sb_ = constrain_sb; } -void SdcOption::set_constrain_cb(const bool& constrain_cb) { +void PnrSdcOption::set_constrain_cb(const bool& constrain_cb) { constrain_cb_ = constrain_cb; } -void SdcOption::set_constrain_configurable_memory_outputs(const bool& constrain_config_mem_outputs) { +void PnrSdcOption::set_constrain_configurable_memory_outputs(const bool& constrain_config_mem_outputs) { constrain_configurable_memory_outputs_ = constrain_config_mem_outputs; } -void SdcOption::set_constrain_routing_multiplexer_outputs(const bool& constrain_routing_mux_outputs) { +void PnrSdcOption::set_constrain_routing_multiplexer_outputs(const bool& constrain_routing_mux_outputs) { constrain_routing_multiplexer_outputs_ = constrain_routing_mux_outputs; } -void SdcOption::set_constrain_switch_block_outputs(const bool& constrain_sb_outputs) { +void PnrSdcOption::set_constrain_switch_block_outputs(const bool& constrain_sb_outputs) { constrain_switch_block_outputs_ = constrain_sb_outputs; } diff --git a/openfpga/src/fpga_sdc/sdc_option.h b/openfpga/src/fpga_sdc/pnr_sdc_option.h similarity index 85% rename from openfpga/src/fpga_sdc/sdc_option.h rename to openfpga/src/fpga_sdc/pnr_sdc_option.h index d53c08e94..a485a1946 100644 --- a/openfpga/src/fpga_sdc/sdc_option.h +++ b/openfpga/src/fpga_sdc/pnr_sdc_option.h @@ -1,8 +1,9 @@ -#ifndef SDC_OPTION_H -#define SDC_OPTION_H +#ifndef PNR_SDC_OPTION_H +#define PNR_SDC_OPTION_H /******************************************************************** * A data structure to include all the options for the SDC generator + * in purpose of constraining physical design of FPGA fabric in back-end flow ********************************************************************/ #include @@ -10,14 +11,12 @@ /* begin namespace openfpga */ namespace openfpga { -class SdcOption { +class PnrSdcOption { public: /* Public Constructors */ - SdcOption(const std::string& sdc_dir); + PnrSdcOption(const std::string& sdc_dir); public: /* Public accessors */ std::string sdc_dir() const; - bool generate_sdc() const; bool generate_sdc_pnr() const; - bool generate_sdc_analysis() const; bool constrain_global_port() const; bool constrain_grid() const; bool constrain_sb() const; @@ -28,7 +27,6 @@ class SdcOption { public: /* Public mutators */ void set_sdc_dir(const std::string& sdc_dir); void set_generate_sdc_pnr(const bool& generate_sdc_pnr); - void set_generate_sdc_analysis(const bool& generate_sdc_analysis); void set_constrain_global_port(const bool& constrain_global_port); void set_constrain_grid(const bool& constrain_grid); void set_constrain_sb(const bool& constrain_sb); @@ -45,7 +43,6 @@ class SdcOption { bool constrain_configurable_memory_outputs_; bool constrain_routing_multiplexer_outputs_; bool constrain_switch_block_outputs_; - bool generate_sdc_analysis_; }; } /* end namespace openfpga */ From e45fa18c4ced231d17390e0e1b5fae655b4614bb Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 28 Feb 2020 10:06:35 -0700 Subject: [PATCH 230/645] adapt PnR SDC writer --- openfpga/src/fpga_sdc/pnr_sdc_writer.cpp | 432 +++++++++++++++++++++++ openfpga/src/fpga_sdc/pnr_sdc_writer.h | 37 ++ 2 files changed, 469 insertions(+) create mode 100644 openfpga/src/fpga_sdc/pnr_sdc_writer.cpp create mode 100644 openfpga/src/fpga_sdc/pnr_sdc_writer.h diff --git a/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp b/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp new file mode 100644 index 000000000..e418bc620 --- /dev/null +++ b/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp @@ -0,0 +1,432 @@ +/******************************************************************** + * This file includes functions that print SDC (Synopsys Design Constraint) + * files in physical design tools, i.e., Place & Route (PnR) tools + * The SDC files are used to constrain the physical design for each module + * in FPGA fabric, such as Configurable Logic Blocks (CLBs), + * Heterogeneous blocks, Switch Blocks (SBs) and Connection Blocks (CBs) + * + * Note that this is different from the SDC to constrain VPR Place&Route + * engine! These SDCs are designed for PnR to generate FPGA layouts!!! + *******************************************************************/ +#include +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_time.h" +#include "vtr_log.h" + +/* Headers from openfpgautil library */ +#include "openfpga_port.h" +#include "openfpga_digest.h" + +#include "mux_utils.h" + +#include "openfpga_naming.h" + +#include "sdc_writer_naming.h" +#include "sdc_writer_utils.h" +#include "sdc_memory_utils.h" +#include "pnr_sdc_routing_writer.h" +#include "pnr_sdc_grid_writer.h" +#include "pnr_sdc_writer.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Local variables + *******************************************************************/ +constexpr float SDC_FIXED_PROG_CLOCK_PERIOD = 100; +constexpr float SDC_FIXED_CLOCK_PERIOD = 10; + +/******************************************************************** + * Print a SDC file to constrain the global ports of FPGA fabric + * in particular clock ports + * + * For programming clock, we give a fixed period, while for operating + * clock, we constrain with critical path delay + *******************************************************************/ +static +void print_pnr_sdc_global_ports(const std::string& sdc_dir, + const float& critical_path_delay, + const CircuitLibrary& circuit_lib, + const std::vector& global_ports) { + + /* Create the file name for Verilog netlist */ + std::string sdc_fname(sdc_dir + std::string(SDC_GLOBAL_PORTS_FILE_NAME)); + + /* Start time count */ + std::string timer_message = std::string("Generating SDC for constraining clocks for P&R flow '") + sdc_fname + std::string("'"); + vtr::ScopedStartFinishTimer timer(timer_message); + + /* Create the file stream */ + std::fstream fp; + fp.open(sdc_fname, std::fstream::out | std::fstream::trunc); + + check_file_stream(sdc_fname.c_str(), fp); + + /* Generate the descriptions*/ + print_sdc_file_header(fp, std::string("Clock contraints for PnR")); + + /* Get clock port from the global port */ + for (const CircuitPortId& clock_port : global_ports) { + if (CIRCUIT_MODEL_PORT_CLOCK != circuit_lib.port_type(clock_port)) { + continue; + } + /* Reach here, it means a clock port and we need print constraints */ + float clock_period = critical_path_delay; + + /* For programming clock, we give a fixed period */ + if (true == circuit_lib.port_is_prog(clock_port)) { + clock_period = SDC_FIXED_PROG_CLOCK_PERIOD; + /* Print comments */ + fp << "##################################################" << std::endl; + fp << "# Create programmable clock " << std::endl; + fp << "##################################################" << std::endl; + } else { + /* Print comments */ + fp << "##################################################" << std::endl; + fp << "# Create clock " << std::endl; + fp << "##################################################" << std::endl; + } + + for (const size_t& pin : circuit_lib.pins(clock_port)) { + BasicPort port_to_constrain(circuit_lib.port_prefix(clock_port), pin, pin); + + fp << "create_clock "; + fp << generate_sdc_port(port_to_constrain) << "-period "; + fp << std::setprecision(10) << clock_period; + fp << " -waveform {0 "; + fp << std::setprecision(10) << clock_period / 2; + fp << "}" << std::endl; + + fp << std::endl; + } + } + + /* For non-clock port from the global port: give a fixed period */ + for (const CircuitPortId& global_port : global_ports) { + if (CIRCUIT_MODEL_PORT_CLOCK == circuit_lib.port_type(global_port)) { + continue; + } + + /* Print comments */ + fp << "##################################################" << std::endl; + fp << "# Constrain other global ports " << std::endl; + fp << "##################################################" << std::endl; + + /* Reach here, it means a non-clock global port and we need print constraints */ + float clock_period = SDC_FIXED_CLOCK_PERIOD; + for (const size_t& pin : circuit_lib.pins(global_port)) { + BasicPort port_to_constrain(circuit_lib.port_prefix(global_port), pin, pin); + fp << "create_clock "; + fp << generate_sdc_port(port_to_constrain) << "-period "; + fp << std::setprecision(10) << clock_period; + fp << " -waveform {0 "; + fp << std::setprecision(10) << clock_period / 2; + fp << "} "; + fp << "[list [get_ports { " << generate_sdc_port(port_to_constrain) << "}]]" << std::endl; + + fp << "set_drive 0 " << generate_sdc_port(port_to_constrain) << std::endl; + + fp << std::endl; + } + } + + /* Close file handler */ + fp.close(); +} + +/******************************************************************** + * Break combinational loops in FPGA fabric, which mainly come from + * configurable memory cells. + * To handle this, we disable the outputs of memory cells + *******************************************************************/ +static +void print_pnr_sdc_constrain_configurable_memory_outputs(const std::string& sdc_dir, + const ModuleManager& module_manager, + const ModuleId& top_module) { + + /* Create the file name for Verilog netlist */ + std::string sdc_fname(sdc_dir + std::string(SDC_DISABLE_CONFIG_MEM_OUTPUTS_FILE_NAME)); + + /* Start time count */ + std::string timer_message = std::string("Generating SDC to disable configurable memory outputs for P&R flow '") + sdc_fname + std::string("'"); + vtr::ScopedStartFinishTimer timer(timer_message); + + /* Create the file stream */ + std::fstream fp; + fp.open(sdc_fname, std::fstream::out | std::fstream::trunc); + + check_file_stream(sdc_fname.c_str(), fp); + + /* Generate the descriptions*/ + print_sdc_file_header(fp, std::string("Disable configurable memory outputs for PnR")); + + /* Go recursively in the module manager, starting from the top-level module: instance id of the top-level module is 0 by default */ + rec_print_pnr_sdc_disable_configurable_memory_module_output(fp, module_manager, top_module, + format_dir_path(module_manager.module_name(top_module))); + + /* Close file handler */ + fp.close(); +} + +/******************************************************************** + * Break combinational loops in FPGA fabric, which mainly come from + * loops of multiplexers. + * To handle this, we disable the timing at outputs of routing multiplexers + *******************************************************************/ +static +void print_sdc_disable_routing_multiplexer_outputs(const std::string& sdc_dir, + const MuxLibrary& mux_lib, + const CircuitLibrary& circuit_lib, + const ModuleManager& module_manager) { + /* Create the file name for Verilog netlist */ + std::string sdc_fname(sdc_dir + std::string(SDC_DISABLE_MUX_OUTPUTS_FILE_NAME)); + + /* Start time count */ + std::string timer_message = std::string("Generating SDC to disable routing multiplexer outputs for P&R flow '") + sdc_fname + std::string("'"); + vtr::ScopedStartFinishTimer timer(timer_message); + + /* Create the file stream */ + std::fstream fp; + fp.open(sdc_fname, std::fstream::out | std::fstream::trunc); + + check_file_stream(sdc_fname.c_str(), fp); + + /* Generate the descriptions*/ + print_sdc_file_header(fp, std::string("Disable routing multiplexer outputs for PnR")); + + /* Iterate over the MUX modules */ + for (const MuxId& mux_id : mux_lib.muxes()) { + const CircuitModelId& mux_model = mux_lib.mux_circuit_model(mux_id); + + /* Skip LUTs, we only care about multiplexers here */ + if (CIRCUIT_MODEL_MUX != circuit_lib.model_type(mux_model)) { + continue; + } + + const MuxGraph& mux_graph = mux_lib.mux_graph(mux_id); + std::string mux_module_name = generate_mux_subckt_name(circuit_lib, mux_model, + find_mux_num_datapath_inputs(circuit_lib, mux_model, mux_graph.num_inputs()), + std::string("")); + /* Find the module name in module manager */ + ModuleId mux_module = module_manager.find_module(mux_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(mux_module)); + + /* Disable the timing for the output ports */ + for (const BasicPort& output_port : module_manager.module_ports_by_type(mux_module, ModuleManager::MODULE_OUTPUT_PORT)) { + fp << "set_disable_timing [get_pins -filter \"name =~ " << output_port.get_name() << "*\" "; + fp << "-of [get_cells -hier -filter \"ref_lib_cell_name == " << mux_module_name << "\"]]" << std::endl; + fp << std::endl; + } + } + + /* Close file handler */ + fp.close(); +} + +/******************************************************************** + * Break combinational loops in FPGA fabric, which mainly come from + * loops of multiplexers. + * To handle this, we disable the timing at outputs of Switch blocks + * This function is designed for flatten routing hierarchy + *******************************************************************/ +static +void print_pnr_sdc_flatten_routing_disable_switch_block_outputs(const std::string& sdc_dir, + const ModuleManager& module_manager, + const DeviceRRGSB& device_rr_gsb) { + /* Create the file name for Verilog netlist */ + std::string sdc_fname(sdc_dir + std::string(SDC_DISABLE_SB_OUTPUTS_FILE_NAME)); + + /* Start time count */ + std::string timer_message = std::string("Generating SDC to disable switch block outputs for P&R flow '") + sdc_fname + std::string("'"); + vtr::ScopedStartFinishTimer timer(timer_message); + + /* Create the file stream */ + std::fstream fp; + fp.open(sdc_fname, std::fstream::out | std::fstream::trunc); + + check_file_stream(sdc_fname.c_str(), fp); + + /* Generate the descriptions*/ + print_sdc_file_header(fp, std::string("Disable Switch Block outputs for PnR")); + + /* Get the range of SB array */ + vtr::Point sb_range = device_rr_gsb.get_gsb_range(); + /* Go for each SB */ + for (size_t ix = 0; ix < sb_range.x(); ++ix) { + for (size_t iy = 0; iy < sb_range.y(); ++iy) { + const RRGSB& rr_gsb = device_rr_gsb.get_gsb(ix, iy); + + if (false == rr_gsb.is_sb_exist()) { + continue; + } + + vtr::Point gsb_coordinate(rr_gsb.get_sb_x(), rr_gsb.get_sb_y()); + std::string sb_instance_name = generate_switch_block_module_name(gsb_coordinate); + + ModuleId sb_module = module_manager.find_module(sb_instance_name); + VTR_ASSERT(true == module_manager.valid_module_id(sb_module)); + + /* Disable the outputs of the module */ + for (const BasicPort& output_port : module_manager.module_ports_by_type(sb_module, ModuleManager::MODULE_OUTPUT_PORT)) { + fp << "set_disable_timing " << sb_instance_name << "/" << output_port.get_name() << std::endl; + fp << std::endl; + } + } + } + + /* Close file handler */ + fp.close(); +} + +/******************************************************************** + * Break combinational loops in FPGA fabric, which mainly come from + * loops of multiplexers. + * To handle this, we disable the timing at outputs of Switch blocks + * This function is designed for compact routing hierarchy + *******************************************************************/ +static +void print_pnr_sdc_compact_routing_disable_switch_block_outputs(const std::string& sdc_dir, + const ModuleManager& module_manager, + const ModuleId& top_module, + const DeviceRRGSB& device_rr_gsb) { + /* Create the file name for Verilog netlist */ + std::string sdc_fname(sdc_dir + std::string(SDC_DISABLE_SB_OUTPUTS_FILE_NAME)); + + /* Start time count */ + std::string timer_message = std::string("Generating SDC to disable switch block outputs for P&R flow '") + sdc_fname + std::string("'"); + vtr::ScopedStartFinishTimer timer(timer_message); + + /* Create the file stream */ + std::fstream fp; + fp.open(sdc_fname, std::fstream::out | std::fstream::trunc); + + check_file_stream(sdc_fname.c_str(), fp); + + /* Generate the descriptions*/ + print_sdc_file_header(fp, std::string("Disable Switch Block outputs for PnR")); + + /* Build unique switch block modules */ + for (size_t isb = 0; isb < device_rr_gsb.get_num_sb_unique_module(); ++isb) { + const RRGSB& rr_gsb = device_rr_gsb.get_sb_unique_module(isb); + vtr::Point gsb_coordinate(rr_gsb.get_sb_x(), rr_gsb.get_sb_y()); + std::string sb_module_name = generate_switch_block_module_name(gsb_coordinate); + + ModuleId sb_module = module_manager.find_module(sb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(sb_module)); + + /* Find all the instances in the top-level module */ + for (const size_t& instance_id : module_manager.child_module_instances(top_module, sb_module)) { + std::string sb_instance_name = module_manager.instance_name(top_module, sb_module, instance_id); + /* Disable the outputs of the module */ + for (const BasicPort& output_port : module_manager.module_ports_by_type(sb_module, ModuleManager::MODULE_OUTPUT_PORT)) { + fp << "set_disable_timing " << sb_instance_name << "/" << output_port.get_name() << std::endl; + fp << std::endl; + } + } + } + + /* Close file handler */ + fp.close(); +} + +/******************************************************************** + * Top-level function to print a number of SDC files in different purpose + * This function will generate files upon the options provided by users + * 1. Design constraints for CLBs + * 2. Design constraints for Switch Blocks + * 3. Design constraints for Connection Blocks + * 4. Design constraints for breaking the combinational loops in FPGA fabric + *******************************************************************/ +void print_pnr_sdc(const PnrSdcOption& sdc_options, + const float& critical_path_delay, + const DeviceContext& device_ctx, + const VprDeviceAnnotation& device_annotation, + const DeviceRRGSB& device_rr_gsb, + const ModuleManager& module_manager, + const MuxLibrary& mux_lib, + const CircuitLibrary& circuit_lib, + const std::vector& global_ports, + const bool& compact_routing_hierarchy) { + + /* Constrain global ports */ + if (true == sdc_options.constrain_global_port()) { + print_pnr_sdc_global_ports(sdc_options.sdc_dir(), critical_path_delay, circuit_lib, global_ports); + } + + std::string top_module_name = generate_fpga_top_module_name(); + ModuleId top_module = module_manager.find_module(top_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(top_module)); + + /* Output Design Constraints to disable outputs of memory cells */ + if (true == sdc_options.constrain_configurable_memory_outputs()) { + print_pnr_sdc_constrain_configurable_memory_outputs(sdc_options.sdc_dir(), module_manager, top_module); + } + + /* Break loops from Multiplexer Output */ + if (true == sdc_options.constrain_routing_multiplexer_outputs()) { + print_sdc_disable_routing_multiplexer_outputs(sdc_options.sdc_dir(), + mux_lib, circuit_lib, + module_manager); + } + + /* Break loops from any SB output */ + if (true == sdc_options.constrain_switch_block_outputs()) { + if (true == compact_routing_hierarchy) { + print_pnr_sdc_compact_routing_disable_switch_block_outputs(sdc_options.sdc_dir(), + module_manager, top_module, + device_rr_gsb); + } else { + VTR_ASSERT_SAFE (false == compact_routing_hierarchy); + print_pnr_sdc_flatten_routing_disable_switch_block_outputs(sdc_options.sdc_dir(), + module_manager, + device_rr_gsb); + } + } + + /* Output routing constraints for Switch Blocks */ + if (true == sdc_options.constrain_sb()) { + if (true == compact_routing_hierarchy) { + print_pnr_sdc_compact_routing_constrain_sb_timing(sdc_options.sdc_dir(), + module_manager, + device_ctx.rr_graph, + device_rr_gsb); + } else { + VTR_ASSERT_SAFE (false == compact_routing_hierarchy); + print_pnr_sdc_flatten_routing_constrain_sb_timing(sdc_options.sdc_dir(), + module_manager, + device_ctx.rr_graph, + device_rr_gsb); + } + } + + /* Output routing constraints for Connection Blocks */ + if (true == sdc_options.constrain_cb()) { + if (true == compact_routing_hierarchy) { + print_pnr_sdc_compact_routing_constrain_cb_timing(sdc_options.sdc_dir(), + module_manager, + device_ctx.rr_graph, + device_rr_gsb); + } else { + VTR_ASSERT_SAFE (false == compact_routing_hierarchy); + print_pnr_sdc_flatten_routing_constrain_cb_timing(sdc_options.sdc_dir(), + module_manager, + device_ctx.rr_graph, + device_rr_gsb); + } + } + + /* Output Timing constraints for Programmable blocks */ + if (true == sdc_options.constrain_grid()) { + print_pnr_sdc_constrain_grid_timing(sdc_options.sdc_dir(), + device_ctx, + device_annotation, + module_manager); + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/pnr_sdc_writer.h b/openfpga/src/fpga_sdc/pnr_sdc_writer.h new file mode 100644 index 000000000..cdadbf19d --- /dev/null +++ b/openfpga/src/fpga_sdc/pnr_sdc_writer.h @@ -0,0 +1,37 @@ +#ifndef PNR_SDC_WRITER_H +#define PNR_SDC_WRITER_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "vpr_context.h" +#include "vpr_device_annotation.h" +#include "device_rr_gsb.h" +#include "module_manager.h" +#include "mux_library.h" +#include "circuit_library.h" +#include "pnr_sdc_option.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_pnr_sdc(const PnrSdcOption& sdc_options, + const float& critical_path_delay, + const DeviceContext& device_ctx, + const VprDeviceAnnotation& device_annotation, + const DeviceRRGSB& device_rr_gsb, + const ModuleManager& module_manager, + const MuxLibrary& mux_lib, + const CircuitLibrary& circuit_lib, + const std::vector& global_ports, + const bool& compact_routing_hierarchy); + +} /* end namespace openfpga */ + +#endif From 092e10afda7c2e2496b638220684bbc2422489db Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 28 Feb 2020 11:14:50 -0700 Subject: [PATCH 231/645] bring pnr sdc generator online and fixed minor bugs in bitstream writing --- openfpga/src/base/openfpga_bitstream.cpp | 8 ++ openfpga/src/base/openfpga_sdc.cpp | 80 +++++++++++++++++++ openfpga/src/base/openfpga_sdc.h | 23 ++++++ openfpga/src/base/openfpga_sdc_command.cpp | 79 ++++++++++++++++++ openfpga/src/base/openfpga_sdc_command.h | 21 +++++ openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp | 2 +- .../src/fpga_sdc/pnr_sdc_routing_writer.cpp | 8 +- openfpga/src/fpga_sdc/pnr_sdc_writer.cpp | 10 +-- openfpga/src/main.cpp | 4 + openfpga/test_script/s298_k6_frac.openfpga | 13 ++- 10 files changed, 236 insertions(+), 12 deletions(-) create mode 100644 openfpga/src/base/openfpga_sdc.cpp create mode 100644 openfpga/src/base/openfpga_sdc.h create mode 100644 openfpga/src/base/openfpga_sdc_command.cpp create mode 100644 openfpga/src/base/openfpga_sdc_command.h diff --git a/openfpga/src/base/openfpga_bitstream.cpp b/openfpga/src/base/openfpga_bitstream.cpp index 08fa7bf16..e732dee92 100644 --- a/openfpga/src/base/openfpga_bitstream.cpp +++ b/openfpga/src/base/openfpga_bitstream.cpp @@ -5,6 +5,9 @@ #include "vtr_time.h" #include "vtr_log.h" +/* Headers from openfpgautil library */ +#include "openfpga_digest.h" + #include "build_device_bitstream.h" #include "bitstream_writer.h" #include "build_fabric_bitstream.h" @@ -30,6 +33,11 @@ void fpga_bitstream(OpenfpgaContext& openfpga_ctx, cmd_context.option_enable(cmd, opt_verbose)); if (true == cmd_context.option_enable(cmd, opt_file)) { + std::string src_dir_path = find_path_dir_name(cmd_context.option_value(cmd, opt_file)); + + /* Create directories */ + create_dir_path(src_dir_path.c_str()); + write_arch_independent_bitstream_to_xml_file(openfpga_ctx.bitstream_manager(), cmd_context.option_value(cmd, opt_file)); } diff --git a/openfpga/src/base/openfpga_sdc.cpp b/openfpga/src/base/openfpga_sdc.cpp new file mode 100644 index 000000000..4fce8d552 --- /dev/null +++ b/openfpga/src/base/openfpga_sdc.cpp @@ -0,0 +1,80 @@ +/******************************************************************** + * This file includes functions to compress the hierachy of routing architecture + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_time.h" +#include "vtr_log.h" + +/* Headers from openfpgautil library */ +#include "openfpga_digest.h" + +#include "circuit_library_utils.h" +#include "pnr_sdc_writer.h" +#include "openfpga_sdc.h" + +/* Include global variables of VPR */ +#include "globals.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * A wrapper function to call the PnR SDC generator of FPGA-SDC + *******************************************************************/ +void write_pnr_sdc(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context) { + + CommandOptionId opt_output_dir = cmd.option("file"); + CommandOptionId opt_constrain_global_port = cmd.option("constrain_global_port"); + CommandOptionId opt_constrain_grid = cmd.option("constrain_grid"); + CommandOptionId opt_constrain_sb = cmd.option("constrain_sb"); + CommandOptionId opt_constrain_cb = cmd.option("constrain_cb"); + CommandOptionId opt_constrain_configurable_memory_outputs = cmd.option("constrain_configurable_memory_outputs"); + CommandOptionId opt_constrain_routing_multiplexer_outputs = cmd.option("constrain_routing_multiplexer_outputs"); + CommandOptionId opt_constrain_switch_block_outputs = cmd.option("constrain_switch_block_outputs"); + + /* This is an intermediate data structure which is designed to modularize the FPGA-SDC + * Keep it independent from any other outside data structures + */ + std::string sdc_dir_path = format_dir_path(cmd_context.option_value(cmd, opt_output_dir)); + + /* Create directories */ + create_dir_path(sdc_dir_path.c_str()); + + PnrSdcOption options(sdc_dir_path); + + options.set_constrain_global_port(cmd_context.option_enable(cmd, opt_constrain_global_port)); + options.set_constrain_grid(cmd_context.option_enable(cmd, opt_constrain_grid)); + options.set_constrain_sb(cmd_context.option_enable(cmd, opt_constrain_sb)); + options.set_constrain_cb(cmd_context.option_enable(cmd, opt_constrain_cb)); + options.set_constrain_configurable_memory_outputs(cmd_context.option_enable(cmd, opt_constrain_configurable_memory_outputs)); + options.set_constrain_routing_multiplexer_outputs(cmd_context.option_enable(cmd, opt_constrain_routing_multiplexer_outputs)); + options.set_constrain_switch_block_outputs(cmd_context.option_enable(cmd, opt_constrain_switch_block_outputs)); + + /* We first turn on default sdc option and then disable part of them by following users' options */ + if (false == options.generate_sdc_pnr()) { + options.set_generate_sdc_pnr(true); + } + + /* Collect global ports from the circuit library: + * TODO: should we place this in the OpenFPGA context? + */ + std::vector global_ports = find_circuit_library_global_ports(openfpga_ctx.arch().circuit_lib); + + /* Execute only when sdc is enabled */ + if (true == options.generate_sdc_pnr()) { + print_pnr_sdc(options, + 0, /* TODO: add critical path stats to OpenFPGA context */ + //openfpga_ctx.vpr_timing_annotation().critical_path_delay(); + g_vpr_ctx.device(), + openfpga_ctx.vpr_device_annotation(), + openfpga_ctx.device_rr_gsb(), + openfpga_ctx.module_graph(), + openfpga_ctx.mux_lib(), + openfpga_ctx.arch().circuit_lib, + global_ports, + openfpga_ctx.flow_manager().compress_routing()); + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_sdc.h b/openfpga/src/base/openfpga_sdc.h new file mode 100644 index 000000000..4e48964f3 --- /dev/null +++ b/openfpga/src/base/openfpga_sdc.h @@ -0,0 +1,23 @@ +#ifndef OPENFPGA_SDC_H +#define OPENFPGA_SDC_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "command.h" +#include "command_context.h" +#include "openfpga_context.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void write_pnr_sdc(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/base/openfpga_sdc_command.cpp b/openfpga/src/base/openfpga_sdc_command.cpp new file mode 100644 index 000000000..cf31d73b0 --- /dev/null +++ b/openfpga/src/base/openfpga_sdc_command.cpp @@ -0,0 +1,79 @@ +/******************************************************************** + * Add commands to the OpenFPGA shell interface, + * in purpose of generate SDC files + * - write_pnr_sdc : generate SDC to constrain the back-end flow for FPGA fabric + * - write_analysis_sdc: TODO: generate SDC based on users' implementations + *******************************************************************/ +#include "openfpga_sdc.h" +#include "openfpga_sdc_command.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * - Add a command to Shell environment: generate PnR SDC + * - Add associated options + * - Add command dependency + *******************************************************************/ +static +void add_openfpga_write_pnr_sdc_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id, + const ShellCommandId& shell_cmd_build_fabric_id) { + Command shell_cmd("write_pnr_sdc"); + + /* Add an option '--file' in short '-f'*/ + CommandOptionId output_opt = shell_cmd.add_option("file", true, "Specify the output directory for SDC files"); + shell_cmd.set_option_short_name(output_opt, "f"); + shell_cmd.set_option_require_value(output_opt, openfpga::OPT_STRING); + + /* Add an option '--constrain_global_port' */ + shell_cmd.add_option("constrain_global_port", false, "Constrain all the global ports of FPGA fabric"); + + /* Add an option '--constrain_grid' */ + shell_cmd.add_option("constrain_grid", false, "Constrain all the grids of FPGA fabric"); + + /* Add an option '--constrain_sb' */ + shell_cmd.add_option("constrain_sb", false, "Constrain all the switch blocks of FPGA fabric"); + + /* Add an option '--constrain_cb' */ + shell_cmd.add_option("constrain_cb", false, "Constrain all the connection blocks of FPGA fabric"); + + /* Add an option '--constrain_configurable_memory_outputs' */ + shell_cmd.add_option("constrain_configurable_memory_outputs", false, "Constrain all the outputs of configurable memories of FPGA fabric"); + + /* Add an option '--constrain_routing_multiplexer_outputs' */ + shell_cmd.add_option("constrain_routing_multiplexer_outputs", false, "Constrain all the outputs of routing multiplexer of FPGA fabric"); + + /* Add an option '--constrain_switch_block_outputs' */ + shell_cmd.add_option("constrain_switch_block_outputs", false, "Constrain all the outputs of switch blocks of FPGA fabric"); + + /* Add an option '--verbose' */ + shell_cmd.add_option("verbose", false, "Enable verbose output"); + + /* Add command 'write_fabric_verilog' to the Shell */ + ShellCommandId shell_cmd_id = shell.add_command(shell_cmd, "generate SDC files to constrain the backend flow for FPGA fabric"); + shell.set_command_class(shell_cmd_id, cmd_class_id); + shell.set_command_execute_function(shell_cmd_id, write_pnr_sdc); + + /* The 'build_fabric' command should NOT be executed before 'link_openfpga_arch' */ + std::vector cmd_dependency; + cmd_dependency.push_back(shell_cmd_build_fabric_id); + shell.set_command_dependency(shell_cmd_id, cmd_dependency); +} + +void add_openfpga_sdc_commands(openfpga::Shell& shell) { + /* Get the unique id of 'build_fabric' command which is to be used in creating the dependency graph */ + const ShellCommandId& shell_cmd_build_fabric_id = shell.command(std::string("build_fabric")); + + /* Add a new class of commands */ + ShellCommandClassId openfpga_sdc_cmd_class = shell.add_command_class("FPGA-SDC"); + + /******************************** + * Command 'write_fabric_verilog' + */ + add_openfpga_write_pnr_sdc_command(shell, + openfpga_sdc_cmd_class, + shell_cmd_build_fabric_id); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_sdc_command.h b/openfpga/src/base/openfpga_sdc_command.h new file mode 100644 index 000000000..9fbeb42a5 --- /dev/null +++ b/openfpga/src/base/openfpga_sdc_command.h @@ -0,0 +1,21 @@ +#ifndef OPENFPGA_SDC_COMMAND_H +#define OPENFPGA_SDC_COMMAND_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "shell.h" +#include "openfpga_context.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void add_openfpga_sdc_commands(openfpga::Shell& shell); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp b/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp index ea7702a12..f8ce1c703 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp +++ b/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp @@ -323,7 +323,7 @@ void print_pnr_sdc_constrain_grid_timing(const std::string& sdc_dir, const ModuleManager& module_manager) { /* Start time count */ - vtr::ScopedStartFinishTimer timer("Generating SDC for constraining grid timing for P&R flow"); + vtr::ScopedStartFinishTimer timer("Write SDC for constraining grid timing for P&R flow"); for (const t_physical_tile_type& physical_tile : device_ctx.physical_tile_types) { /* Bypass empty type or nullptr */ diff --git a/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp b/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp index 348ee3dac..dcb5b6849 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp +++ b/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp @@ -166,7 +166,7 @@ void print_pnr_sdc_flatten_routing_constrain_sb_timing(const std::string& sdc_di const DeviceRRGSB& device_rr_gsb) { /* Start time count */ - vtr::ScopedStartFinishTimer timer("Generating SDC for constrain Switch Block timing for P&R flow"); + vtr::ScopedStartFinishTimer timer("Write SDC for constrain Switch Block timing for P&R flow"); /* Get the range of SB array */ vtr::Point sb_range = device_rr_gsb.get_gsb_range(); @@ -195,7 +195,7 @@ void print_pnr_sdc_compact_routing_constrain_sb_timing(const std::string& sdc_di const DeviceRRGSB& device_rr_gsb) { /* Start time count */ - vtr::ScopedStartFinishTimer timer("Generating SDC for constrain Switch Block timing for P&R flow"); + vtr::ScopedStartFinishTimer timer("Write SDC for constrain Switch Block timing for P&R flow"); for (size_t isb = 0; isb < device_rr_gsb.get_num_sb_unique_module(); ++isb) { const RRGSB& rr_gsb = device_rr_gsb.get_sb_unique_module(isb); @@ -362,7 +362,7 @@ void print_pnr_sdc_flatten_routing_constrain_cb_timing(const std::string& sdc_di const DeviceRRGSB& device_rr_gsb) { /* Start time count */ - vtr::ScopedStartFinishTimer timer("Generating SDC for constrain Connection Block timing for P&R flow"); + vtr::ScopedStartFinishTimer timer("Write SDC for constrain Connection Block timing for P&R flow"); print_pnr_sdc_flatten_routing_constrain_cb_timing(sdc_dir, module_manager, rr_graph, @@ -385,7 +385,7 @@ void print_pnr_sdc_compact_routing_constrain_cb_timing(const std::string& sdc_di const DeviceRRGSB& device_rr_gsb) { /* Start time count */ - vtr::ScopedStartFinishTimer timer("Generating SDC for constrain Connection Block timing for P&R flow"); + vtr::ScopedStartFinishTimer timer("Write SDC for constrain Connection Block timing for P&R flow"); /* Print SDC for unique X-direction connection block modules */ for (size_t icb = 0; icb < device_rr_gsb.get_num_cb_unique_module(CHANX); ++icb) { diff --git a/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp b/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp index e418bc620..aa1895014 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp +++ b/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp @@ -58,7 +58,7 @@ void print_pnr_sdc_global_ports(const std::string& sdc_dir, std::string sdc_fname(sdc_dir + std::string(SDC_GLOBAL_PORTS_FILE_NAME)); /* Start time count */ - std::string timer_message = std::string("Generating SDC for constraining clocks for P&R flow '") + sdc_fname + std::string("'"); + std::string timer_message = std::string("Write SDC for constraining clocks for P&R flow '") + sdc_fname + std::string("'"); vtr::ScopedStartFinishTimer timer(timer_message); /* Create the file stream */ @@ -153,7 +153,7 @@ void print_pnr_sdc_constrain_configurable_memory_outputs(const std::string& sdc_ std::string sdc_fname(sdc_dir + std::string(SDC_DISABLE_CONFIG_MEM_OUTPUTS_FILE_NAME)); /* Start time count */ - std::string timer_message = std::string("Generating SDC to disable configurable memory outputs for P&R flow '") + sdc_fname + std::string("'"); + std::string timer_message = std::string("Write SDC to disable configurable memory outputs for P&R flow '") + sdc_fname + std::string("'"); vtr::ScopedStartFinishTimer timer(timer_message); /* Create the file stream */ @@ -187,7 +187,7 @@ void print_sdc_disable_routing_multiplexer_outputs(const std::string& sdc_dir, std::string sdc_fname(sdc_dir + std::string(SDC_DISABLE_MUX_OUTPUTS_FILE_NAME)); /* Start time count */ - std::string timer_message = std::string("Generating SDC to disable routing multiplexer outputs for P&R flow '") + sdc_fname + std::string("'"); + std::string timer_message = std::string("Write SDC to disable routing multiplexer outputs for P&R flow '") + sdc_fname + std::string("'"); vtr::ScopedStartFinishTimer timer(timer_message); /* Create the file stream */ @@ -242,7 +242,7 @@ void print_pnr_sdc_flatten_routing_disable_switch_block_outputs(const std::strin std::string sdc_fname(sdc_dir + std::string(SDC_DISABLE_SB_OUTPUTS_FILE_NAME)); /* Start time count */ - std::string timer_message = std::string("Generating SDC to disable switch block outputs for P&R flow '") + sdc_fname + std::string("'"); + std::string timer_message = std::string("Write SDC to disable switch block outputs for P&R flow '") + sdc_fname + std::string("'"); vtr::ScopedStartFinishTimer timer(timer_message); /* Create the file stream */ @@ -298,7 +298,7 @@ void print_pnr_sdc_compact_routing_disable_switch_block_outputs(const std::strin std::string sdc_fname(sdc_dir + std::string(SDC_DISABLE_SB_OUTPUTS_FILE_NAME)); /* Start time count */ - std::string timer_message = std::string("Generating SDC to disable switch block outputs for P&R flow '") + sdc_fname + std::string("'"); + std::string timer_message = std::string("Write SDC to disable switch block outputs for P&R flow '") + sdc_fname + std::string("'"); vtr::ScopedStartFinishTimer timer(timer_message); /* Create the file stream */ diff --git a/openfpga/src/main.cpp b/openfpga/src/main.cpp index c018db4bf..b2c9d2e6c 100644 --- a/openfpga/src/main.cpp +++ b/openfpga/src/main.cpp @@ -14,6 +14,7 @@ #include "openfpga_setup_command.h" #include "openfpga_verilog_command.h" #include "openfpga_bitstream_command.h" +#include "openfpga_sdc_command.h" #include "basic_command.h" #include "openfpga_title.h" @@ -60,6 +61,9 @@ int main(int argc, char** argv) { /* Add openfpga bitstream commands */ openfpga::add_openfpga_bitstream_commands(shell); + /* Add openfpga sdc commands */ + openfpga::add_openfpga_sdc_commands(shell); + /* Add basic commands: exit, help, etc. * Note: * This MUST be the last command group to be added! diff --git a/openfpga/test_script/s298_k6_frac.openfpga b/openfpga/test_script/s298_k6_frac.openfpga index d79fc3b9b..a5c2c3669 100644 --- a/openfpga/test_script/s298_k6_frac.openfpga +++ b/openfpga/test_script/s298_k6_frac.openfpga @@ -35,10 +35,19 @@ fpga_bitstream --verbose --file /var/tmp/xtang/openfpga_test_src/fabric_indepene # Write the Verilog netlist for FPGA fabric # - Enable the use of explicit port mapping in Verilog netlist -write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose +write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src/SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose # Write the Verilog testbench for FPGA fabric -write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src --reference_benchmark_file_path /var/tmp/xtang/s298.v --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini /var/tmp/xtang/openfpga_test_src/simulation_deck.ini +# - We suggest the use of same output directory as fabric Verilog netlists +# - Must specify the reference benchmark file if you want to output any testbenches +# - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA +# - Enable pre-configured top-level testbench which is a fast verification skipping programming phase +# - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts +write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src/SRC --reference_benchmark_file_path /var/tmp/xtang/s298.v --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini /var/tmp/xtang/openfpga_test_src/simulation_deck.ini + +# Write the SDC files for PnR backend +# - Turn on every options here +write_pnr_sdc --file /var/tmp/xtang/openfpga_test_src/SDC # Finish and exit OpenFPGA exit From de8425874c68adf9113a84606410f37135dee56d Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 28 Feb 2020 11:24:39 -0700 Subject: [PATCH 232/645] use user defined critical path delay in SDC generation --- openfpga/src/base/openfpga_sdc.cpp | 4 ++-- openfpga/src/fpga_sdc/pnr_sdc_writer.cpp | 23 +++++++++++------------ openfpga/src/fpga_sdc/pnr_sdc_writer.h | 3 ++- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/openfpga/src/base/openfpga_sdc.cpp b/openfpga/src/base/openfpga_sdc.cpp index 4fce8d552..eaa2b9941 100644 --- a/openfpga/src/base/openfpga_sdc.cpp +++ b/openfpga/src/base/openfpga_sdc.cpp @@ -64,8 +64,8 @@ void write_pnr_sdc(OpenfpgaContext& openfpga_ctx, /* Execute only when sdc is enabled */ if (true == options.generate_sdc_pnr()) { print_pnr_sdc(options, - 0, /* TODO: add critical path stats to OpenFPGA context */ - //openfpga_ctx.vpr_timing_annotation().critical_path_delay(); + 1./openfpga_ctx.arch().sim_setting.programming_clock_frequency(), + 1./openfpga_ctx.arch().sim_setting.operating_clock_frequency(), g_vpr_ctx.device(), openfpga_ctx.vpr_device_annotation(), openfpga_ctx.device_rr_gsb(), diff --git a/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp b/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp index aa1895014..9d1478037 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp +++ b/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp @@ -35,12 +35,6 @@ /* begin namespace openfpga */ namespace openfpga { -/******************************************************************** - * Local variables - *******************************************************************/ -constexpr float SDC_FIXED_PROG_CLOCK_PERIOD = 100; -constexpr float SDC_FIXED_CLOCK_PERIOD = 10; - /******************************************************************** * Print a SDC file to constrain the global ports of FPGA fabric * in particular clock ports @@ -50,7 +44,8 @@ constexpr float SDC_FIXED_CLOCK_PERIOD = 10; *******************************************************************/ static void print_pnr_sdc_global_ports(const std::string& sdc_dir, - const float& critical_path_delay, + const float& programming_critical_path_delay, + const float& operating_critical_path_delay, const CircuitLibrary& circuit_lib, const std::vector& global_ports) { @@ -76,11 +71,11 @@ void print_pnr_sdc_global_ports(const std::string& sdc_dir, continue; } /* Reach here, it means a clock port and we need print constraints */ - float clock_period = critical_path_delay; + float clock_period = operating_critical_path_delay; /* For programming clock, we give a fixed period */ if (true == circuit_lib.port_is_prog(clock_port)) { - clock_period = SDC_FIXED_PROG_CLOCK_PERIOD; + clock_period = programming_critical_path_delay; /* Print comments */ fp << "##################################################" << std::endl; fp << "# Create programmable clock " << std::endl; @@ -118,7 +113,7 @@ void print_pnr_sdc_global_ports(const std::string& sdc_dir, fp << "##################################################" << std::endl; /* Reach here, it means a non-clock global port and we need print constraints */ - float clock_period = SDC_FIXED_CLOCK_PERIOD; + float clock_period = operating_critical_path_delay; for (const size_t& pin : circuit_lib.pins(global_port)) { BasicPort port_to_constrain(circuit_lib.port_prefix(global_port), pin, pin); fp << "create_clock "; @@ -343,7 +338,8 @@ void print_pnr_sdc_compact_routing_disable_switch_block_outputs(const std::strin * 4. Design constraints for breaking the combinational loops in FPGA fabric *******************************************************************/ void print_pnr_sdc(const PnrSdcOption& sdc_options, - const float& critical_path_delay, + const float& programming_critical_path_delay, + const float& operating_critical_path_delay, const DeviceContext& device_ctx, const VprDeviceAnnotation& device_annotation, const DeviceRRGSB& device_rr_gsb, @@ -355,7 +351,10 @@ void print_pnr_sdc(const PnrSdcOption& sdc_options, /* Constrain global ports */ if (true == sdc_options.constrain_global_port()) { - print_pnr_sdc_global_ports(sdc_options.sdc_dir(), critical_path_delay, circuit_lib, global_ports); + print_pnr_sdc_global_ports(sdc_options.sdc_dir(), + programming_critical_path_delay, + operating_critical_path_delay, + circuit_lib, global_ports); } std::string top_module_name = generate_fpga_top_module_name(); diff --git a/openfpga/src/fpga_sdc/pnr_sdc_writer.h b/openfpga/src/fpga_sdc/pnr_sdc_writer.h index cdadbf19d..1016d57da 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_writer.h +++ b/openfpga/src/fpga_sdc/pnr_sdc_writer.h @@ -22,7 +22,8 @@ namespace openfpga { void print_pnr_sdc(const PnrSdcOption& sdc_options, - const float& critical_path_delay, + const float& programming_critical_path_delay, + const float& operating_critical_path_delay, const DeviceContext& device_ctx, const VprDeviceAnnotation& device_annotation, const DeviceRRGSB& device_rr_gsb, From 542fadaaae12f53953254dec0ee8bbe7c93b6dc8 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 28 Feb 2020 12:10:27 -0700 Subject: [PATCH 233/645] allow users to use VPR critical path delay in OpenFPGA simulation --- openfpga/src/base/openfpga_link_arch.cpp | 50 +++++++++++++++++++ .../k6_frac_N10_40nm_openfpga.xml | 2 +- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/openfpga/src/base/openfpga_link_arch.cpp b/openfpga/src/base/openfpga_link_arch.cpp index 9dc1e38c7..799a88a2a 100644 --- a/openfpga/src/base/openfpga_link_arch.cpp +++ b/openfpga/src/base/openfpga_link_arch.cpp @@ -7,6 +7,11 @@ #include "vtr_assert.h" #include "vtr_log.h" +/* Headers from vpr library */ +#include "timing_info.h" +#include "AnalysisDelayCalculator.h" +#include "net_delay.h" + #include "vpr_device_annotation.h" #include "pb_type_utils.h" #include "annotate_pb_types.h" @@ -46,6 +51,47 @@ bool is_vpr_rr_graph_supported(const RRGraph& rr_graph) { return true; } +/******************************************************************** + * Annotate simulation setting based on VPR results + * - If the operating clock frequency is set to follow the vpr timing results, + * we will set a new operating clock frequency here + * - If the number of clock cycles in simulation is set to be automatically determined, + * we will infer the number based on the average signal density + *******************************************************************/ +static +void annotate_simulation_setting(const AtomContext& atom_ctx, + SimulationSetting& sim_setting) { + + /* Find if the operating frequency is binded to vpr results */ + if (0. == sim_setting.operating_clock_frequency()) { + VTR_LOG("User specified the operating clock frequency to use VPR results\n"); + /* Run timing analysis and collect critical path delay + * This code is copied from function vpr_analysis() in vpr_api.h + * Should keep updated to latest VPR code base + * Note: + * - MUST mention in documentation that VPR should be run in timing enabled mode + */ + vtr::vector net_delay; + vtr::t_chunk net_delay_ch; + /* Load the net delays */ + net_delay = alloc_net_delay(&net_delay_ch); + load_net_delay_from_routing(net_delay); + + /* Do final timing analysis */ + auto analysis_delay_calc = std::make_shared(atom_ctx.nlist, atom_ctx.lookup, net_delay); + auto timing_info = make_setup_hold_timing_info(analysis_delay_calc); + timing_info->update(); + + /* Get critical path delay. Update simulation settings */ + float T_crit = timing_info->least_slack_critical_path().delay() * (1. + sim_setting.operating_clock_frequency_slack()); + sim_setting.set_operating_clock_frequency(1 / T_crit); + VTR_LOG("Use VPR critical path delay %g [ns] with a %g [%] slack in OpenFPGA.\n", + T_crit / 1e9, sim_setting.operating_clock_frequency_slack() * 100); + } + VTR_LOG("Will apply operating clock frequency %g [MHz] to simulations\n", + sim_setting.operating_clock_frequency() / 1e6); +} + /******************************************************************** * Top-level function to link openfpga architecture to VPR, including: * - physical pb_type @@ -118,6 +164,10 @@ void link_arch(OpenfpgaContext& openfpga_ctx, g_vpr_ctx.clustering(), g_vpr_ctx.placement(), openfpga_ctx.mutable_vpr_placement_annotation()); + + /* TODO: Annotate the number of clock cycles and clock frequency by following VPR results */ + annotate_simulation_setting(g_vpr_ctx.atom(), + openfpga_ctx.mutable_arch().sim_setting); } } /* end namespace openfpga */ diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml index 2f318cbaa..2e9350f51 100644 --- a/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml @@ -232,7 +232,7 @@ - + From 80bb2baae5a99582bce671b56dc240553d5ea86b Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 28 Feb 2020 14:29:01 -0700 Subject: [PATCH 234/645] start verification and bug fixing --- .../verilog_auxiliary_netlists.cpp | 6 ++++++ .../verilog_formal_random_top_testbench.cpp | 1 + .../fpga_verilog/verilog_testbench_utils.cpp | 14 +++++++++++++- .../src/fpga_verilog/verilog_testbench_utils.h | 1 + .../src/fpga_verilog/verilog_top_testbench.cpp | 1 + .../k6_frac_N10_40nm_openfpga.xml | 4 ++-- openfpga/test_script/s298_k6_frac.openfpga | 2 +- openfpga/test_vpr_arch/k6_frac_N10_40nm.xml | 18 ++++++++++++------ 8 files changed, 37 insertions(+), 10 deletions(-) diff --git a/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.cpp b/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.cpp index 9419f4172..fd01a462f 100644 --- a/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.cpp +++ b/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.cpp @@ -183,6 +183,12 @@ void print_verilog_simulation_preprocessing_flags(const std::string& src_dir, /* To enable pre-configured FPGA simulation */ if (true == verilog_testbench_opts.print_formal_verification_top_netlist()) { + print_verilog_define_flag(fp, std::string(VERILOG_FORMAL_VERIFICATION_PREPROC_FLAG), 1); + fp << std::endl; + } + + /* To enable pre-configured FPGA simulation */ + if (true == verilog_testbench_opts.print_preconfig_top_testbench()) { print_verilog_define_flag(fp, std::string(FORMAL_SIMULATION_FLAG), 1); fp << std::endl; } diff --git a/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.cpp b/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.cpp index 7aef8a72f..fa4f68938 100644 --- a/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.cpp +++ b/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.cpp @@ -228,6 +228,7 @@ void print_verilog_random_top_testbench(const std::string& circuit_name, clock_port); print_verilog_testbench_random_stimuli(fp, atom_ctx, netlist_annotation, + clock_port_names, std::string(CHECKFLAG_PORT_POSTFIX), clock_port); diff --git a/openfpga/src/fpga_verilog/verilog_testbench_utils.cpp b/openfpga/src/fpga_verilog/verilog_testbench_utils.cpp index 8d3e856c7..e952d13c4 100644 --- a/openfpga/src/fpga_verilog/verilog_testbench_utils.cpp +++ b/openfpga/src/fpga_verilog/verilog_testbench_utils.cpp @@ -5,6 +5,7 @@ * Note: please try to avoid using global variables in this file * so that we can make it free to use anywhere *******************************************************************/ +#include #include /* Headers from vtrutil library */ @@ -422,6 +423,7 @@ void print_verilog_testbench_clock_stimuli(std::fstream& fp, void print_verilog_testbench_random_stimuli(std::fstream& fp, const AtomContext& atom_ctx, const VprNetlistAnnotation& netlist_annotation, + const std::vector& clock_port_names, const std::string& check_flag_port_postfix, const BasicPort& clock_port) { /* Validate the file stream */ @@ -444,6 +446,11 @@ void print_verilog_testbench_random_stimuli(std::fstream& fp, block_name = netlist_annotation.block_name(atom_blk); } + /* Bypass clock ports */ + if (clock_port_names.end() != std::find(clock_port_names.begin(), clock_port_names.end(), block_name)) { + continue; + } + /* TODO: find the clock inputs will be initialized later */ if (AtomBlockType::INPAD == atom_ctx.nlist.block_type(atom_blk)) { fp << "\t\t" << block_name << " <= 1'b0;" << std::endl; @@ -510,8 +517,13 @@ void print_verilog_testbench_random_stimuli(std::fstream& fp, block_name = netlist_annotation.block_name(atom_blk); } + /* Bypass clock ports */ + if (clock_port_names.end() != std::find(clock_port_names.begin(), clock_port_names.end(), block_name)) { + continue; + } + /* TODO: find the clock inputs will be initialized later */ - if (AtomBlockType::INPAD != atom_ctx.nlist.block_type(atom_blk)) { + if (AtomBlockType::INPAD == atom_ctx.nlist.block_type(atom_blk)) { fp << "\t\t" << block_name << " <= $random;" << std::endl; } } diff --git a/openfpga/src/fpga_verilog/verilog_testbench_utils.h b/openfpga/src/fpga_verilog/verilog_testbench_utils.h index 61d5929c8..5e0bd69f9 100644 --- a/openfpga/src/fpga_verilog/verilog_testbench_utils.h +++ b/openfpga/src/fpga_verilog/verilog_testbench_utils.h @@ -76,6 +76,7 @@ void print_verilog_testbench_clock_stimuli(std::fstream& fp, void print_verilog_testbench_random_stimuli(std::fstream& fp, const AtomContext& atom_ctx, const VprNetlistAnnotation& netlist_annotation, + const std::vector& clock_port_names, const std::string& check_flag_port_postfix, const BasicPort& clock_port); diff --git a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp index f64558dda..911c6defb 100644 --- a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp +++ b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp @@ -855,6 +855,7 @@ void print_verilog_top_testbench(const ModuleManager& module_manager, /* Add stimuli for reset, set, clock and iopad signals */ print_verilog_testbench_random_stimuli(fp, atom_ctx, netlist_annotation, + clock_port_names, std::string(TOP_TESTBENCH_CHECKFLAG_PORT_POSTFIX), BasicPort(std::string(TOP_TB_OP_CLOCK_PORT_NAME), 1)); diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml index 2e9350f51..d02d62d88 100644 --- a/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml @@ -168,11 +168,11 @@ - + - + diff --git a/openfpga/test_script/s298_k6_frac.openfpga b/openfpga/test_script/s298_k6_frac.openfpga index a5c2c3669..5423493e3 100644 --- a/openfpga/test_script/s298_k6_frac.openfpga +++ b/openfpga/test_script/s298_k6_frac.openfpga @@ -1,5 +1,5 @@ # Run VPR for the s298 design -vpr ./test_vpr_arch/k6_frac_N10_40nm.xml ./test_blif/s298.blif --write_rr_graph example_rr_graph.xml +vpr ./test_vpr_arch/k6_frac_N10_40nm.xml ./test_blif/s298.blif --clock_modeling route #--write_rr_graph example_rr_graph.xml # Read OpenFPGA architecture definition read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml diff --git a/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml index 97001c497..44e2ef289 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml @@ -45,19 +45,22 @@ + - - io.outpad io.inpad io.clock - io.outpad io.inpad io.clock - io.outpad io.inpad io.clock - io.outpad io.inpad io.clock + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad @@ -145,7 +148,10 @@ - + From a6c2d2c7d1e2bcdd81199f350969ee180f3e2e29 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 28 Feb 2020 14:46:01 -0700 Subject: [PATCH 235/645] bug fixed for io location mapping --- openfpga/src/fabric/build_top_module.cpp | 2 +- openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/openfpga/src/fabric/build_top_module.cpp b/openfpga/src/fabric/build_top_module.cpp index 7af79d46c..707ff8971 100644 --- a/openfpga/src/fabric/build_top_module.cpp +++ b/openfpga/src/fabric/build_top_module.cpp @@ -172,8 +172,8 @@ vtr::Matrix add_top_module_grid_instances(ModuleManager& module_manager, */ for (int z = 0; z < grids[io_coordinate.x()][io_coordinate.y()].type->capacity; ++z) { io_location_map.set_io_index(io_coordinate.x(), io_coordinate.y(), z, io_counter); + io_counter++; } - io_counter++; } } diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml index d02d62d88..9644530da 100644 --- a/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml @@ -232,7 +232,8 @@ - + + From 05ebd77d7dc57e280015af3ff1b51e489d4dfea4 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 28 Feb 2020 15:41:32 -0700 Subject: [PATCH 236/645] start debugging with micro benchmarks. Spot problem in local routing --- openfpga/test_blif/and.blif | 8 ++ openfpga/test_blif/and.v | 14 +++ openfpga/test_blif/s298.blif | 90 ------------------- ..._k6_frac.openfpga => and_k6_frac.openfpga} | 4 +- 4 files changed, 24 insertions(+), 92 deletions(-) create mode 100644 openfpga/test_blif/and.blif create mode 100644 openfpga/test_blif/and.v delete mode 100644 openfpga/test_blif/s298.blif rename openfpga/test_script/{s298_k6_frac.openfpga => and_k6_frac.openfpga} (88%) diff --git a/openfpga/test_blif/and.blif b/openfpga/test_blif/and.blif new file mode 100644 index 000000000..67d978741 --- /dev/null +++ b/openfpga/test_blif/and.blif @@ -0,0 +1,8 @@ +.model top +.inputs a b +.outputs c + +.names a b c +11 1 + +.end diff --git a/openfpga/test_blif/and.v b/openfpga/test_blif/and.v new file mode 100644 index 000000000..876f1c6fe --- /dev/null +++ b/openfpga/test_blif/and.v @@ -0,0 +1,14 @@ +`timescale 1ns / 1ps + +module top( + a, + b, + c); + +input wire a; +input wire b; +output wire c; + +assign c = a & b; + +endmodule diff --git a/openfpga/test_blif/s298.blif b/openfpga/test_blif/s298.blif deleted file mode 100644 index c588a6e0a..000000000 --- a/openfpga/test_blif/s298.blif +++ /dev/null @@ -1,90 +0,0 @@ -# Benchmark "s298" written by ABC on Tue Mar 12 09:40:31 2019 -.model s298 -.inputs clock G0 G1 G2 -.outputs G117 G132 G66 G118 G133 G67 - -.latch n21 G10 re clock 0 -.latch n26 G11 re clock 0 -.latch n31 G12 re clock 0 -.latch n36 G13 re clock 0 -.latch n41 G14 re clock 0 -.latch n46 G15 re clock 0 -.latch n51 G66 re clock 0 -.latch n55 G67 re clock 0 -.latch n59 G117 re clock 0 -.latch n63 G118 re clock 0 -.latch n67 G132 re clock 0 -.latch n71 G133 re clock 0 -.latch n75 G22 re clock 0 -.latch n80 G23 re clock 0 - -.names n56 n57 G10 n63 -0-0 1 -11- 1 -.names G15 G11 G13 G22 G14 G12 n56 -01---- 1 -0-0--- 1 -0--0-- 1 -0---1- 1 -0----1 1 --11000 1 -.names G14 G13 G12 G118 G11 n57 -01--- 1 -100-0 1 -1-11- 1 --1-1- 1 -.names n56 n59_1 G10 n67 -0-0 1 -11- 1 -.names G14 G13 G12 G132 G11 n59_1 -100-0 1 -11-1- 1 -1-11- 1 -.names G0 G10 n21 -00 1 -.names G10 G11 G0 G12 G13 n26 -010-- 1 -1001- 1 -100-0 1 -.names G12 G0 G11 G10 n31 -0011 1 -100- 1 -10-0 1 -.names G13 G0 G11 G12 G10 n36 -00111 1 -1001- 1 -1010- 1 -10--0 1 -.names n65 G14 G0 n41 -000 1 -110 1 -.names G23 G10 G13 G11 G12 n65 -1---- 0 --1100 0 -.names G0 n56 n46 -00 1 -.names n56 G66 G14 G13 G12 n51 -111-1 1 -11-1- 1 -1-01- 1 -.names n56 G13 G14 G11 G67 G12 n55 -1000-- 1 -10-1-0 1 -111-1- 1 -1-1-11 1 -.names n56 G13 G117 G14 G12 G11 n59 -10-0-- 1 -10--01 1 -1111-- 1 -1-111- 1 -.names n56 G14 G12 G13 G133 G11 n71 -1010-1 1 -111-1- 1 -11-11- 1 -.names G2 G22 G0 n75 -010 1 -100 1 -.names G1 G23 G0 n80 -010 1 -100 1 -.end diff --git a/openfpga/test_script/s298_k6_frac.openfpga b/openfpga/test_script/and_k6_frac.openfpga similarity index 88% rename from openfpga/test_script/s298_k6_frac.openfpga rename to openfpga/test_script/and_k6_frac.openfpga index 5423493e3..51a1c5ae2 100644 --- a/openfpga/test_script/s298_k6_frac.openfpga +++ b/openfpga/test_script/and_k6_frac.openfpga @@ -1,5 +1,5 @@ # Run VPR for the s298 design -vpr ./test_vpr_arch/k6_frac_N10_40nm.xml ./test_blif/s298.blif --clock_modeling route #--write_rr_graph example_rr_graph.xml +vpr ./test_vpr_arch/k6_frac_N10_40nm.xml ./test_blif/and.blif --clock_modeling route #--write_rr_graph example_rr_graph.xml # Read OpenFPGA architecture definition read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml @@ -43,7 +43,7 @@ write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src/SRC --explicit_port # - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA # - Enable pre-configured top-level testbench which is a fast verification skipping programming phase # - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts -write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src/SRC --reference_benchmark_file_path /var/tmp/xtang/s298.v --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini /var/tmp/xtang/openfpga_test_src/simulation_deck.ini +write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src/SRC --reference_benchmark_file_path /var/tmp/xtang/and.v --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini /var/tmp/xtang/openfpga_test_src/simulation_deck.ini # Write the SDC files for PnR backend # - Turn on every options here From 9fd184e3ab9280c7a8e87552d372e80bb09a33e7 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 28 Feb 2020 15:42:18 -0700 Subject: [PATCH 237/645] rm out-of-date script --- openfpga/test_script/s298.openfpga | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 openfpga/test_script/s298.openfpga diff --git a/openfpga/test_script/s298.openfpga b/openfpga/test_script/s298.openfpga deleted file mode 100644 index 216f6d8c4..000000000 --- a/openfpga/test_script/s298.openfpga +++ /dev/null @@ -1,14 +0,0 @@ -# Run VPR for the s298 design -vpr ./test_vpr_arch/k6_N10_40nm.xml ./test_blif/s298.blif - -# Read OpenFPGA architecture definition -read_openfpga_arch -f ./test_openfpga_arch/k6_N10_40nm_openfpga.xml - -# Annotate the OpenFPGA architecture to VPR data base -link_openfpga_arch - -# Check and correct any naming conflicts in the BLIF netlist -check_netlist_naming_conflict --fix --report ./netlist_renaming.xml - -# Finish and exit OpenFPGA -exit From 3807a940f4d7ec953db576f6117f9f38c791ebe5 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 28 Feb 2020 16:45:50 -0700 Subject: [PATCH 238/645] fixed critical bugs in bitstream generation and now we pass microbenchmarks --- openfpga/src/fpga_bitstream/build_grid_bitstream.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp b/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp index 253798c86..f69b57ae6 100644 --- a/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp +++ b/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp @@ -168,7 +168,8 @@ void build_physical_block_pin_interc_bitstream(BitstreamManager& bitstream_manag for (t_pb_graph_pin* src_pb_graph_pin : pb_graph_pin_inputs(des_pb_graph_pin, cur_interc)) { const PhysicalPbId& src_pb_id = physical_pb.find_pb(src_pb_graph_pin->parent_node); /* If the src pb id is not valid, we bypass it */ - if ( (true != physical_pb.valid_pb_id(src_pb_id)) + if ( (true == physical_pb.valid_pb_id(src_pb_id)) + && (AtomNetId::INVALID() != physical_pb.pb_graph_pin_atom_net(des_pb_id, des_pb_graph_pin)) && (physical_pb.pb_graph_pin_atom_net(src_pb_id, src_pb_graph_pin) == physical_pb.pb_graph_pin_atom_net(des_pb_id, des_pb_graph_pin))) { break; } From 7b18f7cd098e19a941776b5374e82f20c5f88e04 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 29 Feb 2020 13:29:16 -0700 Subject: [PATCH 239/645] now the auto select number of clocks in simulation is online --- openfpga/src/base/openfpga_context.h | 5 + openfpga/src/base/openfpga_link_arch.cpp | 135 +++++++- openfpga/src/base/openfpga_setup_command.cpp | 317 +++++++++++++------ openfpga/test_script/and_k6_frac.openfpga | 2 +- 4 files changed, 365 insertions(+), 94 deletions(-) diff --git a/openfpga/src/base/openfpga_context.h b/openfpga/src/base/openfpga_context.h index 4b6cdf2d2..20e235a35 100644 --- a/openfpga/src/base/openfpga_context.h +++ b/openfpga/src/base/openfpga_context.h @@ -60,6 +60,7 @@ class OpenfpgaContext : public Context { const openfpga::BitstreamManager& bitstream_manager() const { return bitstream_manager_; } const std::vector& fabric_bitstream() const { return fabric_bitstream_; } const openfpga::IoLocationMap& io_location_map() { return io_location_map_; } + const std::unordered_map& net_activity() { return net_activity_; } public: /* Public mutators */ openfpga::Arch& mutable_arch() { return arch_; } openfpga::VprDeviceAnnotation& mutable_vpr_device_annotation() { return vpr_device_annotation_; } @@ -75,6 +76,7 @@ class OpenfpgaContext : public Context { openfpga::BitstreamManager& mutable_bitstream_manager() { return bitstream_manager_; } std::vector& mutable_fabric_bitstream() { return fabric_bitstream_; } openfpga::IoLocationMap& mutable_io_location_map() { return io_location_map_; } + std::unordered_map& mutable_net_activity() { return net_activity_; } private: /* Internal data */ /* Data structure to store information from read_openfpga_arch library */ openfpga::Arch arch_; @@ -110,6 +112,9 @@ class OpenfpgaContext : public Context { /* Bitstream database */ openfpga::BitstreamManager bitstream_manager_; std::vector fabric_bitstream_; + + /* Net activities of users' implementation */ + std::unordered_map net_activity_; /* Flow status */ openfpga::FlowManager flow_manager_; diff --git a/openfpga/src/base/openfpga_link_arch.cpp b/openfpga/src/base/openfpga_link_arch.cpp index 799a88a2a..fe89b7024 100644 --- a/openfpga/src/base/openfpga_link_arch.cpp +++ b/openfpga/src/base/openfpga_link_arch.cpp @@ -2,6 +2,9 @@ * This file includes functions to read an OpenFPGA architecture file * which are built on the libarchopenfpga library *******************************************************************/ +#include +#include + /* Headers from vtrutil library */ #include "vtr_time.h" #include "vtr_assert.h" @@ -11,6 +14,7 @@ #include "timing_info.h" #include "AnalysisDelayCalculator.h" #include "net_delay.h" +#include "read_activity.h" #include "vpr_device_annotation.h" #include "pb_type_utils.h" @@ -51,6 +55,108 @@ bool is_vpr_rr_graph_supported(const RRGraph& rr_graph) { return true; } +/******************************************************************** + * Find the number of clock cycles in simulation based on the average signal density + *******************************************************************/ +static +size_t recommend_num_sim_clock_cycle(const AtomContext& atom_ctx, + const std::unordered_map& net_activity, + const float& sim_window_size) { + size_t recmd_num_sim_clock_cycle = 0; + + float avg_density = 0.; + size_t net_cnt = 0; + + float weighted_avg_density = 0.; + size_t weighted_net_cnt = 0; + + /* get the average density of all the nets */ + for (const AtomNetId& atom_net : atom_ctx.nlist.nets()) { + /* Skip the nets without any activity annotation */ + if (0 == net_activity.count(atom_net)) { + continue; + } + + /* Only care non-zero density nets */ + if (0. == net_activity.at(atom_net).density) { + continue; + } + + avg_density += net_activity.at(atom_net).density; + net_cnt++; + + /* Consider the weight of fan-out */ + size_t net_weight; + if (0 == std::distance(atom_ctx.nlist.net_sinks(atom_net).begin(), atom_ctx.nlist.net_sinks(atom_net).end())) { + net_weight = 1; + } else { + VTR_ASSERT(0 < std::distance(atom_ctx.nlist.net_sinks(atom_net).begin(), atom_ctx.nlist.net_sinks(atom_net).end())); + net_weight = std::distance(atom_ctx.nlist.net_sinks(atom_net).begin(), atom_ctx.nlist.net_sinks(atom_net).end()); + } + weighted_avg_density += net_activity.at(atom_net).density* net_weight; + weighted_net_cnt += net_weight; + } + avg_density = avg_density / net_cnt; + weighted_avg_density = weighted_avg_density / weighted_net_cnt; + + /* Sort the net density */ + std::vector net_densities; + net_densities.reserve(net_cnt); + for (const AtomNetId& atom_net : atom_ctx.nlist.nets()) { + /* Skip the nets without any activity annotation */ + if (0 == net_activity.count(atom_net)) { + continue; + } + + /* Only care non-zero density nets */ + if (0. == net_activity.at(atom_net).density) { + continue; + } + + net_densities.push_back(net_activity.at(atom_net).density); + } + std::sort(net_densities.begin(), net_densities.end()); + /* Get the median */ + float median_density = 0.; + /* check for even case */ + if (net_cnt % 2 != 0) { + median_density = net_densities[size_t(net_cnt / 2)]; + } else { + median_density = 0.5 * (net_densities[size_t((net_cnt - 1) / 2)] + net_densities[size_t((net_cnt - 1) / 2)]); + } + + /* It may be more reasonable to use median + * But, if median density is 0, we use average density + */ + if ((0. == median_density) && (0. == avg_density)) { + recmd_num_sim_clock_cycle = 1; + VTR_LOG_WARN("All the signal density is zero!\nNumber of clock cycles in simulations are set to be %ld!\n", + recmd_num_sim_clock_cycle); + } else if (0. == avg_density) { + recmd_num_sim_clock_cycle = (int)round(1 / median_density); + } else if (0. == median_density) { + recmd_num_sim_clock_cycle = (int)round(1 / avg_density); + } else { + /* add a sim window size to balance the weight of average density and median density + * In practice, we find that there could be huge difference between avereage and median values + * For a reasonable number of simulation clock cycles, we do this window size. + */ + recmd_num_sim_clock_cycle = (int)round(1 / (sim_window_size * avg_density + (1 - sim_window_size) * median_density )); + } + + VTR_ASSERT(0 < recmd_num_sim_clock_cycle); + + VTR_LOG("Average net density: %.2f\n", avg_density); + VTR_LOG("Median net density: %.2f\n", median_density); + VTR_LOG("Average net density after weighting: %.2f\n", weighted_avg_density); + VTR_LOG("Window size set for Simulation: %.2f\n", sim_window_size); + VTR_LOG("Net density after Window size : %.2f\n", + (sim_window_size * avg_density + (1 - sim_window_size) * median_density)); + VTR_LOG("Recommend no. of clock cycles: %ld\n", recmd_num_sim_clock_cycle); + + return recmd_num_sim_clock_cycle; +} + /******************************************************************** * Annotate simulation setting based on VPR results * - If the operating clock frequency is set to follow the vpr timing results, @@ -60,6 +166,7 @@ bool is_vpr_rr_graph_supported(const RRGraph& rr_graph) { *******************************************************************/ static void annotate_simulation_setting(const AtomContext& atom_ctx, + const std::unordered_map& net_activity, SimulationSetting& sim_setting) { /* Find if the operating frequency is binded to vpr results */ @@ -90,6 +197,19 @@ void annotate_simulation_setting(const AtomContext& atom_ctx, } VTR_LOG("Will apply operating clock frequency %g [MHz] to simulations\n", sim_setting.operating_clock_frequency() / 1e6); + + if (0. == sim_setting.num_clock_cycles()) { + /* Find the number of clock cycles to be used in simulation by average over the signal activity */ + + VTR_LOG("User specified the number of operating clock cycles to be inferred from signal activities\n"); + size_t num_clock_cycles = recommend_num_sim_clock_cycle(atom_ctx, + net_activity, + 0.5); + sim_setting.set_num_clock_cycles(num_clock_cycles); + + VTR_LOG("Will apply %lu operating clock cycles to simulations\n", + sim_setting.num_clock_cycles()); + } } /******************************************************************** @@ -105,6 +225,7 @@ void link_arch(OpenfpgaContext& openfpga_ctx, vtr::ScopedStartFinishTimer timer("Link OpenFPGA architecture to VPR architecture"); + CommandOptionId opt_activity_file = cmd.option("activity_file"); CommandOptionId opt_verbose = cmd.option("verbose"); /* Annotate pb_type graphs @@ -165,8 +286,20 @@ void link_arch(OpenfpgaContext& openfpga_ctx, g_vpr_ctx.placement(), openfpga_ctx.mutable_vpr_placement_annotation()); - /* TODO: Annotate the number of clock cycles and clock frequency by following VPR results */ + /* Read activity file is manadatory in the following flow-run settings + * - When users specify that number of clock cycles + * should be inferred from FPGA implmentation + * - When FPGA-SPICE is enabled + */ + openfpga_ctx.mutable_net_activity() = read_activity(g_vpr_ctx.atom().nlist, + cmd_context.option_value(cmd, opt_activity_file).c_str()); + + /* TODO: Annotate the number of clock cycles and clock frequency by following VPR results + * We SHOULD create a new simulation setting for OpenFPGA use only + * Avoid overwrite the raw data achieved when parsing!!! + */ annotate_simulation_setting(g_vpr_ctx.atom(), + openfpga_ctx.net_activity(), openfpga_ctx.mutable_arch().sim_setting); } diff --git a/openfpga/src/base/openfpga_setup_command.cpp b/openfpga/src/base/openfpga_setup_command.cpp index e84227187..e3354d046 100644 --- a/openfpga/src/base/openfpga_setup_command.cpp +++ b/openfpga/src/base/openfpga_setup_command.cpp @@ -14,9 +14,199 @@ /* begin namespace openfpga */ namespace openfpga { +/******************************************************************** + * - Add a command to Shell environment: read_openfpga_arch + * - Add associated options + * - Add command dependency + *******************************************************************/ +static +ShellCommandId add_openfpga_read_arch_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id) { + Command shell_cmd("read_openfpga_arch"); + + /* Add an option '--file' in short '-f'*/ + CommandOptionId opt_arch_file = shell_cmd.add_option("file", true, "file path to the architecture XML"); + shell_cmd.set_option_short_name(opt_arch_file, "f"); + shell_cmd.set_option_require_value(opt_arch_file, openfpga::OPT_STRING); + + /* Add command 'read_openfpga_arch' to the Shell */ + ShellCommandId shell_cmd_id = shell.add_command(shell_cmd, "read OpenFPGA architecture file"); + shell.set_command_class(shell_cmd_id, cmd_class_id); + shell.set_command_execute_function(shell_cmd_id, read_arch); + + return shell_cmd_id; +} + +/******************************************************************** + * - Add a command to Shell environment: write_openfpga_arch + * - Add associated options + * - Add command dependency + *******************************************************************/ +static +ShellCommandId add_openfpga_write_arch_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id, + const std::vector& dependent_cmds) { + Command shell_cmd("write_openfpga_arch"); + /* Add an option '--file' in short '-f'*/ + CommandOptionId opt_file = shell_cmd.add_option("file", true, "file path to the architecture XML"); + shell_cmd.set_option_short_name(opt_file, "f"); + shell_cmd.set_option_require_value(opt_file, openfpga::OPT_STRING); + + /* Add command 'write_openfpga_arch' to the Shell */ + ShellCommandId shell_cmd_id = shell.add_command(shell_cmd, "write OpenFPGA architecture file"); + shell.set_command_class(shell_cmd_id, cmd_class_id); + shell.set_command_const_execute_function(shell_cmd_id, write_arch); + + /* The 'write_openfpga_arch' command should NOT be executed before 'read_openfpga_arch' */ + shell.set_command_dependency(shell_cmd_id, dependent_cmds); + + return shell_cmd_id; +} + +/******************************************************************** + * - Add a command to Shell environment: link_openfpga_arch + * - Add associated options + * - Add command dependency + *******************************************************************/ +static +ShellCommandId add_openfpga_link_arch_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id, + const std::vector& dependent_cmds) { + Command shell_cmd("link_openfpga_arch"); + + /* Add an option '--activity_file'*/ + CommandOptionId opt_act_file = shell_cmd.add_option("activity_file", true, "file path to the signal activity"); + shell_cmd.set_option_require_value(opt_act_file, openfpga::OPT_STRING); + + /* Add an option '--verbose' */ + shell_cmd.add_option("verbose", false, "Show verbose outputs"); + + /* Add command 'link_openfpga_arch' to the Shell */ + ShellCommandId shell_cmd_id = shell.add_command(shell_cmd, "Bind OpenFPGA architecture to VPR"); + shell.set_command_class(shell_cmd_id, cmd_class_id); + shell.set_command_execute_function(shell_cmd_id, link_arch); + + /* The 'link_openfpga_arch' command should NOT be executed before 'read_openfpga_arch' and 'vpr' */ + shell.set_command_dependency(shell_cmd_id, dependent_cmds); + + return shell_cmd_id; +} + +/******************************************************************** + * - Add a command to Shell environment: check_netlist_naming_conflict + * - Add associated options + * - Add command dependency + *******************************************************************/ +static +ShellCommandId add_openfpga_check_netlist_naming_conflict_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id, + const std::vector& dependent_cmds) { + + Command shell_cmd("check_netlist_naming_conflict"); + + /* Add an option '--fix' */ + shell_cmd.add_option("fix", false, "Apply correction to any conflicts found"); + + /* Add an option '--report' */ + CommandOptionId opt_rpt = shell_cmd.add_option("report", false, "Output a report file about what any correction applied"); + shell_cmd.set_option_require_value(opt_rpt, openfpga::OPT_STRING); + + /* Add command 'check_netlist_naming_conflict' to the Shell */ + ShellCommandId shell_cmd_id = shell.add_command(shell_cmd, "Check any block/net naming in users' BLIF netlist violates the syntax of fabric generator"); + shell.set_command_class(shell_cmd_id, cmd_class_id); + shell.set_command_execute_function(shell_cmd_id, check_netlist_naming_conflict); + + /* The 'link_openfpga_arch' command should NOT be executed before 'vpr' */ + shell.set_command_dependency(shell_cmd_id, dependent_cmds); + + return shell_cmd_id; +} + +/******************************************************************** + * - Add a command to Shell environment: pb_pin_fixup + * - Add associated options + * - Add command dependency + *******************************************************************/ +static +ShellCommandId add_openfpga_pb_pin_fixup_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id, + const std::vector& dependent_cmds) { + + Command shell_cmd("pb_pin_fixup"); + /* Add an option '--verbose' */ + shell_cmd.add_option("verbose", false, "Show verbose outputs"); + + /* Add command 'pb_pin_fixup' to the Shell */ + ShellCommandId shell_cmd_id = shell.add_command(shell_cmd, "Fix up the packing results due to pin swapping during routing stage"); + shell.set_command_class(shell_cmd_id, cmd_class_id); + shell.set_command_execute_function(shell_cmd_id, pb_pin_fixup); + + /* The 'pb_pin_fixup' command should NOT be executed before 'read_openfpga_arch' and 'vpr' */ + shell.set_command_dependency(shell_cmd_id, dependent_cmds); + + return shell_cmd_id; +} + +/******************************************************************** + * - Add a command to Shell environment: lut_truth_table_fixup + * - Add associated options + * - Add command dependency + *******************************************************************/ +static +ShellCommandId add_openfpga_lut_truth_table_fixup_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id, + const std::vector& dependent_cmds) { + + Command shell_cmd("lut_truth_table_fixup"); + /* Add an option '--verbose' */ + shell_cmd.add_option("verbose", false, "Show verbose outputs"); + + /* Add command 'lut_truth_table_fixup' to the Shell */ + ShellCommandId shell_cmd_id = shell.add_command(shell_cmd, "Fix up the truth table of Look-Up Tables due to pin swapping during packing stage"); + shell.set_command_class(shell_cmd_id, cmd_class_id); + shell.set_command_execute_function(shell_cmd_id, lut_truth_table_fixup); + + /* The 'lut_truth_table_fixup' command should NOT be executed before 'read_openfpga_arch' and 'vpr' */ + shell.set_command_dependency(shell_cmd_id, dependent_cmds); + + return shell_cmd_id; +} + +/******************************************************************** + * - Add a command to Shell environment: build_fabric + * - Add associated options + * - Add command dependency + *******************************************************************/ +static +ShellCommandId add_openfpga_build_fabric_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id, + const std::vector& dependent_cmds) { + + Command shell_cmd("build_fabric"); + + /* Add an option '--compress_routing' */ + shell_cmd.add_option("compress_routing", false, "Compress the number of unique routing modules by identifying the unique GSBs"); + + /* Add an option '--duplicate_grid_pin' */ + shell_cmd.add_option("duplicate_grid_pin", false, "Duplicate the pins on the same side of a grid"); + + /* Add an option '--verbose' */ + shell_cmd.add_option("verbose", false, "Show verbose outputs"); + + /* Add command 'compact_routing_hierarchy' to the Shell */ + ShellCommandId shell_cmd_id = shell.add_command(shell_cmd, "Build the FPGA fabric in a graph of modules"); + shell.set_command_class(shell_cmd_id, cmd_class_id); + shell.set_command_execute_function(shell_cmd_id, build_fabric); + + /* The 'build_fabric' command should NOT be executed before 'link_openfpga_arch' */ + shell.set_command_dependency(shell_cmd_id, dependent_cmds); + + return shell_cmd_id; +} + void add_openfpga_setup_commands(openfpga::Shell& shell) { /* Get the unique id of 'vpr' command which is to be used in creating the dependency graph */ - const ShellCommandId& shell_cmd_vpr_id = shell.command(std::string("vpr")); + const ShellCommandId& vpr_cmd_id = shell.command(std::string("vpr")); /* Add a new class of commands */ ShellCommandClassId openfpga_setup_cmd_class = shell.add_command_class("OpenFPGA setup"); @@ -24,119 +214,62 @@ void add_openfpga_setup_commands(openfpga::Shell& shell) { /******************************** * Command 'read_openfpga_arch' */ - Command shell_cmd_read_arch("read_openfpga_arch"); - /* Add an option '--file' in short '-f'*/ - CommandOptionId read_arch_opt_file = shell_cmd_read_arch.add_option("file", true, "file path to the architecture XML"); - shell_cmd_read_arch.set_option_short_name(read_arch_opt_file, "f"); - shell_cmd_read_arch.set_option_require_value(read_arch_opt_file, openfpga::OPT_STRING); - - /* Add command 'read_openfpga_arch' to the Shell */ - ShellCommandId shell_cmd_read_arch_id = shell.add_command(shell_cmd_read_arch, "read OpenFPGA architecture file"); - shell.set_command_class(shell_cmd_read_arch_id, openfpga_setup_cmd_class); - shell.set_command_execute_function(shell_cmd_read_arch_id, read_arch); + ShellCommandId read_arch_cmd_id = add_openfpga_read_arch_command(shell, + openfpga_setup_cmd_class); /******************************** * Command 'write_openfpga_arch' */ - Command shell_cmd_write_arch("write_openfpga_arch"); - /* Add an option '--file' in short '-f'*/ - CommandOptionId write_arch_opt_file = shell_cmd_write_arch.add_option("file", true, "file path to the architecture XML"); - shell_cmd_write_arch.set_option_short_name(write_arch_opt_file, "f"); - shell_cmd_write_arch.set_option_require_value(write_arch_opt_file, openfpga::OPT_STRING); - - /* Add command 'write_openfpga_arch' to the Shell */ - ShellCommandId shell_cmd_write_arch_id = shell.add_command(shell_cmd_write_arch, "write OpenFPGA architecture file"); - shell.set_command_class(shell_cmd_write_arch_id, openfpga_setup_cmd_class); - shell.set_command_const_execute_function(shell_cmd_write_arch_id, write_arch); - /* The 'write_openfpga_arch' command should NOT be executed before 'read_openfpga_arch' */ - shell.set_command_dependency(shell_cmd_write_arch_id, std::vector(1, shell_cmd_read_arch_id)); + std::vector write_arch_dependent_cmds(1, read_arch_cmd_id); + add_openfpga_write_arch_command(shell, + openfpga_setup_cmd_class, + write_arch_dependent_cmds); /******************************** * Command 'link_openfpga_arch' */ - Command shell_cmd_link_openfpga_arch("link_openfpga_arch"); - /* Add an option '--verbose' */ - shell_cmd_link_openfpga_arch.add_option("verbose", false, "Show verbose outputs"); - - /* Add command 'link_openfpga_arch' to the Shell */ - ShellCommandId shell_cmd_link_openfpga_arch_id = shell.add_command(shell_cmd_link_openfpga_arch, "Bind OpenFPGA architecture to VPR"); - shell.set_command_class(shell_cmd_link_openfpga_arch_id, openfpga_setup_cmd_class); - shell.set_command_execute_function(shell_cmd_link_openfpga_arch_id, link_arch); - /* The 'link_openfpga_arch' command should NOT be executed before 'read_openfpga_arch' and 'vpr' */ - std::vector cmd_dependency_link_openfpga_arch; - cmd_dependency_link_openfpga_arch.push_back(shell_cmd_read_arch_id); - cmd_dependency_link_openfpga_arch.push_back(shell_cmd_vpr_id); - shell.set_command_dependency(shell_cmd_link_openfpga_arch_id, cmd_dependency_link_openfpga_arch); - + std::vector link_arch_dependent_cmds; + link_arch_dependent_cmds.push_back(read_arch_cmd_id); + link_arch_dependent_cmds.push_back(vpr_cmd_id); + ShellCommandId link_arch_cmd_id = add_openfpga_link_arch_command(shell, + openfpga_setup_cmd_class, + link_arch_dependent_cmds); /******************************************* * Command 'check_netlist_naming_conflict' */ - Command shell_cmd_check_netlist_naming_conflict("check_netlist_naming_conflict"); - /* Add an option '--fix' */ - shell_cmd_check_netlist_naming_conflict.add_option("fix", false, "Apply correction to any conflicts found"); - /* Add an option '--report' */ - CommandOptionId check_netlist_opt_rpt = shell_cmd_check_netlist_naming_conflict.add_option("report", false, "Output a report file about what any correction applied"); - shell_cmd_check_netlist_naming_conflict.set_option_require_value(check_netlist_opt_rpt, openfpga::OPT_STRING); - - /* Add command 'check_netlist_naming_conflict' to the Shell */ - ShellCommandId shell_cmd_check_netlist_naming_conflict_id = shell.add_command(shell_cmd_check_netlist_naming_conflict, "Check any block/net naming in users' BLIF netlist violates the syntax of fabric generator"); - shell.set_command_class(shell_cmd_check_netlist_naming_conflict_id, openfpga_setup_cmd_class); - shell.set_command_execute_function(shell_cmd_check_netlist_naming_conflict_id, check_netlist_naming_conflict); - /* The 'link_openfpga_arch' command should NOT be executed before 'vpr' */ - std::vector cmd_dependency_check_netlist_naming_conflict(1, shell_cmd_vpr_id); - shell.set_command_dependency(shell_cmd_link_openfpga_arch_id, cmd_dependency_check_netlist_naming_conflict); + std::vector nlist_naming_dependent_cmds; + nlist_naming_dependent_cmds.push_back(vpr_cmd_id); + add_openfpga_check_netlist_naming_conflict_command(shell, + openfpga_setup_cmd_class, + nlist_naming_dependent_cmds); /******************************** * Command 'pb_pin_fixup' */ - Command shell_cmd_pb_pin_fixup("pb_pin_fixup"); - /* Add an option '--verbose' */ - shell_cmd_pb_pin_fixup.add_option("verbose", false, "Show verbose outputs"); - - /* Add command 'pb_pin_fixup' to the Shell */ - ShellCommandId shell_cmd_pb_pin_fixup_id = shell.add_command(shell_cmd_pb_pin_fixup, "Fix up the packing results due to pin swapping during routing stage"); - shell.set_command_class(shell_cmd_pb_pin_fixup_id, openfpga_setup_cmd_class); - shell.set_command_execute_function(shell_cmd_pb_pin_fixup_id, pb_pin_fixup); - /* The 'pb_pin_fixup' command should NOT be executed before 'read_openfpga_arch' and 'vpr' */ - std::vector cmd_dependency_pb_pin_fixup; - cmd_dependency_pb_pin_fixup.push_back(shell_cmd_read_arch_id); - cmd_dependency_pb_pin_fixup.push_back(shell_cmd_vpr_id); - shell.set_command_dependency(shell_cmd_pb_pin_fixup_id, cmd_dependency_pb_pin_fixup); + std::vector pb_pin_fixup_dependent_cmds; + pb_pin_fixup_dependent_cmds.push_back(read_arch_cmd_id); + pb_pin_fixup_dependent_cmds.push_back(vpr_cmd_id); + add_openfpga_pb_pin_fixup_command(shell, + openfpga_setup_cmd_class, + pb_pin_fixup_dependent_cmds); /******************************** * Command 'lut_truth_table_fixup' */ - Command shell_cmd_lut_truth_table_fixup("lut_truth_table_fixup"); - /* Add an option '--verbose' */ - shell_cmd_lut_truth_table_fixup.add_option("verbose", false, "Show verbose outputs"); - - /* Add command 'lut_truth_table_fixup' to the Shell */ - ShellCommandId shell_cmd_lut_truth_table_fixup_id = shell.add_command(shell_cmd_lut_truth_table_fixup, "Fix up the truth table of Look-Up Tables due to pin swapping during packing stage"); - shell.set_command_class(shell_cmd_lut_truth_table_fixup_id, openfpga_setup_cmd_class); - shell.set_command_execute_function(shell_cmd_lut_truth_table_fixup_id, lut_truth_table_fixup); - /* The 'lut_truth_table_fixup' command should NOT be executed before 'read_openfpga_arch' and 'vpr' */ - std::vector cmd_dependency_lut_truth_table_fixup; - cmd_dependency_lut_truth_table_fixup.push_back(shell_cmd_read_arch_id); - cmd_dependency_lut_truth_table_fixup.push_back(shell_cmd_vpr_id); - shell.set_command_dependency(shell_cmd_lut_truth_table_fixup_id, cmd_dependency_lut_truth_table_fixup); - + std::vector lut_tt_fixup_dependent_cmds; + lut_tt_fixup_dependent_cmds.push_back(read_arch_cmd_id); + lut_tt_fixup_dependent_cmds.push_back(vpr_cmd_id); + add_openfpga_lut_truth_table_fixup_command(shell, + openfpga_setup_cmd_class, + lut_tt_fixup_dependent_cmds); /******************************** * Command 'build_fabric' */ - Command shell_cmd_build_fabric("build_fabric"); - /* Add an option '--verbose' */ - shell_cmd_build_fabric.add_option("compress_routing", false, "Compress the number of unique routing modules by identifying the unique GSBs"); - shell_cmd_build_fabric.add_option("duplicate_grid_pin", false, "Duplicate the pins on the same side of a grid"); - shell_cmd_build_fabric.add_option("verbose", false, "Show verbose outputs"); - - /* Add command 'compact_routing_hierarchy' to the Shell */ - ShellCommandId shell_cmd_build_fabric_id = shell.add_command(shell_cmd_build_fabric, "Build the FPGA fabric in a graph of modules"); - shell.set_command_class(shell_cmd_build_fabric_id, openfpga_setup_cmd_class); - shell.set_command_execute_function(shell_cmd_build_fabric_id, build_fabric); - /* The 'build_fabric' command should NOT be executed before 'link_openfpga_arch' */ - std::vector cmd_dependency_build_fabric; - cmd_dependency_build_fabric.push_back(shell_cmd_link_openfpga_arch_id); - shell.set_command_dependency(shell_cmd_build_fabric_id, cmd_dependency_build_fabric); + std::vector build_fabric_dependent_cmds; + build_fabric_dependent_cmds.push_back(link_arch_cmd_id); + add_openfpga_build_fabric_command(shell, + openfpga_setup_cmd_class, + build_fabric_dependent_cmds); } } /* end namespace openfpga */ diff --git a/openfpga/test_script/and_k6_frac.openfpga b/openfpga/test_script/and_k6_frac.openfpga index 51a1c5ae2..8a16f8cb5 100644 --- a/openfpga/test_script/and_k6_frac.openfpga +++ b/openfpga/test_script/and_k6_frac.openfpga @@ -8,7 +8,7 @@ read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml #write_openfpga_arch -f ./arch_echo.xml # Annotate the OpenFPGA architecture to VPR data base -link_openfpga_arch #--verbose +link_openfpga_arch --activity_file ./test_blif/and.act #--verbose # Check and correct any naming conflicts in the BLIF netlist check_netlist_naming_conflict --fix --report ./netlist_renaming.xml From aa66042dfb0a5063f7b3746f4cdad77c056ce59d Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 29 Feb 2020 15:19:02 -0700 Subject: [PATCH 240/645] move simulation setting annotation to a separated source file --- .../annotate_simulation_setting.cpp | 227 ++++++++++++++++++ .../annotation/annotate_simulation_setting.h | 23 ++ openfpga/src/base/openfpga_link_arch.cpp | 163 +------------ 3 files changed, 251 insertions(+), 162 deletions(-) create mode 100644 openfpga/src/annotation/annotate_simulation_setting.cpp create mode 100644 openfpga/src/annotation/annotate_simulation_setting.h diff --git a/openfpga/src/annotation/annotate_simulation_setting.cpp b/openfpga/src/annotation/annotate_simulation_setting.cpp new file mode 100644 index 000000000..a42816297 --- /dev/null +++ b/openfpga/src/annotation/annotate_simulation_setting.cpp @@ -0,0 +1,227 @@ +/******************************************************************** + * This file includes functions that are used to annotate pb_graph_node + * and pb_graph_pins from VPR to OpenFPGA + *******************************************************************/ +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" + +/* Headers from vpr library */ +#include "timing_info.h" +#include "AnalysisDelayCalculator.h" +#include "net_delay.h" + +#include "annotate_simulation_setting.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Find the average signal density for all the nets of user's benchmark + *******************************************************************/ +static +float average_atom_net_signal_density(const AtomContext& atom_ctx, + const std::unordered_map& net_activity) { + float avg_density = 0.; + size_t net_cnt = 0; + + /* get the average density of all the nets */ + for (const AtomNetId& atom_net : atom_ctx.nlist.nets()) { + /* Skip the nets without any activity annotation */ + if (0 == net_activity.count(atom_net)) { + continue; + } + + /* Only care non-zero density nets */ + if (0. == net_activity.at(atom_net).density) { + continue; + } + + avg_density += net_activity.at(atom_net).density; + net_cnt++; + } + + return avg_density / net_cnt; +} + +/******************************************************************** + * Find the average signal density for all the nets of user's benchmark + * by applying a weight to each net density + *******************************************************************/ +static +float average_weighted_atom_net_signal_density(const AtomContext& atom_ctx, + const std::unordered_map& net_activity) { + + float weighted_avg_density = 0.; + size_t weighted_net_cnt = 0; + + /* get the average density of all the nets */ + for (const AtomNetId& atom_net : atom_ctx.nlist.nets()) { + /* Skip the nets without any activity annotation */ + if (0 == net_activity.count(atom_net)) { + continue; + } + + /* Only care non-zero density nets */ + if (0. == net_activity.at(atom_net).density) { + continue; + } + + /* Consider the weight of fan-out */ + size_t net_weight; + if (0 == std::distance(atom_ctx.nlist.net_sinks(atom_net).begin(), atom_ctx.nlist.net_sinks(atom_net).end())) { + net_weight = 1; + } else { + VTR_ASSERT(0 < std::distance(atom_ctx.nlist.net_sinks(atom_net).begin(), atom_ctx.nlist.net_sinks(atom_net).end())); + net_weight = std::distance(atom_ctx.nlist.net_sinks(atom_net).begin(), atom_ctx.nlist.net_sinks(atom_net).end()); + } + weighted_avg_density += net_activity.at(atom_net).density* net_weight; + weighted_net_cnt += net_weight; + } + + return weighted_avg_density / weighted_net_cnt; +} + +/******************************************************************** + * Find median of signal density of all the nets + *******************************************************************/ +static +size_t median_atom_net_signal_density(const AtomContext& atom_ctx, + const std::unordered_map& net_activity) { + /* Sort the net density */ + std::vector net_densities; + + net_densities.reserve(net_activity.size()); + + for (const AtomNetId& atom_net : atom_ctx.nlist.nets()) { + /* Skip the nets without any activity annotation */ + if (0 == net_activity.count(atom_net)) { + continue; + } + + net_densities.push_back(net_activity.at(atom_net).density); + } + std::sort(net_densities.begin(), net_densities.end()); + + /* Get the median */ + /* check for even case */ + if (net_densities.size() % 2 != 0) { + return net_densities[size_t(net_densities.size() / 2)]; + } + + return 0.5 * (net_densities[size_t((net_densities.size() - 1) / 2)] + net_densities[size_t((net_densities.size() - 1) / 2)]); +} + +/******************************************************************** + * Find the number of clock cycles in simulation based on the average signal density + *******************************************************************/ +static +size_t recommend_num_sim_clock_cycle(const AtomContext& atom_ctx, + const std::unordered_map& net_activity, + const float& sim_window_size) { + + float average_density = average_atom_net_signal_density(atom_ctx, net_activity); + float average_weighted_density = average_weighted_atom_net_signal_density(atom_ctx, net_activity); + float median_density = median_atom_net_signal_density(atom_ctx, net_activity); + + VTR_LOG("Average net density: %.2f\n", + average_density); + VTR_LOG("Median net density: %.2f\n", + median_density); + VTR_LOG("Average net density after weighting: %.2f\n", + average_weighted_density); + + /* We have three choices in selecting the number of clock cycles based on signal density + * 1. average signal density + * 2. median signal density + * 3. a mixed of average and median signal density + */ + size_t recmd_num_clock_cycles = 0; + if ( (0. == median_density) + && (0. == average_density) ) { + recmd_num_clock_cycles = 1; + VTR_LOG_WARN("All the signal density is zero!\nNumber of clock cycles in simulations are set to be %ld!\n", + recmd_num_clock_cycles); + } else if (0. == average_density) { + recmd_num_clock_cycles = (size_t)round(1 / median_density); + } else if (0. == median_density) { + recmd_num_clock_cycles = (size_t)round(1 / average_density); + } else { + /* add a sim window size to balance the weight of average density and median density + * In practice, we find that there could be huge difference between avereage and median values + * For a reasonable number of simulation clock cycles, we do this window size. + */ + recmd_num_clock_cycles = (size_t)round(1 / (sim_window_size * average_density + (1 - sim_window_size) * median_density )); + + VTR_LOG("Window size set for simulation: %.2f\n", + sim_window_size); + VTR_LOG("Net density after applying window size : %.2f\n", + (sim_window_size * average_density + (1 - sim_window_size) * median_density)); + } + + VTR_ASSERT(0 < recmd_num_clock_cycles); + + return recmd_num_clock_cycles; +} + +/******************************************************************** + * Annotate simulation setting based on VPR results + * - If the operating clock frequency is set to follow the vpr timing results, + * we will set a new operating clock frequency here + * - If the number of clock cycles in simulation is set to be automatically determined, + * we will infer the number based on the average signal density + *******************************************************************/ +void annotate_simulation_setting(const AtomContext& atom_ctx, + const std::unordered_map& net_activity, + SimulationSetting& sim_setting) { + + /* Find if the operating frequency is binded to vpr results */ + if (0. == sim_setting.operating_clock_frequency()) { + VTR_LOG("User specified the operating clock frequency to use VPR results\n"); + /* Run timing analysis and collect critical path delay + * This code is copied from function vpr_analysis() in vpr_api.h + * Should keep updated to latest VPR code base + * Note: + * - MUST mention in documentation that VPR should be run in timing enabled mode + */ + vtr::vector net_delay; + vtr::t_chunk net_delay_ch; + /* Load the net delays */ + net_delay = alloc_net_delay(&net_delay_ch); + load_net_delay_from_routing(net_delay); + + /* Do final timing analysis */ + auto analysis_delay_calc = std::make_shared(atom_ctx.nlist, atom_ctx.lookup, net_delay); + auto timing_info = make_setup_hold_timing_info(analysis_delay_calc); + timing_info->update(); + + /* Get critical path delay. Update simulation settings */ + float T_crit = timing_info->least_slack_critical_path().delay() * (1. + sim_setting.operating_clock_frequency_slack()); + sim_setting.set_operating_clock_frequency(1 / T_crit); + VTR_LOG("Use VPR critical path delay %g [ns] with a %g [%] slack in OpenFPGA.\n", + T_crit / 1e9, sim_setting.operating_clock_frequency_slack() * 100); + } + VTR_LOG("Will apply operating clock frequency %g [MHz] to simulations\n", + sim_setting.operating_clock_frequency() / 1e6); + + if (0. == sim_setting.num_clock_cycles()) { + /* Find the number of clock cycles to be used in simulation + * by average over the signal activity + */ + VTR_LOG("User specified the number of operating clock cycles to be inferred from signal activities\n"); + + /* Use a fixed simulation window size now. TODO: this could be specified by users */ + size_t num_clock_cycles = recommend_num_sim_clock_cycle(atom_ctx, + net_activity, + 0.5); + sim_setting.set_num_clock_cycles(num_clock_cycles); + + VTR_LOG("Will apply %lu operating clock cycles to simulations\n", + sim_setting.num_clock_cycles()); + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/annotation/annotate_simulation_setting.h b/openfpga/src/annotation/annotate_simulation_setting.h new file mode 100644 index 000000000..b4db463a9 --- /dev/null +++ b/openfpga/src/annotation/annotate_simulation_setting.h @@ -0,0 +1,23 @@ +#ifndef ANNOTATE_SIMULATION_SETTING_H +#define ANNOTATE_SIMULATION_SETTING_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "vpr_context.h" +#include "openfpga_context.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void annotate_simulation_setting(const AtomContext& atom_ctx, + const std::unordered_map& net_activity, + SimulationSetting& sim_setting); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/base/openfpga_link_arch.cpp b/openfpga/src/base/openfpga_link_arch.cpp index fe89b7024..e0e52f878 100644 --- a/openfpga/src/base/openfpga_link_arch.cpp +++ b/openfpga/src/base/openfpga_link_arch.cpp @@ -2,8 +2,6 @@ * This file includes functions to read an OpenFPGA architecture file * which are built on the libarchopenfpga library *******************************************************************/ -#include -#include /* Headers from vtrutil library */ #include "vtr_time.h" @@ -11,9 +9,6 @@ #include "vtr_log.h" /* Headers from vpr library */ -#include "timing_info.h" -#include "AnalysisDelayCalculator.h" -#include "net_delay.h" #include "read_activity.h" #include "vpr_device_annotation.h" @@ -22,6 +17,7 @@ #include "annotate_pb_graph.h" #include "annotate_routing.h" #include "annotate_rr_graph.h" +#include "annotate_simulation_setting.h" #include "mux_library_builder.h" #include "build_tile_direct.h" #include "annotate_placement.h" @@ -55,163 +51,6 @@ bool is_vpr_rr_graph_supported(const RRGraph& rr_graph) { return true; } -/******************************************************************** - * Find the number of clock cycles in simulation based on the average signal density - *******************************************************************/ -static -size_t recommend_num_sim_clock_cycle(const AtomContext& atom_ctx, - const std::unordered_map& net_activity, - const float& sim_window_size) { - size_t recmd_num_sim_clock_cycle = 0; - - float avg_density = 0.; - size_t net_cnt = 0; - - float weighted_avg_density = 0.; - size_t weighted_net_cnt = 0; - - /* get the average density of all the nets */ - for (const AtomNetId& atom_net : atom_ctx.nlist.nets()) { - /* Skip the nets without any activity annotation */ - if (0 == net_activity.count(atom_net)) { - continue; - } - - /* Only care non-zero density nets */ - if (0. == net_activity.at(atom_net).density) { - continue; - } - - avg_density += net_activity.at(atom_net).density; - net_cnt++; - - /* Consider the weight of fan-out */ - size_t net_weight; - if (0 == std::distance(atom_ctx.nlist.net_sinks(atom_net).begin(), atom_ctx.nlist.net_sinks(atom_net).end())) { - net_weight = 1; - } else { - VTR_ASSERT(0 < std::distance(atom_ctx.nlist.net_sinks(atom_net).begin(), atom_ctx.nlist.net_sinks(atom_net).end())); - net_weight = std::distance(atom_ctx.nlist.net_sinks(atom_net).begin(), atom_ctx.nlist.net_sinks(atom_net).end()); - } - weighted_avg_density += net_activity.at(atom_net).density* net_weight; - weighted_net_cnt += net_weight; - } - avg_density = avg_density / net_cnt; - weighted_avg_density = weighted_avg_density / weighted_net_cnt; - - /* Sort the net density */ - std::vector net_densities; - net_densities.reserve(net_cnt); - for (const AtomNetId& atom_net : atom_ctx.nlist.nets()) { - /* Skip the nets without any activity annotation */ - if (0 == net_activity.count(atom_net)) { - continue; - } - - /* Only care non-zero density nets */ - if (0. == net_activity.at(atom_net).density) { - continue; - } - - net_densities.push_back(net_activity.at(atom_net).density); - } - std::sort(net_densities.begin(), net_densities.end()); - /* Get the median */ - float median_density = 0.; - /* check for even case */ - if (net_cnt % 2 != 0) { - median_density = net_densities[size_t(net_cnt / 2)]; - } else { - median_density = 0.5 * (net_densities[size_t((net_cnt - 1) / 2)] + net_densities[size_t((net_cnt - 1) / 2)]); - } - - /* It may be more reasonable to use median - * But, if median density is 0, we use average density - */ - if ((0. == median_density) && (0. == avg_density)) { - recmd_num_sim_clock_cycle = 1; - VTR_LOG_WARN("All the signal density is zero!\nNumber of clock cycles in simulations are set to be %ld!\n", - recmd_num_sim_clock_cycle); - } else if (0. == avg_density) { - recmd_num_sim_clock_cycle = (int)round(1 / median_density); - } else if (0. == median_density) { - recmd_num_sim_clock_cycle = (int)round(1 / avg_density); - } else { - /* add a sim window size to balance the weight of average density and median density - * In practice, we find that there could be huge difference between avereage and median values - * For a reasonable number of simulation clock cycles, we do this window size. - */ - recmd_num_sim_clock_cycle = (int)round(1 / (sim_window_size * avg_density + (1 - sim_window_size) * median_density )); - } - - VTR_ASSERT(0 < recmd_num_sim_clock_cycle); - - VTR_LOG("Average net density: %.2f\n", avg_density); - VTR_LOG("Median net density: %.2f\n", median_density); - VTR_LOG("Average net density after weighting: %.2f\n", weighted_avg_density); - VTR_LOG("Window size set for Simulation: %.2f\n", sim_window_size); - VTR_LOG("Net density after Window size : %.2f\n", - (sim_window_size * avg_density + (1 - sim_window_size) * median_density)); - VTR_LOG("Recommend no. of clock cycles: %ld\n", recmd_num_sim_clock_cycle); - - return recmd_num_sim_clock_cycle; -} - -/******************************************************************** - * Annotate simulation setting based on VPR results - * - If the operating clock frequency is set to follow the vpr timing results, - * we will set a new operating clock frequency here - * - If the number of clock cycles in simulation is set to be automatically determined, - * we will infer the number based on the average signal density - *******************************************************************/ -static -void annotate_simulation_setting(const AtomContext& atom_ctx, - const std::unordered_map& net_activity, - SimulationSetting& sim_setting) { - - /* Find if the operating frequency is binded to vpr results */ - if (0. == sim_setting.operating_clock_frequency()) { - VTR_LOG("User specified the operating clock frequency to use VPR results\n"); - /* Run timing analysis and collect critical path delay - * This code is copied from function vpr_analysis() in vpr_api.h - * Should keep updated to latest VPR code base - * Note: - * - MUST mention in documentation that VPR should be run in timing enabled mode - */ - vtr::vector net_delay; - vtr::t_chunk net_delay_ch; - /* Load the net delays */ - net_delay = alloc_net_delay(&net_delay_ch); - load_net_delay_from_routing(net_delay); - - /* Do final timing analysis */ - auto analysis_delay_calc = std::make_shared(atom_ctx.nlist, atom_ctx.lookup, net_delay); - auto timing_info = make_setup_hold_timing_info(analysis_delay_calc); - timing_info->update(); - - /* Get critical path delay. Update simulation settings */ - float T_crit = timing_info->least_slack_critical_path().delay() * (1. + sim_setting.operating_clock_frequency_slack()); - sim_setting.set_operating_clock_frequency(1 / T_crit); - VTR_LOG("Use VPR critical path delay %g [ns] with a %g [%] slack in OpenFPGA.\n", - T_crit / 1e9, sim_setting.operating_clock_frequency_slack() * 100); - } - VTR_LOG("Will apply operating clock frequency %g [MHz] to simulations\n", - sim_setting.operating_clock_frequency() / 1e6); - - if (0. == sim_setting.num_clock_cycles()) { - /* Find the number of clock cycles to be used in simulation by average over the signal activity */ - - VTR_LOG("User specified the number of operating clock cycles to be inferred from signal activities\n"); - size_t num_clock_cycles = recommend_num_sim_clock_cycle(atom_ctx, - net_activity, - 0.5); - sim_setting.set_num_clock_cycles(num_clock_cycles); - - VTR_LOG("Will apply %lu operating clock cycles to simulations\n", - sim_setting.num_clock_cycles()); - } -} - /******************************************************************** * Top-level function to link openfpga architecture to VPR, including: * - physical pb_type From a17c14c363551c94939bd7f0cbd4261f2f2115bf Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 2 Mar 2020 10:39:19 -0700 Subject: [PATCH 241/645] clean-up command addition and add fabric bitstream building to sample script --- .../src/base/openfpga_bitstream_command.cpp | 132 ++++++++++++------ openfpga/src/base/openfpga_sdc_command.cpp | 23 +-- openfpga/src/base/openfpga_setup_command.cpp | 19 ++- .../src/base/openfpga_verilog_command.cpp | 40 +++--- openfpga/test_script/and_k6_frac.openfpga | 5 +- 5 files changed, 144 insertions(+), 75 deletions(-) diff --git a/openfpga/src/base/openfpga_bitstream_command.cpp b/openfpga/src/base/openfpga_bitstream_command.cpp index 96201b6c7..b64696d79 100644 --- a/openfpga/src/base/openfpga_bitstream_command.cpp +++ b/openfpga/src/base/openfpga_bitstream_command.cpp @@ -11,6 +11,88 @@ /* begin namespace openfpga */ namespace openfpga { +/******************************************************************** + * - Add a command to Shell environment: repack + * - Add associated options + * - Add command dependency + *******************************************************************/ +static +ShellCommandId add_openfpga_repack_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id, + const std::vector& dependent_cmds) { + Command shell_cmd("repack"); + /* Add an option '--verbose' */ + shell_cmd.add_option("verbose", false, "Enable verbose output"); + + /* Add command 'repack' to the Shell */ + ShellCommandId shell_cmd_id = shell.add_command(shell_cmd, "Pack physical programmable logic blocks"); + shell.set_command_class(shell_cmd_id, cmd_class_id); + shell.set_command_execute_function(shell_cmd_id, repack); + + /* Add command dependency to the Shell */ + shell.set_command_dependency(shell_cmd_id, dependent_cmds); + + return shell_cmd_id; +} + +/******************************************************************** + * - Add a command to Shell environment: build_architecture_bitstream + * - Add associated options + * - Add command dependency + *******************************************************************/ +static +ShellCommandId add_openfpga_arch_bitstream_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id, + const std::vector& dependent_cmds) { + Command shell_cmd("build_architecture_bitstream"); + + /* Add an option '--file' in short '-f'*/ + CommandOptionId opt_file = shell_cmd.add_option("file", true, "file path to output the bitstream database"); + shell_cmd.set_option_short_name(opt_file, "f"); + shell_cmd.set_option_require_value(opt_file, openfpga::OPT_STRING); + + /* Add an option '--verbose' */ + shell_cmd.add_option("verbose", false, "Enable verbose output"); + + /* Add command 'build_architecture_bitstream' to the Shell */ + ShellCommandId shell_cmd_id = shell.add_command(shell_cmd, "Build fabric-independent bitstream database"); + shell.set_command_class(shell_cmd_id, cmd_class_id); + shell.set_command_execute_function(shell_cmd_id, fpga_bitstream); + + /* Add command dependency to the Shell */ + shell.set_command_dependency(shell_cmd_id, dependent_cmds); + + return shell_cmd_id; +} + +/******************************************************************** + * - Add a command to Shell environment: build_fabric_bitstream + * - Add associated options + * - Add command dependency + *******************************************************************/ +static +ShellCommandId add_openfpga_fabric_bitstream_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id, + const std::vector& dependent_cmds) { + Command shell_cmd("build_fabric_bitstream"); + + /* Add an option '--verbose' */ + shell_cmd.add_option("verbose", false, "Enable verbose output"); + + /* Add command 'fabric_bitstream' to the Shell */ + ShellCommandId shell_cmd_id = shell.add_command(shell_cmd, "Reorganize the fabric-independent bitstream for the FPGA fabric created by FPGA-Verilog"); + shell.set_command_class(shell_cmd_id, cmd_class_id); + shell.set_command_execute_function(shell_cmd_id, build_fabric_bitstream); + + /* Add command dependency to the Shell */ + shell.set_command_dependency(shell_cmd_id, dependent_cmds); + + return shell_cmd_id; +} + +/******************************************************************** + * Top-level function to add all the commands related to FPGA-Bitstream + *******************************************************************/ void add_openfpga_bitstream_commands(openfpga::Shell& shell) { /* Get the unique id of 'build_fabric' command which is to be used in creating the dependency graph */ const ShellCommandId& shell_cmd_build_fabric_id = shell.command(std::string("build_fabric")); @@ -21,58 +103,26 @@ void add_openfpga_bitstream_commands(openfpga::Shell& shell) { /******************************** * Command 'repack' */ - Command shell_cmd_repack("repack"); - /* Add an option '--verbose' */ - shell_cmd_repack.add_option("verbose", false, "Enable verbose output"); - - /* Add command 'repack' to the Shell */ - ShellCommandId shell_cmd_repack_id = shell.add_command(shell_cmd_repack, "Pack physical programmable logic blocks"); - shell.set_command_class(shell_cmd_repack_id, openfpga_bitstream_cmd_class); - shell.set_command_execute_function(shell_cmd_repack_id, repack); - /* The 'repack' command should NOT be executed before 'build_fabric' */ std::vector cmd_dependency_repack; cmd_dependency_repack.push_back(shell_cmd_build_fabric_id); - shell.set_command_dependency(shell_cmd_repack_id, cmd_dependency_repack); + ShellCommandId shell_cmd_repack_id = add_openfpga_repack_command(shell, openfpga_bitstream_cmd_class, cmd_dependency_repack); /******************************** - * Command 'fpga_bitstream' + * Command 'build_architecture_bitstream' */ - Command shell_cmd_fpga_bitstream("fpga_bitstream"); - - /* Add an option '--file' in short '-f'*/ - CommandOptionId fpga_bitstream_opt_file = shell_cmd_fpga_bitstream.add_option("file", true, "file path to output the bitstream database"); - shell_cmd_fpga_bitstream.set_option_short_name(fpga_bitstream_opt_file, "f"); - shell_cmd_fpga_bitstream.set_option_require_value(fpga_bitstream_opt_file, openfpga::OPT_STRING); - /* Add an option '--verbose' */ - shell_cmd_fpga_bitstream.add_option("verbose", false, "Enable verbose output"); - - /* Add command 'fpga_bitstream' to the Shell */ - ShellCommandId shell_cmd_fpga_bitstream_id = shell.add_command(shell_cmd_fpga_bitstream, "Build bitstream database"); - shell.set_command_class(shell_cmd_fpga_bitstream_id, openfpga_bitstream_cmd_class); - shell.set_command_execute_function(shell_cmd_fpga_bitstream_id, fpga_bitstream); - - /* The 'fpga_bitstream' command should NOT be executed before 'repack' */ - std::vector cmd_dependency_fpga_bitstream; - cmd_dependency_fpga_bitstream.push_back(shell_cmd_repack_id); - shell.set_command_dependency(shell_cmd_fpga_bitstream_id, cmd_dependency_fpga_bitstream); + /* The 'build_architecture_bitstream' command should NOT be executed before 'repack' */ + std::vector cmd_dependency_arch_bitstream; + cmd_dependency_arch_bitstream.push_back(shell_cmd_repack_id); + ShellCommandId shell_cmd_arch_bitstream_id = add_openfpga_arch_bitstream_command(shell, openfpga_bitstream_cmd_class, cmd_dependency_arch_bitstream); /******************************** * Command 'build_fabric_bitstream' */ - Command shell_cmd_fabric_bitstream("build_fabric_bitstream"); - /* Add an option '--verbose' */ - shell_cmd_fabric_bitstream.add_option("verbose", false, "Enable verbose output"); - - /* Add command 'fabric_bitstream' to the Shell */ - ShellCommandId shell_cmd_fabric_bitstream_id = shell.add_command(shell_cmd_fabric_bitstream, "Reorganize the fabric-independent bitstream for the FPGA fabric created by FPGA-Verilog"); - shell.set_command_class(shell_cmd_fabric_bitstream_id, openfpga_bitstream_cmd_class); - shell.set_command_execute_function(shell_cmd_fabric_bitstream_id, build_fabric_bitstream); - - /* The 'fabric_bitstream' command should NOT be executed before 'fpga_bitstream' */ + /* The 'build_fabric_bitstream' command should NOT be executed before 'build_architecture_bitstream' */ std::vector cmd_dependency_fabric_bitstream; - cmd_dependency_fabric_bitstream.push_back(shell_cmd_fpga_bitstream_id); - shell.set_command_dependency(shell_cmd_fabric_bitstream_id, cmd_dependency_fabric_bitstream); + cmd_dependency_fabric_bitstream.push_back(shell_cmd_arch_bitstream_id); + add_openfpga_fabric_bitstream_command(shell, openfpga_bitstream_cmd_class, cmd_dependency_fabric_bitstream); } } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_sdc_command.cpp b/openfpga/src/base/openfpga_sdc_command.cpp index cf31d73b0..799fad4e6 100644 --- a/openfpga/src/base/openfpga_sdc_command.cpp +++ b/openfpga/src/base/openfpga_sdc_command.cpp @@ -16,9 +16,9 @@ namespace openfpga { * - Add command dependency *******************************************************************/ static -void add_openfpga_write_pnr_sdc_command(openfpga::Shell& shell, - const ShellCommandClassId& cmd_class_id, - const ShellCommandId& shell_cmd_build_fabric_id) { +ShellCommandId add_openfpga_write_pnr_sdc_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id, + const std::vector& dependent_cmds) { Command shell_cmd("write_pnr_sdc"); /* Add an option '--file' in short '-f'*/ @@ -55,25 +55,28 @@ void add_openfpga_write_pnr_sdc_command(openfpga::Shell& shell, shell.set_command_class(shell_cmd_id, cmd_class_id); shell.set_command_execute_function(shell_cmd_id, write_pnr_sdc); - /* The 'build_fabric' command should NOT be executed before 'link_openfpga_arch' */ - std::vector cmd_dependency; - cmd_dependency.push_back(shell_cmd_build_fabric_id); - shell.set_command_dependency(shell_cmd_id, cmd_dependency); + /* Add command dependency to the Shell */ + shell.set_command_dependency(shell_cmd_id, dependent_cmds); + + return shell_cmd_id; } void add_openfpga_sdc_commands(openfpga::Shell& shell) { /* Get the unique id of 'build_fabric' command which is to be used in creating the dependency graph */ - const ShellCommandId& shell_cmd_build_fabric_id = shell.command(std::string("build_fabric")); + const ShellCommandId& build_fabric_id = shell.command(std::string("build_fabric")); /* Add a new class of commands */ ShellCommandClassId openfpga_sdc_cmd_class = shell.add_command_class("FPGA-SDC"); /******************************** - * Command 'write_fabric_verilog' + * Command 'write_pnr_sdc' */ + /* The 'write_pnr_sdc' command should NOT be executed before 'build_fabric' */ + std::vector pnr_sdc_cmd_dependency; + pnr_sdc_cmd_dependency.push_back(build_fabric_id); add_openfpga_write_pnr_sdc_command(shell, openfpga_sdc_cmd_class, - shell_cmd_build_fabric_id); + pnr_sdc_cmd_dependency); } } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_setup_command.cpp b/openfpga/src/base/openfpga_setup_command.cpp index e3354d046..0d7393c26 100644 --- a/openfpga/src/base/openfpga_setup_command.cpp +++ b/openfpga/src/base/openfpga_setup_command.cpp @@ -57,7 +57,7 @@ ShellCommandId add_openfpga_write_arch_command(openfpga::Shell& shell.set_command_class(shell_cmd_id, cmd_class_id); shell.set_command_const_execute_function(shell_cmd_id, write_arch); - /* The 'write_openfpga_arch' command should NOT be executed before 'read_openfpga_arch' */ + /* Add command dependency to the Shell */ shell.set_command_dependency(shell_cmd_id, dependent_cmds); return shell_cmd_id; @@ -86,7 +86,7 @@ ShellCommandId add_openfpga_link_arch_command(openfpga::Shell& shell.set_command_class(shell_cmd_id, cmd_class_id); shell.set_command_execute_function(shell_cmd_id, link_arch); - /* The 'link_openfpga_arch' command should NOT be executed before 'read_openfpga_arch' and 'vpr' */ + /* Add command dependency to the Shell */ shell.set_command_dependency(shell_cmd_id, dependent_cmds); return shell_cmd_id; @@ -116,7 +116,7 @@ ShellCommandId add_openfpga_check_netlist_naming_conflict_command(openfpga::Shel shell.set_command_class(shell_cmd_id, cmd_class_id); shell.set_command_execute_function(shell_cmd_id, check_netlist_naming_conflict); - /* The 'link_openfpga_arch' command should NOT be executed before 'vpr' */ + /* Add command dependency to the Shell */ shell.set_command_dependency(shell_cmd_id, dependent_cmds); return shell_cmd_id; @@ -133,6 +133,7 @@ ShellCommandId add_openfpga_pb_pin_fixup_command(openfpga::Shell& dependent_cmds) { Command shell_cmd("pb_pin_fixup"); + /* Add an option '--verbose' */ shell_cmd.add_option("verbose", false, "Show verbose outputs"); @@ -141,7 +142,7 @@ ShellCommandId add_openfpga_pb_pin_fixup_command(openfpga::Shell& shell) { /******************************** * Command 'write_openfpga_arch' */ + /* The 'write_openfpga_arch' command should NOT be executed before 'read_openfpga_arch' */ std::vector write_arch_dependent_cmds(1, read_arch_cmd_id); add_openfpga_write_arch_command(shell, openfpga_setup_cmd_class, @@ -228,6 +230,7 @@ void add_openfpga_setup_commands(openfpga::Shell& shell) { /******************************** * Command 'link_openfpga_arch' */ + /* The 'link_openfpga_arch' command should NOT be executed before 'vpr' */ std::vector link_arch_dependent_cmds; link_arch_dependent_cmds.push_back(read_arch_cmd_id); link_arch_dependent_cmds.push_back(vpr_cmd_id); @@ -237,6 +240,7 @@ void add_openfpga_setup_commands(openfpga::Shell& shell) { /******************************************* * Command 'check_netlist_naming_conflict' */ + /* The 'check_netlist_naming_conflict' command should NOT be executed before 'vpr' */ std::vector nlist_naming_dependent_cmds; nlist_naming_dependent_cmds.push_back(vpr_cmd_id); add_openfpga_check_netlist_naming_conflict_command(shell, @@ -246,6 +250,7 @@ void add_openfpga_setup_commands(openfpga::Shell& shell) { /******************************** * Command 'pb_pin_fixup' */ + /* The 'pb_pin_fixup' command should NOT be executed before 'read_openfpga_arch' and 'vpr' */ std::vector pb_pin_fixup_dependent_cmds; pb_pin_fixup_dependent_cmds.push_back(read_arch_cmd_id); pb_pin_fixup_dependent_cmds.push_back(vpr_cmd_id); @@ -256,6 +261,7 @@ void add_openfpga_setup_commands(openfpga::Shell& shell) { /******************************** * Command 'lut_truth_table_fixup' */ + /* The 'lut_truth_table_fixup' command should NOT be executed before 'read_openfpga_arch' and 'vpr' */ std::vector lut_tt_fixup_dependent_cmds; lut_tt_fixup_dependent_cmds.push_back(read_arch_cmd_id); lut_tt_fixup_dependent_cmds.push_back(vpr_cmd_id); @@ -265,6 +271,7 @@ void add_openfpga_setup_commands(openfpga::Shell& shell) { /******************************** * Command 'build_fabric' */ + /* The 'build_fabric' command should NOT be executed before 'link_openfpga_arch' */ std::vector build_fabric_dependent_cmds; build_fabric_dependent_cmds.push_back(link_arch_cmd_id); add_openfpga_build_fabric_command(shell, diff --git a/openfpga/src/base/openfpga_verilog_command.cpp b/openfpga/src/base/openfpga_verilog_command.cpp index 3d4f7784b..71fad80f0 100644 --- a/openfpga/src/base/openfpga_verilog_command.cpp +++ b/openfpga/src/base/openfpga_verilog_command.cpp @@ -17,9 +17,9 @@ namespace openfpga { * - Add command dependency *******************************************************************/ static -void add_openfpga_write_fabric_verilog_command(openfpga::Shell& shell, - const ShellCommandClassId& cmd_class_id, - const ShellCommandId& shell_cmd_build_fabric_id) { +ShellCommandId add_openfpga_write_fabric_verilog_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id, + const std::vector& dependent_cmds) { Command shell_cmd("write_fabric_verilog"); /* Add an option '--file' in short '-f'*/ @@ -50,10 +50,10 @@ void add_openfpga_write_fabric_verilog_command(openfpga::Shell& shell.set_command_class(shell_cmd_id, cmd_class_id); shell.set_command_execute_function(shell_cmd_id, write_fabric_verilog); - /* The 'build_fabric' command should NOT be executed before 'link_openfpga_arch' */ - std::vector cmd_dependency; - cmd_dependency.push_back(shell_cmd_build_fabric_id); - shell.set_command_dependency(shell_cmd_id, cmd_dependency); + /* Add command dependency to the Shell */ + shell.set_command_dependency(shell_cmd_id, dependent_cmds); + + return shell_cmd_id; } /******************************************************************** @@ -62,9 +62,9 @@ void add_openfpga_write_fabric_verilog_command(openfpga::Shell& * - Add command dependency *******************************************************************/ static -void add_openfpga_write_verilog_testbench_command(openfpga::Shell& shell, - const ShellCommandClassId& cmd_class_id, - const ShellCommandId& shell_cmd_build_fabric_id) { +ShellCommandId add_openfpga_write_verilog_testbench_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id, + const std::vector& dependent_cmds) { Command shell_cmd("write_verilog_testbench"); /* Add an option '--file' in short '-f'*/ @@ -97,15 +97,15 @@ void add_openfpga_write_verilog_testbench_command(openfpga::Shell cmd_dependency; - cmd_dependency.push_back(shell_cmd_build_fabric_id); - shell.set_command_dependency(shell_cmd_id, cmd_dependency); + /* Add command dependency to the Shell */ + shell.set_command_dependency(shell_cmd_id, dependent_cmds); + + return shell_cmd_id; } void add_openfpga_verilog_commands(openfpga::Shell& shell) { /* Get the unique id of 'build_fabric' command which is to be used in creating the dependency graph */ - const ShellCommandId& shell_cmd_build_fabric_id = shell.command(std::string("build_fabric")); + const ShellCommandId& build_fabric_cmd_id = shell.command(std::string("build_fabric")); /* Add a new class of commands */ ShellCommandClassId openfpga_verilog_cmd_class = shell.add_command_class("FPGA-Verilog"); @@ -113,16 +113,22 @@ void add_openfpga_verilog_commands(openfpga::Shell& shell) { /******************************** * Command 'write_fabric_verilog' */ + /* The 'write_fabric_verilog' command should NOT be executed before 'build_fabric' */ + std::vector fabric_verilog_dependent_cmds; + fabric_verilog_dependent_cmds.push_back(build_fabric_cmd_id); add_openfpga_write_fabric_verilog_command(shell, openfpga_verilog_cmd_class, - shell_cmd_build_fabric_id); + fabric_verilog_dependent_cmds); /******************************** * Command 'write_verilog_testbench' */ + /* The command 'write_verilog_testbench' should NOT be executed before 'build_fabric' */ + std::vector verilog_testbench_dependent_cmds; + verilog_testbench_dependent_cmds.push_back(build_fabric_cmd_id); add_openfpga_write_verilog_testbench_command(shell, openfpga_verilog_cmd_class, - shell_cmd_build_fabric_id); + verilog_testbench_dependent_cmds); } } /* end namespace openfpga */ diff --git a/openfpga/test_script/and_k6_frac.openfpga b/openfpga/test_script/and_k6_frac.openfpga index 8a16f8cb5..1018887f1 100644 --- a/openfpga/test_script/and_k6_frac.openfpga +++ b/openfpga/test_script/and_k6_frac.openfpga @@ -31,7 +31,10 @@ repack #--verbose # Build the bitstream # - Output the fabric-independent bitstream to a file -fpga_bitstream --verbose --file /var/tmp/xtang/openfpga_test_src/fabric_indepenent_bitstream.xml +build_architecture_bitstream --verbose --file /var/tmp/xtang/openfpga_test_src/fabric_indepenent_bitstream.xml + +# Build fabric-dependent bitstream +build_fabric_bitstream --verbose # Write the Verilog netlist for FPGA fabric # - Enable the use of explicit port mapping in Verilog netlist From 543cff58b9ecd6cbaded489988387e3e9f2fd3a5 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 2 Mar 2020 13:44:08 -0700 Subject: [PATCH 242/645] start porting analysis SDC writer --- .../fpga_sdc/analysis_sdc_writer_utils.cpp | 236 ++++++++++++++++++ .../src/fpga_sdc/analysis_sdc_writer_utils.h | 57 +++++ 2 files changed, 293 insertions(+) create mode 100644 openfpga/src/fpga_sdc/analysis_sdc_writer_utils.cpp create mode 100644 openfpga/src/fpga_sdc/analysis_sdc_writer_utils.h diff --git a/openfpga/src/fpga_sdc/analysis_sdc_writer_utils.cpp b/openfpga/src/fpga_sdc/analysis_sdc_writer_utils.cpp new file mode 100644 index 000000000..511271915 --- /dev/null +++ b/openfpga/src/fpga_sdc/analysis_sdc_writer_utils.cpp @@ -0,0 +1,236 @@ +/******************************************************************** + * This file includes most utilized functions + * that are used to output a SDC file + * in order to constrain a FPGA fabric (P&Red netlist) mapped to a benchmark + *******************************************************************/ + +/* Headers from vtrutil library */ +#include "vtr_assert.h" + +/* Headers from openfpgautil library */ +#include "openfpga_digest.h" + +#include "sdc_writer_utils.h" +#include "analysis_sdc_writer_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Identify if a node should be disabled during analysis SDC generation + *******************************************************************/ +bool is_rr_node_to_be_disable_for_analysis(const VprRoutingAnnotation& routing_annotation, + const RRNodeId& cur_rr_node) { + /* Conditions to enable timing analysis for a node + * 1st condition: it have a valid net_number + * TODO: 2nd condition: it is not an parasitic net + */ + return ClusterNetId::INVALID() == routing_annotation.rr_node_net(cur_rr_node); +} + +/******************************************************************** + * Disable all the unused inputs of routing multiplexers, which are not used by benchmark + * Here, we start from each input of a routing module, and traverse forward to the sink + * port of the module net whose source is the input + * We will find the instance name which is the parent of the sink port, and search the + * net id through the instance_name_to_net_map + * The the net id does not match the net id of this input, we will disable the sink port! + * + * parent_module + * +----------------------- + * | MUX instance A + * | +----------- + * input_port--->|--+---x-->| sink port (disable! net_id = Y) + * (net_id = X) | | +---------- + * | | MUX instance B + * | | +---------- + * | +------>| sink port (do not disable! net_id = X) + * + *******************************************************************/ +void disable_analysis_module_input_pin_net_sinks(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const std::string& parent_instance_name, + const ModulePortId& module_input_port, + const size_t& module_input_pin, + const VprRoutingAnnotation& routing_annotation, + const RRNodeId& input_rr_node, + const std::map mux_instance_to_net_map) { + /* Validate file stream */ + valid_file_stream(fp); + + /* Find the module net which sources from this port! */ + ModuleNetId module_net = module_manager.module_instance_port_net(parent_module, parent_module, 0, module_input_port, module_input_pin); + VTR_ASSERT(true == module_manager.valid_module_net_id(parent_module, module_net)); + + /* Touch each sink of the net! */ + for (const ModuleNetSinkId& sink_id : module_manager.module_net_sinks(parent_module, module_net)) { + ModuleId sink_module = module_manager.net_sink_modules(parent_module, module_net)[sink_id]; + size_t sink_instance = module_manager.net_sink_instances(parent_module, module_net)[sink_id]; + + /* Skip when sink module is the parent module, + * the output ports of parent modules have been disabled/enabled already! + */ + if (sink_module == parent_module) { + continue; + } + + std::string sink_instance_name = module_manager.instance_name(parent_module, sink_module, sink_instance); + bool disable_timing = false; + /* Check if this node is used by benchmark */ + if (true == is_rr_node_to_be_disable_for_analysis(routing_annotation, input_rr_node)) { + /* Disable all the sinks! */ + disable_timing = true; + } else { + std::map::const_iterator it = mux_instance_to_net_map.find(sink_instance_name); + if (it != mux_instance_to_net_map.end()) { + /* See if the net id matches. If does not match, we should disable! */ + if (routing_annotation.rr_node_net(input_rr_node) != mux_instance_to_net_map.at(sink_instance_name)) { + disable_timing = true; + } + } + } + + /* Time to write SDC command to disable timing or not */ + if (false == disable_timing) { + continue; + } + + BasicPort sink_port = module_manager.module_port(sink_module, module_manager.net_sink_ports(parent_module, module_net)[sink_id]); + sink_port.set_width(module_manager.net_sink_pins(parent_module, module_net)[sink_id], + module_manager.net_sink_pins(parent_module, module_net)[sink_id]); + /* Get the input id that is used! Disable the unused inputs! */ + fp << "set_disable_timing "; + fp << parent_instance_name << "/"; + fp << sink_instance_name << "/"; + fp << generate_sdc_port(sink_port); + fp << std::endl; + } +} + +/******************************************************************** + * Disable all the unused inputs of routing multiplexers, which are not used by benchmark + * Here, we start from each input of a routing module, and traverse forward to the sink + * port of the module net whose source is the input + * We will find the instance name which is the parent of the sink port, and search the + * net id through the instance_name_to_net_map + * The the net id does not match the net id of this input, we will disable the sink port! + * + * parent_module + * +----------------------- + * | MUX instance A + * | +----------- + * input_port--->|--+---x-->| sink port (disable! net_id = Y) + * (net_id = X) | | +---------- + * | | MUX instance B + * | | +---------- + * | +------>| sink port (do not disable! net_id = X) + * + *******************************************************************/ +void disable_analysis_module_input_port_net_sinks(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const std::string& parent_instance_name, + const ModulePortId& module_input_port, + const VprRoutingAnnotation& routing_annotation, + const RRNodeId& input_rr_node, + const std::map mux_instance_to_net_map) { + /* Validate file stream */ + valid_file_stream(fp); + + /* Find the module net which sources from this port! */ + for (const size_t& pin : module_manager.module_port(parent_module, module_input_port).pins()) { + disable_analysis_module_input_pin_net_sinks(fp, module_manager, parent_module, + parent_instance_name, + module_input_port, pin, + routing_annotation, input_rr_node, + mux_instance_to_net_map); + } +} + +/******************************************************************** + * Disable all the unused inputs of routing multiplexers, which are not used by benchmark + * Here, we start from each output of a child module, and traverse forward to the sink + * port of the module net whose source is the input + * We will find the instance name which is the parent of the sink port, and search the + * net id through the instance_name_to_net_map + * The the net id does not match the net id of this input, we will disable the sink port! + * + * Parent_module + * +--------------------------------------------- + * | + * | +--------------------------------------------+ + * | | MUX child_module | + * | | +-------------+ +-----------+ | + * | +--->| Routing |------>| | | + * input_pin0(netA) --->|----x--->| Multiplexer | netA | output_pin|-----+ + * | +-------------+ | | netA + * | | | + * + + * + *******************************************************************/ +void disable_analysis_module_output_pin_net_sinks(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const std::string& parent_instance_name, + const ModuleId& child_module, + const size_t& child_instance, + const ModulePortId& child_module_port, + const size_t& child_module_pin, + const VprRoutingAnnotation& routing_annotation, + const RRNodeId& output_rr_node, + const std::map mux_instance_to_net_map) { + /* Validate file stream */ + valid_file_stream(fp); + + /* Find the module net which sources from this port! */ + ModuleNetId module_net = module_manager.module_instance_port_net(parent_module, child_module, child_instance, child_module_port, child_module_pin); + VTR_ASSERT(true == module_manager.valid_module_net_id(parent_module, module_net)); + + /* Touch each sink of the net! */ + for (const ModuleNetSinkId& sink_id : module_manager.module_net_sinks(parent_module, module_net)) { + ModuleId sink_module = module_manager.net_sink_modules(parent_module, module_net)[sink_id]; + size_t sink_instance = module_manager.net_sink_instances(parent_module, module_net)[sink_id]; + + /* Skip when sink module is the parent module, + * the output ports of parent modules have been disabled/enabled already! + */ + if (sink_module == parent_module) { + continue; + } + + std::string sink_instance_name = module_manager.instance_name(parent_module, sink_module, sink_instance); + bool disable_timing = false; + /* Check if this node is used by benchmark */ + if (true == is_rr_node_to_be_disable_for_analysis(routing_annotation, output_rr_node)) { + /* Disable all the sinks! */ + disable_timing = true; + } else { + std::map::const_iterator it = mux_instance_to_net_map.find(sink_instance_name); + if (it != mux_instance_to_net_map.end()) { + /* See if the net id matches. If does not match, we should disable! */ + if (routing_annotation.rr_node_net(output_rr_node) != mux_instance_to_net_map.at(sink_instance_name)) { + disable_timing = true; + } + } + } + + /* Time to write SDC command to disable timing or not */ + if (false == disable_timing) { + continue; + } + + BasicPort sink_port = module_manager.module_port(sink_module, module_manager.net_sink_ports(parent_module, module_net)[sink_id]); + sink_port.set_width(module_manager.net_sink_pins(parent_module, module_net)[sink_id], + module_manager.net_sink_pins(parent_module, module_net)[sink_id]); + /* Get the input id that is used! Disable the unused inputs! */ + fp << "set_disable_timing "; + fp << parent_instance_name << "/"; + fp << sink_instance_name << "/"; + fp << generate_sdc_port(sink_port); + fp << std::endl; + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/analysis_sdc_writer_utils.h b/openfpga/src/fpga_sdc/analysis_sdc_writer_utils.h new file mode 100644 index 000000000..12b0dc362 --- /dev/null +++ b/openfpga/src/fpga_sdc/analysis_sdc_writer_utils.h @@ -0,0 +1,57 @@ +#ifndef ANALYSIS_SDC_WRITER_UTILS_H +#define ANALYSIS_SDC_WRITER_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include +#include "module_manager.h" +#include "rr_graph_obj.h" +#include "vpr_routing_annotation.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +bool is_rr_node_to_be_disable_for_analysis(const VprRoutingAnnotation& routing_annotation, + const RRNodeId& cur_rr_node); + +void disable_analysis_module_input_pin_net_sinks(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const std::string& parent_instance_name, + const ModulePortId& module_input_port, + const size_t& module_input_pin, + const VprRoutingAnnotation& routing_annotation, + const RRNodeId& input_rr_node, + const std::map mux_instance_to_net_map); + +void disable_analysis_module_input_port_net_sinks(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const std::string& parent_instance_name, + const ModulePortId& module_input_port, + const VprRoutingAnnotation& routing_annotation, + const RRNodeId& input_rr_node, + const std::map mux_instance_to_net_map); + +void disable_analysis_module_output_pin_net_sinks(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const std::string& parent_instance_name, + const ModuleId& child_module, + const size_t& child_instance, + const ModulePortId& child_module_port, + const size_t& child_module_pin, + const VprRoutingAnnotation& routing_annotation, + const RRNodeId& output_rr_node, + const std::map mux_instance_to_net_map); + +} /* end namespace openfpga */ + +#endif From 647418353924aa22348c747df4a898288db01667 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 2 Mar 2020 14:29:58 -0700 Subject: [PATCH 243/645] adapt analysis SDC writer for routing modules --- .../fpga_sdc/analysis_sdc_routing_writer.cpp | 528 ++++++++++++++++++ .../fpga_sdc/analysis_sdc_routing_writer.h | 35 ++ 2 files changed, 563 insertions(+) create mode 100644 openfpga/src/fpga_sdc/analysis_sdc_routing_writer.cpp create mode 100644 openfpga/src/fpga_sdc/analysis_sdc_routing_writer.h diff --git a/openfpga/src/fpga_sdc/analysis_sdc_routing_writer.cpp b/openfpga/src/fpga_sdc/analysis_sdc_routing_writer.cpp new file mode 100644 index 000000000..f9384af04 --- /dev/null +++ b/openfpga/src/fpga_sdc/analysis_sdc_routing_writer.cpp @@ -0,0 +1,528 @@ +/******************************************************************** + * This file includes functions that are used to output a SDC file + * that constrain routing modules of a FPGA fabric (P&Red netlist) + * using a benchmark + *******************************************************************/ +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" + +/* Headers from openfpgautil library */ +#include "openfpga_digest.h" +#include "openfpga_side_manager.h" +#include "openfpga_port.h" + +#include "openfpga_reserved_words.h" +#include "openfpga_naming.h" + +#include "sdc_writer_utils.h" +#include "analysis_sdc_writer_utils.h" +#include "analysis_sdc_routing_writer.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * This function will disable + * 1. all the unused port (unmapped by a benchmark) of a connection block + * 2. all the unused inputs (unmapped by a benchmark) of routing multiplexers + * in a connection block + *******************************************************************/ +static +void print_analysis_sdc_disable_cb_unused_resources(std::fstream& fp, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const VprRoutingAnnotation& routing_annotation, + const DeviceRRGSB& device_rr_gsb, + const RRGSB& rr_gsb, + const t_rr_type& cb_type, + const bool& compact_routing_hierarchy) { + /* Validate file stream */ + valid_file_stream(fp); + + vtr::Point gsb_coordinate(rr_gsb.get_cb_x(cb_type), rr_gsb.get_cb_y(cb_type)); + + std::string cb_instance_name = generate_connection_block_module_name(cb_type, gsb_coordinate); + + /* If we use the compact routing hierarchy, we need to find the module name !*/ + vtr::Point cb_coordinate(rr_gsb.get_cb_x(cb_type), rr_gsb.get_cb_y(cb_type)); + if (true == compact_routing_hierarchy) { + vtr::Point cb_coord(rr_gsb.get_x(), rr_gsb.get_y()); + /* Note: use GSB coordinate when inquire for unique modules!!! */ + const RRGSB& unique_mirror = device_rr_gsb.get_cb_unique_module(cb_type, cb_coord); + cb_coordinate.set_x(unique_mirror.get_cb_x(cb_type)); + cb_coordinate.set_y(unique_mirror.get_cb_y(cb_type)); + } + + std::string cb_module_name = generate_connection_block_module_name(cb_type, cb_coordinate); + + ModuleId cb_module = module_manager.find_module(cb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(cb_module)); + + /* Print comments */ + fp << "##################################################" << std::endl; + fp << "# Disable timing for Connection block " << cb_module_name << std::endl; + fp << "##################################################" << std::endl; + + /* Disable all the input port (routing tracks), which are not used by benchmark */ + for (size_t itrack = 0; itrack < rr_gsb.get_cb_chan_width(cb_type); ++itrack) { + const RRNodeId& chan_node = rr_gsb.get_chan_node(rr_gsb.get_cb_chan_side(cb_type), itrack); + /* Check if this node is used by benchmark */ + if (false == is_rr_node_to_be_disable_for_analysis(routing_annotation, chan_node)) { + continue; + } + + /* Disable both input of the routing track if it is not used! */ + std::string port_name = generate_cb_module_track_port_name(cb_type, + itrack, + IN_PORT); + + /* Ensure we have this port in the module! */ + ModulePortId module_port = module_manager.find_module_port(cb_module, port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(cb_module, module_port)); + + fp << "set_disable_timing "; + fp << cb_instance_name << "/"; + fp << generate_sdc_port(module_manager.module_port(cb_module, module_port)); + fp << std::endl; + } + + /* Disable all the output port (routing tracks), which are not used by benchmark */ + for (size_t itrack = 0; itrack < rr_gsb.get_cb_chan_width(cb_type); ++itrack) { + const RRNodeId& chan_node = rr_gsb.get_chan_node(rr_gsb.get_cb_chan_side(cb_type), itrack); + /* Check if this node is used by benchmark */ + if (false == is_rr_node_to_be_disable_for_analysis(routing_annotation, chan_node)) { + continue; + } + + /* Disable both input of the routing track if it is not used! */ + std::string port_name = generate_cb_module_track_port_name(cb_type, + itrack, + OUT_PORT); + + /* Ensure we have this port in the module! */ + ModulePortId module_port = module_manager.find_module_port(cb_module, port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(cb_module, module_port)); + + fp << "set_disable_timing "; + fp << cb_instance_name << "/"; + fp << generate_sdc_port(module_manager.module_port(cb_module, module_port)); + fp << std::endl; + } + + /* Build a map between mux_instance name and net_num */ + std::map mux_instance_to_net_map; + + /* Disable all the output port (grid input pins), which are not used by benchmark */ + std::vector cb_sides = rr_gsb.get_cb_ipin_sides(cb_type); + + for (size_t side = 0; side < cb_sides.size(); ++side) { + enum e_side cb_ipin_side = cb_sides[side]; + for (size_t inode = 0; inode < rr_gsb.get_num_ipin_nodes(cb_ipin_side); ++inode) { + RRNodeId ipin_node = rr_gsb.get_ipin_node(cb_ipin_side, inode); + + /* Find the MUX instance that drives the IPIN! */ + std::string mux_instance_name = generate_cb_mux_instance_name(CONNECTION_BLOCK_MUX_INSTANCE_PREFIX, rr_graph.node_side(ipin_node), inode, std::string("")); + mux_instance_to_net_map[mux_instance_name] = routing_annotation.rr_node_net(ipin_node); + + if (false == is_rr_node_to_be_disable_for_analysis(routing_annotation, ipin_node)) { + continue; + } + + if (0 == std::distance(rr_graph.node_configurable_in_edges(ipin_node).begin(), rr_graph.node_configurable_in_edges(ipin_node).end())) { + continue; + } + + std::string port_name = generate_cb_module_grid_port_name(cb_ipin_side, + rr_graph.node_pin_num(ipin_node)); + + /* Find the port in unique mirror! */ + if (true == compact_routing_hierarchy) { + /* Note: use GSB coordinate when inquire for unique modules!!! */ + vtr::Point cb_coord(rr_gsb.get_x(), rr_gsb.get_y()); + const RRGSB& unique_mirror = device_rr_gsb.get_cb_unique_module(cb_type, cb_coord); + const RRNodeId& unique_mirror_ipin_node = unique_mirror.get_ipin_node(cb_ipin_side, inode); + port_name = generate_cb_module_grid_port_name(cb_ipin_side, + rr_graph.node_pin_num(unique_mirror_ipin_node)); + } + + /* Ensure we have this port in the module! */ + ModulePortId module_port = module_manager.find_module_port(cb_module, port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(cb_module, module_port)); + + fp << "set_disable_timing "; + fp << cb_instance_name << "/"; + fp << generate_sdc_port(module_manager.module_port(cb_module, module_port)); + fp << std::endl; + } + } + + /* Disable all the unused inputs of routing multiplexers, which are not used by benchmark + * Here, we start from each input of the Connection Blocks, and traverse forward to the sink + * port of the module net whose source is the input + * We will find the instance name which is the parent of the sink port, and search the + * net id through the instance_name_to_net_map + * The the net id does not match the net id of this input, we will disable the sink port! + * + * cb_module + * +----------------------- + * | MUX instance A + * | +----------- + * input_port--->|--+---x-->| sink port (disable!) + * | | +---------- + * | | MUX instance B + * | | +---------- + * | +------>| sink port (do not disable!) + */ + for (size_t itrack = 0; itrack < rr_gsb.get_cb_chan_width(cb_type); ++itrack) { + const RRNodeId& chan_node = rr_gsb.get_chan_node(rr_gsb.get_cb_chan_side(cb_type), itrack); + + /* Disable both input of the routing track if it is not used! */ + std::string port_name = generate_cb_module_track_port_name(cb_type, + itrack, + OUT_PORT); + + /* Ensure we have this port in the module! */ + ModulePortId module_port = module_manager.find_module_port(cb_module, port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(cb_module, module_port)); + + disable_analysis_module_input_port_net_sinks(fp, + module_manager, cb_module, + cb_instance_name, + module_port, + routing_annotation, + chan_node, + mux_instance_to_net_map); + } +} + +/******************************************************************** + * Iterate over all the connection blocks in a device + * and disable unused ports for each of them + *******************************************************************/ +static +void print_analysis_sdc_disable_unused_cb_ports(std::fstream& fp, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const VprRoutingAnnotation& routing_annotation, + const DeviceRRGSB& device_rr_gsb, + const t_rr_type& cb_type, + const bool& compact_routing_hierarchy) { + /* Build unique X-direction connection block modules */ + vtr::Point cb_range = device_rr_gsb.get_gsb_range(); + + for (size_t ix = 0; ix < cb_range.x(); ++ix) { + for (size_t iy = 0; iy < cb_range.y(); ++iy) { + /* Check if the connection block exists in the device! + * Some of them do NOT exist due to heterogeneous blocks (height > 1) + * We will skip those modules + */ + const RRGSB& rr_gsb = device_rr_gsb.get_gsb(ix, iy); + if (false == rr_gsb.is_cb_exist(cb_type)) { + continue; + } + + print_analysis_sdc_disable_cb_unused_resources(fp, + module_manager, + rr_graph, + routing_annotation, + device_rr_gsb, + rr_gsb, + cb_type, + compact_routing_hierarchy); + } + } +} + +/******************************************************************** + * Iterate over all the connection blocks in a device + * and disable unused ports for each of them + *******************************************************************/ +void print_analysis_sdc_disable_unused_cbs(std::fstream& fp, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const VprRoutingAnnotation& routing_annotation, + const DeviceRRGSB& device_rr_gsb, + const bool& compact_routing_hierarchy) { + + print_analysis_sdc_disable_unused_cb_ports(fp, module_manager, + rr_graph, + routing_annotation, + device_rr_gsb, + CHANX, compact_routing_hierarchy); + + print_analysis_sdc_disable_unused_cb_ports(fp, module_manager, + rr_graph, + routing_annotation, + device_rr_gsb, + CHANY, compact_routing_hierarchy); +} + +/******************************************************************** + * This function will disable + * 1. all the unused port (unmapped by a benchmark) of a switch block + * 2. all the unused inputs (unmapped by a benchmark) of routing multiplexers + * in a switch block + *******************************************************************/ +static +void print_analysis_sdc_disable_sb_unused_resources(std::fstream& fp, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const VprRoutingAnnotation& routing_annotation, + const DeviceRRGSB& device_rr_gsb, + const RRGSB& rr_gsb, + const bool& compact_routing_hierarchy) { + /* Validate file stream */ + valid_file_stream(fp); + + vtr::Point gsb_coordinate(rr_gsb.get_sb_x(), rr_gsb.get_sb_y()); + + std::string sb_instance_name = generate_switch_block_module_name(gsb_coordinate); + + /* If we use the compact routing hierarchy, we need to find the module name !*/ + vtr::Point sb_coordinate(rr_gsb.get_sb_x(), rr_gsb.get_sb_y()); + if (true == compact_routing_hierarchy) { + vtr::Point sb_coord(rr_gsb.get_x(), rr_gsb.get_y()); + /* Note: use GSB coordinate when inquire for unique modules!!! */ + const RRGSB& unique_mirror = device_rr_gsb.get_sb_unique_module(sb_coord); + sb_coordinate.set_x(unique_mirror.get_sb_x()); + sb_coordinate.set_y(unique_mirror.get_sb_y()); + } + + std::string sb_module_name = generate_switch_block_module_name(sb_coordinate); + + ModuleId sb_module = module_manager.find_module(sb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(sb_module)); + + /* Print comments */ + fp << "##################################################" << std::endl; + fp << "# Disable timing for Switch block " << sb_module_name << std::endl; + fp << "##################################################" << std::endl; + + /* Build a map between mux_instance name and net_num */ + std::map mux_instance_to_net_map; + + /* Disable all the input/output port (routing tracks), which are not used by benchmark */ + for (size_t side = 0; side < rr_gsb.get_num_sides(); ++side) { + SideManager side_manager(side); + + for (size_t itrack = 0; itrack < rr_gsb.get_chan_width(side_manager.get_side()); ++itrack) { + const RRNodeId& chan_node = rr_gsb.get_chan_node(side_manager.get_side(), itrack); + + std::string port_name = generate_sb_module_track_port_name(rr_graph.node_type(rr_gsb.get_chan_node(side_manager.get_side(), itrack)), + side_manager.get_side(), itrack, + rr_gsb.get_chan_node_direction(side_manager.get_side(), itrack)); + + if (true == compact_routing_hierarchy) { + /* Note: use GSB coordinate when inquire for unique modules!!! */ + vtr::Point sb_coord(rr_gsb.get_x(), rr_gsb.get_y()); + const RRGSB& unique_mirror = device_rr_gsb.get_sb_unique_module(sb_coord); + port_name = generate_sb_module_track_port_name(rr_graph.node_type(unique_mirror.get_chan_node(side_manager.get_side(), itrack)), + side_manager.get_side(), itrack, + unique_mirror.get_chan_node_direction(side_manager.get_side(), itrack)); + } + + /* Ensure we have this port in the module! */ + ModulePortId module_port = module_manager.find_module_port(sb_module, port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(sb_module, module_port)); + + /* Cache the net name for routing tracks which are outputs of the switch block */ + if (OUT_PORT == rr_gsb.get_chan_node_direction(side_manager.get_side(), itrack)) { + /* Generate the name of mux instance related to this output node */ + std::string mux_instance_name = generate_sb_memory_instance_name(SWITCH_BLOCK_MUX_INSTANCE_PREFIX, side_manager.get_side(), itrack, std::string("")); + mux_instance_to_net_map[mux_instance_name] = routing_annotation.rr_node_net(chan_node); + } + + /* Check if this node is used by benchmark */ + if (false == is_rr_node_to_be_disable_for_analysis(routing_annotation, chan_node)) { + continue; + } + + fp << "set_disable_timing "; + fp << sb_instance_name << "/"; + fp << generate_sdc_port(module_manager.module_port(sb_module, module_port)); + fp << std::endl; + } + } + + /* Disable all the input port (grid output pins), which are not used by benchmark */ + for (size_t side = 0; side < rr_gsb.get_num_sides(); ++side) { + SideManager side_manager(side); + + for (size_t inode = 0; inode < rr_gsb.get_num_opin_nodes(side_manager.get_side()); ++inode) { + const RRNodeId& opin_node = rr_gsb.get_opin_node(side_manager.get_side(), inode); + + std::string port_name = generate_sb_module_grid_port_name(side_manager.get_side(), + rr_graph.node_side(opin_node), + rr_graph.node_pin_num(opin_node)); + + if (true == compact_routing_hierarchy) { + /* Note: use GSB coordinate when inquire for unique modules!!! */ + vtr::Point sb_coord(rr_gsb.get_x(), rr_gsb.get_y()); + const RRGSB& unique_mirror = device_rr_gsb.get_sb_unique_module(sb_coord); + const RRNodeId& unique_mirror_opin_node = unique_mirror.get_opin_node(side_manager.get_side(), inode); + + port_name = generate_sb_module_grid_port_name(side_manager.get_side(), + rr_graph.node_side(unique_mirror_opin_node), + rr_graph.node_pin_num(unique_mirror_opin_node)); + } + + + /* Ensure we have this port in the module! */ + ModulePortId module_port = module_manager.find_module_port(sb_module, port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(sb_module, module_port)); + + /* Check if this node is used by benchmark */ + if (false == is_rr_node_to_be_disable_for_analysis(routing_annotation, opin_node)) { + continue; + } + + fp << "set_disable_timing "; + fp << sb_instance_name << "/"; + fp << generate_sdc_port(module_manager.module_port(sb_module, module_port)); + fp << std::endl; + } + } + + /* Disable all the unused inputs of routing multiplexers, which are not used by benchmark + * Here, we start from each input of the Switch Blocks, and traverse forward to the sink + * port of the module net whose source is the input + * We will find the instance name which is the parent of the sink port, and search the + * net id through the instance_name_to_net_map + * The the net id does not match the net id of this input, we will disable the sink port! + * + * sb_module + * +----------------------- + * | MUX instance A + * | +----------- + * input_port--->|--+---x-->| sink port (disable! net_id = Y) + * (net_id = X) | | +---------- + * | | MUX instance B + * | | +---------- + * | +------>| sink port (do not disable! net_id = X) + * + * Because the input ports of a SB module come from + * 1. Grid output pins + * 2. routing tracks + * We will walk through these ports and do conditionally disable_timing + */ + + /* Iterate over input ports coming from grid output pins */ + for (size_t side = 0; side < rr_gsb.get_num_sides(); ++side) { + SideManager side_manager(side); + + for (size_t inode = 0; inode < rr_gsb.get_num_opin_nodes(side_manager.get_side()); ++inode) { + const RRNodeId& opin_node = rr_gsb.get_opin_node(side_manager.get_side(), inode); + + std::string port_name = generate_sb_module_grid_port_name(side_manager.get_side(), + rr_graph.node_side(opin_node), + rr_graph.node_pin_num(opin_node)); + + if (true == compact_routing_hierarchy) { + /* Note: use GSB coordinate when inquire for unique modules!!! */ + vtr::Point sb_coord(rr_gsb.get_x(), rr_gsb.get_y()); + const RRGSB& unique_mirror = device_rr_gsb.get_sb_unique_module(sb_coord); + const RRNodeId& unique_mirror_opin_node = unique_mirror.get_opin_node(side_manager.get_side(), inode); + + port_name = generate_sb_module_grid_port_name(side_manager.get_side(), + rr_graph.node_side(unique_mirror_opin_node), + rr_graph.node_pin_num(unique_mirror_opin_node)); + } + + + /* Ensure we have this port in the module! */ + ModulePortId module_port = module_manager.find_module_port(sb_module, port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(sb_module, module_port)); + + disable_analysis_module_input_port_net_sinks(fp, module_manager, + sb_module, + sb_instance_name, + module_port, + routing_annotation, + opin_node, + mux_instance_to_net_map); + } + } + + /* Iterate over input ports coming from routing tracks */ + for (size_t side = 0; side < rr_gsb.get_num_sides(); ++side) { + SideManager side_manager(side); + + for (size_t itrack = 0; itrack < rr_gsb.get_chan_width(side_manager.get_side()); ++itrack) { + /* Skip output ports, they have already been disabled or not */ + if (OUT_PORT == rr_gsb.get_chan_node_direction(side_manager.get_side(), itrack)) { + continue; + } + + const RRNodeId& chan_node = rr_gsb.get_chan_node(side_manager.get_side(), itrack); + + std::string port_name = generate_sb_module_track_port_name(rr_graph.node_type(chan_node), + side_manager.get_side(), itrack, + rr_gsb.get_chan_node_direction(side_manager.get_side(), itrack)); + + if (true == compact_routing_hierarchy) { + /* Note: use GSB coordinate when inquire for unique modules!!! */ + vtr::Point sb_coord(rr_gsb.get_x(), rr_gsb.get_y()); + const RRGSB& unique_mirror = device_rr_gsb.get_sb_unique_module(sb_coord); + const RRNodeId& unique_mirror_chan_node = unique_mirror.get_chan_node(side_manager.get_side(), itrack); + + port_name = generate_sb_module_track_port_name(rr_graph.node_type(unique_mirror_chan_node), + side_manager.get_side(), itrack, + unique_mirror.get_chan_node_direction(side_manager.get_side(), itrack)); + } + + + /* Ensure we have this port in the module! */ + ModulePortId module_port = module_manager.find_module_port(sb_module, port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(sb_module, module_port)); + + disable_analysis_module_input_port_net_sinks(fp, module_manager, + sb_module, + sb_instance_name, + module_port, + routing_annotation, + chan_node, + mux_instance_to_net_map); + } + } +} + + +/******************************************************************** + * Iterate over all the connection blocks in a device + * and disable unused ports for each of them + *******************************************************************/ +void print_analysis_sdc_disable_unused_sbs(std::fstream& fp, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const VprRoutingAnnotation& routing_annotation, + const DeviceRRGSB& device_rr_gsb, + const bool& compact_routing_hierarchy) { + + /* Build unique X-direction connection block modules */ + vtr::Point sb_range = device_rr_gsb.get_gsb_range(); + + for (size_t ix = 0; ix < sb_range.x(); ++ix) { + for (size_t iy = 0; iy < sb_range.y(); ++iy) { + /* Check if the connection block exists in the device! + * Some of them do NOT exist due to heterogeneous blocks (height > 1) + * We will skip those modules + */ + const RRGSB& rr_gsb = device_rr_gsb.get_gsb(ix, iy); + if (false == rr_gsb.is_sb_exist()) { + continue; + } + + print_analysis_sdc_disable_sb_unused_resources(fp, + module_manager, + rr_graph, + routing_annotation, + device_rr_gsb, + rr_gsb, + compact_routing_hierarchy); + } + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/analysis_sdc_routing_writer.h b/openfpga/src/fpga_sdc/analysis_sdc_routing_writer.h new file mode 100644 index 000000000..a5ea3249e --- /dev/null +++ b/openfpga/src/fpga_sdc/analysis_sdc_routing_writer.h @@ -0,0 +1,35 @@ +#ifndef ANALYSIS_SDC_ROUTING_WRITER_H +#define ANALYSIS_SDC_ROUTING_WRITER_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "module_manager.h" +#include "device_rr_gsb.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_analysis_sdc_disable_unused_cbs(std::fstream& fp, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const VprRoutingAnnotation& routing_annotation, + const DeviceRRGSB& device_rr_gsb, + const bool& compact_routing_hierarchy); + +void print_analysis_sdc_disable_unused_sbs(std::fstream& fp, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const VprRoutingAnnotation& routing_annotation, + const DeviceRRGSB& device_rr_gsb, + const bool& compact_routing_hierarchy); + +} /* end namespace openfpga */ + +#endif From 24f7416c7130f2ce97a3f8032ec51d19caada5cd Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 2 Mar 2020 17:15:01 -0700 Subject: [PATCH 244/645] adapt analysis SDC writer for grids --- .../src/fpga_sdc/analysis_sdc_grid_writer.cpp | 640 ++++++++++++++++++ .../src/fpga_sdc/analysis_sdc_grid_writer.h | 31 + .../fpga_sdc/analysis_sdc_routing_writer.cpp | 36 +- .../fpga_sdc/analysis_sdc_routing_writer.h | 3 + .../fpga_sdc/analysis_sdc_writer_utils.cpp | 29 +- .../src/fpga_sdc/analysis_sdc_writer_utils.h | 16 +- 6 files changed, 718 insertions(+), 37 deletions(-) create mode 100644 openfpga/src/fpga_sdc/analysis_sdc_grid_writer.cpp create mode 100644 openfpga/src/fpga_sdc/analysis_sdc_grid_writer.h diff --git a/openfpga/src/fpga_sdc/analysis_sdc_grid_writer.cpp b/openfpga/src/fpga_sdc/analysis_sdc_grid_writer.cpp new file mode 100644 index 000000000..19d501a79 --- /dev/null +++ b/openfpga/src/fpga_sdc/analysis_sdc_grid_writer.cpp @@ -0,0 +1,640 @@ +/******************************************************************** + * This file includes functions that are used to write SDC commands + * to disable unused ports of grids, such as Configurable Logic Block + * (CLBs), heterogeneous blocks, etc. + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_assert.h" + +/* Headers from openfpgautil library */ +#include "openfpga_digest.h" + +/* Headers from vprutil library */ +#include "vpr_utils.h" + +#include "openfpga_reserved_words.h" +#include "openfpga_naming.h" + +#include "pb_type_utils.h" + +#include "sdc_writer_utils.h" +#include "analysis_sdc_writer_utils.h" +#include "analysis_sdc_grid_writer.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Recursively visit all the pb_types in the hierarchy + * and disable all the ports + * + * Note: it is a must to disable all the ports in all the child pb_types! + * This can prohibit timing analyzer to consider any FF-to-FF path or + * combinatinal path inside an unused grid, when finding critical paths!!! + *******************************************************************/ +static +void rec_print_analysis_sdc_disable_unused_pb_graph_nodes(std::fstream& fp, + const VprDeviceAnnotation& device_annotation, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const std::string& hierarchy_name, + t_pb_graph_node* physical_pb_graph_node) { + t_pb_type* physical_pb_type = physical_pb_graph_node->pb_type; + + /* Validate file stream */ + valid_file_stream(fp); + + /* Disable all the ports of current module (parent_module)! + * Hierarchy name already includes the instance name of parent_module + */ + fp << "set_disable_timing "; + fp << hierarchy_name; + fp << "/*"; + fp << std::endl; + + /* Return if this is the primitive pb_type */ + if (true == is_primitive_pb_type(physical_pb_type)) { + return; + } + + /* Go recursively */ + t_mode* physical_mode = device_annotation.physical_mode(physical_pb_type); + + /* Disable all the ports by iterating over its instance in the parent module */ + for (int ichild = 0; ichild < physical_mode->num_pb_type_children; ++ichild) { + /* Generate the name of the Verilog module for this child */ + std::string child_module_name = generate_physical_block_module_name(&(physical_mode->pb_type_children[ichild])); + + ModuleId child_module = module_manager.find_module(child_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(child_module)); + + /* Each child may exist multiple times in the hierarchy*/ + for (int inst = 0; inst < physical_mode->pb_type_children[ichild].num_pb; ++inst) { + std::string child_instance_name = module_manager.instance_name(parent_module, child_module, module_manager.child_module_instances(parent_module, child_module)[inst]); + /* Must have a valid instance name!!! */ + VTR_ASSERT(false == child_instance_name.empty()); + + std::string updated_hierarchy_name = hierarchy_name + std::string("/") + child_instance_name + std::string("/"); + + rec_print_analysis_sdc_disable_unused_pb_graph_nodes(fp, device_annotation, module_manager, child_module, hierarchy_name, + &(physical_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ichild][inst])); + } + } +} + +/******************************************************************** + * Disable an unused pin of a pb_graph_node (parent_module) + *******************************************************************/ +static +void disable_pb_graph_node_unused_pin(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const std::string& hierarchy_name, + const t_pb_graph_pin* pb_graph_pin, + const PhysicalPb& physical_pb, + const PhysicalPbId& pb_id) { + /* Validate file stream */ + valid_file_stream(fp); + + /* Identify if the pb_graph_pin has been used or not + * TODO: identify if this is a parasitic net + */ + if (AtomNetId::INVALID() != physical_pb.pb_graph_pin_atom_net(pb_id, pb_graph_pin)) { + /* Used pin; Nothing to do */ + return; + } + + /* Reach here, it means that this pin is not used. Disable timing analysis for the pin */ + /* Find the module port by name */ + std::string module_port_name = generate_pb_type_port_name(pb_graph_pin->port); + ModulePortId module_port = module_manager.find_module_port(parent_module, module_port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(parent_module, module_port)); + BasicPort port_to_disable = module_manager.module_port(parent_module, module_port); + port_to_disable.set_width(pb_graph_pin->pin_number, pb_graph_pin->pin_number); + + fp << "set_disable_timing "; + fp << hierarchy_name; + fp << "/"; + fp << generate_sdc_port(port_to_disable); + fp << std::endl; +} + +/******************************************************************** + * Disable unused input ports and output ports of this pb_graph_node (parent_module) + * This function will iterate over all the input pins, output pins + * of the physical_pb_graph_node, and check if they are mapped + * For unused pins, we will find the port in parent_module + * and then print SDC commands to disable them + *******************************************************************/ +static +void disable_pb_graph_node_unused_pins(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const std::string& hierarchy_name, + t_pb_graph_node* physical_pb_graph_node, + const PhysicalPb& physical_pb) { + const PhysicalPbId& pb_id = physical_pb.find_pb(physical_pb_graph_node); + VTR_ASSERT(true == physical_pb.valid_pb_id(pb_id)); + + /* Disable unused input pins */ + for (int iport = 0; iport < physical_pb_graph_node->num_input_ports; ++iport) { + for (int ipin = 0; ipin < physical_pb_graph_node->num_input_pins[iport]; ++ipin) { + disable_pb_graph_node_unused_pin(fp, module_manager, parent_module, + hierarchy_name, + &(physical_pb_graph_node->input_pins[iport][ipin]), + physical_pb, pb_id); + } + } + + /* Disable unused output pins */ + for (int iport = 0; iport < physical_pb_graph_node->num_output_ports; ++iport) { + for (int ipin = 0; ipin < physical_pb_graph_node->num_output_pins[iport]; ++ipin) { + disable_pb_graph_node_unused_pin(fp, module_manager, parent_module, + hierarchy_name, + &(physical_pb_graph_node->output_pins[iport][ipin]), + physical_pb, pb_id); + } + } + + /* Disable unused clock pins */ + for (int iport = 0; iport < physical_pb_graph_node->num_clock_ports; ++iport) { + for (int ipin = 0; ipin < physical_pb_graph_node->num_clock_pins[iport]; ++ipin) { + disable_pb_graph_node_unused_pin(fp, module_manager, parent_module, + hierarchy_name, + &(physical_pb_graph_node->clock_pins[iport][ipin]), + physical_pb, pb_id); + } + } +} + +/******************************************************************** + * Disable unused inputs of routing multiplexers of this pb_graph_node + * This function will first cache the nets for each input and output pins + * and store the results in a mux_name-to-net mapping + *******************************************************************/ +static +void disable_pb_graph_node_unused_mux_inputs(std::fstream& fp, + const VprDeviceAnnotation& device_annotation, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const std::string& hierarchy_name, + t_pb_graph_node* physical_pb_graph_node, + const PhysicalPb& physical_pb) { + + t_pb_type* physical_pb_type = physical_pb_graph_node->pb_type; + + t_mode* physical_mode = device_annotation.physical_mode(physical_pb_type); + + std::map mux_instance_to_net_map; + + /* Cache the nets for each input pins of each child pb_graph_node */ + for (int ichild = 0; ichild < physical_mode->num_pb_type_children; ++ichild) { + for (int inst = 0; inst < physical_mode->pb_type_children[ichild].num_pb; ++inst) { + + t_pb_graph_node* child_pb_graph_node = &(physical_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ichild][inst]); + + /* Cache the nets for input pins of the child pb_graph_node */ + for (int iport = 0; iport < child_pb_graph_node->num_input_ports; ++iport) { + for (int ipin = 0; ipin < child_pb_graph_node->num_input_pins[iport]; ++ipin) { + const PhysicalPbId& pb_id = physical_pb.find_pb(child_pb_graph_node); + VTR_ASSERT(true == physical_pb.valid_pb_id(pb_id)); + /* Generate the mux name */ + std::string mux_instance_name = generate_pb_mux_instance_name(GRID_MUX_INSTANCE_PREFIX, &(child_pb_graph_node->input_pins[iport][ipin]), std::string("")); + /* Cache the net */ + mux_instance_to_net_map[mux_instance_name] = physical_pb.pb_graph_pin_atom_net(pb_id, &(child_pb_graph_node->input_pins[iport][ipin])); + } + } + + /* Cache the nets for clock pins of the child pb_graph_node */ + for (int iport = 0; iport < child_pb_graph_node->num_clock_ports; ++iport) { + for (int ipin = 0; ipin < child_pb_graph_node->num_clock_pins[iport]; ++ipin) { + const PhysicalPbId& pb_id = physical_pb.find_pb(child_pb_graph_node); + /* Generate the mux name */ + std::string mux_instance_name = generate_pb_mux_instance_name(GRID_MUX_INSTANCE_PREFIX, &(child_pb_graph_node->clock_pins[iport][ipin]), std::string("")); + /* Cache the net */ + mux_instance_to_net_map[mux_instance_name] = physical_pb.pb_graph_pin_atom_net(pb_id, &(child_pb_graph_node->clock_pins[iport][ipin])); + } + } + + } + } + + /* Cache the nets for each output pins of this pb_graph_node */ + for (int iport = 0; iport < physical_pb_graph_node->num_output_ports; ++iport) { + for (int ipin = 0; ipin < physical_pb_graph_node->num_output_pins[iport]; ++ipin) { + const PhysicalPbId& pb_id = physical_pb.find_pb(physical_pb_graph_node); + /* Generate the mux name */ + std::string mux_instance_name = generate_pb_mux_instance_name(GRID_MUX_INSTANCE_PREFIX, &(physical_pb_graph_node->output_pins[iport][ipin]), std::string("")); + /* Cache the net */ + mux_instance_to_net_map[mux_instance_name] = physical_pb.pb_graph_pin_atom_net(pb_id, &(physical_pb_graph_node->output_pins[iport][ipin])); + } + } + + /* Now disable unused inputs of routing multiplexers, by tracing from input pins of the parent_module */ + for (int iport = 0; iport < physical_pb_graph_node->num_input_ports; ++iport) { + for (int ipin = 0; ipin < physical_pb_graph_node->num_input_pins[iport]; ++ipin) { + /* Find the module port by name */ + std::string module_port_name = generate_pb_type_port_name(physical_pb_graph_node->input_pins[iport][ipin].port); + ModulePortId module_port = module_manager.find_module_port(parent_module, module_port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(parent_module, module_port)); + + const PhysicalPbId& pb_id = physical_pb.find_pb(physical_pb_graph_node); + const AtomNetId& mapped_net = physical_pb.pb_graph_pin_atom_net(pb_id, &(physical_pb_graph_node->input_pins[iport][ipin])); + + disable_analysis_module_input_pin_net_sinks(fp, module_manager, parent_module, + hierarchy_name, + module_port, ipin, + mapped_net, + mux_instance_to_net_map); + } + } + + for (int iport = 0; iport < physical_pb_graph_node->num_clock_ports; ++iport) { + for (int ipin = 0; ipin < physical_pb_graph_node->num_clock_pins[iport]; ++ipin) { + /* Find the module port by name */ + std::string module_port_name = generate_pb_type_port_name(physical_pb_graph_node->clock_pins[iport][ipin].port); + ModulePortId module_port = module_manager.find_module_port(parent_module, module_port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(parent_module, module_port)); + + const PhysicalPbId& pb_id = physical_pb.find_pb(physical_pb_graph_node); + const AtomNetId& mapped_net = physical_pb.pb_graph_pin_atom_net(pb_id, &(physical_pb_graph_node->clock_pins[iport][ipin])); + + disable_analysis_module_input_pin_net_sinks(fp, module_manager, parent_module, + hierarchy_name, + module_port, ipin, + mapped_net, + mux_instance_to_net_map); + } + } + + /* Now disable unused inputs of routing multiplexers, by tracing from output pins of the child_module */ + for (int ichild = 0; ichild < physical_mode->num_pb_type_children; ++ichild) { + /* Generate the name of the Verilog module for this child */ + std::string child_module_name = generate_physical_block_module_name(&(physical_mode->pb_type_children[ichild])); + + ModuleId child_module = module_manager.find_module(child_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(child_module)); + + for (int inst = 0; inst < physical_mode->pb_type_children[ichild].num_pb; ++inst) { + + t_pb_graph_node* child_pb_graph_node = &(physical_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ichild][inst]); + + for (int iport = 0; iport < child_pb_graph_node->num_output_ports; ++iport) { + for (int ipin = 0; ipin < child_pb_graph_node->num_output_pins[iport]; ++ipin) { + /* Find the module port by name */ + std::string module_port_name = generate_pb_type_port_name(child_pb_graph_node->output_pins[iport][ipin].port); + ModulePortId module_port = module_manager.find_module_port(child_module, module_port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(child_module, module_port)); + + const PhysicalPbId& pb_id = physical_pb.find_pb(child_pb_graph_node); + const AtomNetId& mapped_net = physical_pb.pb_graph_pin_atom_net(pb_id, &(child_pb_graph_node->output_pins[iport][ipin])); + + /* Corner case: if the pb_graph_pin has no fan-out we will skip this pin */ + if (0 == child_pb_graph_node->output_pins[iport][ipin].num_output_edges) { + continue; + } + + disable_analysis_module_output_pin_net_sinks(fp, module_manager, parent_module, + hierarchy_name, + child_module, inst, + module_port, ipin, + mapped_net, + mux_instance_to_net_map); + } + } + } + } +} + +/******************************************************************** + * Recursively visit all the pb_types in the hierarchy + * and disable all the unused resources, including: + * 1. input ports + * 2. output ports + * 3. unused inputs of routing multiplexers + * + * As this function is executed in a recursive way. + * To avoid repeated disable timing for ports, during each run of this function, + * only the unused input ports, output ports of the parent module will be disabled. + * In addition, we will cache all the net ids mapped to the input ports of + * child modules, and the net ids mapped to the output ports of parent module. + * As such, we can trace from + * 1. the input ports of parent module to disable unused inputs of routing multiplexer + * which drives the inputs of child modules + * + * Parent_module + * +--------------------------------------------- + * | MUX child_module + * | +-------------+ +-------- + * input_pin0(netA) --->|-------->| Routing |------>| + * input_pin1(netB) --->|----x--->| Multiplexer | netA | + * | +-------------+ | + * | | + * + * 2. the output ports of child module to disable unused inputs of routing multiplexer + * which drives the outputs of parent modules + * + * Case 1: + * parent_module + * --------------------------------------+ + * child_module | + * -------------+ | + * | +-------------+ | + * output_pin0 (netA) |--->| Routing |----->|----> + * output_pin1 (netB) |-x->| Multiplexer | netA | + * | +-------------+ | + * + * Case 2: + * + * Parent_module + * +--------------------------------------------- + * | + * | +--------------------------------------------+ + * | | MUX child_module | + * | | +-------------+ +-----------+ | + * | +--->| Routing |------>| | | + * input_pin0(netA) --->|----x--->| Multiplexer | netA | output_pin|-----+ + * | +-------------+ | | netA + * | | | + * + * + * Note: it is a must to disable all the ports in all the child pb_types! + * This can prohibit timing analyzer to consider any FF-to-FF path or + * combinatinal path inside an unused grid, when finding critical paths!!! + *******************************************************************/ +static +void rec_print_analysis_sdc_disable_pb_graph_node_unused_resources(std::fstream& fp, + const VprDeviceAnnotation& device_annotation, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const std::string& hierarchy_name, + t_pb_graph_node* physical_pb_graph_node, + const PhysicalPb& physical_pb) { + t_pb_type* physical_pb_type = physical_pb_graph_node->pb_type; + + /* Disable unused input ports and output ports of this pb_graph_node (parent_module) */ + disable_pb_graph_node_unused_pins(fp, module_manager, parent_module, + hierarchy_name, physical_pb_graph_node, physical_pb); + + /* Return if this is the primitive pb_type + * Note: this must return before we disable any unused inputs of routing multiplexer! + * This is due to that primitive pb_type does NOT contain any routing multiplexers inside!!! + */ + if (true == is_primitive_pb_type(physical_pb_type)) { + return; + } + + /* Disable unused inputs of routing multiplexers of this pb_graph_node */ + disable_pb_graph_node_unused_mux_inputs(fp, device_annotation, + module_manager, parent_module, + hierarchy_name, physical_pb_graph_node, + physical_pb); + + + t_mode* physical_mode = device_annotation.physical_mode(physical_pb_type); + + /* Disable all the ports by iterating over its instance in the parent module */ + for (int ichild = 0; ichild < physical_mode->num_pb_type_children; ++ichild) { + /* Generate the name of the Verilog module for this child */ + std::string child_module_name = generate_physical_block_module_name(&(physical_mode->pb_type_children[ichild])); + + ModuleId child_module = module_manager.find_module(child_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(child_module)); + + /* Each child may exist multiple times in the hierarchy*/ + for (int inst = 0; inst < physical_pb_type->modes[physical_mode->index].pb_type_children[ichild].num_pb; ++inst) { + std::string child_instance_name = module_manager.instance_name(parent_module, child_module, module_manager.child_module_instances(parent_module, child_module)[inst]); + /* Must have a valid instance name!!! */ + VTR_ASSERT(false == child_instance_name.empty()); + + std::string updated_hierarchy_name = hierarchy_name + std::string("/") + child_instance_name + std::string("/"); + + rec_print_analysis_sdc_disable_pb_graph_node_unused_resources(fp, device_annotation, + module_manager, child_module, hierarchy_name, + &(physical_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ichild][inst]), + physical_pb); + } + } +} + +/******************************************************************** + * This function can work in two differnt modes: + * 1. For partially unused pb blocks + * --------------------------------- + * Disable the timing for only unused resources in a physical block + * We have to walk through pb_graph node, port by port and pin by pin. + * Identify which pins have not been used, and then disable the timing + * for these ports. + * Plus, for input ports, we will trace the routing multiplexers + * and disable the timing for unused inputs. + * + * 2. For fully unused pb_blocks + * ----------------------------- + * Disable the timing for a fully unused grid! + * This is very straightforward! + * Just walk through each pb_type and disable all the ports using wildcards + *******************************************************************/ +static +void print_analysis_sdc_disable_pb_block_unused_resources(std::fstream& fp, + t_physical_tile_type_ptr grid_type, + const vtr::Point& grid_coordinate, + const VprDeviceAnnotation& device_annotation, + const ModuleManager& module_manager, + const std::string& grid_instance_name, + const size_t& grid_z, + const PhysicalPb& physical_pb, + const bool& unused_block) { + /* If the block is partially unused, we should have a physical pb */ + if (false == unused_block) { + VTR_ASSERT(false == physical_pb.empty()); + } + + VTR_ASSERT(1 == grid_type->equivalent_sites.size()); + t_pb_graph_node* pb_graph_head = grid_type->equivalent_sites[0]->pb_graph_head; + VTR_ASSERT(nullptr != pb_graph_head); + + /* Find an unique name to the pb instance in this grid + * Note: this must be consistent with the instance name we used in build_grid_module()!!! + */ + /* TODO: validate that the instance name is used in module manager!!! */ + std::string pb_module_name = generate_physical_block_module_name(pb_graph_head->pb_type); + std::string pb_instance_name = generate_physical_block_instance_name(pb_graph_head->pb_type, grid_z); + + ModuleId pb_module = module_manager.find_module(pb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(pb_module)); + + /* Print comments */ + fp << "#######################################" << std::endl; + + if (true == unused_block) { + fp << "# Disable Timing for unused grid[" << grid_coordinate.x() << "][" << grid_coordinate.y() << "][" << grid_z << "]" << std::endl; + } else { + VTR_ASSERT_SAFE(false == unused_block); + fp << "# Disable Timing for unused resources in grid[" << grid_coordinate.x() << "][" << grid_coordinate.y() << "][" << grid_z << "]" << std::endl; + } + + fp << "#######################################" << std::endl; + + std::string hierarchy_name = grid_instance_name + std::string("/") + pb_instance_name + std::string("/"); + + /* Go recursively through the pb_graph hierarchy, and disable all the ports level by level */ + if (true == unused_block) { + rec_print_analysis_sdc_disable_unused_pb_graph_nodes(fp, device_annotation, + module_manager, pb_module, hierarchy_name, + pb_graph_head); + } else { + VTR_ASSERT_SAFE(false == unused_block); + rec_print_analysis_sdc_disable_pb_graph_node_unused_resources(fp, device_annotation, + module_manager, pb_module, hierarchy_name, + pb_graph_head, physical_pb); + } +} + +/******************************************************************** + * Disable the timing for a fully unused grid! + * This is very straightforward! + * Just walk through each pb_type and disable all the ports using wildcards + *******************************************************************/ +static +void print_analysis_sdc_disable_unused_grid(std::fstream& fp, + const vtr::Point& grid_coordinate, + const DeviceGrid& grids, + const VprDeviceAnnotation& device_annotation, + const VprClusteringAnnotation& cluster_annotation, + const VprPlacementAnnotation& place_annotation, + const ModuleManager& module_manager, + const e_side& border_side) { + /* Validate file stream */ + valid_file_stream(fp); + + t_physical_tile_type_ptr grid_type = grids[grid_coordinate.x()][grid_coordinate.y()].type; + /* Bypass conditions for grids : + * 1. EMPTY type, which is by nature unused + * 2. Offset > 0, which has already been processed when offset = 0 + */ + if ( (true == is_empty_type(grid_type)) + || (0 < grids[grid_coordinate.x()][grid_coordinate.y()].width_offset) + || (0 < grids[grid_coordinate.x()][grid_coordinate.y()].height_offset) ) { + return; + } + + /* Find an unique name to the grid instane + * Note: this must be consistent with the instance name we used in build_top_module()!!! + */ + /* TODO: validate that the instance name is used in module manager!!! */ + std::string grid_module_name_prefix(GRID_MODULE_NAME_PREFIX); + std::string grid_module_name = generate_grid_block_module_name(grid_module_name_prefix, std::string(grid_type->name), is_io_type(grid_type), border_side); + std::string grid_instance_name = generate_grid_block_instance_name(grid_module_name_prefix, std::string(grid_type->name), is_io_type(grid_type), border_side, grid_coordinate); + + ModuleId grid_module = module_manager.find_module(grid_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(grid_module)); + + /* Print comments */ + fp << "#######################################" << std::endl; + fp << "# Disable Timing for grid[" << grid_coordinate.x() << "][" << grid_coordinate.y() << "]" << std::endl; + fp << "#######################################" << std::endl; + + /* For used grid, find the unused rr_node in the local rr_graph + * and then disable each port which is not used + * as well as the unused inputs of routing multiplexers! + */ + size_t grid_z = 0; + for (const ClusterBlockId& blk_id : place_annotation.grid_blocks(grid_coordinate)) { + if (ClusterBlockId::INVALID() != blk_id) { + const PhysicalPb& physical_pb = cluster_annotation.physical_pb(blk_id); + print_analysis_sdc_disable_pb_block_unused_resources(fp, grid_type, grid_coordinate, + device_annotation, + module_manager, grid_instance_name, grid_z, + physical_pb, false); + } else { + VTR_ASSERT(ClusterBlockId::INVALID() == blk_id); + /* For unused grid, disable all the pins in the physical_pb_type */ + print_analysis_sdc_disable_pb_block_unused_resources(fp, grid_type, grid_coordinate, + device_annotation, + module_manager, grid_instance_name, grid_z, + PhysicalPb(), true); + } + grid_z++; + } +} + +/******************************************************************** + * Top-level function writes SDC commands to disable unused ports + * of grids, such as Configurable Logic Block (CLBs), heterogeneous blocks, etc. + * + * This function will iterate over all the grids available in the FPGA fabric + * It will disable the timing analysis for + * 1. Grids, which are totally not used (no logic has been mapped to) + * 2. Unused part of grids, including the ports, inputs of routing multiplexers + * + * Note that it is a must to disable the unused inputs of routing multiplexers + * because it will cause unexpected paths in timing analysis + * For example: + * +---------------------+ + * inputA (net0) ------->| | + * | Routing multiplexer |----> output (net0) + * inputB (net1) ------->| | + * +---------------------+ + * + * During timing analysis, the path from inputA to output should be considered + * while the path from inputB to output should NOT be considered!!! + * + *******************************************************************/ +void print_analysis_sdc_disable_unused_grids(std::fstream& fp, + const DeviceGrid& grids, + const VprDeviceAnnotation& device_annotation, + const VprClusteringAnnotation& cluster_annotation, + const VprPlacementAnnotation& place_annotation, + const ModuleManager& module_manager) { + + /* Process unused core grids */ + for (size_t ix = 1; ix < grids.width() - 1; ++ix) { + for (size_t iy = 1; iy < grids.height() - 1; ++iy) { + /* We should not meet any I/O grid */ + VTR_ASSERT(false != is_io_type(grids[ix][iy].type)); + + print_analysis_sdc_disable_unused_grid(fp, vtr::Point(ix, iy), + grids, device_annotation, cluster_annotation, place_annotation, + module_manager, NUM_SIDES); + } + } + + /* Instanciate I/O grids */ + /* Create the coordinate range for each side of FPGA fabric */ + std::vector io_sides{TOP, RIGHT, BOTTOM, LEFT}; + std::map>> io_coordinates; + + /* TOP side*/ + for (size_t ix = 1; ix < grids.width() - 1; ++ix) { + io_coordinates[TOP].push_back(vtr::Point(ix, grids.height() - 1)); + } + + /* RIGHT side */ + for (size_t iy = 1; iy < grids.height() - 1; ++iy) { + io_coordinates[RIGHT].push_back(vtr::Point(grids.width() - 1, iy)); + } + + /* BOTTOM side*/ + for (size_t ix = 1; ix < grids.width() - 1; ++ix) { + io_coordinates[BOTTOM].push_back(vtr::Point(ix, 0)); + } + + /* LEFT side */ + for (size_t iy = 1; iy < grids.height() - 1; ++iy) { + io_coordinates[LEFT].push_back(vtr::Point(0, iy)); + } + + /* Add instances of I/O grids to top_module */ + for (const e_side& io_side : io_sides) { + for (const vtr::Point& io_coordinate : io_coordinates[io_side]) { + /* We should not meet any I/O grid */ + VTR_ASSERT(true == is_io_type(grids[io_coordinate.x()][io_coordinate.y()].type)); + + print_analysis_sdc_disable_unused_grid(fp, io_coordinate, + grids, device_annotation, cluster_annotation, place_annotation, + module_manager, io_side); + } + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/analysis_sdc_grid_writer.h b/openfpga/src/fpga_sdc/analysis_sdc_grid_writer.h new file mode 100644 index 000000000..be01bbf28 --- /dev/null +++ b/openfpga/src/fpga_sdc/analysis_sdc_grid_writer.h @@ -0,0 +1,31 @@ +#ifndef ANALYSIS_SDC_GRID_WRITER_H +#define ANALYSIS_SDC_GRID_WRITER_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "device_grid.h" +#include "module_manager.h" +#include "vpr_device_annotation.h" +#include "vpr_clustering_annotation.h" +#include "vpr_placement_annotation.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_analysis_sdc_disable_unused_grids(std::fstream& fp, + const DeviceGrid& grids, + const VprDeviceAnnotation& device_annotation, + const VprClusteringAnnotation& cluster_annotation, + const VprPlacementAnnotation& place_annotation, + const ModuleManager& module_manager); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_sdc/analysis_sdc_routing_writer.cpp b/openfpga/src/fpga_sdc/analysis_sdc_routing_writer.cpp index f9384af04..d210ecacd 100644 --- a/openfpga/src/fpga_sdc/analysis_sdc_routing_writer.cpp +++ b/openfpga/src/fpga_sdc/analysis_sdc_routing_writer.cpp @@ -31,6 +31,7 @@ namespace openfpga { *******************************************************************/ static void print_analysis_sdc_disable_cb_unused_resources(std::fstream& fp, + const AtomContext& atom_ctx, const ModuleManager& module_manager, const RRGraph& rr_graph, const VprRoutingAnnotation& routing_annotation, @@ -112,7 +113,7 @@ void print_analysis_sdc_disable_cb_unused_resources(std::fstream& fp, } /* Build a map between mux_instance name and net_num */ - std::map mux_instance_to_net_map; + std::map mux_instance_to_net_map; /* Disable all the output port (grid input pins), which are not used by benchmark */ std::vector cb_sides = rr_gsb.get_cb_ipin_sides(cb_type); @@ -124,7 +125,7 @@ void print_analysis_sdc_disable_cb_unused_resources(std::fstream& fp, /* Find the MUX instance that drives the IPIN! */ std::string mux_instance_name = generate_cb_mux_instance_name(CONNECTION_BLOCK_MUX_INSTANCE_PREFIX, rr_graph.node_side(ipin_node), inode, std::string("")); - mux_instance_to_net_map[mux_instance_name] = routing_annotation.rr_node_net(ipin_node); + mux_instance_to_net_map[mux_instance_name] = atom_ctx.lookup.atom_net(routing_annotation.rr_node_net(ipin_node)); if (false == is_rr_node_to_be_disable_for_analysis(routing_annotation, ipin_node)) { continue; @@ -187,12 +188,13 @@ void print_analysis_sdc_disable_cb_unused_resources(std::fstream& fp, ModulePortId module_port = module_manager.find_module_port(cb_module, port_name); VTR_ASSERT(true == module_manager.valid_module_port_id(cb_module, module_port)); + AtomNetId mapped_atom_net = atom_ctx.lookup.atom_net(routing_annotation.rr_node_net(chan_node)); + disable_analysis_module_input_port_net_sinks(fp, module_manager, cb_module, cb_instance_name, module_port, - routing_annotation, - chan_node, + mapped_atom_net, mux_instance_to_net_map); } } @@ -203,6 +205,7 @@ void print_analysis_sdc_disable_cb_unused_resources(std::fstream& fp, *******************************************************************/ static void print_analysis_sdc_disable_unused_cb_ports(std::fstream& fp, + const AtomContext& atom_ctx, const ModuleManager& module_manager, const RRGraph& rr_graph, const VprRoutingAnnotation& routing_annotation, @@ -224,6 +227,7 @@ void print_analysis_sdc_disable_unused_cb_ports(std::fstream& fp, } print_analysis_sdc_disable_cb_unused_resources(fp, + atom_ctx, module_manager, rr_graph, routing_annotation, @@ -240,19 +244,22 @@ void print_analysis_sdc_disable_unused_cb_ports(std::fstream& fp, * and disable unused ports for each of them *******************************************************************/ void print_analysis_sdc_disable_unused_cbs(std::fstream& fp, + const AtomContext& atom_ctx, const ModuleManager& module_manager, const RRGraph& rr_graph, const VprRoutingAnnotation& routing_annotation, const DeviceRRGSB& device_rr_gsb, const bool& compact_routing_hierarchy) { - print_analysis_sdc_disable_unused_cb_ports(fp, module_manager, + print_analysis_sdc_disable_unused_cb_ports(fp, atom_ctx, + module_manager, rr_graph, routing_annotation, device_rr_gsb, CHANX, compact_routing_hierarchy); - print_analysis_sdc_disable_unused_cb_ports(fp, module_manager, + print_analysis_sdc_disable_unused_cb_ports(fp, atom_ctx, + module_manager, rr_graph, routing_annotation, device_rr_gsb, @@ -267,6 +274,7 @@ void print_analysis_sdc_disable_unused_cbs(std::fstream& fp, *******************************************************************/ static void print_analysis_sdc_disable_sb_unused_resources(std::fstream& fp, + const AtomContext& atom_ctx, const ModuleManager& module_manager, const RRGraph& rr_graph, const VprRoutingAnnotation& routing_annotation, @@ -301,7 +309,7 @@ void print_analysis_sdc_disable_sb_unused_resources(std::fstream& fp, fp << "##################################################" << std::endl; /* Build a map between mux_instance name and net_num */ - std::map mux_instance_to_net_map; + std::map mux_instance_to_net_map; /* Disable all the input/output port (routing tracks), which are not used by benchmark */ for (size_t side = 0; side < rr_gsb.get_num_sides(); ++side) { @@ -331,7 +339,7 @@ void print_analysis_sdc_disable_sb_unused_resources(std::fstream& fp, if (OUT_PORT == rr_gsb.get_chan_node_direction(side_manager.get_side(), itrack)) { /* Generate the name of mux instance related to this output node */ std::string mux_instance_name = generate_sb_memory_instance_name(SWITCH_BLOCK_MUX_INSTANCE_PREFIX, side_manager.get_side(), itrack, std::string("")); - mux_instance_to_net_map[mux_instance_name] = routing_annotation.rr_node_net(chan_node); + mux_instance_to_net_map[mux_instance_name] = atom_ctx.lookup.atom_net(routing_annotation.rr_node_net(chan_node)); } /* Check if this node is used by benchmark */ @@ -435,12 +443,13 @@ void print_analysis_sdc_disable_sb_unused_resources(std::fstream& fp, ModulePortId module_port = module_manager.find_module_port(sb_module, port_name); VTR_ASSERT(true == module_manager.valid_module_port_id(sb_module, module_port)); + AtomNetId mapped_atom_net = atom_ctx.lookup.atom_net(routing_annotation.rr_node_net(opin_node)); + disable_analysis_module_input_port_net_sinks(fp, module_manager, sb_module, sb_instance_name, module_port, - routing_annotation, - opin_node, + mapped_atom_net, mux_instance_to_net_map); } } @@ -477,12 +486,13 @@ void print_analysis_sdc_disable_sb_unused_resources(std::fstream& fp, ModulePortId module_port = module_manager.find_module_port(sb_module, port_name); VTR_ASSERT(true == module_manager.valid_module_port_id(sb_module, module_port)); + AtomNetId mapped_atom_net = atom_ctx.lookup.atom_net(routing_annotation.rr_node_net(chan_node)); + disable_analysis_module_input_port_net_sinks(fp, module_manager, sb_module, sb_instance_name, module_port, - routing_annotation, - chan_node, + mapped_atom_net, mux_instance_to_net_map); } } @@ -494,6 +504,7 @@ void print_analysis_sdc_disable_sb_unused_resources(std::fstream& fp, * and disable unused ports for each of them *******************************************************************/ void print_analysis_sdc_disable_unused_sbs(std::fstream& fp, + const AtomContext& atom_ctx, const ModuleManager& module_manager, const RRGraph& rr_graph, const VprRoutingAnnotation& routing_annotation, @@ -515,6 +526,7 @@ void print_analysis_sdc_disable_unused_sbs(std::fstream& fp, } print_analysis_sdc_disable_sb_unused_resources(fp, + atom_ctx, module_manager, rr_graph, routing_annotation, diff --git a/openfpga/src/fpga_sdc/analysis_sdc_routing_writer.h b/openfpga/src/fpga_sdc/analysis_sdc_routing_writer.h index a5ea3249e..7322fc8f6 100644 --- a/openfpga/src/fpga_sdc/analysis_sdc_routing_writer.h +++ b/openfpga/src/fpga_sdc/analysis_sdc_routing_writer.h @@ -6,6 +6,7 @@ *******************************************************************/ #include #include +#include "vpr_context.h" #include "module_manager.h" #include "device_rr_gsb.h" @@ -17,6 +18,7 @@ namespace openfpga { void print_analysis_sdc_disable_unused_cbs(std::fstream& fp, + const AtomContext& atom_ctx, const ModuleManager& module_manager, const RRGraph& rr_graph, const VprRoutingAnnotation& routing_annotation, @@ -24,6 +26,7 @@ void print_analysis_sdc_disable_unused_cbs(std::fstream& fp, const bool& compact_routing_hierarchy); void print_analysis_sdc_disable_unused_sbs(std::fstream& fp, + const AtomContext& atom_ctx, const ModuleManager& module_manager, const RRGraph& rr_graph, const VprRoutingAnnotation& routing_annotation, diff --git a/openfpga/src/fpga_sdc/analysis_sdc_writer_utils.cpp b/openfpga/src/fpga_sdc/analysis_sdc_writer_utils.cpp index 511271915..8ac184b61 100644 --- a/openfpga/src/fpga_sdc/analysis_sdc_writer_utils.cpp +++ b/openfpga/src/fpga_sdc/analysis_sdc_writer_utils.cpp @@ -53,9 +53,8 @@ void disable_analysis_module_input_pin_net_sinks(std::fstream& fp, const std::string& parent_instance_name, const ModulePortId& module_input_port, const size_t& module_input_pin, - const VprRoutingAnnotation& routing_annotation, - const RRNodeId& input_rr_node, - const std::map mux_instance_to_net_map) { + const AtomNetId& mapped_net, + const std::map mux_instance_to_net_map) { /* Validate file stream */ valid_file_stream(fp); @@ -78,14 +77,14 @@ void disable_analysis_module_input_pin_net_sinks(std::fstream& fp, std::string sink_instance_name = module_manager.instance_name(parent_module, sink_module, sink_instance); bool disable_timing = false; /* Check if this node is used by benchmark */ - if (true == is_rr_node_to_be_disable_for_analysis(routing_annotation, input_rr_node)) { + if (AtomNetId::INVALID() == mapped_net) { /* Disable all the sinks! */ disable_timing = true; } else { - std::map::const_iterator it = mux_instance_to_net_map.find(sink_instance_name); + std::map::const_iterator it = mux_instance_to_net_map.find(sink_instance_name); if (it != mux_instance_to_net_map.end()) { /* See if the net id matches. If does not match, we should disable! */ - if (routing_annotation.rr_node_net(input_rr_node) != mux_instance_to_net_map.at(sink_instance_name)) { + if (mapped_net != mux_instance_to_net_map.at(sink_instance_name)) { disable_timing = true; } } @@ -132,9 +131,8 @@ void disable_analysis_module_input_port_net_sinks(std::fstream& fp, const ModuleId& parent_module, const std::string& parent_instance_name, const ModulePortId& module_input_port, - const VprRoutingAnnotation& routing_annotation, - const RRNodeId& input_rr_node, - const std::map mux_instance_to_net_map) { + const AtomNetId& mapped_net, + const std::map mux_instance_to_net_map) { /* Validate file stream */ valid_file_stream(fp); @@ -143,7 +141,7 @@ void disable_analysis_module_input_port_net_sinks(std::fstream& fp, disable_analysis_module_input_pin_net_sinks(fp, module_manager, parent_module, parent_instance_name, module_input_port, pin, - routing_annotation, input_rr_node, + mapped_net, mux_instance_to_net_map); } } @@ -178,9 +176,8 @@ void disable_analysis_module_output_pin_net_sinks(std::fstream& fp, const size_t& child_instance, const ModulePortId& child_module_port, const size_t& child_module_pin, - const VprRoutingAnnotation& routing_annotation, - const RRNodeId& output_rr_node, - const std::map mux_instance_to_net_map) { + const AtomNetId& mapped_net, + const std::map mux_instance_to_net_map) { /* Validate file stream */ valid_file_stream(fp); @@ -203,14 +200,14 @@ void disable_analysis_module_output_pin_net_sinks(std::fstream& fp, std::string sink_instance_name = module_manager.instance_name(parent_module, sink_module, sink_instance); bool disable_timing = false; /* Check if this node is used by benchmark */ - if (true == is_rr_node_to_be_disable_for_analysis(routing_annotation, output_rr_node)) { + if (AtomNetId::INVALID() == mapped_net) { /* Disable all the sinks! */ disable_timing = true; } else { - std::map::const_iterator it = mux_instance_to_net_map.find(sink_instance_name); + std::map::const_iterator it = mux_instance_to_net_map.find(sink_instance_name); if (it != mux_instance_to_net_map.end()) { /* See if the net id matches. If does not match, we should disable! */ - if (routing_annotation.rr_node_net(output_rr_node) != mux_instance_to_net_map.at(sink_instance_name)) { + if (mapped_net != mux_instance_to_net_map.at(sink_instance_name)) { disable_timing = true; } } diff --git a/openfpga/src/fpga_sdc/analysis_sdc_writer_utils.h b/openfpga/src/fpga_sdc/analysis_sdc_writer_utils.h index 12b0dc362..3e5582f94 100644 --- a/openfpga/src/fpga_sdc/analysis_sdc_writer_utils.h +++ b/openfpga/src/fpga_sdc/analysis_sdc_writer_utils.h @@ -9,6 +9,7 @@ #include #include "module_manager.h" #include "rr_graph_obj.h" +#include "atom_netlist_fwd.h" #include "vpr_routing_annotation.h" /******************************************************************** @@ -27,18 +28,16 @@ void disable_analysis_module_input_pin_net_sinks(std::fstream& fp, const std::string& parent_instance_name, const ModulePortId& module_input_port, const size_t& module_input_pin, - const VprRoutingAnnotation& routing_annotation, - const RRNodeId& input_rr_node, - const std::map mux_instance_to_net_map); + const AtomNetId& mapped_net, + const std::map mux_instance_to_net_map); void disable_analysis_module_input_port_net_sinks(std::fstream& fp, const ModuleManager& module_manager, const ModuleId& parent_module, const std::string& parent_instance_name, const ModulePortId& module_input_port, - const VprRoutingAnnotation& routing_annotation, - const RRNodeId& input_rr_node, - const std::map mux_instance_to_net_map); + const AtomNetId& mapped_net, + const std::map mux_instance_to_net_map); void disable_analysis_module_output_pin_net_sinks(std::fstream& fp, const ModuleManager& module_manager, @@ -48,9 +47,8 @@ void disable_analysis_module_output_pin_net_sinks(std::fstream& fp, const size_t& child_instance, const ModulePortId& child_module_port, const size_t& child_module_pin, - const VprRoutingAnnotation& routing_annotation, - const RRNodeId& output_rr_node, - const std::map mux_instance_to_net_map); + const AtomNetId& mapped_net, + const std::map mux_instance_to_net_map); } /* end namespace openfpga */ From 037c7e5c43ceb6f98190ebaa18faab8b08dad74c Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 2 Mar 2020 17:58:44 -0700 Subject: [PATCH 245/645] adapt top-level function for analysis SDC writer --- openfpga/src/base/openfpga_context.h | 4 +- .../fpga_sdc/analysis_sdc_routing_writer.h | 1 + openfpga/src/fpga_sdc/analysis_sdc_writer.cpp | 284 ++++++++++++++++++ openfpga/src/fpga_sdc/analysis_sdc_writer.h | 28 ++ 4 files changed, 315 insertions(+), 2 deletions(-) create mode 100644 openfpga/src/fpga_sdc/analysis_sdc_writer.cpp create mode 100644 openfpga/src/fpga_sdc/analysis_sdc_writer.h diff --git a/openfpga/src/base/openfpga_context.h b/openfpga/src/base/openfpga_context.h index 20e235a35..d4320915f 100644 --- a/openfpga/src/base/openfpga_context.h +++ b/openfpga/src/base/openfpga_context.h @@ -59,8 +59,8 @@ class OpenfpgaContext : public Context { const openfpga::FlowManager& flow_manager() const { return flow_manager_; } const openfpga::BitstreamManager& bitstream_manager() const { return bitstream_manager_; } const std::vector& fabric_bitstream() const { return fabric_bitstream_; } - const openfpga::IoLocationMap& io_location_map() { return io_location_map_; } - const std::unordered_map& net_activity() { return net_activity_; } + const openfpga::IoLocationMap& io_location_map() const { return io_location_map_; } + const std::unordered_map& net_activity() const { return net_activity_; } public: /* Public mutators */ openfpga::Arch& mutable_arch() { return arch_; } openfpga::VprDeviceAnnotation& mutable_vpr_device_annotation() { return vpr_device_annotation_; } diff --git a/openfpga/src/fpga_sdc/analysis_sdc_routing_writer.h b/openfpga/src/fpga_sdc/analysis_sdc_routing_writer.h index 7322fc8f6..e50bc73b6 100644 --- a/openfpga/src/fpga_sdc/analysis_sdc_routing_writer.h +++ b/openfpga/src/fpga_sdc/analysis_sdc_routing_writer.h @@ -9,6 +9,7 @@ #include "vpr_context.h" #include "module_manager.h" #include "device_rr_gsb.h" +#include "vpr_routing_annotation.h" /******************************************************************** * Function declaration diff --git a/openfpga/src/fpga_sdc/analysis_sdc_writer.cpp b/openfpga/src/fpga_sdc/analysis_sdc_writer.cpp new file mode 100644 index 000000000..d9699d9b0 --- /dev/null +++ b/openfpga/src/fpga_sdc/analysis_sdc_writer.cpp @@ -0,0 +1,284 @@ +/******************************************************************** + * This file includes functions that are used to output a SDC file + * that constrain a FPGA fabric (P&Red netlist) using a benchmark + *******************************************************************/ +#include +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_time.h" + +/* Headers from openfpgautil library */ +#include "openfpga_digest.h" +#include "openfpga_port.h" + +#include "mux_utils.h" + +#include "openfpga_naming.h" +#include "openfpga_atom_netlist_utils.h" + +#include "sdc_writer_naming.h" +#include "sdc_writer_utils.h" +#include "sdc_memory_utils.h" + +#include "analysis_sdc_grid_writer.h" +#include "analysis_sdc_routing_writer.h" +#include "analysis_sdc_writer.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Generate SDC constaints for inputs and outputs + * We consider the top module in formal verification purpose here + * which is easier + *******************************************************************/ +static +void print_analysis_sdc_io_delays(std::fstream& fp, + const AtomContext& atom_ctx, + const PlacementContext& place_ctx, + const VprNetlistAnnotation& netlist_annotation, + const IoLocationMap& io_location_map, + const ModuleManager& module_manager, + const ModuleId& top_module, + const CircuitLibrary& circuit_lib, + const std::vector& global_ports, + const float& critical_path_delay) { + /* Validate the file stream */ + valid_file_stream(fp); + + /* Print comments */ + fp << "##################################################" << std::endl; + fp << "# Create clock " << std::endl; + fp << "##################################################" << std::endl; + + /* Get clock port from the global port */ + std::vector operating_clock_ports; + for (const CircuitPortId& clock_port : global_ports) { + if (CIRCUIT_MODEL_PORT_CLOCK != circuit_lib.port_type(clock_port)) { + continue; + } + /* We only constrain operating clock here! */ + if (true == circuit_lib.port_is_prog(clock_port)) { + continue; + } + + /* Find the module port and Update the operating port list */ + ModulePortId module_port = module_manager.find_module_port(top_module, circuit_lib.port_prefix(clock_port)); + operating_clock_ports.push_back(module_manager.module_port(top_module, module_port)); + } + + for (const BasicPort& operating_clock_port : operating_clock_ports) { + /* Reach here, it means a clock port and we need print constraints */ + fp << "create_clock "; + fp << generate_sdc_port(operating_clock_port); + fp << " -period " << std::setprecision(10) << critical_path_delay; + fp << " -waveform {0 " << std::setprecision(10) << critical_path_delay / 2 << "}"; + fp << std::endl; + + /* Add an empty line as a splitter */ + fp << std::endl; + } + + /* There should be only one operating clock! + * TODO: this should be changed when developing multi-clock support!!! + */ + VTR_ASSERT(1 == operating_clock_ports.size()); + + /* In this function, we support only 1 type of I/Os */ + VTR_ASSERT(1 == module_manager.module_ports_by_type(top_module, ModuleManager::MODULE_GPIO_PORT).size()); + BasicPort module_io_port = module_manager.module_ports_by_type(top_module, ModuleManager::MODULE_GPIO_PORT)[0]; + + /* Keep tracking which I/Os have been used */ + std::vector io_used(module_io_port.get_width(), false); + + /* Find clock ports in benchmark */ + std::vector benchmark_clock_port_names = find_atom_netlist_clock_port_names(atom_ctx.nlist, netlist_annotation); + + /* Print comments */ + fp << "##################################################" << std::endl; + fp << "# Create input and output delays for used I/Os " << std::endl; + fp << "##################################################" << std::endl; + + for (const AtomBlockId& atom_blk : atom_ctx.nlist.blocks()) { + /* Bypass non-I/O atom blocks ! */ + if ( (AtomBlockType::INPAD != atom_ctx.nlist.block_type(atom_blk)) + && (AtomBlockType::OUTPAD != atom_ctx.nlist.block_type(atom_blk)) ) { + continue; + } + + /* clock net or constant generator should be disabled in timing analysis */ + if (benchmark_clock_port_names.end() != std::find(benchmark_clock_port_names.begin(), benchmark_clock_port_names.end(), atom_ctx.nlist.block_name(atom_blk))) { + continue; + } + + /* Find the index of the mapped GPIO in top-level FPGA fabric */ + size_t io_index = io_location_map.io_index(place_ctx.block_locs[atom_ctx.lookup.atom_clb(atom_blk)].loc.x, + place_ctx.block_locs[atom_ctx.lookup.atom_clb(atom_blk)].loc.y, + place_ctx.block_locs[atom_ctx.lookup.atom_clb(atom_blk)].loc.z); + + /* Ensure that IO index is in range */ + BasicPort module_mapped_io_port = module_io_port; + /* Set the port pin index */ + VTR_ASSERT(io_index < module_mapped_io_port.get_width()); + module_mapped_io_port.set_width(io_index, io_index); + + /* For input I/O, we set an input delay constraint correlated to the operating clock + * For output I/O, we set an output delay constraint correlated to the operating clock + */ + if (AtomBlockType::INPAD == atom_ctx.nlist.block_type(atom_blk)) { + print_sdc_set_port_input_delay(fp, module_mapped_io_port, + operating_clock_ports[0], critical_path_delay); + } else { + VTR_ASSERT(AtomBlockType::OUTPAD == atom_ctx.nlist.block_type(atom_blk)); + print_sdc_set_port_output_delay(fp, module_mapped_io_port, + operating_clock_ports[0], critical_path_delay); + } + + /* Mark this I/O has been used/wired */ + io_used[io_index] = true; + } + + /* Add an empty line as a splitter */ + fp << std::endl; + + /* Print comments */ + fp << "##################################################" << std::endl; + fp << "# Disable timing for unused I/Os " << std::endl; + fp << "##################################################" << std::endl; + + /* Wire the unused iopads to a constant */ + for (size_t io_index = 0; io_index < io_used.size(); ++io_index) { + /* Bypass used iopads */ + if (true == io_used[io_index]) { + continue; + } + + /* Wire to a contant */ + BasicPort module_unused_io_port = module_io_port; + /* Set the port pin index */ + module_unused_io_port.set_width(io_index, io_index); + print_sdc_disable_port_timing(fp, module_unused_io_port); + } + + /* Add an empty line as a splitter */ + fp << std::endl; +} + +/******************************************************************** + * Disable the timing for all the global port except the operating clock ports + *******************************************************************/ +static +void print_analysis_sdc_disable_global_ports(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module, + const CircuitLibrary& circuit_lib, + const std::vector& global_ports) { + /* Validate file stream */ + valid_file_stream(fp); + + /* Print comments */ + fp << "##################################################" << std::endl; + fp << "# Disable timing for global ports " << std::endl; + fp << "##################################################" << std::endl; + + for (const CircuitPortId& global_port : global_ports) { + /* Skip operating clock here! */ + if ( (CIRCUIT_MODEL_PORT_CLOCK == circuit_lib.port_type(global_port)) + && (false == circuit_lib.port_is_prog(global_port)) ) { + continue; + } + + ModulePortId module_port = module_manager.find_module_port(top_module, circuit_lib.port_prefix(global_port)); + BasicPort port_to_disable = module_manager.module_port(top_module, module_port); + + print_sdc_disable_port_timing(fp, port_to_disable); + } +} + +/******************************************************************** + * Top-level function outputs a SDC file + * that constrain a FPGA fabric (P&Red netlist) using a benchmark + *******************************************************************/ +void print_analysis_sdc(const std::string& sdc_dir, + const float& critical_path_delay, + const VprContext& vpr_ctx, + const OpenfpgaContext& openfpga_ctx, + const std::vector& global_ports, + const bool& compact_routing_hierarchy) { + /* Create the file name for Verilog netlist */ + std::string sdc_fname(sdc_dir + std::string(SDC_ANALYSIS_FILE_NAME)); + + std::string timer_message = std::string("Generating SDC for Timing/Power analysis on the mapped FPGA '") + + sdc_fname + + std::string("'"); + + /* Start time count */ + vtr::ScopedStartFinishTimer timer(timer_message); + + /* Create the file stream */ + std::fstream fp; + fp.open(sdc_fname, std::fstream::out | std::fstream::trunc); + + /* Validate file stream */ + check_file_stream(sdc_fname.c_str(), fp); + + /* Generate the descriptions*/ + print_sdc_file_header(fp, std::string("Constrain for Timing/Power analysis on the mapped FPGA")); + + /* Find the top_module */ + ModuleId top_module = openfpga_ctx.module_graph().find_module(generate_fpga_top_module_name()); + VTR_ASSERT(true == openfpga_ctx.module_graph().valid_module_id(top_module)); + + /* Create clock and set I/O ports with input/output delays */ + print_analysis_sdc_io_delays(fp, + vpr_ctx.atom(), vpr_ctx.placement(), + openfpga_ctx.vpr_netlist_annotation(), openfpga_ctx.io_location_map(), + openfpga_ctx.module_graph(), top_module, + openfpga_ctx.arch().circuit_lib, global_ports, + critical_path_delay); + + /* Disable the timing for global ports */ + print_analysis_sdc_disable_global_ports(fp, + openfpga_ctx.module_graph(), top_module, + openfpga_ctx.arch().circuit_lib, global_ports); + + /* Disable the timing for configuration cells */ + rec_print_pnr_sdc_disable_configurable_memory_module_output(fp, + openfpga_ctx.module_graph(), top_module, + format_dir_path(openfpga_ctx.module_graph().module_name(top_module))); + + + /* Disable timing for unused routing resources in connection blocks */ + print_analysis_sdc_disable_unused_cbs(fp, + vpr_ctx.atom(), + openfpga_ctx.module_graph(), + vpr_ctx.device().rr_graph, + openfpga_ctx.vpr_routing_annotation(), + openfpga_ctx.device_rr_gsb(), + compact_routing_hierarchy); + + /* Disable timing for unused routing resources in switch blocks */ + print_analysis_sdc_disable_unused_sbs(fp, + vpr_ctx.atom(), + openfpga_ctx.module_graph(), + vpr_ctx.device().rr_graph, + openfpga_ctx.vpr_routing_annotation(), + openfpga_ctx.device_rr_gsb(), + compact_routing_hierarchy); + + /* Disable timing for unused routing resources in grids (programmable blocks) */ + print_analysis_sdc_disable_unused_grids(fp, + vpr_ctx.device().grid, + openfpga_ctx.vpr_device_annotation(), + openfpga_ctx.vpr_clustering_annotation(), + openfpga_ctx.vpr_placement_annotation(), + openfpga_ctx.module_graph()); + + /* Close file handler */ + fp.close(); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/analysis_sdc_writer.h b/openfpga/src/fpga_sdc/analysis_sdc_writer.h new file mode 100644 index 000000000..158dda9f4 --- /dev/null +++ b/openfpga/src/fpga_sdc/analysis_sdc_writer.h @@ -0,0 +1,28 @@ +#ifndef ANALYSIS_SDC_WRITER_H +#define ANALYSIS_SDC_WRITER_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "vpr_context.h" +#include "openfpga_context.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_analysis_sdc(const std::string& sdc_dir, + const float& critical_path_delay, + const VprContext& vpr_ctx, + const OpenfpgaContext& openfpga_ctx, + const std::vector& global_ports, + const bool& compact_routing_hierarchy); + +} /* end namespace openfpga */ + +#endif From 3241d8bd3746c4e776c9e268b495242c42e45278 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 2 Mar 2020 19:54:18 -0700 Subject: [PATCH 246/645] put analysis sdc writer online. Minor bug in redudant '/' to be fixed --- openfpga/src/base/openfpga_sdc.cpp | 35 ++++++++++++++++ openfpga/src/base/openfpga_sdc.h | 3 ++ openfpga/src/base/openfpga_sdc_command.cpp | 41 +++++++++++++++++++ .../src/fpga_sdc/analysis_sdc_grid_writer.cpp | 9 ++-- openfpga/src/fpga_sdc/analysis_sdc_writer.cpp | 4 +- openfpga/src/fpga_sdc/analysis_sdc_writer.h | 3 +- .../fpga_sdc/analysis_sdc_writer_utils.cpp | 4 +- openfpga/test_script/and_k6_frac.openfpga | 3 ++ 8 files changed, 92 insertions(+), 10 deletions(-) diff --git a/openfpga/src/base/openfpga_sdc.cpp b/openfpga/src/base/openfpga_sdc.cpp index eaa2b9941..8b708f133 100644 --- a/openfpga/src/base/openfpga_sdc.cpp +++ b/openfpga/src/base/openfpga_sdc.cpp @@ -10,6 +10,7 @@ #include "circuit_library_utils.h" #include "pnr_sdc_writer.h" +#include "analysis_sdc_writer.h" #include "openfpga_sdc.h" /* Include global variables of VPR */ @@ -77,4 +78,38 @@ void write_pnr_sdc(OpenfpgaContext& openfpga_ctx, } } +/******************************************************************** + * A wrapper function to call the analysis SDC generator of FPGA-SDC + *******************************************************************/ +void write_analysis_sdc(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context) { + + CommandOptionId opt_output_dir = cmd.option("file"); + + /* This is an intermediate data structure which is designed to modularize the FPGA-SDC + * Keep it independent from any other outside data structures + */ + std::string sdc_dir_path = format_dir_path(cmd_context.option_value(cmd, opt_output_dir)); + + /* Create directories */ + create_dir_path(sdc_dir_path.c_str()); + + AnalysisSdcOption options(sdc_dir_path); + options.set_generate_sdc_analysis(true); + + /* Collect global ports from the circuit library: + * TODO: should we place this in the OpenFPGA context? + */ + std::vector global_ports = find_circuit_library_global_ports(openfpga_ctx.arch().circuit_lib); + + if (true == options.generate_sdc_analysis()) { + print_analysis_sdc(options, + 1./openfpga_ctx.arch().sim_setting.operating_clock_frequency(), + g_vpr_ctx, + openfpga_ctx, + global_ports, + openfpga_ctx.flow_manager().compress_routing()); + } +} + } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_sdc.h b/openfpga/src/base/openfpga_sdc.h index 4e48964f3..110a8cd72 100644 --- a/openfpga/src/base/openfpga_sdc.h +++ b/openfpga/src/base/openfpga_sdc.h @@ -18,6 +18,9 @@ namespace openfpga { void write_pnr_sdc(OpenfpgaContext& openfpga_ctx, const Command& cmd, const CommandContext& cmd_context); +void write_analysis_sdc(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context); + } /* end namespace openfpga */ #endif diff --git a/openfpga/src/base/openfpga_sdc_command.cpp b/openfpga/src/base/openfpga_sdc_command.cpp index 799fad4e6..91d65aa0a 100644 --- a/openfpga/src/base/openfpga_sdc_command.cpp +++ b/openfpga/src/base/openfpga_sdc_command.cpp @@ -61,6 +61,36 @@ ShellCommandId add_openfpga_write_pnr_sdc_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id, + const std::vector& dependent_cmds) { + Command shell_cmd("write_analysis_sdc"); + + /* Add an option '--file' in short '-f'*/ + CommandOptionId output_opt = shell_cmd.add_option("file", true, "Specify the output directory for SDC files"); + shell_cmd.set_option_short_name(output_opt, "f"); + shell_cmd.set_option_require_value(output_opt, openfpga::OPT_STRING); + + /* Add an option '--verbose' */ + shell_cmd.add_option("verbose", false, "Enable verbose output"); + + /* Add command 'write_fabric_verilog' to the Shell */ + ShellCommandId shell_cmd_id = shell.add_command(shell_cmd, "generate SDC files for timing analysis a PnRed FPGA fabric mapped by a benchmark"); + shell.set_command_class(shell_cmd_id, cmd_class_id); + shell.set_command_execute_function(shell_cmd_id, write_analysis_sdc); + + /* Add command dependency to the Shell */ + shell.set_command_dependency(shell_cmd_id, dependent_cmds); + + return shell_cmd_id; +} + void add_openfpga_sdc_commands(openfpga::Shell& shell) { /* Get the unique id of 'build_fabric' command which is to be used in creating the dependency graph */ const ShellCommandId& build_fabric_id = shell.command(std::string("build_fabric")); @@ -77,6 +107,17 @@ void add_openfpga_sdc_commands(openfpga::Shell& shell) { add_openfpga_write_pnr_sdc_command(shell, openfpga_sdc_cmd_class, pnr_sdc_cmd_dependency); + + /******************************** + * Command 'write_analysis_sdc' + */ + /* The 'write_analysis_sdc' command should NOT be executed before 'build_fabric' */ + std::vector analysis_sdc_cmd_dependency; + analysis_sdc_cmd_dependency.push_back(build_fabric_id); + add_openfpga_write_analysis_sdc_command(shell, + openfpga_sdc_cmd_class, + analysis_sdc_cmd_dependency); + } } /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/analysis_sdc_grid_writer.cpp b/openfpga/src/fpga_sdc/analysis_sdc_grid_writer.cpp index 19d501a79..d868df992 100644 --- a/openfpga/src/fpga_sdc/analysis_sdc_grid_writer.cpp +++ b/openfpga/src/fpga_sdc/analysis_sdc_grid_writer.cpp @@ -49,7 +49,7 @@ void rec_print_analysis_sdc_disable_unused_pb_graph_nodes(std::fstream& fp, */ fp << "set_disable_timing "; fp << hierarchy_name; - fp << "/*"; + fp << "*"; fp << std::endl; /* Return if this is the primitive pb_type */ @@ -74,7 +74,7 @@ void rec_print_analysis_sdc_disable_unused_pb_graph_nodes(std::fstream& fp, /* Must have a valid instance name!!! */ VTR_ASSERT(false == child_instance_name.empty()); - std::string updated_hierarchy_name = hierarchy_name + std::string("/") + child_instance_name + std::string("/"); + std::string updated_hierarchy_name = hierarchy_name + child_instance_name + std::string("/"); rec_print_analysis_sdc_disable_unused_pb_graph_nodes(fp, device_annotation, module_manager, child_module, hierarchy_name, &(physical_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ichild][inst])); @@ -114,7 +114,6 @@ void disable_pb_graph_node_unused_pin(std::fstream& fp, fp << "set_disable_timing "; fp << hierarchy_name; - fp << "/"; fp << generate_sdc_port(port_to_disable); fp << std::endl; } @@ -407,7 +406,7 @@ void rec_print_analysis_sdc_disable_pb_graph_node_unused_resources(std::fstream& /* Must have a valid instance name!!! */ VTR_ASSERT(false == child_instance_name.empty()); - std::string updated_hierarchy_name = hierarchy_name + std::string("/") + child_instance_name + std::string("/"); + std::string updated_hierarchy_name = hierarchy_name + child_instance_name + std::string("/"); rec_print_analysis_sdc_disable_pb_graph_node_unused_resources(fp, device_annotation, module_manager, child_module, hierarchy_name, @@ -591,7 +590,7 @@ void print_analysis_sdc_disable_unused_grids(std::fstream& fp, for (size_t ix = 1; ix < grids.width() - 1; ++ix) { for (size_t iy = 1; iy < grids.height() - 1; ++iy) { /* We should not meet any I/O grid */ - VTR_ASSERT(false != is_io_type(grids[ix][iy].type)); + VTR_ASSERT(false == is_io_type(grids[ix][iy].type)); print_analysis_sdc_disable_unused_grid(fp, vtr::Point(ix, iy), grids, device_annotation, cluster_annotation, place_annotation, diff --git a/openfpga/src/fpga_sdc/analysis_sdc_writer.cpp b/openfpga/src/fpga_sdc/analysis_sdc_writer.cpp index d9699d9b0..de9dfb55d 100644 --- a/openfpga/src/fpga_sdc/analysis_sdc_writer.cpp +++ b/openfpga/src/fpga_sdc/analysis_sdc_writer.cpp @@ -202,14 +202,14 @@ void print_analysis_sdc_disable_global_ports(std::fstream& fp, * Top-level function outputs a SDC file * that constrain a FPGA fabric (P&Red netlist) using a benchmark *******************************************************************/ -void print_analysis_sdc(const std::string& sdc_dir, +void print_analysis_sdc(const AnalysisSdcOption& option, const float& critical_path_delay, const VprContext& vpr_ctx, const OpenfpgaContext& openfpga_ctx, const std::vector& global_ports, const bool& compact_routing_hierarchy) { /* Create the file name for Verilog netlist */ - std::string sdc_fname(sdc_dir + std::string(SDC_ANALYSIS_FILE_NAME)); + std::string sdc_fname(option.sdc_dir() + std::string(SDC_ANALYSIS_FILE_NAME)); std::string timer_message = std::string("Generating SDC for Timing/Power analysis on the mapped FPGA '") + sdc_fname diff --git a/openfpga/src/fpga_sdc/analysis_sdc_writer.h b/openfpga/src/fpga_sdc/analysis_sdc_writer.h index 158dda9f4..c3d08794a 100644 --- a/openfpga/src/fpga_sdc/analysis_sdc_writer.h +++ b/openfpga/src/fpga_sdc/analysis_sdc_writer.h @@ -8,6 +8,7 @@ #include #include "vpr_context.h" #include "openfpga_context.h" +#include "analysis_sdc_option.h" /******************************************************************** * Function declaration @@ -16,7 +17,7 @@ /* begin namespace openfpga */ namespace openfpga { -void print_analysis_sdc(const std::string& sdc_dir, +void print_analysis_sdc(const AnalysisSdcOption& option, const float& critical_path_delay, const VprContext& vpr_ctx, const OpenfpgaContext& openfpga_ctx, diff --git a/openfpga/src/fpga_sdc/analysis_sdc_writer_utils.cpp b/openfpga/src/fpga_sdc/analysis_sdc_writer_utils.cpp index 8ac184b61..974940eb9 100644 --- a/openfpga/src/fpga_sdc/analysis_sdc_writer_utils.cpp +++ b/openfpga/src/fpga_sdc/analysis_sdc_writer_utils.cpp @@ -100,7 +100,7 @@ void disable_analysis_module_input_pin_net_sinks(std::fstream& fp, module_manager.net_sink_pins(parent_module, module_net)[sink_id]); /* Get the input id that is used! Disable the unused inputs! */ fp << "set_disable_timing "; - fp << parent_instance_name << "/"; + fp << parent_instance_name; fp << sink_instance_name << "/"; fp << generate_sdc_port(sink_port); fp << std::endl; @@ -223,7 +223,7 @@ void disable_analysis_module_output_pin_net_sinks(std::fstream& fp, module_manager.net_sink_pins(parent_module, module_net)[sink_id]); /* Get the input id that is used! Disable the unused inputs! */ fp << "set_disable_timing "; - fp << parent_instance_name << "/"; + fp << parent_instance_name; fp << sink_instance_name << "/"; fp << generate_sdc_port(sink_port); fp << std::endl; diff --git a/openfpga/test_script/and_k6_frac.openfpga b/openfpga/test_script/and_k6_frac.openfpga index 1018887f1..4133dcea5 100644 --- a/openfpga/test_script/and_k6_frac.openfpga +++ b/openfpga/test_script/and_k6_frac.openfpga @@ -52,5 +52,8 @@ write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src/SRC --reference_ # - Turn on every options here write_pnr_sdc --file /var/tmp/xtang/openfpga_test_src/SDC +# Write the SDC to run timing analysis for a mapped FPGA fabric +write_analysis_sdc --file /var/tmp/xtang/openfpga_test_src/SDC_analysis + # Finish and exit OpenFPGA exit From 7fcd27e00076e4500c1eeceb8a70d4ead4c0f7ca Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 3 Mar 2020 12:29:58 -0700 Subject: [PATCH 247/645] now we give explicit instance name to each interconnect inside grid. Thus resolve the problem in sdc writer --- openfpga/src/base/openfpga_naming.cpp | 13 +++++++++++++ openfpga/src/base/openfpga_naming.h | 3 +++ openfpga/src/fabric/build_grid_modules.cpp | 6 ++++++ .../src/fpga_sdc/analysis_sdc_grid_writer.cpp | 16 ++++++++++++++-- .../src/fpga_sdc/analysis_sdc_writer_utils.cpp | 4 ++++ .../src/fpga_verilog/verilog_module_writer.cpp | 4 +++- 6 files changed, 43 insertions(+), 3 deletions(-) diff --git a/openfpga/src/base/openfpga_naming.cpp b/openfpga/src/base/openfpga_naming.cpp index fab27a12b..9bfad01a2 100644 --- a/openfpga/src/base/openfpga_naming.cpp +++ b/openfpga/src/base/openfpga_naming.cpp @@ -17,6 +17,19 @@ /* begin namespace openfpga */ namespace openfpga { +/************************************************ + * A generic function to generate the instance name + * in the following format: + * __ + * This is mainly used by module manager to give a default + * name for each instance when outputting the module + * in Verilog/SPICE format + ***********************************************/ +std::string generate_instance_name(const std::string& instance_name, + const size_t& instance_id) { + return instance_name + std::string("_") + std::to_string(instance_id) + std::string("_"); +} + /************************************************ * Generate the node name for a multiplexing structure * Case 1 : If there is an intermediate buffer followed by, diff --git a/openfpga/src/base/openfpga_naming.h b/openfpga/src/base/openfpga_naming.h index 13d4c56c0..e4f1ee8f8 100644 --- a/openfpga/src/base/openfpga_naming.h +++ b/openfpga/src/base/openfpga_naming.h @@ -23,6 +23,9 @@ /* begin namespace openfpga */ namespace openfpga { +std::string generate_instance_name(const std::string& instance_name, + const size_t& instance_id); + std::string generate_mux_node_name(const size_t& node_level, const bool& add_buffer_postfix); diff --git a/openfpga/src/fabric/build_grid_modules.cpp b/openfpga/src/fabric/build_grid_modules.cpp index a5c346025..2ee5527e7 100644 --- a/openfpga/src/fabric/build_grid_modules.cpp +++ b/openfpga/src/fabric/build_grid_modules.cpp @@ -428,6 +428,12 @@ void add_module_pb_graph_pin_interc(ModuleManager& module_manager, size_t wire_instance = module_manager.num_instance(pb_module, wire_module); module_manager.add_child_module(pb_module, wire_module); + /* Give an instance name: this name should be consistent with the block name given in SDC generator, + * If you want to bind the SDC generation to modules + */ + std::string wire_instance_name = generate_instance_name(module_manager.module_name(wire_module), wire_instance); + module_manager.set_child_instance_name(pb_module, wire_module, wire_instance, wire_instance_name); + /* Ensure input and output ports of the wire model has only 1 pin respectively */ VTR_ASSERT(1 == circuit_lib.port_size(interc_model_inputs[0])); VTR_ASSERT(1 == circuit_lib.port_size(interc_model_outputs[0])); diff --git a/openfpga/src/fpga_sdc/analysis_sdc_grid_writer.cpp b/openfpga/src/fpga_sdc/analysis_sdc_grid_writer.cpp index d868df992..d9a85280b 100644 --- a/openfpga/src/fpga_sdc/analysis_sdc_grid_writer.cpp +++ b/openfpga/src/fpga_sdc/analysis_sdc_grid_writer.cpp @@ -47,6 +47,10 @@ void rec_print_analysis_sdc_disable_unused_pb_graph_nodes(std::fstream& fp, /* Disable all the ports of current module (parent_module)! * Hierarchy name already includes the instance name of parent_module */ + fp << "#######################################" << std::endl; + fp << "# Disable all the ports for pb_graph_node " << physical_pb_graph_node->pb_type->name << "[" << physical_pb_graph_node->placement_index << "]" << std::endl; + fp << "#######################################" << std::endl; + fp << "set_disable_timing "; fp << hierarchy_name; fp << "*"; @@ -76,7 +80,7 @@ void rec_print_analysis_sdc_disable_unused_pb_graph_nodes(std::fstream& fp, std::string updated_hierarchy_name = hierarchy_name + child_instance_name + std::string("/"); - rec_print_analysis_sdc_disable_unused_pb_graph_nodes(fp, device_annotation, module_manager, child_module, hierarchy_name, + rec_print_analysis_sdc_disable_unused_pb_graph_nodes(fp, device_annotation, module_manager, child_module, updated_hierarchy_name, &(physical_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ichild][inst])); } } @@ -135,6 +139,10 @@ void disable_pb_graph_node_unused_pins(std::fstream& fp, const PhysicalPbId& pb_id = physical_pb.find_pb(physical_pb_graph_node); VTR_ASSERT(true == physical_pb.valid_pb_id(pb_id)); + fp << "#######################################" << std::endl; + fp << "# Disable unused pins for pb_graph_node " << physical_pb_graph_node->pb_type->name << "[" << physical_pb_graph_node->placement_index << "]" << std::endl; + fp << "#######################################" << std::endl; + /* Disable unused input pins */ for (int iport = 0; iport < physical_pb_graph_node->num_input_ports; ++iport) { for (int ipin = 0; ipin < physical_pb_graph_node->num_input_pins[iport]; ++ipin) { @@ -180,6 +188,10 @@ void disable_pb_graph_node_unused_mux_inputs(std::fstream& fp, t_pb_graph_node* physical_pb_graph_node, const PhysicalPb& physical_pb) { + fp << "#######################################" << std::endl; + fp << "# Disable unused mux_inputs for pb_graph_node " << physical_pb_graph_node->pb_type->name << "[" << physical_pb_graph_node->placement_index << "]" << std::endl; + fp << "#######################################" << std::endl; + t_pb_type* physical_pb_type = physical_pb_graph_node->pb_type; t_mode* physical_mode = device_annotation.physical_mode(physical_pb_type); @@ -409,7 +421,7 @@ void rec_print_analysis_sdc_disable_pb_graph_node_unused_resources(std::fstream& std::string updated_hierarchy_name = hierarchy_name + child_instance_name + std::string("/"); rec_print_analysis_sdc_disable_pb_graph_node_unused_resources(fp, device_annotation, - module_manager, child_module, hierarchy_name, + module_manager, child_module, updated_hierarchy_name, &(physical_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ichild][inst]), physical_pb); } diff --git a/openfpga/src/fpga_sdc/analysis_sdc_writer_utils.cpp b/openfpga/src/fpga_sdc/analysis_sdc_writer_utils.cpp index 974940eb9..f761cafea 100644 --- a/openfpga/src/fpga_sdc/analysis_sdc_writer_utils.cpp +++ b/openfpga/src/fpga_sdc/analysis_sdc_writer_utils.cpp @@ -98,6 +98,8 @@ void disable_analysis_module_input_pin_net_sinks(std::fstream& fp, BasicPort sink_port = module_manager.module_port(sink_module, module_manager.net_sink_ports(parent_module, module_net)[sink_id]); sink_port.set_width(module_manager.net_sink_pins(parent_module, module_net)[sink_id], module_manager.net_sink_pins(parent_module, module_net)[sink_id]); + + VTR_ASSERT(!sink_instance_name.empty()); /* Get the input id that is used! Disable the unused inputs! */ fp << "set_disable_timing "; fp << parent_instance_name; @@ -221,6 +223,8 @@ void disable_analysis_module_output_pin_net_sinks(std::fstream& fp, BasicPort sink_port = module_manager.module_port(sink_module, module_manager.net_sink_ports(parent_module, module_net)[sink_id]); sink_port.set_width(module_manager.net_sink_pins(parent_module, module_net)[sink_id], module_manager.net_sink_pins(parent_module, module_net)[sink_id]); + + VTR_ASSERT(!sink_instance_name.empty()); /* Get the input id that is used! Disable the unused inputs! */ fp << "set_disable_timing "; fp << parent_instance_name; diff --git a/openfpga/src/fpga_verilog/verilog_module_writer.cpp b/openfpga/src/fpga_verilog/verilog_module_writer.cpp index 15a5bc28d..59f863701 100644 --- a/openfpga/src/fpga_verilog/verilog_module_writer.cpp +++ b/openfpga/src/fpga_verilog/verilog_module_writer.cpp @@ -16,6 +16,8 @@ #include "openfpga_port.h" #include "openfpga_digest.h" +#include "openfpga_naming.h" + #include "module_manager_utils.h" #include "verilog_port_types.h" #include "verilog_writer_utils.h" @@ -371,7 +373,7 @@ void write_verilog_instance_to_file(std::fstream& fp, * if not, we use a default name _ */ if (true == module_manager.instance_name(parent_module, child_module, instance_id).empty()) { - fp << module_manager.module_name(child_module) << "_" << instance_id << "_" << " (" << std::endl; + fp << generate_instance_name(module_manager.module_name(child_module), instance_id) << " (" << std::endl; } else { fp << module_manager.instance_name(parent_module, child_module, instance_id) << " (" << std::endl; } From aed3b01800a1ae185f8d6105d43013077a3d169e Mon Sep 17 00:00:00 2001 From: AurelienUoU Date: Wed, 4 Mar 2020 09:09:06 -0700 Subject: [PATCH 248/645] Directlist extension bug fix --- .../module_builder/build_top_module_directs.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/vpr7_x2p/vpr/SRC/fpga_x2p/module_builder/build_top_module_directs.cpp b/vpr7_x2p/vpr/SRC/fpga_x2p/module_builder/build_top_module_directs.cpp index 6179e4d4c..06d3339d1 100644 --- a/vpr7_x2p/vpr/SRC/fpga_x2p/module_builder/build_top_module_directs.cpp +++ b/vpr7_x2p/vpr/SRC/fpga_x2p/module_builder/build_top_module_directs.cpp @@ -285,9 +285,9 @@ vtr::Point find_intra_direct_destination_coordinate(const vtr::Point find_intra_direct_destination_coordinate(const vtr::Point find_intra_direct_destination_coordinate(const vtr::Point| Grid | + * | Grid |<------| Grid | * +------+ +------+ */ for (size_t ix = 1 ; ix < device_size.x() - 1; ++ix) { @@ -373,10 +373,10 @@ vtr::Point find_intra_direct_destination_coordinate(const vtr::Point| Grid | * +------+ +------+ */ - if (NEGATIVE_DIR == direct.x_dir) { + if (POSITIVE_DIR == direct.x_dir) { std::reverse(second_search_space.begin(), second_search_space.end()); } } @@ -472,7 +472,7 @@ void add_top_module_nets_inter_clb2clb_direct_connections(ModuleManager& module_ next_col_src_grid_coords.push_back(vtr::Point(ix, iy)); } /* For positive y- direction, we should start from y = 1 */ - if (POSITIVE_DIR == direct.y_dir) { + if (NEGATIVE_DIR == direct.y_dir) { std::reverse(next_col_src_grid_coords.begin(), next_col_src_grid_coords.end()); } vtr::Point src_clb_coord = find_grid_coordinate_given_type(device_size, grids, next_col_src_grid_coords, direct.from_clb_type); From 524798799c0dd41faf01507fb4843a6a8a3354ac Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 4 Mar 2020 11:21:34 -0700 Subject: [PATCH 249/645] start adapting tileable rr_graph builder. Bring channel node detail data structure online --- .../tileable_rr_graph/chan_node_details.cpp | 292 ++++++++++++++++++ vpr/src/tileable_rr_graph/chan_node_details.h | 80 +++++ 2 files changed, 372 insertions(+) create mode 100644 vpr/src/tileable_rr_graph/chan_node_details.cpp create mode 100644 vpr/src/tileable_rr_graph/chan_node_details.h diff --git a/vpr/src/tileable_rr_graph/chan_node_details.cpp b/vpr/src/tileable_rr_graph/chan_node_details.cpp new file mode 100644 index 000000000..b9a2cac07 --- /dev/null +++ b/vpr/src/tileable_rr_graph/chan_node_details.cpp @@ -0,0 +1,292 @@ +/************************************************************************ + * This file contains member functions for class ChanNodeDetails + ***********************************************************************/ +#include +#include +#include "chan_node_details.h" + +/************************************************************************ + * Constructors + ***********************************************************************/ +ChanNodeDetails::ChanNodeDetails(const ChanNodeDetails& src) { + /* duplicate */ + size_t chan_width = src.get_chan_width(); + this->reserve(chan_width); + for (size_t itrack = 0; itrack < chan_width; ++itrack) { + track_node_ids_.push_back(src.get_track_node_id(itrack)); + track_direction_.push_back(src.get_track_direction(itrack)); + seg_ids_.push_back(src.get_track_segment_id(itrack)); + seg_length_.push_back(src.get_track_segment_length(itrack)); + track_start_.push_back(src.is_track_start(itrack)); + track_end_.push_back(src.is_track_end(itrack)); + } +} + +ChanNodeDetails::ChanNodeDetails() { + this->clear(); +} + +/************************************************************************ + * Accessors + ***********************************************************************/ +size_t ChanNodeDetails::get_chan_width() const { + assert(validate_chan_width()); + return track_node_ids_.size(); +} + +size_t ChanNodeDetails::get_track_node_id(const size_t& track_id) const { + assert(validate_track_id(track_id)); + return track_node_ids_[track_id]; +} + +/* Return a copy of vector */ +std::vector ChanNodeDetails::get_track_node_ids() const { + std::vector copy; + for (size_t inode = 0; inode < get_chan_width(); ++inode) { + copy.push_back(track_node_ids_[inode]); + } + return copy; +} + +e_direction ChanNodeDetails::get_track_direction(const size_t& track_id) const { + assert(validate_track_id(track_id)); + return track_direction_[track_id]; +} + +size_t ChanNodeDetails::get_track_segment_length(const size_t& track_id) const { + assert(validate_track_id(track_id)); + return seg_length_[track_id]; +} + +size_t ChanNodeDetails::get_track_segment_id(const size_t& track_id) const { + assert(validate_track_id(track_id)); + return seg_ids_[track_id]; +} + +bool ChanNodeDetails::is_track_start(const size_t& track_id) const { + assert(validate_track_id(track_id)); + return track_start_[track_id]; +} + +bool ChanNodeDetails::is_track_end(const size_t& track_id) const { + assert(validate_track_id(track_id)); + return track_end_[track_id]; +} + +/* Track_id is the starting point of group (whose is_start should be true) + * This function will try to find the track_ids with the same directionality as track_id and seg_length + * A group size is the number of such nodes between the starting points (include the 1st starting point) + */ +std::vector ChanNodeDetails::get_seg_group(const size_t& track_id) const { + assert(validate_chan_width()); + assert(validate_track_id(track_id)); + assert(is_track_start(track_id)); + + std::vector group; + /* Make sure a clean start */ + group.clear(); + + for (size_t itrack = track_id; itrack < get_chan_width(); ++itrack) { + if ( (get_track_direction(itrack) != get_track_direction(track_id) ) + || (get_track_segment_id(itrack) != get_track_segment_id(track_id)) ) { + /* Bypass any nodes in different direction and segment information*/ + continue; + } + if ( (false == is_track_start(itrack)) + || ( (true == is_track_start(itrack)) && (itrack == track_id)) ) { + group.push_back(itrack); + continue; + } + /* Stop if this another starting point */ + if (true == is_track_start(itrack)) { + break; + } + } + return group; +} + +/* Get a list of track_ids with the given list of track indices */ +std::vector ChanNodeDetails::get_seg_group_node_id(const std::vector& seg_group) const { + std::vector group; + /* Make sure a clean start */ + group.clear(); + + for (size_t id = 0; id < seg_group.size(); ++id) { + assert(validate_track_id(seg_group[id])); + group.push_back(get_track_node_id(seg_group[id])); + } + + return group; +} + +/* Get the number of tracks that starts in this routing channel */ +size_t ChanNodeDetails::get_num_starting_tracks(const e_direction& track_direction) const { + size_t counter = 0; + for (size_t itrack = 0; itrack < get_chan_width(); ++itrack) { + /* Bypass unmatched track_direction */ + if (track_direction != get_track_direction(itrack)) { + continue; + } + if (false == is_track_start(itrack)) { + continue; + } + counter++; + } + return counter; +} + +/* Get the number of tracks that ends in this routing channel */ +size_t ChanNodeDetails::get_num_ending_tracks(const e_direction& track_direction) const { + size_t counter = 0; + for (size_t itrack = 0; itrack < get_chan_width(); ++itrack) { + /* Bypass unmatched track_direction */ + if (track_direction != get_track_direction(itrack)) { + continue; + } + if (false == is_track_end(itrack)) { + continue; + } + counter++; + } + return counter; +} + + +/************************************************************************ + * Mutators + ***********************************************************************/ +/* Reserve the capacitcy of vectors */ +void ChanNodeDetails::reserve(const size_t& chan_width) { + track_node_ids_.reserve(chan_width); + track_direction_.reserve(chan_width); + seg_length_.reserve(chan_width); + seg_ids_.reserve(chan_width); + track_start_.reserve(chan_width); + track_end_.reserve(chan_width); +} + +/* Add a track to the channel */ +void ChanNodeDetails::add_track(const size_t& track_node_id, const e_direction& track_direction, + const size_t& seg_id, const size_t& seg_length, + const size_t& is_start, const size_t& is_end) { + track_node_ids_.push_back(track_node_id); + track_direction_.push_back(track_direction); + seg_ids_.push_back(seg_id); + seg_length_.push_back(seg_length); + track_start_.push_back(is_start); + track_end_.push_back(is_end); +} + +/* Update the node_id of a given track */ +void ChanNodeDetails::set_track_node_id(const size_t& track_index, const size_t& track_node_id) { + assert(validate_track_id(track_index)); + track_node_ids_[track_index] = track_node_id; +} + +/* Update the node_ids from a vector */ +void ChanNodeDetails::set_track_node_ids(const std::vector& track_node_ids) { + /* the size of vector should match chan_width */ + assert ( get_chan_width() == track_node_ids.size() ); + for (size_t inode = 0; inode < track_node_ids.size(); ++inode) { + track_node_ids_[inode] = track_node_ids[inode]; + } +} + +/* Set tracks with a given direction to start */ +void ChanNodeDetails::set_tracks_start(const e_direction& track_direction) { + for (size_t inode = 0; inode < get_chan_width(); ++inode) { + /* Bypass non-match tracks */ + if (track_direction != get_track_direction(inode)) { + continue; /* Pass condition*/ + } + track_start_[inode] = true; + } +} + +/* Set tracks with a given direction to end */ +void ChanNodeDetails::set_tracks_end(const e_direction& track_direction) { + for (size_t inode = 0; inode < get_chan_width(); ++inode) { + /* Bypass non-match tracks */ + if (track_direction != get_track_direction(inode)) { + continue; /* Pass condition*/ + } + track_end_[inode] = true; + } +} + +/* rotate the track_node_id by an offset */ +void ChanNodeDetails::rotate_track_node_id(const size_t& offset, const e_direction& track_direction, const bool& counter_rotate) { + /* Direct return if offset = 0*/ + if (0 == offset) { + return; + } + + /* Rotate the node_ids by groups + * A group begins from a track_start and ends before another track_start + */ + assert(validate_chan_width()); + for (size_t itrack = 0; itrack < get_chan_width(); ++itrack) { + /* Bypass non-start segment */ + if (false == is_track_start(itrack) ) { + continue; + } + /* Bypass segments do not match track_direction */ + if (track_direction != get_track_direction(itrack) ) { + continue; + } + /* Find the group nodes */ + std::vector track_group = get_seg_group(itrack); + /* Build a vector of the node ids of the tracks */ + std::vector track_group_node_id = get_seg_group_node_id(track_group); + /* adapt offset to the range of track_group_node_id */ + size_t actual_offset = offset % track_group_node_id.size(); + /* Rotate or Counter rotate */ + if (true == counter_rotate) { + std::rotate(track_group_node_id.rbegin(), track_group_node_id.rbegin() + actual_offset, track_group_node_id.rend()); + } else { + std::rotate(track_group_node_id.begin(), track_group_node_id.begin() + actual_offset, track_group_node_id.end()); + } + /* Update the node_ids */ + for (size_t inode = 0; inode < track_group.size(); ++inode) { + track_node_ids_[track_group[inode]] = track_group_node_id[inode]; + } + } + return; +} + +void ChanNodeDetails::clear() { + track_node_ids_.clear(); + track_direction_.clear(); + seg_ids_.clear(); + seg_length_.clear(); + track_start_.clear(); + track_end_.clear(); +} + +/************************************************************************ + * Validators + ***********************************************************************/ +bool ChanNodeDetails::validate_chan_width() const { + size_t chan_width = track_node_ids_.size(); + if ( (chan_width == track_direction_.size()) + &&(chan_width == seg_ids_.size()) + &&(chan_width == seg_length_.size()) + &&(chan_width == track_start_.size()) + &&(chan_width == track_end_.size()) ) { + return true; + } + return false; +} + +bool ChanNodeDetails::validate_track_id(const size_t& track_id) const { + if ( (track_id < track_node_ids_.size()) + && (track_id < track_direction_.size()) + && (track_id < seg_ids_.size()) + && (track_id < seg_length_.size()) + && (track_id < track_start_.size()) + && (track_id < track_end_.size()) ) { + return true; + } + return false; +} + diff --git a/vpr/src/tileable_rr_graph/chan_node_details.h b/vpr/src/tileable_rr_graph/chan_node_details.h new file mode 100644 index 000000000..abb595965 --- /dev/null +++ b/vpr/src/tileable_rr_graph/chan_node_details.h @@ -0,0 +1,80 @@ +/************************************************************************ + * This file contains a class to model the details of routing node + * in a channel: + * 1. segment information: length, frequency etc. + * 2. starting point of segment + * 3. ending point of segment + * 4. potentail track_id(ptc_num) of each segment + ***********************************************************************/ + +/* IMPORTANT: + * The following preprocessing flags are added to + * avoid compilation error when this headers are included in more than 1 times + */ +#ifndef CHAN_NODE_DETAILS_H +#define CHAN_NODE_DETAILS_H + +/* + * Notes in include header files in a head file + * Only include the neccessary header files + * that is required by the data types in the function/class declarations! + */ +/* Header files should be included in a sequence */ +/* Standard header files required go first */ +#include +#include "vpr_types.h" + +/************************************************************************ + * ChanNodeDetails records segment length, directionality and starting of routing tracks + * +---------------------------------+ + * | Index | Direction | Start Point | + * +---------------------------------+ + * | 0 | --------> | Yes | + * +---------------------------------+ + ***********************************************************************/ + + +class ChanNodeDetails { + public : /* Constructor */ + ChanNodeDetails(const ChanNodeDetails&); /* Duplication */ + ChanNodeDetails(); /* Initilization */ + public: /* Accessors */ + size_t get_chan_width() const; + size_t get_track_node_id(const size_t& track_id) const; + std::vector get_track_node_ids() const; + e_direction get_track_direction(const size_t& track_id) const; + size_t get_track_segment_length(const size_t& track_id) const; + size_t get_track_segment_id(const size_t& track_id) const; + bool is_track_start(const size_t& track_id) const; + bool is_track_end(const size_t& track_id) const; + std::vector get_seg_group(const size_t& track_id) const; + std::vector get_seg_group_node_id(const std::vector& seg_group) const; + size_t get_num_starting_tracks(const e_direction& track_direction) const; + size_t get_num_ending_tracks(const e_direction& track_direction) const; + public: /* Mutators */ + void reserve(const size_t& chan_width); /* Reserve the capacitcy of vectors */ + void add_track(const size_t& track_node_id, const e_direction& track_direction, + const size_t& seg_id, const size_t& seg_length, + const size_t& is_start, const size_t& is_end); + void set_track_node_id(const size_t& track_index, const size_t& track_node_id); + void set_track_node_ids(const std::vector& track_node_ids); + void set_tracks_start(const e_direction& track_direction); + void set_tracks_end(const e_direction& track_direction); + void rotate_track_node_id(const size_t& offset, + const e_direction& track_direction, + const bool& counter_rotate); /* rotate the track_node_id by an offset */ + void clear(); + private: /* validators */ + bool validate_chan_width() const; + bool validate_track_id(const size_t& track_id) const; + private: /* Internal data */ + std::vector track_node_ids_; /* indices of each track */ + std::vector track_direction_; /* direction of each track */ + std::vector seg_ids_; /* id of segment of each track */ + std::vector seg_length_; /* Length of each segment */ + std::vector track_start_; /* flag to identify if this is the starting point of the track */ + std::vector track_end_; /* flag to identify if this is the ending point of the track */ +}; + +#endif + From 4b7d2221d1d6caedcf1b007b1fb4f3a46d6a094c Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 4 Mar 2020 13:55:53 -0700 Subject: [PATCH 250/645] adapt rr_graph builder utilized functions and move rr_graph utils from openfpga to vpr --- .../tileable_rr_graph/chan_node_details.cpp | 4 + vpr/src/tileable_rr_graph/chan_node_details.h | 31 +- .../openfpga_rr_graph_utils.cpp | 0 .../openfpga_rr_graph_utils.h | 0 .../rr_graph_builder_utils.cpp | 289 ++++++++++++++++++ .../rr_graph_builder_utils.h | 69 +++++ 6 files changed, 375 insertions(+), 18 deletions(-) rename {openfpga/src/utils => vpr/src/tileable_rr_graph}/openfpga_rr_graph_utils.cpp (100%) rename {openfpga/src/utils => vpr/src/tileable_rr_graph}/openfpga_rr_graph_utils.h (100%) create mode 100644 vpr/src/tileable_rr_graph/rr_graph_builder_utils.cpp create mode 100644 vpr/src/tileable_rr_graph/rr_graph_builder_utils.h diff --git a/vpr/src/tileable_rr_graph/chan_node_details.cpp b/vpr/src/tileable_rr_graph/chan_node_details.cpp index b9a2cac07..f52188d93 100644 --- a/vpr/src/tileable_rr_graph/chan_node_details.cpp +++ b/vpr/src/tileable_rr_graph/chan_node_details.cpp @@ -5,6 +5,9 @@ #include #include "chan_node_details.h" +/* begin namespace openfpga */ +namespace openfpga { + /************************************************************************ * Constructors ***********************************************************************/ @@ -290,3 +293,4 @@ bool ChanNodeDetails::validate_track_id(const size_t& track_id) const { return false; } +} /* end namespace openfpga */ diff --git a/vpr/src/tileable_rr_graph/chan_node_details.h b/vpr/src/tileable_rr_graph/chan_node_details.h index abb595965..e991b07e0 100644 --- a/vpr/src/tileable_rr_graph/chan_node_details.h +++ b/vpr/src/tileable_rr_graph/chan_node_details.h @@ -1,3 +1,12 @@ +#ifndef CHAN_NODE_DETAILS_H +#define CHAN_NODE_DETAILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include "vpr_types.h" + /************************************************************************ * This file contains a class to model the details of routing node * in a channel: @@ -7,22 +16,8 @@ * 4. potentail track_id(ptc_num) of each segment ***********************************************************************/ -/* IMPORTANT: - * The following preprocessing flags are added to - * avoid compilation error when this headers are included in more than 1 times - */ -#ifndef CHAN_NODE_DETAILS_H -#define CHAN_NODE_DETAILS_H - -/* - * Notes in include header files in a head file - * Only include the neccessary header files - * that is required by the data types in the function/class declarations! - */ -/* Header files should be included in a sequence */ -/* Standard header files required go first */ -#include -#include "vpr_types.h" +/* begin namespace openfpga */ +namespace openfpga { /************************************************************************ * ChanNodeDetails records segment length, directionality and starting of routing tracks @@ -33,7 +28,6 @@ * +---------------------------------+ ***********************************************************************/ - class ChanNodeDetails { public : /* Constructor */ ChanNodeDetails(const ChanNodeDetails&); /* Duplication */ @@ -76,5 +70,6 @@ class ChanNodeDetails { std::vector track_end_; /* flag to identify if this is the ending point of the track */ }; -#endif +} /* end namespace openfpga */ +#endif diff --git a/openfpga/src/utils/openfpga_rr_graph_utils.cpp b/vpr/src/tileable_rr_graph/openfpga_rr_graph_utils.cpp similarity index 100% rename from openfpga/src/utils/openfpga_rr_graph_utils.cpp rename to vpr/src/tileable_rr_graph/openfpga_rr_graph_utils.cpp diff --git a/openfpga/src/utils/openfpga_rr_graph_utils.h b/vpr/src/tileable_rr_graph/openfpga_rr_graph_utils.h similarity index 100% rename from openfpga/src/utils/openfpga_rr_graph_utils.h rename to vpr/src/tileable_rr_graph/openfpga_rr_graph_utils.h diff --git a/vpr/src/tileable_rr_graph/rr_graph_builder_utils.cpp b/vpr/src/tileable_rr_graph/rr_graph_builder_utils.cpp new file mode 100644 index 000000000..209d8a80b --- /dev/null +++ b/vpr/src/tileable_rr_graph/rr_graph_builder_utils.cpp @@ -0,0 +1,289 @@ +/************************************************************************ + * This file contains most utilized functions for rr_graph builders + ***********************************************************************/ +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" + +#include "vpr_utils.h" + +#include "rr_graph_builder_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/************************************************************************ + * Get the class index of a grid pin + ***********************************************************************/ +int get_grid_pin_class_index(const t_grid_tile& cur_grid, + const int pin_index) { + /* check */ + VTR_ASSERT(pin_index < cur_grid.type->num_pins); + return cur_grid.type->pin_class[pin_index]; +} + +/* Deteremine the side of a io grid */ +e_side determine_io_grid_pin_side(const vtr::Point& device_size, + const vtr::Point& grid_coordinate) { + /* TOP side IO of FPGA */ + if (device_size.y() == grid_coordinate.y()) { + return BOTTOM; /* Such I/O has only Bottom side pins */ + } else if (device_size.x() == grid_coordinate.x()) { /* RIGHT side IO of FPGA */ + return LEFT; /* Such I/O has only Left side pins */ + } else if (0 == grid_coordinate.y()) { /* BOTTOM side IO of FPGA */ + return TOP; /* Such I/O has only Top side pins */ + } else if (0 == grid_coordinate.x()) { /* LEFT side IO of FPGA */ + return RIGHT; /* Such I/O has only Right side pins */ + } else { + VTR_LOGF_ERROR(__FILE__, __LINE__, + "I/O Grid is in the center part of FPGA! Currently unsupported!\n"); + exit(1); + } +} + +/************************************************************************ + * Get a list of pin_index for a grid (either OPIN or IPIN) + * For IO_TYPE, only one side will be used, we consider one side of pins + * For others, we consider all the sides + ***********************************************************************/ +std::vector get_grid_side_pins(const t_grid_tile& cur_grid, + const e_pin_type& pin_type, + const e_side& pin_side, + const int& pin_width, + const int& pin_height) { + std::vector pin_list; + /* Make sure a clear start */ + pin_list.clear(); + + for (int ipin = 0; ipin < cur_grid.type->num_pins; ++ipin) { + int class_id = cur_grid.type->pin_class[ipin]; + if ( (1 == cur_grid.type->pinloc[pin_width][pin_height][pin_side][ipin]) + && (pin_type == cur_grid.type->class_inf[class_id].type) ) { + pin_list.push_back(ipin); + } + } + return pin_list; +} + +/************************************************************************ + * Get the number of pins for a grid (either OPIN or IPIN) + * For IO_TYPE, only one side will be used, we consider one side of pins + * For others, we consider all the sides + ***********************************************************************/ +size_t get_grid_num_pins(const t_grid_tile& cur_grid, + const e_pin_type& pin_type, + const e_side& io_side) { + size_t num_pins = 0; + + /* For IO_TYPE sides */ + for (const e_side& side : {TOP, RIGHT, BOTTOM, LEFT}) { + /* skip unwanted sides */ + if ( (true == is_io_type(cur_grid.type)) + && (side != io_side) ) { + continue; + } + /* Get pin list */ + for (int width = 0; width < cur_grid.type->width; ++width) { + for (int height = 0; height < cur_grid.type->height; ++height) { + std::vector pin_list = get_grid_side_pins(cur_grid, pin_type, side, width, height); + num_pins += pin_list.size(); + } + } + } + + return num_pins; +} + +/************************************************************************ + * Get the number of pins for a grid (either OPIN or IPIN) + * For IO_TYPE, only one side will be used, we consider one side of pins + * For others, we consider all the sides + ***********************************************************************/ +size_t get_grid_num_classes(const t_grid_tile& cur_grid, + const e_pin_type& pin_type) { + size_t num_classes = 0; + + for (int iclass = 0; iclass < cur_grid.type->num_class; ++iclass) { + /* Bypass unmatched pin_type */ + if (pin_type != cur_grid.type->class_inf[iclass].type) { + continue; + } + num_classes++; + } + + return num_classes; +} + +/************************************************************************ + * Get the track_id of a routing track w.r.t its coordinator + * In tileable routing architecture, the track_id changes SB by SB. + * Therefore the track_ids are stored in a vector, indexed by the relative coordinator + * based on the starting point of the track + * For routing tracks in INC_DIRECTION + * (xlow, ylow) should be the starting point + * + * (xlow, ylow) (xhigh, yhigh) + * track_id[0] -------------------------------> track_id[xhigh - xlow + yhigh - ylow] + * + * For routing tracks in DEC_DIRECTION + * (xhigh, yhigh) should be the starting point + * + * (xlow, ylow) (xhigh, yhigh) + * track_id[0] <------------------------------- track_id[xhigh - xlow + yhigh - ylow] + * + * + ***********************************************************************/ +short get_rr_node_actual_track_id(const RRGraph& rr_graph, + const RRNodeId& track_rr_node, + const vtr::Point& coord, + const vtr::vector>& tileable_rr_graph_node_track_ids) { + vtr::Point low_coord(rr_graph.node_xlow(track_rr_node), rr_graph.node_ylow(track_rr_node)); + size_t offset = (int)abs((int)coord.x() - (int)low_coord.x() + (int)coord.y() - (int)low_coord.y()); + return tileable_rr_graph_node_track_ids[track_rr_node][offset]; +} + +/************************************************************************ + * Get the ptc of a routing track in the channel where it ends + * For routing tracks in INC_DIRECTION + * the ptc is the last of track_ids + * + * For routing tracks in DEC_DIRECTION + * the ptc is the first of track_ids + ***********************************************************************/ +short get_track_rr_node_end_track_id(const RRGraph& rr_graph, + const RRNodeId& track_rr_node, + const vtr::vector>& tileable_rr_graph_node_track_ids) { + /* Make sure we have CHANX or CHANY */ + VTR_ASSERT( (CHANX == rr_graph.node_type(track_rr_node)) + || (CHANY == rr_graph.node_type(track_rr_node)) ); + + if (INC_DIRECTION == rr_graph.node_direction(track_rr_node)) { + return tileable_rr_graph_node_track_ids[track_rr_node].back(); + } + + VTR_ASSERT(DEC_DIRECTION == rr_graph.node_direction(track_rr_node)); + return tileable_rr_graph_node_track_ids[track_rr_node].front(); +} + +/************************************************************************ + * Find the number of nodes in the same class + * in a routing resource graph + ************************************************************************/ +short find_rr_graph_num_nodes(const RRGraph& rr_graph, + const std::vector& node_types) { + short counter = 0; + + for (const RRNodeId& node : rr_graph.nodes()) { + /* Bypass the nodes not in the class */ + if (node_types.end() == std::find(node_types.begin(), node_types.end(), rr_graph.node_type(node))) { + continue; + } + counter++; + } + + return counter; +} + +/************************************************************************ + * Find the maximum fan-in for a given class of nodes + * in a routing resource graph + ************************************************************************/ +short find_rr_graph_max_fan_in(const RRGraph& rr_graph, + const std::vector& node_types) { + short max_fan_in = 0; + + for (const RRNodeId& node : rr_graph.nodes()) { + /* Bypass the nodes not in the class */ + if (node_types.end() == std::find(node_types.begin(), node_types.end(), rr_graph.node_type(node))) { + continue; + } + max_fan_in = std::max(rr_graph.node_fan_in(node), max_fan_in); + } + + return max_fan_in; +} + +/************************************************************************ + * Find the minimum fan-in for a given class of nodes + * in a routing resource graph + ************************************************************************/ +short find_rr_graph_min_fan_in(const RRGraph& rr_graph, + const std::vector& node_types) { + short min_fan_in = 0; + + for (const RRNodeId& node : rr_graph.nodes()) { + /* Bypass the nodes not in the class */ + if (node_types.end() == std::find(node_types.begin(), node_types.end(), rr_graph.node_type(node))) { + continue; + } + min_fan_in = std::min(rr_graph.node_fan_in(node), min_fan_in); + } + + + return min_fan_in; +} + +/************************************************************************ + * Find the average fan-in for a given class of nodes + * in a routing resource graph + ************************************************************************/ +short find_rr_graph_average_fan_in(const RRGraph& rr_graph, + const std::vector& node_types) { + /* Get the maximum SB mux size */ + size_t sum = 0; + size_t counter = 0; + + for (const RRNodeId& node : rr_graph.nodes()) { + /* Bypass the nodes not in the class */ + if (node_types.end() == std::find(node_types.begin(), node_types.end(), rr_graph.node_type(node))) { + continue; + } + + sum += rr_graph.node_fan_in(node); + counter++; + } + + return sum / counter; +} + +/************************************************************************ + * Print statistics of multiplexers in a routing resource graph + ************************************************************************/ +void print_rr_graph_mux_stats(const RRGraph& rr_graph) { + + /* Print MUX size distribution */ + std::vector sb_node_types; + sb_node_types.push_back(CHANX); + sb_node_types.push_back(CHANY); + + /* Print statistics */ + VTR_LOG("------------------------------------------------\n"); + VTR_LOG("Total No. of Switch Block multiplexer size: %d\n", + find_rr_graph_num_nodes(rr_graph, sb_node_types)); + VTR_LOG("Maximum Switch Block multiplexer size: %d\n", + find_rr_graph_max_fan_in(rr_graph, sb_node_types)); + VTR_LOG("Minimum Switch Block multiplexer size: %d\n", + find_rr_graph_min_fan_in(rr_graph, sb_node_types)); + VTR_LOG("Average Switch Block multiplexer size: %lu\n", + find_rr_graph_average_fan_in(rr_graph, sb_node_types)); + VTR_LOG("------------------------------------------------\n"); + + /* Get the maximum CB mux size */ + std::vector cb_node_types(1, IPIN); + + VTR_LOG("------------------------------------------------\n"); + VTR_LOG("Total No. of Connection Block Multiplexer size: %d\n", + find_rr_graph_num_nodes(rr_graph, cb_node_types)); + VTR_LOG("Maximum Connection Block Multiplexer size: %d\n", + find_rr_graph_max_fan_in(rr_graph, cb_node_types)); + VTR_LOG("Minimum Connection Block Multiplexer size: %d\n", + find_rr_graph_min_fan_in(rr_graph, cb_node_types)); + VTR_LOG("Average Connection Block Multiplexer size: %lu\n", + find_rr_graph_average_fan_in(rr_graph, cb_node_types)); + VTR_LOG("------------------------------------------------\n"); +} + +} /* end namespace openfpga */ diff --git a/vpr/src/tileable_rr_graph/rr_graph_builder_utils.h b/vpr/src/tileable_rr_graph/rr_graph_builder_utils.h new file mode 100644 index 000000000..f67acb038 --- /dev/null +++ b/vpr/src/tileable_rr_graph/rr_graph_builder_utils.h @@ -0,0 +1,69 @@ +#ifndef RR_GRAPH_BUILDER_UTILS_H +#define RR_GRAPH_BUILDER_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "device_grid.h" +#include "rr_graph_obj.h" +#include "vtr_geometry.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +int get_grid_pin_class_index(const t_grid_tile& cur_grid, + const int pin_index); + +e_side determine_io_grid_pin_side(const vtr::Point& device_size, + const vtr::Point& grid_coordinate); + +std::vector get_grid_side_pins(const t_grid_tile& cur_grid, + const e_pin_type& pin_type, + const e_side& pin_side, + const int& pin_width, + const int& pin_height); + +size_t get_grid_num_pins(const t_grid_tile& cur_grid, + const e_pin_type& pin_type, + const e_side& io_side); + +size_t get_grid_num_classes(const t_grid_tile& cur_grid, + const e_pin_type& pin_type); + +short get_rr_node_actual_track_id(const RRGraph& rr_graph, + const RRNodeId& track_rr_node, + const vtr::Point& coord, + const vtr::vector>& tileable_rr_graph_node_track_ids); + +vtr::Point get_track_rr_node_start_coordinator(const RRGraph& rr_graph, + const RRNodeId& track_rr_node); + +vtr::Point get_track_rr_node_end_coordinator(const RRGraph& rr_graph, + const RRNodeId& track_rr_node); + +short get_track_rr_node_end_track_id(const RRGraph& rr_graph, + const RRNodeId& track_rr_node, + const vtr::vector>& tileable_rr_graph_node_track_ids); + +short find_rr_graph_num_nodes(const RRGraph& rr_graph, + const std::vector& node_types); + +short find_rr_graph_max_fan_in(const RRGraph& rr_graph, + const std::vector& node_types); + +short find_rr_graph_min_fan_in(const RRGraph& rr_graph, + const std::vector& node_types); + +short find_rr_graph_average_fan_in(const RRGraph& rr_graph, + const std::vector& node_types); + +void print_rr_graph_mux_stats(const RRGraph& rr_graph); + +} /* end namespace openfpga */ + +#endif + From 6e83154703dc8960cfe2a9ca94725d012c39840a Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 4 Mar 2020 14:14:28 -0700 Subject: [PATCH 251/645] move rr_gsb and rr_chan to tileable rr_graph builder --- vpr/CMakeLists.txt | 15 ++++++++------- .../src/tileable_rr_graph}/rr_chan.cpp | 0 .../src/tileable_rr_graph}/rr_chan.h | 0 .../src/tileable_rr_graph}/rr_gsb.cpp | 0 .../src/tileable_rr_graph}/rr_gsb.h | 0 5 files changed, 8 insertions(+), 7 deletions(-) rename {openfpga/src/annotation => vpr/src/tileable_rr_graph}/rr_chan.cpp (100%) rename {openfpga/src/annotation => vpr/src/tileable_rr_graph}/rr_chan.h (100%) rename {openfpga/src/annotation => vpr/src/tileable_rr_graph}/rr_gsb.cpp (100%) rename {openfpga/src/annotation => vpr/src/tileable_rr_graph}/rr_gsb.h (100%) diff --git a/vpr/CMakeLists.txt b/vpr/CMakeLists.txt index 4d06b1e1a..592fa2edb 100644 --- a/vpr/CMakeLists.txt +++ b/vpr/CMakeLists.txt @@ -56,13 +56,14 @@ set_target_properties(libvpr8 PROPERTIES PREFIX "") #Avoid extra 'lib' prefix #Specify link-time dependancies target_link_libraries(libvpr8 - libvtrutil - libarchfpga - libsdcparse - libblifparse - libtatum - libargparse - libpugixml) + libvtrutil + libopenfpgautil + libarchfpga + libsdcparse + libblifparse + libtatum + libargparse + libpugixml) #link graphics library only when graphics set to on if (VPR_USE_EZGL STREQUAL "on") diff --git a/openfpga/src/annotation/rr_chan.cpp b/vpr/src/tileable_rr_graph/rr_chan.cpp similarity index 100% rename from openfpga/src/annotation/rr_chan.cpp rename to vpr/src/tileable_rr_graph/rr_chan.cpp diff --git a/openfpga/src/annotation/rr_chan.h b/vpr/src/tileable_rr_graph/rr_chan.h similarity index 100% rename from openfpga/src/annotation/rr_chan.h rename to vpr/src/tileable_rr_graph/rr_chan.h diff --git a/openfpga/src/annotation/rr_gsb.cpp b/vpr/src/tileable_rr_graph/rr_gsb.cpp similarity index 100% rename from openfpga/src/annotation/rr_gsb.cpp rename to vpr/src/tileable_rr_graph/rr_gsb.cpp diff --git a/openfpga/src/annotation/rr_gsb.h b/vpr/src/tileable_rr_graph/rr_gsb.h similarity index 100% rename from openfpga/src/annotation/rr_gsb.h rename to vpr/src/tileable_rr_graph/rr_gsb.h From 4455615980b40516860801d782c4dc5fedb60e25 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 4 Mar 2020 14:21:35 -0700 Subject: [PATCH 252/645] adapt tileable routing channel detail builder --- .../tileable_chan_details_builder.cpp | 244 ++++++++++++++++++ .../tileable_chan_details_builder.h | 26 ++ 2 files changed, 270 insertions(+) create mode 100644 vpr/src/tileable_rr_graph/tileable_chan_details_builder.cpp create mode 100644 vpr/src/tileable_rr_graph/tileable_chan_details_builder.h diff --git a/vpr/src/tileable_rr_graph/tileable_chan_details_builder.cpp b/vpr/src/tileable_rr_graph/tileable_chan_details_builder.cpp new file mode 100644 index 000000000..5259c9445 --- /dev/null +++ b/vpr/src/tileable_rr_graph/tileable_chan_details_builder.cpp @@ -0,0 +1,244 @@ +/************************************************************************ + * This file contains a builder for the ChanNodeDetails data structure + * Different from VPR rr_graph builders, this builder aims to create a + * highly regular routing channel. Thus, it is called tileable, + * which brings significant advantage in producing large FPGA fabrics. + ***********************************************************************/ +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" + +#include "tileable_chan_details_builder.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/************************************************************************ + * Generate the number of tracks for each types of routing segments + * w.r.t. the frequency of each of segments and channel width + * Note that if we dertermine the number of tracks per type using + * chan_width * segment_frequency / total_freq may cause + * The total track num may not match the chan_width, + * therefore, we assign tracks one by one until we meet the frequency requirement + * In this way, we can assign the number of tracks with repect to frequency + ***********************************************************************/ +static +std::vector get_num_tracks_per_seg_type(const size_t& chan_width, + const std::vector& segment_inf, + const bool& use_full_seg_groups) { + std::vector result; + std::vector demand; + /* Make sure a clean start */ + result.resize(segment_inf.size()); + demand.resize(segment_inf.size()); + + /* Scale factor so we can divide by any length + * and still use integers */ + /* Get the sum of frequency */ + size_t scale = 1; + size_t freq_sum = 0; + for (size_t iseg = 0; iseg < segment_inf.size(); ++iseg) { + scale *= segment_inf[iseg].length; + freq_sum += segment_inf[iseg].frequency; + } + size_t reduce = scale * freq_sum; + + /* Init assignments to 0 and set the demand values */ + /* Get the fraction of each segment type considering the frequency: + * num_track_per_seg = chan_width * (freq_of_seg / sum_freq) + */ + for (size_t iseg = 0; iseg < segment_inf.size(); ++iseg) { + result[iseg] = 0; + demand[iseg] = scale * chan_width * segment_inf[iseg].frequency; + if (true == use_full_seg_groups) { + demand[iseg] /= segment_inf[iseg].length; + } + } + + /* check if the sum of num_tracks, matches the chan_width */ + /* Keep assigning tracks until we use them up */ + size_t assigned = 0; + size_t size = 0; + size_t imax = 0; + while (assigned < chan_width) { + /* Find current maximum demand */ + double max = 0; + for (size_t iseg = 0; iseg < segment_inf.size(); ++iseg) { + if (demand[iseg] > max) { + imax = iseg; + } + max = std::max(demand[iseg], max); + } + + /* Assign tracks to the type and reduce the types demand */ + size = (use_full_seg_groups ? segment_inf[imax].length : 1); + demand[imax] -= reduce; + result[imax] += size; + assigned += size; + } + + /* Undo last assignment if we were closer to goal without it */ + if ((assigned - chan_width) > (size / 2)) { + result[imax] -= size; + } + + return result; +} + +/************************************************************************ + * Adapt the number of channel width to a tileable routing architecture + ***********************************************************************/ +int adapt_to_tileable_route_chan_width(const int& chan_width, + const std::vector& segment_infs) { + int tileable_chan_width = 0; + + /* Estimate the number of segments per type by the given ChanW*/ + std::vector num_tracks_per_seg_type = get_num_tracks_per_seg_type(chan_width, + segment_infs, + true); /* Force to use the full segment group */ + /* Sum-up the number of tracks */ + for (size_t iseg = 0; iseg < num_tracks_per_seg_type.size(); ++iseg) { + tileable_chan_width += num_tracks_per_seg_type[iseg]; + } + + return tileable_chan_width; +} + +/************************************************************************ + * Build details of routing tracks in a channel + * The function will + * 1. Assign the segments for each routing channel, + * To be specific, for each routing track, we assign a routing segment. + * The assignment is subject to users' specifications, such as + * a. length of each type of segment + * b. frequency of each type of segment. + * c. routing channel width + * + * 2. The starting point of each segment in the channel will be assigned + * For each segment group with same directionality (tracks have the same length), + * every L track will be a starting point (where L denotes the length of segments) + * In this case, if the number of tracks is not a multiple of L, + * indeed we may have some | Yes | No | + * +---------------------------------------+--------------+ + * | 1 | <--------MUX | Yes | No | + * +---------------------------------------+--------------+ + * | 2 | --------> | No | No | + * +---------------------------------------+--------------+ + * | 3 | <-------- | No | No | + * +---------------------------------------+--------------+ + * | 4 | --------> | No | No | + * +---------------------------------------+--------------+ + * | 5 | <-------- | No | No | + * +---------------------------------------+--------------+ + * | 7 | -------->MUX | No | Yes | + * +---------------------------------------+--------------+ + * | 8 | MUX<-------- | No | Yes | + * +---------------------------------------+--------------+ + * | 9 | MUX--------> | Yes | No | + * +---------------------------------------+--------------+ + * | 10 | <--------MUX | Yes | No | + * +---------------------------------------+--------------+ + * | 11 | -------->MUX | No | Yes | + * +------------------------------------------------------+ + * | 12 | <-------- | No | No | + * +------------------------------------------------------+ + * + * 3. SPECIAL for fringes: TOP|RIGHT|BOTTOM|RIGHT + * if device_side is NUM_SIDES, we assume this channel does not locate on borders + * All segments will start and ends with no exception + * + * 4. IMPORTANT: we should be aware that channel width maybe different + * in X-direction and Y-direction channels!!! + * So we will load segment details for different channels + ***********************************************************************/ +ChanNodeDetails build_unidir_chan_node_details(const size_t& chan_width, const size_t& max_seg_length, + const enum e_side& device_side, + const std::vector& segment_inf) { + ChanNodeDetails chan_node_details; + size_t actual_chan_width = chan_width; + /* Correct the chan_width: it should be an even number */ + if (0 != actual_chan_width % 2) { + actual_chan_width++; /* increment it to be even */ + } + VTR_ASSERT(0 == actual_chan_width % 2); + + /* Reserve channel width */ + chan_node_details.reserve(chan_width); + /* Return if zero width is forced */ + if (0 == actual_chan_width) { + return chan_node_details; + } + + /* Find the number of segments required by each group */ + std::vector num_tracks = get_num_tracks_per_seg_type(actual_chan_width/2, segment_inf, false); + + /* Add node to ChanNodeDetails */ + size_t cur_track = 0; + for (size_t iseg = 0; iseg < segment_inf.size(); ++iseg) { + /* segment length will be set to maxium segment length if this is a longwire */ + size_t seg_len = segment_inf[iseg].length; + if (true == segment_inf[iseg].longline) { + seg_len = max_seg_length; + } + for (size_t itrack = 0; itrack < num_tracks[iseg]; ++itrack) { + bool seg_start = false; + bool seg_end = false; + /* Every first track of a group of Length-N wires, we set a starting point */ + if (0 == itrack % seg_len) { + seg_start = true; + } + /* Every last track of a group of Length-N wires or this is the last track in this group, we set an ending point */ + if ( (seg_len - 1 == itrack % seg_len) + || (itrack == num_tracks[iseg] - 1) ) { + seg_end = true; + } + /* Since this is a unidirectional routing architecture, + * Add a pair of tracks, 1 INC_DIRECTION track and 1 DEC_DIRECTION track + */ + chan_node_details.add_track(cur_track, INC_DIRECTION, iseg, seg_len, seg_start, seg_end); + cur_track++; + chan_node_details.add_track(cur_track, DEC_DIRECTION, iseg, seg_len, seg_start, seg_end); + cur_track++; + } + } + /* Check if all the tracks have been satisified */ + VTR_ASSERT(cur_track == actual_chan_width); + + /* If this is on the border of a device, segments should start */ + switch (device_side) { + case TOP: + case RIGHT: + /* INC_DIRECTION should all end */ + chan_node_details.set_tracks_end(INC_DIRECTION); + /* DEC_DIRECTION should all start */ + chan_node_details.set_tracks_start(DEC_DIRECTION); + break; + case BOTTOM: + case LEFT: + /* INC_DIRECTION should all start */ + chan_node_details.set_tracks_start(INC_DIRECTION); + /* DEC_DIRECTION should all end */ + chan_node_details.set_tracks_end(DEC_DIRECTION); + break; + case NUM_SIDES: + break; + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid device_side!\n"); + exit(1); + } + + return chan_node_details; +} + +} /* end namespace openfpga */ diff --git a/vpr/src/tileable_rr_graph/tileable_chan_details_builder.h b/vpr/src/tileable_rr_graph/tileable_chan_details_builder.h new file mode 100644 index 000000000..cc111541f --- /dev/null +++ b/vpr/src/tileable_rr_graph/tileable_chan_details_builder.h @@ -0,0 +1,26 @@ +#ifndef TILEABLE_CHAN_DETAILS_BUILDER_H +#define TILEABLE_CHAN_DETAILS_BUILDER_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include "physical_types.h" +#include "chan_node_details.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +int adapt_to_tileable_route_chan_width(const int& chan_width, const std::vector& segment_inf); + +ChanNodeDetails build_unidir_chan_node_details(const size_t& chan_width, const size_t& max_seg_length, + const e_side& device_side, + const std::vector& segment_inf); + +} /* end namespace openfpga */ + +#endif From 646ee9093780276fc46819d697dc3e448476d518 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 4 Mar 2020 18:19:53 -0700 Subject: [PATCH 253/645] bring tileable gsb builder for rr_graph online --- vpr/src/device/clb2clb_directs.h | 16 + vpr/src/route/rr_graph.cpp | 12 +- vpr/src/tileable_rr_graph/rr_chan.cpp | 6 +- vpr/src/tileable_rr_graph/rr_chan.h | 2 +- vpr/src/tileable_rr_graph/rr_gsb.cpp | 2 +- vpr/src/tileable_rr_graph/rr_gsb.h | 4 +- .../tileable_rr_graph_gsb.cpp | 1296 +++++++++++++++++ .../tileable_rr_graph/tileable_rr_graph_gsb.h | 80 + 8 files changed, 1401 insertions(+), 17 deletions(-) create mode 100644 vpr/src/device/clb2clb_directs.h create mode 100755 vpr/src/tileable_rr_graph/tileable_rr_graph_gsb.cpp create mode 100755 vpr/src/tileable_rr_graph/tileable_rr_graph_gsb.h diff --git a/vpr/src/device/clb2clb_directs.h b/vpr/src/device/clb2clb_directs.h new file mode 100644 index 000000000..7cc54bb88 --- /dev/null +++ b/vpr/src/device/clb2clb_directs.h @@ -0,0 +1,16 @@ +#ifndef CLB2CLB_DIRECTS_H +#define CLB2CLB_DIRECTS_H + +#include "physical_types.h" + +struct t_clb_to_clb_directs { + t_physical_tile_type_ptr from_clb_type; + int from_clb_pin_start_index; + int from_clb_pin_end_index; + t_physical_tile_type_ptr to_clb_type; + int to_clb_pin_start_index; + int to_clb_pin_end_index; + int switch_index; //The switch type used by this direct connection +}; + +#endif diff --git a/vpr/src/route/rr_graph.cpp b/vpr/src/route/rr_graph.cpp index e17a0c912..ae6e2593e 100644 --- a/vpr/src/route/rr_graph.cpp +++ b/vpr/src/route/rr_graph.cpp @@ -41,6 +41,8 @@ #include "rr_graph_obj_util.h" #include "check_rr_graph_obj.h" +#include "clb2clb_directs.h" + //#define VERBOSE struct t_mux { @@ -55,16 +57,6 @@ struct t_mux_size_distribution { t_mux_size_distribution* next; }; -struct t_clb_to_clb_directs { - t_physical_tile_type_ptr from_clb_type; - int from_clb_pin_start_index; - int from_clb_pin_end_index; - t_physical_tile_type_ptr to_clb_type; - int to_clb_pin_start_index; - int to_clb_pin_end_index; - int switch_index; //The switch type used by this direct connection -}; - struct t_pin_loc { int pin_index; int width_offset; diff --git a/vpr/src/tileable_rr_graph/rr_chan.cpp b/vpr/src/tileable_rr_graph/rr_chan.cpp index b85d1b917..9d23372e5 100644 --- a/vpr/src/tileable_rr_graph/rr_chan.cpp +++ b/vpr/src/tileable_rr_graph/rr_chan.cpp @@ -127,8 +127,8 @@ std::vector RRChan::get_segment_ids() const { } /* Get a list of nodes whose segment_id is specified */ -std::vector RRChan::get_node_ids_by_segment_ids(const RRSegmentId& seg_id) const { - std::vector node_list; +std::vector RRChan::get_node_ids_by_segment_ids(const RRSegmentId& seg_id) const { + std::vector node_list; /* make sure a clean start */ node_list.clear(); @@ -137,7 +137,7 @@ std::vector RRChan::get_node_ids_by_segment_ids(const RRSegmentId& seg for (size_t inode = 0; inode < get_chan_width(); ++inode) { /* Try to find the node_segment id in the list */ if ( seg_id == node_segments_[inode] ) { - node_list.push_back(nodes_[inode]); + node_list.push_back(inode); } } diff --git a/vpr/src/tileable_rr_graph/rr_chan.h b/vpr/src/tileable_rr_graph/rr_chan.h index ab8bb6617..3277bb173 100644 --- a/vpr/src/tileable_rr_graph/rr_chan.h +++ b/vpr/src/tileable_rr_graph/rr_chan.h @@ -54,7 +54,7 @@ class RRChan { RRSegmentId get_node_segment(const size_t& track_num) const; bool is_mirror(const RRGraph& rr_graph, const RRChan& cand) const; /* evaluate if two RR_chan is mirror to each other */ std::vector get_segment_ids() const; /* Get a list of segments used in this routing channel */ - std::vector get_node_ids_by_segment_ids(const RRSegmentId& seg_id) const; /* Get a list of segments used in this routing channel */ + std::vector get_node_ids_by_segment_ids(const RRSegmentId& seg_id) const; /* Get a list of segments used in this routing channel */ public: /* Mutators */ /* copy */ void set(const RRChan&); diff --git a/vpr/src/tileable_rr_graph/rr_gsb.cpp b/vpr/src/tileable_rr_graph/rr_gsb.cpp index cd57d8e6b..5b558fffd 100644 --- a/vpr/src/tileable_rr_graph/rr_gsb.cpp +++ b/vpr/src/tileable_rr_graph/rr_gsb.cpp @@ -117,7 +117,7 @@ std::vector RRGSB::get_chan_segment_ids(const e_side& side) const { } /* Get a list of rr_nodes whose sed_id is specified */ -std::vector RRGSB::get_chan_node_ids_by_segment_ids(const e_side& side, +std::vector RRGSB::get_chan_node_ids_by_segment_ids(const e_side& side, const RRSegmentId& seg_id) const { return chan_node_[size_t(side)].get_node_ids_by_segment_ids(seg_id); } diff --git a/vpr/src/tileable_rr_graph/rr_gsb.h b/vpr/src/tileable_rr_graph/rr_gsb.h index 4541c88dd..f582d7ce3 100644 --- a/vpr/src/tileable_rr_graph/rr_gsb.h +++ b/vpr/src/tileable_rr_graph/rr_gsb.h @@ -77,8 +77,8 @@ class RRGSB { std::vector get_chan_segment_ids(const e_side& side) const; /* Get a list of segments used in this routing channel */ - std::vector get_chan_node_ids_by_segment_ids(const e_side& side, - const RRSegmentId& seg_id) const; + std::vector get_chan_node_ids_by_segment_ids(const e_side& side, + const RRSegmentId& seg_id) const; /* get a rr_node at a given side and track_id */ RRNodeId get_chan_node(const e_side& side, const size_t& track_id) const; diff --git a/vpr/src/tileable_rr_graph/tileable_rr_graph_gsb.cpp b/vpr/src/tileable_rr_graph/tileable_rr_graph_gsb.cpp new file mode 100755 index 000000000..332a44e94 --- /dev/null +++ b/vpr/src/tileable_rr_graph/tileable_rr_graph_gsb.cpp @@ -0,0 +1,1296 @@ +/************************************************************************ + * This file contains a builder for track-to-track connections inside a + * tileable General Switch Block (GSB). + ***********************************************************************/ +#include +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" + +/* Headers from openfpgautil library */ +#include "openfpga_side_manager.h" + +#include "vpr_utils.h" +#include "rr_graph_obj_util.h" +#include "openfpga_rr_graph_utils.h" +#include "rr_graph_builder_utils.h" +#include "tileable_chan_details_builder.h" +#include "tileable_rr_graph_gsb.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/************************************************************************ + * Internal data structures + ***********************************************************************/ +typedef std::vector> t_track_group; + +/************************************************************************ + * A enumeration to list the status of a track inside a GSB + * 1. start; 2. end; 3. passing + * This is used to group tracks which ease the building of + * track-to-track mapping matrix + ***********************************************************************/ +enum e_track_status { + TRACK_START, + TRACK_END, + TRACK_PASS, + NUM_TRACK_STATUS /* just a place holder to get the number of status */ +}; + +/************************************************************************ + * Check if a track starts from this GSB or not + * (xlow, ylow) should be same as the GSB side coordinate + * + * Check if a track ends at this GSB or not + * (xhigh, yhigh) should be same as the GSB side coordinate + ***********************************************************************/ +static +enum e_track_status determine_track_status_of_gsb(const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const enum e_side& gsb_side, + const size_t& track_id) { + enum e_track_status track_status = TRACK_PASS; + /* Get the rr_node */ + RRNodeId track_node = rr_gsb.get_chan_node(gsb_side, track_id); + /* Get the coordinates */ + vtr::Point side_coordinate = rr_gsb.get_side_block_coordinate(gsb_side); + + /* Get the coordinate of where the track starts */ + vtr::Point track_start = get_track_rr_node_start_coordinate(rr_graph, track_node); + + /* INC_DIRECTION start_track: (xlow, ylow) should be same as the GSB side coordinate */ + /* DEC_DIRECTION start_track: (xhigh, yhigh) should be same as the GSB side coordinate */ + if ( (track_start.x() == side_coordinate.x()) + && (track_start.y() == side_coordinate.y()) + && (OUT_PORT == rr_gsb.get_chan_node_direction(gsb_side, track_id)) ) { + /* Double check: start track should be an OUTPUT PORT of the GSB */ + track_status = TRACK_START; + } + + /* Get the coordinate of where the track ends */ + vtr::Point track_end = get_track_rr_node_end_coordinate(rr_graph, track_node); + + /* INC_DIRECTION end_track: (xhigh, yhigh) should be same as the GSB side coordinate */ + /* DEC_DIRECTION end_track: (xlow, ylow) should be same as the GSB side coordinate */ + if ( (track_end.x() == side_coordinate.x()) + && (track_end.y() == side_coordinate.y()) + && (IN_PORT == rr_gsb.get_chan_node_direction(gsb_side, track_id)) ) { + /* Double check: end track should be an INPUT PORT of the GSB */ + track_status = TRACK_END; + } + + return track_status; +} + +/************************************************************************ + * Check if the GSB is in the Connection Block (CB) population list of the segment + * SB population of a L4 wire: 1 0 0 1 + * + * +----+ +----+ +----+ +----+ + * | CB |--->| CB |--->| CB |--->| CB | + * +----+ +----+ +----+ +----+ + * Engage CB connection Yes No No Yes + * + * We will find the offset between gsb_side_coordinate and (xlow,ylow) of the track + * Use the offset to check if the tracks should engage in this GSB connection + ***********************************************************************/ +static +bool is_gsb_in_track_cb_population(const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const e_side& gsb_side, + const int& track_id, + const std::vector& segment_inf) { + /* Get the rr_node */ + RRNodeId track_node = rr_gsb.get_chan_node(gsb_side, track_id); + /* Get the coordinates */ + vtr::Point side_coordinate = rr_gsb.get_side_block_coordinate(gsb_side); + + vtr::Point track_start = get_track_rr_node_start_coordinate(rr_graph, track_node); + + /* Get the offset */ + size_t offset = std::abs((int)side_coordinate.x() - (int)track_start.x()) + + std::abs((int)side_coordinate.y() - (int)track_start.y()); + + /* Get segment id */ + RRSegmentId seg_id = rr_gsb.get_chan_node_segment(gsb_side, track_id); + /* validate offset */ + VTR_ASSERT(offset < segment_inf[size_t(seg_id)].cb.size()); + + /* Get the SB population */ + bool in_cb_population = false; + if (true == segment_inf[size_t(seg_id)].cb[offset]) { + in_cb_population = true; + } + + return in_cb_population; +} + +/************************************************************************ + * Check if the GSB is in the Switch Block (SB) population list of the segment + * SB population of a L3 wire: 1 0 0 1 + * + * +----+ +----+ +----+ +----+ + * | SB |--->| SB |--->| SB |--->| SB | + * +----+ +----+ +----+ +----+ + * Engage SB connection Yes No No Yes + * + * We will find the offset between gsb_side_coordinate and (xlow,ylow) of the track + * Use the offset to check if the tracks should engage in this GSB connection + ***********************************************************************/ +static +bool is_gsb_in_track_sb_population(const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const e_side& gsb_side, + const int& track_id, + const std::vector& segment_inf) { + /* Get the rr_node */ + const RRNodeId& track_node = rr_gsb.get_chan_node(gsb_side, track_id); + /* Get the coordinates */ + vtr::Point side_coordinate = rr_gsb.get_side_block_coordinate(gsb_side); + + vtr::Point track_start = get_track_rr_node_start_coordinate(rr_graph, track_node); + + /* Get the offset */ + size_t offset = std::abs((int)side_coordinate.x() - (int)track_start.x()) + + std::abs((int)side_coordinate.y() - (int)track_start.y()); + + /* Get segment id */ + RRSegmentId seg_id = rr_gsb.get_chan_node_segment(gsb_side, track_id); + /* validate offset */ + VTR_ASSERT(offset < segment_inf[size_t(seg_id)].sb.size()); + + /* Get the SB population */ + bool in_sb_population = false; + if (true == segment_inf[size_t(seg_id)].sb[offset]) { + in_sb_population = true; + } + + return in_sb_population; +} + +/************************************************************************ + * Create a list of track_id based on the to_track and num_to_tracks + * We consider the following list [to_track, to_track + Fs/3 - 1] + * if the [to_track + Fs/3 - 1] exceeds the num_to_tracks, we start over from 0! +***********************************************************************/ +static +std::vector get_to_track_list(const int& Fs, const int& to_track, const int& num_to_tracks) { + std::vector to_tracks; + + for (int i = 0; i < Fs; i = i + 3) { + /* TODO: currently, for Fs > 3, I always search the next from_track until Fs is satisfied + * The optimal track selection should be done in a more scientific way!!! + */ + int to_track_i = to_track + i; + /* make sure the track id is still in range */ + if ( to_track_i > num_to_tracks - 1) { + to_track_i = to_track_i % num_to_tracks; + } + /* Ensure we are in the range */ + VTR_ASSERT(to_track_i < num_to_tracks); + /* from track must be connected */ + to_tracks.push_back(to_track_i); + } + return to_tracks; +} + +/************************************************************************ + * This function aims to return the track indices that drive the from_track + * in a Switch Block + * The track_ids to return will depend on different topologies of SB + * SUBSET, UNIVERSAL, and WILTON. + ***********************************************************************/ +static +std::vector get_switch_block_to_track_id(const e_switch_block_type& switch_block_type, + const int& Fs, + const e_side& from_side, + const int& from_track, + const e_side& to_side, + const int& num_to_tracks) { + + /* This routine returns the track number to which the from_track should + * connect. It supports any Fs % 3 == 0, switch blocks. + */ + std::vector to_tracks; + + /* TODO: currently, for Fs > 3, I always search the next from_track until Fs is satisfied + * The optimal track selection should be done in a more scientific way!!! + */ + VTR_ASSERT(0 == Fs % 3); + + /* Adapt from_track to fit in the range of num_to_tracks */ + size_t actual_from_track = from_track % num_to_tracks; + + switch (switch_block_type) { + case SUBSET: /* NB: Global routing uses SUBSET too */ + to_tracks = get_to_track_list(Fs, actual_from_track, num_to_tracks); + /* Finish, we return */ + return to_tracks; + case UNIVERSAL: + if ( (from_side == LEFT) + || (from_side == RIGHT) ) { + /* For the prev_side, to_track is from_track + * For the next_side, to_track is num_to_tracks - 1 - from_track + * For the opposite_side, to_track is always from_track + */ + SideManager side_manager(from_side); + if ( (to_side == side_manager.get_opposite()) + || (to_side == side_manager.get_rotate_counterclockwise()) ) { + to_tracks = get_to_track_list(Fs, actual_from_track, num_to_tracks); + } else if (to_side == side_manager.get_rotate_clockwise()) { + to_tracks = get_to_track_list(Fs, num_to_tracks - 1 - actual_from_track, num_to_tracks); + } + } + + if ( (from_side == TOP) + || (from_side == BOTTOM) ) { + /* For the next_side, to_track is from_track + * For the prev_side, to_track is num_to_tracks - 1 - from_track + * For the opposite_side, to_track is always from_track + */ + SideManager side_manager(from_side); + if ( (to_side == side_manager.get_opposite()) + || (to_side == side_manager.get_rotate_clockwise()) ) { + to_tracks = get_to_track_list(Fs, actual_from_track, num_to_tracks); + } else if (to_side == side_manager.get_rotate_counterclockwise()) { + to_tracks = get_to_track_list(Fs, num_to_tracks - 1 - actual_from_track, num_to_tracks); + } + } + /* Finish, we return */ + return to_tracks; + /* End switch_block_type == UNIVERSAL case. */ + case WILTON: + /* See S. Wilton Phd thesis, U of T, 1996 p. 103 for details on following. */ + if (from_side == LEFT) { + if (to_side == RIGHT) { /* CHANX to CHANX */ + to_tracks = get_to_track_list(Fs, actual_from_track, num_to_tracks); + } else if (to_side == TOP) { /* from CHANX to CHANY */ + to_tracks = get_to_track_list(Fs, (num_to_tracks - actual_from_track ) % num_to_tracks, num_to_tracks); + } else if (to_side == BOTTOM) { + to_tracks = get_to_track_list(Fs, (num_to_tracks + actual_from_track - 1) % num_to_tracks, num_to_tracks); + } + } else if (from_side == RIGHT) { + if (to_side == LEFT) { /* CHANX to CHANX */ + to_tracks = get_to_track_list(Fs, actual_from_track, num_to_tracks); + } else if (to_side == TOP) { /* from CHANX to CHANY */ + to_tracks = get_to_track_list(Fs, (num_to_tracks + actual_from_track - 1) % num_to_tracks, num_to_tracks); + } else if (to_side == BOTTOM) { + to_tracks = get_to_track_list(Fs, (2 * num_to_tracks - 2 - actual_from_track) % num_to_tracks, num_to_tracks); + } + } else if (from_side == BOTTOM) { + if (to_side == TOP) { /* CHANY to CHANY */ + to_tracks = get_to_track_list(Fs, actual_from_track, num_to_tracks); + } else if (to_side == LEFT) { /* from CHANY to CHANX */ + to_tracks = get_to_track_list(Fs, (actual_from_track + 1) % num_to_tracks, num_to_tracks); + } else if (to_side == RIGHT) { + to_tracks = get_to_track_list(Fs, (2 * num_to_tracks - 2 - actual_from_track) % num_to_tracks, num_to_tracks); + } + } else if (from_side == TOP) { + if (to_side == BOTTOM) { /* CHANY to CHANY */ + to_tracks = get_to_track_list(Fs, from_track, num_to_tracks); + } else if (to_side == LEFT) { /* from CHANY to CHANX */ + to_tracks = get_to_track_list(Fs, (num_to_tracks - actual_from_track) % num_to_tracks, num_to_tracks); + } else if (to_side == RIGHT) { + to_tracks = get_to_track_list(Fs, (actual_from_track + 1) % num_to_tracks, num_to_tracks); + } + } + /* Finish, we return */ + return to_tracks; + /* End switch_block_type == WILTON case. */ + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid switch block pattern !\n"); + exit(1); + } + + return to_tracks; +} + + +/************************************************************************ + * Build the track_to_track_map[from_side][0..chan_width-1][to_side][track_indices] + * For a group of from_track nodes and to_track nodes + * For each side of from_tracks, we call a routine to get the list of to_tracks + * Then, we fill the track2track_map + ***********************************************************************/ +static +void build_gsb_one_group_track_to_track_map(const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const e_switch_block_type& sb_type, + const int& Fs, + const bool& wire_opposite_side, + const t_track_group& from_tracks, /* [0..gsb_side][track_indices] */ + const t_track_group& to_tracks, /* [0..gsb_side][track_indices] */ + t_track2track_map& track2track_map) { + for (size_t side = 0; side < from_tracks.size(); ++side) { + SideManager side_manager(side); + e_side from_side = side_manager.get_side(); + /* Find the other sides where the start tracks will locate */ + std::vector to_track_sides; + /* 0. opposite side */ + to_track_sides.push_back(side_manager.get_opposite()); + /* 1. prev side */ + /* Previous side definition: TOP => LEFT; RIGHT=>TOP; BOTTOM=>RIGHT; LEFT=>BOTTOM */ + to_track_sides.push_back(side_manager.get_rotate_counterclockwise()); + /* 2. next side */ + /* Next side definition: TOP => RIGHT; RIGHT=>BOTTOM; BOTTOM=>LEFT; LEFT=>TOP */ + to_track_sides.push_back(side_manager.get_rotate_clockwise()); + + for (size_t inode = 0; inode < from_tracks[side].size(); ++inode) { + for (size_t to_side_id = 0; to_side_id < to_track_sides.size(); ++to_side_id) { + enum e_side to_side = to_track_sides[to_side_id]; + SideManager to_side_manager(to_side); + size_t to_side_index = to_side_manager.to_size_t(); + /* Bypass those to_sides have no nodes */ + if (0 == to_tracks[to_side_index].size()) { + continue; + } + /* Bypass those from_side is same as to_side */ + if (from_side == to_side) { + continue; + } + /* Bypass those from_side is opposite to to_side if required */ + if ( (true == wire_opposite_side) + && (to_side_manager.get_opposite() == from_side) ) { + continue; + } + /* Get other track_ids depending on the switch block pattern */ + /* Find the track ids that will start at the other sides */ + std::vector to_track_ids = get_switch_block_to_track_id(sb_type, Fs, from_side, inode, + to_side, + to_tracks[to_side_index].size()); + /* Update the track2track_map: */ + for (size_t to_track_id = 0; to_track_id < to_track_ids.size(); ++to_track_id) { + size_t from_side_index = side_manager.to_size_t(); + size_t from_track_index = from_tracks[side][inode]; + /* Check the id is still in the range !*/ + VTR_ASSERT( to_track_ids[to_track_id] < to_tracks[to_side_index].size() ); + size_t to_track_index = to_tracks[to_side_index][to_track_ids[to_track_id]]; + //printf("from_track(size=%lu): %lu , to_track_ids[%lu]:%lu, to_track_index: %lu in a group of %lu tracks\n", + // from_tracks[side].size(), inode, to_track_id, to_track_ids[to_track_id], + // to_track_index, to_tracks[to_side_index].size()); + const RRNodeId& to_track_node = rr_gsb.get_chan_node(to_side, to_track_index); + VTR_ASSERT(true == rr_graph.valid_node_id(to_track_node)); + + /* from_track should be IN_PORT */ + VTR_ASSERT(IN_PORT == rr_gsb.get_chan_node_direction(from_side, from_track_index)); + /* to_track should be OUT_PORT */ + VTR_ASSERT(OUT_PORT == rr_gsb.get_chan_node_direction(to_side, to_track_index)); + + /* Check if the to_track_node is already in the list ! */ + std::vector::iterator it = std::find(track2track_map[from_side_index][from_track_index].begin(), + track2track_map[from_side_index][from_track_index].end(), + to_track_node); + if (it != track2track_map[from_side_index][from_track_index].end()) { + continue; /* the node_id is already in the list, go for the next */ + } + /* Clear, we should add to the list */ + track2track_map[from_side_index][from_track_index].push_back(to_track_node); + } + } + } + } +} + +/************************************************************************ + * Build the track_to_track_map[from_side][0..chan_width-1][to_side][track_indices] + * based on the existing routing resources in the General Switch Block (GSB) + * The track_indices is the indices of tracks that the node at from_side and [0..chan_width-1] will drive + * IMPORTANT: the track_indices are the indicies in the GSB context, but not the rr_graph!!! + * We separate the connections into two groups: + * Group 1: the routing tracks start from this GSB + * We will apply switch block patterns (SUBSET, UNIVERSAL, WILTON) + * Group 2: the routing tracks do not start from this GSB (bypassing wires) + * We will apply switch block patterns (SUBSET, UNIVERSAL, WILTON) + * but we will check the Switch Block (SB) population of these + * routing segments, and determine which requires connections + * + * CHANY CHANY CHANY CHANY + * [0] [1] [2] [3] + * start yes no yes no + * end +-------------------------+ start Group 1 Group 2 + * no CHANX[0] | TOP | CHANX[0] yes TOP/BOTTOM TOP/BOTTOM + * | | CHANY[0,2] CHANY[1,3] + * yes CHANX[1] | | CHANX[1] no + * | LEFT RIGHT | + * no CHANX[2] | | CHANX[2] yes + * | | + * yes CHANX[3] | BOTTOM | CHANX[3] no + * +-------------------------+ + * CHANY CHANY CHANY CHANY + * [0] [1] [2] [3] + * start yes no yes no + * + * The mapping is done in the following steps: (For each side of the GSB) + * 1. Build a list of tracks that will start from this side + * if a track starts, its xlow/ylow is the same as the x,y of this gsb + * 2. Build a list of tracks on the other sides belonging to Group 1. + * Take the example of RIGHT side, we will collect + * a. tracks that will end at the LEFT side + * b. tracks that will start at the TOP side + * c. tracks that will start at the BOTTOM side + * 3. Apply switch block patterns to Group 1 (SUBSET, UNIVERSAL, WILTON) + * 4. Build a list of tracks on the other sides belonging to Group 1. + * Take the example of RIGHT side, we will collect + * a. tracks that will bypass at the TOP side + * b. tracks that will bypass at the BOTTOM side + * 5. Apply switch block patterns to Group 2 (SUBSET, UNIVERSAL, WILTON) + ***********************************************************************/ +t_track2track_map build_gsb_track_to_track_map(const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const e_switch_block_type& sb_type, + const int& Fs, + const e_switch_block_type& sb_subtype, + const int& subFs, + const bool& wire_opposite_side, + const std::vector& segment_inf) { + t_track2track_map track2track_map; /* [0..gsb_side][0..chan_width][track_indices] */ + + /* Categorize tracks into 3 groups: + * (1) tracks will start here + * (2) tracks will end here + * (2) tracks will just pass through the SB */ + t_track_group start_tracks; /* [0..gsb_side][track_indices] */ + t_track_group end_tracks; /* [0..gsb_side][track_indices] */ + t_track_group pass_tracks; /* [0..gsb_side][track_indices] */ + + /* resize to the number of sides */ + start_tracks.resize(rr_gsb.get_num_sides()); + end_tracks.resize(rr_gsb.get_num_sides()); + pass_tracks.resize(rr_gsb.get_num_sides()); + + /* Walk through each side */ + for (size_t side = 0; side < rr_gsb.get_num_sides(); ++side) { + SideManager side_manager(side); + e_side gsb_side = side_manager.get_side(); + /* Build a list of tracks that will start from this side */ + for (size_t inode = 0; inode < rr_gsb.get_chan_width(gsb_side); ++inode) { + /* We need to check Switch block population of this track + * The track node will not be considered if there supposed to be no SB at this position + */ + if (false == is_gsb_in_track_sb_population(rr_graph, rr_gsb, gsb_side, inode, segment_inf)) { + continue; /* skip this node and go to the next */ + } + /* check if this track will start from here */ + enum e_track_status track_status = determine_track_status_of_gsb(rr_graph, rr_gsb, gsb_side, inode); + + switch (track_status) { + case TRACK_START: + /* update starting track list */ + start_tracks[side].push_back(inode); + break; + case TRACK_END: + /* Update end track list */ + end_tracks[side].push_back(inode); + break; + case TRACK_PASS: + /* Update passing track list */ + /* Note that the pass_track should be IN_PORT only !!! */ + if (IN_PORT == rr_gsb.get_chan_node_direction(gsb_side, inode)) { + pass_tracks[side].push_back(inode); + } + break; + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid track status!\n"); + exit(1); + } + } + } + + /* Allocate track2track map */ + track2track_map.resize(rr_gsb.get_num_sides()); + for (size_t side = 0; side < rr_gsb.get_num_sides(); ++side) { + SideManager side_manager(side); + enum e_side gsb_side = side_manager.get_side(); + /* allocate track2track_map[gsb_side] */ + track2track_map[side].resize(rr_gsb.get_chan_width(gsb_side)); + for (size_t inode = 0; inode < rr_gsb.get_chan_width(gsb_side); ++inode) { + /* allocate track2track_map[gsb_side][inode] */ + track2track_map[side][inode].clear(); + } + } + + /* For Group 1: we build connections between end_tracks and start_tracks*/ + build_gsb_one_group_track_to_track_map(rr_graph, rr_gsb, + sb_type, Fs, + true, /* End tracks should always to wired to start tracks */ + end_tracks, start_tracks, + track2track_map); + + /* For Group 2: we build connections between end_tracks and start_tracks*/ + /* Currently, I use the same Switch Block pattern for the passing tracks and end tracks, + * TODO: This can be improved with different patterns! + */ + build_gsb_one_group_track_to_track_map(rr_graph, rr_gsb, + sb_subtype, subFs, + wire_opposite_side, /* Pass tracks may not be wired to start tracks */ + pass_tracks, start_tracks, + track2track_map); + + return track2track_map; +} + +/* Build a RRChan Object with the given channel type and coorindators */ +static +RRChan build_one_tileable_rr_chan(const vtr::Point& chan_coordinate, + const t_rr_type& chan_type, + const RRGraph& rr_graph, + const ChanNodeDetails& chan_details) { + std::vector chan_rr_nodes; + + /* Create a rr_chan object and check if it is unique in the graph */ + RRChan rr_chan; + + /* Fill the information */ + rr_chan.set_type(chan_type); + + /* Collect rr_nodes for this channel */ + chan_rr_nodes = find_rr_graph_chan_nodes(rr_graph, + chan_coordinate.x(), chan_coordinate.y(), + chan_type); + + /* Reserve */ + /* rr_chan.reserve_node(size_t(chan_width)); */ + + /* Fill the rr_chan */ + for (size_t itrack = 0; itrack < chan_rr_nodes.size(); ++itrack) { + size_t iseg = chan_details.get_track_segment_id(itrack); + rr_chan.add_node(rr_graph, chan_rr_nodes[itrack], RRSegmentId(iseg)); + } + + return rr_chan; +} + +/*********************************************************************** + * Build a General Switch Block (GSB) + * which includes: + * [I] A Switch Box subckt consists of following ports: + * 1. Channel Y [x][y] inputs + * 2. Channel X [x+1][y] inputs + * 3. Channel Y [x][y-1] outputs + * 4. Channel X [x][y] outputs + * 5. Grid[x][y+1] Right side outputs pins + * 6. Grid[x+1][y+1] Left side output pins + * 7. Grid[x+1][y+1] Bottom side output pins + * 8. Grid[x+1][y] Top side output pins + * 9. Grid[x+1][y] Left side output pins + * 10. Grid[x][y] Right side output pins + * 11. Grid[x][y] Top side output pins + * 12. Grid[x][y+1] Bottom side output pins + * + * -------------- -------------- + * | | CBY | | + * | Grid | ChanY | Grid | + * | [x][y+1] | [x][y+1] | [x+1][y+1] | + * | | | | + * -------------- -------------- + * ---------- + * ChanX & CBX | Switch | ChanX + * [x][y] | Box | [x+1][y] + * | [x][y] | + * ---------- + * -------------- -------------- + * | | | | + * | Grid | ChanY | Grid | + * | [x][y] | [x][y] | [x+1][y] | + * | | | | + * -------------- -------------- + * For channels chanY with INC_DIRECTION on the top side, they should be marked as outputs + * For channels chanY with DEC_DIRECTION on the top side, they should be marked as inputs + * For channels chanY with INC_DIRECTION on the bottom side, they should be marked as inputs + * For channels chanY with DEC_DIRECTION on the bottom side, they should be marked as outputs + * For channels chanX with INC_DIRECTION on the left side, they should be marked as inputs + * For channels chanX with DEC_DIRECTION on the left side, they should be marked as outputs + * For channels chanX with INC_DIRECTION on the right side, they should be marked as outputs + * For channels chanX with DEC_DIRECTION on the right side, they should be marked as inputs + * + * [II] A X-direction Connection Block [x][y] + * The connection block shares the same routing channel[x][y] with the Switch Block + * We just need to fill the ipin nodes at TOP and BOTTOM sides + * as well as properly fill the ipin_grid_side information + * [III] A Y-direction Connection Block [x][y+1] + * The connection block shares the same routing channel[x][y+1] with the Switch Block + * We just need to fill the ipin nodes at LEFT and RIGHT sides + * as well as properly fill the ipin_grid_side information + ***********************************************************************/ +RRGSB build_one_tileable_rr_gsb(const DeviceGrid& grids, + const RRGraph& rr_graph, + const std::vector& device_chan_width, + const std::vector& segment_inf, + const vtr::Point& gsb_coordinate) { + /* Create an object to return */ + RRGSB rr_gsb; + + /* Check */ + VTR_ASSERT(gsb_coordinate.x() <= grids.width()); + VTR_ASSERT(gsb_coordinate.y() <= grids.height()); + + /* Coordinator initialization */ + rr_gsb.set_coordinate(gsb_coordinate.x(), gsb_coordinate.y()); + + /* Basic information*/ + rr_gsb.init_num_sides(4); /* Fixed number of sides */ + + /* Find all rr_nodes of channels */ + /* SideManager: TOP => 0, RIGHT => 1, BOTTOM => 2, LEFT => 3 */ + for (size_t side = 0; side < rr_gsb.get_num_sides(); ++side) { + /* Local variables inside this for loop */ + SideManager side_manager(side); + vtr::Point coordinate = rr_gsb.get_side_block_coordinate(side_manager.get_side()); + RRChan rr_chan; + std::vector> temp_opin_rr_nodes(2); + enum e_side opin_grid_side[2] = {NUM_SIDES, NUM_SIDES}; + enum PORTS chan_dir_to_port_dir_mapping[2] = {OUT_PORT, IN_PORT}; /* 0: INC_DIRECTION => ?; 1: DEC_DIRECTION => ? */ + + /* Build a segment details, where we need the segment ids for building rr_chan + * We do not care starting and ending points here, so set chan_side as NUM_SIDES + */ + ChanNodeDetails chanx_details = build_unidir_chan_node_details(device_chan_width[0], grids.width() - 1, + NUM_SIDES, segment_inf); + ChanNodeDetails chany_details = build_unidir_chan_node_details(device_chan_width[1], grids.height() - 1, + NUM_SIDES, segment_inf); + + switch (side) { + case TOP: /* TOP = 0 */ + /* For the bording, we should take special care */ + if (gsb_coordinate.y() == grids.height() - 1) { + rr_gsb.clear_one_side(side_manager.get_side()); + break; + } + /* Routing channels*/ + /* SideManager: TOP => 0, RIGHT => 1, BOTTOM => 2, LEFT => 3 */ + /* Create a rr_chan object and check if it is unique in the graph */ + rr_chan = build_one_tileable_rr_chan(coordinate, CHANY, rr_graph, chany_details); + chan_dir_to_port_dir_mapping[0] = OUT_PORT; /* INC_DIRECTION => OUT_PORT */ + chan_dir_to_port_dir_mapping[1] = IN_PORT; /* DEC_DIRECTION => IN_PORT */ + + /* Assign grid side of OPIN */ + /* Grid[x][y+1] RIGHT side outputs pins */ + opin_grid_side[0] = RIGHT; + /* Grid[x+1][y+1] left side outputs pins */ + opin_grid_side[1] = LEFT; + + /* Build the Switch block: opin and opin_grid_side */ + /* Include Grid[x][y+1] RIGHT side outputs pins */ + temp_opin_rr_nodes[0] = find_rr_graph_grid_nodes(rr_graph, grids, + gsb_coordinate.x(), gsb_coordinate.y() + 1, + OPIN, opin_grid_side[0]); + /* Include Grid[x+1][y+1] Left side output pins */ + temp_opin_rr_nodes[1] = find_rr_graph_grid_nodes(rr_graph, grids, + gsb_coordinate.x() + 1, gsb_coordinate.y() + 1, + OPIN, opin_grid_side[1]); + + break; + case RIGHT: /* RIGHT = 1 */ + /* For the bording, we should take special care */ + if (gsb_coordinate.x() == grids.width() - 1) { + rr_gsb.clear_one_side(side_manager.get_side()); + break; + } + /* Routing channels*/ + /* SideManager: TOP => 0, RIGHT => 1, BOTTOM => 2, LEFT => 3 */ + /* Collect rr_nodes for Tracks for top: chany[x][y+1] */ + /* Create a rr_chan object and check if it is unique in the graph */ + rr_chan = build_one_tileable_rr_chan(coordinate, CHANX, rr_graph, chanx_details); + chan_dir_to_port_dir_mapping[0] = OUT_PORT; /* INC_DIRECTION => OUT_PORT */ + chan_dir_to_port_dir_mapping[1] = IN_PORT; /* DEC_DIRECTION => IN_PORT */ + + /* Assign grid side of OPIN */ + /* Grid[x+1][y+1] BOTTOM side outputs pins */ + opin_grid_side[0] = BOTTOM; + /* Grid[x+1][y] TOP side outputs pins */ + opin_grid_side[1] = TOP; + + /* Build the Switch block: opin and opin_grid_side */ + /* include Grid[x+1][y+1] Bottom side output pins */ + temp_opin_rr_nodes[0] = find_rr_graph_grid_nodes(rr_graph, grids, + gsb_coordinate.x() + 1, gsb_coordinate.y() + 1, + OPIN, opin_grid_side[0]); + /* include Grid[x+1][y] Top side output pins */ + temp_opin_rr_nodes[1] = find_rr_graph_grid_nodes(rr_graph, grids, + gsb_coordinate.x() + 1, gsb_coordinate.y(), + OPIN, opin_grid_side[1]); + break; + case BOTTOM: /* BOTTOM = 2*/ + /* For the bording, we should take special care */ + if (gsb_coordinate.y() == 0) { + rr_gsb.clear_one_side(side_manager.get_side()); + break; + } + /* Routing channels*/ + /* SideManager: TOP => 0, RIGHT => 1, BOTTOM => 2, LEFT => 3 */ + /* Collect rr_nodes for Tracks for bottom: chany[x][y] */ + /* Create a rr_chan object and check if it is unique in the graph */ + rr_chan = build_one_tileable_rr_chan(coordinate, CHANY, rr_graph, chany_details); + chan_dir_to_port_dir_mapping[0] = IN_PORT; /* INC_DIRECTION => IN_PORT */ + chan_dir_to_port_dir_mapping[1] = OUT_PORT; /* DEC_DIRECTION => OUT_PORT */ + + /* Assign grid side of OPIN */ + /* Grid[x+1][y] LEFT side outputs pins */ + opin_grid_side[0] = LEFT; + /* Grid[x][y] RIGHT side outputs pins */ + opin_grid_side[1] = RIGHT; + + /* Build the Switch block: opin and opin_grid_side */ + /* include Grid[x+1][y] Left side output pins */ + temp_opin_rr_nodes[0] = find_rr_graph_grid_nodes(rr_graph, grids, + gsb_coordinate.x() + 1, gsb_coordinate.y(), + OPIN, opin_grid_side[0]); + /* include Grid[x][y] Right side output pins */ + temp_opin_rr_nodes[1] = find_rr_graph_grid_nodes(rr_graph, grids, + gsb_coordinate.x(), gsb_coordinate.y(), + OPIN, opin_grid_side[1]); + break; + case LEFT: /* LEFT = 3 */ + /* For the bording, we should take special care */ + if (gsb_coordinate.x() == 0) { + rr_gsb.clear_one_side(side_manager.get_side()); + break; + } + /* Routing channels*/ + /* SideManager: TOP => 0, RIGHT => 1, BOTTOM => 2, LEFT => 3 */ + /* Collect rr_nodes for Tracks for left: chanx[x][y] */ + /* Create a rr_chan object and check if it is unique in the graph */ + rr_chan = build_one_tileable_rr_chan(coordinate, CHANX, rr_graph, chanx_details); + chan_dir_to_port_dir_mapping[0] = IN_PORT; /* INC_DIRECTION => IN_PORT */ + chan_dir_to_port_dir_mapping[1] = OUT_PORT; /* DEC_DIRECTION => OUT_PORT */ + + /* Grid[x][y+1] BOTTOM side outputs pins */ + opin_grid_side[0] = BOTTOM; + /* Grid[x][y] TOP side outputs pins */ + opin_grid_side[1] = TOP; + + /* Build the Switch block: opin and opin_grid_side */ + /* include Grid[x][y+1] Bottom side outputs pins */ + temp_opin_rr_nodes[0] = find_rr_graph_grid_nodes(rr_graph, grids, + gsb_coordinate.x(), gsb_coordinate.y() + 1, + OPIN, opin_grid_side[0]); + /* include Grid[x][y] Top side output pins */ + temp_opin_rr_nodes[1] = find_rr_graph_grid_nodes(rr_graph, grids, + gsb_coordinate.x(), gsb_coordinate.y(), + OPIN, opin_grid_side[1]); + + break; + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid side index!\n"); + exit(1); + } + + /* Organize a vector of port direction */ + if (0 < rr_chan.get_chan_width()) { + std::vector rr_chan_dir; + rr_chan_dir.resize(rr_chan.get_chan_width()); + for (size_t itrack = 0; itrack < rr_chan.get_chan_width(); ++itrack) { + /* Identify the directionality, record it in rr_node_direction */ + if (INC_DIRECTION == rr_graph.node_direction(rr_chan.get_node(itrack))) { + rr_chan_dir[itrack] = chan_dir_to_port_dir_mapping[0]; + } else { + VTR_ASSERT(DEC_DIRECTION == rr_graph.node_direction(rr_chan.get_node(itrack))); + rr_chan_dir[itrack] = chan_dir_to_port_dir_mapping[1]; + } + } + /* Fill chan_rr_nodes */ + rr_gsb.add_chan_node(side_manager.get_side(), rr_chan, rr_chan_dir); + } + + /* Fill opin_rr_nodes */ + /* Copy from temp_opin_rr_node to opin_rr_node */ + for (const RRNodeId& inode : temp_opin_rr_nodes[0]) { + /* Grid[x+1][y+1] Bottom side outputs pins */ + rr_gsb.add_opin_node(inode, side_manager.get_side()); + } + for (const RRNodeId& inode : temp_opin_rr_nodes[1]) { + /* Grid[x+1][y] TOP side outputs pins */ + rr_gsb.add_opin_node(inode, side_manager.get_side()); + } + + /* Clean ipin_rr_nodes */ + /* We do not have any IPIN for a Switch Block */ + rr_gsb.clear_ipin_nodes(side_manager.get_side()); + + /* Clear the temp data */ + temp_opin_rr_nodes[0].clear(); + temp_opin_rr_nodes[1].clear(); + opin_grid_side[0] = NUM_SIDES; + opin_grid_side[1] = NUM_SIDES; + } + + /* SideManager: TOP => 0, RIGHT => 1, BOTTOM => 2, LEFT => 3 */ + for (size_t side = 0; side < rr_gsb.get_num_sides(); ++side) { + /* Local variables inside this for loop */ + SideManager side_manager(side); + size_t ix; + size_t iy; + enum e_side chan_side; + std::vector temp_ipin_rr_nodes; + enum e_side ipin_rr_node_grid_side; + + switch (side) { + case TOP: /* TOP = 0 */ + /* For the bording, we should take special care */ + /* Check if left side chan width is 0 or not */ + chan_side = LEFT; + /* Build the connection block: ipin and ipin_grid_side */ + /* BOTTOM side INPUT Pins of Grid[x][y+1] */ + ix = rr_gsb.get_sb_x(); + iy = rr_gsb.get_sb_y() + 1; + ipin_rr_node_grid_side = BOTTOM; + break; + case RIGHT: /* RIGHT = 1 */ + /* For the bording, we should take special care */ + /* Check if TOP side chan width is 0 or not */ + chan_side = TOP; + /* Build the connection block: ipin and ipin_grid_side */ + /* LEFT side INPUT Pins of Grid[x+1][y+1] */ + ix = rr_gsb.get_sb_x() + 1; + iy = rr_gsb.get_sb_y() + 1; + ipin_rr_node_grid_side = LEFT; + break; + case BOTTOM: /* BOTTOM = 2*/ + /* For the bording, we should take special care */ + /* Check if left side chan width is 0 or not */ + chan_side = LEFT; + /* Build the connection block: ipin and ipin_grid_side */ + /* TOP side INPUT Pins of Grid[x][y] */ + ix = rr_gsb.get_sb_x(); + iy = rr_gsb.get_sb_y(); + ipin_rr_node_grid_side = TOP; + break; + case LEFT: /* LEFT = 3 */ + /* For the bording, we should take special care */ + /* Check if left side chan width is 0 or not */ + chan_side = TOP; + /* Build the connection block: ipin and ipin_grid_side */ + /* RIGHT side INPUT Pins of Grid[x][y+1] */ + ix = rr_gsb.get_sb_x(); + iy = rr_gsb.get_sb_y() + 1; + ipin_rr_node_grid_side = RIGHT; + break; + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid side index!\n"); + exit(1); + } + + /* If there is no channel at this side, we skip ipin_node annotation */ + if (0 == rr_gsb.get_chan_width(chan_side)) { + continue; + } + /* Collect IPIN rr_nodes*/ + temp_ipin_rr_nodes = find_rr_graph_grid_nodes(rr_graph, grids, + ix, iy, IPIN, ipin_rr_node_grid_side); + /* Fill the ipin nodes of RRGSB */ + for (const RRNodeId& inode : temp_ipin_rr_nodes) { + rr_gsb.add_ipin_node(inode, side_manager.get_side()); + } + /* Clear the temp data */ + temp_ipin_rr_nodes.clear(); + } + + return rr_gsb; +} + +/************************************************************************ + * Create edges for each rr_node of a General Switch Blocks (GSB): + * 1. create edges between CHANX | CHANY and IPINs (connections inside connection blocks) + * 2. create edges between OPINs, CHANX and CHANY (connections inside switch blocks) + * 3. create edges between OPINs and IPINs (direct-connections) + ***********************************************************************/ +void build_edges_for_one_tileable_rr_gsb(RRGraph& rr_graph, + const RRGSB& rr_gsb, + const t_track2pin_map& track2ipin_map, + const t_pin2track_map& opin2track_map, + const t_track2track_map& track2track_map, + const vtr::vector rr_node_driver_switches) { + + /* Walk through each sides */ + for (size_t side = 0; side < rr_gsb.get_num_sides(); ++side) { + SideManager side_manager(side); + enum e_side gsb_side = side_manager.get_side(); + + /* Find OPINs */ + for (size_t inode = 0; inode < rr_gsb.get_num_opin_nodes(gsb_side); ++inode) { + const RRNodeId& opin_node = rr_gsb.get_opin_node(gsb_side, inode); + + /* 1. create edges between OPINs and CHANX|CHANY, using opin2track_map */ + /* add edges to the opin_node */ + for (const RRNodeId& track_node : opin2track_map[gsb_side][inode]) { + rr_graph.create_edge(opin_node, track_node, rr_node_driver_switches[track_node]); + } + } + + /* Find CHANX or CHANY */ + /* For TRACKs to IPINs, we only care LEFT and TOP sides + * Skip RIGHT and BOTTOM for the ipin2track_map since they should be handled in other GSBs + */ + if ( (side_manager.get_side() == rr_gsb.get_cb_chan_side(CHANX)) + || (side_manager.get_side() == rr_gsb.get_cb_chan_side(CHANY)) ) { + /* 2. create edges between CHANX|CHANY and IPINs, using ipin2track_map */ + for (size_t inode = 0; inode < rr_gsb.get_chan_width(gsb_side); ++inode) { + const RRNodeId& chan_node = rr_gsb.get_chan_node(gsb_side, inode); + for (const RRNodeId& ipin_node : track2ipin_map[gsb_side][inode]) { + rr_graph.create_edge(chan_node, ipin_node, rr_node_driver_switches[ipin_node]); + } + } + } + + /* 3. create edges between CHANX|CHANY and CHANX|CHANY, using track2track_map */ + for (size_t inode = 0; inode < rr_gsb.get_chan_width(gsb_side); ++inode) { + const RRNodeId& chan_node = rr_gsb.get_chan_node(gsb_side, inode); + for (const RRNodeId& track_node : track2track_map[gsb_side][inode]) { + rr_graph.create_edge(chan_node, track_node, rr_node_driver_switches[track_node]); + } + } + } +} + +/************************************************************************ + * Build track2ipin_map for an IPIN + * 1. build a list of routing tracks which are allowed for connections + * We will check the Connection Block (CB) population of each routing track. + * By comparing current chan_y - ylow, we can determine if a CB connection + * is required for each routing track + * 2. Divide the routing tracks by segment types, so that we can balance + * the connections between IPINs and different types of routing tracks. + * 3. Scale the Fc of each pin to the actual number of routing tracks + * actual_Fc = (int) Fc * num_tracks / chan_width + ***********************************************************************/ +static +void build_gsb_one_ipin_track2pin_map(const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const enum e_side& ipin_side, + const size_t& ipin_node_id, + const size_t& Fc, + const size_t& offset, + const std::vector& segment_inf, + t_track2pin_map& track2ipin_map) { + /* Get a list of segment_ids*/ + enum e_side chan_side = rr_gsb.get_cb_chan_side(ipin_side); + SideManager chan_side_manager(chan_side); + std::vector seg_list = rr_gsb.get_chan_segment_ids(chan_side); + size_t chan_width = rr_gsb.get_chan_width(chan_side); + SideManager ipin_side_manager(ipin_side); + const RRNodeId& ipin_node = rr_gsb.get_ipin_node(ipin_side, ipin_node_id); + + for (size_t iseg = 0; iseg < seg_list.size(); ++iseg) { + /* Get a list of node that have the segment id */ + std::vector track_list = rr_gsb.get_chan_node_ids_by_segment_ids(chan_side, seg_list[iseg]); + /* Refine the track_list: keep those will have connection blocks in the GSB */ + std::vector actual_track_list; + for (size_t inode = 0; inode < track_list.size(); ++inode) { + /* Check if tracks allow connection blocks in the GSB*/ + if (false == is_gsb_in_track_cb_population(rr_graph, rr_gsb, chan_side, track_list[inode], segment_inf)) { + continue; /* Bypass condition */ + } + /* Push the node to actual_track_list */ + actual_track_list.push_back(track_list[inode]); + } + /* Check the actual track list */ + VTR_ASSERT(0 == actual_track_list.size() % 2); + + /* Scale Fc */ + int actual_Fc = std::ceil((float)Fc * (float)actual_track_list.size() / (float)chan_width); + /* Minimum Fc should be 2 : ensure we will connect to a pair of routing tracks */ + actual_Fc = std::max(1, actual_Fc); + /* Compute the step between two connection from this IPIN to tracks: + * step = W' / Fc', W' and Fc' are the adapted W and Fc from actual_track_list and Fc_in + */ + size_t track_step = std::floor((float)actual_track_list.size() / (float)actual_Fc); + /* Make sure step should be at least 2 */ + track_step = std::max(1, (int)track_step); + /* Adapt offset to the range of actual_track_list */ + size_t actual_offset = offset % actual_track_list.size(); + /* rotate the track list by an offset */ + if (0 < actual_offset) { + std::rotate(actual_track_list.begin(), actual_track_list.begin() + actual_offset, actual_track_list.end()); + } + + /* Assign tracks: since we assign 2 track per round, we increment itrack by 2* step */ + int track_cnt = 0; + /* Keep assigning until we meet the Fc requirement */ + for (size_t itrack = 0; itrack < actual_track_list.size(); itrack = itrack + 2 * track_step) { + /* Update pin2track map */ + size_t chan_side_index = chan_side_manager.to_size_t(); + /* itrack may exceed the size of actual_track_list, adapt it */ + size_t actual_itrack = itrack % actual_track_list.size(); + /* track_index may exceed the chan_width(), adapt it */ + size_t track_index = actual_track_list[actual_itrack] % chan_width; + + track2ipin_map[chan_side_index][track_index].push_back(ipin_node); + + /* track_index may exceed the chan_width(), adapt it */ + track_index = (actual_track_list[actual_itrack] + 1) % chan_width; + + track2ipin_map[chan_side_index][track_index].push_back(ipin_node); + + track_cnt += 2; + } + + /* Ensure the number of tracks is similar to Fc */ + /* Give a warning if Fc is < track_cnt */ + /* + if (actual_Fc != track_cnt) { + vpr_printf(TIO_MESSAGE_INFO, + "IPIN Node(%lu) will have a different Fc(=%lu) than specified(=%lu)!\n", + ipin_node - rr_graph->rr_node, track_cnt, actual_Fc); + } + */ + } +} + +/************************************************************************ + * Build opin2track_map for an OPIN + * 1. build a list of routing tracks which are allowed for connections + * We will check the Switch Block (SB) population of each routing track. + * By comparing current chan_y - ylow, we can determine if a SB connection + * is required for each routing track + * 2. Divide the routing tracks by segment types, so that we can balance + * the connections between OPINs and different types of routing tracks. + * 3. Scale the Fc of each pin to the actual number of routing tracks + * actual_Fc = (int) Fc * num_tracks / chan_width + ***********************************************************************/ +static +void build_gsb_one_opin_pin2track_map(const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const enum e_side& opin_side, + const size_t& opin_node_id, + const size_t& Fc, + const size_t& offset, + const std::vector& segment_inf, + t_pin2track_map& opin2track_map) { + /* Get a list of segment_ids*/ + std::vector seg_list = rr_gsb.get_chan_segment_ids(opin_side); + enum e_side chan_side = opin_side; + size_t chan_width = rr_gsb.get_chan_width(chan_side); + SideManager opin_side_manager(opin_side); + + for (size_t iseg = 0; iseg < seg_list.size(); ++iseg) { + /* Get a list of node that have the segment id */ + std::vector track_list = rr_gsb.get_chan_node_ids_by_segment_ids(chan_side, seg_list[iseg]); + /* Refine the track_list: keep those will have connection blocks in the GSB */ + std::vector actual_track_list; + for (size_t inode = 0; inode < track_list.size(); ++inode) { + /* Check if tracks allow connection blocks in the GSB*/ + if (false == is_gsb_in_track_sb_population(rr_graph, rr_gsb, chan_side, + track_list[inode], segment_inf)) { + continue; /* Bypass condition */ + } + if (TRACK_START != determine_track_status_of_gsb(rr_graph, rr_gsb, chan_side, track_list[inode])) { + continue; /* Bypass condition */ + } + /* Push the node to actual_track_list */ + actual_track_list.push_back(track_list[inode]); + } + + /* Go the next segment if offset is zero or actual_track_list is empty */ + if (0 == actual_track_list.size()) { + continue; + } + + /* Scale Fc */ + int actual_Fc = std::ceil((float)Fc * (float)actual_track_list.size() / (float)chan_width); + /* Minimum Fc should be 1 : ensure we will drive 1 routing track */ + actual_Fc = std::max(1, actual_Fc); + /* Compute the step between two connection from this IPIN to tracks: + * step = W' / Fc', W' and Fc' are the adapted W and Fc from actual_track_list and Fc_in + */ + size_t track_step = std::floor((float)actual_track_list.size() / (float)actual_Fc); + /* Track step mush be a multiple of 2!!!*/ + /* Make sure step should be at least 1 */ + track_step = std::max(1, (int)track_step); + /* Adapt offset to the range of actual_track_list */ + size_t actual_offset = offset % actual_track_list.size(); + + /* No need to rotate if offset is zero */ + if (0 < actual_offset) { + /* rotate the track list by an offset */ + std::rotate(actual_track_list.begin(), actual_track_list.begin() + actual_offset, actual_track_list.end()); + } + + /* Assign tracks */ + int track_cnt = 0; + /* Keep assigning until we meet the Fc requirement */ + for (size_t itrack = 0; itrack < actual_track_list.size(); itrack = itrack + track_step) { + /* Update pin2track map */ + size_t opin_side_index = opin_side_manager.to_size_t(); + /* itrack may exceed the size of actual_track_list, adapt it */ + size_t actual_itrack = itrack % actual_track_list.size(); + size_t track_index = actual_track_list[actual_itrack]; + const RRNodeId& track_rr_node_index = rr_gsb.get_chan_node(chan_side, track_index); + opin2track_map[opin_side_index][opin_node_id].push_back(track_rr_node_index); + /* update track counter */ + track_cnt++; + /* Stop when we have enough Fc: this may lead to some tracks have zero drivers. + * So I comment it. And we just make sure its track_cnt >= actual_Fc + if (actual_Fc == track_cnt) { + break; + } + */ + } + + /* Ensure the number of tracks is similar to Fc */ + /* Give a warning if Fc is < track_cnt */ + /* + if (actual_Fc != track_cnt) { + vpr_printf(TIO_MESSAGE_INFO, + "OPIN Node(%lu) will have a different Fc(=%lu) than specified(=%lu)!\n", + opin_node_id, track_cnt, actual_Fc); + } + */ + } + + return; +} + + +/************************************************************************ + * Build the track_to_ipin_map[gsb_side][0..chan_width-1][ipin_indices] + * based on the existing routing resources in the General Switch Block (GSB) + * This function supports both X-directional and Y-directional tracks + * The mapping is done in the following steps: + * 1. Build ipin_to_track_map[gsb_side][0..num_ipin_nodes-1][track_indices] + * For each IPIN, we ensure at least one connection to the tracks. + * Then, we assign IPINs to tracks evenly while satisfying the actual_Fc + * 2. Convert the ipin_to_track_map to track_to_ipin_map + ***********************************************************************/ +t_track2pin_map build_gsb_track_to_ipin_map(const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const DeviceGrid& grids, + const std::vector& segment_inf, + int** Fc_in) { + t_track2pin_map track2ipin_map; + /* Resize the matrix */ + track2ipin_map.resize(rr_gsb.get_num_sides()); + + /* offset counter: it aims to balance the track-to-IPIN for each connection block */ + size_t offset_size = 0; + std::vector offset; + for (size_t side = 0; side < rr_gsb.get_num_sides(); ++side) { + SideManager side_manager(side); + enum e_side ipin_side = side_manager.get_side(); + /* Get the chan_side */ + enum e_side chan_side = rr_gsb.get_cb_chan_side(ipin_side); + SideManager chan_side_manager(chan_side); + /* resize offset to the maximum chan_side*/ + offset_size = std::max(offset_size, chan_side_manager.to_size_t() + 1); + } + /* Initial offset */ + offset.resize(offset_size); + offset.assign(offset.size(), 0); + + /* Walk through each side */ + for (size_t side = 0; side < rr_gsb.get_num_sides(); ++side) { + SideManager side_manager(side); + enum e_side ipin_side = side_manager.get_side(); + /* Get the chan_side */ + enum e_side chan_side = rr_gsb.get_cb_chan_side(ipin_side); + SideManager chan_side_manager(chan_side); + /* This track2pin mapping is for Connection Blocks, so we only care two sides! */ + /* Get channel width and resize the matrix */ + size_t chan_width = rr_gsb.get_chan_width(chan_side); + track2ipin_map[chan_side_manager.to_size_t()].resize(chan_width); + /* Find the ipin/opin nodes */ + for (size_t inode = 0; inode < rr_gsb.get_num_ipin_nodes(ipin_side); ++inode) { + const RRNodeId& ipin_node = rr_gsb.get_ipin_node(ipin_side, inode); + /* Skip EMPTY type */ + if (true == is_empty_type(grids[rr_graph.node_xlow(ipin_node)][rr_graph.node_ylow(ipin_node)].type)) { + continue; + } + int grid_type_index = grids[rr_graph.node_xlow(ipin_node)][rr_graph.node_ylow(ipin_node)].type->index; + /* Get Fc of the ipin */ + int ipin_Fc = Fc_in[grid_type_index][rr_graph.node_pin_num(ipin_node)]; + /* skip Fc = 0 */ + if ( (-1 == ipin_Fc) + || (0 == ipin_Fc) ) { + continue; + } + /* Build track2ipin_map for this IPIN */ + build_gsb_one_ipin_track2pin_map(rr_graph, rr_gsb, ipin_side, inode, ipin_Fc, + /* Give an offset for the first track that this ipin will connect to */ + offset[chan_side_manager.to_size_t()], + segment_inf, track2ipin_map); + /* update offset */ + offset[chan_side_manager.to_size_t()] += 2; + //printf("offset[%lu]=%lu\n", chan_side_manager.to_size_t(), offset[chan_side_manager.to_size_t()]); + } + } + + return track2ipin_map; +} + +/************************************************************************ + * Build the opin_to_track_map[gsb_side][0..num_opin_nodes-1][track_indices] + * based on the existing routing resources in the General Switch Block (GSB) + * This function supports both X-directional and Y-directional tracks + * The mapping is done in the following steps: + * 1. Build a list of routing tracks whose starting points locate at this GSB + * (xlow - gsb_x == 0) + * 2. Divide the routing tracks by segment types, so that we can balance + * the connections between OPINs and different types of routing tracks. + * 3. Scale the Fc of each pin to the actual number of routing tracks + * actual_Fc = (int) Fc * num_tracks / chan_width + ***********************************************************************/ +t_pin2track_map build_gsb_opin_to_track_map(const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const DeviceGrid& grids, + const std::vector& segment_inf, + int** Fc_out) { + t_pin2track_map opin2track_map; + /* Resize the matrix */ + opin2track_map.resize(rr_gsb.get_num_sides()); + + /* offset counter: it aims to balance the OPIN-to-track for each switch block */ + std::vector offset; + /* Get the chan_side: which is the same as the opin side */ + offset.resize(rr_gsb.get_num_sides()); + /* Initial offset */ + offset.assign(offset.size(), 0); + + /* Walk through each side */ + for (size_t side = 0; side < rr_gsb.get_num_sides(); ++side) { + SideManager side_manager(side); + enum e_side opin_side = side_manager.get_side(); + /* Get the chan_side */ + /* This track2pin mapping is for Connection Blocks, so we only care two sides! */ + /* Get channel width and resize the matrix */ + size_t num_opin_nodes = rr_gsb.get_num_opin_nodes(opin_side); + opin2track_map[side].resize(num_opin_nodes); + /* Find the ipin/opin nodes */ + for (size_t inode = 0; inode < num_opin_nodes; ++inode) { + const RRNodeId& opin_node = rr_gsb.get_opin_node(opin_side, inode); + /* Skip EMPTY type */ + if (true == is_empty_type(grids[rr_graph.node_xlow(opin_node)][rr_graph.node_ylow(opin_node)].type)) { + continue; + } + int grid_type_index = grids[rr_graph.node_xlow(opin_node)][rr_graph.node_ylow(opin_node)].type->index; + /* Get Fc of the ipin */ + int opin_Fc = Fc_out[grid_type_index][rr_graph.node_pin_num(opin_node)]; + /* skip Fc = 0 or unintialized, those pins are in the */ + if ( (-1 == opin_Fc) + || (0 == opin_Fc) ) { + continue; + } + /* Build track2ipin_map for this IPIN */ + build_gsb_one_opin_pin2track_map(rr_graph, rr_gsb, opin_side, inode, opin_Fc, + /* Give an offset for the first track that this ipin will connect to */ + offset[side_manager.to_size_t()], + segment_inf, opin2track_map); + /* update offset: aim to rotate starting tracks by 1*/ + offset[side_manager.to_size_t()] += 1; + } + + /* Check: + * 1. We want to ensure that each OPIN will drive at least one track + * 2. We want to ensure that each track will be driven by at least 1 OPIN */ + } + + return opin2track_map; +} + +} /* end namespace openfpga */ diff --git a/vpr/src/tileable_rr_graph/tileable_rr_graph_gsb.h b/vpr/src/tileable_rr_graph/tileable_rr_graph_gsb.h new file mode 100755 index 000000000..170249f74 --- /dev/null +++ b/vpr/src/tileable_rr_graph/tileable_rr_graph_gsb.h @@ -0,0 +1,80 @@ +#ifndef TILEABLE_RR_GRAPH_GSB_H +#define TILEABLE_RR_GRAPH_GSB_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include + +#include "vtr_vector.h" +#include "vtr_geometry.h" + +#include "physical_types.h" +#include "device_grid.h" + +#include "rr_gsb.h" +#include "rr_graph_obj.h" +#include "clb2clb_directs.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +/************************************************************************ + * Data stuctures related to the functions + ***********************************************************************/ +typedef std::vector>> t_track2track_map; +typedef std::vector>> t_track2pin_map; +typedef std::vector>> t_pin2track_map; + +/************************************************************************ + * Functions + ***********************************************************************/ +t_track2track_map build_gsb_track_to_track_map(const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const e_switch_block_type& sb_type, + const int& Fs, + const e_switch_block_type& sb_subtype, + const int& subFs, + const bool& wire_opposite_side, + const std::vector& segment_inf); + +RRGSB build_one_tileable_rr_gsb(const DeviceGrid& grids, + const RRGraph& rr_graph, + const std::vector& device_chan_width, + const std::vector& segment_inf, + const vtr::Point& gsb_coordinate); + +void build_edges_for_one_tileable_rr_gsb(RRGraph& rr_graph, + const RRGSB& rr_gsb, + const t_track2pin_map& track2ipin_map, + const t_pin2track_map& opin2track_map, + const t_track2track_map& track2track_map, + const vtr::vector rr_node_driver_switches); + +t_track2pin_map build_gsb_track_to_ipin_map(const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const DeviceGrid& grids, + const std::vector& segment_inf, + int** Fc_in); + +t_pin2track_map build_gsb_opin_to_track_map(const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const DeviceGrid& grids, + const std::vector& segment_inf, + int** Fc_out); + +void build_direct_connections_for_one_gsb(RRGraph& rr_graph, + const DeviceGrid& grids, + const vtr::Point& from_grid_coordinate, + const RRSwitchId& delayless_switch, + const std::vector& directs, + const std::vector& clb_to_clb_directs); + +} /* end namespace openfpga */ + +#endif + From de62ce8872757347c2e05dc905aa8843e392fd60 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 5 Mar 2020 15:34:04 -0700 Subject: [PATCH 254/645] add node builder for tileable rr_graph builder --- .../rr_graph_builder_utils.cpp | 190 ++++++++++ .../rr_graph_builder_utils.h | 18 + vpr/src/tileable_rr_graph/rr_gsb.h | 10 +- .../tileable_chan_details_builder.cpp | 40 +-- .../tileable_chan_details_builder.h | 6 +- .../tileable_rr_graph_gsb.cpp | 4 +- .../tileable_rr_graph_node_builder.cpp | 340 ++++++++++++++++++ .../tileable_rr_graph_node_builder.h | 31 ++ 8 files changed, 607 insertions(+), 32 deletions(-) create mode 100644 vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp create mode 100644 vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.h diff --git a/vpr/src/tileable_rr_graph/rr_graph_builder_utils.cpp b/vpr/src/tileable_rr_graph/rr_graph_builder_utils.cpp index 209d8a80b..47926e8c7 100644 --- a/vpr/src/tileable_rr_graph/rr_graph_builder_utils.cpp +++ b/vpr/src/tileable_rr_graph/rr_graph_builder_utils.cpp @@ -117,6 +117,196 @@ size_t get_grid_num_classes(const t_grid_tile& cur_grid, return num_classes; } +/************************************************************************ + * Idenfity if a X-direction routing channel exist in the fabric + * This could be entirely possible that a routig channel + * is in the middle of a multi-width and multi-height grid + * + * As the chanx always locates on top of a grid with the same coord + * + * +----------+ + * | CHANX | + * | [x][y] | + * +----------+ + * + * +----------+ + * | Grid | height_offset = height - 1 + * | [x][y] | + * +----------+ + * + * +----------+ + * | Grid | height_offset = height - 2 + * | [x][y-1] | + * +----------+ + * If the CHANX is in the middle of a multi-width and multi-height grid + * it should locate at a grid whose height_offset is lower than the its height defined in physical_tile + * When height_offset == height - 1, it means that the grid is at the top side of this multi-width and multi-height block + ***********************************************************************/ +bool is_chanx_exist(const DeviceGrid& grids, + const vtr::Point& chanx_coord) { + return (grids[chanx_coord.x()][chanx_coord.y()].height_offset == grids[chanx_coord.x()][chanx_coord.y()].type->height - 1); +} + +/************************************************************************ + * Idenfity if a Y-direction routing channel exist in the fabric + * This could be entirely possible that a routig channel + * is in the middle of a multi-width and multi-height grid + * + * As the chany always locates on right of a grid with the same coord + * + * +-----------+ +---------+ +--------+ + * | Grid | | Grid | | CHANY | + * | [x-1][y] | | [x][y] | | [x][y] | + * +-----------+ +---------+ +--------+ + * width_offset width_offset + * = width - 2 = width -1 + * If the CHANY is in the middle of a multi-width and multi-height grid + * it should locate at a grid whose width_offset is lower than the its width defined in physical_tile + * When height_offset == height - 1, it means that the grid is at the top side of this multi-width and multi-height block + ***********************************************************************/ +bool is_chany_exist(const DeviceGrid& grids, + const vtr::Point& chany_coord) { + return (grids[chany_coord.x()][chany_coord.y()].width_offset == grids[chany_coord.y()][chany_coord.y()].type->width - 1); +} + +/************************************************************************ + * Identify if a X-direction routing channel is at the right side of a + * multi-height grid + * + * +-----------------+ + * | | + * | | +-------------+ + * | Grid | | CHANX | + * | [x-1][y] | | [x][y] | + * | | +-------------+ + * | | + * +-----------------+ + ***********************************************************************/ +bool is_chanx_right_to_multi_height_grid(const DeviceGrid& grids, + const vtr::Point& chanx_coord) { + VTR_ASSERT(0 < chanx_coord.x()); + if (1 == chanx_coord.x()) { + /* This is already the LEFT side of FPGA fabric, + * it is the same results as chanx is right to a multi-height grid + */ + return true; + } + + /* We check the left neighbor of chanx, if it does not exist, the chanx is left to a multi-height grid */ + vtr::Point left_chanx_coord(chanx_coord.x() - 1, chanx_coord.y()); + if (false == is_chanx_exist(grids, left_chanx_coord)) { + return true; + } + + return false; +} + +/************************************************************************ + * Identify if a X-direction routing channel is at the left side of a + * multi-height grid + * + * +-----------------+ + * | | + * +---------------+ | | + * | CHANX | | Grid | + * | [x][y] | | [x+1][y] | + * +---------------+ | | + * | | + * +-----------------+ + ***********************************************************************/ +bool is_chanx_left_to_multi_height_grid(const DeviceGrid& grids, + const vtr::Point& chanx_coord) { + VTR_ASSERT(chanx_coord.x() < grids.width() - 1); + if (grids.width() - 2 == chanx_coord.x()) { + /* This is already the RIGHT side of FPGA fabric, + * it is the same results as chanx is right to a multi-height grid + */ + return true; + } + + /* We check the right neighbor of chanx, if it does not exist, the chanx is left to a multi-height grid */ + vtr::Point right_chanx_coord(chanx_coord.x() + 1, chanx_coord.y()); + if (false == is_chanx_exist(grids, right_chanx_coord)) { + return true; + } + + return false; +} + +/************************************************************************ + * Identify if a Y-direction routing channel is at the top side of a + * multi-width grid + * + * +--------+ + * | CHANY | + * | [x][y] | + * +--------+ + * + * +-----------------+ + * | | + * | | + * | Grid | + * | [x-1][y] | + * | | + * | | + * +-----------------+ + ***********************************************************************/ +bool is_chany_top_to_multi_width_grid(const DeviceGrid& grids, + const vtr::Point& chany_coord) { + VTR_ASSERT(0 < chany_coord.y()); + if (1 == chany_coord.y()) { + /* This is already the BOTTOM side of FPGA fabric, + * it is the same results as chany is at the top of a multi-width grid + */ + return true; + } + + /* We check the bottom neighbor of chany, if it does not exist, the chany is left to a multi-height grid */ + vtr::Point bottom_chany_coord(chany_coord.x(), chany_coord.y() - 1); + if (false == is_chany_exist(grids, bottom_chany_coord)) { + return true; + } + + return false; +} + +/************************************************************************ + * Identify if a Y-direction routing channel is at the bottom side of a + * multi-width grid + * + * +-----------------+ + * | | + * | | + * | Grid | + * | [x+1][y] | + * | | + * | | + * +-----------------+ + * +--------+ + * | CHANY | + * | [x][y] | + * +--------+ + * + ***********************************************************************/ +bool is_chany_bottom_to_multi_width_grid(const DeviceGrid& grids, + const vtr::Point& chany_coord) { + VTR_ASSERT(chany_coord.y() < grids.height() - 1); + if (grids.height() - 2 == chany_coord.y()) { + /* This is already the TOP side of FPGA fabric, + * it is the same results as chany is at the bottom of a multi-width grid + */ + return true; + } + + /* We check the top neighbor of chany, if it does not exist, the chany is left to a multi-height grid */ + vtr::Point top_chany_coord(chany_coord.x(), chany_coord.y() + 1); + if (false == is_chany_exist(grids, top_chany_coord)) { + return true; + } + + return false; +} + /************************************************************************ * Get the track_id of a routing track w.r.t its coordinator * In tileable routing architecture, the track_id changes SB by SB. diff --git a/vpr/src/tileable_rr_graph/rr_graph_builder_utils.h b/vpr/src/tileable_rr_graph/rr_graph_builder_utils.h index f67acb038..1367466d4 100644 --- a/vpr/src/tileable_rr_graph/rr_graph_builder_utils.h +++ b/vpr/src/tileable_rr_graph/rr_graph_builder_utils.h @@ -34,6 +34,24 @@ size_t get_grid_num_pins(const t_grid_tile& cur_grid, size_t get_grid_num_classes(const t_grid_tile& cur_grid, const e_pin_type& pin_type); +bool is_chanx_exist(const DeviceGrid& grids, + const vtr::Point& chanx_coord); + +bool is_chany_exist(const DeviceGrid& grids, + const vtr::Point& chany_coord); + +bool is_chanx_right_to_multi_height_grid(const DeviceGrid& grids, + const vtr::Point& chanx_coord); + +bool is_chanx_left_to_multi_height_grid(const DeviceGrid& grids, + const vtr::Point& chanx_coord); + +bool is_chany_top_to_multi_width_grid(const DeviceGrid& grids, + const vtr::Point& chany_coord); + +bool is_chany_bottom_to_multi_width_grid(const DeviceGrid& grids, + const vtr::Point& chany_coord); + short get_rr_node_actual_track_id(const RRGraph& rr_graph, const RRNodeId& track_rr_node, const vtr::Point& coord, diff --git a/vpr/src/tileable_rr_graph/rr_gsb.h b/vpr/src/tileable_rr_graph/rr_gsb.h index f582d7ce3..8d729139a 100644 --- a/vpr/src/tileable_rr_graph/rr_gsb.h +++ b/vpr/src/tileable_rr_graph/rr_gsb.h @@ -19,11 +19,11 @@ namespace openfpga { * 2. A X-direction Connection block locates at the left side of the switch block * 2. A Y-direction Connection block locates at the top side of the switch block * - * +---------------------------------+ - * | Y-direction CB | - * | [x][y + 1] | - * +---------------------------------+ - * + * +-------------+ +---------------------------------+ + * | | | Y-direction CB | + * | Grid | | [x][y + 1] | + * | [x][y+1] | +---------------------------------+ + * +-------------+ * TOP SIDE * +-------------+ +---------------------------------+ * | | | OPIN_NODE CHAN_NODES OPIN_NODES | diff --git a/vpr/src/tileable_rr_graph/tileable_chan_details_builder.cpp b/vpr/src/tileable_rr_graph/tileable_chan_details_builder.cpp index 5259c9445..23c1d7c07 100644 --- a/vpr/src/tileable_rr_graph/tileable_chan_details_builder.cpp +++ b/vpr/src/tileable_rr_graph/tileable_chan_details_builder.cpp @@ -161,8 +161,10 @@ int adapt_to_tileable_route_chan_width(const int& chan_width, * in X-direction and Y-direction channels!!! * So we will load segment details for different channels ***********************************************************************/ -ChanNodeDetails build_unidir_chan_node_details(const size_t& chan_width, const size_t& max_seg_length, - const enum e_side& device_side, +ChanNodeDetails build_unidir_chan_node_details(const size_t& chan_width, + const size_t& max_seg_length, + const bool& force_start, + const bool& force_end, const std::vector& segment_inf) { ChanNodeDetails chan_node_details; size_t actual_chan_width = chan_width; @@ -180,7 +182,7 @@ ChanNodeDetails build_unidir_chan_node_details(const size_t& chan_width, const s } /* Find the number of segments required by each group */ - std::vector num_tracks = get_num_tracks_per_seg_type(actual_chan_width/2, segment_inf, false); + std::vector num_tracks = get_num_tracks_per_seg_type(actual_chan_width / 2, segment_inf, false); /* Add node to ChanNodeDetails */ size_t cur_track = 0; @@ -188,7 +190,7 @@ ChanNodeDetails build_unidir_chan_node_details(const size_t& chan_width, const s /* segment length will be set to maxium segment length if this is a longwire */ size_t seg_len = segment_inf[iseg].length; if (true == segment_inf[iseg].longline) { - seg_len = max_seg_length; + seg_len = max_seg_length; } for (size_t itrack = 0; itrack < num_tracks[iseg]; ++itrack) { bool seg_start = false; @@ -213,29 +215,21 @@ ChanNodeDetails build_unidir_chan_node_details(const size_t& chan_width, const s } /* Check if all the tracks have been satisified */ VTR_ASSERT(cur_track == actual_chan_width); - - /* If this is on the border of a device, segments should start */ - switch (device_side) { - case TOP: - case RIGHT: - /* INC_DIRECTION should all end */ - chan_node_details.set_tracks_end(INC_DIRECTION); - /* DEC_DIRECTION should all start */ - chan_node_details.set_tracks_start(DEC_DIRECTION); - break; - case BOTTOM: - case LEFT: + + /* If this is on the border of a device/heterogeneous blocks, segments should start/end */ + if (true == force_start) { /* INC_DIRECTION should all start */ chan_node_details.set_tracks_start(INC_DIRECTION); /* DEC_DIRECTION should all end */ chan_node_details.set_tracks_end(DEC_DIRECTION); - break; - case NUM_SIDES: - break; - default: - VTR_LOGF_ERROR(__FILE__, __LINE__, - "Invalid device_side!\n"); - exit(1); + } + + /* If this is on the border of a device/heterogeneous blocks, segments should start/end */ + if (true == force_end) { + /* INC_DIRECTION should all end */ + chan_node_details.set_tracks_end(INC_DIRECTION); + /* DEC_DIRECTION should all start */ + chan_node_details.set_tracks_start(DEC_DIRECTION); } return chan_node_details; diff --git a/vpr/src/tileable_rr_graph/tileable_chan_details_builder.h b/vpr/src/tileable_rr_graph/tileable_chan_details_builder.h index cc111541f..b1a1198b9 100644 --- a/vpr/src/tileable_rr_graph/tileable_chan_details_builder.h +++ b/vpr/src/tileable_rr_graph/tileable_chan_details_builder.h @@ -17,8 +17,10 @@ namespace openfpga { int adapt_to_tileable_route_chan_width(const int& chan_width, const std::vector& segment_inf); -ChanNodeDetails build_unidir_chan_node_details(const size_t& chan_width, const size_t& max_seg_length, - const e_side& device_side, +ChanNodeDetails build_unidir_chan_node_details(const size_t& chan_width, + const size_t& max_seg_length, + const bool& force_start, + const bool& force_end, const std::vector& segment_inf); } /* end namespace openfpga */ diff --git a/vpr/src/tileable_rr_graph/tileable_rr_graph_gsb.cpp b/vpr/src/tileable_rr_graph/tileable_rr_graph_gsb.cpp index 332a44e94..9cbc69dff 100755 --- a/vpr/src/tileable_rr_graph/tileable_rr_graph_gsb.cpp +++ b/vpr/src/tileable_rr_graph/tileable_rr_graph_gsb.cpp @@ -651,9 +651,9 @@ RRGSB build_one_tileable_rr_gsb(const DeviceGrid& grids, * We do not care starting and ending points here, so set chan_side as NUM_SIDES */ ChanNodeDetails chanx_details = build_unidir_chan_node_details(device_chan_width[0], grids.width() - 1, - NUM_SIDES, segment_inf); + false, false, segment_inf); ChanNodeDetails chany_details = build_unidir_chan_node_details(device_chan_width[1], grids.height() - 1, - NUM_SIDES, segment_inf); + false, false, segment_inf); switch (side) { case TOP: /* TOP = 0 */ diff --git a/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp b/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp new file mode 100644 index 000000000..c8dbe38b1 --- /dev/null +++ b/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp @@ -0,0 +1,340 @@ +/************************************************************************ + * This file contains functions that are used to allocate nodes + * for the tileable routing resource graph builder + ***********************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" +#include "vtr_geometry.h" + +#include "vpr_utils.h" + +#include "rr_graph_builder_utils.h" +#include "tileable_chan_details_builder.h" +#include "tileable_rr_graph_node_builder.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/************************************************************************ + * Find the number output pins by considering all the grid + ***********************************************************************/ +static +size_t estimate_num_grid_rr_nodes_by_type(const DeviceGrid& grids, + const t_rr_type& node_type) { + size_t num_grid_rr_nodes = 0; + + for (size_t ix = 0; ix < grids.width(); ++ix) { + for (size_t iy = 0; iy < grids.height(); ++iy) { + + /* Skip EMPTY tiles */ + if (true == is_empty_type(grids[ix][iy].type)) { + continue; + } + + /* Skip height > 1 or width > 1 tiles (mostly heterogeneous blocks) */ + if ( (0 < grids[ix][iy].width_offset) + || (0 < grids[ix][iy].height_offset) ) { + continue; + } + + enum e_side io_side = NUM_SIDES; + + /* If this is the block on borders, we consider IO side */ + if (true == is_io_type(grids[ix][iy].type)) { + vtr::Point io_device_size(grids.width() - 1, grids.height() - 1); + vtr::Point grid_coordinate(ix, iy); + io_side = determine_io_grid_pin_side(io_device_size, grid_coordinate); + } + + switch (node_type) { + case OPIN: + /* get the number of OPINs */ + num_grid_rr_nodes += get_grid_num_pins(grids[ix][iy], DRIVER, io_side); + break; + case IPIN: + /* get the number of IPINs */ + num_grid_rr_nodes += get_grid_num_pins(grids[ix][iy], RECEIVER, io_side); + break; + case SOURCE: + /* SOURCE: number of classes whose type is DRIVER */ + num_grid_rr_nodes += get_grid_num_classes(grids[ix][iy], DRIVER); + break; + case SINK: + /* SINK: number of classes whose type is RECEIVER */ + num_grid_rr_nodes += get_grid_num_classes(grids[ix][iy], RECEIVER); + break; + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid routing resource node!\n"); + exit(1); + } + } + } + + return num_grid_rr_nodes; +} + +/************************************************************************ + * For X-direction Channel: CHANX + * We pair each x-direction routing channel to the grid below it + * as they share the same coordinate + * + * As such, the range of CHANX coordinate starts from x = 1, y = 0 + * which is the grid (I/O) at the left bottom of the fabric + * + * As such, the range of CHANX coordinate ends to x = width - 2, y = height - 2 + * which is the grid at the top right of the core fabric + * Note that the I/O ring is + * + * TOP SIDE OF FPGA + * + * +-------------+ +-------------+ +---------------------+ + * | Grid | | Grid | ... | Grid | + * | [1][0] | | [2][0] | | [width-2][height-1] | + * +-------------+ +-------------+ +---------------------+ + * + * +-------------+ +-------------+ +---------------------+ + * | X-Channel | | X-Channel | ... | X-Channel | + * | [1][0] | | [2][0] | | [width-2][height-2] | + * +-------------+ +-------------+ +---------------------+ + * + * +-------------+ +-------------+ +---------------------+ + * | Grid | | Grid | ... | Grid | + * | [1][0] | | [2][0] | | [width-2][height-2] | + * +-------------+ +-------------+ +---------------------+ + * + * ... ... ... + * + * +-------------+ +-------------+ +--------------+ + * | X-Channel | | X-Channel | ... | X-Channel | + * | [1][1] | | [2][1] | | [width-2][1] | + * +-------------+ +-------------+ +--------------+ + * + * LEFT +-------------+ +-------------+ +--------------+ RIGHT + * SIDE | Grid | | Grid | ... | Grid | SIDE + * GRID | [1][1] | | [2][1] | | [width-2][1] | GRID + * +-------------+ +-------------+ +--------------+ + * + * +-------------+ +-------------+ +--------------+ + * | X-Channel | | X-Channel | ... | X-Channel | + * | [1][0] | | [2][0] | | [width-2][0] | + * +-------------+ +-------------+ +--------------+ + * + * +-------------+ +-------------+ +--------------+ + * | Grid | | Grid | ... | Grid | + * | [1][0] | | [2][0] | | [width-2][0] | + * +-------------+ +-------------+ +--------------+ + * + * BOTTOM SIDE OF FPGA + * + * The figure above describe how the X-direction routing channels are + * organized in a homogeneous FPGA fabric + * Note that we talk about general-purpose uni-directional routing architecture here + * It means that a routing track may span across multiple grids + * However, the hard limits are as follows + * All the routing tracks will start at the most LEFT routing channel + * All the routing tracks will end at the most RIGHT routing channel + * + * Things will become more complicated in terms of track starting and end + * in the context of heterogeneous FPGAs + * We may have a grid which span multiple column and rows, as exemplified in the figure below + * In such case, + * all the routing tracks [x-1][y] at the left side of the grid [x][y] are forced to end + * all the routing tracks [x+2][y] at the right side of the grid [x][y] are forced to start + * And there are no routing tracks inside the grid[x][y] + * It means that X-channel [x][y] & [x+1][y] will no exist + * + * +------------+ +-------------+ +-------------+ +--------------+ + * | X-Channel | | X-Channel | | X-Channel | | X-Channel | + * | [x-1][y+2] | | [x][y+2] | | [x+1][y+2] | | [x+2][y+2] | + * +------------+ +-------------+ +-------------+ +--------------+ + * + * +------------+ +-----------------------------------+ +--------------+ + * | Grid | | | | Grid | + * | [x-1][y+1] | | | | [x+2][y+1] | + * +------------+ | | +--------------+ + * | | + * +------------+ | | +--------------+ + * | X-channel | | Grid | | X-Channel | + * | [x-1][y] | | [x][y] - [x+1][y+1] | | [x+2][y] | + * +------------+ | | +--------------+ + * | | + * +------------+ | | +--------------+ + * | Grid | | | | Grid | + * | [x-1][y] | | | | [x+2][y] | + * +------------+ +-----------------------------------+ +--------------+ + * + * + * + ***********************************************************************/ +static +size_t estimate_num_chanx_rr_nodes(const DeviceGrid& grids, + const size_t& chan_width, + const std::vector& segment_infs) { + size_t num_chanx_rr_nodes = 0; + + for (size_t iy = 0; iy < grids.height() - 1; ++iy) { + for (size_t ix = 1; ix < grids.width() - 1; ++ix) { + vtr::Point chanx_coord(ix, iy); + + /* Bypass if the routing channel does not exist */ + if (false == is_chanx_exist(grids, chanx_coord)) { + continue; + } + + bool force_start = false; + bool force_end = false; + + /* All the tracks have to start when + * - the routing channel touch the RIGHT side a heterogeneous block + * - the routing channel touch the LEFT side of FPGA + */ + if (true == is_chanx_right_to_multi_height_grid(grids, chanx_coord)) { + force_start = true; + } + + /* All the tracks have to end when + * - the routing channel touch the LEFT side a heterogeneous block + * - the routing channel touch the RIGHT side of FPGA + */ + if (true == is_chanx_left_to_multi_height_grid(grids, chanx_coord)) { + force_end = true; + } + + /* Evaluate if the routing channel locates in the middle of a grid */ + ChanNodeDetails chanx_details = build_unidir_chan_node_details(chan_width, grids.width() - 2, force_start, force_end, segment_infs); + /* When an INC_DIRECTION CHANX starts, we need a new rr_node */ + num_chanx_rr_nodes += chanx_details.get_num_starting_tracks(INC_DIRECTION); + /* When an DEC_DIRECTION CHANX ends, we need a new rr_node */ + num_chanx_rr_nodes += chanx_details.get_num_ending_tracks(DEC_DIRECTION); + } + } + + return num_chanx_rr_nodes; +} + +/************************************************************************ + * Estimate the number of CHANY rr_nodes for Y-direction routing channels + * The technical rationale is very similar to the X-direction routing channel + * Refer to the detailed explanation there + ***********************************************************************/ +static +size_t estimate_num_chany_rr_nodes(const DeviceGrid& grids, + const size_t& chan_width, + const std::vector& segment_infs) { + size_t num_chany_rr_nodes = 0; + + for (size_t ix = 0; ix < grids.width() - 1; ++ix) { + for (size_t iy = 1; iy < grids.height() - 1; ++iy) { + vtr::Point chany_coord(ix, iy); + + /* Bypass if the routing channel does not exist */ + if (false == is_chany_exist(grids, chany_coord)) { + continue; + } + + bool force_start = false; + bool force_end = false; + + /* All the tracks have to start when + * - the routing channel touch the TOP side a heterogeneous block + * - the routing channel touch the BOTTOM side of FPGA + */ + if (true == is_chany_top_to_multi_width_grid(grids, chany_coord)) { + force_start = true; + } + + /* All the tracks have to end when + * - the routing channel touch the BOTTOM side a heterogeneous block + * - the routing channel touch the TOP side of FPGA + */ + if (true == is_chany_bottom_to_multi_width_grid(grids, chany_coord)) { + force_end = true; + } + + ChanNodeDetails chany_details = build_unidir_chan_node_details(chan_width, grids.height() - 2, force_start, force_end, segment_infs); + /* When an INC_DIRECTION CHANX starts, we need a new rr_node */ + num_chany_rr_nodes += chany_details.get_num_starting_tracks(INC_DIRECTION); + /* When an DEC_DIRECTION CHANX ends, we need a new rr_node */ + num_chany_rr_nodes += chany_details.get_num_ending_tracks(DEC_DIRECTION); + } + } + + return num_chany_rr_nodes; +} + +/************************************************************************ + * Estimate the number of nodes by each type in a routing resource graph + ***********************************************************************/ +static +std::vector estimate_num_rr_nodes(const DeviceGrid& grids, + const vtr::Point& chan_width, + const std::vector& segment_infs) { + + /* Reset the OPIN, IPIN, SOURCE, SINK counter to be zero */ + std::vector num_rr_nodes_per_type(NUM_RR_TYPES, 0); + + /** + * 1 Find number of rr nodes related to grids + */ + num_rr_nodes_per_type[OPIN] = estimate_num_grid_rr_nodes_by_type(grids, OPIN); + num_rr_nodes_per_type[IPIN] = estimate_num_grid_rr_nodes_by_type(grids, IPIN); + num_rr_nodes_per_type[SOURCE] = estimate_num_grid_rr_nodes_by_type(grids, SOURCE); + num_rr_nodes_per_type[SINK] = estimate_num_grid_rr_nodes_by_type(grids, SINK); + + /** + * 2. Assign the segments for each routing channel, + * To be specific, for each routing track, we assign a routing segment. + * The assignment is subject to users' specifications, such as + * a. length of each type of segment + * b. frequency of each type of segment. + * c. routing channel width + * + * SPECIAL for fringes: + * All segments will start and ends with no exception + * + * IMPORTANT: we should be aware that channel width maybe different + * in X-direction and Y-direction channels!!! + * So we will load segment details for different channels + */ + num_rr_nodes_per_type[CHANX] = estimate_num_chanx_rr_nodes(grids, chan_width.x(), segment_infs); + num_rr_nodes_per_type[CHANY] = estimate_num_chany_rr_nodes(grids, chan_width.y(), segment_infs); + + return num_rr_nodes_per_type; +} + +/************************************************************************ + * Allocate rr_nodes to a rr_graph object + * This function just allocate the memory and ensure its efficiency + * It will NOT fill detailed information for each node!!! + * + * Note: ensure that there are NO nodes in the rr_graph + ***********************************************************************/ +void alloc_rr_graph_nodes(RRGraph& rr_graph, + const DeviceGrid& grids, + const vtr::Point& chan_width, + const std::vector& segment_infs) { + VTR_ASSERT(0 == rr_graph.nodes().size()); + + std::vector num_rr_nodes_per_type = estimate_num_rr_nodes(grids, chan_width, segment_infs); + + /* Reserve the number of node to be memory efficient */ + size_t num_nodes = 0; + for (const size_t& num_node_per_type : num_rr_nodes_per_type) { + num_nodes += num_node_per_type; + } + + rr_graph.reserve_nodes(num_nodes); + + /* Add nodes by types */ + for (const t_rr_type& node_type : {SOURCE, SINK, IPIN, OPIN, CHANX, CHANY}) { + for (size_t inode = 0; inode < num_rr_nodes_per_type[size_t(node_type)]; ++inode) { + rr_graph.create_node(node_type); + } + } + + VTR_ASSERT(num_nodes == rr_graph.nodes().size()); +} + +} /* end namespace openfpga */ diff --git a/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.h b/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.h new file mode 100644 index 000000000..a263324fe --- /dev/null +++ b/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.h @@ -0,0 +1,31 @@ +#ifndef TILEABLE_RR_GRAPH_NODE_BUILDER_H +#define TILEABLE_RR_GRAPH_NODE_BUILDER_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_geometry.h" + +/* Headers from readarch library */ +#include "physical_types.h" + +/* Headers from vpr library */ +#include "rr_graph_obj.h" +#include "device_grid.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void alloc_rr_graph_nodes(RRGraph& rr_graph, + const DeviceGrid& grids, + const vtr::Point& chan_width, + const std::vector& segment_infs); + +} /* end namespace openfpga */ + +#endif From 850788eacea0934cbfa7ec2e3f983e6b482c335b Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 5 Mar 2020 17:15:49 -0700 Subject: [PATCH 255/645] adapt tileable rr_graph node builder for rr_graph object --- .../tileable_rr_graph_node_builder.cpp | 330 +++++++++++++++++- .../tileable_rr_graph_node_builder.h | 15 +- 2 files changed, 332 insertions(+), 13 deletions(-) diff --git a/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp b/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp index c8dbe38b1..34347befc 100644 --- a/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp +++ b/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp @@ -7,8 +7,14 @@ #include "vtr_log.h" #include "vtr_geometry.h" +/* Headers from openfpgautil library */ +#include "openfpga_side_manager.h" + +#include "vpr_types.h" #include "vpr_utils.h" +#include "rr_node.h" + #include "rr_graph_builder_utils.h" #include "tileable_chan_details_builder.h" #include "tileable_rr_graph_node_builder.h" @@ -311,10 +317,11 @@ std::vector estimate_num_rr_nodes(const DeviceGrid& grids, * * Note: ensure that there are NO nodes in the rr_graph ***********************************************************************/ -void alloc_rr_graph_nodes(RRGraph& rr_graph, - const DeviceGrid& grids, - const vtr::Point& chan_width, - const std::vector& segment_infs) { +void alloc_tileable_rr_graph_nodes(RRGraph& rr_graph, + vtr::vector& rr_node_driver_switches, + const DeviceGrid& grids, + const vtr::Point& chan_width, + const std::vector& segment_infs) { VTR_ASSERT(0 == rr_graph.nodes().size()); std::vector num_rr_nodes_per_type = estimate_num_rr_nodes(grids, chan_width, segment_infs); @@ -327,14 +334,319 @@ void alloc_rr_graph_nodes(RRGraph& rr_graph, rr_graph.reserve_nodes(num_nodes); - /* Add nodes by types */ - for (const t_rr_type& node_type : {SOURCE, SINK, IPIN, OPIN, CHANX, CHANY}) { - for (size_t inode = 0; inode < num_rr_nodes_per_type[size_t(node_type)]; ++inode) { - rr_graph.create_node(node_type); + rr_node_driver_switches.reserve(num_nodes); +} + +/************************************************************************ + * Configure OPIN rr_nodes for this grid + * coordinates: xlow, ylow, xhigh, yhigh, + * features: capacity, ptc_num (pin_num), + * + * Note: this function should be applied ONLY to grid with 0 width offset and 0 height offset!!! + ***********************************************************************/ +static +void load_one_grid_opin_nodes_basic_info(RRGraph& rr_graph, + vtr::vector& rr_node_driver_switches, + const vtr::Point& grid_coordinate, + const t_grid_tile& cur_grid, + const e_side& io_side, + const RRSwitchId& delayless_switch) { + SideManager io_side_manager(io_side); + + /* Walk through the width height of each grid, + * get pins and configure the rr_nodes + */ + for (int width = 0; width < cur_grid.type->width; ++width) { + for (int height = 0; height < cur_grid.type->height; ++height) { + /* Walk through sides */ + for (size_t side = 0; side < NUM_SIDES; ++side) { + SideManager side_manager(side); + /* skip unwanted sides */ + if ( (true == is_io_type(cur_grid.type)) + && (side != io_side_manager.to_size_t()) ) { + continue; + } + /* Find OPINs */ + /* Configure pins by pins */ + std::vector opin_list = get_grid_side_pins(cur_grid, DRIVER, side_manager.get_side(), + width, height); + for (const int& pin_num : opin_list) { + /* Create a new node and fill information */ + const RRNodeId& node = rr_graph.create_node(OPIN); + + /* node bounding box */ + rr_graph.set_node_bounding_box(node, vtr::Rect(grid_coordinate.x() + width, + grid_coordinate.x() + width, + grid_coordinate.y() + height, + grid_coordinate.y() + height)); + rr_graph.set_node_side(node, side_manager.get_side()); + rr_graph.set_node_pin_num(node, pin_num); + + rr_graph.set_node_capacity(node, 1); + + /* cost index is a FIXED value for OPIN */ + rr_graph.set_node_cost_index(node, OPIN_COST_INDEX); + + /* Switch info */ + VTR_ASSERT(size_t(node) == rr_node_driver_switches.size()); + rr_node_driver_switches.push_back(delayless_switch); + + /* RC data */ + rr_graph.set_node_rc_data_index(node, find_create_rr_rc_data(0., 0.)); + + } /* End of loading OPIN rr_nodes */ + } /* End of side enumeration */ + } /* End of height enumeration */ + } /* End of width enumeration */ +} + +/************************************************************************ + * Configure IPIN rr_nodes for this grid + * coordinates: xlow, ylow, xhigh, yhigh, + * features: capacity, ptc_num (pin_num), + * + * Note: this function should be applied ONLY to grid with 0 width offset and 0 height offset!!! + ***********************************************************************/ +static +void load_one_grid_ipin_nodes_basic_info(RRGraph& rr_graph, + vtr::vector& rr_node_driver_switches, + const vtr::Point& grid_coordinate, + const t_grid_tile& cur_grid, + const e_side& io_side, + const RRSwitchId& wire_to_ipin_switch) { + SideManager io_side_manager(io_side); + + /* Walk through the width and height of each grid, + * get pins and configure the rr_nodes + */ + for (int width = 0; width < cur_grid.type->width; ++width) { + for (int height = 0; height < cur_grid.type->height; ++height) { + /* Walk through sides */ + for (size_t side = 0; side < NUM_SIDES; ++side) { + SideManager side_manager(side); + /* skip unwanted sides */ + if ( (true == is_io_type(cur_grid.type)) + && (side != io_side_manager.to_size_t()) ) { + continue; + } + + /* Find IPINs */ + /* Configure pins by pins */ + std::vector ipin_list = get_grid_side_pins(cur_grid, RECEIVER, side_manager.get_side(), width, height); + for (const int& pin_num : ipin_list) { + /* Create a new node and fill information */ + const RRNodeId& node = rr_graph.create_node(IPIN); + + /* node bounding box */ + rr_graph.set_node_bounding_box(node, vtr::Rect(grid_coordinate.x() + width, + grid_coordinate.x() + width, + grid_coordinate.y() + height, + grid_coordinate.y() + height)); + rr_graph.set_node_side(node, side_manager.get_side()); + rr_graph.set_node_pin_num(node, pin_num); + + rr_graph.set_node_capacity(node, 1); + + /* cost index is a FIXED value for OPIN */ + rr_graph.set_node_cost_index(node, IPIN_COST_INDEX); + + /* Switch info */ + VTR_ASSERT(size_t(node) == rr_node_driver_switches.size()); + rr_node_driver_switches.push_back(wire_to_ipin_switch); + + /* RC data */ + rr_graph.set_node_rc_data_index(node, find_create_rr_rc_data(0., 0.)); + + } /* End of loading IPIN rr_nodes */ + } /* End of side enumeration */ + } /* End of height enumeration */ + } /* End of width enumeration */ +} + +/************************************************************************ + * Configure SOURCE rr_nodes for this grid + * coordinates: xlow, ylow, xhigh, yhigh, + * features: capacity, ptc_num (pin_num), + * + * Note: this function should be applied ONLY to grid with 0 width offset and 0 height offset!!! + ***********************************************************************/ +static +void load_one_grid_source_nodes_basic_info(RRGraph& rr_graph, + vtr::vector& rr_node_driver_switches, + const vtr::Point& grid_coordinate, + const t_grid_tile& cur_grid, + const e_side& io_side, + const RRSwitchId& delayless_switch) { + SideManager io_side_manager(io_side); + + /* Set a SOURCE rr_node for each DRIVER class */ + for (int iclass = 0; iclass < cur_grid.type->num_class; ++iclass) { + /* Set a SINK rr_node for the OPIN */ + if (DRIVER != cur_grid.type->class_inf[iclass].type) { + continue; + } + + /* Create a new node and fill information */ + const RRNodeId& node = rr_graph.create_node(SOURCE); + + /* node bounding box */ + rr_graph.set_node_bounding_box(node, vtr::Rect(grid_coordinate.x(), + grid_coordinate.x(), + grid_coordinate.y(), + grid_coordinate.y())); + rr_graph.set_node_class_num(node, iclass); + + rr_graph.set_node_capacity(node, 1); + + /* The capacity should be the number of pins in this class*/ + rr_graph.set_node_capacity(node, cur_grid.type->class_inf[iclass].num_pins); + + /* cost index is a FIXED value for SOURCE */ + rr_graph.set_node_cost_index(node, SOURCE_COST_INDEX); + + /* Switch info */ + VTR_ASSERT(size_t(node) == rr_node_driver_switches.size()); + rr_node_driver_switches.push_back(delayless_switch); + + /* RC data */ + rr_graph.set_node_rc_data_index(node, find_create_rr_rc_data(0., 0.)); + + } /* End of class enumeration */ +} + +/************************************************************************ + * Configure SINK rr_nodes for this grid + * coordinates: xlow, ylow, xhigh, yhigh, + * features: capacity, ptc_num (pin_num), + * + * Note: this function should be applied ONLY to grid with 0 width offset and 0 height offset!!! + ***********************************************************************/ +static +void load_one_grid_sink_nodes_basic_info(RRGraph& rr_graph, + vtr::vector& rr_node_driver_switches, + const vtr::Point& grid_coordinate, + const t_grid_tile& cur_grid, + const e_side& io_side, + const RRSwitchId& delayless_switch) { + SideManager io_side_manager(io_side); + + /* Set a SINK rr_node for each RECEIVER class */ + for (int iclass = 0; iclass < cur_grid.type->num_class; ++iclass) { + /* Set a SINK rr_node for the OPIN */ + if (RECEIVER != cur_grid.type->class_inf[iclass].type) { + continue; + } + + /* Create a new node and fill information */ + const RRNodeId& node = rr_graph.create_node(SINK); + + /* node bounding box */ + rr_graph.set_node_bounding_box(node, vtr::Rect(grid_coordinate.x(), + grid_coordinate.x(), + grid_coordinate.y(), + grid_coordinate.y())); + rr_graph.set_node_class_num(node, iclass); + + rr_graph.set_node_capacity(node, 1); + + /* The capacity should be the number of pins in this class*/ + rr_graph.set_node_capacity(node, cur_grid.type->class_inf[iclass].num_pins); + + /* cost index is a FIXED value for SINK */ + rr_graph.set_node_cost_index(node, SINK_COST_INDEX); + + /* Switch info */ + VTR_ASSERT(size_t(node) == rr_node_driver_switches.size()); + rr_node_driver_switches.push_back(delayless_switch); + + /* RC data */ + rr_graph.set_node_rc_data_index(node, find_create_rr_rc_data(0., 0.)); + + } /* End of class enumeration */ +} + +/************************************************************************ + * Create all the rr_nodes for grids + ***********************************************************************/ +static +void load_grid_nodes_basic_info(RRGraph& rr_graph, + vtr::vector& rr_node_driver_switches, + const DeviceGrid& grids, + const RRSwitchId& wire_to_ipin_switch, + const RRSwitchId& delayless_switch) { + + for (size_t iy = 0; iy < grids.height(); ++iy) { + for (size_t ix = 0; ix < grids.width(); ++ix) { + /* Skip EMPTY tiles */ + if (true == is_empty_type(grids[ix][iy].type)) { + continue; + } + + /* We only build rr_nodes for grids with width_offset = 0 and height_offset = 0 */ + if ( (0 < grids[ix][iy].width_offset) + || (0 < grids[ix][iy].height_offset) ) { + continue; + } + + vtr::Point grid_coordinate(ix, iy); + enum e_side io_side = NUM_SIDES; + + /* If this is the block on borders, we consider IO side */ + if (true == is_io_type(grids[ix][iy].type)) { + vtr::Point io_device_size(grids.width() - 1, grids.height() - 1); + io_side = determine_io_grid_pin_side(io_device_size, grid_coordinate); + } + + /* Configure source rr_nodes for this grid */ + load_one_grid_source_nodes_basic_info(rr_graph, + rr_node_driver_switches, + grid_coordinate, + grids[ix][iy], + io_side, + delayless_switch); + + /* Configure sink rr_nodes for this grid */ + load_one_grid_sink_nodes_basic_info(rr_graph, + rr_node_driver_switches, + grid_coordinate, + grids[ix][iy], + io_side, + delayless_switch); + + /* Configure opin rr_nodes for this grid */ + load_one_grid_opin_nodes_basic_info(rr_graph, + rr_node_driver_switches, + grid_coordinate, + grids[ix][iy], + io_side, + delayless_switch); + + /* Configure ipin rr_nodes for this grid */ + load_one_grid_ipin_nodes_basic_info(rr_graph, + rr_node_driver_switches, + grid_coordinate, + grids[ix][iy], + io_side, + wire_to_ipin_switch); + } } +} + +/************************************************************************ + * Create all the rr_nodes covering both grids and routing channels + ***********************************************************************/ +void create_tileable_rr_graph_nodes(RRGraph& rr_graph, + vtr::vector& rr_node_driver_switches, + const DeviceGrid& grids, + const RRSwitchId& wire_to_ipin_switch, + const RRSwitchId& delayless_switch) { + load_grid_nodes_basic_info(rr_graph, + rr_node_driver_switches, + grids, + wire_to_ipin_switch, + delayless_switch); - VTR_ASSERT(num_nodes == rr_graph.nodes().size()); } } /* end namespace openfpga */ diff --git a/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.h b/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.h index a263324fe..7cd8c673a 100644 --- a/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.h +++ b/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.h @@ -21,10 +21,17 @@ /* begin namespace openfpga */ namespace openfpga { -void alloc_rr_graph_nodes(RRGraph& rr_graph, - const DeviceGrid& grids, - const vtr::Point& chan_width, - const std::vector& segment_infs); +void alloc_tileable_rr_graph_nodes(RRGraph& rr_graph, + vtr::vector& driver_switches, + const DeviceGrid& grids, + const vtr::Point& chan_width, + const std::vector& segment_infs); + +void create_tileable_rr_graph_nodes(RRGraph& rr_graph, + vtr::vector& rr_node_driver_switches, + const DeviceGrid& grids, + const RRSwitchId& wire_to_ipin_switch, + const RRSwitchId& delayless_switch); } /* end namespace openfpga */ From 5067dd846e8919754fe41d55a78ce3fbd482c2ef Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 5 Mar 2020 17:47:48 -0700 Subject: [PATCH 256/645] adapting channel rr_node builder for tileable rr_graph --- .../tileable_rr_graph/chan_node_details.cpp | 33 +++--- .../tileable_rr_graph_node_builder.cpp | 105 ++++++++++++++++++ 2 files changed, 123 insertions(+), 15 deletions(-) diff --git a/vpr/src/tileable_rr_graph/chan_node_details.cpp b/vpr/src/tileable_rr_graph/chan_node_details.cpp index f52188d93..ef2df157e 100644 --- a/vpr/src/tileable_rr_graph/chan_node_details.cpp +++ b/vpr/src/tileable_rr_graph/chan_node_details.cpp @@ -1,8 +1,11 @@ /************************************************************************ * This file contains member functions for class ChanNodeDetails ***********************************************************************/ -#include #include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" + #include "chan_node_details.h" /* begin namespace openfpga */ @@ -33,12 +36,12 @@ ChanNodeDetails::ChanNodeDetails() { * Accessors ***********************************************************************/ size_t ChanNodeDetails::get_chan_width() const { - assert(validate_chan_width()); + VTR_ASSERT(validate_chan_width()); return track_node_ids_.size(); } size_t ChanNodeDetails::get_track_node_id(const size_t& track_id) const { - assert(validate_track_id(track_id)); + VTR_ASSERT(validate_track_id(track_id)); return track_node_ids_[track_id]; } @@ -52,27 +55,27 @@ std::vector ChanNodeDetails::get_track_node_ids() const { } e_direction ChanNodeDetails::get_track_direction(const size_t& track_id) const { - assert(validate_track_id(track_id)); + VTR_ASSERT(validate_track_id(track_id)); return track_direction_[track_id]; } size_t ChanNodeDetails::get_track_segment_length(const size_t& track_id) const { - assert(validate_track_id(track_id)); + VTR_ASSERT(validate_track_id(track_id)); return seg_length_[track_id]; } size_t ChanNodeDetails::get_track_segment_id(const size_t& track_id) const { - assert(validate_track_id(track_id)); + VTR_ASSERT(validate_track_id(track_id)); return seg_ids_[track_id]; } bool ChanNodeDetails::is_track_start(const size_t& track_id) const { - assert(validate_track_id(track_id)); + VTR_ASSERT(validate_track_id(track_id)); return track_start_[track_id]; } bool ChanNodeDetails::is_track_end(const size_t& track_id) const { - assert(validate_track_id(track_id)); + VTR_ASSERT(validate_track_id(track_id)); return track_end_[track_id]; } @@ -81,9 +84,9 @@ bool ChanNodeDetails::is_track_end(const size_t& track_id) const { * A group size is the number of such nodes between the starting points (include the 1st starting point) */ std::vector ChanNodeDetails::get_seg_group(const size_t& track_id) const { - assert(validate_chan_width()); - assert(validate_track_id(track_id)); - assert(is_track_start(track_id)); + VTR_ASSERT(validate_chan_width()); + VTR_ASSERT(validate_track_id(track_id)); + VTR_ASSERT(is_track_start(track_id)); std::vector group; /* Make sure a clean start */ @@ -115,7 +118,7 @@ std::vector ChanNodeDetails::get_seg_group_node_id(const std::vector& track_node_ids) { /* the size of vector should match chan_width */ - assert ( get_chan_width() == track_node_ids.size() ); + VTR_ASSERT ( get_chan_width() == track_node_ids.size() ); for (size_t inode = 0; inode < track_node_ids.size(); ++inode) { track_node_ids_[inode] = track_node_ids[inode]; } @@ -227,7 +230,7 @@ void ChanNodeDetails::rotate_track_node_id(const size_t& offset, const e_directi /* Rotate the node_ids by groups * A group begins from a track_start and ends before another track_start */ - assert(validate_chan_width()); + VTR_ASSERT(validate_chan_width()); for (size_t itrack = 0; itrack < get_chan_width(); ++itrack) { /* Bypass non-start segment */ if (false == is_track_start(itrack) ) { diff --git a/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp b/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp index 34347befc..0ea35af13 100644 --- a/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp +++ b/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp @@ -633,6 +633,111 @@ void load_grid_nodes_basic_info(RRGraph& rr_graph, } } +/************************************************************************ + * Initialize the basic information of routing track rr_nodes + * coordinates: xlow, ylow, xhigh, yhigh, + * features: capacity, track_ids, ptc_num, direction + ***********************************************************************/ +static +void load_one_chan_rr_nodes_basic_info(RRGraph& rr_graph, + vtr::vector& rr_node_driver_switches, + std::map>& rr_node_track_ids, + const vtr::Point& chan_coordinate, + const t_rr_type& chan_type, + ChanNodeDetails& chan_details, + const std::vector& segment_infs, + const int& cost_index_offset) { + /* Check each node_id(potential ptc_num) in the channel : + * If this is a starting point, we set a new rr_node with xlow/ylow, ptc_num + * If this is a ending point, we set xhigh/yhigh and track_ids + * For other nodes, we set changes in track_ids + */ + for (size_t itrack = 0; itrack < chan_details.get_chan_width(); ++itrack) { + /* For INC direction, a starting point requires a new chan rr_node */ + if ( ( (true == chan_details.is_track_start(itrack)) + && (INC_DIRECTION == chan_details.get_track_direction(itrack)) ) + /* For DEC direction, an ending point requires a new chan rr_node */ + || + ( (true == chan_details.is_track_end(itrack)) + && (DEC_DIRECTION == chan_details.get_track_direction(itrack)) ) ) { + + /* Create a new chan rr_node */ + const RRNodeId& node = rr_graph.create_node(chan_type); + + rr_graph.set_node_xlow(node, chan_coordinate.x()); + rr_graph.set_node_ylow(node, chan_coordinate.y()); + + rr_graph.set_node_direction(node, chan_details.get_track_direction(itrack)); + rr_graph.set_node_track_num(node, itrack); + rr_node_track_ids[node].push_back(itrack); + + rr_graph.set_node_capacity(node, 1); + + /* assign switch id */ + size_t seg_id = chan_details.get_track_segment_id(itrack); + VTR_ASSERT(size_t(node) == rr_node_driver_switches.size()); + rr_node_driver_switches.push_back(RRSwitchId(segment_infs[seg_id].arch_opin_switch)); + + /* Update chan_details with node_id */ + chan_details.set_track_node_id(itrack, size_t(node)); + + /* cost index depends on the segment index */ + rr_graph.set_node_cost_index(node, cost_index_offset + seg_id); + /* Finish here, go to next */ + } + + /* For INC direction, an ending point requires an update on xhigh and yhigh */ + if ( ( (true == chan_details.is_track_end(itrack)) + && (INC_DIRECTION == chan_details.get_track_direction(itrack)) ) + || + /* For DEC direction, an starting point requires an update on xlow and ylow */ + ( (true == chan_details.is_track_start(itrack)) + && (DEC_DIRECTION == chan_details.get_track_direction(itrack)) ) ) { + + /* Get the node_id */ + const RRNodeId& rr_node_id = RRNodeId(chan_details.get_track_node_id(itrack)); + + /* Do a quick check, make sure we do not mistakenly modify other nodes */ + VTR_ASSERT(chan_type == rr_graph.node_type(rr_node_id)); + VTR_ASSERT(chan_details.get_track_direction(itrack) == rr_graph.node_direction(rr_node_id)); + + /* set xhigh/yhigh and push changes to track_ids */ + rr_graph.set_node_xhigh(rr_node_id, chan_coordinate.x()); + rr_graph.set_node_yhigh(rr_node_id, chan_coordinate.y()); + + /* Do not update track_ids for length-1 wires, they should have only 1 track_id */ + if ( (rr_graph.node_xhigh(rr_node_id) > rr_graph.node_xlow(rr_node_id)) + || (rr_graph.node_yhigh(rr_node_id) > rr_graph.node_ylow(rr_node_id)) ) { + rr_node_track_ids[rr_node_id].push_back(itrack); + } + /* Finish here, go to next */ + } + + /* Finish processing starting and ending tracks */ + if ( (true == chan_details.is_track_start(itrack)) + || (true == chan_details.is_track_end(itrack)) ) { + /* Finish here, go to next */ + continue; + } + + /* For other nodes, we get the node_id and just update track_ids */ + /* Ensure those nodes are neither starting nor ending points */ + VTR_ASSERT( (false == chan_details.is_track_start(itrack)) + && (false == chan_details.is_track_end(itrack)) ); + + /* Get the node_id */ + const RRNodeId& rr_node_id = RRNodeId(chan_details.get_track_node_id(itrack)); + + /* Do a quick check, make sure we do not mistakenly modify other nodes */ + VTR_ASSERT(chan_type == rr_graph.node_type(rr_node_id)); + VTR_ASSERT(chan_details.get_track_direction(itrack) == rr_graph.node_direction(rr_node_id)); + + /* Update track_ids */ + rr_node_track_ids[rr_node_id].push_back(itrack); + /* Finish here, go to next */ + } +} + /************************************************************************ * Create all the rr_nodes covering both grids and routing channels ***********************************************************************/ From 328488f357190e598c44f926d46839ce175d25e6 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 5 Mar 2020 20:15:16 -0700 Subject: [PATCH 257/645] adapt chan rr node builder to use rr_graph obj --- .../tileable_rr_graph_node_builder.cpp | 227 ++++++++++++++++++ .../tileable_rr_graph_node_builder.h | 4 + 2 files changed, 231 insertions(+) diff --git a/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp b/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp index 0ea35af13..2928ed335 100644 --- a/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp +++ b/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp @@ -738,12 +738,222 @@ void load_one_chan_rr_nodes_basic_info(RRGraph& rr_graph, } } +/************************************************************************ + * Initialize the basic information of X-channel rr_nodes: + * coordinates: xlow, ylow, xhigh, yhigh, + * features: capacity, track_ids, ptc_num, direction + * grid_info : pb_graph_pin + ***********************************************************************/ +static +void load_chanx_rr_nodes_basic_info(RRGraph& rr_graph, + vtr::vector& rr_node_driver_switches, + std::map>& rr_node_track_ids, + const DeviceGrid& grids, + const size_t& chan_width, + const std::vector& segment_infs) { + + /* For X-direction Channel: CHANX */ + for (size_t iy = 0; iy < grids.height() - 1; ++iy) { + /* Keep a vector of node_ids for the channels, because we will rotate them when walking through ix */ + std::vector track_node_ids; + + for (size_t ix = 1; ix < grids.width() - 1; ++ix) { + vtr::Point chanx_coord(ix, iy); + + /* Bypass if the routing channel does not exist */ + if (false == is_chanx_exist(grids, chanx_coord)) { + continue; + } + + bool force_start = false; + bool force_end = false; + + /* All the tracks have to start when + * - the routing channel touch the RIGHT side a heterogeneous block + * - the routing channel touch the LEFT side of FPGA + */ + if (true == is_chanx_right_to_multi_height_grid(grids, chanx_coord)) { + force_start = true; + } + + /* All the tracks have to end when + * - the routing channel touch the LEFT side a heterogeneous block + * - the routing channel touch the RIGHT side of FPGA + */ + if (true == is_chanx_left_to_multi_height_grid(grids, chanx_coord)) { + force_end = true; + } + + ChanNodeDetails chanx_details = build_unidir_chan_node_details(chan_width, grids.width() - 2, + force_start, force_end, segment_infs); + /* Force node_ids from the previous chanx */ + if (0 < track_node_ids.size()) { + /* Rotate should be done based on a typical case of routing tracks. + * Tracks on the borders are not regularly started and ended, + * which causes the node_rotation malfunction + */ + ChanNodeDetails chanx_details_tt = build_unidir_chan_node_details(chan_width, grids.width() - 2, + false, false, segment_infs); + chanx_details_tt.set_track_node_ids(track_node_ids); + + /* Rotate the chanx_details by an offset of ix - 1, the distance to the most left channel */ + /* For INC_DIRECTION, we use clockwise rotation + * node_id A ----> -----> node_id D + * node_id B ----> / ----> node_id A + * node_id C ----> / ----> node_id B + * node_id D ----> ----> node_id C + */ + chanx_details_tt.rotate_track_node_id(1, INC_DIRECTION, true); + /* For DEC_DIRECTION, we use clockwise rotation + * node_id A <----- <----- node_id B + * node_id B <----- \ <----- node_id C + * node_id C <----- \ <----- node_id D + * node_id D <----- <----- node_id A + */ + chanx_details_tt.rotate_track_node_id(1, DEC_DIRECTION, false); + + track_node_ids = chanx_details_tt.get_track_node_ids(); + chanx_details.set_track_node_ids(track_node_ids); + } + + /* Configure CHANX in this channel */ + load_one_chan_rr_nodes_basic_info(rr_graph, + rr_node_driver_switches, + rr_node_track_ids, + chanx_coord, CHANX, + chanx_details, + segment_infs, + CHANX_COST_INDEX_START); + /* Get a copy of node_ids */ + track_node_ids = chanx_details.get_track_node_ids(); + } + } +} + +/************************************************************************ + * Initialize the basic information of Y-channel rr_nodes: + * coordinates: xlow, ylow, xhigh, yhigh, + * features: capacity, track_ids, ptc_num, direction + ***********************************************************************/ +static +void load_chany_rr_nodes_basic_info(RRGraph& rr_graph, + vtr::vector& rr_node_driver_switches, + std::map>& rr_node_track_ids, + const DeviceGrid& grids, + const size_t& chan_width, + const std::vector& segment_infs) { + + /* For Y-direction Channel: CHANY */ + for (size_t ix = 0; ix < grids.width() - 1; ++ix) { + /* Keep a vector of node_ids for the channels, because we will rotate them when walking through ix */ + std::vector track_node_ids; + + for (size_t iy = 1; iy < grids.height() - 1; ++iy) { + vtr::Point chany_coord(ix, iy); + + /* Bypass if the routing channel does not exist */ + if (false == is_chany_exist(grids, chany_coord)) { + continue; + } + + bool force_start = false; + bool force_end = false; + + /* All the tracks have to start when + * - the routing channel touch the TOP side a heterogeneous block + * - the routing channel touch the BOTTOM side of FPGA + */ + if (true == is_chany_top_to_multi_width_grid(grids, chany_coord)) { + force_start = true; + } + + /* All the tracks have to end when + * - the routing channel touch the BOTTOM side a heterogeneous block + * - the routing channel touch the TOP side of FPGA + */ + if (true == is_chany_bottom_to_multi_width_grid(grids, chany_coord)) { + force_end = true; + } + + ChanNodeDetails chany_details = build_unidir_chan_node_details(chan_width, grids.height() - 2, + force_start, force_end, segment_infs); + /* Force node_ids from the previous chanx */ + if (0 < track_node_ids.size()) { + /* Rotate should be done based on a typical case of routing tracks. + * Tracks on the borders are not regularly started and ended, + * which causes the node_rotation malfunction + */ + ChanNodeDetails chany_details_tt = build_unidir_chan_node_details(chan_width, grids.height() - 2, + false, false, segment_infs); + + chany_details_tt.set_track_node_ids(track_node_ids); + /* Rotate the chany_details by an offset of 1*/ + /* For INC_DIRECTION, we use clockwise rotation + * node_id A ----> -----> node_id D + * node_id B ----> / ----> node_id A + * node_id C ----> / ----> node_id B + * node_id D ----> ----> node_id C + */ + chany_details_tt.rotate_track_node_id(1, INC_DIRECTION, true); + /* For DEC_DIRECTION, we use clockwise rotation + * node_id A <----- <----- node_id B + * node_id B <----- \ <----- node_id C + * node_id C <----- \ <----- node_id D + * node_id D <----- <----- node_id A + */ + chany_details_tt.rotate_track_node_id(1, DEC_DIRECTION, false); + + track_node_ids = chany_details_tt.get_track_node_ids(); + chany_details.set_track_node_ids(track_node_ids); + } + /* Configure CHANX in this channel */ + load_one_chan_rr_nodes_basic_info(rr_graph, + rr_node_driver_switches, + rr_node_track_ids, + chany_coord, CHANY, + chany_details, + segment_infs, + CHANX_COST_INDEX_START + segment_infs.size()); + /* Get a copy of node_ids */ + track_node_ids = chany_details.get_track_node_ids(); + } + } +} + +/************************************************************************ + * Reverse the track_ids of CHANX and CHANY nodes in DEC_DIRECTION + * This is required as the track ids are allocated in the sequence + * of incrementing x and y + * However, DEC direction routing tracks should have a reversed sequence in + * track ids + ***********************************************************************/ +static +void reverse_dec_chan_rr_node_track_ids(const RRGraph& rr_graph, + std::map>& rr_node_track_ids) { + for (const RRNodeId& node : rr_graph.nodes()) { + /* Bypass condition: only focus on CHANX and CHANY in DEC_DIRECTION */ + if ( (CHANX != rr_graph.node_type(node)) + && (CHANY != rr_graph.node_type(node)) ) { + continue; + } + /* Reach here, we must have a node of CHANX or CHANY */ + if (DEC_DIRECTION != rr_graph.node_direction(node)) { + continue; + } + std::reverse(rr_node_track_ids[node].begin(), + rr_node_track_ids[node].end() ); + } +} + /************************************************************************ * Create all the rr_nodes covering both grids and routing channels ***********************************************************************/ void create_tileable_rr_graph_nodes(RRGraph& rr_graph, vtr::vector& rr_node_driver_switches, + std::map>& rr_node_track_ids, const DeviceGrid& grids, + const vtr::Point& chan_width, + const std::vector& segment_infs, const RRSwitchId& wire_to_ipin_switch, const RRSwitchId& delayless_switch) { load_grid_nodes_basic_info(rr_graph, @@ -752,6 +962,23 @@ void create_tileable_rr_graph_nodes(RRGraph& rr_graph, wire_to_ipin_switch, delayless_switch); + load_chanx_rr_nodes_basic_info(rr_graph, + rr_node_driver_switches, + rr_node_track_ids, + grids, + chan_width.x(), + segment_infs); + + load_chany_rr_nodes_basic_info(rr_graph, + rr_node_driver_switches, + rr_node_track_ids, + grids, + chan_width.y(), + segment_infs); + + reverse_dec_chan_rr_node_track_ids(rr_graph, + rr_node_track_ids); + } } /* end namespace openfpga */ diff --git a/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.h b/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.h index 7cd8c673a..a69cd187c 100644 --- a/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.h +++ b/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.h @@ -29,10 +29,14 @@ void alloc_tileable_rr_graph_nodes(RRGraph& rr_graph, void create_tileable_rr_graph_nodes(RRGraph& rr_graph, vtr::vector& rr_node_driver_switches, + std::map>& rr_node_track_ids, const DeviceGrid& grids, + const vtr::Point& chan_width, + const std::vector& segment_infs, const RRSwitchId& wire_to_ipin_switch, const RRSwitchId& delayless_switch); + } /* end namespace openfpga */ #endif From 8d350ee22ff41af0877a9781715b3d44bacf58c5 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 5 Mar 2020 20:50:21 -0700 Subject: [PATCH 258/645] adapt tileable rr_graph edge builder to rr_graph object --- .../tileable_rr_graph_edge_builder.cpp | 169 ++++++++++++++++++ .../tileable_rr_graph_edge_builder.h | 42 +++++ .../tileable_rr_graph_gsb.cpp | 8 +- .../tileable_rr_graph/tileable_rr_graph_gsb.h | 4 +- 4 files changed, 217 insertions(+), 6 deletions(-) create mode 100644 vpr/src/tileable_rr_graph/tileable_rr_graph_edge_builder.cpp create mode 100644 vpr/src/tileable_rr_graph/tileable_rr_graph_edge_builder.h diff --git a/vpr/src/tileable_rr_graph/tileable_rr_graph_edge_builder.cpp b/vpr/src/tileable_rr_graph/tileable_rr_graph_edge_builder.cpp new file mode 100644 index 000000000..185e4923b --- /dev/null +++ b/vpr/src/tileable_rr_graph/tileable_rr_graph_edge_builder.cpp @@ -0,0 +1,169 @@ +/************************************************************************ + * This file contains functions that are used to build edges + * between nodes of a tileable routing resource graph + ***********************************************************************/ +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" +#include "vtr_time.h" + +#include "vpr_utils.h" + +#include "rr_graph_builder_utils.h" +#include "tileable_rr_graph_gsb.h" +#include "tileable_rr_graph_edge_builder.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/************************************************************************ + * Build the edges for all the SOURCE and SINKs nodes: + * 1. create edges between SOURCE and OPINs + ***********************************************************************/ +static +void build_rr_graph_edges_for_source_nodes(RRGraph& rr_graph, + const vtr::vector& rr_node_driver_switches, + const DeviceGrid& grids) { + for (const RRNodeId& node : rr_graph.nodes()) { + /* Bypass all the non OPIN nodes */ + if (OPIN != rr_graph.node_type(node)) { + continue; + } + /* Now, we have an OPIN node, we get the source node index */ + short xlow = rr_graph.node_xlow(node); + short ylow = rr_graph.node_ylow(node); + short src_node_class_num = get_grid_pin_class_index(grids[xlow][ylow], + rr_graph.node_pin_num(node)); + + /* Create edges between SOURCE and OPINs */ + const RRNodeId& src_node = rr_graph.find_node(xlow - grids[xlow][ylow].width_offset, + ylow - grids[xlow][ylow].height_offset, + SOURCE, src_node_class_num); + VTR_ASSERT(true == rr_graph.valid_node_id(src_node)); + + /* add edges to the src_node */ + rr_graph.create_edge(src_node, node, rr_node_driver_switches[node]); + } +} + +/************************************************************************ + * Build the edges for all the SINKs nodes: + * 1. create edges between IPINs and SINKs + ***********************************************************************/ +static +void build_rr_graph_edges_for_sink_nodes(RRGraph& rr_graph, + const vtr::vector& rr_node_driver_switches, + const DeviceGrid& grids) { + for (const RRNodeId& node : rr_graph.nodes()) { + /* Bypass all the non IPIN nodes */ + if (IPIN != rr_graph.node_type(node)) { + continue; + } + /* Now, we have an OPIN node, we get the source node index */ + short xlow = rr_graph.node_xlow(node); + short ylow = rr_graph.node_ylow(node); + short sink_node_class_num = get_grid_pin_class_index(grids[xlow][ylow], + rr_graph.node_pin_num(node)); + /* 1. create edges between IPINs and SINKs */ + const RRNodeId& sink_node = rr_graph.find_node(xlow - grids[xlow][ylow].width_offset, + ylow - grids[xlow][ylow].height_offset, + SINK, sink_node_class_num); + VTR_ASSERT(true == rr_graph.valid_node_id(sink_node)); + + /* add edges to connect the IPIN node to SINK nodes */ + rr_graph.create_edge(node, sink_node, rr_node_driver_switches[sink_node]); + } +} + +/************************************************************************ + * Build the edges of each rr_node tile by tile: + * We classify rr_nodes into a general switch block (GSB) data structure + * where we create edges to each rr_nodes in the GSB with respect to + * Fc_in and Fc_out, switch block patterns + * For each GSB: + * 1. create edges between CHANX | CHANY and IPINs (connections inside connection blocks) + * 2. create edges between OPINs, CHANX and CHANY (connections inside switch blocks) + * 3. create edges between OPINs and IPINs (direct-connections) + ***********************************************************************/ +void build_rr_graph_edges(RRGraph& rr_graph, + const vtr::vector& rr_node_driver_switches, + const DeviceGrid& grids, + const vtr::Point& device_chan_width, + const std::vector& segment_inf, + int** Fc_in, int** Fc_out, + const e_switch_block_type& sb_type, const int& Fs, + const e_switch_block_type& sb_subtype, const int& subFs, + const bool& wire_opposite_side) { + + /* Create edges for SOURCE and SINK nodes for a tileable rr_graph */ + build_rr_graph_edges_for_source_nodes(rr_graph, rr_node_driver_switches, grids); + build_rr_graph_edges_for_sink_nodes(rr_graph, rr_node_driver_switches, grids); + + vtr::Point gsb_range(grids.width() - 2, grids.height() - 2); + + /* Go Switch Block by Switch Block */ + for (size_t ix = 0; ix <= gsb_range.x(); ++ix) { + for (size_t iy = 0; iy <= gsb_range.y(); ++iy) { + //vpr_printf(TIO_MESSAGE_INFO, "Building edges for GSB[%lu][%lu]\n", ix, iy); + + vtr::Point gsb_coord(ix, iy); + /* Create a GSB object */ + const RRGSB& rr_gsb = build_one_tileable_rr_gsb(grids, rr_graph, + device_chan_width, segment_inf, + gsb_coord); + + /* adapt the track_to_ipin_lookup for the GSB nodes */ + t_track2pin_map track2ipin_map; /* [0..track_gsb_side][0..num_tracks][ipin_indices] */ + track2ipin_map = build_gsb_track_to_ipin_map(rr_graph, rr_gsb, grids, segment_inf, Fc_in); + + /* adapt the opin_to_track_map for the GSB nodes */ + t_pin2track_map opin2track_map; /* [0..gsb_side][0..num_opin_node][track_indices] */ + opin2track_map = build_gsb_opin_to_track_map(rr_graph, rr_gsb, grids, segment_inf, Fc_out); + + /* adapt the switch_block_conn for the GSB nodes */ + t_track2track_map sb_conn; /* [0..from_gsb_side][0..chan_width-1][track_indices] */ + sb_conn = build_gsb_track_to_track_map(rr_graph, rr_gsb, + sb_type, Fs, sb_subtype, subFs, wire_opposite_side, + segment_inf); + + /* Build edges for a GSB */ + build_edges_for_one_tileable_rr_gsb(rr_graph, rr_gsb, + track2ipin_map, opin2track_map, + sb_conn, rr_node_driver_switches); + /* Finish this GSB, go to the next*/ + } + } +} + +/************************************************************************ + * Build direct edges for Grids * + ***********************************************************************/ +void build_rr_graph_direct_connections(RRGraph& rr_graph, + const DeviceGrid& grids, + const RRSwitchId& delayless_switch, + const std::vector& directs, + const std::vector& clb_to_clb_directs) { + for (size_t ix = 0; ix < grids.width(); ++ix) { + for (size_t iy = 0; iy < grids.height(); ++iy) { + /* Skip EMPTY tiles */ + if (true == is_empty_type(grids[ix][iy].type)) { + continue; + } + /* Skip height > 1 or width > 1 tiles (mostly heterogeneous blocks) */ + if ( (0 < grids[ix][iy].width_offset) + || (0 < grids[ix][iy].height_offset) ) { + continue; + } + vtr::Point from_grid_coordinate(ix, iy); + build_direct_connections_for_one_gsb(rr_graph, + grids, + from_grid_coordinate, + delayless_switch, + directs, clb_to_clb_directs); + } + } +} + +} /* end namespace openfpga */ diff --git a/vpr/src/tileable_rr_graph/tileable_rr_graph_edge_builder.h b/vpr/src/tileable_rr_graph/tileable_rr_graph_edge_builder.h new file mode 100644 index 000000000..34c2ced24 --- /dev/null +++ b/vpr/src/tileable_rr_graph/tileable_rr_graph_edge_builder.h @@ -0,0 +1,42 @@ +#ifndef TILEABLE_RR_GRAPH_EDGE_BUILDER_H +#define TILEABLE_RR_GRAPH_EDGE_BUILDER_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include + +/* Headers from vtrutil library */ +#include "vtr_geometry.h" + +#include "physical_types.h" +#include "device_grid.h" +#include "rr_graph_obj.h" +#include "clb2clb_directs.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void build_rr_graph_edges(RRGraph& rr_graph, + const vtr::vector& rr_node_driver_switches, + const DeviceGrid& grids, + const vtr::Point& device_chan_width, + const std::vector& segment_inf, + int** Fc_in, int** Fc_out, + const e_switch_block_type& sb_type, const int& Fs, + const e_switch_block_type& sb_subtype, const int& subFs, + const bool& wire_opposite_side); + +void build_rr_graph_direct_connections(RRGraph& rr_graph, + const DeviceGrid& grids, + const RRSwitchId& delayless_switch, + const std::vector& directs, + const std::vector& clb_to_clb_directs); + +} /* end namespace openfpga */ + +#endif diff --git a/vpr/src/tileable_rr_graph/tileable_rr_graph_gsb.cpp b/vpr/src/tileable_rr_graph/tileable_rr_graph_gsb.cpp index 9cbc69dff..e2bf6e4ce 100755 --- a/vpr/src/tileable_rr_graph/tileable_rr_graph_gsb.cpp +++ b/vpr/src/tileable_rr_graph/tileable_rr_graph_gsb.cpp @@ -620,7 +620,7 @@ RRChan build_one_tileable_rr_chan(const vtr::Point& chan_coordinate, ***********************************************************************/ RRGSB build_one_tileable_rr_gsb(const DeviceGrid& grids, const RRGraph& rr_graph, - const std::vector& device_chan_width, + const vtr::Point& device_chan_width, const std::vector& segment_inf, const vtr::Point& gsb_coordinate) { /* Create an object to return */ @@ -650,9 +650,9 @@ RRGSB build_one_tileable_rr_gsb(const DeviceGrid& grids, /* Build a segment details, where we need the segment ids for building rr_chan * We do not care starting and ending points here, so set chan_side as NUM_SIDES */ - ChanNodeDetails chanx_details = build_unidir_chan_node_details(device_chan_width[0], grids.width() - 1, + ChanNodeDetails chanx_details = build_unidir_chan_node_details(device_chan_width.x(), grids.width() - 1, false, false, segment_inf); - ChanNodeDetails chany_details = build_unidir_chan_node_details(device_chan_width[1], grids.height() - 1, + ChanNodeDetails chany_details = build_unidir_chan_node_details(device_chan_width.y(), grids.height() - 1, false, false, segment_inf); switch (side) { @@ -907,7 +907,7 @@ void build_edges_for_one_tileable_rr_gsb(RRGraph& rr_graph, const t_track2pin_map& track2ipin_map, const t_pin2track_map& opin2track_map, const t_track2track_map& track2track_map, - const vtr::vector rr_node_driver_switches) { + const vtr::vector& rr_node_driver_switches) { /* Walk through each sides */ for (size_t side = 0; side < rr_gsb.get_num_sides(); ++side) { diff --git a/vpr/src/tileable_rr_graph/tileable_rr_graph_gsb.h b/vpr/src/tileable_rr_graph/tileable_rr_graph_gsb.h index 170249f74..7c7568fb9 100755 --- a/vpr/src/tileable_rr_graph/tileable_rr_graph_gsb.h +++ b/vpr/src/tileable_rr_graph/tileable_rr_graph_gsb.h @@ -44,7 +44,7 @@ t_track2track_map build_gsb_track_to_track_map(const RRGraph& rr_graph, RRGSB build_one_tileable_rr_gsb(const DeviceGrid& grids, const RRGraph& rr_graph, - const std::vector& device_chan_width, + const vtr::Point& device_chan_width, const std::vector& segment_inf, const vtr::Point& gsb_coordinate); @@ -53,7 +53,7 @@ void build_edges_for_one_tileable_rr_gsb(RRGraph& rr_graph, const t_track2pin_map& track2ipin_map, const t_pin2track_map& opin2track_map, const t_track2track_map& track2track_map, - const vtr::vector rr_node_driver_switches); + const vtr::vector& rr_node_driver_switches); t_track2pin_map build_gsb_track_to_ipin_map(const RRGraph& rr_graph, const RRGSB& rr_gsb, From 441de129366d77d218bb6d0b18642f4cea1782ad Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 6 Mar 2020 14:43:12 -0700 Subject: [PATCH 259/645] adapt Fc in gsb connection builder to use VPR8 Fc builder --- .../tileable_rr_graph_edge_builder.cpp | 3 +- .../tileable_rr_graph_edge_builder.h | 4 +- .../tileable_rr_graph_gsb.cpp | 57 +++++++++++++------ .../tileable_rr_graph/tileable_rr_graph_gsb.h | 4 +- 4 files changed, 47 insertions(+), 21 deletions(-) diff --git a/vpr/src/tileable_rr_graph/tileable_rr_graph_edge_builder.cpp b/vpr/src/tileable_rr_graph/tileable_rr_graph_edge_builder.cpp index 185e4923b..fd2d4db62 100644 --- a/vpr/src/tileable_rr_graph/tileable_rr_graph_edge_builder.cpp +++ b/vpr/src/tileable_rr_graph/tileable_rr_graph_edge_builder.cpp @@ -92,7 +92,8 @@ void build_rr_graph_edges(RRGraph& rr_graph, const DeviceGrid& grids, const vtr::Point& device_chan_width, const std::vector& segment_inf, - int** Fc_in, int** Fc_out, + const std::vector>& Fc_in, + const std::vector>& Fc_out, const e_switch_block_type& sb_type, const int& Fs, const e_switch_block_type& sb_subtype, const int& subFs, const bool& wire_opposite_side) { diff --git a/vpr/src/tileable_rr_graph/tileable_rr_graph_edge_builder.h b/vpr/src/tileable_rr_graph/tileable_rr_graph_edge_builder.h index 34c2ced24..4a643fcda 100644 --- a/vpr/src/tileable_rr_graph/tileable_rr_graph_edge_builder.h +++ b/vpr/src/tileable_rr_graph/tileable_rr_graph_edge_builder.h @@ -7,6 +7,7 @@ #include /* Headers from vtrutil library */ +#include "vtr_ndmatrix.h" #include "vtr_geometry.h" #include "physical_types.h" @@ -26,7 +27,8 @@ void build_rr_graph_edges(RRGraph& rr_graph, const DeviceGrid& grids, const vtr::Point& device_chan_width, const std::vector& segment_inf, - int** Fc_in, int** Fc_out, + const std::vector>& Fc_in, + const std::vector>& Fc_out, const e_switch_block_type& sb_type, const int& Fs, const e_switch_block_type& sb_subtype, const int& subFs, const bool& wire_opposite_side); diff --git a/vpr/src/tileable_rr_graph/tileable_rr_graph_gsb.cpp b/vpr/src/tileable_rr_graph/tileable_rr_graph_gsb.cpp index e2bf6e4ce..cca9e001c 100755 --- a/vpr/src/tileable_rr_graph/tileable_rr_graph_gsb.cpp +++ b/vpr/src/tileable_rr_graph/tileable_rr_graph_gsb.cpp @@ -966,7 +966,7 @@ void build_gsb_one_ipin_track2pin_map(const RRGraph& rr_graph, const RRGSB& rr_gsb, const enum e_side& ipin_side, const size_t& ipin_node_id, - const size_t& Fc, + const std::vector& Fc, const size_t& offset, const std::vector& segment_inf, t_track2pin_map& track2ipin_map) { @@ -995,7 +995,7 @@ void build_gsb_one_ipin_track2pin_map(const RRGraph& rr_graph, VTR_ASSERT(0 == actual_track_list.size() % 2); /* Scale Fc */ - int actual_Fc = std::ceil((float)Fc * (float)actual_track_list.size() / (float)chan_width); + int actual_Fc = std::ceil((float)Fc[iseg] * (float)actual_track_list.size() / (float)chan_width); /* Minimum Fc should be 2 : ensure we will connect to a pair of routing tracks */ actual_Fc = std::max(1, actual_Fc); /* Compute the step between two connection from this IPIN to tracks: @@ -1060,7 +1060,7 @@ void build_gsb_one_opin_pin2track_map(const RRGraph& rr_graph, const RRGSB& rr_gsb, const enum e_side& opin_side, const size_t& opin_node_id, - const size_t& Fc, + const std::vector& Fc, const size_t& offset, const std::vector& segment_inf, t_pin2track_map& opin2track_map) { @@ -1094,7 +1094,7 @@ void build_gsb_one_opin_pin2track_map(const RRGraph& rr_graph, } /* Scale Fc */ - int actual_Fc = std::ceil((float)Fc * (float)actual_track_list.size() / (float)chan_width); + int actual_Fc = std::ceil((float)Fc[iseg] * (float)actual_track_list.size() / (float)chan_width); /* Minimum Fc should be 1 : ensure we will drive 1 routing track */ actual_Fc = std::max(1, actual_Fc); /* Compute the step between two connection from this IPIN to tracks: @@ -1144,8 +1144,6 @@ void build_gsb_one_opin_pin2track_map(const RRGraph& rr_graph, } */ } - - return; } @@ -1163,7 +1161,7 @@ t_track2pin_map build_gsb_track_to_ipin_map(const RRGraph& rr_graph, const RRGSB& rr_gsb, const DeviceGrid& grids, const std::vector& segment_inf, - int** Fc_in) { + const std::vector>& Fc_in) { t_track2pin_map track2ipin_map; /* Resize the matrix */ track2ipin_map.resize(rr_gsb.get_num_sides()); @@ -1202,16 +1200,29 @@ t_track2pin_map build_gsb_track_to_ipin_map(const RRGraph& rr_graph, if (true == is_empty_type(grids[rr_graph.node_xlow(ipin_node)][rr_graph.node_ylow(ipin_node)].type)) { continue; } + int grid_type_index = grids[rr_graph.node_xlow(ipin_node)][rr_graph.node_ylow(ipin_node)].type->index; /* Get Fc of the ipin */ - int ipin_Fc = Fc_in[grid_type_index][rr_graph.node_pin_num(ipin_node)]; - /* skip Fc = 0 */ - if ( (-1 == ipin_Fc) - || (0 == ipin_Fc) ) { + /* skip Fc = 0 or unintialized, those pins are in the */ + bool skip_conn2track = true; + std::vector ipin_Fc_out; + for (size_t iseg = 0; iseg < segment_inf.size(); ++iseg) { + int ipin_Fc = Fc_in[grid_type_index][rr_graph.node_pin_num(ipin_node)][iseg]; + ipin_Fc_out.push_back(ipin_Fc); + if (0 != ipin_Fc) { + skip_conn2track = false; + break; + } + } + + if (true == skip_conn2track) { continue; } + + VTR_ASSERT(ipin_Fc_out.size() == segment_inf.size()); + /* Build track2ipin_map for this IPIN */ - build_gsb_one_ipin_track2pin_map(rr_graph, rr_gsb, ipin_side, inode, ipin_Fc, + build_gsb_one_ipin_track2pin_map(rr_graph, rr_gsb, ipin_side, inode, ipin_Fc_out, /* Give an offset for the first track that this ipin will connect to */ offset[chan_side_manager.to_size_t()], segment_inf, track2ipin_map); @@ -1240,7 +1251,7 @@ t_pin2track_map build_gsb_opin_to_track_map(const RRGraph& rr_graph, const RRGSB& rr_gsb, const DeviceGrid& grids, const std::vector& segment_inf, - int** Fc_out) { + const std::vector>& Fc_out) { t_pin2track_map opin2track_map; /* Resize the matrix */ opin2track_map.resize(rr_gsb.get_num_sides()); @@ -1269,15 +1280,27 @@ t_pin2track_map build_gsb_opin_to_track_map(const RRGraph& rr_graph, continue; } int grid_type_index = grids[rr_graph.node_xlow(opin_node)][rr_graph.node_ylow(opin_node)].type->index; + /* Get Fc of the ipin */ - int opin_Fc = Fc_out[grid_type_index][rr_graph.node_pin_num(opin_node)]; /* skip Fc = 0 or unintialized, those pins are in the */ - if ( (-1 == opin_Fc) - || (0 == opin_Fc) ) { + bool skip_conn2track = true; + std::vector opin_Fc_out; + for (size_t iseg = 0; iseg < segment_inf.size(); ++iseg) { + int opin_Fc = Fc_out[grid_type_index][rr_graph.node_pin_num(opin_node)][iseg]; + opin_Fc_out.push_back(opin_Fc); + if (0 != opin_Fc) { + skip_conn2track = false; + break; + } + } + + if (true == skip_conn2track) { continue; } + VTR_ASSERT(opin_Fc_out.size() == segment_inf.size()); + /* Build track2ipin_map for this IPIN */ - build_gsb_one_opin_pin2track_map(rr_graph, rr_gsb, opin_side, inode, opin_Fc, + build_gsb_one_opin_pin2track_map(rr_graph, rr_gsb, opin_side, inode, opin_Fc_out, /* Give an offset for the first track that this ipin will connect to */ offset[side_manager.to_size_t()], segment_inf, opin2track_map); diff --git a/vpr/src/tileable_rr_graph/tileable_rr_graph_gsb.h b/vpr/src/tileable_rr_graph/tileable_rr_graph_gsb.h index 7c7568fb9..adf6118fb 100755 --- a/vpr/src/tileable_rr_graph/tileable_rr_graph_gsb.h +++ b/vpr/src/tileable_rr_graph/tileable_rr_graph_gsb.h @@ -59,13 +59,13 @@ t_track2pin_map build_gsb_track_to_ipin_map(const RRGraph& rr_graph, const RRGSB& rr_gsb, const DeviceGrid& grids, const std::vector& segment_inf, - int** Fc_in); + const std::vector>& Fc_in); t_pin2track_map build_gsb_opin_to_track_map(const RRGraph& rr_graph, const RRGSB& rr_gsb, const DeviceGrid& grids, const std::vector& segment_inf, - int** Fc_out); + const std::vector>& Fc_out); void build_direct_connections_for_one_gsb(RRGraph& rr_graph, const DeviceGrid& grids, From 441a30710084631f5da10a07c5a253a407b1cd5c Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 6 Mar 2020 14:54:40 -0700 Subject: [PATCH 260/645] add routing chan width corrector to rr_graph builder utils --- .../tileable_rr_graph/rr_graph_builder_utils.cpp | 15 +++++++++++++++ .../tileable_rr_graph/rr_graph_builder_utils.h | 2 ++ .../tileable_chan_details_builder.cpp | 7 ++----- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/vpr/src/tileable_rr_graph/rr_graph_builder_utils.cpp b/vpr/src/tileable_rr_graph/rr_graph_builder_utils.cpp index 47926e8c7..446518faf 100644 --- a/vpr/src/tileable_rr_graph/rr_graph_builder_utils.cpp +++ b/vpr/src/tileable_rr_graph/rr_graph_builder_utils.cpp @@ -15,6 +15,21 @@ /* begin namespace openfpga */ namespace openfpga { +/************************************************************************ + * Correct number of routing channel width to be compatible to + * uni-directional routing architecture + ***********************************************************************/ +size_t find_unidir_routing_channel_width(const size_t& chan_width) { + size_t actual_chan_width = chan_width; + /* Correct the chan_width: it should be an even number */ + if (0 != actual_chan_width % 2) { + actual_chan_width++; /* increment it to be even */ + } + VTR_ASSERT(0 == actual_chan_width % 2); + + return actual_chan_width; +} + /************************************************************************ * Get the class index of a grid pin ***********************************************************************/ diff --git a/vpr/src/tileable_rr_graph/rr_graph_builder_utils.h b/vpr/src/tileable_rr_graph/rr_graph_builder_utils.h index 1367466d4..b63afa59d 100644 --- a/vpr/src/tileable_rr_graph/rr_graph_builder_utils.h +++ b/vpr/src/tileable_rr_graph/rr_graph_builder_utils.h @@ -15,6 +15,8 @@ /* begin namespace openfpga */ namespace openfpga { +size_t find_unidir_routing_channel_width(const size_t& chan_width); + int get_grid_pin_class_index(const t_grid_tile& cur_grid, const int pin_index); diff --git a/vpr/src/tileable_rr_graph/tileable_chan_details_builder.cpp b/vpr/src/tileable_rr_graph/tileable_chan_details_builder.cpp index 23c1d7c07..ec02b1bb1 100644 --- a/vpr/src/tileable_rr_graph/tileable_chan_details_builder.cpp +++ b/vpr/src/tileable_rr_graph/tileable_chan_details_builder.cpp @@ -11,6 +11,7 @@ #include "vtr_assert.h" #include "vtr_log.h" +#include "rr_graph_builder_utils.h" #include "tileable_chan_details_builder.h" /* begin namespace openfpga */ @@ -167,11 +168,7 @@ ChanNodeDetails build_unidir_chan_node_details(const size_t& chan_width, const bool& force_end, const std::vector& segment_inf) { ChanNodeDetails chan_node_details; - size_t actual_chan_width = chan_width; - /* Correct the chan_width: it should be an even number */ - if (0 != actual_chan_width % 2) { - actual_chan_width++; /* increment it to be even */ - } + size_t actual_chan_width = find_unidir_routing_channel_width(chan_width); VTR_ASSERT(0 == actual_chan_width % 2); /* Reserve channel width */ From 3eb59d201f10308870304022cbacfa65b065ae96 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 6 Mar 2020 15:24:26 -0700 Subject: [PATCH 261/645] adapt top function of tileable rr_graph builder --- vpr/src/device/rr_graph_obj.h | 12 +- vpr/src/route/check_rr_graph.h | 1 + vpr/src/route/rr_graph.cpp | 56 +-- vpr/src/route/rr_graph.h | 27 ++ .../tileable_chan_details_builder.cpp | 1 - .../tileable_chan_details_builder.h | 4 + .../tileable_rr_graph_builder.cpp | 323 ++++++++++++++++++ .../tileable_rr_graph_builder.h | 38 +++ 8 files changed, 413 insertions(+), 49 deletions(-) create mode 100644 vpr/src/tileable_rr_graph/tileable_rr_graph_builder.cpp create mode 100644 vpr/src/tileable_rr_graph/tileable_rr_graph_builder.h diff --git a/vpr/src/device/rr_graph_obj.h b/vpr/src/device/rr_graph_obj.h index 08abf682c..648934554 100644 --- a/vpr/src/device/rr_graph_obj.h +++ b/vpr/src/device/rr_graph_obj.h @@ -583,6 +583,12 @@ class RRGraph { /* Validate is the edge id does exist in the RRGraph */ bool valid_edge_id(const RREdgeId& edge) const; + /* Validate switch list */ + bool valid_switch_id(const RRSwitchId& switch_id) const; + + /* Validate segment list */ + bool valid_segment_id(const RRSegmentId& segment_id) const; + public: /* Mutators */ /* Reserve the lists of nodes, edges, switches etc. to be memory efficient. * This function is mainly used to reserve memory space inside RRGraph, @@ -835,12 +841,6 @@ class RRGraph { bool validate_edge_src_nodes() const; bool validate_edge_sink_nodes() const; - /* Validate switch list */ - bool valid_switch_id(const RRSwitchId& switch_id) const; - - /* Validate segment list */ - bool valid_segment_id(const RRSegmentId& segment_id) const; - private: /* Internal Data */ /* Node related data */ size_t num_nodes_; /* Range of node ids */ diff --git a/vpr/src/route/check_rr_graph.h b/vpr/src/route/check_rr_graph.h index 71d435e70..2416fe666 100644 --- a/vpr/src/route/check_rr_graph.h +++ b/vpr/src/route/check_rr_graph.h @@ -1,6 +1,7 @@ #ifndef CHECK_RR_GRAPH_H #define CHECK_RR_GRAPH_H #include "physical_types.h" +#include "vpr_context.h" void check_rr_graph(const t_graph_type graph_type, const DeviceGrid& grid, diff --git a/vpr/src/route/rr_graph.cpp b/vpr/src/route/rr_graph.cpp index ae6e2593e..50f2799d9 100644 --- a/vpr/src/route/rr_graph.cpp +++ b/vpr/src/route/rr_graph.cpp @@ -236,30 +236,12 @@ void uniquify_edges(t_rr_edge_info_set& rr_edges_to_create); void alloc_and_load_edges(RRGraph& rr_graph, const t_rr_edge_info_set& rr_edges_to_create); -static void alloc_and_load_rr_switch_inf(const int num_arch_switches, - const float R_minW_nmos, - const float R_minW_pmos, - const int wire_to_arch_ipin_switch, - int* wire_to_rr_ipin_switch); - -static -t_rr_switch_inf create_rr_switch_from_arch_switch(int arch_switch_idx, - const float R_minW_nmos, - const float R_minW_pmos); - static void remap_rr_node_switch_indices(const t_arch_switch_fanin& switch_fanin); static void load_rr_switch_inf(const int num_arch_switches, const float R_minW_nmos, const float R_minW_pmos, const t_arch_switch_fanin& switch_fanin); static void alloc_rr_switch_inf(t_arch_switch_fanin& switch_fanin); -static void rr_graph_externals(const std::vector& segment_inf, - int max_chan_width, - int wire_to_rr_ipin_switch, - enum e_base_cost_type base_cost_type); - -static t_clb_to_clb_directs* alloc_and_load_clb_to_clb_directs(const t_direct_inf* directs, const int num_directs, const int delayless_switch); - static void free_type_track_to_pin_map(t_track_to_pin_lookup& track_to_pin_map, const std::vector& types, int max_chan_width); @@ -267,15 +249,6 @@ static void free_type_track_to_pin_map(t_track_to_pin_lookup& track_to_pin_map, static t_seg_details* alloc_and_load_global_route_seg_details(const int global_route_switch, int* num_seg_details = nullptr); -static std::vector> alloc_and_load_actual_fc(const std::vector& types, - const int max_pins, - const std::vector& segment_inf, - const int* sets_per_seg_type, - const int max_chan_width, - const e_fc_type fc_type, - const enum e_directionality directionality, - bool* Fc_clipped); - static RRNodeId pick_best_direct_connect_target_rr_node(const RRGraph& rr_graph, const RRNodeId& from_rr, const std::vector& candidate_rr_nodes); @@ -836,7 +809,7 @@ static void build_rr_graph(const t_graph_type graph_type, * and count how many different fan-ins exist for each arch switch. * Then we create these rr switches and update the switch indices * of rr_nodes to index into the rr_switch_inf array. */ -static void alloc_and_load_rr_switch_inf(const int num_arch_switches, const float R_minW_nmos, const float R_minW_pmos, const int wire_to_arch_ipin_switch, int* wire_to_rr_ipin_switch) { +void alloc_and_load_rr_switch_inf(const int num_arch_switches, const float R_minW_nmos, const float R_minW_pmos, const int wire_to_arch_ipin_switch, int* wire_to_rr_ipin_switch) { /* we will potentially be creating a couple of versions of each arch switch where * each version corresponds to a different fan-in. We will need to fill device_ctx.rr_switch_inf * with this expanded list of switches. @@ -962,7 +935,6 @@ static void load_rr_switch_inf(const int num_arch_switches, const float R_minW_n } -static t_rr_switch_inf create_rr_switch_from_arch_switch(int arch_switch_idx, const float R_minW_nmos, const float R_minW_pmos) { @@ -1057,10 +1029,10 @@ static void remap_rr_node_switch_indices(const t_arch_switch_fanin& switch_fanin } } -static void rr_graph_externals(const std::vector& segment_inf, - int max_chan_width, - int wire_to_rr_ipin_switch, - enum e_base_cost_type base_cost_type) { +void rr_graph_externals(const std::vector& segment_inf, + int max_chan_width, + int wire_to_rr_ipin_switch, + enum e_base_cost_type base_cost_type) { auto& device_ctx = g_vpr_ctx.device(); add_rr_graph_C_from_switches(device_ctx.rr_switch_inf[wire_to_rr_ipin_switch].Cin); @@ -1152,14 +1124,14 @@ static t_seg_details* alloc_and_load_global_route_seg_details(const int global_r } /* Calculates the number of track connections from each block pin to each segment type */ -static std::vector> alloc_and_load_actual_fc(const std::vector& types, - const int max_pins, - const std::vector& segment_inf, - const int* sets_per_seg_type, - const int max_chan_width, - const e_fc_type fc_type, - const enum e_directionality directionality, - bool* Fc_clipped) { +std::vector> alloc_and_load_actual_fc(const std::vector& types, + const int max_pins, + const std::vector& segment_inf, + const int* sets_per_seg_type, + const int max_chan_width, + const e_fc_type fc_type, + const enum e_directionality directionality, + bool* Fc_clipped) { //Initialize Fc of all blocks to zero auto zeros = vtr::Matrix({size_t(max_pins), segment_inf.size()}, 0); std::vector> Fc(types.size(), zeros); @@ -2775,7 +2747,7 @@ static void build_unidir_rr_opins(const int i, const int j, * This data structure supplements the the info in the "directs" data structure * TODO: The function that does this parsing in placement is poorly done because it lacks generality on heterogeniety, should replace with this one */ -static t_clb_to_clb_directs* alloc_and_load_clb_to_clb_directs(const t_direct_inf* directs, const int num_directs, int delayless_switch) { +t_clb_to_clb_directs* alloc_and_load_clb_to_clb_directs(const t_direct_inf* directs, const int num_directs, int delayless_switch) { int i; t_clb_to_clb_directs* clb_to_clb_directs; char *tile_name, *port_name; diff --git a/vpr/src/route/rr_graph.h b/vpr/src/route/rr_graph.h index 1f60b1afc..35ddcc138 100644 --- a/vpr/src/route/rr_graph.h +++ b/vpr/src/route/rr_graph.h @@ -7,6 +7,7 @@ #define INCLUDE_TRACK_BUFFERS false #include "device_grid.h" +#include "clb2clb_directs.h" enum e_graph_type { GRAPH_GLOBAL, /* One node per channel with wire capacity > 1 and full connectivity */ @@ -56,4 +57,30 @@ void load_rr_switch_from_arch_switch(int arch_switch_idx, t_non_configurable_rr_sets identify_non_configurable_rr_sets(); +void rr_graph_externals(const std::vector& segment_inf, + int max_chan_width, + int wire_to_rr_ipin_switch, + enum e_base_cost_type base_cost_type); + +void alloc_and_load_rr_switch_inf(const int num_arch_switches, + const float R_minW_nmos, + const float R_minW_pmos, + const int wire_to_arch_ipin_switch, + int* wire_to_rr_ipin_switch); + +t_clb_to_clb_directs* alloc_and_load_clb_to_clb_directs(const t_direct_inf* directs, const int num_directs, const int delayless_switch); + +std::vector> alloc_and_load_actual_fc(const std::vector& types, + const int max_pins, + const std::vector& segment_inf, + const int* sets_per_seg_type, + const int max_chan_width, + const e_fc_type fc_type, + const enum e_directionality directionality, + bool* Fc_clipped); + +t_rr_switch_inf create_rr_switch_from_arch_switch(int arch_switch_idx, + const float R_minW_nmos, + const float R_minW_pmos); + #endif diff --git a/vpr/src/tileable_rr_graph/tileable_chan_details_builder.cpp b/vpr/src/tileable_rr_graph/tileable_chan_details_builder.cpp index ec02b1bb1..67c8dbe27 100644 --- a/vpr/src/tileable_rr_graph/tileable_chan_details_builder.cpp +++ b/vpr/src/tileable_rr_graph/tileable_chan_details_builder.cpp @@ -26,7 +26,6 @@ namespace openfpga { * therefore, we assign tracks one by one until we meet the frequency requirement * In this way, we can assign the number of tracks with repect to frequency ***********************************************************************/ -static std::vector get_num_tracks_per_seg_type(const size_t& chan_width, const std::vector& segment_inf, const bool& use_full_seg_groups) { diff --git a/vpr/src/tileable_rr_graph/tileable_chan_details_builder.h b/vpr/src/tileable_rr_graph/tileable_chan_details_builder.h index b1a1198b9..e6adb7162 100644 --- a/vpr/src/tileable_rr_graph/tileable_chan_details_builder.h +++ b/vpr/src/tileable_rr_graph/tileable_chan_details_builder.h @@ -15,6 +15,10 @@ /* begin namespace openfpga */ namespace openfpga { +std::vector get_num_tracks_per_seg_type(const size_t& chan_width, + const std::vector& segment_inf, + const bool& use_full_seg_groups); + int adapt_to_tileable_route_chan_width(const int& chan_width, const std::vector& segment_inf); ChanNodeDetails build_unidir_chan_node_details(const size_t& chan_width, diff --git a/vpr/src/tileable_rr_graph/tileable_rr_graph_builder.cpp b/vpr/src/tileable_rr_graph/tileable_rr_graph_builder.cpp new file mode 100644 index 000000000..d9f98d895 --- /dev/null +++ b/vpr/src/tileable_rr_graph/tileable_rr_graph_builder.cpp @@ -0,0 +1,323 @@ +/************************************************************************ + * This file contains a builder for the complex rr_graph data structure + * Different from VPR rr_graph builders, this builder aims to create a + * highly regular rr_graph, where each Connection Block (CB), Switch + * Block (SB) is the same (except for those on the borders). Thus, the + * rr_graph is called tileable, which brings significant advantage in + * producing large FPGA fabrics. + ***********************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_time.h" +#include "vtr_log.h" +#include "vtr_memory.h" + +#include "vpr_error.h" +#include "vpr_utils.h" + +#include "rr_graph.h" +#include "check_rr_graph.h" +#include "check_rr_graph_obj.h" + +#include "rr_graph_builder_utils.h" +#include "tileable_chan_details_builder.h" +#include "tileable_rr_graph_node_builder.h" +#include "tileable_rr_graph_edge_builder.h" +#include "tileable_rr_graph_builder.h" + +#include "globals.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/************************************************************************ + * Main function of this file + * Builder for a detailed uni-directional tileable rr_graph + * Global graph is not supported here, the VPR rr_graph generator can be used + * It follows the procedures to complete the rr_graph generation + * 1. Assign the segments for each routing channel, + * To be specific, for each routing track, we assign a routing segment. + * The assignment is subject to users' specifications, such as + * a. length of each type of segment + * b. frequency of each type of segment. + * c. routing channel width + * 2. Estimate the number of nodes in the rr_graph + * This will estimate the number of + * a. IPINs, input pins of each grid + * b. OPINs, output pins of each grid + * c. SOURCE, virtual node which drives OPINs + * d. SINK, virtual node which is connected to IPINs + * e. CHANX and CHANY, routing segments of each channel + * 3. Create the connectivity of OPINs + * a. Evenly assign connections to OPINs to routing tracks + * b. the connection pattern should be same across the fabric + * 4. Create the connectivity of IPINs + * a. Evenly assign connections from routing tracks to IPINs + * b. the connection pattern should be same across the fabric + * 5. Create the switch block patterns, + * It is based on the type of switch block, the supported patterns are + * a. Disjoint, which connects routing track (i)th from (i)th and (i)th routing segments + * b. Universal, which connects routing track (i)th from (i)th and (M-i)th routing segments + * c. Wilton, which rotates the connection of Disjoint by 1 track + * 6. Allocate rr_graph, fill the node information + * For each node, fill + * a. basic information: coordinate(xlow, xhigh, ylow, yhigh), ptc_num + * b. edges (both incoming and outcoming) + * c. handle direct-connections + * 7. Build fast look-up for the rr_graph + * 8. Allocate external data structures + * a. cost_index + * b. RC tree + ***********************************************************************/ +void build_tileable_unidir_rr_graph(const std::vector& types, + const DeviceGrid& grids, + const t_chan_width& chan_width, + const e_switch_block_type& sb_type, const int& Fs, + const e_switch_block_type& sb_subtype, const int& subFs, + const std::vector& segment_inf, + const int& delayless_switch, + const int& wire_to_arch_ipin_switch, + const float R_minW_nmos, + const float R_minW_pmos, + const enum e_base_cost_type& base_cost_type, + const t_direct_inf *directs, + const int& num_directs, + int* wire_to_rr_ipin_switch, + const bool& wire_opposite_side, + int *Warnings) { + + vtr::ScopedStartFinishTimer timer("Build tileable routing resource graph"); + + /* Reset warning flag */ + *Warnings = RR_GRAPH_NO_WARN; + + /* Create a matrix of grid */ + /* Create a vector of channel width, we support X-direction and Y-direction has different W */ + vtr::Point device_chan_width(chan_width.x_max, chan_width.y_max); + + VTR_LOG("X-direction routing channel width is %lu\n", device_chan_width.x()); + VTR_LOG("Y-direction routing channel width is %lu\n", device_chan_width.y()); + + /* Get a mutable device ctx so that we have a mutable rr_graph */ + DeviceContext& device_ctx = g_vpr_ctx.mutable_device(); + + /* The number of segments are in general small, reserve segments may not bring + * significant memory efficiency */ + device_ctx.rr_graph.reserve_segments(segment_inf.size()); + /* Create the segments */ + for (size_t iseg = 0; iseg < segment_inf.size(); ++iseg) { + device_ctx.rr_graph.create_segment(segment_inf[iseg]); + } + + /* TODO: Load architecture switch to rr_graph switches + * Draft the switches as internal data of RRGraph object + * These are temporary switches copied from arch switches + * We use them to build the edges + * We will reset all the switches in the function + * alloc_and_load_rr_switch_inf() + */ + /* TODO: Spot the switch id in the architecture switch list */ + RRSwitchId wire_to_ipin_rr_switch = RRSwitchId::INVALID(); + RRSwitchId delayless_rr_switch = RRSwitchId::INVALID(); + + device_ctx.rr_graph.reserve_switches(device_ctx.num_arch_switches); + /* Create the switches */ + for (int iswitch = 0; iswitch < device_ctx.num_arch_switches; ++iswitch) { + const t_rr_switch_inf& temp_rr_switch = create_rr_switch_from_arch_switch(iswitch, R_minW_nmos, R_minW_pmos); + RRSwitchId rr_switch = device_ctx.rr_graph.create_switch(temp_rr_switch); + if (iswitch == wire_to_arch_ipin_switch) { + wire_to_ipin_rr_switch = rr_switch; + } + if (iswitch == delayless_switch) { + delayless_rr_switch = rr_switch; + } + } + /* Validate the special switches */ + VTR_ASSERT(true == device_ctx.rr_graph.valid_switch_id(wire_to_ipin_rr_switch)); + VTR_ASSERT(true == device_ctx.rr_graph.valid_switch_id(delayless_rr_switch)); + + /* A temp data about the driver switch ids for each rr_node */ + vtr::vector rr_node_driver_switches; + + /* A temp data about the track ids for each CHANX and CHANY rr_node */ + std::map> rr_node_track_ids; + + /************************ + * Allocate the rr_nodes + ************************/ + alloc_tileable_rr_graph_nodes(device_ctx.rr_graph, + rr_node_driver_switches, + grids, + device_chan_width, + segment_inf); + + /************************ + * Create all the rr_nodes + ************************/ + create_tileable_rr_graph_nodes(device_ctx.rr_graph, + rr_node_driver_switches, + rr_node_track_ids, + grids, + device_chan_width, + segment_inf, + wire_to_ipin_rr_switch, + delayless_rr_switch); + + /************************************************************************ + * Create the connectivity of OPINs + * a. Evenly assign connections to OPINs to routing tracks + * b. the connection pattern should be same across the fabric + * + * Create the connectivity of IPINs + * a. Evenly assign connections from routing tracks to IPINs + * b. the connection pattern should be same across the fabric + ***********************************************************************/ + /* Global routing uses a single longwire track */ + int max_chan_width = find_unidir_routing_channel_width(chan_width.max); + VTR_ASSERT(max_chan_width > 0); + + /* get maximum number of pins across all blocks */ + int max_pins = types[0].num_pins; + for (const auto& type : types) { + if (is_empty_type(&type)) { + continue; + } + + if (type.num_pins > max_pins) { + max_pins = type.num_pins; + } + } + + /* Fc assignment still uses the old function from VPR. + * Should use tileable version so that we have can have full control + */ + std::vector num_tracks = get_num_tracks_per_seg_type(max_chan_width / 2, segment_inf, false); + int* sets_per_seg_type = (int*)vtr::malloc(sizeof(int) * segment_inf.size()); + VTR_ASSERT(num_tracks.size() == segment_inf.size()); + for (size_t iseg = 0; iseg < num_tracks.size(); ++iseg) { + sets_per_seg_type[iseg] = num_tracks[iseg]; + } + + bool Fc_clipped = false; + /* [0..num_types-1][0..num_pins-1] */ + std::vector> Fc_in; + Fc_in = alloc_and_load_actual_fc(types, max_pins, segment_inf, sets_per_seg_type, max_chan_width, + e_fc_type::IN, UNI_DIRECTIONAL, &Fc_clipped); + if (Fc_clipped) { + *Warnings |= RR_GRAPH_WARN_FC_CLIPPED; + } + + Fc_clipped = false; + /* [0..num_types-1][0..num_pins-1] */ + std::vector> Fc_out; + Fc_out = alloc_and_load_actual_fc(types, max_pins, segment_inf, sets_per_seg_type, max_chan_width, + e_fc_type::OUT, UNI_DIRECTIONAL, &Fc_clipped); + + if (Fc_clipped) { + *Warnings |= RR_GRAPH_WARN_FC_CLIPPED; + } + + /************************************************************************ + * Build the connections tile by tile: + * We classify rr_nodes into a general switch block (GSB) data structure + * where we create edges to each rr_nodes in the GSB with respect to + * Fc_in and Fc_out, switch block patterns + * In addition, we will also handle direct-connections: + * Add edges that bridge OPINs and IPINs to the rr_graph + ***********************************************************************/ + /* Create edges for a tileable rr_graph */ + build_rr_graph_edges(device_ctx.rr_graph, + rr_node_driver_switches, + grids, + device_chan_width, + segment_inf, + Fc_in, Fc_out, + sb_type, Fs, sb_subtype, subFs, + wire_opposite_side); + + /************************************************************************ + * Build direction connection lists + * TODO: use tile direct builder + ***********************************************************************/ + /* Create data structure of direct-connections */ + t_clb_to_clb_directs* clb_to_clb_directs = NULL; + if (num_directs > 0) { + clb_to_clb_directs = alloc_and_load_clb_to_clb_directs(directs, num_directs, delayless_switch); + } + std::vector arch_directs; + std::vector clb2clb_directs; + for (int idirect = 0; idirect < num_directs; ++idirect) { + arch_directs.push_back(directs[idirect]); + clb2clb_directs.push_back(clb_to_clb_directs[idirect]); + } + + build_rr_graph_direct_connections(device_ctx.rr_graph, grids, delayless_rr_switch, + arch_directs, clb2clb_directs); + + /* First time to build edges so that we can remap the architecture switch to rr_switch + * This is a must-do before function alloc_and_load_rr_switch_inf() + */ + device_ctx.rr_graph.rebuild_node_edges(); + + /* Allocate and load routing resource switches, which are derived from the switches from the architecture file, + * based on their fanin in the rr graph. This routine also adjusts the rr nodes to point to these new rr switches */ + alloc_and_load_rr_switch_inf(device_ctx.num_arch_switches, R_minW_nmos, R_minW_pmos, wire_to_arch_ipin_switch, wire_to_rr_ipin_switch); + + /* Save the channel widths for the newly constructed graph */ + device_ctx.chan_width = chan_width; + + /************************************************************************ + * Allocate external data structures + * a. cost_index + * b. RC tree + ***********************************************************************/ + rr_graph_externals(segment_inf, max_chan_width, + *wire_to_rr_ipin_switch, base_cost_type); + + /* Rebuild the link between RRGraph node and segments + * Should be called only AFTER the function + * rr_graph_externals() + */ + for (const RRNodeId& inode : device_ctx.rr_graph.nodes()) { + if ( (CHANX != device_ctx.rr_graph.node_type(inode)) + && (CHANY != device_ctx.rr_graph.node_type(inode)) ) { + continue; + } + short irc_data = device_ctx.rr_graph.node_cost_index(inode); + short iseg = device_ctx.rr_indexed_data[irc_data].seg_index; + device_ctx.rr_graph.set_node_segment(inode, RRSegmentId(iseg)); + } + + /************************************************************************ + * Sanitizer for the rr_graph, check connectivities of rr_nodes + ***********************************************************************/ + /* Essential check for rr_graph, build look-up and */ + if (false == device_ctx.rr_graph.validate()) { + /* Error out if built-in validator of rr_graph fails */ + vpr_throw(VPR_ERROR_ROUTE, + __FILE__, + __LINE__, + "Fundamental errors occurred when validating rr_graph object!\n"); + } + + check_rr_graph(GRAPH_UNIDIR, grids, types); + /* Error out if advanced checker of rr_graph fails */ + if (false == check_rr_graph(device_ctx.rr_graph)) { + vpr_throw(VPR_ERROR_ROUTE, + __FILE__, + __LINE__, + "Advanced checking rr_graph object fails! Routing may still work " + "but not smooth\n"); + } + + /************************************************************************ + * Free all temp stucts + ***********************************************************************/ + free(sets_per_seg_type); + + if (nullptr != clb_to_clb_directs) { + free(clb_to_clb_directs); + } +} + +} /* end namespace openfpga */ diff --git a/vpr/src/tileable_rr_graph/tileable_rr_graph_builder.h b/vpr/src/tileable_rr_graph/tileable_rr_graph_builder.h new file mode 100644 index 000000000..601c7de18 --- /dev/null +++ b/vpr/src/tileable_rr_graph/tileable_rr_graph_builder.h @@ -0,0 +1,38 @@ +#ifndef TILEABLE_RR_GRAPH_BUILDER_H +#define TILEABLE_RR_GRAPH_BUILDER_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include + +#include "physical_types.h" +#include "device_grid.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void build_tileable_unidir_rr_graph(const std::vector& types, + const DeviceGrid& grids, + const t_chan_width& chan_width, + const e_switch_block_type& sb_type, const int& Fs, + const e_switch_block_type& sb_subtype, const int& subFs, + const std::vector& segment_inf, + const int& delayless_switch, + const int& wire_to_arch_ipin_switch, + const float R_minW_nmos, + const float R_minW_pmos, + const enum e_base_cost_type& base_cost_type, + const t_direct_inf *directs, + const int& num_directs, + int* wire_to_rr_ipin_switch, + const bool& wire_opposite_side, + int *Warnings); + +} /* end namespace openfpga */ + +#endif From 245a379c4fbb27b280c84fe279d0437bb597b5d4 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 6 Mar 2020 16:03:00 -0700 Subject: [PATCH 262/645] start plug in tileable rr_graph builder --- vpr/src/base/vpr_context.h | 5 ++ vpr/src/base/vpr_types.h | 6 ++ vpr/src/route/rr_graph.cpp | 75 ++++++++++------ .../tileable_rr_graph_builder.cpp | 3 + .../tileable_rr_graph_gsb.cpp | 90 +++++++++++++++++++ 5 files changed, 153 insertions(+), 26 deletions(-) diff --git a/vpr/src/base/vpr_context.h b/vpr/src/base/vpr_context.h index 487382480..d823c921b 100644 --- a/vpr/src/base/vpr_context.h +++ b/vpr/src/base/vpr_context.h @@ -148,6 +148,11 @@ struct DeviceContext : public Context { /* RRGraph object */ RRGraph rr_graph; + /* Track ids for each rr_node in the rr_graph. + * This is used by drawer for tileable routing resource graph + */ + std::map> rr_node_track_ids; + /* Structures to define the routing architecture of the FPGA. */ std::vector rr_nodes; /* autogenerated in build_rr_graph */ diff --git a/vpr/src/base/vpr_types.h b/vpr/src/base/vpr_types.h index dee5fd7e6..009887d71 100644 --- a/vpr/src/base/vpr_types.h +++ b/vpr/src/base/vpr_types.h @@ -1004,6 +1004,12 @@ struct t_det_routing_arch { enum e_switch_block_type switch_block_type; std::vector switchblocks; + /* Xifan Tang: subtype of switch blocks. + * Sub type and Fs are applied to pass tracks + */ + int subFs; + enum e_switch_block_type switch_block_subtype; + short global_route_switch; short delayless_switch; int wire_to_arch_ipin_switch; diff --git a/vpr/src/route/rr_graph.cpp b/vpr/src/route/rr_graph.cpp index 50f2799d9..b21310ad4 100644 --- a/vpr/src/route/rr_graph.cpp +++ b/vpr/src/route/rr_graph.cpp @@ -41,6 +41,8 @@ #include "rr_graph_obj_util.h" #include "check_rr_graph_obj.h" +#include "tileable_rr_graph_builder.h" + #include "clb2clb_directs.h" //#define VERBOSE @@ -318,33 +320,54 @@ void create_rr_graph(const t_graph_type graph_type, free_rr_graph(); - build_rr_graph(graph_type, - block_types, - grid, - nodes_per_chan, - det_routing_arch->switch_block_type, - det_routing_arch->Fs, - det_routing_arch->switchblocks, - num_arch_switches, - segment_inf, - det_routing_arch->global_route_switch, - det_routing_arch->wire_to_arch_ipin_switch, - det_routing_arch->delayless_switch, - det_routing_arch->R_minW_nmos, - det_routing_arch->R_minW_pmos, - base_cost_type, - trim_empty_channels, - trim_obs_channels, - directs, num_directs, - &det_routing_arch->wire_to_rr_ipin_switch, - Warnings); + if (GRAPH_UNIDIR_TILEABLE != graph_type) { + build_rr_graph(graph_type, + block_types, + grid, + nodes_per_chan, + det_routing_arch->switch_block_type, + det_routing_arch->Fs, + det_routing_arch->switchblocks, + num_arch_switches, + segment_inf, + det_routing_arch->global_route_switch, + det_routing_arch->wire_to_arch_ipin_switch, + det_routing_arch->delayless_switch, + det_routing_arch->R_minW_nmos, + det_routing_arch->R_minW_pmos, + base_cost_type, + trim_empty_channels, + trim_obs_channels, + directs, num_directs, + &det_routing_arch->wire_to_rr_ipin_switch, + Warnings); - if (clock_modeling == DEDICATED_NETWORK) { - ClockRRGraphBuilder::create_and_append_clock_rr_graph(segment_inf, - det_routing_arch->R_minW_nmos, - det_routing_arch->R_minW_pmos, - det_routing_arch->wire_to_rr_ipin_switch, - base_cost_type); + if (clock_modeling == DEDICATED_NETWORK) { + ClockRRGraphBuilder::create_and_append_clock_rr_graph(segment_inf, + det_routing_arch->R_minW_nmos, + det_routing_arch->R_minW_pmos, + det_routing_arch->wire_to_rr_ipin_switch, + base_cost_type); + } + } else { + /* We do not support dedicated network for clocks in tileable rr_graph generation */ + openfpga::build_tileable_unidir_rr_graph(block_types, + grid, + nodes_per_chan, + det_routing_arch->switch_block_type, + det_routing_arch->Fs, + det_routing_arch->switch_block_subtype, + det_routing_arch->subFs, + segment_inf, + det_routing_arch->wire_to_arch_ipin_switch, + det_routing_arch->delayless_switch, + det_routing_arch->R_minW_nmos, + det_routing_arch->R_minW_pmos, + base_cost_type, + directs, num_directs, + &det_routing_arch->wire_to_rr_ipin_switch, + false, /* Do not allow passing tracks to be wired to the same routing channels */ + Warnings); } /* Xifan Tang - Create rr_graph object: load rr_nodes to the object */ diff --git a/vpr/src/tileable_rr_graph/tileable_rr_graph_builder.cpp b/vpr/src/tileable_rr_graph/tileable_rr_graph_builder.cpp index d9f98d895..f103fb545 100644 --- a/vpr/src/tileable_rr_graph/tileable_rr_graph_builder.cpp +++ b/vpr/src/tileable_rr_graph/tileable_rr_graph_builder.cpp @@ -266,6 +266,9 @@ void build_tileable_unidir_rr_graph(const std::vector& typ /* Save the channel widths for the newly constructed graph */ device_ctx.chan_width = chan_width; + /* Save the track ids for tileable routing resource graph */ + device_ctx.rr_node_track_ids = rr_node_track_ids; + /************************************************************************ * Allocate external data structures * a. cost_index diff --git a/vpr/src/tileable_rr_graph/tileable_rr_graph_gsb.cpp b/vpr/src/tileable_rr_graph/tileable_rr_graph_gsb.cpp index cca9e001c..b37265dfb 100755 --- a/vpr/src/tileable_rr_graph/tileable_rr_graph_gsb.cpp +++ b/vpr/src/tileable_rr_graph/tileable_rr_graph_gsb.cpp @@ -1316,4 +1316,94 @@ t_pin2track_map build_gsb_opin_to_track_map(const RRGraph& rr_graph, return opin2track_map; } +/************************************************************************ + * Add all direct clb-pin-to-clb-pin edges to given opin + ***********************************************************************/ +void build_direct_connections_for_one_gsb(RRGraph& rr_graph, + const DeviceGrid& grids, + const vtr::Point& from_grid_coordinate, + const RRSwitchId& delayless_switch, + const std::vector& directs, + const std::vector& clb_to_clb_directs) { + VTR_ASSERT(directs.size() == clb_to_clb_directs.size()); + + const t_grid_tile& from_grid = grids[from_grid_coordinate.x()][from_grid_coordinate.y()]; + t_physical_tile_type_ptr grid_type = from_grid.type; + + /* Iterate through all direct connections */ + for (size_t i = 0; i < directs.size(); ++i) { + /* Bypass unmatched direct clb-to-clb connections */ + if (grid_type != clb_to_clb_directs[i].from_clb_type) { + continue; + } + + /* This opin is specified to connect directly to an ipin, + * now compute which ipin to connect to + */ + vtr::Point to_grid_coordinate(from_grid_coordinate.x() + directs[i].x_offset, + from_grid_coordinate.y() + directs[i].y_offset); + + /* Bypass unmatched direct clb-to-clb connections */ + t_physical_tile_type_ptr to_grid_type = grids[to_grid_coordinate.x()][to_grid_coordinate.y()].type; + /* Check if to_grid if the same grid */ + if (to_grid_type != clb_to_clb_directs[i].to_clb_type) { + continue; + } + + bool swap; + int max_index, min_index; + /* Compute index of opin with regards to given pins */ + if ( clb_to_clb_directs[i].from_clb_pin_start_index + > clb_to_clb_directs[i].from_clb_pin_end_index) { + swap = true; + max_index = clb_to_clb_directs[i].from_clb_pin_start_index; + min_index = clb_to_clb_directs[i].from_clb_pin_end_index; + } else { + swap = false; + min_index = clb_to_clb_directs[i].from_clb_pin_start_index; + max_index = clb_to_clb_directs[i].from_clb_pin_end_index; + } + + /* get every opin in the range */ + for (int opin = min_index; opin <= max_index; ++opin) { + int offset = opin - min_index; + + if ( (to_grid_coordinate.x() < grids.width() - 1) + && (to_grid_coordinate.y() < grids.height() - 1) ) { + int ipin = OPEN; + if ( clb_to_clb_directs[i].to_clb_pin_start_index + > clb_to_clb_directs[i].to_clb_pin_end_index) { + if (true == swap) { + ipin = clb_to_clb_directs[i].to_clb_pin_end_index + offset; + } else { + ipin = clb_to_clb_directs[i].to_clb_pin_start_index - offset; + } + } else { + if(true == swap) { + ipin = clb_to_clb_directs[i].to_clb_pin_end_index - offset; + } else { + ipin = clb_to_clb_directs[i].to_clb_pin_start_index + offset; + } + } + + /* Get the pin index in the rr_graph */ + int from_grid_width_ofs = from_grid.width_offset; + int from_grid_height_ofs = from_grid.height_offset; + int to_grid_width_ofs = grids[to_grid_coordinate.x()][to_grid_coordinate.y()].width_offset; + int to_grid_height_ofs = grids[to_grid_coordinate.x()][to_grid_coordinate.y()].height_offset; + const RRNodeId& opin_node_id = rr_graph.find_node(from_grid_coordinate.x() - from_grid_width_ofs, + from_grid_coordinate.y() - from_grid_height_ofs, + OPIN, opin); + const RRNodeId& ipin_node_id = rr_graph.find_node(to_grid_coordinate.x() - to_grid_width_ofs, + to_grid_coordinate.y() - to_grid_height_ofs, + IPIN, ipin); + /* add edges to the opin_node */ + rr_graph.create_edge(opin_node_id, ipin_node_id, + delayless_switch); + } + } + } +} + + } /* end namespace openfpga */ From 5be118d695ed6682e6bf6e5a78a0062511cee7a2 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 6 Mar 2020 16:18:45 -0700 Subject: [PATCH 263/645] tileable rr_graph builder ready to debug --- libs/libarchfpga/src/physical_types.h | 4 ++++ libs/libarchfpga/src/read_xml_arch_file.cpp | 21 ++++++++++++++++++--- openfpga/test_vpr_arch/k6_frac_N10_40nm.xml | 4 ++-- vpr/src/base/SetupVPR.cpp | 2 ++ 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/libs/libarchfpga/src/physical_types.h b/libs/libarchfpga/src/physical_types.h index c72a57378..d1cfae62a 100644 --- a/libs/libarchfpga/src/physical_types.h +++ b/libs/libarchfpga/src/physical_types.h @@ -1589,13 +1589,17 @@ struct t_clock_arch_spec { /* Detailed routing architecture */ struct t_arch { char* architecture_id; //Secure hash digest of the architecture file to uniquely identify this architecture + + bool tileable; t_chan_width_dist Chans; enum e_switch_block_type SBType; + enum e_switch_block_type SBSubType; std::vector switchblocks; float R_minW_nmos; float R_minW_pmos; int Fs; + int subFs; float grid_logic_tile_area; std::vector Segments; t_arch_switch_inf* Switches = nullptr; diff --git a/libs/libarchfpga/src/read_xml_arch_file.cpp b/libs/libarchfpga/src/read_xml_arch_file.cpp index 32381faca..1c2bba875 100644 --- a/libs/libarchfpga/src/read_xml_arch_file.cpp +++ b/libs/libarchfpga/src/read_xml_arch_file.cpp @@ -2533,8 +2533,10 @@ static void ProcessModelPorts(pugi::xml_node port_group, t_model* model, std::se static void ProcessLayout(pugi::xml_node layout_tag, t_arch* arch, const pugiutil::loc_data& loc_data) { VTR_ASSERT(layout_tag.name() == std::string("layout")); - //Expect no attributes on - expect_only_attributes(layout_tag, {}, loc_data); + //Expect only tileable attributes on + //expect_only_attributes(layout_tag, {"tileable"}, loc_data); + + arch->tileable = get_attribute(layout_tag, "tileable", loc_data).as_bool(); //Count the number of or tags size_t auto_layout_cnt = 0; @@ -2882,7 +2884,7 @@ static void ProcessDevice(pugi::xml_node Node, t_arch* arch, t_default_fc_spec& // tag Cur = get_single_child(Node, "switch_block", loc_data); - expect_only_attributes(Cur, {"type", "fs"}, loc_data); + //expect_only_attributes(Cur, {"type", "fs", "sub_type", "sub_fs"}, loc_data); Prop = get_attribute(Cur, "type", loc_data).value(); if (strcmp(Prop, "wilton") == 0) { arch->SBType = WILTON; @@ -2898,8 +2900,21 @@ static void ProcessDevice(pugi::xml_node Node, t_arch* arch, t_default_fc_spec& "Unknown property %s for switch block type x\n", Prop); } + Prop = get_attribute(Cur, "sub_type", loc_data, BoolToReqOpt(false)).value(); + if (strcmp(Prop, "wilton") == 0) { + arch->SBSubType = WILTON; + } else if (strcmp(Prop, "universal") == 0) { + arch->SBSubType = UNIVERSAL; + } else if (strcmp(Prop, "subset") == 0) { + arch->SBSubType = SUBSET; + } else { + archfpga_throw(loc_data.filename_c_str(), loc_data.line(Cur), + "Unknown property %s for switch block subtype x\n", Prop); + } + ReqOpt CUSTOM_SWITCHBLOCK_REQD = BoolToReqOpt(!custom_switch_block); arch->Fs = get_attribute(Cur, "fs", loc_data, CUSTOM_SWITCHBLOCK_REQD).as_int(3); + arch->subFs = get_attribute(Cur, "sub_fs", loc_data, BoolToReqOpt(false)).as_int(3); Cur = get_single_child(Node, "default_fc", loc_data, ReqOpt::OPTIONAL); if (Cur) { diff --git a/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml index 44e2ef289..f5a4b31fa 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml @@ -76,7 +76,7 @@ - + @@ -110,7 +110,7 @@ - + diff --git a/vpr/src/base/SetupVPR.cpp b/vpr/src/base/SetupVPR.cpp index 4fd21004e..0cf69a9bb 100644 --- a/vpr/src/base/SetupVPR.cpp +++ b/vpr/src/base/SetupVPR.cpp @@ -309,9 +309,11 @@ static void SetupSwitches(const t_arch& Arch, static void SetupRoutingArch(const t_arch& Arch, t_det_routing_arch* RoutingArch) { RoutingArch->switch_block_type = Arch.SBType; + RoutingArch->switch_block_subtype = Arch.SBSubType; RoutingArch->R_minW_nmos = Arch.R_minW_nmos; RoutingArch->R_minW_pmos = Arch.R_minW_pmos; RoutingArch->Fs = Arch.Fs; + RoutingArch->subFs = Arch.subFs; RoutingArch->directionality = BI_DIRECTIONAL; if (Arch.Segments.size()) { RoutingArch->directionality = Arch.Segments[0].directionality; From f54f46483bcfe56808992773ac017681d7caccd3 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 6 Mar 2020 17:02:22 -0700 Subject: [PATCH 264/645] start debugging tileable rr_graph generator --- vpr/src/base/vpr_api.cpp | 5 +++++ vpr/src/route/rr_graph.cpp | 1 + 2 files changed, 6 insertions(+) diff --git a/vpr/src/base/vpr_api.cpp b/vpr/src/base/vpr_api.cpp index f2900d0d2..53ec0e732 100644 --- a/vpr/src/base/vpr_api.cpp +++ b/vpr/src/base/vpr_api.cpp @@ -829,6 +829,11 @@ void vpr_create_rr_graph(t_vpr_setup& vpr_setup, const t_arch& arch, int chan_wi graph_type = GRAPH_GLOBAL; } else { graph_type = (det_routing_arch->directionality == BI_DIRECTIONAL ? GRAPH_BIDIR : GRAPH_UNIDIR); + /* Branch on tileable routing */ + if ( (UNI_DIRECTIONAL == det_routing_arch->directionality) + && (true == arch.tileable) ) { + graph_type = GRAPH_UNIDIR_TILEABLE; + } } int warnings = 0; diff --git a/vpr/src/route/rr_graph.cpp b/vpr/src/route/rr_graph.cpp index b21310ad4..f80c450c7 100644 --- a/vpr/src/route/rr_graph.cpp +++ b/vpr/src/route/rr_graph.cpp @@ -1504,6 +1504,7 @@ void free_rr_graph() { /* Xifan Tang - Clear the rr_graph object */ device_ctx.rr_graph.clear(); + device_ctx.rr_node_track_ids.clear(); } static void build_rr_sinks_sources(const int i, From c36c3020521c62a557cf838f0f9f608f7cd9b3a9 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 6 Mar 2020 17:16:53 -0700 Subject: [PATCH 265/645] looks like tileable routing is working --- .../tileable_rr_graph/tileable_rr_graph_node_builder.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp b/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp index 2928ed335..57f98b584 100644 --- a/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp +++ b/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp @@ -376,8 +376,8 @@ void load_one_grid_opin_nodes_basic_info(RRGraph& rr_graph, /* node bounding box */ rr_graph.set_node_bounding_box(node, vtr::Rect(grid_coordinate.x() + width, - grid_coordinate.x() + width, grid_coordinate.y() + height, + grid_coordinate.x() + width, grid_coordinate.y() + height)); rr_graph.set_node_side(node, side_manager.get_side()); rr_graph.set_node_pin_num(node, pin_num); @@ -439,8 +439,8 @@ void load_one_grid_ipin_nodes_basic_info(RRGraph& rr_graph, /* node bounding box */ rr_graph.set_node_bounding_box(node, vtr::Rect(grid_coordinate.x() + width, - grid_coordinate.x() + width, grid_coordinate.y() + height, + grid_coordinate.x() + width, grid_coordinate.y() + height)); rr_graph.set_node_side(node, side_manager.get_side()); rr_graph.set_node_pin_num(node, pin_num); @@ -491,8 +491,8 @@ void load_one_grid_source_nodes_basic_info(RRGraph& rr_graph, /* node bounding box */ rr_graph.set_node_bounding_box(node, vtr::Rect(grid_coordinate.x(), - grid_coordinate.x(), grid_coordinate.y(), + grid_coordinate.x(), grid_coordinate.y())); rr_graph.set_node_class_num(node, iclass); @@ -542,8 +542,8 @@ void load_one_grid_sink_nodes_basic_info(RRGraph& rr_graph, /* node bounding box */ rr_graph.set_node_bounding_box(node, vtr::Rect(grid_coordinate.x(), - grid_coordinate.x(), grid_coordinate.y(), + grid_coordinate.x(), grid_coordinate.y())); rr_graph.set_node_class_num(node, iclass); From 37423729ec9ad92a51fa13ac7aadbe728b7b511e Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 7 Mar 2020 15:44:57 -0700 Subject: [PATCH 266/645] bug fixing for naming the duplicated pins --- openfpga/src/base/openfpga_naming.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openfpga/src/base/openfpga_naming.cpp b/openfpga/src/base/openfpga_naming.cpp index 9bfad01a2..530db8cc6 100644 --- a/openfpga/src/base/openfpga_naming.cpp +++ b/openfpga/src/base/openfpga_naming.cpp @@ -496,7 +496,7 @@ std::string generate_grid_duplicated_port_name(const size_t& width, port_name += std::to_string(width); port_name += std::string("_height_"); port_name += std::to_string(height); - port_name += std::string("_pin_"); + port_name += std::string("__pin_"); port_name += std::to_string(pin_id); port_name += std::string("_"); From e48c2b116d9007a7de15ec05d01cf59fc3046734 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 7 Mar 2020 15:46:12 -0700 Subject: [PATCH 267/645] bug fixing for duplicated grid pin names --- vpr7_x2p/vpr/SRC/fpga_x2p/base/fpga_x2p_naming.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vpr7_x2p/vpr/SRC/fpga_x2p/base/fpga_x2p_naming.cpp b/vpr7_x2p/vpr/SRC/fpga_x2p/base/fpga_x2p_naming.cpp index 64e54be65..4f1e21d80 100644 --- a/vpr7_x2p/vpr/SRC/fpga_x2p/base/fpga_x2p_naming.cpp +++ b/vpr7_x2p/vpr/SRC/fpga_x2p/base/fpga_x2p_naming.cpp @@ -484,7 +484,7 @@ std::string generate_grid_duplicated_port_name(const size_t& height, std::string port_name = std::string(side_manager.to_string()); port_name += std::string("_height_"); port_name += std::to_string(height); - port_name += std::string("_pin_"); + port_name += std::string("__pin_"); port_name += std::to_string(pin_id); port_name += std::string("_"); From ca92c2717f28b9a32629967c6bdee3db01b214d9 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 7 Mar 2020 16:00:32 -0700 Subject: [PATCH 268/645] bug fix for tile directs --- openfpga/src/tile_direct/build_tile_direct.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/openfpga/src/tile_direct/build_tile_direct.cpp b/openfpga/src/tile_direct/build_tile_direct.cpp index a99370a0b..676fa8291 100644 --- a/openfpga/src/tile_direct/build_tile_direct.cpp +++ b/openfpga/src/tile_direct/build_tile_direct.cpp @@ -203,9 +203,9 @@ vtr::Point find_inter_direct_destination_coordinate(const DeviceGrid& gr * +------+ * | Grid | ny * +------+ + * ^ . * | . * | . - * v . * +------+ * | Grid | 1 * +------+ @@ -219,14 +219,14 @@ vtr::Point find_inter_direct_destination_coordinate(const DeviceGrid& gr * +------+ * | Grid | ny * +------+ - * ^ . * | . * | . + * v . * +------+ * | Grid | 1 * +------+ */ - if (NEGATIVE_DIR == arch_direct.y_dir(arch_direct_id)) { + if (POSITIVE_DIR == arch_direct.y_dir(arch_direct_id)) { std::reverse(second_search_space.begin(), second_search_space.end()); } } @@ -279,7 +279,7 @@ vtr::Point find_inter_direct_destination_coordinate(const DeviceGrid& gr * * 1 ... nx * +------+ +------+ - * | Grid |------>| Grid | + * | Grid |<------| Grid | * +------+ +------+ */ for (size_t ix = 1 ; ix < grids.width() - 1; ++ix) { @@ -291,10 +291,10 @@ vtr::Point find_inter_direct_destination_coordinate(const DeviceGrid& gr * * 1 ... nx * +------+ +------+ - * | Grid |<------| Grid | + * | Grid |------>| Grid | * +------+ +------+ */ - if (NEGATIVE_DIR == arch_direct.x_dir(arch_direct_id)) { + if (POSITIVE_DIR == arch_direct.x_dir(arch_direct_id)) { std::reverse(second_search_space.begin(), second_search_space.end()); } } @@ -515,7 +515,7 @@ void build_inter_column_row_tile_direct(TileDirect& tile_direct, next_col_src_grid_coords.push_back(vtr::Point(ix, iy)); } /* For positive y- direction, we should start from y = 1 */ - if (POSITIVE_DIR == arch_direct.y_dir(arch_direct_id)) { + if (NEGATIVE_DIR == arch_direct.y_dir(arch_direct_id)) { std::reverse(next_col_src_grid_coords.begin(), next_col_src_grid_coords.end()); } From 0fbf3fca412a306297510ec85f26e9cc26381522 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 7 Mar 2020 23:30:55 -0700 Subject: [PATCH 269/645] start developing edge sorting inside RRGSB --- vpr/src/tileable_rr_graph/rr_gsb.cpp | 93 +++++++++++++++++++++++++++- vpr/src/tileable_rr_graph/rr_gsb.h | 61 ++++++++++++++++-- 2 files changed, 145 insertions(+), 9 deletions(-) diff --git a/vpr/src/tileable_rr_graph/rr_gsb.cpp b/vpr/src/tileable_rr_graph/rr_gsb.cpp index 5b558fffd..1390a9714 100644 --- a/vpr/src/tileable_rr_graph/rr_gsb.cpp +++ b/vpr/src/tileable_rr_graph/rr_gsb.cpp @@ -22,8 +22,13 @@ namespace openfpga { RRGSB::RRGSB() { /* Set a clean start! */ coordinate_.set(0, 0); + + chan_node_.clear(); chan_node_direction_.clear(); + chan_node_in_edges_.clear(); + ipin_node_.clear(); + opin_node_.clear(); } @@ -184,10 +189,10 @@ RRNodeId RRGSB::get_opin_node(const e_side& side, const size_t& node_id) const { VTR_ASSERT(side_manager.validate()); /* Ensure the side is valid in the context of this switch block */ - VTR_ASSERT( validate_side(side) ); + VTR_ASSERT(validate_side(side) ); /* Ensure the track is valid in the context of this switch block at a specific side */ - VTR_ASSERT( validate_opin_node_id(side, node_id) ); + VTR_ASSERT(validate_opin_node_id(side, node_id) ); return opin_node_[side_manager.to_size_t()][node_id]; } @@ -731,7 +736,9 @@ void RRGSB::init_num_sides(const size_t& num_sides) { } /* Add a node to the chan_node_ list and also assign its direction in chan_node_direction_ */ -void RRGSB::add_chan_node(const e_side& node_side, RRChan& rr_chan, const std::vector& rr_chan_dir) { +void RRGSB::add_chan_node(const e_side& node_side, + const RRChan& rr_chan, + const std::vector& rr_chan_dir) { /* Validate: 1. side is valid, the type of node is valid */ VTR_ASSERT(validate_side(node_side)); @@ -757,6 +764,86 @@ void RRGSB::add_opin_node(const RRNodeId& node, const e_side& node_side) { opin_node_[size_t(node_side)].push_back(node); } +void RRGSB::sort_chan_node_in_edges(const RRGraph& rr_graph, + const e_side& chan_side, + const size_t& track_id) { + std::map> from_grid_edge_map; + std::map> from_track_edge_map; + + const RRNodeId& chan_node = chan_node_[size_t(chan_side)].get_node(track_id); + + /* Count the edges and ensure every of them has been sorted */ + size_t edge_counter = 0; + + /* For each incoming edge, find the node side and index in this GSB. + * and cache these. Then we will use the data to sort the edge in the + * following sequence: + * 0----------------------------------------------------------------> num_in_edges() + * |<--TOP side-->|<--RIGHT side-->|<--BOTTOM side-->|<--LEFT side-->| + * For each side, the edge will be sorted by the node index starting from 0 + * For each side, the edge from grid pins will be the 1st part + * while the edge from routing tracks will be the 2nd part + */ + for (const RREdgeId& edge : rr_graph.node_in_edges(chan_node)) { + /* We care the source node of this edge, and it should be an input of the GSB!!! */ + const RRNodeId& src_node = rr_graph.edge_src_node(edge); + e_side side = NUM_SIDES; + int index = 0; + get_node_side_and_index(rr_graph, src_node, IN_PORT, side, index); + + /* Must have valid side and index */ + VTR_ASSERT(NUM_SIDES != side); + VTR_ASSERT(OPEN != index); + + if (OPIN == rr_graph.node_type(src_node)) { + from_grid_edge_map[side][index] = edge; + } else { + VTR_ASSERT( (CHANX == rr_graph.node_type(src_node)) + || (CHANY == rr_graph.node_type(src_node)) ); + from_track_edge_map[side][index] = edge; + } + + edge_counter++; + } + + /* Store the sorted edge */ + for (size_t side = 0; side < get_num_sides(); ++side) { + /* Edges from grid outputs are the 1st part */ + for (size_t opin_id = 0; opin_id < opin_node_[side].size(); ++opin_id) { + if ( (0 < from_grid_edge_map.count(side)) + && (0 < from_grid_edge_map.at(side).count(opin_id)) ) { + chan_node_in_edges_[size_t(chan_side)][track_id].push_back(from_grid_edge_map[side][opin_id]); + } + } + + /* Edges from routing tracks are the 2nd part */ + for (size_t itrack = 0; itrack < chan_node_[side].get_chan_width(); ++itrack) { + if ( (0 < from_track_edge_map.count(side)) + && (0 < from_track_edge_map.at(side).count(itrack)) ) { + chan_node_in_edges_[size_t(chan_side)][track_id].push_back(from_track_edge_map[side][itrack]); + } + } + } + + VTR_ASSERT(edge_counter == chan_node_in_edges_[size_t(chan_side)][track_id].size()); +} + +void RRGSB::sort_chan_node_in_edges(const RRGraph& rr_graph) { + /* Allocate here, as sort edge is optional, we do not allocate when adding nodes */ + chan_node_in_edges_.resize(get_num_sides()); + + for (size_t side = 0; side < get_num_sides(); ++side) { + SideManager side_manager(side); + chan_node_in_edges_[side].resize(chan_node_[side].get_chan_width()); + for (size_t track_id = 0; track_id < chan_node_[side].get_chan_width(); ++track_id) { + /* Only sort the output nodes */ + if (OUT_PORT == chan_node_direction_[side][track_id]) { + sort_chan_node_in_edges(rr_graph, side_manager.get_side(), track_id); + } + } + } +} + /************************************************************************ * Public Mutators: clean-up functions ***********************************************************************/ diff --git a/vpr/src/tileable_rr_graph/rr_gsb.h b/vpr/src/tileable_rr_graph/rr_gsb.h index 8d729139a..76633bcb2 100644 --- a/vpr/src/tileable_rr_graph/rr_gsb.h +++ b/vpr/src/tileable_rr_graph/rr_gsb.h @@ -173,12 +173,35 @@ class RRGSB { vtr::Point get_side_block_coordinate(const e_side& side) const; vtr::Point get_grid_coordinate() const; public: /* Mutators */ - void set(const RRGSB& src); /* get a copy from a source */ + /* get a copy from a source */ + void set(const RRGSB& src); void set_coordinate(const size_t& x, const size_t& y); - void init_num_sides(const size_t& num_sides); /* Allocate the vectors with the given number of sides */ - void add_chan_node(const e_side& node_side, RRChan& rr_chan, const std::vector& rr_chan_dir); /* Add a node to the chan_rr_node_ list and also assign its direction in chan_rr_node_direction_ */ - void add_ipin_node(const RRNodeId& node, const e_side& node_side); /* Add a node to the chan_rr_node_ list and also assign its direction in chan_rr_node_direction_ */ - void add_opin_node(const RRNodeId& node, const e_side& node_side); /* Add a node to the chan_rr_node_ list and also assign its direction in chan_rr_node_direction_ */ + + /* Allocate the vectors with the given number of sides */ + void init_num_sides(const size_t& num_sides); + + /* Add a node to the chan_rr_node_ list and also + * assign its direction in chan_rr_node_direction_ + */ + void add_chan_node(const e_side& node_side, + const RRChan& rr_chan, + const std::vector& rr_chan_dir); + + /* Add a node to the chan_rr_node_ list and also + * assign its direction in chan_rr_node_direction_ + */ + void add_ipin_node(const RRNodeId& node, + const e_side& node_side); + + /* Add a node to the chan_rr_node_ list and also + * assign its direction in chan_rr_node_direction_ + */ + void add_opin_node(const RRNodeId& node, + const e_side& node_side); + + /* Sort all the incoming edges for routing channel rr_node */ + void sort_chan_node_in_edges(const RRGraph& rr_graph); + public: /* Mutators: cleaners */ void clear(); @@ -193,6 +216,13 @@ class RRGSB { /* Clean chan/opin/ipin nodes at one side */ void clear_one_side(const e_side& node_side); + + private: /* Private Mutators: edge sorting */ + /* Sort all the incoming edges for one channel rr_node */ + void sort_chan_node_in_edges(const RRGraph& rr_graph, + const e_side& chan_side, + const size_t& track_id); + private: /* internal functions */ bool is_sb_node_mirror(const RRGraph& rr_graph, const RRGSB& cand, @@ -217,10 +247,29 @@ class RRGSB { private: /* Internal Data */ /* Coordinator */ vtr::Point coordinate_; - /* Routing channel data */ + + /* Routing channel data + * Each GSB may have four sides of routing track nodes + */ + /* Node id in rr_graph denoting each routing track */ std::vector chan_node_; + + /* Direction of a port when the channel node appear in the GSB module */ std::vector> chan_node_direction_; + /* Sequence of edge ids for each routing channel node, + * this is sorted by the location of edge source nodes in the context of GSB + * The edge sorting is critical to uniquify the routing modules in OpenFPGA + * This is due to that VPR allocate and sort edges randomly when building the rr_graph + * As a result, previous nodes of a chan node may be the same in different GSBs + * but their sequence is not. This will cause graph comparison to fail when uniquifying + * the routing modules. Therefore, edge sorting can be done inside the GSB + * + * Storage organization: + * [chan_side][chan_node][edge_id_in_gsb_context] + */ + std::vector>> chan_node_in_edges_; + /* Logic Block Inputs data */ std::vector> ipin_node_; From b219b096ee89ff0decf3913bc8c53512f787df07 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 8 Mar 2020 13:54:49 -0600 Subject: [PATCH 270/645] hotfix on removing dangling inputs from GSB, which are CLB direct output --- .../k6_N10_sram_chain_HC_template.xml | 5 +++++ ...k6_N10_sram_chain_HC_tileable_template.xml | 4 ++++ .../fpga_x2p/base/fpga_x2p_unique_routing.c | 8 +++++++ .../verilog/verilog_module_writer.cpp | 22 ++++++++++++++----- 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/openfpga_flow/arch/template/k6_N10_sram_chain_HC_template.xml b/openfpga_flow/arch/template/k6_N10_sram_chain_HC_template.xml index 9af75ba69..cf2b87542 100644 --- a/openfpga_flow/arch/template/k6_N10_sram_chain_HC_template.xml +++ b/openfpga_flow/arch/template/k6_N10_sram_chain_HC_template.xml @@ -499,6 +499,11 @@ 1 1 1 1 + + + + + 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 *=z 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%0D^447?Ya{b+MB$f|4W+81|A(^{5M{)Iu$lFL1g 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

e_wmf+7iZ`CHAqLmo1vZLD$z+A}R@!m^_1i|uBE zd%nURnLLSWs}ka&f3h-%xAoG{E|{^PPD}I(MYb+fL)QrV8% z>n<}NXOd(0Z{69Q?oey`HzfPaS5aIJ5|aA4Uqg$p1KxcI)kol?BR?jxJ?(cA*ZiYz z<3`4}iXrk_b|1BUSk0UuOwN8*s}TVdUSP0=xZgyi50s!bGAl#}*Q4rCSZ!1r;)zoQw+sD{+ z6W(aRkVQk~ET2OfxRMaw?V!`?oZ5rNtg(*o0~E)JMs%6^5=SZV*he?s;6s5%g; zqk2N$jKQ_~TA02OGoH(vtpZ#h(A78*vA6AZ){kqY{@c%WHLfI2C4ea@9HjI?Z>p7=> zT>PN`9s?F(ap(?FEg(~LvK%5c%k0((Y_)Bb$0UQ{uau%O3>&$%I7&%LkYzs{!}WKk zBpqqbbC5gohh+jKQerucTThxz*f*;if&V!hiywgC))lH|h4{r?62o|`5Kq(6j3l(4 zFjr+N)>HTr+zi@O5I@&NzD6zr>P?-IhT7!1pRXg@2p@q1?+}*je|w22GyI2DlBYxt z_KdoDiM%t}?si$@q;-Sd{j88^yEUZzY6l3}wlhB7Cck9SY0-jOv&&yqfTWcN3TEta ztIFwEs|ZO5`lD8k%HAK6`^+QPKbVsx&U=oHc5_HDq&}>ISx{5btRPV8)Edjd5ufB~ zcHujzw^XLj^EmuYELdI!M?&<->OcnZ?}-5{F$VcdzzhF!1A^}YleRqs{`p(cY?3El z>d|lWFR^q@_f-(X!Tb!lZX^)LN#xw7fla~0;G~?F33lra

^XoymloS751+*G8q@ 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 ON8*jFSeb~v|NjABNt~?! literal 0 HcmV?d00001 diff --git a/docs/source/figures/openfpga_motivation.png b/docs/source/figures/openfpga_motivation.png new file mode 100644 index 0000000000000000000000000000000000000000..30002f5a1f62a01cba8bc19808703f22157c594a GIT binary patch literal 279950 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

dFT|XlT`j5IhajngHkXJ#MB2!(){1HtC zj*FL^asfFD?bYtp9dvu9Ak*sy$W>6zsw;_RzYZ;Z+$KA1w1YZe`ts0jjobZ6ZC@Lt zK=s3!bAl=*QJTKB(CtoOg_pdPuvl(0!AidTW2!ar`iNJ9#7178n>jTgQ1%DDEGAbs$NhA@Q=y78DF&u=wPbRyY-#1mrKG zHQDR#8Ef5=q{?_^ld5y|MWS4seZ+r+l0EI@Fw1-8sd4{#%RO;KLE$So&!s{8@T<4_ zisWHv{ANX`W|_6i_@r^ouT0L!T|)LU0tx@@i?=f}Gdy6o%XH9ycM-G?d*TQ3qJ`Fh z>E~fBHCOC;iWx25&7ln{G8Q14AWVDF-s&ucydgWS{k31t zQ&)N4?}Ez0M%u}tSCpR#H3P_Z(ktdP#)nM__pQBg&Fl&}iP=d}6*!#H6NxLC72-;e z=h}pOv0KFB{Fy~!!C7%{haR?p!ZH8pgk5KD{ol|=GemKa+e7ALHa-433 zG{Nx3Y@LrHTF7e^QEFnmQkour>WgQ``)!B|@9w^-3q z4!t-O{GaMyoo2EuNL!kYSbL;jIE zPz|%`{_*4VH_5$)mAVJ{v4QX@pONQD5~E51B`Fn$GSXV9w{F*D+@RxviM2E4BR{H?d$|U5*~d@4HFu0g*Db$ z1iWo~7HHRAkxIvUfYv=*qhmF^MNW z>BF5$!3LN{{@r*ZaSD&1PoZzU2hEZ$99OQ)#}Y>8*!xWaO)in+^r9z$Xc>uV3!fVD zV}G5V=YQyI^BV(1Tpy(vc zWUT7~h35Fl^nPpxH2S=C- zAL|e7+}}mUhP4Vk=p2s=PC{;4%;1MhX*Y`=9RGpY7v%oKQQf42zN&2X~HIs_V9j z9oO3U#yn9H{mYB8MQkxy)o~q6*VyOkX?^->p%M>LHbGk+>2&6=^rzDn54)k696%2* z`QLhf0b^F{@P$I@Ry)^eX)_R4C<8D~td!Ke zK`8EOLJ!32!ytbTCkY$yKu zOgTp{T}!ecK;&3lYt-*5w9UCJ_pE!A>^Qda{9tMg7M9ERH%`72uf^fqy^k8^NC>@HJVLp2Dz{F8yyKB(5m)6sqUh6Gy64AI;B5 z6__V!KTo22dL`mF9{b3>S${Ke5Lr>#z+`NEdgGMVAJW*wq?lbQ=tT4S{e~ye`35)2 zOki8zlAPSBo6uZ#9H`}G0?k-|gBIhi8fnFc96?z(F!SP|Mx`HiP zI&HS1VzNCk*{g;?exy3%Ty-!X+bXEs4y=MjkKrn59VfDk@kFQ4tm!xJNxF*xX0a4s zX{}8QDY&Ikji2vjuHd%oxL6J8b|py&dp4t^qW-91!#F@otk@{OQ7o@Ko+w2LT=s2_ zvTxW-19@4{U1cECBQR%7csFJ`fe{2`=Dj;KyGB34sW(_9W|EU6CRII-Cs#Y2X7Ok_ z=m_o~b{g1UlMGq0epf|w#-$SGW4P%y14UI+SR>C zk`|1TK*#G#wngRFAZ?t;g?`>*iscIxYvXT(pRWN$B+a zQ){xtnadBWpYF${@K!xeUac>L|JoHQcc08EGIG#!ZI-RokPCrFQurc!jEqUT0y~OL zlroh9pR(@n@Ds+4Gda>1)xW!Oj1Uo`Tq+}oZW z5hR)#pTcx!Lv}J5LcV0tX85eiz%y|RJN!#Fm^SczaHvt&?=a@X()FfS3!ojPAcA^2=tsEFUA&#e92yh*FIY9aq|x*u8V+)qcr?296R#fDw`#> zjIXWHr76aqV4gHBogRPIcbG|je`;Ht#kXvwn;8MK?vOJ{Dxc{7cIi;z`%PJSRUH9d z($AwK=iBZLJ&s9;@t{u^$}@C8E>DAsV0z7Avc*C?r0%s_iW@A3`%|fWmOJ#^DI5}k zw~wgW)wkKBnC(i=cBV6ZD}Qu8r8Eo>pZPQn=xd2w5cwDri^CI>yESwy)g|f32fxslnsI z{LKL?Vwl6tXTN)T3Oc)2l8Nq!l%&2QOW!)`3%%H|7wV*KUso`fCS?1Nz?FBV?fz?! zbpX4HPBB!3qIy`UOn?O*yE5Wdm_ZO5WVvN@X71ggxdU}c#>v*OAX$Lk1ki`R3~rq* z1R=MGqvEe*8e1wU5*jDDor8GRlGC8@TsJK~^BtJSJ)-8u`b1I(7aF)_`TpJ=U3L}b zJG}nIS8@AsbkzM;ZJ|l({NCx2Mw+PkEu7N9!KJouVh``F^4!#0g?S*QsYW z-UTWja%hiRoy9yQ=fC-30jo^W!vZ7vyJO}Ha7tcjPF|4pu=AL6hYHFeqL?_KDW}Ql zZMg<-oKsG1Zgj*fpc=*dbyq&gHjLwH!Bs`jp~wpY@Z(Dvl-i(oxlp3(!+JbX$GVGC zDkRM^lEE$przQ2Tl`LUOL(YQr9hWb5`Z~MA5#JAs!n*-H(hS(Fd&-s5CYLp6Ndt0NrERV zkxd{vf&{7+EtM(j?LXT_<@JZqnQJj)DX;ZwC-RG07XEthj)}Y+E3z4MHzoQ&9}b(V zn0z;YJEn8{vU+B?CCoAoz0gN!PNu9?Bps{FH|WkGh+_Xs z$?8+ojg$0a6yvSwm0QuYZ2P}&I`BSduf&k8(b~0O^RUDhoWdTalyM&ej{APSIt9q( zw{oBYN!-J!Ha*wnqlubm;Zy#>!=A({1#NiT&w3I;)fbyBoE#|BDqqjY>V5P*+d|4InN1bY}?GQ-Gis^DVbTlj{=t z;nJ0eIGJlgykfI4p8$VuUq&UDthd@B%*zJF6)~w;G`=gR|?20TD ziT+l{;m{tO*uK$(!%jOkLE_|8L1@R#KXf3=ZQ%H!jc|z4C~v-LjF6M?3|`x1*j{B~ zLcxTYgf*yV)BAIg&ofjeanW|e`lx#m2)T^QZ3#nD-{|()xU-_lqb!gupVmG)+|8yF z^YHQ)NteQ6hH{IGiw~j){UnTAU-*i1_`|!X?_EJLU;%L=5XU`&R{>N>gDF>Jl_=7O z7&_&J(f}u9{39;R;Lrdya>2|IWVyUq7xwFGVysJRSb{g!sD+=#;5%PyAF#i`3rp4k z+=EKdtcU>yS@4Q^S#1ZQ*`Qun-e@kbZOBueWhc4Hy9Dt;1`NsZVobRNA!&;q7S<<< zK@;;1-nX5fDuYHRsmVgs>rD5RvrA{#$5c z-u5Kv;*;;d_9|4jo)VQUEzRQ1~4}w{Fn?wR?*yM zr+;q^w<$sbWb@Xg7u5=v?_+|ntFG?PPFA1?Vq%3!nPB_Pg>LnVbSlnM^FggKLpJ)0 zQu=!tS+&R?B>awp0wf&DJ*4)Kz^7xn^QQYSX9XA0gGO)2Pw%FWSUsfFIj#4z+Hqr% zZ}71IXRU>Z%n&MgUqG&oo~K;N%d-4- zG3_W%gC#ep|Ijj6>0NNjWi}v$_qT2M!-5p7JC?yfRJ(+dr^kDNYjf2XV4RfyRI^kd z5-@BfEW6Ne$|hBPsbSTtC@>5*+SziwaLQP^8);wKUO_+JWVyI9`1+DQw!?}5>5sm} z<5Z4nLgf^jvIi3;{5bNE9poW?X7~n>`uQVj5%R=Vg98N>w{0~cD1CL|YiionciDaS zPK9V;{_a+R9jo!mZw1br@gz?9aLc2)0VRyuEr0pGh`%Oc{H`CtpL3Yd7j9-2;(QMB zM-J%jrm z>M`%tm&Q28bk#6RDI~yh97kaG zu5~0z_U$2YVNDxmn^Q1ImuCQCRhY^wjk@>clvpZ)k0I_#%_`Gjd~1EgUjM_tP~vZ} z$o+)mM_zP<{NSYO7;lR#w}7WT=ov%(95)r%lq{H7SaVF;pM6NeMiVvJMDR|jTHB7! zlp%POTgzs-Pok%hBc-$)dJZrja*>eYIo8^0xs`|?qD?p{aA0B`K*K^!dXkWVcI_W5plNuiZc_^3a8LK@1Nvd&B6o0W3WW=t99u8(yt2(GmVOwsmf%tjJL(0_9t zMjpiF*CSRE^^Sw8D^%+HKbPEkPq{BBuWsWRR=vCVWvMFF5iK@jc;V|J)uvdLLlJi= zVHt_V?n0A`bg|hQ!eH{aJ6#ySv(BgJFp-!&w4aV)(vS}Md;AU6ksp4Q_?CeQBY44t zgJF$jl!^sOXoRQyKb}13ovI%^h@+Lda-h!WZAR>{2ngX!PX_xJ7gJg;=WATYJ<&m88u5GJ00EZsh4Y|8ewxs~}VY+K?Ewg$(x^ za-iBX76`ANT}cx*BAvNsDflT)`8_nxM?qD>zD_K4UC4@)F)k0jYaJiK9YiCRirr(n zL@tV}6!4TdZ2v`U>&50uH>~Z`A{2T^xyLYD@wYbq>&X7`+h%2QrwFS8V%E?NQT{l; zpVK~U`JfVHE>jUK*)U4g8{jKlEKDs~9C>GKYn|0TEGs#?)t+J+1--{Fg!93}v7`)e zgR66>b+!%&rb2D?K8VPg$C7J8$mC|=U6%jO3#C(XEWclnV4V5i!t&3Zq0ga;1RI7~ z1%o<9p~M9J${~yd&*4DA1Fyx%2g@imYwS=z3tb}AK6taw4B2Oe!OVc+-e8H`sr(|E z@!29HnU>I%XmDMG+59xOUaAS(6OA^P;1AFKTP*+FX$%)OEEIPOL;DYQe-nj&A{65A z?Vr3#KwDO|Vm>3xP-#u*HOC=I{Y6bc$n1hWXpUD8%1u2@_`_BFz1(`4rl!V5xo}lj zfHQtD3PkbfJKjGb0#sZ7>&S8okZ`41Qryk#4E}~uJ(*nplpzZ74$(}B`-I-zL%aRK zNp2gw)PdS3(a=%YqIXJt7{bsigKWknnh-dP$5sDrS^n#ifBrEX~$xJ+!=)Kc1 z%3|gD6QPE^lbD1q5Rz$lr9mnX`ABcelRr-uI}ID_*J}0L6dC4l?E3Lx)D#=V1pSA% zY>`KbkQMU~YM7MN#R6&_qjVa6p-2E=M&bvn1Zskf`VS^+msG9P?N5Z3=D=yMrG*2< zf=b^>F@Sf`9+;tl6wb#1O)T>IaOR%s&rcDmM+!QqOT>Nz$Fc4KbK1~XsPN(%WmOT@ z@*=$hR=S}w13E>$6Dxsp+6gGk0B-Kqbt|}H^Gu6=I|J(R|8+2=unS5~oH89KGD1^rzMIs4RCvJuVjh@ zKh-&F+jsGM)ML?*{_|hu3d|XNz!4q^lDE=nO!z)&FlKm!q$8nlCtNAQtKZ4MJuyHv zYiN^;;n4mxA9ZQ8D|WFb#N9yL7Xio(YGa^xv7=WxUmq--PAT{aZ$_}MS4(XD7f}Ok zsW1#qlJx*n2pXsj$DxMo%;Lw0t@PFZ)FkeRpnX9OH|mp*fB&8&3|p#{JYt*DTMJIg z`F*}LS4RVDzK;{RTU(|lPnx#MqLe!peq%1SC@BZxGW-HRmF78!3|>h4<*=Wtx6_26 z_Tj&S(f^P%3n)0~bJ@(-{1I(a_$N!=-^T+^XvqV_gF`Oo4AFvmiGen0DbZHi zlR4J6Ysj4U>Mf<0nWA*b$NIHRDwLuG(7*00q;3)Rii|y8+0@6U!#hOZQ2Qsd<~V`g zpN9s}0FIZ1aQe{d_?$M*NSQ)|l_sSAzcBC?KFOK&O_`krROS!ZVDEiIC+1EsZ(SVM zju30+zy;=Y2p>vSMsB>9TV2e17T7ElQn}J51xf{~3Nr`RYS*((%UDn>7$!CMWT}Ye z|8mc4Xl|ZlSWY)jte9$vw3YcE4#MC1^%bXmvYen zpdaqE3kfQ%|LF()Lne{51o9$Lh(4k^6x!=#DL3ybxep~zLMztw;yIn@ZMpdv%uEBw z%Vmw$RmNF$e-K&lCBF*D9;7VKNHL=A=`x&h%_n}zk{ARz6zVYVHcccsth}dRK*oMA zHyDM?+V~$}^)DIy{O%o;Labg*jXx9)VGZ*KoGNx^I|QOoLE%y4yQDh~0yF?-OIpp|3vNJCzd(c#&Pun%YYAoNyvV zW1jl?o=bX7OMJy493fYDMwT=dX}8hS6BLmp2$pIJ=YXRHSpVcX`bUlb6SB4>dUl8H zJk);49>BW}5WL?^vm6#eJY77-3q zQfqyT2p-PM0qvfZX|!o095BnFG^dI~_WK5PpPJIO?&ya@qyEn$K@(Py(17;-`|I(9 zrE8Q6L3$wCdwY+rb%%5PAI^pteYf5FkWHoH?s3gDdc1NjaY><079{vO=&Cm9BCn#( z0!>@r%%#gq6}PX1V_JH3ZTDD19@5}nj86Rlhu3EtWvPaqX~zR64%{S{`hpR9lz>z- z=ytE`)65~BJ%{Vs)%%+9LA=u@2LZmnt0+6(NPY|D5^i1-Mxd)Sr0g=AR>EO)xP@PC} z4Xk!fmd=M)Yq&H7vSXS&g;6=* zyC&74+f|z{Q{1bNy9%y!%udJBTso0jF4v{z^(jw@C06F8Sij%$OIo>ng@sTP0qpYr zbuOx~@F1{-@EDus5HtdA3sR(OSzs{Q2&ZGB#qx?A{R+_B!`Z<}U7w5xDtRf@Cno28 zmfH8}|7(#?lM^^0RbmH-Nah#k>dxtwethqKb`V6`IMP}{&z*zV2 zG_1clVHDCm%Bj{(ox`}AQ0j~@D)6F}7;_ktO#J?_hsOVH^+YOamy7vdkh?yGr zgX_6&{Vc`0d2lVC@n=1y7M7bd@AcjR6dFm3*H_gf*mV>(>}cr(A5;T9!}*+~i8c#} zCRDB2x#9&!je@Q#p&|Z%`-{09SX-S2zTPTo);ifpK~Y>gOTWn!?XdmIgqh^_a4zMY z%aa*{lW(x4(;M)P`e_*7uSr4J>I8P4)&ZpDmS%5;q3;gNUGc3X9IS=rVXGm2t&obq zOru4v^FrO^9{?vK~6Z>iQk-+FV&cmz6xo703};gfGW-7ak+a*l-9tpkrbuL=B;Ou z+-h2hd2O(kX8qcoF<3u%D@%g(#8mC$CD7Md0}I$)OwJeN3iaOv?r}F$XvBYYJ+9DA9!;C_R>Xh_ zBwrVT*l5Jik?rpMI5_necH2gGt>jE9=Q1RyX+&O`DO3!H@Q@GJulv~@urCgTO64uT zrRJE8zc%lQAPYIz-S^4Mj}y%(pRhZ$Sagzob5W)Rk=?@Qv9v)tiOaiU*Tlsy{I9U{ z4{lvy!C;nL>na61^O|vCBZITyvS#LebKaW?t1g6nkfllc^*OLIo@RyaEBxF~dJYN~ z#*OqO=kc(@?Fad)lXr9XKTcm9Bij0sqhz#>cepCiF#=q* zici%|-+Tjm)mMtd95{gfHn_7}9i^xOI(_jl`;;^Qe&7lXs(HNRifT&jJL%O{_4jqN zY}ZCvikK-jisaHKzjT#pDEe+#0KRl`E{)OE<-uJ<_|Z^WzpK%JR9se&*pB^XXD{i19p6>i=5G660P3xE5`Mcsi5y4n zcCb4u&nokYGKMyG)o30?kbWOuwJy<+iztNI!E%zab25dUClXCI%s!VKZXM0iPnXGj zK$*@DbN!p+T=Z_9d)r%CpIdUoeILD-Px{-zoV2CVr*3&P#fuWZr742MU>lvAC(S9> z5h6HEQP(~k8^8_$&3Q{hI|EA>dK2Cbta1Xm0VU%p*Bv$$i1HW?FH$HH>_HHpLyzCW zHgq~^0}O%GNrw@{5n>qrRQ`MSs{gKiLGys`;w@sd;R1t+)G5^%BbVNlURJ^{KSX^J z4FSc(4%;*sqm!@D&(9Bye*fSkaZemd(39h4iG3wyXgY$BXzDXdD6$X=V^cYYwdo7NVR>Dsm|Pt-rzEuK+NkMP05)Q zV6&EBX=ZoWGilSVoSDWOSI8JqlsN(mR7LR_RQ& zJhD$?0$S4O+a7ADf})hEYJW=~S?3vyuIAxRtll5stQz)>G{l=UMtCyp3kg`%{!&)i z6u})ms5TjIn{bqRaLV(N(vduu`kwJfyOzji5WXv3Jjyg{nT8(s{g=X&xMkTK*T3=v z5?I5re+9#B$yvdQuQJuh;jehh##h1TwJD3#BK|lPr9sy5X9^9f1w4dH&xk|5u^8gU zw>|L7`Qc{Q7{$?`^51@oz%a#Lt87JN!6_iJ)f_0qw2+{j0-N9uS(Y$rO-B}T8MwAb zX@RL0VC26@bXA{CX8&`k~uL#dRR?HTEp=O$^&`T$g z{5zxn2yfm>Qu-tAmY4Ld#{jUA3HXL~Sg|sVanxK|!+$uRC$2yKjGwB?`0=X?VWeWR zBAR=!T=MI8UL>7lPWVq(#XIk}zD-+o9EgW}W{gRmU>r;9FypmWPFCijhJA@C#6bzc ziI!IdC1sgwJFOj2C>+XYA{fCmfr0 znz=&%>I85jp{#NxO)h@-LtfL&y9*1ns#hCYWN${&yHf&&bv@;n*Y*!l9^O?{wue|Ddf}+`u zYU)9H;ger%St1E+)I<(g{xr_fj05_JhnjCY6ijFPE<~6Uwm*WNj@<=2%~V_)^60Pm z7y<_xrQ{k`_D_eONJkLS|HDgKba71 z&L>N(BXt-)7JR3B*PmeIZTo0n=d;{iPzTZd;a7{YIasGf?nXR zi>KI*Zd6C!9^G3p-Ao3M-VtpHw&RY}z&V9xBdnu0hUO%6H*opJSB{_n{YBxkcyzRZhb71Z; zMN8DDxhliWC-bNL&IJRLLo^&H0lk=@5oK2Oex!;t!$6t z&W&Tq(D)5U^hBPjWu6yYf>HE!ar5w!7! zmdG01p3pKovi0TnE}h@vD|^y&bR}9M*3qVfac}^I{#!;1vP>1mUuzN4sV7BPzn1c6UEs3HhvOFU0aJ=KhDH=1m>{!W|<#6-xB>uLRz&Z%a$;`mFTu zw$%Ea9S17au#L(unc~Z|e!+8W9Sb2Xj@QJ5`^GEecEM6>=dHo9>nw$eYL@&3Q<%eT z2yG+(jaij4t*BMVr~sL7X=RL>UlVcS7Pc1;KMjTGI%4;Q1`E=CkaBa2eK6CxA0o<( zIa5k+Z;Crrr%R(ekQ}EYGsok1M5R2Z7)=#Q-~o3SyeR> zE$=jja}@I(Ssh8?S?j_a+MkdA6;2+IvSvm*BkNNJa&O$zrFqh(^A{OSU=MXJ^l$ev z6^BrS2!XZjKW+pEVzmu1^<4T-nLFGof)Ul05=vn$X})#ulmNDeb0YjYE6=nFjaZA33T)zr3eG*k=EZ2yr4a_=pu7E}M33){rIejHQYE*?Q z`wz})Nc7NroZiTlWqwH-luXywVehR)NFo%`zg$cGzl|Qj!{{q)KX(44DdJ@e;s))s}sM-E%n=rc>@q z#+#_S8qzPuCULm;|Crd3UX>XYdwahY`cx$slR)`gny*y!t^Ag5k;)v)?bem~+yGA0 z`W(XMi*Q^mWgg3AeXB4L{B94$Q1%}IReeSeyTeP-`sp$k|9%q{=$-^v{!QsZbZCtJxj%hJ!b6@)`-3a*u zTt0RA(BNN4K5uK1y}osuWoZK69|k<39{5ghZxRSR-_TzCn1_TP|IU$DfioYsRI50E zENAq#K5}Z)H=q#*r$oe)cDPU<;`SKFq1}O-aE_O+TzSTHtN=qjRL4HMgXm-DW!Gy* z9;=+%##P8WBW_tMPvvA+#`Y7NKRFdIw5#lk2KpKdChirhjSJQVElSN>xQv)17rg%; zQ*YT8XWK+;;_d{u#@&NA9s-R7hv4oG0UCFA*WeP|-Q6WXaCdjt;eGb8XQqF_{iUy} zI#<GcM4zLd%HQOodr^Yg?<(<`tSwAq;@)!SvsNl*_%SoJlzlQqjZzpZlg!6JTQ{^1 zTv(ke2}`3GG2Z#nh(`Nk;J<%;&z)tFm=*yDyzc5lX8GaO`?X5zSUuPC@Jftpivv zDm{-a^u?KfrNqTm&&~Tco6z}DS&1~3Mw07y}hTzyl{s%VE(F@^U-y*H3K zS{nsdXow-$BiT%Uvc;WGgU4r(7UG4t)ZA}4o5rLB>romiCE@hS&#wDw8ZXDCH_3H? zxD3M~-g&m6!8SgcY6&S0SIg{`f=$rDMnWTjsxku9jQfA7 zzp34z(^A&_8?QIMJG_cXG{rPFfraaki8zoLN6#ea^6cOF<)5#rD$-P=WH0u~;Z#&x zP9s^njMux}5lSgPSyb2g`BG1@s)iV&9=Rj(V18gMJYBr`y5x6)-pY-M)JYdR6}3F^#VFh@ z-ut4fFjlNMwva0|>CdzZAG%eHv@OgRYu&C^Tm>s;2L%T7}S-V9>L4 zO&7&PswysVZ!7h*vTX1*WL$S%_%B$Dusw?73rQU0xDPLo?j4-puFXYt{;uQLyS)&$ z;q`T2nZ6@61^o}Vp;J~*I77)6Vz}Wtz8xJFuAUsKt^s#t)s1^VvX|f|TN0_k=%&ru zAY0rWP5RSqMzkSV+2^<;w=g^_zs`J+<=VjXBa7mtO`yvqE=;qGXcG{e!+?!>xf7j&-|(Y6eTTZldE!c9{OAb&w2t*(u!dHnb=*V- z&q_$`3^^6IXn;ae7{aB~)JCT<3Y`z!8{e&%sr5DriGCS}Uf27d3g3+eD($KIB*Cy> zxiEkuUqb`}e{Fv~SXjiR@Bwb*_K&Eg>!tX+#&EF%5#^v|I@tBXYKs6HCIV8Ky&tQQ z$40rY(O}kF6?5*bJ{WJbcf7e4a`i?bOb^IxT3i8RVbvDNERxeW8F@i67^}kmON&y7t0BA5b2Hu8GXZpvGs!mUU^9+n zxU~e7a3h-ZTP0+P-V#KuZ`HW}7lH~dwo&6pXzb!y4 z`-3a0k`^!N?#p}l*?6u=77exIDf1~%xw^m7bLo5aod==>_tg$_GT~4{|MU^_l>4W& z)3+=)_0RWU1Hxd!#AsyKs4bIUwJJ4nAkK2&CI{(S z`F^vpPTZgU5pV2_MQo{J`#!%=P;o@}bk;*AvqB`F5;5N)_w4TBr``b!db%VggEQqI z#Ew~E4&eRx7qKOGzHfXtt=@)|?M-j}R?_Q& zNh-VQeW1|jG;Wwv6JqxAl9&40D1$X(aMOd_g@^rUEou02se*#;iI0hZo~<%SnG<*d z1`Zx{dQGl5?)?__?YbQrQvX7qBI~VK*`n_nS5Cry)3UNTvW>cl{fh!AWoNc`g7Wf_ zy#?8)Bs*DZH##pyK)r_FKfTD56jC_&G_X-rTp=mQSA?PHMC_*pU)5i`Y;Afmq+Nhk z0DeZcThb}Qul!?&!=ob_J6WGS`1v)~1Vz3QR|Pr^@<97W0jfkXl=nRkiH0e7T!t*}^bE@1j5HiiNXY(gz|SIZGSnByq*G7N!CSCzm!S}f^_5ZIq$Nj?i^bmeUMr6o zFb33PXQiqt)wo1aNn&4ClLR%LRUiF6ilW&SVMQ3tkB(0rf>#Q#l0!Ve6|RQQ%Iw>| ztHN|e5nd=ox$+$&@f)jxgmMNySSg)dg}UNc8`;uqg*%|?C&K(?N6Di^jGw70{5 z>$jl7?1QKlk5S7 zyH`r+dJ~ZLsn(j`ImTw;;0wT2X`o9PW3+;kz<(G6o#p@|5@SOZW6(<6XxFco3P01g zf@J$KaFnuSXm(GsA0soDHYl~c>m;&tND{hk^yotFzwI4{56s{RKvb!d4T82KX{duw zpz7e3kLk^HI6tRcB#CbKYOFrj6e0&Tqes%P9wM)gcf7sxmk${PBhLgw1$_X;KT4z*T~?8Nt;gR8?Mj7|v2{)mh1pHLg(h`Tzdsq4m5Q^-0!t0z@~ zC&mZX7h8-bCO0QnA!F|WhPAp+b05<09cL>~U&qTP%szgSpC89;CtO*KbKM0X`NS&! zZ+d!V=kBBK-?Mz0)ak-p8YeRe8q@A6y-X?%+9ux&p{Le~2r1T7f}s)5SzGG9co2v{ znlxJ?wn9d+V_AIcWwRGZwpeq``~F%ONkNFXh&THFyZ$IPjn5)rRbxM*`{ zCI{C&>v_s(y!{@KnTdBPKSv;Kn7m9u-L{hOze&rfn}3gipXQaWqTSn#^a$ZEn%u@B z1+^pOttO}H`o|-Chk~2DLy=iXHaTEPDN7BXC{RyWss|s)Ky)m4ZTv7++g7UHNbFQ{ z`yFWd=vzeky1LOOj$5UNJ_8|$jyi&b;bV*LCA*moYD}b$g&jd^uSC19U007{6-iM6 z^ovl3!rA95X(0C+JjV7*N*3l~2{`WD{cNKW@#L8E^W#&^7lK1qeDv^-^i^k&8o>t& zwr$28Rl?GTKYzm_hp%>WEv%B(7OEmB>W2NiU~k2=sIEZk<${{}O9xauVK^yZIE|;< z*V^%;We~$xQE~HwztpUVAmm_Uqce!?fXVt3$*GUYPTz||TI5SX>IZLc zA~+wU4z)?H1#jpwZ6q}ZFZ~tC@XKl?ACEvn|aU*Q)oRukCf;g;bnsPtS;A2O6S&GC=xY-;z&28%h7~Y(c~UWAh**{W)5e z!dfP!{dP3qdRUxCMt(ziyJYiT?2&W;8rL9F?WXMux#VWo0Y`KKb%dIX>Ye-1xsSAA z+}Y#gDzZOru3nztcIBp7QilCi+!x`ud*QzWX&>74VpJ-QDmGE65-Yp|JKXJtu>n*0<;Oh3PK*VKI1qnPLO52 z>Ls}0F}`Fp0=dBdeRMYpg#yyWJn=00{-4>>D}-2u1{itJp3(H2Kw-&@utpMJbkp*2 zwD_~NR+i|)dBa|D#;N;A>v7mo5y~)-sDhPqZBCHLUr3_c#=CH+pCl$OB}_Kp;|pmPy}mzhRkv$ z_m@@j4%YBEpj54*15io7=xTWWz)3-3Wub0IBa*zFCqJEBi&<-2Lm=C{_LT}EJTjUi zN{Cy;*ymilb+^%pH?)61C z8|r$#)@CV2wb>ZI>YAnlK+m?ryG#Ud+H#^{C+j8GU${`j7`^vCWt?U+tp6^$O^Be1 zA^w5TZnvhpD_ZY9jvo<9DZr25NbTQZD5rqFPavK_w@(lqCA}fl^{v`=`D@ZytcxFr zWMHSrU=yxrF*BoIv|GgJa7=EPkEhPAHO7Q8IJLUG>N_1YRt1ofsx{jWZ?&5t8!VZi z#HGpT`mu9&eQVP+BHxEDHERgo?>*s-Z29iS3=x7*39E~)xS9JuE;AtNiyp32z?fCH zxiy|Izs~RgymdMvSqz5f2$lc+2M-UZg(@#h%5as7C^SBN^A{0cz1>d%wfF9u#`>#^ zg>5s&medd5;x6^zkQb3EM^0vjPDYi%PXX2$C7d|yLfcd7d!E_W@vB#~nwuJR7p*+? z2K7b@zNj-2u6QMiH;($pM02lJvB^X%&vxHUtmpZU^>$pAy$IDUD`n&X@lEL0GvObv z!$&c(4zcFnG3gMYh)&hSb7Sp|%9uz??u(SA^<3AS)0WMc#^)5qc7^mIJ_^~NGHO&9 z9Lhdbc$w`{tk+-3U9}v%>#zf?NB(>CAwjMbK3VzWX?q<9T_T`Z9XM(S(+44m#=~pu zpY>G_Uc+51Lv#~=+9)L7tW}esE|eBC8HC{YJyE5{-E`WKTcOfr&K*={8XQ7rszTzz19{ipm&}0c(Wg~g%W`+^kZTx5l^IVzlr^JYtQ|I*at29s5CQ<99 z44iab?gqEEBAhI8gacRsmMjRVp;bM&H2JfYbK1=4t1-8#>C^Q7SlCFKM!^DvmbmF)A>`WycSj zRLP)@ANakO#>X=!kXGf=QqD1f3EWP>i=5a1wui==Ptaz#Yp_c$N{%S>9?`nogx`Y4 zg1_tt4*`M|;$=5mgh;ct`eMHBPdd2ijyv^#Y&1v-LHcHCADKT-?t0Zw|%QT zg<-ewBvB`}8!QHls9!C7AhkSaW)x4;yUsESVCv0(nwzZI)@t^38zJw60Cb4gzy!~3eMS@2EG*q%9 zC38{n3FKHOl@9aD5&Ijj?GHvY20jeHlJv}7=C6#~l%g~bw2#hYmMLd2LQ=b{g%pov zEm!BiWEQF?CjJkoXVHu}t5_kT))h5V`FLx=dA05aU8}JaiWcneJ`iuR%e-qfWqw*XF|Y!%N7OjT*J8P1#n(GokJw zQ7qDH>O(X7Xa@RetZ8gvn+R$AVk89+Vjy6#I1Yh(yHv#ZC+mo4plwv-D8h3~@ONsc z`1*BKgLpz~nW?JJa6w7QUein^EPSZm_^Jc-zVF1l|F$~4k5JJ;a&l2mr4>ZG3?OE* z;V`;rFL7uDc?=v@?u$}(L{P$uTvQZ3#M|53BDiR3)z}I&8}DGZdYuPPV$j&Y;=T{M zS5b|_fNMb_@*G$QXqg9ic4PFSqvV&j6{}P9^>NI_L{%Tx{8cfkP*I(y5IXyFdwZYi zRp^6HH5vL9K+3eJ?nD)B#PG2NBTG*2KT(enPL3v)%DfHmpa8fqZqg7-R14wIut2 ztMD{%2iyj}1202WN1`A=+*!!FbwCdJ5l?T&6~}o^Dzcb*kA86uoRbBHgE;5;$5(56 zA+@yGbCnz7mD-`sdb>71vu%5Jl|++5M)3;_WidxII3aRF=cej*aGWvoDl19>VlM%Ps>}f;FO(5 z?{kT)Z&rtU^S>qQ#i3B~pmq*38ii zyKp#E8!h-dyx?R*gzln;@WX?Djl^pdo^h!Es}>vr6V(itMJEeUrf;i(yGw&SUG|bc z;C<3>x%p9e?~M7iG|1)Jmj?LY}Bt^~scR#x7%$Se1JC#s%4INqn`Rh{zwbi={Dr z?o>5m6-R)*!13gWf{#pat*pealV<4WbCje33T=z$i=12Q287W&DNYpZ4jdm;SY^Xe z!q>5j3>Qw<-Rx|D+o4pWaYkGuKU1& zN5)^(2I?_OBY^q}j!++0OZutcY64e~4Vb`6?;CP{Y)nXZR!Y;6w1 zCV@;mU{4NL0_@8c_GR(9SIzmi3D2T=$*8lt~^4lf>d;0pFg3k07eA{854*7}^gB8>2=^HygecPFB-+9!K}qo%k1`J%YU8v*zMqK0^BEm z1{Zbg_d)5#*m>>{O;|m3zMLn7ka;h*qix(|Jq(wsmhE0{_4PXH61C6keLOE@prdfa z#QM-zeb8osp%(OGo(RB00_*JaX-?Y*S7~NhNgW;!hSn)en)OmYgx`MEo&D?sVb#|; z9sF=3AQ~LN6rzNbf3j956DGU^0M*NXlFOv9a5>PZ)rQi~&-# zxtwuKL5oLUI!$m>seS4kdx!Zk$xR(+HnpTz<)FD*)*&-5>1wF>{7bq$n;#T>fUfo} z!lPpkTyiP6YI4#p^cguDc<)n@E%B!{hzn0}ZHE3P1P>Va^(gt>FQuT#2mSO?6r(rl z8+@VbEt?pMO2982io2+?OPz*L5`C17GO&Ka?s;f+YivJ1HnPP;P}SYC>-7+HcDs+d zkedv0WdYAtt40hw{3S&X&RH)`YNV0X(6BfH$+X_1FB-D?>UwZIE1xv-)HSP_(!0$2 zb8^UD8{R41Du8L6TTMbEkdPh`3OVE#>S&=>05)5@gHaC3`+8vQ_=K=QC#RmmWb*`& zaDd1GS@vm`zvQ2%)S?Ig;4Lf{jhFm)tf8UdpYE3AK7%<%X*|(q%9GyI^sYR?Xe~}$ zrZZM$o8n;e#X!6u+pWo;H&ghW5!j$hwq z{Ay`)oaA0p?f+{FSfm`-;pm6IY@|zEcl+TP<2yhL*Sb)0ci)dF{7NC_)ZfIE;?%8> zrywm=hwCQ64?Q3xAjUPy8Hy}ij&VKJ0l-=b3LOy;vyC8PMcpD4HqnG(#3xaLxqRrL zgjw3xNrP!q@Um(=gBGA`lh$((QjmJFjNz_n9H4r+Ah9TsR#@WOaKL-Q{OL5pN(HBw zG290WE042B0dkEbgksv?FlcCM#_ZBSflhjjkyJL8Kqph=TtEjOc9)e-HM-VFFijj#GHr{A_cy2!I%&uOVz zuP7bWZmyL65<%xGhupgeq)!Gykl|iF@87+zI!&bol2;{6`MlrE49op3qQ_kto%${* z8S<3lx4veU?bZ17ba-}_wASXDLAA*SEC*d*X&#@Rem!~e%_TsGl0w2|5O6t-6@J}9 zul4!xcIzWr*Izz(rSp9AKA6-oefu=dAD=2-I7vwO!H<}&0k-@hs_Sva)v}kYHIuEZ z?b*$qKy?`otn$I?=}(7w_;lT%ImmR11_5t4g`7d##L=UI537tktM&nILS;2uBqLTQ zE`bN>ZVX*AQq>y{LnPY|FMHx-&#}ZFr#1TziC)$T+kR$z77U%E!o0QU=s$w5)_N|4 z*HiiC%}v9Y2j#ZgFmjb;n|=lIY+;vf<#x<0d@ytlp&9fEfp=*x-y(!+76HpUxvJEa zL%XC)K93UpK{|mvP|1EQS~Nma9Q8YAc%h!HEgAmNkKVth^7IUXveqv2x-+^r@GNOA z-u-?EdA_0Fuv)GEE}EWZv#c3d{dd^&s4XeN;|cM0$kleYx)Sa&$bywz5`|BM4U8_eJv7uxz%00^*`t+N zPtO%CszsS7Qn_WWM-%<{TuZY=^l@OS?xA3a2W(dG7#v z3N-BTlbg&{2*}m(>59iBgB7vcQo3SnG6mY!<)iX?agjzf^m^VTk?yU>t2$pmUyw!x z`{H}oOI|tVPN_piP_cZMRqBP`hLw#~nPL1=BeA2e+QE*23)k|c-7&K9atlT=kT=K_ zMe6ktK2Drp{|omvfZu`@cK}W^)R{2oAOoHpMhe`#2|AEyhIG1Nz%Pg?$1`!L6F!Zd zVC(3w*1(Y>kIy{~2{;lx-~hq5r=z^EVCCY^?UM5HKG%|5v5QkaVszn}?-}XV{Uuj}W{=N0?E9+({uY>A0Rppw8&09pS9f;uQ(?+MqM@vb^Mx38Z zS0;704P*!%s9s85!gUf6B#sni@!S25AIv*w>cc~u$`N=}q+7FJpnRCyliEy$bg|O< zJiy_L0eQQw)$S^C99@!BjDD8roWzV}YZ0IEe(rINC;ZaI;c5Gg<(ibyTON5h zeK-;#OwHXXVHLO)h`pPnuFEb+0t%kFiF3wv8$Dsw+iK{)d7c(WPop9Y8%U5BTDtvd z!e?{6Y`pA_x_-^%b=dx4}|TQPIqF$p&!`9#=PCsFf2SjtD&eteO_-)z*K@ zoK_lbX4cl2sp#kf0s=14yi_Iy7oHCK%O@Yv&oIeudXPv5!B;xHR~FfOxIJT&k?3cT zPvh_c+5#(rm)+2gA8!wz+n7%W*{mn?pC?&;&UL6SvuHP8_A9uqo9#Bdp9WaH=~>$^ z{RvonbHCAL%FD>ql~A=4qzCqMc7%cIdAPXJOg3KkdG_)mSmGmCI~Q*k4}RX>%WVZd z?Fi6>|D1SqWIt;~?Uxy3e5b!quN%Gv%XLF{`lB02)u)p~fTQ8B)?6{Zop1wx@rPbe z043r7rW?1Lc_VIG%|OduN(4J7gj^8CpzHP`K&=UTip2M^Zk_p4E9tZl4yis$SN1$0 zSuj6#-GCN=xYXJQ%OiqIkATW?BN=`h=o-ta=9{li4|PvQQibi9iwf(roMs18e3H~U zPQCd_;Ub8iHYhYnGY0RB+e3eqysKbl+=yxLXc5O2E4;h(p!`G{C)IN)5Ku+OG#+_3 zSSH&$Sk#x7rqZV7Qe69gD-71u@3K(Y(`GaUPG6fGvNw`8Ezb@}=3(U1f@!yUO z4u!?}4q8~enS8VFxXlq-b6`CER<&FqGUNt~Z_?`ucoeDsEtXq=upHDx6V)E7lEwex zRXcgb3{z-JhvqNGOhYKCvO3457~2^&fUoFAb(*lUwB7&|j3Q9|7|PCbRN_Qs8M|)n zKlCExGEv$qh47CO-giZsW^_l9tUE0H9Vu^2%$EH>yVX%Vb=Ap3~ zYidb}995;W(H&HDD63;O7q2w0ZVmd%;s$1FhnTadxd4VC#sY>@EZ=wI?d9rJBrFRz zRB5>=$EqP!@DQX26Z?~k99XZ^ytye5Ir04Xi4!DGP?`91Yw25S1!nkQ+YQAAVF>1pWJsH`sbThS>&%Kxnf z|3!Q88p(Ypja6f}&}@f#yC0VN_ebUXRs*tQQXsm6Ofx$v&_hs_ks6M5j-{POdqsqU zpT3H~Ktpu_Am}BCRpuG?{T*3&0BtUtK%f)jBl)fueTF8n59K#8k4$|w#-EoQ z;6o0HFpBX(xYfQHndCr5ki5(xm#}{b`&;PHI0l@`WZcOfF$nDjl@+Vcb1ggYk3^#; z_o-y%%|CU$r0mz-;?x)4xrK#{Z)b{<4e19Lf!NVJ!Z)|I2X4k5%SJ$R_hydM?oO|l z@x>XQ4bM8_Paw7acJ7)10}RtO43(!GR=&}r0yAGu(b;eHw0mtkwBuj;%F(!A>MTe6 z{OeJ(-vLl~xl)7N7Tqz3A%bBnf~*nB=e;I2y6H+(XBy<(d<&0-MloR{Jn1ua6Mu|$ z1R1?aV?1!-lUk*%7xH=6nq=15%8r4<`5PP%@OQSgqRJR72TeS&?uB$!GAzr#Ak_EK zIon3WS314C9-SUQLyG^Vc>!h%C<$-Xf2^%{aBuyzmOztcoj(rHffD?YLdwoO!y)1;w{|~ZMbElr&(j2WyixG^wr1xjj4i} z_Zk=rE-XU_T|iIM=Dg(GfL?$8AF9TJ@AenxGra`ysVicg`lIdw6T^cwtgThe^HSwR z2uN3@N05AxrB44Ix3f33{%l&J0awfQRxyt5SOsK-cZm$Q6K-Gj3^9S(-C)xliwc;t zg5WzuTaY$!jY4S;47J;WjIDqd(fqnS)T!={)q-9-%;iH2g9@ zr{b4c9*5aJe;&Os(kU2XO!t=-CqHTi=R7`u3qab=&UeTUn7Hf*wjbau9>x_mlohf@ zreG6>Py;g)F~Pxi=tA@Xijh#YoLG>M{@|j2|C}8$)_*rZ_hEU|$dsaU3^MBOkrBYO z;3Pe#Yl4VRq0lbx*Ep#H#NWWE_$r8@yO!3esuwoN&>pB)FAJlcI`B1Ja>NDpLB#kF zaB={Vnq)7)w-gj2r53w3lmKfT?Io&CxTe$PPCdg8XO2bW$WKL6#F%U!2X={R5m1gB zh(25sAgJ`OIaIsqm(2yd0hFDj9;?(EdH=Ew%UV^>*up;mbQC7ynI}P&3J*yB0kD0- z9VTEZr2{KeyQ?AkC*T5-oHTBMxg@?7rdQIpT<6Pv^a0o(pDcN~R5O;Jl4B-<58&Ca zYWI<6-MWut(Ruw-LwBQIUkG_thYpc9B2cTvv9MgHZJr9`MmCsQysCX9B|G){y#`@f zJOnK*kOCyREOh)QpMwwpX^v>rQp?=I_7wi%2MsTa_4oSpbHCnc7xvud1Ubfk@fy>b{0s;Fd@h!$EV6yxVf*6*sk3tj8@7IqC9`nq{cXC09j9kcaSsHV zYDIs{Da)sA5%cP~lWe%G*oeJy+pZ#9sJ!z*b6E&EfYNex;&V|_F3a@ZkP2+jLshE? z$eJoDP}$Api6VS5_{k(EId7HzROe4VUaP!2Cu4tq@7HG;##2Fx_#IDMf-0KFB=WT=ngG(G0L}B9>>~7LJwg z;y@E@cZWWc@{nuX4ibC1KI+N!Q(59axK(ZAm}~{fJcAq>gER`1A`ndlZ>^e6!G0>HF3s zyV0%UPI!U4^oscB4l!N&fYQbcxr~x?dQMwZ1hmlNSoA9k8Qu>gd3oz+CD{{1;t=XI z#IM$w=;#IU&jqZf5Qs4LV-l0<`W^O=3sJqGPL#FuA3L$C>xX}q_J&im8oz*3diPcA z^B1muTl|*pYFVED-)iSd4*8*fEIfA1Nb-El2s*bgUAAVUTy&r9YAz%=&7Un+kmV`6 zYY9LGezQD^*>)`NDLcQUxT!|V6#XLHMB09!(QOwF>x&6_zNcMR=)H=~b5-CPUHxHts7l1U*r3@G2Wjk9rrb=mhndBRX zk0qI%?{sdakBS%t2wn;eD#0(+e1DJ3Mx}gylAb>1etk*-&0Y`8Y|IA9b$>VhD1+b2 zC+j5sHgQqV_L-B_s3zS^nu{5X3)&NjYOW4jggwxq^LhNIb_62E*a#;q(Wp_uTDf$; z(B}$WDbT)*=G-e&e=8$)zLp06oA)!nm)^+LJz8!-&vGN8Mw_`?FSd*4s?wra>sy z7vDfzp@)$jAchOViYXu)5g3%CUV8crGEXnq*ohGq+Q+n{eW4+W=_dgRcCP=pj%C-} zc<6j6Y^PQny5c~5iQ5#sn_C#W@O}`$C=A)$drs$rA5{GvZjUl1lYC|P#Q*j=Xq&mO z)JBiCI3$#-rO|r5m7<`VePBmSyk5odPy!)o%pQqn;Wtx0!2d8oy6!MpzR3Q2CcyTl*QTZa6nBX4mK1!7vLB6NX5%i1kPT* z)Tt)X5`SxxaVuK{DbcU$PAJPD{kjTcsEvUdcL(Ye%EWGtlHi5*lnnE&9t?Vo$%vDT zi@ZQ9A+yKPm8;UU7ej3lv;3!{W-+U6eGxDL#|&m7ADa|`{_=8!}SwR{@h$?TZ>yL zr%>R9BJ+iQWYw!*xuTp!+`A+6pY*VBTu8HPCT+;W@e_0z8w1?MJAmC-#I5JZn^3;( z)LwMo7;ZMaKf^(H9d9MU{$M$h>wT%!4A#R>^qUV=sp^e*ooW~!W=R1o1AvVAfX7Ss zcY?KA!++9lZ}Gk;8zCv?JE}9vM+u+#nUT+EBijiWM9y+b^A8;$chWGa_7YN?bmLw9 z?B?Y1L>%U!zF9`9+_Ob(#%MN3);^+HSo!f$q5inDq-5tPr5hyOGaomcRcpdR?>&a4 z^(Gz56JYHhg7u6O!I@^PE~=(%CK4Z-wg2k{u+>uy!nnG#9@N&k!kYlEf#b?Kf5hc( zw3n z^v1u$ITN4&#TDwdLid8<++K=B+6-88xp0$<4sI%B^;^j9Lwp*Xs!GD=7AU)!>%~LN zss)5f?ZyEOBiFK)E$jO}^)8Xhhg>;`46BJ3c@YO(sp|;}tQHWC9#^p${Z7^OouhPk zbDtR^>3!F#v`hYXt7r_OLRq zP=_T!wC@%aZ_50<8fO7Sn92N&^HXId%WQsvF?B~riyO{CtM!u7ET_xMz0H1HE^bk+ z_gA>I;gKSshVo@IH9(u1GEsjv*Ut}XdoZLSRcA@cvQXG*e5eEn}jTq z>oGw$yk`%@UJUjO2X^enyQXJVWS+!5mX+Mkcg?$*u1j;GIRU2OVwU>=X=H353ti|> z9l$>fc|>&*7&c`Xult8ejvdZzm9EpMg`PBQ*0^5uhrp8<-wE?Y&%U4PW3 zcl2%Yc>~V}v|LuwOcB&y+(TxNVQ#?b!s)j%ydNUo(bXS>R8drIEJ3!kgHy9&SqK zN%};di>kZd0*5Ivs{6I8jfL7GJf zUkD)A#Ww}{XPE+$H4lL)3(sVY8TdG&4aLHl1eIdfUqg3h6nk_Y{w^KRBy!geZ z8}!iVt8Y%2{GJ5Op@I{)phkv;x82)oqa1x*HKa{3({9)SCztM39!hrg_ufzqq9Mk<^LHUI?!S@02!fk}`J(4;``i`?k0vj}-u?Qk zw0EJ&)%dz)GPo_qu+?4`Z?P%>VPBni#qPVqmNk2yM(;xx|FLBkxrZZbinI7FZZPDJNICLKNS_QI)=j;8?UBL zYgA$cW69tC{Ih$dYjK)Anw?gNd91LL2=!&b*ogVOQf6*iHFU2i5<@OP7(GANb$dQ4 zmg7ik&T<^3Hyxk1#y88LKadI!C9Smcc@fx5dd&l=Bt+8Wwpso|!YHNs2}0Vz4XL`? z$yLN6hZM=67-ZmS;uxPOUw__5AG?x-zvPpMwA4Rmw-l{qiDTGs3{$VNTv$ui~B}ZS%&kXw5DjgU`}j>!j^enR*I^wXRb76T$O3 z8TU%n6KY{iPb>z`&-`EFF#}|#Fm^wSPq;J6b_^FXjwL!fx zrtD`yQY*F3hNZeo+90V#(K>mv^YHKNQC;+ejf1STCj)T@aP?XCYUZ}SZg&tog( zkg({3*p{i;y4m#2zo(EpGKFMA_}EWhgYaPA^^0Lk@V6}+wYWVi%s?*smlMyKpFxX$ z5u)YWT%Ju^DXoj7pt(#M#rADNiO6{vmmU}L91EKY_4(gmV((p#J=lI`Yu+LWcMSI& zEExSW!}en&!<#3(#*qp1ibTPx(|GJDUX9lLju~W^9JETJ265+WVV*T|FMwT0&DPv? z{)wm~;UZP3SbK=8Ow`x0fF3gw1l_`{^gr$A6O6`MdMQ-N6h*|tGhXtjqc*PVEUaCv zGU;rMQw!7)>gBQ1hvXK525CSyyzMp?9~Dy1QwzRERH}RdihO&kvLR0^cgej@tp~t2 z2%t4Ksm6U#(hN_yT!p1aA-g8?*4oS#Kl2u@I0b_GG}~7|NLFbQ zysd|tOZ%4u9NMok#7>o^1hE0Gk`ZL`GLCYY(9|+7H+IyeH4&YQ$OdVsb-ERmj4}jhNRsN_$dm@z=|1YE%d;P_`Qb zF~TF1$a}wNG=X2_iSrRHaL%STP&{;~(u>5#1Su$Mkw7F8hfuIzE4m#MXr?@C$hV)G zFV~wyBpoCs(0`$4)VZI1Vacl(Uvavzx@tXGw8!{BrFi5lbD`1MI(3!Is)zRCT^D^L z_;x$Hx6t+8z^5Lco++%?!P;PNZP;Ipbdcpi+V13<=Aiu<(^Xi`d<{0-rfU}PlX?IZ`tIlw24Fnl~=(2gDgtm z$tA+JM9lUJ8z0P&T4j$&)IM-4dRialQ+IYg|87nN1?Z6thm@tD z*QgzeKTG4QWg25Fj{8G$LVR($bhk`8Wgc^oSwpSaSjab&$Lv93mT0|X3YALf_#OJU za!_UF?Ak+H6c(X=ejH+sgU@wdsqJH*>xr7W;KSQSm6=5wWWb&H8Ij<8inV-1N^Jrj zvYnfDK;C6|$TeT%D<81Vh?VT)OU1Pl`2Oga>o974_g}d0JLRsWgj$t)Om{ z1!N-G?S0kn9i+&$3mAIP6qEYuSUv0WcfjH!*c~GH7s;34-h=BtE&WlJ1VljtqJ1dS z-ZdotLU~St@(WGa;#{%A0>isyx-9SBfJrWvaambI7e}}**v3&QYn7e-QSkU>RW{w-+BZVz>;6od_bA-t zNkcU7ugb(MAoZ@nWU$}}dc&vRsPsM~c$y`I(uppXq6Qwq3t(L7Kk8$3sGvqC6V;mo z5KKv=2$tE_UmiqL48ghnN8~naT&7*>y14{H6QGC@-d5c8`1J({M+t zSk-QC-0N0-?7N7_Z;1BXT=E|lJ`&1-ozKT*kFN*9Q$$c>Mre?!P|7CsDP!r5^*@;` zbCe(|eJAhd-28GQB1F1e;z}+VIz-!ATN~)#^Wp{FZ}C-&U4O@2mUet7#VBQW>-rVW z6w#@CV(0qKk;iXW499994C*O(IJo*Vr4(ztfs@~^>tQ_WVDT02(?xcgxmMSbqGTOc z_RTsSMS(9+emu6TQq!&C%Gsk|KC&OfV%Xta48H_nxRaNp-MW6BtEZoJ7lP9pr!?B$ zUe4RlH{Q-4zRCoD?XVf5rFrD5%UPKPFc`2(+@6LYOSlVq=!ic)O8USCK)Whg*cxv1 zbBJKj3oq;vCdnkllv?h&bP9)#$-<$NGux=7w*L7UcJrp>o3P_3K+(P~F~XD@IuGf{ zZWZ)iU9N5|v-^my{Zf3d8HUcoWB+!S(s)I3<>C1Obk_N;Epwyo)GxfN<;jrX!>ouR z&B_{Y1+6u89-TLw*d-j)bLF!wBjAm!=d&k}^84`Ql>2sJfdz`^DJIqm58PoQ2;~lQ zi%{N-(9f3_7vRtf*5YFsq8b})dF zAaN!j6g;W$C17P9tH_=G`Tiv^LR8wxKQ(QfL(dUew^u29CCKJBn@J>`Z&9IA&4ODiD zqE38d6l`vUuIc|8t(uB5a_FxEly@?TuhP&DyXYp@~C$S}4-|-+(`%P)UwIOlKW~P!Up7 z=0CL8VVfLdYy;#W6KfH6u~iSX>^dZ3rb1Fkz(hqz5J0zUs{Q@c&B#$1#fWsU)0S8S zgd}9Rj*7>_!-ECGj~cFFMOZKPMkpvtNl8gA{bhNRhQ%*Zajhnqc3p3cMZa*W>0O|e z3NFsBHv`aTrf%g>Ny&iV*sylnf8AytFaN-aouFD4#cFA?i;f43IJj9k$~y zIq>Q6029f2I`L1qV!UP871bM6@H2dp*5Gv}IBv#Op6;HR_|q~^t-{LOTz0XHoTfq}O_{Uq>%_EY7PZTk7% zV+jBW$dbiuW}ou7N5`+31Fjv@X|eViT)%YmM2QSW74U^y5WOHQr1r|`gr(NhBA<4* z|ApI_5ENnHwtNZvPZ=Gf&5+lLK_NSCit1h--A3!GyWsB3O^VxFPdldXkhO^a7^e6w z&baIVMP$AVQ?q^PmuE5lcu)D>qe>tU1vy-vaJmtZawU9j9(G3dE<}}uyKn-JM;36# zl*+`6^?SrIO1hw+ZWYP#1@mq4^99oP%>>2B*A`*DWh-hG!z85CKBPdPqBa8Q7GOL= zwjno>OjJFvy&kDP6|-iGBo34zl%p!&YXuaq`g9?#f+OeSUMTNhz9Hp3+n zPMKhTbR1cN=b;)N0^iWiEYN`ph_SHG^K@dhAYK*8Vbs_*Cgt2e8T294Rv(UycO=-Q z$>8}m?761ul|1M!W?~|DCdD1}lDTS~oB@AKaqSnUanj>)c;u29F}9k#m1SX+rrq;% za3u5DBc12M@|=(;t@oL}CBa7`jSN*fdaxLw8Od{Jhgdj*75?6TI@S0Q;p5;qqSsp; z1Pvao`|i&7r~T;>{2-=tRnPP#P^_ zpn=pj=N&+;Q>l?Oj_0_@1AKtL_1~Yw@Shu-HO3`D!#Dy{&wnS+P%n?O>Qm_MAp+03 zyFX3JL~Qfl&15UXd65sh{oLd2>$s+D;|c8U(vGS0?2_~0ZKST|FJ;@bsOihzYcIn&;XV7RtvpGgxV}({ z9i&U@Xnp&0INy}b&rp0HIE(ua%C{bq5(ri=6=B#!eeOvp8j$EcXBU)*KTubm8l#}h z9iUu6q)6b4G8{#o=06;8c!A`({NT*vkNx9J!|C=9*OD8G(bF~4&=$8so|3Rz&PU)o zZqX4+{|UTk_Fmk_Dunv1V$w;GlG%fu4VBSCCNCO`TOVK5z^c5P_A1OiCdSwdtJ_tl zuvKs-sQB;D&7;ZF2oE{W zEzuT&G3AYd0yKlFGKV6`fT`M5H_x}$zWFi6Y7plu^@mP!S1lzK90H&7JFB0?YpNDo?Vwz1I#Dm4qnAAzi8*R)eai;+nofj;KvPs`D)rCNhp8rkWs zR=xH$hir^z)#CqeAx*-M{5chc8KVv?dJQoIW> zkJb(BgW{>L-~yKVmk$VGQYf`jb+vo49{|WE@y|xPUrTA<80p1nlIbe zmVxaPKd|;_|0wU#EFtJ+7*yWzVnw@r3(2$cmTpvRa0nRFyYWpv>-QYp9#JsfJ-i`fISOuF?^L2kiKW#mod;#ZehTzP^D-GOy>^CVqc#O zP(o-v#}$ZwFd8Q3^i*y43DEv*B~d$dQ(SC(3Y5G_bN}@?Gzr3)%q;S~Deg2hXidH9 z`80_A;ib72lp%LwWKONod;d?{h@iz{rkZTPn!1Av=@Ko6VAJ zTLMq~S#BLV-H6K}3nrn^e9zleCslw4@i&hOp|>(Tr_RXkHczV_%?LG2z2O=!OYQ?8 zESVSS*%!gb`olsG$<)qOt;wU`{2R$&z1+{?-A1upb7MRNjdC&iLX8WU=4PloIX(%- z4Wd`;8Gdj#Vn6Ll7}vTjy8E|z(92q;K8|b?E-K2hIw0G%#lzGxN~M?B3%IcvPMn2lrE`T1-> zfDYQ}@HuQ}9)8ihL;S*s3{j73qJQTy_{FSii7 zZe)2SkL=Aj50B%ZRL5@)NS#>{+4fXnhpylogMhF^sluAY22x|Xgj{+F6h2mu#)S*q zUR^&ue8xN=RIVt(CfDlz)MsFL5C#n(q6=2@?)6tF71BJ(p*L4$ z*<1+TcmT*Tfcgi9@OrO-aVp7loI35?m^IT~Z}2%6y$A5U!g7Y+P#B*j-j)=Tt|!K~ z_wnDi8QR}vYCsFUJQi+1q8WxfmrKWNTctW^^?9AAJw(O5)!}mFrEw(Uz=t?g+>vbp zJ-#gPo|S$x-c)7=@7e$TU;6*cXCn=*ZMFC6dbk4&J>9x9J~=C@PDTNe4FNoqx}_o% zUE+~YV&ZUkW{C9r9*t(Fh+~Lcon*Skx}4!S@#B^wAr|GHbp^Y30fmvXV&U30A~wI0 zXdv0?AuiWBJjg`XLY{n6P<~w4{KlCBH5Nv>As&=L2M1W8iiT%SO^(l(vk8Wxn}v!> zK?C__)ezT`Cb-;iyXmJ85D2r{iX!ML6LGS`kb?tfp@;|f>!HKlz!9vgF})!a zZ+@^T!&^{R0T?Bs2gBGm_v@J2(<-C4O2r{NbP*EtvdDC4`;|yNOOEX1V;@U*=|DS$ zK^%WBBv}1ytf~#CEzqpYj=3{S2Nqx|?|$7hvye+#q{|qn5-h@X6ViKm!nY)=$u)@| zfmbd}R{gDJ$#44|f$Q=O&AXikE}*am!Cc;Jq9hG8aVZw6-#{s{gFsILyeH_JI9e?N zv`3ONU8gxGzMBjX2I^vo;K{>zU<{<9yh5H_c3$-(GIotXSu?C567;Jo@)y&HrJjZh z2}@^Ds9p`4fDFwQ6{oo$4Ju+Amq(L6`}>8Z=78-b20z~2CVzw0c<`CVI^2JmYBlI3 zbk5RivZqH7Vh20oSy-E)YDmrn3T)Pg)`z)qWgoct7bA_u8}lUk0}%-?N^o z5Olj6F7!v4tCBj@1^$ETU!E^tXnQj4QCBLun=gk_J)W+^*Hf>NZQjAJkaYESDhk91 zFE~?u69xb6VGSi%UD~G&D1!b8?jQPvTc|BB2VYAS`j^95B(c4Zo}JBo{G29kKxj6gXYaFxAwP? zr&7F3CSJ1y+nQU^xSwN8oC-tFX{^dAVI?V#?80K5v_dzV>F=r1t$v08Yh!1+K%;~< zZc_G$DPCC$pCbSVhf>73K^mhQUx@q#eJJSvcBO^Z^gmOTqZ*ligU-Wk(W z{-(*|!E_Uc!9HNz%NVjX74CHIaTQz%=!+%pii04n9>3_1JcKGiWY+X`|A}>!w={NK zqv9`nUnhe@S2sFfHSs3zRj6CdX)E~ZpV3;$_m_-G>5m zT$0z31?h#y2OeEf4dMXh|71Y;57QN`!^y-MjXJL?n(GP06sAoQCqjza~= zYdhF3Izx*=xSoHe_IIO+Cxt<+?~XOw2iBGS?Z+mP+LH#J;5g9_RB!)_`p4^usotYB z0l!6wqOwnzOke7#|5TK~kxP<1!E4qaF2g}-g9h}YX!hQyScn3c;Yh%oM73oUXog#%#i#B}d zpY2})jrJ8k2}4BbPl)-gY}!fa?&-8^NG_ktl5a5UHbwgO16sqa*f&5kJ$^W~sS17a z9X3Y97q+q`L9{<_k^{78Pn>)4Owe}PzAP@juj|`Odp#akMCNtdeI>{1URV|w$EgaX z=_^*6>l#AUWx9;8Fk{>)Md~W48)9h3K6l5;8~(f)i%#pS;w8JpT#iC#;VWw z7cYGo&dfS1S}bS#@zJpKT~oD+rto%a_-=ossOEGufuYKRAwA@%w2TROw|AgDIc}M0 zczipmdEX^;6;uC==_+?lHE@thg+Z8}|ALic=JlPc?yNljKH4Wq+-mD!S)!#rV|3Sfvzy zYla2<4h>m_hkvVpgr7)LX(JL5$|%UhiQtDm`>D)bsIT;hT%&cbe4UjTZqt^Q!=W}ma$tI2whw6 z6p6fW;lX#De}I)3hYM3iDM+H?Zu4nZm5RLv`ANU@sLSA_7Z1~cYxCKWj5L#}-wiCW zptpIKTUQ$;$f!w9hUnep(a*rP{$l^F_2_)si{f&RW}4|PMkN<9)5xy3=W5=FAz7By z+{9-&g!yNaUQ-8Z3G_HMEf!B{+N^PYjh-^qu8o6ztXn)y&b?|xxzf?Nh*HZs)Rz7W zcyvsw6COO)&yA1az@4E4H;lq0t+aqGUXwI%)7t3h3Oy@69wvQb4M4ry-#)2Y7zi3qsjFMx&|q(~3e}dqbVB0c<(AKV*wfdg#Kxz&Xu)xA zn$H0{SH>O@ar?og#oQrc!O7kdSk~0kPLb(*0S4kgRwEmq?QCC` zCI5WE)Bi)c{GZX*eg&?|@c7g`C|Lp+SN?mLS+Z`8X%&@PdZ0)=)X{!!tVpYc$^QW( z4&8cA$5|d@`@QlVuEXZeu=sXfHApmFLMf(jIKil=nkR@9JBknklsMG~QcH`7Y5}j& zqBJGrb6ZTetqkfQMX7B3*-#3Bv0Iy+FbzbKkS8MV%Atc;B3P`YZ!5>MbJp(K);pM% z_0G>IPjZ|4F{=@{`UPwdSe}y*&5!uLO@1S*q2wvh_awj-qi;-ODCGw9B2=y;llR*t zzoxV52WjwH+iIAh)29w1Wo|>uPKkioE&J-z*odd@Q&$PMoU!Fg054i+5;+#Yb2DX@ zO49W!LFrh|aQM!S_|o5t!y4mm+@y;M$42oXpY^w@WE4`h2?ioNq16Ct-CPc&}#mxG0 zTsQ1izg_E=3`GODXr(uEZo24*-9RGodhEg}*|Y(oq$I}5zmG}+tbNBJ=@LSyQeIAN z0@2`{lxBh;namx5B-+flJC(yg1W?UZ9LtcwA(c<|Ct&-oIED*Gq z9YFI6>P>~TU}4Qn1%<`df(j-0mSf4Rz<}at%8>d*8gr^2h|TI$Z&dhPZJdEUu{vCC zy+J>GcS#0%JMgFy%)d)Ed+B(2@B83z!}QqFH9fQxaN+=+>2h z5|dxu@j2{=CGPu*Qz(@`!5$A-cj%SR)ZVJmCNW=E5K1ckGIVB)fp)U#j%bfmf~;F` z`Skfz2PJljQ}vfB*1i-RumP1#jW){+NI5coU&T>c@&_}yrcW5W-dL8z4Z+s#dMH#A zbEN0U+5j(cUwn3beqI)3ud2imuygh}AD%wUpuu2zPR!@uPnoCJTxNa1`tw&2zf62x za^yRl5A>7F=X^Qad&anSSIkAQfaVE{jp?`2VbcBV?Cg0u1hHA`M%i0_)_sXV+A{+s zcE&%h;2jLA%{?a#|7qlvUlXj#mk^hF1#GyZw(iqF&-t5y&tKT5z1Rx^vV7W?l#QbS z^~*!-7{z@>qNC*?7tF?&&YxzF+$~!;mG2Mpxni6WargQ;v=BVD{2Hcr_y7Hv_sPWc zA%xU8X~R}MxRdj(h_X{)u-_c%)Je0|AM11uU?YGRix6vccEUDQ(_p5#!fg~Q3;rrq7{I)gjRG~xngrO5foruWnZX5?Z($cEscX%i>qbsTqFJNN&w z`n|~QozHTTG>RjsVTx3eKz{9}iR!F{3hzdis5r}z90giBzyPaK7#Pc?GKZxy)z+1o z&&WGCmPk#8@2K$qnzipScKkUUu^RxC8+&&q(j@@0tXS@SCe5Y1Q8CbzHQ4>Pv;0<@ zWa%bdBW;SN1khKsg&QnS+6pbPiMmG!oV+aDgLk4ow56=TQ$zB^n468V^;wfvGK;B#-$o&oqZEb#+;~FP48Z~95UHENvnF0` z-&m!RN{nLMci;E#M)>VRKNW}}%m#9WP{PjTv|X3n#t8J7RH4rbhO6DoznL`i3IDrQ zU7fBF`QfDw1%;y`78w}m@9OSOk}hX#ZZ0ERu(-Nf-Va)p6x!gu>MHC`p?MM&6?rfzu;Kqo==t%C%|7#GL30j_>NmZZb)9GABv`*@EhjO}K>&!1u z6SuP88Q%I(8N8vx#~zC0w+!;eT%DbxE;86?p}1iigcn-$gD$*sPy%ZrJ<7j`lA zZupmf4JvIVXfsA<>`1*^ZU2MDMk22p$g51e{-ax*OWG}uWKF0J*=Q-tLIYnP5Uu)o zpe7PIM!@64hod;5L~|%Sq&^gOO_;|2j764%WXYm6e;R%%OsOVm-4lR*pr@dgmjJ$s zHzUrJMmM?t$3_yZN&A<>5=|Q$D-8_5FV18BtQlFfUKmCBQm!*#bQztbATU~>#2v`^ z?>p~im)=|8=%+$|<+mJ78ko}!t8K^bZYFaJCK_1((+&WIU=oq0?*O4OLb zWGP~K;rGx3a!u@kE4JteuewroSEke)oF|6l-GM9ib$P4c05723kP$a$Pl> zr1#r5>#CohBZcWlBEEJuNEJV=ykT)?1K1?-W0a`j>8Q zoF+Mh77C0>N#d@0KYL(yzT`b@D9h1S8Ud#&;%g3&di69oI5>TGcWf9M6zs((VMu4} zOf(K*<>fR3JGj-1d(WrN?F!%;hW74Q`}-z>{`5<*dw#$xe2)J6$>NOEYN)uoutS_d zccHw(r;^LnMr=j@yFxe^$RjR$qx(UKBj^=i{fu`>3{AFz6?42mm43Ij&*i;0T(aMq zOXPGU#_zlI%{E&D2G_y`kLw+9yDH>xJ^aF-T1(?k_qV$eLa;T&_FarU>|0fTOGiHc zurhAN)bbB`k;x`mTcblCHBT&48%|(E#+g&b>ShD(e-0G16CwO;Izgr89wnoqk+AN8#gVe|j(da}=P&*-9!`*4lCP_?1DbKwqo}W*Mn+R2ZMh z73LQX^WivD%_Y=ZuO036xPH89=&}O0ySB7PU^EMcZuiZD{kfu}pePJ|g^8foaA6u_ zodBy^OXdr+{@$HE=VCSM>-APABNMWE+l!qii#PVZZs@277&9^8ccFxw#rU!I_-Ohi zt~OOjT2_QQBIcz+R#hL5lK4jMuy#XEKq3}O(@)2nRI_HSMRYa*f|o!_U5^ElHi!`a z#G!l*ZtJAoNBh(Bg0U079u{J?2+nq+&InTf%L_(-m>^{R?{G3>4MdW)@-1T7GXB(R zRdHu0nR?E@69p?89)<53>}zGoUH$PTA!w|I-dVG4A>n|$AcL=88%Fp>B;XW8RHKD7 z^l;Bin!OI4{9P@e3{1m#^XnQG{;?G3pQK70=~>HF7=JQ$cJf6%zusP>LCOGYp?Z!} z`nFl4Q6L^ly@17@>dcv|jPEMK`dTBP?fmh}>}oAN$j~a-7&4E6fuYSVkW2g(f`wiqr`9ov`Op1{zd^&Su%6)@ILT;WU{_k7tf{634py7AGub~{KMTJBBZs+RUQ zs+>ga!%aN{BP-k)4l%%ewL-yxjep<>mtlPSgWE6QLoTjwF){#Ir&dAtUiQmUG`|amp+)wLy=EbG2fwpZBfK^QwH=)KAm-H)3XT ztal*fZ@TIo)nHd`SQ?_Zs>+etHOxviZAz znk@|FPLzp#5!k=H@L?aNoiBxn&m|s_u&*;_3(Telye=!p3#0RkzuWtFmVTUb0p3Hr zL~2Ehl^(%Z_-Nw(=dY#Cow_{s4h}-TRNwb1s`)aLr*|tkROfU{syye6-sJgr3w-ej z;!0K0)4;LisCMvqf50&1u+b=oQ(gbJ!vpebPXJDrItWC$PFhyZ+P)I`i>IW^g<5g; zC|PC|t8{PuxEQ#O=&Mhw1z%cr+Q`eqojClI|Lu@D6gd?eS~Lh@p3)8rg4$VE{-q@a zE@Uewb8w>Gslvb1kX{b_gUt?`4$ecVIP)YQn<2Rggpx`3uPxbn5iGxMSzAOS4+S6# z;HK2OZK(?B!IHxsW~vj2!V*=gm^&1L@0X8TJ^`iVc*(V?vGg0&F$oKxDMMRs$8| zbJ{P|bVe(CY6 zNYT@}K8KYL0(L@OGmJ{j-a!`^%L3yST`)wRJHkk8jl6UclP$ua!5dFqS+W+N#~jts z359ipEZUlA9}*HZtYCA=ygh@D1B2#&RGbwBv(U}XuY9^ve$i)3xd#4Ft~=h(%w00< zWzzaI+rpynvcCJXo7kHpjfzyB6I0Owm*^&s3p4DQ9VtR|>3>z^Ud~_zG8%__(W^3yycW zC^Kc9-Dy<#u}lMO?E+*u2W8qr;US(W6bH!mfmL!NLvZXN+EA(Ii(uH@o*_$+oYun7jcF=X01 zoGG0g(`e|)J6I_JzSSFEEF+AmDu@0{EtWoZq@}^AQX(LBm@(qa)0-_FhX6ew*^Bh7 z{i`ndqaK_U!?!(;D;=sd^SaHdL_PowS(|kMr_y``9{&Q@In;~tp@DqgqmOWs^r}Tw zt%3=68H|9Q3d7M0&0(qm1vHHA8rwlW^5~H-Gh$t`QIaj=l zw7nL6DZpw=*d%E8TA?C~_GD&V;VM1bV8y?uu6duYQo0sXvE&qf*IKaB3A(#!B0-e=X?B2}(D zqU@HCD1vWwFv161foaC;y6bBiRzHm1I@z}uW`8MH>)lk^ ziO#zlFdo(;X;YZb{eY}3MF-s1cHLIMM4NSL!hZn<4*7ea+jx&((|P<;^r`y8^QA{` zIK2*sjR^(`eczo()B=S`B2O#;P}65$~?&(XR?{Fnp?k3grEdsY)~hLjg_lNqCAf0a)7CjLlUke- zROJYE%v5#UD1#C>Dz2nz1?DBi>E(gJD*u>kpZ@^-U~zbGM}-=sAy z(}j%a66Mv}w@KIF(`LxJ*qSS-9R+h4Bb~WJ!?ODpl|m|A36jOq;VX3`IOsN7p&O++2 zOf%2oE8S*>)y2Jm(j;=NYTDc(qK$2@EzNM5StBZ&tvu(0oj+cAAkL2CQ!|HhIAimc zAK`4}>XKVA>KILVwYi3~mh=^w(Qsk_Nd<)W=IPA&9ji{8J#G$dv61otPi0Y(m1V#| z2_dk<-QVHtnpD6EpUZYh{N*6yImdnz%oUFwvQ*b-PN+ZXJBu3FhJR-ZB~SD^;HeaI zOjXJ=N*a=vAqd3XfMBX6!=+_Ifa3F|YMnCl1D~UWsEX&3hr7EuNp~0|Z!}rK?=W`B z$E%C_aAmfYCKNCQDb?35a$U~i-N5y+t}XvvYylr@KKh3rq%|zak)eNbAHQr{7*GEM zKV9DD7~d0?h|8};)(lo87yF()dj6%ZG0(-t#=;prEH#D~^?t;Y=Rq!`aPJ)1DhwKZ zxPA2AG;8$2zHxjO8I&HxwftD`X`tQ0B`!u*Lc%AjltyO>LZUG+n?>dR95tGG6K(wDcn{q8Oz$37D(R5m%%1}j3DWE{K$o<=_uqwlN|nu!u2)=PYpTxLCR z;gztWm7E1^qQ|Rj1Eb${Tt>OY2GADx3nQk_gBVqUnC&G$E12joHFzumHno)_+JF!n zgj1Vy(1v`Tii~hQ)vi`ER!bMI@L~i>eogsw2vcA|>ADbL6U}oHxVOkx;1?W{FTWBY zjHE`nThQRqQ&#Y!E^^c4Dle}H&KLjM!)`FX9r7fLaF<}1;8>_7DYSY#mVf{Cq+BLI zmc43T1a5OPm`w7B?WJ7%z}0s^vvvFD=nx%CqzTljxTdWUWz(sUMzZt}R9gZ;>MDmd zkdmZa4mhN56K~J(V1VbFQ1f^nJT&zewd(gazcpu>brX?jA zZ9CJ9O?WzqZapu&KW*89isw_Qy>Jn)IJ6TQe|KiRlMg2W#y?Bt^v=vyFm0;G{9li5<1&;r?FK zu>4oWl`PGz7TL_hmt19_Mz7PbS&MyJQ~4!oGpNbKUCVk?zx01M|x1jtmcjRwKZae^OD3GdvU zc3$-NQLwkUA19pbi*=Lc^Lb~(0uRg)73st!vVLZ!j)`{JQlUAfB6ci+8vqO##Grh@PmG`bOZ>`$QX+qz>2Ev%0I*IDHavtSn|hj9;CH?{0dcivU$?+eYyGo&;FHrbTRTdfN+dmiOmn8LH(bi5D8qU{2yW| zbz7*ZPrsA(<$o)*ZsZvt6KqcIWxQed=yLI<42YFyuDD5KLq1DiL^CH?5|7fK>Q84$Y9a(k$9x_ z+U$Zp)YooF?%sR@Uv%!4hiK~s_dB_0L`ep?Zqg9qHI~5t3j|%-7gD;Ym5tt zzl-_ui}gvaNV=tQd<#QP+W_^sE0E`Sp{OUD;3Jb9rIl{>*>CuayMLQG%+s&$n@bFS zXU9L(=L_kX-i|Wvhy)+n@thCR*@S9lpo%5*M>`y{t_THmT^}N*u{uaNql`8`a&=pA z=0EPTQv=Ey4p5{*!N!EzD&cl)*sLID^D35OoKqYp(t~I7A(v_0-Vq!z!8p0N%2pBn z7&A|Dx|y3x1iO1|IEBNUGGMYWY&q->jXe8|F<1g;bR0GZS`<;srj*S(65%_`><@j6 zzS0y0VPc#w5RwmZ1?NidWPrN$AjNU4PY|QO~aebNm(e_u#(j1TC)AAke6Muduz4asb zm%bvuM{=X;IlpT{>=OmwuY&gmAYMu}*phbh1^?3IFP{@m$dL_WH8YPhH%Qt3tslrF zfT;ZO^qbh3uh3TY)NXH9l6c>Xhy)BPRR%cttObOJqp%N!xX^$Ax9f7=s=?B=D7_zm z%knzBkN1s0Q+i$&dv}mp{zUBgH@hd!`8J5nd8>{hh8i%_DfKNqThhy_XYkAu_9*MTPvo{5@oWy_`v-^r&Fg31x^eIC-jwO< zKH1I-S0nmUANtt7?dye6=fY$M{(9fI>Cb(m=l%QY|IQA5sTGom0s+^4(j`4>aG2-1 z8zBV1cmC=-p@k;%w{XwE&nC7A)Nh?|JGNYRdu+P{Q1*TWD8uH5_4TF12k8(U8g53} z=;GCe1-)mO3H1;q{zZAZ1yp8>LI+v!gnmSrDBBQ-i8ATsjx$QqW8k=yhoxjF*I^%+24dr!WS- zzy0l+=W2I>G>%-Po-C$LblbuD%pM_}m>&q3f2GM*8v|FErIKrK5OsEj(GMmxx$$!4 zQy1Ak)`*{Ww)X4J6sVl1j=-*-jGldD9hpMu=UoqtVvQ6_7NiK@Z<09B@SKN7~siF%jc{Sy(i;6ll0|?=7;8eApDZ|u*Dhk$GPEiR z%1z5Nk{qroS$7%f=6UtBE(gLI*io+tDAT<6I&MF^v(_jT;t1_ii`pcM><2Q<(zbv` zvtCZT-xb~#c>IFj$sOfwp$lfIzdJ4cn-;7ER{{@v-1NwWIb4&O`G+L`V8eK{-rB9V zlPe^cKJ@t&frL(X$Zi7(p1NlNj8ZID1O+p__#Gd4@T-ln^J<(b;l zY3YwIe8Lj@D*fY0_Ad61Ok5$55;Xl~V!9pLFw^tY6m@0GlD^PoND($C$je{O%UkIA zv>WcUZr5rxoWpYsiBb@dY~p`;H>2II8txi1g=p=dq)`0y};(68vQ46Uef z5+1N4MwUOQz~=@1GUmOih}ozp@LYto{*`j5TB%`+B-TyWr^lx+_%D6h^7X9l9MVfO5rTgh;e+kdynDEWY_OC{E8`vR-_zh`@|| z(nnx=Bd;&jgJ_3pT<~^%QgH9Awp$+h;`wuqzRdKKyyt1p!-$Q4m{Nm&yQ=3tN=&H7 za@$h8Ybb@5c4zpv^Y8iAlmDrFVBm$I{rjpSo1_heCy^W_QPEk|gsdbhXCiz|S_|#3 zlp6}-78P7V*9DoU>w!~x0hQXdB#ZXl3A}s+2_o{e%E zzGUDTv8*Xg*7u+Tnm{%|Ii;4xMkT_hf8ouArw4Z^(#ld}BH}oeMpbvUcGH0zN+rbB z#fM>5YY-;Cw(9wsaz@FxGlckuF$wOGn^=HjVc+%k5Oj4z3WrfiY$q4S%am4Esq151 z5s7-kTi~${;WEX#4MmMFQ9&Yjjm@EnKJ+6;71s^bFo~vWvg~cutke_}WwY9SqQ$xt z{J;MgD}!h|%i1EHeUQ@=P=DGX(>9RMa25plpz!dWNiA z=h2p|8VWF{^DQfxXwm^@FB|G7HkpTMNFQK_G)nY%@|yfi5mP;MQB-Bx=0S9{{LbR5 zz`o%T7lycG>(D4|Z;zIrWoQEM-z$<_b)8mo0i{4o$lqV7JtJgeBep5lHu@%C7kp&2 zR`NMiQXM?E%lPQL*6TgCKRDE;zC#p>JT0p@Av)8y1(O}U0oG6xT3DNY?chFp6b+$xZ@^9kG=`~OV6bW@rQqXyb50- zn3$Tfj@E>VAg;`QpqWF7j4K0)=hMZvR6iuULx2BD4(ry=*00roTz8H&L-T>rDiWZ< z_MS>P>+QLCLz@xopCT@#Z4E!}RC}{HKe~I(j&so;gibPeUDi*d?^U3d!Ks+pd>5t3 zd>(B`r+=sNect7IfB1x`erdhFrwW^1e`$>C9{QuLW2D~GFYx*1^|+Q49UZ3brsX7pZ}wz(p|1y)aL zijzBt2E?0P2kM8wSc9U~y`!g8 zK1QAa*j`qZ)lh6LdAF*Bg2r67>nQF^emS=k6^*PcBQ)#i3E)+hjUsD6!Z+)E=#g!v z&r=}D&!S2%VE|JiDXU6sAVB@<{)9KtfY*RuztHS{1O`9|7dxo*(iPqzr3^cA{k%V0 zwwd2q_tUK~IMShVll~UZo@+sEkxXQTVm#Wk`U%=?C{ICraD4CR(FH%e6Xsn(=}mn8xHA6;+%VWDnK zJdN1PsBhGdO0SzGKc9n;Jr4(Y^V50;d{92aQhzt|DWbJ}0=637koX$+%^u!SZkjJZ zy$q^Y=r(>SuT-8CpF=D@r4ofC|1X4MtQ5{425wl6%37=Ld*PE%@u4zD-VnM7GrGbf7{V_<$rgg){?Xs3KCHk zPLP>!eel;DLy!q&z~qx&sT4SOU3Bs-g2ZY~miGUN6S4e@@0qQuA56TM{?A-%e=z3O z*026R6F15`h{Zgp(UAJc6(AdeJ=Y|(rjCG|B3rsrW$5lG)=P(P8r_o8aoTU!J?1oL z0N1Miq`nzMB}|EaC2CTV(ugvWluqc%EZc1;m7#?*eY4QWqF<0ef=&LRPdSI3kuevn zXnt<_G~zY)Qa_)qSVKP9(c~1(}O6n zJS3!p40LuY3yV??fL;wZc<5U5P|6hSo@u9U=H4@plv?ouZC))BcbDP@v8cT$4>0O6e|hoBs7dq8LtQuxlsVjQ~lsOAcgT z?GBDr?4p({Y_XN-upk;D6lq2Ko$;a=+|Kvh=HedGC`#P6d=ms*JsxN9c91K4RWl$s ziN0L3pjReSXo#N&Z;>jRNzJ#^P6^C1{1-0tuT2<1_xJZ!@tRZ3=Ty=9HU3aF6eFE4 zg`N7^no2DnY({6jS*^1F@vnb7{2YtxP^3Ti$<~GIqFgPN{CaK<2p#hBKPW$re{rS| zPA|9o2%SkTksf2D>&)%BaVERw<$G!$FW(X6QMVt@?yRaKMmZuX>-m2jfRSjLf!nEL z;sa`^ML2W=YU!?#WA!|&Hn}$#BLQuhkuGEYD~{-s9pLYBF!LUdB<7F9%$TbYp2pa| zyNizA=~7`#fB#bW%~t}Lk{hia!G`d%9;S=rDc52sv$Ca}JAA+6O03=BK)6ugtWIh=qQYSxihS%E!*CAe@u3-+`5AF1_ zuywDBG~rm`-sFRte8R=Sb1{Oxv=)O5On|qh1rg3mi141Q9SHrBF zy52IZt%m6qE^fi0xCcscclSW?AjPdTxI=;B?(XjHuEn9advS`pwdkR}-$%apJLhMv zXas-oZDpD-a5eOGN*f>4+YiDwYy$ooJQ5L=;YrF*6c0xV|W>$-&9 zNuFavTXlEie`7Y+s>P2gK!4=_5N8bG9ah3ti&rgYCdt2?_fZi*MV`9nnaBEyt^LP}psG@T3!L6(4szDBlr zd=|Suvol#-M2eCk<|9_|JVaH=W^Vss_;}yHMJmAaL?nkGfpO;f5F{sj9cFJMvsEOrkj;G8akJ-5lTHXG}?W z&Q<1~-AH+o@wh>&UcWHj-BzSk$Kqmk(5XeQCK++pIniLv&kO553sC$TQx^e89-lo% zL^NKHwj!P{B@HrtD#2rJN@mL^TK_(&yWJ8(#f2(iZNS#sUU?roJY?Aw<@i?E#Rj|* z#lz@Urng%l61fEDA19v74HNz4+B*7e@=#GK%cYb34>E0X2>~9!lzHFvG9C3=YYH0s zzMiXsNL}j;j(5m5B}J$X)ErW7C zJwF~wZ15qtC-Lro&Qhoje6dSu(0S_pc0H}wZSV+BWF$05R7^Xp*yAmYsOOcz1>@CX zGLhwx>cwf}?NDv9caxwY`1+&`_Nqmtt^Ioi^zR1sm4pGDm%jb@r|if%B?Wd?RaWtM zh<{k)n^Jqi9+~NK82_}4XT-*}G0Uw(z=w>E;)tZ0lSmA-Zx3&C$m5J_DgkQcr0z&) z3IR<#QB<^R0PY5QXKuT{R+~Qr#)`W{yA!3eTYA*Kz9b|Bc!x# zjA`dv>@16+hR_u6CWC2^(ljD_n&`5rX^&3ss8~nxjbXXw`8r7D%ry6N4iA)+NK-@v zRYbhEiPt&@J2G)r`cZOM(Z@BJ-q;mk{BY3hoIf&jYF_2h#iD0y0lK z=|+kIZ-Mt#(eI*8obXh(Rc;V}>F6wztKJH_uWUUr?fhOkX?UQEZbr>sIm++6ecmh8 zR{PXGy$%Oy`=x0#?LgwZcB4{7?A7YwQ-NZY?qS|oW;qk_nyl)ZBQt|0w;X}>ugvHbi@uHh81oQQu@rvu)4c8X_g!bGJr+zNq>JwVe*r7@?3(Oweyh?JL9XtwPfct8T$!W=vUljbDn;#(Jt@ zJECg(VO&{|h#VvT^(Ds9J&zYS*iNrUYy?IZ_LRBuYwpMAl7GZWm1#*Jd|kAyf_t}a zC}kt2CcOkGk{2!RE9nrY;2Cto$d8@v18V$8Q_~D*?ZI^89S6rmj$Sn&rI9U$Zz{9T zmD&08>U#C&A)B8S8AQ$*vN!Wqd#$pWfJ{T4?QeKi<=vu^;lM4ljrYI8<0jl zMMo_zrVsA(wxMJ?l?TRul->=g(5`i2IFpu`;igQ@&L-~Oy8_;Ouj!Q;-GYhYec}?! zhSYHt&Qkaf^fT9-sGB|yO`BschLVN6fmT^=I0e|BG%uNe#{PNf&POL9lX?auAFeE_ z_gJtY)Pc!6rRbYZM3bd;tA&9w4fWH5*u~&`?V-zX{zekU+jg4-mEV4 zdb~1=S}r}H=G0xlBcvhHdM@ZfTo9amAFU=mWpCAYP7m%Z6+fp@3wJWbki+LKxKk$< zBK(B)o~Tw0S69$wyJCS65}ANeedEW2i|Y2xv54$(Apk1rv~O0 z*`UB2C$Zt&5|Gyo%jYV#ee8LtO^KNw4)@_$ICAbZ;`$5ht7H)WE>DS4p-;imKP|BYxfdU7#*J zn2@Dy?)UdW`Zp%LDmj+0unKE3)b^wYYh!P;T|2dyXnblprfN$dT&Y8E!>a2MSq?ry zws3ykIxdjUCAiPwDKq#-Psme5 zd08S*)v@=hW76t*g9^BZCI^glzVk4tJlr*GEXWyPR)UQ95n!t}@KHa#Yp%;f^?bBK zQaVs}&M0{XW=|=b+hVCSyUC0aGG;sM1*1%}k32pT;4- zzGZXUGtX{=D!Sj>tTmaM;JQCD+__|IMtZLcJJ6qw?BWUkTo~Zgv|-hPq!y7aNkKyL}g|r&YW9U^~6J&qTcS_3;AtN3R5v=o?C_jc2 zwwi1PvZdHhA1P%pP4P8zzeNOK=nSx@3|o zfC;}r3(}{d6ym>NXm%ZgWMt65@Cr272+*n zP(?|OwBWKe?s0lF^SE^CAROx@jPnTd^`8C(TK|5B^kMcvK>*Ww(n0S9WeSu_@qM1? z`aK1+HYGV`G@=YF5E{!z7jc!`q^x-hF{m~yIw|8~mvXdV{a9p%<32Rj_7h7yV)Uy& zb<6-0FQep}^TD|YVWnnjzkI8@?2SIqdgX=`jgCO&1h{#}P zni#Eg8UBT%<2~uLDlV8HuwYuMO9ml0qCvU5cxcZJu&0%mIwumcV|dyJw?kJKY^sjw z&j|0-ZwS#%-uCF59bq9rE12dtq;)_~KNbgcQ1UL}`Sr%Ny;l=bWQ znya8F`vGkX^1uM4!S?_ynDFpbfPR?~EveLICu8%4q~n8fz8p}uDHkGC6F!w=m|Rxx zQhgvz&j7d2Oq8nzbI+^eX$ZycLfjLuiwn#{n}+2j{^D3Ti@{!UN}}10K&b;+xXMJ< zaE7hi?r}1!+h_%xw064eR(9-^ONhLN?90MG;hy(zc(HtV*Wt3YYV(^Hx3yJTcwpx7 zyS68tt1lTYtiq)jaOpnJo_p8nZ;1aqDI%2TepI$FF%{6ju)Z{z+1ujt*<^cC6bo7) zzNRGS!D(p9dlMB&Seowxn=Iw!Q832h5aAgyU($j(2f%tR9lP0YTX!b4bg(TQ5<_K^ zQsG%irDS%@2UI85$Lor0{De(y_*gw6>iYpEdLmkfYd3I?p2LP{Sre;(CyFm$k`rok z2!29^;ix*H<_61)5K?!hxW2lybS+OnG*>tOVcu}goVbkv+I8QKH)kD@yZ zN3z#(t#Wyup?>7zQ6^hW52UB(w+wXBZQ$WlK&ySanMCnvgoH7o1&{;g)M5wpJbl;D zFT#XrB8d(~EwyXzi50NVV+3S>02+vc6S#<&xQ4HlJ~`Hm9g))IM^m;Cf&ydG$D_YE zxmGeU*)&wIl94N{gH+8@@5 z$3DOk&qtDit?DULQ9z;#y*F59;2w-C*Ls8CMIYlX`bRzsF1Fe$S%AuQp)ZnW8cg_+pyHC8DQoRa_0NX zRNkLAp78ZvQx&8-wZ+6^BWu#>nEA}02y zEs}a@xXZ@|QV6)3BkxUAQd;b89HKgInMv@|n5X4wT1cBAbPchr^jK1bYn{A7SR34o z!9iW+q8u>HufUmqQik{E-B;Q?(gcyFT>%PIiXTr@iA&SqgAR*|r3$L(LJ?BNqq#ht?{um+OMvzCEahMew z<(^!`Hx{0s`mB^Nm+Pj+OttXsjr<}ZqFC};dFe@RT}{?=LE3&?umnSoK`Cn_nnLff@~Jer&%Lz4%RQVV&<4k$s(G=WdhZfv4i z8VG)-L>`!8rURH}mu>HG3q|e2=u~ebhIKMYD}R#DC{3=JtM0l*lQuY^sqYBYQw^C> zXO_Y1=mog^h6Bp<`I0u4K0qy_GHkCNe_Bm>z-dS8ZBxkVPj?_aWE6@tp^3J>v-^C& z)A=+hxq9OgH8V3uHMMMVju&#{i9R>r{p(yP?43{ZlBL*O**>^|t+;#!V*RkGpq&LQ-Hv*}( z&4h=8>Bi=A<_Qte-GWt7m+A@Y@g_mw*k+u0XkuS$l>65N9$k?tgaU6ldhJtB?nHYcsSvb9vUC*9@FC_d8nOl}X+X|u%B zI%1U>H=@UdfOAZ}jmQx}rIIix6!eN6CB9QAnnlB)yw)$@r>qI&9<5m z+sF)(ATZikO_ZEt?}XYfMeL_cB3?mbX>HsJ6u&W)*cV^1P%|_!-q(_MTT>A&rNPMi zR*su{BjWE-)T!It&op-dWq+V`+s3_UNDXA$K4_Qz5|F$VHqf|;{K#y>U3=NQbP<|OB@ zLrQ`i=sj)+MN2ETHiVXO+6%mewdFxDp@bhBv~PpCC#7237c(KNcxB-Qn%T zdv{D=DrP`vyUYeVny@~4(oCy;Lq5djigA+)w=(`PP4Z;o`(Q#VBU}nrtKVEOZIF{_ zu7iiv?f~g|*1K!C<{Fw*J>n=YySHn(2u^@xR+|C$DB>QOWwZ}i{ztb-YJqeKDLQ1KQg-Xo!ahrLY7F=zzc$wS0&lL0cwsZURq^;_k_p+2 zKknBcr9WJ{uslEuN($fTWw*;#O=1iTO2>>ARJZ?51K6f|8#hCK;eFA8A>+G1->=Z} zm%M>=hi$yRk9TA0+r1jRP08dF#dHn{oeat%xAU8Xz{sa--+niw@9pUPPrC3rHwJh6 zNrc+ltOrqi&O`%I3KTg6fDU`5=8k}&$M^N~55fDcnmb^txxvj4ie>0kF-0}MZcV$L zIIwRiBY1@HK7)E+4Qu+hBtV`-+VIes!Ez2G7vXU9*|453D*94KqJnr+K)kN@YVwvu zIyYII$}QMj9U;dbDxq7$vrx>*3cCVz8GF)KbnN#~mP`)Vghk_t3Oo-#M0$BZ-v;t@ ze|u8kK85}>q$o5d4QprtViTP%MQPgON)a_qW@Wh5JD9yD>%`nUE*oG6lq!Ky_{6%d z*(*raiT$}$F~{*M{1u8?J~4LCnsF{`Ri5lT*MKd1D_=E|l?9}YBLMkRpxOI+H;ng2 zdKt*3m2vX_Ai3HrlDBG{r5`s*7@Seq#_B##QE0#X%wjrsYZRulzaLmxf1HV11SmoF63N~1$>y$a+S4sz2aMJ<}E#2MWotPZYK zx4WUFLRVQd6O$F2QW(X@Iza}#aB|S4ZSU!JfJ2<-{e4BXHgXS5&>niRZA9k4K?0Qe zTLLu6+D5~hoFBQbS8ybZt^6|^t}{-QLWr0)w%7^N=|Uik0g0c)3C@h;puke>5mFA} z^(M%q9)?+7bRvDgOrp5p>CoYW#zt#{v}x%1hfKTY{J)q{ ziE%ie)D)-VDDI(W4p_!l`9McN0NWqt@+(W-t|p)R)FLcq;i3mL0`0`f1^|^9%zKc49iSjx&xYyZKjGTCB$?rfo*_@XS30c zWV6kLG&}O5R(C}g`(XPRs!qv9p6M+3S#ija(g9ht!9mWAFvZ7}K4?$sWvla7(B%yv z=pcy12DfM>M&nul`{Y!Cpjej24FVT?DVPI4eokZMe2A%$#IN_(l{ihW;@YX9xU<1c zKAAZ28<-?W+eYjJZ&OMY_&I3uiVHM*+1k@LL@&+`y_f@Q8D@M8<|ex&u|aSe5!v!<95scs0^th9MatFjdjjs!w{M@Qx0q3I6HanXI*51K%INv!Ff( z^jX;f;ra`zgFAv+pdV`617>IoKGnslUZ;OZqnM2b!jBuK4}zhc`a%5F(Hub+zg)-$ zbK36mG^~4gM23Bkckk~BC&UGGZ(lFmkRp95jK6UcbZgb34OhHU__7sSO#9Q`uF6&8 zQ`}z;=8pSX&QIs~-#sv5E@~sLD(REn&R4{A{5M0>_3i@QZU#QI@2LJrW-$U6ngdxL z#MThm3EeN85ijxvYxdxgW;<(rS*c;uV0WuKe5hO$a-B*7w|=p_4t3PJ-bb0D8LfSR zHpXTx2W0ebY6}Nk(}4Kh3enKl5B!>_>c65%(uwufC&?3k?`D??WXq@1{lv4NNhY}r zzxsHQH&iuU*C3gs~;?6=AG^ zj;m>2`3xVtAQWN=3|l$vBpGe0wOiCqVEJ?eboHzqRvuUU8lx$qH4HnwE_k#Gps;|2?4kU5~)nKAry2$q#3msrf~xcE0`Lf zTKWm8*bg3cY(^YAgicR$DBXZkrI~?a73h!m(QXH6lXYQ;aqTy~1a5(EFIIPH#;fKJ zT1u(JdK@Y71IWISB*u~R7UzBbwSP@4U{@IQc|ibYQDfweB~dQBh&12LDL%b2NZsGx zf-a(to{-eDR?I#Q)^1XqzZog4LD^)Kg))cuks~@>(b$zflKt~SGVX3qBsUAIGk)=P z$eX!}7RtF+@X$PgZ-b$c^NhG|@y9$qx}j`Le$h5>R#+)aT{>|;O$2T5C{tEtUWYlR zDoKI4FVGel6q{?E>&>X7OeEh^OKh2WQ~h&R<}fw*oj8w)Wc5^D*Af0wE zE%OnNL!g9Rr<5;$;I$j{ZiF%iR}qVAJ2VyOCoJZn+x~PA3&7L?IJm15bDZZ=QU4YeQm( zvYM1G#@%=#YsL;*&!Ym_-#l(#;eU@y{XmOPc2xt~Jy=xa3$WGu(IV5 zf~mUZq70PInpJb8{5*L)<&^*>-!Sxc3x>{E8pOe+F`YV~d%H7OV zwy_!C3|}LU|*k$H!(1^7^c+gB#rChNrT?Zu<=a zH87*k4BT(VzkAC4p-KPZLH{UO?yvZI{0mjSF_h?CU{4$>FoY)(&>zkuDC@3^LJ5p?+biHQ7(ieL(+4(H!o@2kWJX$_0mh}SPUn$|M>`r4hOxV&hv-=k?i5KD1s43q! zJNWjU>%fhXBdMWND9r%I?0H6o0}qwpbg9Cr^P=7x3m<>TY(zwYV4@d(SE@vfodo(? zT)aJ;V#-Z{4gJpA4E##+Kx1OWnt$s=z%eH>dW|uTuSsedBXL5N2%6C{2+qHS#c|sX zoOzoI|D;QV#s|ip2vk>^#0HNAvZ@YuM-)S5#OI3GG&oTmuXKUAW}~e8auWknq4O6$ zQ`+a2TV^{~gvj-X=dLzl*!6)*>_Y&uF79u)gQ z;e0RIPR`XYW5K}~R)2=c!li&uJygQAG_dOL4{h}3LbCjiT8UNp{|qVSYe=tA6<}mS#LKIq=o|`ZTtZjjw1)e)h!n+qWEN|DHwFKchE~%w7x%- z!FoL&%X2(yFq&`o!bo_%2WqSpHvNozGRh`ZLBU~TwK1z&2G%DGj`<+?wJw69LA0Sf z#BxcT(%9ItHYg-Yk@B|=(er?Sc%|jn%Xh-TXM(GK+J>|^6{xfeaFP5n?F+rTE%INU z;E!!lTp~`TtCRy*vRy2g(Wr)co0kGUh8rY2*EER&g=0kA23rV7sc>m=d{ac9k zX@Y$vNTsj(huBe!$q$+}2%G`GM}0B!iG9jB<{ZzC{)~T6Q4htf&RSj;ZBQmNXXB(T zvB2*>0^*@85!VR1NhxT#6DR+9d}v9#@*4vpyy8sV&ICHGq{_*E?Uddx2aO&7no8NUF&$xd^Lekp5= zE5m0hgXl1j&*h1!+p%cmQg^AN>d9eL5~|hFt$rYhW6!(hCXsIY5H$~hCKB&0uJ>^K z%xMBqp1dADAH|(T!^xhR?n!S;q&OppWx(;Dw!bAObzxeS=? z4%(SPD%XiLx^)e@g}d&zs>l)VKlUzNxuwQFfx}H1dqPC5*fz94d&uMqyAOFvMvfpK z*PhkNIb%RF;8%Nhab(=07Ziua^04~x8r!f>7lkBtp~4h~=}b2{5o&Agu0*QQDov=N zUNm^HF$=e}%!*zgH^<)dO1uts>E2=GUF^$0elItYPYfoSOS3|p|B;hOOxXVqq5rrO zY2jTUQpMPc1#j7kcWLKm?YUoO4|oZYgPP`|G^ElUk~^)Gr$BIY9l|hJZoXkNA5c%b z#F!F%6$1)4*^WbCb$N(I`?zY%G=@A2vn^TD>Er!fQaW=I(cB~GwAy6QRp!KiB9%ud6)jJNH6!ypP31%tk)sLanbKEeP4&S7Dj;`D#v@%F z8fYU9EF8JE?PylOir0*}m_NqpEstb54u|bEME@a*XO;n_AHH>1-v((a1$xQfkCjg1 zkR+&QP-uE!Rh&9=rpezAn!-knyGs&5VI3KjDqYByuqR!mbcOuO@u&Lh_@mvA3vZFd zdHq&MzqjK3yusYgxEJ#-=6|!`V-i?T&ojrHlLIq}=rn)_)?4V*N{X?Z--JtCn+Gjc z^dh+VVmB6)V_SI~U#-x?fPO^Erb1p`@AfZC#Ih3)RXv=3mfbV%l z49mU4rfA-l(@05O4eh8|sw9}PKbmF##;1L+HT0I%P6Jv|cfZ%DIic4MPAQ0Pk64#NDqPv+34F;Ho2GgXT%Bx z!7ce2v&`4l1OIC}P!#cD`|Hh3Pl0PRya==oNxXJAcsS@d>exTPp&Lhmz4+ zBLpl(@O5Y&6v?I=#Q@U*3FRKIv$%FxRoFw}aX_Yk$yuWPIF|XM;3^Qh&NCi{MHOd1 zd9|e5xE3w3Kp@<0#kvJW0yUIi;_giq6>&~(l3q3P_vOaJT){0d0(>QYCog?pr%QJBMX?5f%|X-qN1W|v^DF10#{__n?{Q)Y&&Dv2`zhPe zKn}5GThA!9YQv0!+5fr)peefufWI<(pAGMut0Z$it=*NE|M7o#l=PowmEUgr`9?a^ zV=K&v^**rrFT+S&BIM8|vG^jTk7WhMwR$(R zhATPZ)?BkV9s#Mf8fripE>w*9(ijGS?7c|(2zv(C* z*a&>D>UA{r#Xc;=a_-_d03|)sG&sozN>JlKP(*A-Qb_=I!TdFq6H6Cu^Ef%OzMyS# zJV_HVrKVtT5kZy4@h;+$CWY6CQtz#LeFezTPlzDhl`I31N{<#Pb9h^(#4W7hkV=)% zU-xKwuA4U1iGtZQSA+}B`V%hgBkpf=sD?^m<9IZGd6=B7g`-8K_-*o(6jOme<9D@gJZ!gngC{^+kjDdbsjZf+nV7}VY z^QJdPV0`@Ue9})bTe8;Ck|K0Cg)2~|ab5->^gCnb*LVG2w1nWeLP0b|vbt!-s7Yxj zR1g(9v%w+`XnaVd|C0v}D>cmEEe1<*iDb(zi8<7XHG*HIcr9~ydRDiNWw@c@J=bQb zKLnShnspNxQz)iZf`S6ubxH(D#YQ9|p0-(pND}a$gRR1A)wh4)a?27Usy#pYW@Kb+K&F_Q8hV4z?Xx6sU0nAr3VV}u7wv2lvc zodJ8ausYPkgzCU^fr8CKy~DArfK*bW>xLU~V7Yre07b;*PdZYMOVHx>r)#Fb8vG^;7Wk$9ISYIVKPAwQHMRFa;n_2oV>7t9ntJApL9CC22LH_8caAlaV%*Lapc- zm&mg?Ftz)VE4$SX3Zi~|@jFWHL|Ou)IkyzEa-g4(0!%ESQd036N0hx(D%yzrni4&e zE}U$K;tp(M5e>S5BY7nXS)6dDc@K2c^o1&y66J=fMFtFXSYBNW`nnlHHEu)FKO7BqjAUGAz}=CQa?CpK#>v^W*da^MV!a^11aWsNp`0yV~H9qy>x+>SCoIB4fGBv72%~VyIUW_czKWbo!kBxIW;o+6eL;i^)QGr4n>pivmfMw(}TliAmYttLY}kN6*?Yea1+K)YCps_`Rq(FjRHXZ+&?BxC0b z-esop9+O|Q&B{Gr9AR|>?RRg0YW3G0YV*+zLp&b5$g0+$~n-*rFNN5G?gj%a?LsS%43A1#oUR z#H{_4U+bJphn?&N|Aq%JIUL}YMW4bYY-h+k7N*O@LvW zd&8|?@ol1nT<3*;{;IrVO=uY9*G^^FuQMKIqy=5?)vh{zlQM6EP-@b!0~m(?GVcq? z4L+ikaK(BNx1LT7I@YuYD1DRtM3sW6Yl7OAYeN^f7I~(Xq(u`zg}< zNURzT#Rp*b3Xidr5^n*C+xp?HWvw@-!n^=kvWj);V*F~-wOQRmHT*1y>+&nU_>cXp z;YU#O;xD0Z5v4JIPI|P}_$s&%o@G445?Ybu%PE^Q&tV<2AQ3&f1-m|yu~*~Twp)ojiNn+;b;h()RSlKT zUi+D6lrukq*zAsqNlZ>tL_LfBWrRbio-ooqNZjf(16J0smNg}aB$ExXij~P`xPKs* zM~ac{*;X2m1YAg0JI4&G@QWDjz9IHNapUt@iS)u3EfN`lN`NxKHOZuWyI#wBb3sh` zovWT)lG7HoAOe;!fN@l>-0(0Op1Nr3w=@$u2WC$;&W3Gn_(wXOjxB%~5fNf>zS@B} zQO=>p2l0A+C7I>Y9y1Z7=o*O6VczDqIlZGt>cGujQCj7ckcVvP%}*=af(gz6exINzI6*1e62Yu?7=r>#7N`T=*EN6 zb}1AZ9*o(02XB7wyg?o7S#~3<5H8J@dQlc!9Y{SiHqshgEukt#m2tBV_Ru9#0)e)i zTYLi)!qzH~JG2D*uIS$WT!#sT7&=N#QcOWmb7lI|n(LP(L;DQ!Y87f(qUO#{gN(ze zRQE0d11C>-fdJ;s;{rC^qI#_J;UVs#>ib7bAopc)fR)JuwWDg(<|sC2d8IXZMgP<# zJOmFdSDjZTN_}%g%Xcd@=W$QTqa8W`Xi~Kt5O-CB`Ca*@jcm;bnpF#r7JTLw!tta} zaGFp_(f*b)MYiC88go)o>gFqyAXT87pZ<$e&@Jq>-w!a$H|w1INaYxFN&&*bOB8VC zbYb=~&BX_cz~=)fw=xd_uhg~1*?g@Qp?e3ba;D!rlVQCR@O`Y|_8o9xS99h;9+A;+ z*7}~GjAfR4@*0B%WYhK(CR4k+OkMq+n*`~SJeKJvxfAnCrPXRvev-3`OH%e+rSfUP zTcHBK%3D7Z1Oat*YehK&;ilRa{}j0MgW75o^mt0$K|Ttk#56O~*u>7?P88|NVSo7+J}1l<1446`6HJaxJ#eS0VTx2m zTQ&QaV-BrCz0|v2Y@Cpa>rSg6;K1QgQh+hoJ_L?!AyD3(M~>G z(_G8M-}AtRkytIE0<;YC+oUJUQM-$xpxztBHcLZXt3$Jsq(!K0AkorwuR}D#!BQub zFzw!G-v_}`Me=XH8(0^$0k)7EvjeTAss1)6S{idA2^76E&sA<~VIRDmtIs*6%ej6$V5&!})Oyu)U`XI=~g_nN$0s>w}_?v9FLk50f3s<8MAv zFrAp$$^20KJdmkxZ}GRXoGbjDzQ^)#wf@t^Wr&GjNVa{Se@ljfb>N~IF75FVyR-5o z`bMriBWoEB>cOk_)Z}E2=Bt@YFsfm)bBD!x*#gbOlOM(T8<#XUlD22!rk1FiPxF5Z zjSap}kx;}?wCOYH%+O0%Mg}M2vgSRpXQKZ>))|9)#ldtH{tLTnoc{Jo-7sdUrBBB- z@0G?o&%BojeT(&so3JcS;eP$;U)&-f|6h9)eKt=cS!!N;?z?Nu9*0p7Gul7)+Ib?m zNF*whS_ESm%oqXr7Te_aUAah;vU6MNh2h`yKkr40qQ(tK*j0OjBc!Wu`UF&fMiwVw zO8nRG5y|^1po4b`y#l}R0|#V~Jx$28@HF<}4k=7+hunNAG!tg7kqR^za4Rcik;=&V zY*zY4huAKkpc?srpQV|5OEL?W<9bg@Y@Jj&zbIg}U~l7xj2R~n&nbAI4tS#6w%Aa{ zFMG>ROrszd#ury1PQNcXAke?;awK3nP z+{{+FWBa~si~2i+0hzlfrdF}rk>HlICIlRtU+0k+YDDJe3E=9j!ReQBDFI2ccxZOO zk7w{Imb-uXX@R~qnM3P#m2|#H^>$zyVUtOxXJB!tu|Py-#*9GpQmx&!&DWp-!fcqQ zFybfWpgsKfW{rla^{6+F|>X zEkV*<5bfXwgyPIp$^3;J!Z`6|P&?APdgRXoz1m2jpV%XtAH=GC&7e$Gxjg6L&R z_Z7XP|2~t3=83Omg0d+=R>Si^yffvjoA3HqQ28^STX@s&TesVKWx$=obB=D*Zuy_(wDX4r=f|0>XO7ncLFXwGUrpWV zWEL74k|Jh1qaH#Qx{^MkSDVDW9zuae|A7$mz$k^9HE{Pk_UZu_}y<9 z2ZOpX3NmP9G^TK{qR^iozDe9n{2!X$DY&+-Yuin-Vq?X&ZQHhOW5u>@+qP}nwr%In z^L~3Dj~Z2D)@-f!#`{7mf9iz2K_Z#<@c=mln>%-Bdr`v`{Ymk#TXy|B()6*5^Z7f1 z%eptOJHNw30+cJo#KBVeMs^;@;3X_h6~B7`y?hFzi8Vt|dtRCwVnyj!LSvQ3oZIEM zEwpM`aK<8E2}!GlK-Ywu2Lj41d?u?Q!KdyjT9+Gu zM+Cp&O%DWqk*tF@c$(KbLukH}&}fLj%(7i9e@VN*%ow zJry21jSitfMwYQE=s}pDrGeab(n`$gPtil8oiF4{alHo9ci&&>%zOJV#3f{ct|b;cju-8iZO>d$+-wsTLVh@d^mx%?gi93DK1fG9NjjV zy33*!isVre1XmY=US5d*uU>!&=@({M{oKzSqkEP2I`KnVxfNI;H`$$?yf5;bC26f@`Lt9|F9<>NPIF9Rn1mh8XjP@q8)|iT9KGTFbMkvNayEY% zznF}W<@!()tzKp;fvQwgwM7nz>n|3O-2dXA&hdb7jsX>_Ii zW?}?0B#WWxf?DU+0HsSMUoTQf7*Px$vj)Lh&7;HEBX4|TjNNyES7E>8O&3+x^+jY6 z^Ddq%>|cyL%6*0+qEdDggLk$@O#Oye&LwhuLx>U;$kA!!rr<}0?RincJg$}1RSE%} z6Wwz~MpV?Qrl+G&FrsFJt`q}OBYv|~D1(YuR$oml1I70~EYwKc3QX1(2tk9eHz&&8 zdJf9^#Un~SuP8GRm7XC_d=2m*H>p7Xv9A*OyA|EH_XXidfYIIvQ#ve2_TC~G6}wp2 z*fQaTI`HULMVc$zK@?0ntBT$_1YgGxwo-hapRmhUD+PhM4KG549X3n~ZxmeX@Q5A= zg=+t@++}79g0{mO8AeEM6e0b|?*>tn9}a`Kdh_I8& zzJ2vUb#2d=Pa{BFExGY0>-^9}(okrHva92#cBZ|n?gtrB8NOUKbRk661z-FH5^34uP0>92e@8 zweBAzLz&j|{DQa7)^uhoHw){%+I~XkuFc!KkN_$bIV_xM^H&NS>>Q!mU7Zh8RA_Pk zZVXjhYnUfZG(vgyFA3}9uB}oYrrN%%>@EbRn*`X8vib|shf=1QkrwvHL95lz>Nj1+ z{%;T7(bYvm*mfw&oLgr|U84-hWf+T8T#}$(8bsdt^3+0u0vR_Y$LDf*Gnasx?$y(bpRaC(EIdtki$2% zARm{g?))zX#4m*l1!2Xk*AMG(j&T$ z<_TX?Q2n4KIszg{LL!|&f7@O=>Kq~?4&Fescp*cxG{K)Y(ELI9iY0hiqq((pBKZ}5 zeCZ!0M7g`72DB48ub$?Iz-fYQT&cdYgqJ936J4xg1K0TkqPq7*J1LW_h(N4LSCg zHgRDqUpnzAgJN-Jiy(?^H0nQA<*MfD8prkwTsCu}w4D<5&C&7pNbAQWJ8@j+a4_#> z@txKfXgO5#&bc8N;!+tnEuxWV9C;K0eePoIoyFdWDq5fUSG^ISvj_TzhCIQp-+*D4 zMzrA~yy4OX5(XJXYB@ zKhYqYz!TKLyw(1r|_y?d~xc<-spmE^O>eDXFR{Lq~Vj3B@*u#*Ix|p zvTCzk+vYlnT#g25+WMtlgW;S-zes^7u91ck-I%nm=hMu_k|9Jg^X|R%6NTw3$Hq3E zAdr?AoetN@(pIaORrdnYw2oV217GZs!CdoAed>K7L16SqT9+6McuNOei#S8F6_xVB zhUh^IGTvK2-8hHt4Z>B;X%*ISG$wiVv-_mWCo)3BX(-Kx6<>~F#gje6xvMFZxcNGn@* zAh=s-#f}*V?4ISVik76Bp0ACbuU}jz??l$MDRrlj=^GybAMmvg+VwoWXWY)%;K#Nk zhB7zGk&LeUSih=kZ%yJ4YEOUI%UudHT=r|+DRah+drwj6o^GLzNQbi_tmD_p3Qa=H ze^nS(IKTwpi0TW+XS_Ozcrj>tRwnC@x@q%3;!x5EA>9#+-m}(6iz~+0v622Z5G=7_ zJ?q~w#Rr6I$60H+u{F5bAI_Hhh4>-43Q;{0F#-p*&SCwVp*z@OS$g0I3JpOxvhxCt z*n#B*#uGd5FU0mH<;WokHqRAoHFi-aOHscylB(1e`CNx4hg*BZ4pVx0%viZdd~T8T z=Be5;G0?Nbm?eG8p;wT>7h?FfS1baebcIjHr){{+;KetRNJt0@9a;DbMf-LUxK-H2 zC`BTcidm#TlZVlH+1LL%V?d^kI^vN926Nx-8Y7?2u?Vg}yvv~tiFm?I%KcoU1fQP3 z-HFl%lqS=ONr{cMBYTwlp9*hBqdn%4e3thLdASEN~UY;IT(~M zj>Bds!j5Z2`kl=Z2CYG^dPOb)kVm!&N7)NbO=R`SFa$0fegA}UiiL9rRF#9&xxiM3o)JC2l#Xkz%8DMmaor z=4WfGPlpl@|M%vcOLSt+$NxkxX%!n`54ou#(RT=kI#9$E@5z(y2@69Ubu~a7PXh6E znq-LNduNbb{;b+o!+COK@0GoRpSD4eMwN|uq=AW)!Ah``+=1=+^fTTYL*{1{pqG(wbG@sKQETB1U%~6dQ)}9YWZ-S zIc%?E5I_FJ-t}I-PZn3-7nNGa4ClmnO646HuSEBlm4+Rt-F|OA)7C!692>gF4EESy z>I)sq5j~VYp~PZ~IXg|C?hUwY+x-<2!1A9O=X1&e%RiI`gA+94W&LYKcHjoT*N>>tLqK72)fq*e~w?e&Aw`C zPS6}M|GT@v1aocald|*V{KM8s)?UlV5F&K%o(?Bw4> zR1AG#m-1M`Rs^gOujCN3nZw+vqb29WGrJzy9^C;N#`2zo%Vp7Pki)Try%M}DFlmB3 zh1Y+9iN6!h25gSSD8u`uQA712K8yBNl^jH3+UtYC_WB&XYi%@*!Wv+9qdl0;a#IeY zyj!^xSPO)~{viSgt3d!sD1+bG~y}hVaX5>E2<341fBt{w$HzIA=lX zP&ikWix?y!4&@Pn(!*>kK<+Wj%b=$FaV(lO#Koo)w)XKyV91PXb8y~C*uTy7QAIx2Xl9ae^&$5+I zO@z)bjgvFQ(acTf@+T1^_b;@4l?Kn`E!Zd=k^$8b%$ilezXn(vTXG?YQcQKn!#|E9 zfnvGSh_T#d+Ld}XS7pVF@vc5xWbH_LK9WYO9u{u^6Xl$%ZXnk?@!>8_X`sevs|^Ii zXb*l`AVqlU*fP1BJ-=z;t`f<=-EjzK3A~?I@6#?`504nkv7Ei{-_`JtAhb(ohrdSQJX^ zkq*-%7qApKu0k>J085CM{%@VAw|i3c7k3;IKSQBk>-vk&`l_ha7#9QR6h zp;>9$kgRT77`9i{<2(f$^KeI73%u5`Fu0qQP`Nem{=c9Ghcfr4mY0f(o#q09>@@*ZSp394EaLf<)MOS6k^S{hD?qwHel+JM_ zt&xFB+j^lNpUfxYWGh>4p#8SzcoP5mZ84<5U$^Q;7(8s|1P4!|#JT?JKhj|n^Jb9z{?}V$yblmXsZgXZg!=2%s zU--`ObN}`Ag4j!U=I)5)zoHx^9$zxG0E03_zfwJf0dxHozgtkDLaSov467zk-?DCDiXq<$u70NkKih zgBR+6kke^*zw>}-y6nKt_r7Xdbr+6fz4W7IGxDiT03EG_RjmoHw#!WOI9Py=c~ z+_h0zg8-otoD9B@4Gm2q#{~S=V(`B7yzy8+bw=YsruGSz=zp!<;q7T74h=Ft%JHMn zEA5-u{^l_H6?4OTK**JGWYpM*a`!mv95sEm1c+FhEL*Di^p#E2kyWqYO}xIBvB3iL zxlQ~bI+-Ez?Zn}C^JL!xK{tCyIb43WPO^Kw+(zIzk@%=`)*E63i8S%7hfeHw)))>M zuSmd@yuXZT@$D{IK+{l{Ea1wTIMAAOjnDU=+KPL*xWxj{x0cV?8mn*)QWw+II@b zofT}~NjnrnBnG(M&c8c{o4dceE>EC7K%)~W;G4PNo>?o@YDGH+<|}=>04ZVbv^CWk zCy%u10@P2cR3T3m($HlwH|u=xVYsFc2KFw7pMbz+3Q%lI7Zx}z3Ph}M`y4D2m zFCQybw?Mc*CzZB!+YOdmJNuuQ_*y~x&n_-kv?f2;7bohUZC}J!47gst#LG9=FShse zBadBH9yIRaNP^)*%r{KlMhREee7ii^f{PJX>$#Pvs-44;zkUSD=kQcPK=<&|@PcjU z{Hy0ZbJlgaw(YWjgb0)uz~`S!MzF73jZKLnchhvxm7c3J(ezaXo}Zhq0|+I|KM0S{ z2aZn^_-HlOY{lEv^7Amgwl;~wlA#TcXt({<#mTti`7p(Llg4OzoOa@|eX-zys}%(c zRO=AMHGXjTDBZLbDtHmwsl8lHDIZo`!zOsv|Jyb14C7owO#lL&kyF(z`qfzMi3orot^SOp&CF@|<25{Fmf_g)CQK$n(g zG7dz=pSf$yA_~A}yAS`xM0F-jcalXXC(%m=J>&>D7Do~(+%k|{#!rJj()W$Utjwx- z(3qk}angWLI2z6@DrmIe+&ip)mp6!}Ua{yB#XStH6<-@Z7LdBMCCfG5=Y_85flx5W z&wvqB0IIlrrsbw&H!wMj8#7qC{B;wE*nct9|){ovbX2`ss)_U zW>vUtZIG$Q!mpqS!*9AxVVaOZFLnm{!f?LEvSi?B#M_Wb6P%)v3ks`_%TtTXFTp*@ zxe)#aeK(8bBdnun~A^~rFnpnUtGdu zU%ND;SFbJ|_fm5B@Z{pwO%n)PasnMF+h@lCz-MbaqQv=WFD_btoNmw!=$3wP5~UM@G`S>$Yg#}b#j@&iS>acV=I2Cs_xQf}m#e3$!&Gvl zUazgWcIt7wSgZ49jOurFW0Z}Y^KkW9KOtGd`1*-*`m3OS(xAnfb9GIN2F+{xrf)DI z1?Ihxq0dpVdQ83O0$RH^H#i@=@PQzJ*WIe&i;sC8o6xDB^4Lt1r`y!H5t^L-^1?PVC%KLta?qR9~je{kqHYF?AyliiEuos?e}UX%x8)Qc#{-vvB|q4RKuqptv>U^JQ3Rq^YL8fzj7 zQ(5-K3%OY-gtTjpB$%^{CG02|Wk66K2q)4?WVIsz`5{AcVm^_d!|v;6bn7_17O@rv z(2k?ebc75#gGh82a3iUc=$q2tC-K2}kgW`IGvyUI5L~RF*89$1!D}Rg6eIxjSm%#T z8nHMJs^||(ca5#o1S&(p)VN5~3zu3axnZM_g0TA~>cS#X`~lX)7KWAw>VwDRD-ROv z^#Zn`1UFuX7)Bqa*U5$E$N$q}o(a(>62~CV;_IFvxuMP0%3DI`sGXlOA*6@mgEBP{ zoH(f0x5$Pb&))b*8}!1576J3gUSe{Q*NxN2%*^oY1|0-uKNhkBVB3uY<{*WK^1Po3 zh0@h=1MBvDiy^xiK@++w>uZgNB)@75|3>&MqJ3~*eR z;KydXNi3DB+R?K=Khw{5q zAcyEyo(})7H}Ut-m^VY!uiZNV^n8W^^W=0dB;4~QPPVLmqb{zUB{#DFd-8cvZ*N0*)G#L0U`&@@LJU)s0eX7>!HCFEi@3+Z$ZaRIs1A)O)?zS5Xjw@9t zS4b`9YUspqV2%UOz(Ky>c`rE93vd*y64IiSFuM?MYiw8vRVXw? zF2AWCBJiMG2xTp44?uSJkZF0p#$06JzlM;ExfgPw~h^?Mb~^GMLh zJqy>;9)GTx$h={2%t+cP&WN8M5NPStd4-Puw;gU;!5|$3*~u!e<{U)EP_)se?3adr z4GM4kVs;bzRVb`Rsj;Rn`|(Kblq5WJlo?x~dkC1@xplCCRtiLYH=J@e2U>?Hf&oK0 zPMqnk@N#-XzbuRWtWa-i*jV$``Jh@3_MI!GRHbPcoqh6__%a2vIvjFc(?Yn$4g=6I zx|*SBZvH;Nj)a?>{$#(pd=NO`A>{Il>zC^z=|$Zvcevm(bo0xj0n=)5UkE|%5cf@3D~bApED9g4y)D1gL6U5#-4;;tkQ|U=d_V%}s!kMnfayC<4OhE_$aF`>;F+oJ}X3b=|TBE4ITB9yW_iqyVGd^E*zWVg9vExdJlbz z`vs;tCn2i@&T*zBT;xM6lUn|{N}E@Abmm&IYc+GJhWlcrEFY?w8Y;*_wbJm6i+se? z>*#*X#PDHeXjND{H;2_XZeUq^qgbiBncqUaw-+=O)%d&#<(^o4_#_BF9Ias?Ty%{Y z+h;C+#PtVha4%(7WTnJSIAON2hzlc`68cIYbSu+W7L zHbIhdK?3-edWebN;rivb`Ts|}Zix0xXfNJQMeqh>8MBeLAMKh_`VQ3?#2ePXb+>;v zuNe~TS>>mmgGcGHBnNeQfsd)r^UCv)M6jaU1Ev;Q7+xxBv+rpZlJibxq zI2^7vAJ*9gXl^ky48bE(CV#GXPgxQHQQ5CO8p~{kN-a480ZcNrh(LV7Fw$;w-WjiQ z%A(YF4AYy4cYA7-UlYy(dk~E;2BW4zRGUoND~Y$zYn%fCGLFEO<~eGl0a~!+izVM^ zuLufJ@4U4wf+%DeC-tru(?PDR2~*M)yA zw*RbB|3_gIo(@I<6#uvo7zjN-&67GN6y8=7^5jhW zRI45CijXpv@S&wX55;^1nDg^ck1!JO!e= z#yEcWx(z9Bd*>k~|5G>jD-hvRE81UKIEoftRZN+!$<2{kS?8hwmy}GcE>HSXq-r1Z zs#VpQb$@o*+g&Wbt7dJpO2SgTASi{=C0J?9bi)P0O=GA;^Vs{B~?pAG+u z0b1b%j3r`@}l}b1GG$zcFbf zJuTwM(-xsoXoBWGk0yKBM=_!BH|M^;(NFN5+o*5YV05sc6t7p^&{n{w5(<`qK^vCy z(+HLW2ETBCMJdpIuZ6SpAU%N5DzCAZE@U@0Ee$#rAzetb6@lFrq1X=YJa{y0U7cVjI?o)l8b z=&&geA#4Obm!>Lgk)=pO>%O0BOp3er24zB&0`^; zEvZxHZdB=GK>y~0M0mR1J}=gIVNGroA@@qSwsf@4KKI6qZ@QHyOETK3$L|+wp zWym4@^TuhV%bGuPm{%zn0LklvyI?chb75=PvlIYRD*bn)#X*OJM4b$hk(5FCKs1!C zm6a`@?4-T^C-ZKI0ElhJjUvDDQubeZDM<|EAV~xq{YsA4ViwGaZ{ zeVn{RilzsiasGSDWhd&Qx&v0TnM*RcA?m|3_8aFN^w-W8z~R}tJnzr!Y(`q!FSc%5 zcKl;^4my2NQ+hX9QlR4Vx^4oJkuq#XZFp}HL>X}vWHT%==Urgky|`Nz09V+-WOsf) z{1JgJO^Vnq$-UghBJ#LO1}N7kjKNPKTd)+$$QdPkvi|y6VJ~3d@+y!7!kq&f0Vp6| zFHECwlLegtp*1R&`+Q*g5a9e=ptNH!{4*hBw+XW}2qU1b78kjbvhI?!RQyYdux~<+ z3W&Brm3R{v->oPV`2g!RYoHU9FcSea(#T`I3ONz5HutwDx4Q3jN=TE0rYfvc~L>(fdi{d1yZ8u%hd&;O?TIpcusK+a#?LZ%%XD@-=DyJ;Iwd{|5HqOuX ziSUkNV*~pEea1+U&W0`4&bk6}l2p=N}0FI+p_%4sH?z69w{o}bjA()UK>(1sasA%+8*qzZ!Y&dNl83Yh_k=3jGAP-n5;j4biWQvh>CXl z2Z>k97s-?}=8MGLe94QDptx3$Ehskb5Y)P!u}$}~QZl0{SasRto(;W-RHJFr?$l1x9y~67h`S58`o8nD?x4SV6BQJiVh9))7y)(2mV<9hv;I5Hf&A1`C zb3h4#$$%KW(k^0HMQcKoO7$ol&b78hEA7@-BPtJR+-Fi8{>({*k_t!=2KAADNowxX z>Y>azql721?`F&>)Rz1^d7=og&v|q@q9*1@l~3HI8uYkvAXTS0G5K|M*cDhJ&_^x$ zb|W~~Em<4S12S3b0$3A(O0OFW98R6E+7sK4C!FeAaoRwoQ(-lvKpZCMhL+f)K#U6z>+?s#kewu%H4;1 zvipIVjeZ6H#MCCmI;7F)VGHuAqo~%%RI+-oW(HdZgrl@rpNtI?xy2qHoHR1WPjv7~ zv&T%>Wm_`@AIjQt=^`JD&`-$bVo75?xKbgFP{13v>)J4LzMivAF^mmpNvs0i+Wh+a z-H$eaNfs#bmu`AJMvc0jt4tG?sACX;^^raDY+p#73uOPRMY!_+qS32q#nhfe{zG4~ z$q?SMJYRIAcYh^6D6pVj7$1F{)ht*s=`zylU6!REJ+S}8n!FV;Y>Y*-m~m|Mzpd5E zYcg+)UP4U`Qg38+vk3w(+iPbyf3-U)JnpAK-s-rY6}|3)@rL^O0bWgSwAMQ3>un7x z<#b9B)E-fg+|dw|XMQ1I%Y02*Lo@X6zIgI=nITeU)qkH(VMKuXH@%{~@dN9e=&gUD z**V{2?3s0E6=*vx{3e3CZlZXywbU8L=~O^gp#Bvs73d_G%pwr|$EWJ(2GXFpzKU&X zh}fJh_&fp-{>Ac<)`S)HXvZ6R45W4w8k{f+H7@I6{ZWGouJkiv?eNE5?#D_@R6>2VKsU zG)F6%^!D+1@F9Z)UVZFUmd~`N!XKp5gMI5JJN`-*kfR+Bl#`LUt<2tBM=xa z32CqYvWLE|=ogH}804l2d?s=&SPGBZ1&@p-SQpSaq^J~g5=FLi@uU;T<>1qE=5bie zgo#RmncNkFO40@qBM-{W%LD zj#chearU@pHILo_1=Q&KcQo?j#lG#{=U{Op;jI}59V@a%1$a6?6H{OM^S?ix8147K z^@8v=b5D5!hiqHk{r|N9er}j<%#4%xnjcYiAT&tvCR$ps0#O+y`C2yHN*lx32bx5Lv6Hp~*&6%>5f@<6T zAB6dqix5wD2xDxqZ=;$KG)I-9XtB?8qRhQQ-;0~3aPlYSn6yW9v6qxTseESg?n^1sf~?azq> ziUhv`zSFHweO+RTu*no#cZd2iSJj5Ta50BJY`cNFhmZ;LnK%#$wS|iPtz$#tzH2}g z4@0cAAeczIsjH-#DU1tOpy`hnU@7Vv^3EpRz+&Nrc9)=qakbNgqj`f$FFW90>A{l4OZlQ)aZQAiH-lyTf@|k$VuN#eV&oXoxiUs$r{}-S z>!%!HRoQvhhDK7LawjDT*ZMBW7`c(ph<%sBsYg}C=hrepI@7&Fy<@-Wz5w6r7yz}J zoY_&otEt)gKQkuAUy`XWB1TAnEGJwzr4cLcDQHjYeXHj~-rwCpT&XwIF;j30fBd_n zt2ZQhfQAQFqy_JAJ?DA4KJn{6;h}=}111f+;$?I0f!8gVQF%GkOG39YZ%!D`B7G@u zK3xz}t9B2}xHIZdEwNVYFyj*Q?4d35TK&wXf1gtxgTLD7;U02Kh206rIQA<0=Vjpd zouC6oRP_!j80AMtB`l%)#fjH{S}~5~9rt@qDH!63+Kr>9j_h4}qiI%}HLEf}AUA9? zb0U~k577v8}*_Ea{vem<(X8NO_(@M%>Y0j zG@fPYjSQlM8o@6yLYrko2@aUXh z_+H%Ii=TeY{ud(*h5GvXXr8X)|M2eC*Om%F>ambmRWwXcNMw|YYib~&R6*;0Wi;1X z=?vz4r|^tz?tl6$#1QYk|9z=xs%X8nc2D_z=#8IJLq+)^xK`ioYDG}n?%6^>iuWx* zI!Uop6~x%zt~2Iuov?binK##cLFm2pqlZQk3_^eMYfW!EFn!u z0=SZ&_v*rPH=WT2ahHOA5VE!>P=QX_+*QrEJw5*L!`Zb_QM5r>25|bWB{r;gM{=L*ZSnttrB~!=_Otf9JrLKTbkJf zOw5EYi7Y_Xv&Jg{tdN8JE2b5A+PstzvcPa6o*IU4Nm2S+CQxYmXDI}rwuEHFrf|TJ1o2m4*w^bd26|EjBk~y_k#{GezM*)MPp*B#tt^Uh~<1 z?D}$fW=q0_jtJwqo83Yb?@%xdT>sf$=F&6iZYZAaLI@>LyE{wWh@l9Sz9SRFP6#@m z_p0J#i7hFT0LmRF;^C3uS-BnG3?H|N%=Ogu{kxXIiZq-5WAMGEg!itvHM*EZw`MC+f5d?oBe0P|21RbTO`v9#NaS&U*4gHvZSwgbaWlvlXMy7jFJ~|X6=rl2Jy|k`+}5Xkfy4-b&?W^Sq6GS8 z)_81P{N-LUbSun1qv*Y1YOkKBZrZtsHpYGO;(Yuik{*Rz|JIX{3NKP; za96mcTHtUNL&uU~{lxL#FUDdA`u5MtB}t1U!K{HtFBGs>YS&(5nktB;CeTfERN#~H zWhKeu@H)b<+OCgrFArdIdLoE)iYB;uG+2%8&FklH{5YTMB~mDEmSN*aCa9;Bn6*X2 z_g9p+$S+Q-wWwVMv1IVYw+M5NtwxjfNK3EZEx7z=BdZ>`WTyRS}Jh(iv}Y~zik|lVvT-p%E*qA;D<1{^&W}~ z2B!j9ldXwxS#_^xREv34;0+uicId)Luf`HOy2?e={tl5!66>s%R6Trj*@0A)bP)MFRgY-0cs85M9Sab@lV6k_%51r>)Qm1$_e6c7fNFz)lhU?mzwLfg4Na5pBP48JIL*(;_Q>jHnCq68sc${vWJvF-Y`rk6qF`R{q*?bW8>Z(~*v~)FY8ql}JkiRG zAW6GITEZI|)1v<+OJg;y^BUrE?7A<~T_FFjD3B(+8^eXu?M_RdL=s&Uzi@GOaq6;w zTLh(MR`k%tBa;h7=?tN5$xZ_HKGv^~KJ3FLtx-z#Ty|P}@ zYR6@kS+P}CZ;e)GfkXR%cdC2@Vb9&`iHFh_SU1T%D1Jokrs9)7g3Dh1Q zea`)|`ESxsRe433>9?b0O!D<#hi_YY<%;+(;C8KUE>G5NwpZ;_@MQJ2CEM^b*>FS% z*AbT>7vAwp??3n01ZB!u%BV>7a1Ir_8H%!pOKn~6ljKHG$kFT-y(RpT( z3BpwERXijVn4p3Ze_O#NhWcYM1nNiizwT|gMY*-kw=hN&q!O9Ip#TR2h(gSm)9S9l z?C{q78X!vw_;QID7@{3nL|7cK1Q+LF7LiMXCJuElaYO2w(l3=sXe#gznmGOZ%+RP8 zH<5+tk_b9jGYQDDM5KlP)RO=$0{cQ4foM`Kwk@3&@uWOh(rhI5A_MXtEid^-RDbb~ zbJ(SQ9Xk2YNGB~(TWenZ+ZX-0!>Fe0Vb0dxpm=TA>w{7I*N4%_ittG~s-dm@&(-HM zF6lbFqBsjRR@zrRCWRK=?B{ia*RJoaqR#x`0J1=FE2RA@uK4K5sBLqRVa?RdtE?i~ zyyVEbbYi`^ctfSCB+FtWiKn!*4rLyL14&DWhrf80(=N3=SD~_h_=4j`xX00mBn4#NSLQMLeB4qJs z7VzPGho_9?`%YNp*3H_rtI!9IUv-y$9PiIN@=3L|kks~_`=jk_oj`CC%{LOmJ^^$p zx7STM6q4_ejt+B>!I~~&y}zR`sOr;vp?)s6aL5HWz$ucTtX>5w%~c zFUTSfG3P9NzzRx8gaRN`c^0tl!tKNT0p6dL336iDH&6~34+m)GYoL(Be>@}jx4`mV zR0}FzbrM4wA!%=wl->ZrB?#*RffMM#Wd6#&zSkZ35tpK zQ+dnAqP@a$>F+flE^%K(s4Kljns0Pcb@Rd5Ym8MOSf2t_kRLr~>zx*ugWW2c|W){q4gy z-t@b~RQeAO>H9)2erm%`P<-5lX7=AO07>!dbnLeojyLzk2i2qkWT#tEk*VIdY`r5U zXgi=Ij;ZBzgc4ba@bY|(3<(L*&@n{pJh9raifAOjUMjuVM)k08*aBmGJ`ct@%2+Y= zEZg0WK4OpV8LYt;oz}}ZYN|*d6soHg4fB#yR+Lkg-p`dZtADY7Ddt3o{Gu)?KoYXa z4+HV~H#Oa_21RD=))O!I$W6u!HCHc28|moQUjsv2_Dn3=S!d%+8DtF>*iPluw7SXn zRkGYc8oX;adGI{nIX_`!h6MI$(;%5&gkgwSZH1k{=hs&QWV)W_($DzS*{bLD8m43M z#H&)V&V>9wEj@}jZ6(7pRV6m03xrE?4p-iZ;WGz0?X*o(C-aGKzoVtL1^+iBkm3p) zfB92!YI)`m(x-XFo(2%LG7A#R9q*Mw*?!iaiHxVp>^A2_l*_+bSW`5zG(OU&)Vss| zXgjAoTM3L38r+7s>f7L6*mDa;=(x8jl5L zp($zzZoZ~qN8tDFg$LX=LCVZ@H9Mm7u9&+UCzmA<jdkhkr%Xu`nN zCiRnTx-?VdsSgG2+aGfmwvzc((@~E&k$tvyFn){dvwGlM`8(RITm<1NS@Wm*NPqsf zl*<(YUIqd3O%S0R{D2Bwn~HtESlL+odGe=uYzWNV5}BFbY7xSb-YWrtG@BEy zOOsO`vsB203HF~L6aj?4lMG49dOh&A8>K$uW|R_1A)Vw_B9WSu^>Q#iFBSxDIPl{q zIub=#Ffqn3eX9>?{W1ZSGzpdZS|A@UzYv4OR-Hy9h{$ZBBcY=LY}!>o>0fI_1`X8g zZRitcpkNVH3XxH^s^|i5BIq9l%I{`#%yf&?-H6JSf4W|fh%xdHbSC~)&Hi|0QSKRl zR9sak|BtG73drp7qK50~$#zY)J=v3O+mmhEHQAbMO}1_G$!@C2{`&pj_r3V8&$&J8 zthM)Ad(Hi&f{hV?!{({0a$_JZl=Z+U{%gG~^=mmi$TQ-lR2=7(hgnijlL*{0uRKsl z8*XX94!G$GgK13NK4M7aabAvOw6QG#>rrB81)D!6Nc2zwwr4H~!Vg6;L#zL33WJGh z7X#%@Ew~eq^U^$_;pteHbfISg$DVM@f*}~emlVJtP>c()Ot?y09&T-pPfv%k-Uvp> z#%^~$nn}?s0j>|!eow}@HXfZ1AEIIi>cbw!l}R&F;6h+Z^72G;GyRrsUMblYiXiN8 z?=CE0xlM2!Y=mUQ(Vyaqo?bjD$~RS{<+Sm(higy*iyu|{o&vEz3j=lw%x(+`DCrn~ z0IozYc68lkhw7^E)XJnQ!EP?83hq^b31$(2Vp=-iZ8c|?r;R|s!wFfT0)e1Vjn4fX zuE$Pi%#1*zv$$=)4O^}{_gYpyAEzW`XbgvODgK=iLO+~+pan!=z`xth@W`p(rUa>3 zkr%6KmFhG@v^W$t=e~>an1(f*RqAx=*&fE0nqF(;A0GlX8|{eaE43ryfBhe~`6JMJ z=~^1)1tBb)(qGJgnHo7>aQE^2Dix!S6Lohr)6+DE4a7G3U(4j7X1*KX@@LNLMk^{A zu$aQ}T;t=IPLdo=nkbZ?eg$1kLvt;n9*sc-95as;Cu71qY+@ zbMSG=u&A49-`lgtB}Li}d#!J*Q|Ho>53HJb7U4#S0*cU9+q-4emmIkv`g|suISaRG z$hgPv9H!iJq$kDkB2N;S;C5I)E!_oy9@IBKIyR`8dabc%pjot-N^+@KQ(XD^`K31x zG%0ElN2f$=Et+cDbCmPf^vBJ-!CeMAOH|d>X=|1jhg8NXkrhL|hu7Y23s4IES#Rwd7T+8mF{`=>4PLdM$oUqE?o43w(7ZB{=MQXO z8{~OmaN+*cSfG+K>xi1{{of`hhY%ipR_%{o>71*riU0DvS^L)v+Yvu7x6i!~)j75PPeXBO0}-l-vjyRM@+li`(<$LH zHjOxREWH8hKx5$m=VKEfr8AHLo#nzaA34+kNv(;4QkR4*>7`?VGk8;5W$QZu0e zILPa}rY%~PtQf%QJ;Ze}t&2vBvV3~G927l~;v1`Yq5^ymdrIiULq2Yfbk`7wufHf`8k`_z? zA{aPsdp<3b;mwKe$XgS&9a;J8uFvdSEUqLMiI3|;BB+Q=Jd2V>c6SRv>L%I>dQC~x zhgONuWlW4iRb`Q+bL5G1x9~NoZFKg3_z)!#UJ5!0DK?L}Liy!sWVZtR-|_q#Jl`r{ zg`UmMW(02U7EUhDf>w^hbC{&8Dl5t%bqif&Y{lBSgr?SX{BPhlpZ19gZMS_o&$q7n zEd(C9DUh5l_CU`M?hfzqevU9RAh$mGQ{ zJF4mX*wL@CCvvmyy4rUO(P;;|c?FrAFIPX!#ruVH&0O?*P?v#q06O6fKh|apqL(T) z=r%47j=cvI?0OeAa{I5ZVH!*+CoD7!bi!NynGL-PdZeJ)A2#k`ov~c3HA1&+d(k!j z%O=9bquTj);3=8Ebr^!b(1u+#d@OLgHved`*Qn~o743f({6t^TeNZvFqMiQ^qZa`7 zzx>FTZAT9Xym(s;nc4r7!dQzfh9c!BC4Lg@f*Ys(0=YMdZxiEMn_b2vyK?B_sYgw} zJPnIIOz&T8@n^zZ_w^!$21_efkFWSZP9@b`|77n><`gZ7<0UGxd{AQVAA(*@iv*hy z*14OHXnxj~rig)rflQt;pc8J@lRn9+yuF6)YcY82s=`ylgl~Xa_ce1V#UWzp0OntG z4Lc@mj!qB9gzbqz1r-k(9u#9RB@dZa(A}TdG%qcWQK93-*u|Zz?Lb(T)ouS|)-S4N zTi_$2Wc>ZTPvW)4?c$K2{l^fqPSiwetS;XFad~iy_fGGg?~@hcfZE`(#v#9Knyy$S z0_|fW^*;-i)4P6ZEyanIF7+AM4%f&L{M0%ncjz~|i>+R5I)oi(eeB!S3^~c=B7G3P zD}6G%t4JK$i?gzjpFDuW!PNsV+bwiAsv zo8aF6Gd@2nzbNlpAwUJxn)Bw&}E(gNezZ0Z5L(sNfGU|?6f_CEr1q2SS(64~}iky)YnG5kb z-B1Cf5hhA>W)x~9yM43Lax@bqCZ{e;$)XWVA-Xa8y8$YA{XmLUFs zJ0$dm>^N~Gk{_O7LHyy{^}#rof+5+K0h26%l8cS&1rqbtmVDM+6K!wgC8NR<^$JQ0e#nTLOeD~)mO#^En3b=IKZMiZ58Cb- z25TU5KR>Yekxw)%3oK)(+#+@=IEE$KR2|JXwh#2foK3J4dq!HQtU<63mx>xNleJ4* z2K4t8H;E~a(x>H3z-(;nxax!#2^InNqhP?|s@~Z$mvYgTs`o(7gUz>5gK+ z@B4F}VPQyeuKEnS93m*Q7RVg_D;ieQBOqo8tj`hi*Vn~b;BQ9X_wy8<_2y7mJW{gR zV=`;q+YIU7@(Y=VJ0-~wtgN~OSr)G zk8v&Ch#e2tLtq&(fXPV9cww6KCo03z;Fw7gY39zyt}8X^$IC5=HZ=Iawt*|VP$_PF z{E;<_oa#lTS890Vq%k@K?bGPx3m~~hwnx9|9JauxLF?&oUjL9=@%xLoUjmiv9&L$_ z5Bd#Oj`iH(J&up;_q1U-Ai*KqGYhz8Lh=bQj^D$Yg-C#y(T)DAL7s-tP$VT4)%;5C zUpuq5FodmtGcFB#gE3w<{Wz&Jm9fW0W@;2`t^R7_lQaRG!uOTipWsDbZr({}h3OZY z)X8UxXiTWH&-aHGG?&g$dcDXTAG>*d2?=O<-8M83Mfyimtq-K7)!?{=DG7D%p{rsu zs``Bvqc;TM>NRn$hcW!e{s2)>_{vj#z0x9@DFNX(QMRU^-d^66r`JDoAF>Fs8QyfemSmKx!gZB$88=q^E&BxyNs2UGXd~@yG z_ZFWL^|*M~`-|bGJLppa7jQfvIjUk|{0H(2;$70||C};kIiPkJ7%JEFO-!Io&+dD2m8xXeh4ltIUd`&^I0aV>fH>ok&$8T(e;08Euh z-qX(_goV6${*+5vS(OYytdgJ_iCI^@34v-}toQjx?hY6X_{eaNP%>jhSsb__S3M1_ zG!%8Nw*!Y{bB|9yqlNo1xz6(Ah(Jz2;lXW- z`!?3gUp|OwY@(-#!i=XO?Pl~{-;X#Kfi5LWh0p)2|Gmy}LSa&oPO>rNxh)w9E#DGc zQ5+ULmj8;z4VaIRn%WGa}bh(~47UwV${aGjJmWl3$l*PI1fvUwS59UA= zRl^0>Ol|yZBswo^C$=@8ed6?wCY_7UReOxj)R^=s8nP^2oA_tS zh@=k&M1fKI=-VW^DFe={ksCrmML#f)3aYdg4+$9@(V5?Aj1~#DL|3>0jb_hBY=Syl ze;g2zTg|3GNmFZT&dTL%IwH;&6C3?XUS{Q%gm_W73Dz;>!F8$i)FqpnV$#o)ga{kd zueQW6T1;C8(iX#Kfxun~MldK0&8!F3r#3tNbdiDIPp8m{S=)xir=s>ZbXm?HU&#+J z#tP0E>-{rra{>#PlASa&V`b(A*UHdN7djvMl1o-c#~ypEfWWr-ZncMP=hJY&GV9`w z=X(o*)5HvVoM4o*QOmfW{p;4%QyZG>LAj0{pls_U3nWW}MJy|uGybML&I?mlp=s%A znfqC+g{iyMVlN2w3N0;C_e@r$oo#rG`RNzhK<&EJ3=Iv98Yf1TRvc8N(C|T%45nxs z5;NeAx|1yG(Qke%qhLk^aDwe^cUl_r06}lZBm0~Ld+;AZoPEP9`BUC(V!Y*j)uo}8 z{65O;hFhX9V5+!g{-|h#YDhqO7PSQNz=7Bg zBV^$tNO-Jdcad_?0JZ{Jym*7eVPoP)w1S25N2S4=GWcyMxbJo*JDgB=-tn(dg;9(9 znwBm-BMBz$W(_v?(2q{e4rvnWahRtsSl^f6~`Gvlr{r1%mi&f_`=MH2Yz-JKnEF+Qtc! z8GPNXLGi5y(v>$2QN*_Ue*^jdiN+GtlcVjGInG)8l9oBDOU*_-NEggm4V!OzD^2JG zdYmjvR3l12?OKUnYc{Iu;`NVYan?uk`~p?ozaOU@zJF3b4_nwu466F#>^APZigNo^ zFV^FLW=4W%l2>Mi5^FhmFVj#AJVG=93k~_-)oVKUP*bai_d*#2Wyc=(-6hI?W1s(L z3m<{Od8T;q_ANUa=t=7}NpyK24R=Wsfo$JEmg?=P^k>p2WSdGto zwtG9gvDI8uj}GTR+D)>^i^r5XD#!y&Aua;{s5@)ERpQl|crb0M129wWWx>>fx8riY zBpTb4Z~P6~($T|!TLbhyU1Qn7wU5ST>`Km3m|*oLxb?C$iUZJ611naGvQJxpXkW(W z=iT~FJjQHi)au}G$8lfr9fGMs92`-dKj2hdPTWy=VMY{^i4E#I+0pORj8qeMia{~# zOo3DM&kwhAfBt+c2z5KSfQ$(bSJGOD8D0XHrWiCV@kj}zS0_ty&Y6DyswR;`p$n5O zM4Vu_9QF8d;_C&&fWL?viH*|8`s$NJ1nG~l7MiR++L}$_I%#t2Rb2=%uw8}OXCrJ4&oHcH zMn3Mk;REboM5E?sskbNNl}%5~tc$mWCPQQd;)iWPqZYeeMVnE`N`WcueEL2C-VU1- z5-M~6uDRAkz$!H%ji9%4P6DC7!L{V8Wjk7pb`4n~ot*aDRR|O2btUNt_JZM)L(lL> z3PIk=1$XZ>YFKrOscb%h4&>+#4OTxMZ)cK||3TB11m)NirVx9XsV$kJ&zGdvQ9*vS zGrt8`>83?d)%y%Zjrrv`4xgT!@s};9XD_cT?dl`V*jrm?(I;gnv4Rw|>a&?(b;J7< z*Qj0-Bd93P$5HNccyombz$L3;H`G?!?GlvIBZSLGD9T|HG;oNJl^@7T!R#8U16Jm{ zmk+o?Ev=6gpYaVyVVs*v)S(!Z@pGMEkscJRfBU}Jo8DA>6?RYm3x0j18Jkv3tyoNW ze!j&h@83z*8^88HUN?G-mW7(?QJ41zCV4x7NkzJ8AWya%;D zTL_AfT`j!QNZn-2_=$t}LT=tCCD0?N3WBybZ$<mA=~wO0xg@=4g6V_pe8*AteJ#;Rmcvw1n=8sb{bdo zd0~H4-ut_}$Jd*lDtkP0OLhEyq-(DMJ)NyMtmJ(F?L&zyvVF1qGe9?S z?$o`vzddgqXY&4vezrHM6cPQoFU`tB%FnLZzR`Zrce@C8iCUT*V>G$^+Ts7`F_G6H zBuY8dCK>r~ZP&fO!}Es{{=XUAFU$2d>S{06@+?U1OCx^&qEV2ngP%hj;Cf5%gy1f= zW_jDU#*Z=}P!*}e(`jhTl9nxtQe|v8?ku5yQt1yQz5duf;#Emg1B!BbMxD zkFjg=-Mr6N;$Bc_6 zrBjm=2#--z)17|`coII?xsmGdBPbLSv17tBFSbDBjzd9m(J=Q|u(ON#-d5odOdCG> z0@eP(M>2k!vveY!TvXjnSO|0^b5$c4y*2Wt7qMN5T{>ZRko1m;EI}jOd=`axpkNAR zo8GNFxYLZG1g=qxaNxZtA16hQ@7~?eqmfaWm}Wr`?gO4A;7@)Milp;YzvsBQ+)bC} zR)IURQeN?DMow?RD@I@%jXO&^hGIq%hu1RNN?@}a$}ytDna0OWlX>(ORJGmUR4^MN#@JE^^pAMXj$`V)&!mz98`Oz5zgMIV}fz?_uk1Htjl;_oR z5gtthV|tEUjX*uUXjBWI4*2lZc&NMdu>%GmHNIlF9>fGoYg&v^(8hE!o~H>o^kYMH zpI1@%GBI9^V&igv15*-YLQZ;|C#)$IhT^ceVr%MH$1l8MdeLvLe?22uBGWpSVoRZd zepXP>*<0e=r-f;b)Xp-I1zTKP zR)5L;$&qB2GX&JqRtZ%6TC<%KpU?Db3Ql;evbuD|^_H#@UA3#=b~6QJE@yJR83a@~ zy}m;D=fO2M{0Ic?XTI?KK`FvaGFhA2z4*rsk3y?LgP!Io6@)>GLn&1Z6qQe4cwTw^ zyN}}XYE;*>_-kI@fnU1hc1*6HR-T8BzQB>M!Wt-pUhFJmJbPd+bH*8?b;QdU4 z?sk2K_=h5kSiTZ`vouB9QI5yIF=<^OpTO*r)I&$WFCWTNzos!s!suzh9ESe4tddF@_-d+djnX$%aCFKdW$;Uvrqdqaw88r%+j-mbtY=UP7U`#wobsEFy9hGJrGd~6n> zN)djMU=XyS?42n=GA9?jR{Y5lJezT#l8sB8_6ht~Q zx3zT71knmdq`6vgNQnlBpY1sDb!nrWEHw~X>Gu1Tbpe_+xqCffqDWfd!AG2|lhWm~BbN zn#@p#=HPnWx1CyF;8X?AZV3qCV4WgWSH&r-{U+}u5HFOFPsJ{I+vT}v#^sVX76AZ4 zvijoB&yd`v0<>x(l~%vn+tW_Pj01S;#!z%+Rn16Il4Tvdk$Psx_nC)TWoL&(HGx8` z56qi2z;9$qsvYUNF|SgP5=w@oOlgJ-k;I*;C>JDD03=pNJ>0<&$WE7Aj}b%CS!=^j zwsQRj%Pms`3)E2xBlCYOf)ohie5NyTcm1fy19)U7XWt|!N6FQFj5%D6eqEl4@{Sqo z(r3gB%X6ju4KNy`RvG2!PrWC0{D~u#7qCH76xQI=VJ6l<60jxW{j18|h)?QLi}lTk z#_IS8-IM@rkb;_g%+hqU5zLNGLA#v%D)WBqUywTe&W46TNcS9!-{%e?4)xM?C)1l7 zoq+4RSC!UwOj26QRQqE;pbAaA7^*HY_VAU&Z~?ACa>>k0{8m9|!Z}0MI=iZSq>M;g zO{Y*xClfQZEo-3hu!EA=I2FunttQ!^>!FzgQgeZWn0Woe_mwgu`iv(5RLKRFMoE)(T9J?)6=|FH?cDZtdzS3{X zR3b&TS&4O#aSz^B@h@B)o=p#f%gnr5ZUmkQvWQ^ zi`JG{PkQxc5>{a`I;$9|#Q6DHKSdioy!Qb!eX+6K|A`1CdAj>XEqIIW5mP|DgCFPCx zoH{U|{*a@xIdRt*X;j`@WTxgHW?13poePZGr36?1{`d9{OO#czu|)rh@s93t-^!5v z36WFFof5*7eNys2Q^f!9;SPowu=RD@z=#%qpb{x*{q#4al68zX~`6d4^3!XP&?xpu1?3fdQW8(iR zXfpoYVNvXN?m;RIMVFDUEEx$7ZhV$$Rerw`uFaa-jzkH|W6C895_%{??AY3HpnT>i zcVUuM2jJi%XOQ&%97mgFaa9To$q&lV#n-_;kNIWhhNc|KJiH)G9&0-L?W+k~v}JAz z=pW;AL3P_Kv?U72^fb>_z=PFH+|8I!x=LKHCe}@#QYq^lB-i0U66ClR28m8dh#vsLB>yX&#kQy@mnhefcADF0nunI}w8(6v4z)(kuJ25V zr6M0d58}!wr4)%U{F(msxlT1xi188ELK)9HO2}zCmY{=2X$zmY?1FT3d?wnv1)W*i3CDpprJ&nd zR;uKHs+BB;&1f|8==jD2`1+Iqa@@jvnhy%UNdeg^>jMI`yJjXf-8xS<9`_=z^dbW^ z!kFMk*>2afpsM`>gHDm4ySZAt$Rg%02ElD3K3ad2K@upKnPggskh>g0?1P!f>VKfJh3*q~;H)3FTXswqw_ zhx;~OU)KD7N>E&NZ`C)b3bu|;`BJLal=O95y(SW=(rP}pj#ojyNg5p98a9GSuUQvY z`f9I9Ax`6g+R-SK=6&H|h*GL!HFdEo$7ag=sv9q>S)kmfr6?E9DK(P@e20~(qy~NG z5#{#FsmUW38PQ_kS^TeyaVhh~p!ZqbYOFhV$IezyN?*9T-g|`+QWrZTl9M{X(vs-R zs|h5sXopDy_RMD>VF|YM@OP{7q(?fjVI6k4iSojV{q2y+-v~|As5XZ6C-@lAq zlDS}Z5^=ESH+XfrM1Xgw?G)>?u7UGzs5H@dOOl852ePSTR;XR^4Vq{ntih}52~SAu=p>z{9oEf=)Xih+DW?| zDT;LyxDo|a9OlNCgIIz4Fj>l0Lk_gg1roDgNj`1-%6)P%KLEFnt`cjmAMzfZ!{&W| z*00J@%KSJrMvd(pVNjBf!Z~uz8-+-J?8MHc5Ts(fQK;+ED2RG3RK#;rGKe>E!}O%| z%uh&rglK>_pK-e}?@&8>&X+nEaS%H4v{3L0L29L~pZz>V^&lbbW%_MekQ(j>4j6O- zYF8ui;^rZn8Gbcr!q z>^0>y4b@%{J8Nwo_-Px$E7DxK6rWxVCC?eyM@#pz>NY*IJI#Vufv1y!V=bUnZYrKo zZAtQUv=2GJ!xTqV)|AP@f~rax24|Ix(q*7-DG51rqGQIUzQy12=LG^k&jj z)HoDgxn{$uAr?|G>kU$6XdG2xTh0#P*AN!B1B}6hUqe?Il}oBnlE}~gCiDyfraW+n z2GalNIS&@GmMq8H{Cd($X*l3JdXZ&!xBO7)*=!gjyp!w zVM3_U=<`|63OW4r|5dfS`Qu4DOi6N`dUvO|+Zx>}3!holu77-q9jEeSz|*wa?ynaV zyB%HyjNKr-8ZeOhQ|PEBK*ab<1vq%DQm)`Ldd(ff=*zk6M_=)!Wp+njpW+eqzDU4u z0%4NTFb+-?=Pk@Yl*eLlBBI*yrhLoOACB1{ryF(m8}=kg@c{x8(o%m)>`DPD3*%sQ zzH9QUi8-b(IfEpp#ct9&>)70H&!sD$A(1KH>fg&tF%T6zJ(cVLCu7VX$gCY5>EwCi z${@|HmgKQcFbCG;u1!D338v9Z`7|&MSe#e#RI;q=plGgNAg}XlY~cT~-ZoDcF=!-a_i{`Ic-i zg<<74?DA56#!I)~?6k1WK+OehQml~<(5t>}!hCK(h=Sdk^0x*-$4RT=qY_Fp!85-l z-|gN5y~BW-KMULQ<0i-KF_-DEe4o3y9oNq@deJ@G-bAiSW0DpGQV56SV3x-gWun3L zSmGtL3d9=~l>bnK8{B(t?rZn`d!FTrH7|qd%We-BzLJ&SPA*jr;INo3);YacudK3#X?C49cX^tG#gYa8!(%uZ}6!?0J^vwnz`1e$>a$H-pto$xSUR zaT%}4pgMHrN)&c!BRD_%c#WXOJDo*kwYC0YB(Aj5a5!>y`B*>w|8a?AF++kh`+^Dk z6RVdZ0-5H@I^C-y9Ur4kX~7Rc?-g)!39Tm3#_QI0W0u@i5t=kLtR%D;4yhMb-`T~g!yh{@G<+I=RzdBX+vE5LNQHry?c0caiZ|`tXuqFN80K%OW$3s&AZ*Q z!o<Tpj+KGrNJ1-mlcNlWRRtohbOSv`wdSGka1?c9uNjtNW$kgokf37(n9!11 ze)s-hX8SATT!(HV29oIaqg~||oV?rnVY?M!9WXj+mXa8!C3k3aCo>8nt0)WbibPA-ClKWd2Z8aSl(|s%(u6XCWn{N zZQphB5$pSRNWluR6c--QdhY^XiFzTi^;fUBp9ik|4ZwBybs3ogvPGkbq=Smeo@Niz z3f*KuGZf%lL+G<2OjEiELK51jwMzz@AMlLFOiE?osD8A7h&-yf*!JEwQBNSPez*J z6E?tqZpc?ZDLBxnX9VXa&Pd+A5ybuDb8-Gju=y$>z5>u}GWr&d~UA>yHV)qZR@&-*l|2r7H)-V)Vc zn9AbZzg()E6R%_Lb2d8ki2?qEM!!MA1atK_91Xj79D?(K3C}^YEk%85FOhi>#=!M8UcuQUWD_8KfDwCTN&I}MG9!>k<^E_xR zCpsO!d~`OfbIL+ztFONRIFiwtPH=$qKb;wguY*v9|FXbY z2dY=D*cHV(ls)pMkH@f4<9tVD<5c8dgR39FF5@(t_%UD5e3Kf#wf3@$#=YS5P4r1b z|Mx#6njd8(wGqQ+Del%0BjLSQiAaZiB!omEJFkR_=@rU@lOZ;NKb)=&mm*n ztJ0Xr2ZxJ+?k?&VB7+=kjXVjK5cfG%<9rVk#u~UHMd7r^ihm8^%a4^r7Vj5J%ik~? z((q5Xx2%b~ete-1_!eNK4DyVtwz4d`yhZU{y&%B{U zr2l_j4j%weKm{@Df&a1Hj;obJ#D>VAq(ph52IJ}-5?MA`XWkKx96xkC)Ap{hBgt`s1@vSq;9 z-Ke@cEuRuXa*pnM(na9He5m`m!4K;hE7e%0AH!FnUlO~(P0;hxnAxdQvnuB;P6)Hj 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

*2_ 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 A2LJ#7 literal 0 HcmV?d00001 diff --git a/docs/source/motivation.rst b/docs/source/motivation.rst index 7d068ac52..d52370f66 100644 --- a/docs/source/motivation.rst +++ b/docs/source/motivation.rst @@ -1,6 +1,60 @@ Motivation ========== +OpenFPGA aims to be an open-source framework that enables rapid prototyping of customizable FPGA architectures. As shown in :numref:`fig_openfpga_motivation`, a conventional approach will take a large group of experienced engineers more than one year to achieve production-ready layout and assoicated CAD tools. In fact, most of the engineering efforts are spent on manual layouts and developing ad-hoc CAD support. + +.. _fig_openfpga_motivation: + +.. figure:: ./figures/openfpga_motivation.png + :scale: 50% + :alt: OpenFPGA: a fast prototyping framework for customizable FPGAs + + Comparison on engineering time and effort to prototype an FPGA using OpenFPGA and conventional approaches + +Using OpenFPGA, the development cycle in both hardware and software can be significantly accelerated. OpenFPGA can automatically generate Verilog netlists describing a full FPGA fabric based on an XML-based description file. Thanks to modern semi-custom design tools, production-ready layout generation can be achieved within 24 hours. To help sign-off, OpenFPGA can auto-generate Verilog testbenches to validate the correctness of FPGA fabric using modern verification tools. +OpenFPGA also provides native bitstream generation support based the same XML-based description file used in Verilog generation. This avoid the recurring engineering in developing CAD tools for different FPGAs. Once the FPGA architecture is finalized, the CAD tool is ready to use. + +OpenFPGA can support any architecture that VPR can describe, covering most of the architecture enhancements available in modern FPGAs, and hence unlocks a large design space in prototyping customizable FPGAs. In addition, OpenFPGA provides enriched syntax which allows users to customized primitive circuit designed downto transistor-level parameters. This helps developers to customize the P.P.A. (Power, Performance and Area) to the best. All these features open the door of prototyping/studying flexible FPGAs to a small group of junior engineers or researchers. + +In terms of tool functionality, OpenFPGA consists of the following parts: FPGA-Verilog, FPGA-SDC, FPGA-Bitstream and FPGA-SPICE. +The rest of this section will focus on detailed motivation on each of them, as depicted in :numref:`fig_openfpga_flow`. + +.. _fig_openfpga_flow: + +.. figure:: ./figures/openfpga_flow.png + :scale: 50% + :alt: Design flows avaiable in OpenFPGA + + Design flows in different purposes using OpenFPGA + + +FPGA-Verilog +------------ + +Driven by the strong need in data processing applications, Field Programmable Gate Arrays (FPGAs) are playing an ever-increasing role as programmable accelerators in modern +computing systems. To fully unlock processing capabilities for domain-specific applications, FPGA architectures have to be tailored for seamless cooperation with other computing resources. However, prototyping and bringing to production a customized FPGA is a costly and complex endeavor even for industrial vendors. OpenFPGA, an opensource framework, aims to rapid prototype of customizable FPGA architectures through a semi-custom design approach. We propose an XML-to-Prototype design flow, where the Verilog netlists of a full FPGA fabric can be autogenerated using an extension of the XML language from the VTR framework and then fed into a back-end flow to generate production-ready layouts. + +The technical details can be found in our TVLSI'19 paper :cite:`XTang_TVLSI_2019` and FPL'19 paper :cite:`XTang_FPL_2019`. + +FPGA-SDC +-------- + +Design constraints are indepensible in modern ASIC design flows to guarantee the performance level. +OpenFPGA includes a rich SDC generator in the OpenFPGA framework to deal with both PnR constraints and sign-off timing analysis. +Our flow automatically generates two sets of SDC files. The first set of SDC is designed for the P&R flow, where all the combinational loops are broken to enable wellcontrolled timing-driven P&R. In addition, there are SDC files devoted to constrain pin-to-pin timing for all the resources in FPGAs, in order to obtain nicely constrained and homogeneous delays across the fabric. The second set of SDC is designed for the timing analysis of a benchmark at the post P&R stage. + +The technical details can be found in our FPL'19 paper :cite:`XTang_FPL_2019`. + + +FPGA-Bitstream +-------------- + +EDA support is essential for end-users to implement designs on a customized FPGA. OpenFPGA provides a general-purpose bitstream generator FPGA-Bitstream for any architecture that can be described by VPR. As the native CAD tool for any customized FPGA that is produced by FPGA-Verilog, FPGA-Bitstream is ready to use once users finalize the XML-based architecture description file. This eliminates the huge engineering efforts spent on developing bitstream generator for customized FPGAs. + +Using FPGA-Bitstream, users can launch (1) Verilog-to-Bitstream flow. This is the typical implementation flow for end-users; (2) Verilog-to-Verification flow. OpenFPGA can output Verilog testbenches with self-testing features to validate users' implemetations on their customized FPGA fabrics. + +The technical details can be found in our TVLSI'19 paper :cite:`XTang_TVLSI_2019` and FPL'19 paper :cite:`XTang_FPL_2019`. + FPGA-SPICE ---------- @@ -17,19 +71,4 @@ In the appendix, we introduce the hierarchy of the generated SPICE netlists and The technical details can be found in our ICCD’15 paper :cite:`XTang_ICCD_2015` and TVLSI'19 paper :cite:`XTang_TVLSI_2019`. -FPGA-Verilog ------------- -Driven by the strong need in data processing applications, Field Programmable Gate Arrays (FPGAs) are playing an ever-increasing role as programmable accelerators in modern -computing systems. To fully unlock processing capabilities for domain-specific applications, FPGA architectures have to be tailored for seamless cooperation with other computing resources. However, prototyping and bringing to production a customized FPGA is a costly and complex endeavor even for industrial vendors. OpenFPGA, an opensource framework, aims to rapid prototype of customizable FPGA architectures through a semi-custom design approach. We propose an XML-to-Prototype design flow, where the Verilog netlists of a full FPGA fabric can be autogenerated using an extension of the XML language from the VTR framework and then fed into a back-end flow to generate production-ready layouts. - -The technical details can be found in our TVLSI'19 paper :cite:`XTang_TVLSI_2019` and FPL'19 paper :cite:`XTang_FPL_2019`. - -FPGA-Bitstream --------------- - -EDA support is essential for end-users to implement designs on a customized FPGA. OpenFPGA provides a general-purpose bitstream generator FPGA-Bitstream for any architecture that can be described by VPR. As the native CAD tool for any customized FPGA that is produced by FPGA-Verilog, FPGA-Bitstream is ready to use once users finalize the XML-based architecture description file. This eliminates the huge engineering efforts spent on developing bitstream generator for customized FPGAs. - -Using FPGA-Bitstream, users can launch (1) Verilog-to-Bitstream flow. This is the typical implementation flow for end-users; (2) Verilog-to-Verification flow. OpenFPGA can output Verilog testbenches with self-testing features to validate users' implemetations on their customized FPGA fabrics. - -The technical details can be found in our TVLSI'19 paper :cite:`XTang_TVLSI_2019` and FPL'19 paper :cite:`XTang_FPL_2019`. From 3c7fd30e12a9f540623c3bcd40b4faed8a90d778 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 9 Mar 2020 13:58:24 -0600 Subject: [PATCH 282/645] merged tutorial to online documentation and reworked compilation guidelines --- README.md | 2 +- docs/source/index.rst | 16 +--- docs/source/motivation.rst | 6 -- docs/source/tutorials/compile.rst | 68 ++++++++++++++ docs/source/{ => tutorials}/eda_flow.rst | 43 +-------- .../{ => tutorials}/figures/eda_flow.pdf | Bin .../{ => tutorials}/figures/eda_flow.png | Bin .../source/tutorials/figures}/frac_lut8.pdf | Bin .../source/tutorials/figures}/fract_lut6.pdf | Bin docs/source/tutorials/index.rst | 12 ++- docs/source/{ => tutorials}/run_fpga_flow.rst | 0 docs/source/{ => tutorials}/run_fpga_task.rst | 0 tutorials/building.md | 54 ----------- tutorials/fpga_flow/folder_organization.md | 30 ------ tutorials/fpga_flow/how2use.md | 88 ------------------ tutorials/fpga_flow/options.md | 75 --------------- tutorials/tutorial_index.md | 20 ---- 17 files changed, 82 insertions(+), 332 deletions(-) create mode 100644 docs/source/tutorials/compile.rst rename docs/source/{ => tutorials}/eda_flow.rst (61%) rename docs/source/{ => tutorials}/figures/eda_flow.pdf (100%) rename docs/source/{ => tutorials}/figures/eda_flow.png (100%) rename {tutorials/images/architectures_schematics => docs/source/tutorials/figures}/frac_lut8.pdf (100%) rename {tutorials/images/architectures_schematics => docs/source/tutorials/figures}/fract_lut6.pdf (100%) rename docs/source/{ => tutorials}/run_fpga_flow.rst (100%) rename docs/source/{ => tutorials}/run_fpga_task.rst (100%) delete mode 100644 tutorials/building.md delete mode 100644 tutorials/fpga_flow/folder_organization.md delete mode 100644 tutorials/fpga_flow/how2use.md delete mode 100644 tutorials/fpga_flow/options.md delete mode 100644 tutorials/tutorial_index.md diff --git a/README.md b/README.md index c75d2e705..efc0d0ad3 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ The OpenFPGA framework is the **first open-source FPGA IP generator** supporting highly-customizable homogeneous FPGA architectures. OpenFPGA provides a full set of EDA support for customized FPGAs, including Verilog-to-bitstream generation and self-testing verification [testbenches/scripts](./testbenches/scripts) OpenFPGA opens the door to democratizing FPGA technology and EDA techniques, with agile prototyping approaches and constantly evolving EDA tools for chip designers and researchers. ## Compilation -Dependencies and help using docker can be found at [**./tutorials/building.md**](./tutorials/building.md). +Dependencies and help using docker can be found [**here**](./docs/source/tutorials/building.rst). **Compilation Steps:** ```bash diff --git a/docs/source/index.rst b/docs/source/index.rst index 51fd1b119..9f4dd36d8 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -12,14 +12,10 @@ Welcome to OpenFPGA's documentation! motivation .. toctree:: + :maxdepth: 2 :caption: Getting Started - eda_flow - - run_fpga_flow - - run_fpga_task - + tutorials/index .. toctree:: :maxdepth: 2 @@ -33,12 +29,6 @@ Welcome to OpenFPGA's documentation! fpga_bitstream/index -.. toctree:: - :maxdepth: 2 - :caption: User Guide - - tutorials/index - .. toctree:: :maxdepth: 2 :caption: Appendix @@ -52,8 +42,6 @@ For more information on the Yosys see yosys_doc_ or yosys_github_ For more information on the original FPGA architecture description language see xml_vtr_ - - Indices and tables ================== diff --git a/docs/source/motivation.rst b/docs/source/motivation.rst index d52370f66..2dd4447df 100644 --- a/docs/source/motivation.rst +++ b/docs/source/motivation.rst @@ -65,10 +65,4 @@ FPGA-SPICE aims at generating SPICE netlists and testbenches for the FPGA archit SPICE modeling for FPGA architectures requires detailed transistor-level modeling for all the circuit elements within the considered FPGA architecture. However, current VPR architectural description language :cite:`JLuu_FPGA_2011` does not offer enough transistor-level parameters to model the most common circuit modules, such as multiplexers and LUTs. Therefore, we develop an extension on the VPR architectural description language to model the transistor-level circuit designs. -In this manual, we will introduce how to use FPGA-SPICE to conduct an accurate power analysis. First, we give an overview of the design flow of FPGA-SPICE-based tool suites. Then, we show the command-line options of FPGA-SPICE. Afterward, we introduce the extension of architectural language and the transistor-level design supports. Finally, we present how to simulate the generated SPICE netlists and testbenches. - -In the appendix, we introduce the hierarchy of the generated SPICE netlists and testbenches, to help you customize the SPICE netlists. We also attach an example of an architecture XML file for your interest. - The technical details can be found in our ICCD’15 paper :cite:`XTang_ICCD_2015` and TVLSI'19 paper :cite:`XTang_TVLSI_2019`. - - diff --git a/docs/source/tutorials/compile.rst b/docs/source/tutorials/compile.rst new file mode 100644 index 000000000..cfa4dbe44 --- /dev/null +++ b/docs/source/tutorials/compile.rst @@ -0,0 +1,68 @@ +How to Compile +============== + +General Compilation Guidelines +------------------------------ +OpenFPGA uses CMake to generate the Makefile scripts +In general, please follow the steps to compile + +:: + + git clone https://github.com/LNIS-Projects/OpenFPGA.git + cd OpenFPGA + mkdir build + cd build + cmake .. -DCMAKE_BUILD_TYPE=debug + make + +.. note:: OpenFPGA requires gcc/g++ version >5 + +.. note:: cmake3.12+ is recommended to compile OpenFPGA with GUI + +.. note:: recommand to use ``make -j`` to accelerate the compilation + +Quick Compilation Verification +To quickly verify the tool is well compiled, user can run the following command from OpenFPGA root repository + +:: + + python3 openfpga_flow/scripts/run_fpga_task.py compilation_verification --debug --show_thread_logs + +Dependencies +------------ +Full list of dependencies can be found at travis_setup_link_ +In particular, OpenFPGA requires specific versions for the following dependencies: + +:cmake: + version >3.12 for graphical interface + +:iverilog: + version 10.1+ is required to run Verilog-to-Verification flow + +.. _travis_setup_link: https://github.com/LNIS-Projects/OpenFPGA/blob/0cfb88a49f152aab0a06f309ff160f222bb51ed7/.travis.yml#L34 + +Docker +------ +If some of these dependencies are not installed on your machine, you can choose to use a Docker (the Docker tool needs to be installed). +For the ease of the customer first experience, a Dockerfile is provided in the OpenFPGA folder. A container ready to use can be created with the following command + +:: + + docker run lnis/open_fpga:release + +.. note:: This command is for quick testing. If you want to conserve your work, you should certainly use other options, such as ``-v``. + +Otherwise, a container where you can build OpenFPGA yourself can be created with the following commands + +:: + + docker build . -t open_fpga + docker run -it --rm -v $PWD:/localfile/OpenFPGA -w="/localfile/OpenFPGA" open_fpga bash + +For more information about dock, see dock_download_link_ + +.. _dock_download_link: https://www.docker.com/products/docker-desktop + +To build the tool, go in the OpenFPGA folder and follow the compilation steps + +.. note:: Using docker, you cannot use ``make -j``, errors will happen diff --git a/docs/source/eda_flow.rst b/docs/source/tutorials/eda_flow.rst similarity index 61% rename from docs/source/eda_flow.rst rename to docs/source/tutorials/eda_flow.rst index 038b984af..446098d60 100644 --- a/docs/source/eda_flow.rst +++ b/docs/source/tutorials/eda_flow.rst @@ -5,7 +5,7 @@ As illustrated in :numref:`fig_eda_flow`, FPGA-SPICE creates a modified VTR flow .. _fig_eda_flow: -.. figure:: figures/eda_flow.png +.. figure:: ./figures/eda_flow.png :scale: 50% :alt: map to buried treasure @@ -14,44 +14,3 @@ As illustrated in :numref:`fig_eda_flow`, FPGA-SPICE creates a modified VTR flow FPGA-Verilog is the part of the flow in charge of the Verilog and the semi-custom design flow. In our case, we use Cadence Innovus. The goal is to get the full-FPGA layout to complete the analysis provided by FPGA-SPICE. By having the layout, we can get an area analysis on the one hand and have new information concerning the power analysis. For instance, having the layout allows the user to have new information on the circuit such as the parasitics. FPGA-Bitstream is the part of the flow in charge of the functional verification of the produced FPGA. Testbenches are generated by FPGA-Verilog and are combined with the full FPGA fabric in Modelsim. A bitstream is generated at the same time as the testbenches. This bitstream configures the FPGA with the functionality given by the user to VPR at the beginning of the flow. First, we configure the FPGA with the bitstream, and then waveforms are sent onto the I/O pads to check the functionality. - - -How to compile -============== -Guides can be found in the *compilation* directory in the main folder. We tested it for MacOS High Sierra 10.13.4, Ubuntu 18.04 and Red Hat 7.5. This list is not exhaustive as other distributions could work as well. - -As a general rule, the compilation follows these steps: - -1) You clone the repository with: -git clone --recurse-submodules https://github.com/LNIS-Projects/OpenFPGA,git - -Two different approaches exist from then on: Either you need the full flow, or you just need the extended version of VPR. -If you need the full flow: - -2) Go into the folder you just cloned and make the different submodules through a global Makefile: -cd OpenFPGA -mkdir build (*if folder doesn't already exist*) -cd build -cmake .. -make OR make -j (*if you have multiple cores, this will make the compilation way faster*) - -If you only need vpr: -cd OpenFPGA -mkdir build (if folder doesn't already exist) -cd build -cmake .. -make vpr/make vpr -j - -3) Architectures, circuits and already written scripts exist to allow you to test the flow without having to provide any new information to the system. For this: -cd vpr7_x2p -cd vpr -source ./go_fpga_verilog/spice.sh - -They are scripts linking to a testing architecture and a simple circuit. - -4) If you only need to see the new options implemented in vpr, do: -./vpr - -This step will show you all the different options which were added on top of VPR to enable deeper analysis of FPGA architectures. - -The released package includes a version of VPR with FPGA-SPICE, Verilog and Bitstream support, Yosys and ACE2. diff --git a/docs/source/figures/eda_flow.pdf b/docs/source/tutorials/figures/eda_flow.pdf similarity index 100% rename from docs/source/figures/eda_flow.pdf rename to docs/source/tutorials/figures/eda_flow.pdf diff --git a/docs/source/figures/eda_flow.png b/docs/source/tutorials/figures/eda_flow.png similarity index 100% rename from docs/source/figures/eda_flow.png rename to docs/source/tutorials/figures/eda_flow.png diff --git a/tutorials/images/architectures_schematics/frac_lut8.pdf b/docs/source/tutorials/figures/frac_lut8.pdf similarity index 100% rename from tutorials/images/architectures_schematics/frac_lut8.pdf rename to docs/source/tutorials/figures/frac_lut8.pdf diff --git a/tutorials/images/architectures_schematics/fract_lut6.pdf b/docs/source/tutorials/figures/fract_lut6.pdf similarity index 100% rename from tutorials/images/architectures_schematics/fract_lut6.pdf rename to docs/source/tutorials/figures/fract_lut6.pdf diff --git a/docs/source/tutorials/index.rst b/docs/source/tutorials/index.rst index 562a00bbb..e1cae21ac 100644 --- a/docs/source/tutorials/index.rst +++ b/docs/source/tutorials/index.rst @@ -1,10 +1,18 @@ .. _tutorials: - Tutorials + Getting Started .. toctree:: :maxdepth: 2 - getting_started + compile + + eda_flow + + run_fpga_flow + + run_fpga_task + + diff --git a/docs/source/run_fpga_flow.rst b/docs/source/tutorials/run_fpga_flow.rst similarity index 100% rename from docs/source/run_fpga_flow.rst rename to docs/source/tutorials/run_fpga_flow.rst diff --git a/docs/source/run_fpga_task.rst b/docs/source/tutorials/run_fpga_task.rst similarity index 100% rename from docs/source/run_fpga_task.rst rename to docs/source/tutorials/run_fpga_task.rst diff --git a/tutorials/building.md b/tutorials/building.md deleted file mode 100644 index badf9bab5..000000000 --- a/tutorials/building.md +++ /dev/null @@ -1,54 +0,0 @@ -# How to build? - -## Dependencies -OpenFPGA requires all the following dependencies: -- autoconf -- automake -- bash -- bison -- build-essential -- cmake (version 3.12 for graphical interface or at least 3.X) -- ctags -- curl -- doxygen -- flex -- fontconfig -- g++-8 -- gcc-8 -- g++-4.9 -- gcc-4.9 -- gdb -- git -- gperf -- iverilog -- libcairo2-dev -- libevent-dev -- libfontconfig1-dev -- liblist-moreutils-perl -- libncurses5-dev -- libx11-dev -- libxft-dev -- libxml++2.6-dev -- perl -- python -- texinfo -- time -- valgrind -- zip -- qt5-default - -## Docker -If some of these dependencies are not installed on your machine, you can choose to use a Docker (the Docker tool needs to be installed). For the ease of the customer first experience, a Dockerfile is provided in the OpenFPGA folder. A container ready to use can be created with the following command: -- docker run lnis/open_fpga:release
-*Warning: This command is for quick testing. If you want to conserve your work, you should certainly use other options, such as "-v".* - -Otherwise, a container where you can build OpenFPGA yourself can be created with the following commands: -- docker build . -t open_fpga -- docker run -it --rm -v $PWD:/localfile/OpenFPGA -w="/localfile/OpenFPGA" open_fpga bash
-[*docker download link*](https://www.docker.com/products/docker-desktop) - -## Building -To build the tool, go in the OpenFPGA folder and do: -- mkdir build && cd build -- cmake .. -DCMAKE_BUILD_TYPE=debug -- make (*WARNING using docker you cannot use "make -j", errors will happen*) diff --git a/tutorials/fpga_flow/folder_organization.md b/tutorials/fpga_flow/folder_organization.md deleted file mode 100644 index 172f10782..000000000 --- a/tutorials/fpga_flow/folder_organization.md +++ /dev/null @@ -1,30 +0,0 @@ -# fpga_flow folder organization - -The fpga_flow folder is organized as follow: -* **arch**: contains architectures description files -* **benchmarks**: contains Verilog and blif benchmarks + lists -* **configs**: contains configuration files to run fpga_flow.pl -* **scripts**: contains all the scripts required to run the flow -* **tech**: contains xml tech files for power estimation - -## arch -In this folder are saved the architecture files. These files are Hardware description for the FPGA written in XML. This folder contains 3 sub-folders: -- **fpga_spice**: contains existing architecture ready to use. -- **template**: contains template architecture which contain keyword to replace -- **generated**: empty at the beginning, will host rewritten template - -## benchmarks -This folder contains benchmarks to implement in the FPGA. it's divided in 3 folders: -- **Blif**: Contains .blif and .act file to use in OpenFPGA. Benchmarks are divided in folder with the same name as the top module -- **Verilog**: Contains Verilog netlist of benchmarks to use in OpenFPGA. Each project is saved in a folder with the same name as the top module. -- **List**: Contains files with a list of benchmarks to run in one flow. More details are available in [fpga_flow tutorial](https://github.com/LNIS-Projects/OpenFPGA/blob/master/tutorials/fpga_flow/how2use.md#benchmark-list) - -## configs -This folder contains configuration files required by openFPGA flow. They specify path to tools and benchmarks as well as flow utilization mode. More details are available in [fpga_flow tutorial](https://github.com/LNIS-Projects/OpenFPGA/blob/master/tutorials/fpga_flow/how2use.md#configuration-file) - -## scripts -This folder contains scripts call by OpenFPGA flow. Some of them can be used out of the flow as **pro_blif.pl** and **rewrite_path_in_file.pl** which respectively rewrite a blif file with 3 members on a ".latch" module to let it have 5 and replace a keyword in a file.
-Any script provide help if call without argument. - -## tech -This folder contains XML files describing the technology used. These files are used during power analysis. diff --git a/tutorials/fpga_flow/how2use.md b/tutorials/fpga_flow/how2use.md deleted file mode 100644 index 905adb5d1..000000000 --- a/tutorials/fpga_flow/how2use.md +++ /dev/null @@ -1,88 +0,0 @@ -# FPGA Flow -This tutorial will help the user to understand how to use the OpenFPGA flow.
-During this tutorial, we consider that the user starts in the OpenFPGA folder and we will use tips and information provided in [tutorial index](https://github.com/LNIS-Projects/OpenFPGA/blob/master/tutorials/tutorial_index.md#tips-and-informations). Details on how the folder is organized are available [here](https://github.com/LNIS-Projects/OpenFPGA/blob/master/tutorials/fpga_flow/folder_organization.md). - -## Running fpga_flow.pl -A script example can be found at OPENFPGAPATHKEYWORD/fpga_flow/tuto_fpga_flow.sh. - -### Experiment -cd fpga_flow
-./tuto_fpga_flow.sh
- -### Explanation -The *fpga_flow.pl* script takes an architecture description file (.xml), generates its netlists and generates a bitstream to implement a benchmark on the FPGA fabric and verifis its correct implementation.
-When you open the perl script, you can see that 2 scripts are called. The first one is **rewrite_path_in_file.pl** which allows us to make this tutorial generic by generating full path to the dependencies.
-The second one is **fpga_flow.pl**. This script launches the OpenFPGA flow and can be used with many different [options](https://github.com/LNIS-Projects/OpenFPGA/blob/master/tutorials/fpga_flow/options.md).
-There are 3 important things to observe here: -- All the FPGA-Verilog options have been activated -- fpga_flow.pl calls a configuration file through the "config_file" variable -- fpga_flow.pl calls a list of benchmark to be implemented and tested through the "bench_txt" variable - -### Configuration File -In this file, paths have to be defined as **absolute** paths as relative paths could lead to errors.
-The file is organized in 3 parts: -* **dir_path**: provides all the tool and repository paths -* **flow_conf**: provides information on how the flow runs -* **csv_tags**: *to be completed* - -When empty, the file is as follow: - -[dir_path]
-script_base = OPENFPGAPATHKEYWORD/fpga_flow/scripts
-benchmark_dir = **
-yosys_path = OPENFPGAPATHKEYWORD/yosys
-odin2_path = not_used
-cirkit_path = not_used
-abc_path = OPENFPGAPATHKEYWORD/abc
-abc_mccl_path = OPENFPGAPATHKEYWORD/abc
-abc_with_bb_support_path = OPENFPGAPATHKEYWORD/abc
-mpack1_path = not_used
-m2net_path = not_used
-mpack2_path = not_used
-vpr_path = OPENFPGAPATHKEYWORD/vpr7_x2p/vpr
-rpt_dir = **
-ace_path = OPENFPGAPATHKEYWORD/ace2
- -[flow_conf]
-flow_type = yosys_vpr *to use verilog input*
-vpr_arch = **
-mpack1_abc_stdlib = DRLC7T_SiNWFET.genlib # Use relative path under ABC folder is OK
-m2net_conf = not_used
-mpack2_arch = not_used
-power_tech_xml = **
- -[csv_tags]
-mpack1_tags = Global mapping efficiency:|efficiency:|occupancy wo buf:|efficiency wo buf:
-mpack2_tags = BLE Number:|BLE Fill Rate:
-vpr_tags = Netlist clb blocks:|Final critical path:|Total logic delay:|total net delay:|Total routing area:|Total used logic block area:|Total wirelength:|Packing took|Placement took|Routing took|Average net density:|Median net density:|Recommend no. of clock cycles:
-vpr_power_tags = PB Types|Routing|Switch Box|Connection Box|Primitives|Interc Structures|lut6|ff
- -*This example file can be found at OPENFPGAPATHKEYWORD/fpga_flow/configs/tutorial/tuto.conf* - -### Benchmark List -The benchmark folder contains 3 sub-folders: -* **Blif**: contains .blif and .act of benchmarks -* **List**: contains all benchmark list files -* **Verilog**: contains Verilog designs - -Blif and Verilog folders are organized by folders using the name of the projects. **The folder, top module and top module file must share the same name.**
-The benchmark list file can contain as many benchmarks as available in the same folder targetted by the "benchmark_dir" variable from the configuration file. It's written as:
-top_module/*.v,; where is the number of channel/wire between each block. - -*This example file can be found at OPENFPGAPATHKEYWORD/fpga_flow/benchmarks/List/tuto_benchmark.txt* - - -## Modifying the Flow -Once the dependencies are understood, the flow can be modified by changing the architecture file and the route channel width. - -### Experiment -* cd OPENFPGAPATHKEYWORD/fpga_flow/configs/tutorial -* replace the architecture "k6_N10_sram_chain_HC_template.xml" and "k6_N10_sram_chain_HC.xml" respectively with "k8_N10_sram_chain_FC_template.xml" and "k8_N10_sram_chain_FC.xml" in tuto.conf -* cd OPENFPGAPATHKEYWORD/fpga_flow/benchmarks/List -* replace "200" with "300" in tuto_benchmark.txt -* cd OPENFPGAPATHKEYWORD/fpga_flow -* replace the architecture "k6_N10_sram_chain_HC_template.xml" and "k6_N10_sram_chain_HC.xml" respectively with "k8_N10_sram_chain_FC_template.xml" and "k8_N10_sram_chain_FC.xml" in tuto_fpga_flow.sh -* ./tuto_fpga_flow.sh - -### Explanations -With this last experiment, the [**K6 architecture**](https://github.com/LNIS-Projects/OpenFPGA/blob/master/tutorials/images/architectures_schematics/frac_lut6.pdf) was replaced by a [**K8 architecture**](https://github.com/LNIS-Projects/OpenFPGA/blob/master/tutorials/images/architectures_schematics/frac_lut8.pdf), which means that an 8-input fracturable LUT (implemented by LUT6 and LUT4 with 2 shared inputs) is used. This architecture provides more modes for the CLB and the crossbar which is changed from a half-connected to a fully connected, implying bigger multiplexors between the CLB and LUT inputs. These requirements in term of interconnection will lead an increase in the routing channel width. Indeed, if the routing channel is too low, it could be impossible to route a benchmark or the FPGA output could be delayed. diff --git a/tutorials/fpga_flow/options.md b/tutorials/fpga_flow/options.md deleted file mode 100644 index 38932d0c1..000000000 --- a/tutorials/fpga_flow/options.md +++ /dev/null @@ -1,75 +0,0 @@ -# OpenFPGA flow options - -Usage -> **fpga_flow *-options * **
-Mandatory options:
-- -conf : *specify the basic configuration files for fpga_flow* -- -benchmark : *the configuration file contains benchmark file names* -- -rpt : *CSV file consists of data* -- -N : *N-LUT/Matrix* - -## Other Options: -### General -- -matlab_rpt : *.m file consists of data compatible to matlab scripts. Specify the data name to be appeared in the script* -- -I : *Number of inputs of a CLB, mandatory when mpack1 flow is chosen* -- -K : *K-LUT, mandatory when standard flow is chosen* -- -M : *M-Matrix, mandatory when mpack1 flow is chosen* -- -power : *run power estimation oriented flow* -- -black_box_ace: *run activity estimation with black box support. It increase the power.* -- -remove_designs: *remove all the old results.* -- -multi_thread : *turn on the mutli-thread mode, specify the number of threads* -- -multi_task : *turn on the mutli-task mode* -- -parse_results_only : *only parse the flow results and write CSV report.* -- -debug : *debug mode* -- -help : *print usage* -- -end_flow_with_test: *Uses Icarus Verilog simulator to verified bencmark implementation* -### ODIN II -- -min_hard_adder_size: *min. size of hard adder in carry chain defined in Arch XML.(Default:1)* -- -mem_size: *size of memory, mandatory when VTR/VTR_MCCL/VTR_MIG_MCCL flow is chosen* -- -odin2_carry_chain_support: *turn on the carry_chain support only valid for VTR_MCCL/VTR_MIG_MCCL flow * -### ABC -- -abc_scl : *run ABC optimization for sequential circuits, mandatory when VTR flow is selected.* -- -abc_verilog_rewrite : *run ABC to convert a blif netlist to a Verilog netlist.* -### ACE -- -ace_p : *specify the default signal probablity of PIs in ACE2.* -- -ace_d : *specify the default signal density of PIs in ACE2.* -### VPR - Original Version -- -vpr_timing_pack_off : *turn off the timing-driven pack for vpr.* -- -vpr_place_clb_pin_remap: *turn on place_clb_pin_remap in VPR.* -- -vpr_max_router_iteration : *specify the max router iteration in VPR.* -- -vpr_route_breadthfirst : *use the breadth-first routing algorithm of VPR.* -- -vpr_use_tileable_route_chan_width: *turn on the conversion to tileable_route_chan_width in VPR.* -- -min_route_chan_width : *turn on routing with * min_route_chan_width.* -- -fix_route_chan_width : *turn on routing with a fixed route_chan_width, defined in benchmark configuration file.* -### VPR - FPGA-X2P Extension -- -vpr_fpga_x2p_rename_illegal_port : *turn on renaming illegal ports option of VPR FPGA SPICE* -- -vpr_fpga_x2p_signal_density_weight : *specify the option signal_density_weight of VPR FPGA SPICE* -- -vpr_fpga_x2p_sim_window_size : *specify the option sim_window_size of VPR FPGA SPICE* -- -vpr_fpga_x2p_compact_routing_hierarchy : *allow routing block modularization* -### VPR - FPGA-SPICE Extension -- -vpr_fpga_spice : *turn on SPICE netlists print-out in VPR, specify a task file* -- -vpr_fpga_spice_sim_mt_num : *specify the option sim_mt_num of VPR FPGA SPICE* -- -vpr_fpga_spice_print_component_tb : *print component-level testbenches in VPR FPGA SPICE* -- -vpr_fpga_spice_print_grid_tb : *print Grid-level testbenches in VPR FPGA SPICE* -- -vpr_fpga_spice_print_top_tb : *print full-chip testbench in VPR FPGA SPICE* -- -vpr_fpga_spice_leakage_only : *turn on leakage_only mode in VPR FPGA SPICE* -- -vpr_fpga_spice_parasitic_net_estimation_off : *turn off parasitic_net_estimation in VPR FPGA SPICE* -- -vpr_fpga_spice_testbench_load_extraction_off : *turn off testbench_load_extraction in VPR FPGA SPICE* -- -vpr_fpga_spice_simulator_path : *Specify simulator path* -### VPR - FPGA-Verilog Extension -- -vpr_fpga_verilog : *turn on OpenFPGA Verilog Generator* -- -vpr_fpga_verilog_dir : *provides the path where generated verilog files will be written* -- -vpr_fpga_verilog_include_timing : *turn on printing delay specification in Verilog files* -- -vpr_fpga_verilog_include_signal_init : *turn on printing signal initialization in Verilog files* -- -vpr_fpga_verilog_print_autocheck_top_testbench: *turn on printing autochecked top-level testbench for OpenFPGA Verilog Generator* -- -vpr_fpga_verilog_formal_verification_top_netlist : *turn on printing formal top Verilog files* -- -vpr_fpga_verilog_include_icarus_simulator : *Add syntax and definition required to use Icarus Verilog simulator* -- -vpr_fpga_verilog_print_user_defined_template : *Generates a template of hierarchy modules and their port mapping* -- -vpr_fpga_verilog_print_report_timing_tcl : *Generates tcl script useful for timing report generation* -- -vpr_fpga_verilog_report_timing_rpt_path : *Specify path for report timing* -- -vpr_fpga_verilog_print_sdc_pnr : *Generates sdc file to constraint Hardware P&R* -- -vpr_fpga_verilog_print_sdc_analysis : *Generates sdc file to do STA* -- -vpr_fpga_verilog_print_top_tb : *turn on printing top-level testbench for OpenFPGA Verilog Generator* -- -vpr_fpga_verilog_print_input_blif_tb : *turn on printing testbench for input blif file in OpenFPGA Verilog Generator* -- -vpr_fpga_verilog_print_modelsim_autodeck : *turn on printing modelsim simulation script* -### VPR - FPGA-Bitstream Extension -- -vpr_fpga_bitstream_generator: *turn on FPGA-SPICE bitstream generator* diff --git a/tutorials/tutorial_index.md b/tutorials/tutorial_index.md deleted file mode 100644 index bbf95ff86..000000000 --- a/tutorials/tutorial_index.md +++ /dev/null @@ -1,20 +0,0 @@ -# Tutorial Introduction -OpenFPGA is an IP Verilog Generator allowing reliable and fast testing of homogeneous FPGA architectures.
-Its main goal is to easily and efficiently generated a complete customizable FPGA and uses a semi-custom design flow.

-In order to help you get in touch with the software, we provide few tutorials which are organized as follow: -* [Building the tool and his dependencies](https://github.com/LNIS-Projects/OpenFPGA/blob/master/tutorials/building.md) -* [Launching the flow and understand how it works](https://github.com/LNIS-Projects/OpenFPGA/blob/master/tutorials/fpga_flow/how2use.md) -* Architecture modification - -## Folder Organization -OpenFPGA repository is organized as follow: -* **abc**: open source synthesys tool -* **ace2**: abc extension generating .act files -* **ace2**: abc extension generating activity files (.act) -* **vpr7_x2p**: sources of modified vpr -* **yosys**: opensource synthesys tool -* **fpga_flow**: scripts and dependencies to run the complete flow - -## Tips and Information -Some keywords will be used during in the tutorials: -* OPENFPGAPATHKEYWORD: refers to OpenFPGA folder full path From 751735bf412adb3b71d49537d6157e6b80d16341 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 9 Mar 2020 17:40:33 -0600 Subject: [PATCH 283/645] update documentation in simulation setting syntax --- docs/source/arch_lang/addon_vpr_syntax.rst | 37 +++++ ...cuit_modules.rst => annotate_vpr_arch.rst} | 16 +- ...ircuit_modules.rst => circuit_library.rst} | 22 ++- .../arch_lang/circuit_model_examples.rst | 24 +-- ...terconnect.rst => direct_interconnect.rst} | 53 +++--- docs/source/arch_lang/generality.rst | 40 ++++- docs/source/arch_lang/index.rst | 17 +- ...sim_setting.rst => simulation_setting.rst} | 154 +++++++++++++++++- .../{tech_lib.rst => technology_library.rst} | 6 +- docs/source/contact.rst | 2 +- docs/source/fpga_bitstream/index.rst | 5 +- docs/source/fpga_spice/index.rst | 5 +- docs/source/fpga_verilog/index.rst | 5 +- docs/source/index.rst | 6 +- docs/source/motivation.rst | 12 +- docs/source/tutorials/compile.rst | 14 +- docs/source/tutorials/eda_flow.rst | 6 +- 17 files changed, 337 insertions(+), 87 deletions(-) create mode 100644 docs/source/arch_lang/addon_vpr_syntax.rst rename docs/source/arch_lang/{link_circuit_modules.rst => annotate_vpr_arch.rst} (97%) rename docs/source/arch_lang/{circuit_modules.rst => circuit_library.rst} (97%) rename docs/source/arch_lang/{interconnect.rst => direct_interconnect.rst} (50%) rename docs/source/arch_lang/{spice_sim_setting.rst => simulation_setting.rst} (56%) rename docs/source/arch_lang/{tech_lib.rst => technology_library.rst} (96%) diff --git a/docs/source/arch_lang/addon_vpr_syntax.rst b/docs/source/arch_lang/addon_vpr_syntax.rst new file mode 100644 index 000000000..7122c9870 --- /dev/null +++ b/docs/source/arch_lang/addon_vpr_syntax.rst @@ -0,0 +1,37 @@ +.. _addon_vpr_syntax: + +Additional Syntax to Original VPR XML +------------------------------------- + +.. warning:: Note this is only applicable to VPR8! + + +Each ```` should contain a ```` that describe the physical implementation of the ````. Note that this is fully compatible to the VPR architecture XML syntax. + +```` should include the models that describe the primitive ```` in physical mode. + +```` may include additioinal syntax to enable tileable routing resource graph generation + +.. option:: tileable="" + + Turn on/off tileable routing resource graph generator + +```` may include addition syntax to enable different connectivity for pass tracks + +.. option:: sub_type="" + + Connecting type for pass tracks in each switch block + If not specified, the pass tracks will the same connecting patterns as start/end tracks, which are defined in ``type`` + +.. option:: sub_Fs="" + + Connectivity parameter for pass tracks in each switch block. Must be a multiple of 3. + If not specified, the pass tracks will the same connectivity as start/end tracks, which are defined in ``fs`` + +.. note:: Currently, OpenFPGA only supports uni-directional routing architectures + +.. note:: Currently, OpenFPGA only supports 1 ```` to be defined under each ```` + +.. note:: OpenFPGA require explicit names to be defined for each routing segement in ```` + + diff --git a/docs/source/arch_lang/link_circuit_modules.rst b/docs/source/arch_lang/annotate_vpr_arch.rst similarity index 97% rename from docs/source/arch_lang/link_circuit_modules.rst rename to docs/source/arch_lang/annotate_vpr_arch.rst index fc2a27256..25b0e5d3e 100644 --- a/docs/source/arch_lang/link_circuit_modules.rst +++ b/docs/source/arch_lang/annotate_vpr_arch.rst @@ -1,9 +1,11 @@ -Link circuit modules --------------------- +.. _annotate_vpr_arch: + +Bind circuit modules to VPR architecture +---------------------------------------- Each defined circuit model should be linked to an FPGA module defined in the original part of architecture descriptions. It helps FPGA-circuit creating the circuit netlists for logic/routing blocks. Since the original part lacks such support, we create a few XML properties to link to Circuit models. SRAM -==== +~~~~ To link the defined circuit model of SRAM into the FPGA architecture description, a new line in XML format should be added under the XML node device. The new XML node is named as sram, which defines the area of an SRAM and the name of the circuit model to be linked. An example is shown as follows: @@ -42,7 +44,7 @@ Here is an example. Switch Boxes -============= +~~~~~~~~~~~~ Original VPR architecture description contains an XML node called switchlist under which all the multiplexers of switch blocks are described. To link a defined circuit model to a multiplexer in the switch blocks, a new XML property circuit_model_name should be added to the descriptions. @@ -59,7 +61,7 @@ Here is an example: Connection Blocks -================== +~~~~~~~~~~~~~~~~~ To link the defined circuit model of the multiplexer to the Connection Blocks, a circuit_model_name should be added to the definition of Connection Blocks switches. However, the original architecture descriptions do not offer a switch description for connection boxes as they do for the switch blocks. Therefore, FPGA-circuit requires a new XML node called **cblock** under the root XML node architecture, where a switch for connection blocks can be defined. @@ -75,7 +77,7 @@ Here is the example: * **circuit_model_name:** should match a circuit model whose type is mux defined under module_circuit_models. Channel Wire Segments -===================== +~~~~~~~~~~~~~~~~~~~~~ Similar to the Switch Boxes and Connection Blocks, the channel wire segments in the original architecture descriptions can be adapted to provide a link to the defined circuit model. @@ -88,7 +90,7 @@ Similar to the Switch Boxes and Connection Blocks, the channel wire segments in * circuit_model_name: should match a circuit model whose type is chan_wire defined under module_circuit_models. Primitive Blocks inside Multi-mode Configurable Logic Blocks -============================================================= +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The architecture description employs a hierarchy of ``pb_types`` to depict the sub-modules and complex interconnections inside logic blocks. Each leaf node and interconnection in the pb_type hierarchy should be linked to a circuit model. Each primitive block, i.e., the leaf ``pb_types``, should be linked to a valid circuit model, using the XML syntax ``circuit_model_name``. diff --git a/docs/source/arch_lang/circuit_modules.rst b/docs/source/arch_lang/circuit_library.rst similarity index 97% rename from docs/source/arch_lang/circuit_modules.rst rename to docs/source/arch_lang/circuit_library.rst index e7f4e736a..138544bb2 100644 --- a/docs/source/arch_lang/circuit_modules.rst +++ b/docs/source/arch_lang/circuit_library.rst @@ -1,13 +1,15 @@ -Define Circuit-level Modules -============================ +.. _circuit_library: + +Circuit Library +--------------- To support FPGA Verilog/SPICE, Verily and Bitstream Generator, physical modules containing gate-level and transistor-level features are required for FPGA primitive blocks. The physical modules are defined in XML syntax, similar to the original VPR FPGA architecture description language. For each module that appears in the FPGA architecture, a circuit model should be defined. In the definition of a circuit model, the user can specify if the Verilog/SPICE netlist of the module is either auto-generated or user-defined. -Define circuit_models ---------------------- +Circuit Model Attributes +~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: xml @@ -43,8 +45,8 @@ Define circuit_models .. note:: Under the XML node circuit_model, the features of transistor-level designs can be defined. In the following table, we show the common features supported for all the modules. Then, we will introduce unique features supported only for some circuit models types. -Transistor level ----------------- +Design Technology-related Attributes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: xml @@ -65,6 +67,14 @@ Transistor level .. note:: Currently, the RRAM-based designs are only supported for multiplexers. + +Input/Output Buffer +~~~~~~~~~~~~~~~~~~~ + +Circuit Port Attributes +~~~~~~~~~~~~~~~~~~~~~~~ + + * input_buffer and output_buffer: * **exist:** [on|off]. Define the existence of the input_buffer or output_buffer. Note that the existence is valid for all the inputs and outputs. Note that if users want only part of the inputs (or outputs) to be buffered, this is not supported here. A solution can be building a user-defined Verilog/SPICE netlist. diff --git a/docs/source/arch_lang/circuit_model_examples.rst b/docs/source/arch_lang/circuit_model_examples.rst index 26cb27e13..7e6a50229 100644 --- a/docs/source/arch_lang/circuit_model_examples.rst +++ b/docs/source/arch_lang/circuit_model_examples.rst @@ -1,9 +1,11 @@ +.. _circuit_model_examples: + Circuit model examples -====================== +---------------------- The next subsections are dedicated to detailed examples of each circuit model type. Through these examples, we give a global overview of the different implementations which are available for the user. Inverters and Buffers ---------------------- +~~~~~~~~~~~~~~~~~~~~~ .. code-block:: xml @@ -131,7 +133,7 @@ This example shows: Pass-gate Logic ---------------- +~~~~~~~~~~~~~~~ .. code-block:: xml @@ -212,7 +214,7 @@ This example shows: SRAMs ------ +~~~~~ .. code-block:: xml @@ -231,7 +233,7 @@ SRAMs .. note:: The support SRAM modules should have a BL and a WL when the memory-bank-style configuration circuit is declared. Note that the WL should be the write/read enable signal, while BL is the data input. Logic gates ------------ +~~~~~~~~~~~ .. code-block:: xml @@ -250,7 +252,7 @@ Logic gates .. note:: It may happen that the port sequence in generated Verilog netlists has conflicts with the port sequence in standard and customized cells. To avoid this, users can set the XML keyword ``dump_explicit_port_map`` to be true, which enables explicit port mapping are dumped. Users can specify the pin/port name in the standard cell library using the XML keyword ``lib_name``. Multiplexers ------------- +~~~~~~~~~~~~ .. code-block:: xml @@ -353,7 +355,7 @@ If we arbitrarily fix the number of Mux entries at 4, the following code could i * The number of entries parametrized by ``size`` in input port-type. Look-Up Tables --------------- +~~~~~~~~~~~~~~ .. code-block:: xml @@ -425,7 +427,7 @@ The code describing this LUT is: * How each blocks is defined Flip-Flops ----------- +~~~~~~~~~~ .. code-block:: xml @@ -509,7 +511,7 @@ The code describing this FF is: * 1 port, ``clk``, defined as global Hard Logics ------------ +~~~~~~~~~~~ .. code-block:: xml @@ -533,7 +535,7 @@ Instructions of defining design parameters: * **port:** two types of ports (``input`` and ``output``) should be defined. If the user provides a user-defined Verilog/SPICE netlist, the bandwidth of ports should be defined to the same as the Verilog/SPICE netlist. Routing Wire Segments ---------------------- +~~~~~~~~~~~~~~~~~~~~~ FPGA-Verilog/SPICE provides two types of Verilog/SPICE models for the wire segments in FPGA architecture: @@ -596,7 +598,7 @@ The code describing this wire is: * How to use this circuit_model to auto-generate the Verilog/SPICE netlist I/O pads --------- +~~~~~~~~ .. code-block:: xml diff --git a/docs/source/arch_lang/interconnect.rst b/docs/source/arch_lang/direct_interconnect.rst similarity index 50% rename from docs/source/arch_lang/interconnect.rst rename to docs/source/arch_lang/direct_interconnect.rst index a6542ccb5..21b91b74b 100644 --- a/docs/source/arch_lang/interconnect.rst +++ b/docs/source/arch_lang/direct_interconnect.rst @@ -1,10 +1,12 @@ -Interconnection extensions -========================== +.. _direct_interconnect: + +Inter-Tile Direct Interconnection extensions +-------------------------------------------- This section introduces extensions on the architecture description file about existing interconnection description. Directlist ----------- +~~~~~~~~~~ The original direct connections in the directlist section are documented here_. Its description is given below: @@ -26,40 +28,49 @@ Our extension include three more options: -.. note:: these options are optional. However, if *interconnection_type* is set *x_dir* and *y_dir* are required. +.. note:: these options are optional. However, if `interconnection_type` is set `x_dir` and `y_dir` are required. -* **interconnection_type**: [``NONE`` | ``column`` | ``row``], specifies if it applies on a column or a row ot if it doesn't apply. +.. option:: interconnection_type="" -* **x_dir**: [``positive`` | ``negative``], specifies if the next cell to connect has a bigger or lower x value. Considering a coordinate system where (0,0) is the origin at the bottom left and *x* and *y* are positives: + the type of interconnection should be a string. + Available types are ``NONE`` | ``column`` | ``row``, specifies if it applies on a column or a row ot if it doesn't apply. - * x_dir="positive": +.. option:: x_dir="" - * interconnection_type="column": a column will be connected to a column on the **right**, if it exists. + Available directionalities are ``positive`` | ``negative``, specifies if the next cell to connect has a bigger or lower ``x`` value. + Considering a coordinate system where (0,0) is the origin at the bottom left and ``x`` and ``y`` are positives: - * interconnection_type="row": the most on the **right** cell from a row connection will connect the most on the **left** cell of next row, if it exists. + - x_dir="positive": - * x_dir="negative": + - interconnection_type="column": a column will be connected to a column on the ``right``, if it exists. - * interconnection_type="column": a column will be connected to a column on the **left**, if it exists. + - interconnection_type="row": the most on the ``right`` cell from a row connection will connect the most on the ``left`` cell of next row, if it exists. - * interconnection_type="row": the most on the **left** cell from a row connection will connect the most on the **right** cell of next row, if it exists. + - x_dir="negative": -* **y_dir**: [``positive`` | ``negative``], specifies if the next cell to connect has a bigger or lower x value. Considering a coordinate system where (0,0) is the origin at the bottom left and *x* and *y* are positives: + - interconnection_type="column": a column will be connected to a column on the ``left``, if it exists. - * y_dir="positive": + - interconnection_type="row": the most on the ``left`` cell from a row connection will connect the most on the ``right`` cell of next row, if it exists. - * interconnection_type="column": the **bottom** cell of a column will be connected to the next column **top** cell, if it exists. +.. option:: y_dir="" - * interconnection_type="row": a row will be connected on an **above** row, if it exists. + Available directionalities are ``positive`` | ``negative``, specifies if the next cell to connect has a bigger or lower x value. + Considering a coordinate system where (0,0) is the origin at the bottom left and `x` and `y` are positives: - * y_dir="negative": + - y_dir="positive": - * interconnection_type="column": the **top** cell of a column will be connected to the next column **bottom** cell, if it exists. + - interconnection_type="column": the ``bottom`` cell of a column will be connected to the next column ``top`` cell, if it exists. - * interconnection_type="row": a row will be connected on a row **below**, if it exists. + - interconnection_type="row": a row will be connected on an ``above`` row, if it exists. + + - y_dir="negative": + + - interconnection_type="column": the ``top`` cell of a column will be connected to the next column ``bottom`` cell, if it exists. + + - interconnection_type="row": a row will be connected on a row ``below``, if it exists. Example -------- +~~~~~~~ For this example, we will study a scan-chain implementation. The description could be: @@ -81,7 +92,7 @@ For this example, we will study a scan-chain implementation. The description cou In this figure, the red arrows represent the initial direct connection. The green arrows represent the point to point connection to connect all the columns of CLB. Truth table ------------ +~~~~~~~~~~~ A point to point connection can be applied in different ways than showed in the example section. To help the designer implement his point to point connection, a truth table with our new parameters id provided below. diff --git a/docs/source/arch_lang/generality.rst b/docs/source/arch_lang/generality.rst index 02da8e8ab..d594ff896 100644 --- a/docs/source/arch_lang/generality.rst +++ b/docs/source/arch_lang/generality.rst @@ -1,8 +1,46 @@ +.. _generality: + General Hierarchy -================= +----------------- + +For OpenFPGA using VPR7 +~~~~~~~~~~~~~~~~~~~~~~~ + The extension of the VPR architectural description language is developed as an independent branch of the original one. Most of the FPGA-SPICE descriptions are located under a XML node called , which is a child node under the root node . Under the , some child node is created for describing SPICE simulation settings, technology library and transistor-level modeling of circuit modules. In the following sub-sections, we will introduce the structures of these XML nodes and the parameters provided. +For OpenFPGA using VPR8 +~~~~~~~~~~~~~~~~~~~~~~~ + +OpenFPGA uses a separated XML file other than the VPR8 architecture description file. +This is to keep a loose integration to VPR8 so that OpenFPGA can easily integrate any future version of VPR with least engineering effort. +However, to implement a physical FPGA, OpenFPGA requires the original VPR XML to include full physical design details. +Full syntax can be found in :ref:`addon_vpr_syntax`. + +The OpenFPGA architecture description XML file consisting of the following parts: + + - ```` contains architecture-level information, such as device-level description, circuit-level and architecture annotations to original VPR architecture XML. It consists of the following code blocks + + - ```` includes a number of ``circuit_model``, each of which describe a primitive block in FPGA architecture, such as Look-Up Tables and multiplexers. Full syntax can be found in :ref:`circuit_library`. + - ```` includes transistor-level parameters, where users can specify which transistor models are going to be used when building the ``circuit models``. + - ```` includes detailed description on the configuration protocols to be used in FPGA fabric. + - ```` includes annotation on the connection block definition ```` in original VPR XML + - ```` includes annotation on the switch block definition ```` in original VPR XML + - ```` includes annotation on the routing segment definition ```` in original VPR XML + - ```` includes annotation on the inter-tile direct connection definitioin ```` in original VPR XML + - ```` includes annotation on the programmable block architecture ```` in original VPR XML + + - ```` includes all the parameters to be used in generate testbenches in simulation purpose. Full syntax can be found in :ref:`simulation_setting`. + + - ```` defines the clock-related settings in simulation, such as clock frequency and number of clock cycles to be used + - ```` defines universal options available in both HDL and SPICE simulators. This is mainly used by FPGA-SPICE + - ```` defines critical parameters to be used in monte-carlo simulations. This is used by FPGA-SPICE + - ```` defines the parameters used to measure signal slew and delays. This is used by FPGA-SPICE + - ```` defines the parameters used to generate voltage stimuli in testbenches. This is used by FPGA-SPICE + +.. note:: ```` will be applied to ``circuit_model`` when running FPGA-SPICE. It will not impact FPGA-Verilog, FPGA-Bitstream, FPGA-SDC. + +.. note:: the parameters in ```` will be applied to both FPGA-Verilog and FPGA-SPICE simulations diff --git a/docs/source/arch_lang/index.rst b/docs/source/arch_lang/index.rst index 86edc3886..52fd17caa 100644 --- a/docs/source/arch_lang/index.rst +++ b/docs/source/arch_lang/index.rst @@ -1,6 +1,3 @@ -Extended Architecture Description Language -========================================== - .. _arch_lang: Extended FPGA Architecture Description Language @@ -9,16 +6,18 @@ Extended Architecture Description Language generality - interconnect - - spice_sim_setting + addon_vpr_syntax - tech_lib + direct_interconnect + + simulation_setting + + technology_library - circuit_modules + circuit_library circuit_model_examples - link_circuit_modules + annotate_vpr_arch diff --git a/docs/source/arch_lang/spice_sim_setting.rst b/docs/source/arch_lang/simulation_setting.rst similarity index 56% rename from docs/source/arch_lang/spice_sim_setting.rst rename to docs/source/arch_lang/simulation_setting.rst index 3beda6e51..1b63e8c17 100644 --- a/docs/source/arch_lang/spice_sim_setting.rst +++ b/docs/source/arch_lang/simulation_setting.rst @@ -1,5 +1,11 @@ -Parameters for SPICE simulation settings -======================================== +.. _simulation_setting: + +Simulation settings +------------------- + +For OpenFPGA using VPR7 +~~~~~~~~~~~~~~~~~~~~~~~ + All the parameters that need to be defined in the HSPICE simulations are located under a child node called , which is under its father node . The parameters are divided into three categories and can be defined in three XML nodes, , and , respectively. @@ -130,5 +136,149 @@ Define the starting and ending point in measuring the delay between two signals * **input_thres_pct:** the starting point in measuring the delay of a falling edge. It is expressed as a percentage of the maximum voltage of a signal. For example, upper_thres_pct=0.5 is depicted in :numref:`fig_meas_edge`. * **output_thres_pct:** the ending point in measuring the delay of a falling edge. It is expressed as a percentage of the maximum voltage of a signal. For example, lower_thres_pct=0. 5 is depicted in :numref:`fig_meas_edge`. + + +For OpenFPGA using VPR8 +~~~~~~~~~~~~~~~~~~~~~~~ + +All the simulation settings are stored under the XML node ```` +General organization is as follows + +.. code-block:: xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Clock Setting +^^^^^^^^^^^^^ +Clock setting focuses on defining the clock periods to applied on FPGA fabrics +As a programmable device, an FPGA has two types of clocks. +The first is the operating clock, which is applied by users' implementations. +The second is the programming clock, which is applied on the configuration protocol to load users' implementation to FPGA fabric. +OpenFPGA allows users to freely define these clocks as well as the number of clock cycles. +We should the full syntax in the code block below and then provide details on each of them. + +.. code-block:: xml + + + + + + +Operating clock setting +``````````````````````` +Operating clocks are defined under the XML node ```` + +- ``frequency="`` + Specify frequency of the operating clock. OpenFPGA allows users to specify an absolute value in the unit of ``[Hz]`` + Alternatively, users can bind the frequency to the maximum clock frequency analyzed by VPR STA engine. + This is very useful to validate the maximum operating frequency for users' implementations + In such case, the value of this attribute should be a reserved word ``auto``. + +- ``num_cycles="|"`` + can be either ``auto`` or an integer. When set to ``auto``, OpenFPGA will infer the number of clock cycles from the average/median of all the signal activities. + When set to an integer, OpenFPGA will use the given number of clock cycles in HDL and SPICE simulations. + +- ``slack=""`` + add a margin to the critical path delay in the HDL and SPICE simulations. + This parameter is applied to the critical path delay provided by VPR STA engine. + So it is only valid when option ``frequency`` is set to ``auto``. + This aims to compensate any inaccuracy in STA results. + Typically, the slack value is between ``0`` and ``1``. + For example, ``slack=0.2`` implies that the actual clock period in simulations is 120% of the critical path delay reported by VPR. + +.. note:: Only valid when option ``frequency`` is set to ``auto`` + +.. warning:: Avoid to use a negative slack! This may cause your simulation to fail! + +Programming clock setting +````````````````````````` +Programming clocks are defined under the XML node ```` + +- ``frequency=""`` + Specify the frequency of the programming clock using an absolute value in the unit of ``[Hz]`` + This frequency is used in testbenches for programming phase simulation. + +.. note:: Programming clock frequency is typically much slower than the operating clock and strongly depends on the process technology. Suggest to characterize the speed of your configuration protocols before specifying a value! + +Simulator Option +^^^^^^^^^^^^^^^^ +This XML node includes universal options available in both HDL and SPICE simulators. + +.. note:: This is mainly used by FPGA-SPICE + +```` + Specify the temperature which will be defined in SPICE netlists. In the top SPICE netlists, it will show as + +.. code-block:: python + + .temp + +```` + Specify the options in outputting simulation results to log files + +- ``verbose="true|false"`` + + Specify if the simulation waveforms should be printed out after SPICE simulations. If turned on, it will show in all the SPICE netlists + +.. code-block:: python + + .option POST + +.. note:: when the SPICE netlists are large or a long simulation duration is defined, the post option is recommended to be off. If not, huge disk space will be occupied by the waveform files. + +- ``captab="true|false"`` + Specify if the capacitances of all the nodes in the SPICE netlists will be printed out. If turned on, it will show inn the top-level SPICE netlists + +.. code-block:: python + + .option CAPTAB + +.. note:: When turned on, the SPICE simulation runtime may increase. + +```` + Specify the simulation steps (accuracy) to be used + +- ``type="abs|frac"`` + + Specify the type of transient step in SPICE simulation. + + * When ``abs`` is selected, the accuracy should be the absolute value, such as ``1e-12``. + + * When ``frac`` is selected, the accuracy is the number of simulation points in a clock cycle period, for example, 100. +- ``value=""`` + + Specify the transient step in SPICE simulation. Typically, the smaller the step is, the higher the accuracy that can be reached while the long simulation runtime is. The recommended accuracy is between 0.1ps and 0.01ps, which generates good accuracy and runtime is not significantly long. diff --git a/docs/source/arch_lang/tech_lib.rst b/docs/source/arch_lang/technology_library.rst similarity index 96% rename from docs/source/arch_lang/tech_lib.rst rename to docs/source/arch_lang/technology_library.rst index 264d1bc1a..7ec621f55 100644 --- a/docs/source/arch_lang/tech_lib.rst +++ b/docs/source/arch_lang/technology_library.rst @@ -1,5 +1,7 @@ -Technology library Declaration -============================== +.. _technology_library_syntax: + +Technology library +------------------ .. code-block:: xml diff --git a/docs/source/contact.rst b/docs/source/contact.rst index 469defe1a..1e7e1e801 100644 --- a/docs/source/contact.rst +++ b/docs/source/contact.rst @@ -1,7 +1,7 @@ .. _contact: Contact -======= +~~~~~~~ General questions: diff --git a/docs/source/fpga_bitstream/index.rst b/docs/source/fpga_bitstream/index.rst index 44a1e0312..6380cc322 100644 --- a/docs/source/fpga_bitstream/index.rst +++ b/docs/source/fpga_bitstream/index.rst @@ -1,8 +1,5 @@ -FPGA-Bitstream -============== - .. _fpga_bitstream: - User Manual for FPGA Bitstream Generator + FPGA-Bitstream .. toctree:: :maxdepth: 2 diff --git a/docs/source/fpga_spice/index.rst b/docs/source/fpga_spice/index.rst index c147f8e90..c9c04568a 100644 --- a/docs/source/fpga_spice/index.rst +++ b/docs/source/fpga_spice/index.rst @@ -1,8 +1,5 @@ -FPGA-SPICE: SPICE Auto-Generation -==================================== - .. _fpga_spice: - User Manual for FPGA-SPICE support + FPGA-SPICE .. toctree:: diff --git a/docs/source/fpga_verilog/index.rst b/docs/source/fpga_verilog/index.rst index b5ef00c9f..2ea55c478 100644 --- a/docs/source/fpga_verilog/index.rst +++ b/docs/source/fpga_verilog/index.rst @@ -1,8 +1,5 @@ -FPGA-Verilog: Verilog Auto-Generation -------------------------------------- - .. _fpga_verilog: - User Manual for FPGA Verilog Generator + FPGA-Verilog .. toctree:: :maxdepth: 2 diff --git a/docs/source/index.rst b/docs/source/index.rst index 9f4dd36d8..34beae468 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -19,10 +19,14 @@ Welcome to OpenFPGA's documentation! .. toctree:: :maxdepth: 2 - :caption: Tools Guide + :caption: Architecture Description Language arch_lang/index +.. toctree:: + :maxdepth: 2 + :caption: OpenFPGA Tools + fpga_spice/index fpga_verilog/index diff --git a/docs/source/motivation.rst b/docs/source/motivation.rst index 2dd4447df..01ae7da54 100644 --- a/docs/source/motivation.rst +++ b/docs/source/motivation.rst @@ -1,5 +1,5 @@ -Motivation -========== +Why OpenFPGA? +------------- OpenFPGA aims to be an open-source framework that enables rapid prototyping of customizable FPGA architectures. As shown in :numref:`fig_openfpga_motivation`, a conventional approach will take a large group of experienced engineers more than one year to achieve production-ready layout and assoicated CAD tools. In fact, most of the engineering efforts are spent on manual layouts and developing ad-hoc CAD support. @@ -29,7 +29,7 @@ The rest of this section will focus on detailed motivation on each of them, as d FPGA-Verilog ------------- +~~~~~~~~~~~~ Driven by the strong need in data processing applications, Field Programmable Gate Arrays (FPGAs) are playing an ever-increasing role as programmable accelerators in modern computing systems. To fully unlock processing capabilities for domain-specific applications, FPGA architectures have to be tailored for seamless cooperation with other computing resources. However, prototyping and bringing to production a customized FPGA is a costly and complex endeavor even for industrial vendors. OpenFPGA, an opensource framework, aims to rapid prototype of customizable FPGA architectures through a semi-custom design approach. We propose an XML-to-Prototype design flow, where the Verilog netlists of a full FPGA fabric can be autogenerated using an extension of the XML language from the VTR framework and then fed into a back-end flow to generate production-ready layouts. @@ -37,7 +37,7 @@ computing systems. To fully unlock processing capabilities for domain-specific a The technical details can be found in our TVLSI'19 paper :cite:`XTang_TVLSI_2019` and FPL'19 paper :cite:`XTang_FPL_2019`. FPGA-SDC --------- +~~~~~~~~ Design constraints are indepensible in modern ASIC design flows to guarantee the performance level. OpenFPGA includes a rich SDC generator in the OpenFPGA framework to deal with both PnR constraints and sign-off timing analysis. @@ -47,7 +47,7 @@ The technical details can be found in our FPL'19 paper :cite:`XTang_FPL_2019`. FPGA-Bitstream --------------- +~~~~~~~~~~~~~~ EDA support is essential for end-users to implement designs on a customized FPGA. OpenFPGA provides a general-purpose bitstream generator FPGA-Bitstream for any architecture that can be described by VPR. As the native CAD tool for any customized FPGA that is produced by FPGA-Verilog, FPGA-Bitstream is ready to use once users finalize the XML-based architecture description file. This eliminates the huge engineering efforts spent on developing bitstream generator for customized FPGAs. @@ -56,7 +56,7 @@ Using FPGA-Bitstream, users can launch (1) Verilog-to-Bitstream flow. This is th The technical details can be found in our TVLSI'19 paper :cite:`XTang_TVLSI_2019` and FPL'19 paper :cite:`XTang_FPL_2019`. FPGA-SPICE ----------- +~~~~~~~~~~ The built-in timing and power analysis engines of VPR are based on analytical models :cite:`VBetz_Book_1999,JGoeders_FPT_2012`. Analytical model-based analysis can promise accuracy only on a limited number of circuit designs for which the model is valid. As the technology advancements create more opportunities on circuit designs and FPGA architectures, the analytical power model require to be updated to follow the new trends. However, without referring to simulation results, the analytical power models cannot prove their accuracy. SPICE simulators have the advantages of generality and accuracy over analytical models. For this reason, SPICE simulation results are often selected to check the accuracy of analytical models. Therefore, there is a strong need for a simulation-based power analysis approach for FPGAs, which can support general circuit designs. diff --git a/docs/source/tutorials/compile.rst b/docs/source/tutorials/compile.rst index cfa4dbe44..6844740d2 100644 --- a/docs/source/tutorials/compile.rst +++ b/docs/source/tutorials/compile.rst @@ -1,8 +1,10 @@ -How to Compile -============== +.. _compile: -General Compilation Guidelines ------------------------------- +How to Compile +-------------- + +General Guidelines +~~~~~~~~~~~~~~~~~~ OpenFPGA uses CMake to generate the Makefile scripts In general, please follow the steps to compile @@ -29,7 +31,7 @@ To quickly verify the tool is well compiled, user can run the following command python3 openfpga_flow/scripts/run_fpga_task.py compilation_verification --debug --show_thread_logs Dependencies ------------- +~~~~~~~~~~~~ Full list of dependencies can be found at travis_setup_link_ In particular, OpenFPGA requires specific versions for the following dependencies: @@ -42,7 +44,7 @@ In particular, OpenFPGA requires specific versions for the following dependencie .. _travis_setup_link: https://github.com/LNIS-Projects/OpenFPGA/blob/0cfb88a49f152aab0a06f309ff160f222bb51ed7/.travis.yml#L34 Docker ------- +~~~~~~ If some of these dependencies are not installed on your machine, you can choose to use a Docker (the Docker tool needs to be installed). For the ease of the customer first experience, a Dockerfile is provided in the OpenFPGA folder. A container ready to use can be created with the following command diff --git a/docs/source/tutorials/eda_flow.rst b/docs/source/tutorials/eda_flow.rst index 446098d60..31cffa0ca 100644 --- a/docs/source/tutorials/eda_flow.rst +++ b/docs/source/tutorials/eda_flow.rst @@ -1,5 +1,7 @@ -EDA flow -======== +.. _eda_flow: + +Supported EDA flows in OpenFPGA +------------------------------- As illustrated in :numref:`fig_eda_flow`, FPGA-SPICE creates a modified VTR flow. All the input files for VPR do not need modifications except the architecture description XML. As simulation-based power analysis requires the transistor-level netlists, we extend the architecture description language to support transistor-level modeling (See details in "Tools Guide>Extended Architecture Description Language"). FPGA-SPICE, embedded in VPR, outputs the SPICE netlists and testbenches according to placement and routing results when enabled by command-line options. (See each "FPGA-*Branch*" about command-line options available) Besides automatically generating all the SPICE netlists, FPGA-SPICE supports user-defined SPICE netlists for modules. We believe the support on user-defined SPICE netlists allows FPGA-SPICE to be general enough to support novel circuit designs and even technologies. (See "FPGA-SPICE... > Create Customized SPICE Modules" for guidelines in customizing your FPGA-SPICE compatible SPICE netlists.) With the dumped SPICE netlists and testbenches, a SPICE simulator, i.e., HSPICE, can be called to conduct a power analysis. FPGA-SPICE automatically generates a shell script, which brings convenience for users to run all the simulations (See "FPGA-SPICE... > Run SPICE simulation"). From cb7e4a1dfa1c78ec8550d70742e8e7fb7fcc9358 Mon Sep 17 00:00:00 2001 From: Xifan Tang Date: Mon, 9 Mar 2020 20:03:37 -0600 Subject: [PATCH 284/645] finish documentation the simulation settings in VPR8 integration --- docs/source/arch_lang/simulation_setting.rst | 98 +++++++++++++++++++- docs/source/arch_lang/technology_library.rst | 2 +- 2 files changed, 95 insertions(+), 5 deletions(-) diff --git a/docs/source/arch_lang/simulation_setting.rst b/docs/source/arch_lang/simulation_setting.rst index 1b63e8c17..c6d9c1545 100644 --- a/docs/source/arch_lang/simulation_setting.rst +++ b/docs/source/arch_lang/simulation_setting.rst @@ -200,6 +200,8 @@ Operating clock setting ``````````````````````` Operating clocks are defined under the XML node ```` +.. option:: + - ``frequency="`` Specify frequency of the operating clock. OpenFPGA allows users to specify an absolute value in the unit of ``[Hz]`` Alternatively, users can bind the frequency to the maximum clock frequency analyzed by VPR STA engine. @@ -226,6 +228,8 @@ Programming clock setting ````````````````````````` Programming clocks are defined under the XML node ```` +.. option:: + - ``frequency=""`` Specify the frequency of the programming clock using an absolute value in the unit of ``[Hz]`` This frequency is used in testbenches for programming phase simulation. @@ -238,14 +242,23 @@ This XML node includes universal options available in both HDL and SPICE simulat .. note:: This is mainly used by FPGA-SPICE -```` +Operating condition +``````````````````` + +.. option:: `` + +- ``temperature=""`` Specify the temperature which will be defined in SPICE netlists. In the top SPICE netlists, it will show as .. code-block:: python .temp -```` +Output logs +``````````` + +.. option:: `` + Specify the options in outputting simulation results to log files - ``verbose="true|false"`` @@ -259,7 +272,7 @@ This XML node includes universal options available in both HDL and SPICE simulat .. note:: when the SPICE netlists are large or a long simulation duration is defined, the post option is recommended to be off. If not, huge disk space will be occupied by the waveform files. - ``captab="true|false"`` - Specify if the capacitances of all the nodes in the SPICE netlists will be printed out. If turned on, it will show inn the top-level SPICE netlists + Specify if the capacitances of all the nodes in the SPICE netlists will be printed out. If turned on, it will show in the top-level SPICE netlists .. code-block:: python @@ -267,7 +280,11 @@ This XML node includes universal options available in both HDL and SPICE simulat .. note:: When turned on, the SPICE simulation runtime may increase. -```` +Simulation Accuracy +``````````````````` + +.. option:: `` + Specify the simulation steps (accuracy) to be used - ``type="abs|frac"`` @@ -281,4 +298,77 @@ This XML node includes universal options available in both HDL and SPICE simulat - ``value=""`` Specify the transient step in SPICE simulation. Typically, the smaller the step is, the higher the accuracy that can be reached while the long simulation runtime is. The recommended accuracy is between 0.1ps and 0.01ps, which generates good accuracy and runtime is not significantly long. + +Simulation Speed +```````````````` +.. option:: + + Specify if any runtime optimization will be applied to the simulator. + +- ``fast_simulation="true|false"`` + + Specify if fast simulation is turned on for the simulator. + + If turned on, it will show in the top-level SPICE netlists + +.. code-block:: python + + .option fast + +Monte Carlo Simulation +`````````````````````` + +.. option:: + + Run SPICE simulations in monte carlo mode. + This is mainly for FPGA-SPICE + When turned on, FPGA-SPICE will apply the device variation defined in :ref:`technology_library` to monte carlo simulation + +- ``num_simulation_points=""`` + + Specify the number of simulation points to be considered in monte carlo. + The larger the number is, the longer simulation time will be but more accurate the results will be. + +Measurement Setting +``````````````````` +- Users can define the parameters in measuring the slew of signals, under XML node ```` + +- Users can define the parameters in measuring the delay of signals, under XML node ```` + +Both delay and slew measurement share the same syntax in defining the upper and lower voltage thresholds. + +.. option:: + + Define the starting and ending point in measuring the slew of a rising or a falling edge of a signal. + + - ``upper_thres_pct=""`` the ending point in measuring the slew of a rising edge. It is expressed as a percentage of the maximum voltage of a signal. For example, the meaning of upper_thres_pct=0.95 is depicted in :numref:`fig_measure_edge`. + + - ``lower_thres_pct=""`` the starting point in measuring the slew of a rising edge. It is expressed as a percentage of the maximum voltage of a signal. For example, the meaning of lower_thres_pct=0.05 is depicted in :numref:`fig_measure_edge`. + +.. _fig_measure_edge: + +.. figure:: figures/meas_edge.png + :scale: 80% + :alt: map to buried traesure + + An illustrative example on measuring the slew and delay of signals + +Stimulus Setting +```````````````` +Users can define the slew time of input and clock signals to be applied to FPGA I/Os in testbenches under XML node ```` and ```` respectively. +This is used by FPGA-SPICE in generating testbenches + +.. option:: + + Specify the slew rate of an input or clock signal at rising or falling edge + + - ``slew_type="[abs|frac]"`` specify the type of slew time definition at the rising or falling edge of a lock/input port. + + * The type of ``abs`` implies that the slew time is the absolute value. For example, ``slew_type="abs" slew_time="20e-12"`` means that the slew of a clock signal is 20ps. + * The type of ``frac`` means that the slew time is related to the period (frequency) of the clock signal. For example, ``slew_type="frac" slew_time="0.05"`` means that the slew of a clock signal takes 5% of the period of the clock. + + - ``slew_time=""`` specify the slew rate of an input or clock signal at the rising/falling edge. + + :numref:`fig_measure_edge` depicts the definition of the slew and delays of signals and the parameters that can be supported by FPGA-SPICE. + diff --git a/docs/source/arch_lang/technology_library.rst b/docs/source/arch_lang/technology_library.rst index 7ec621f55..180c45de5 100644 --- a/docs/source/arch_lang/technology_library.rst +++ b/docs/source/arch_lang/technology_library.rst @@ -1,4 +1,4 @@ -.. _technology_library_syntax: +.. _technology_library: Technology library ------------------ From d14fa16905996e501dffa394dfe458b10e39323b Mon Sep 17 00:00:00 2001 From: Xifan Tang Date: Mon, 9 Mar 2020 21:17:25 -0600 Subject: [PATCH 285/645] finish documentation update on technology library --- docs/source/arch_lang/technology_library.rst | 109 +++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/docs/source/arch_lang/technology_library.rst b/docs/source/arch_lang/technology_library.rst index 180c45de5..1b1598cce 100644 --- a/docs/source/arch_lang/technology_library.rst +++ b/docs/source/arch_lang/technology_library.rst @@ -3,6 +3,9 @@ Technology library ------------------ +For OpenFPGA using VPR7 +~~~~~~~~~~~~~~~~~~~~~~~ + .. code-block:: xml @@ -34,3 +37,109 @@ Technology library * **min_width:** specify the minimum width of p/n type transistor. This parameter will be used in building inverter, buffer, etc. as a base number for transistor sizing. +For OpenFPGA using VPR8 +~~~~~~~~~~~~~~~~~~~~~~~ + +Technology library aims to describe transistor-level parameters to be applied to the physical design of FPGAs. In addition to transistor models, technology library also supports the definition of process variations on any transistor models. +General organization is as follows. + +.. code-block:: xml + + + + + + + + + + + + + + + + +Device Library +^^^^^^^^^^^^^^ +Device library contains detailed description on device models, such as transistors and Resistive Random Access Memories (RRAMs). +A device library may consist of a number of ```` and each of them denotes a different transistor model. + +A device model represents a transistor/RRAM model available in users' technology library. + +.. option:: + + Specify the name and type of a device model + + - ``name=""`` is the unique name of the device model in the context of ````. + - ``type="transistor|rram"`` is the type of device model in terms of functionality + Currently, OpenFPGA supports two types: transistor and RRAM. + +.. note:: the name of ```` may not be the name in users' technology library. + +.. option:: + Specify the technology library that defines the device model + + - ``type="academia|industry"`` For the industry library, FPGA-SPICE will use ``.lib `` to include the library file in SPICE netlists. For academia library, FPGA-SPICE will use ``.include `` to include the library file in SPICE netlists + + - ``corner=""`` is the process corner name available in technology library. + For example, the type of transistors can be ``TT``, ``SS`` and ``FF`` *etc*. + + - ``ref=""`` specify the reference of in calling a transistor model. In SPICE netlists, define a transistor follows the convention: + + .. code-block:: xml + + + + The reference depends on the technology and the type of library. For example, the PTM bulk model uses “M†as the reference while the PTM FinFET model uses “X†as the reference. + + - ``path=""`` specify the path of the technology library file. For example: + + .. code-block:: xml + + lib_path=/home/tech/45nm.pm. + +.. option:: + + Specify transistor-level design parameters + + - ``vdd=""`` specify the working voltage for the technology. The voltage will be used as the supply voltage in all the SPICE netlists. + + - ``pn_ratio=""`` specify the ratio between *p*-type and *n*-type transistors. The ratio will be used when building circuit structures such as inverters, buffers, etc. + +.. option:: + + Specify device-level parameters for transistors + + - ``name=""`` specify the name of the p/n type transistor, which can be found in the manual of the technology provider. + + - ``chan_length=""`` specify the channel length of *p/n* type transistor. + + - ``min_width=""`` specify the minimum width of *p/n* type transistor. This parameter will be used in building inverter, buffer, *etc*. as a base number for transistor sizing. + + - ``variation=""`` specify the variation name defined in the ```` + +.. option:: + + Specify device-level parameters for RRAMs + + - ``rlrs=""`` specify the resistance of Low Resistance State (LRS) of a RRAM device + + - ``rhrs=""`` specify the resistance of High Resistance State (HRS) of a RRAM device + + - ``variation=""`` specify the variation name defined in the ```` + +Variation Library +^^^^^^^^^^^^^^^^^ +Variation library contains detailed description on device variations specified by users. +A variation library may consist of a number of ```` and each of them denotes a different variation parameter. + +.. option:: + + Specify detail variation parameters + + - ``name=""`` is the unique name of the device variation in the context of ````. The name will be used in ```` to bind variations + + - ``abs_variation=""`` is the absolute deviation of a variation + + - ``num_sigma=""`` is the standard deviation of a variation From 2a3c5b98a52d64e5e58cf9732e8149090c0629da Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 9 Mar 2020 21:25:13 -0600 Subject: [PATCH 286/645] minor format fix in documentation --- docs/source/arch_lang/technology_library.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/source/arch_lang/technology_library.rst b/docs/source/arch_lang/technology_library.rst index 1b1598cce..5089f9f2e 100644 --- a/docs/source/arch_lang/technology_library.rst +++ b/docs/source/arch_lang/technology_library.rst @@ -78,6 +78,7 @@ A device model represents a transistor/RRAM model available in users' technology .. note:: the name of ```` may not be the name in users' technology library. .. option:: + Specify the technology library that defines the device model - ``type="academia|industry"`` For the industry library, FPGA-SPICE will use ``.lib `` to include the library file in SPICE netlists. For academia library, FPGA-SPICE will use ``.include `` to include the library file in SPICE netlists From 54dfdc0cc1a4d28955916befa1294c6b2499cba6 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 10 Mar 2020 12:18:12 -0600 Subject: [PATCH 287/645] update general documentation on circuit library --- docs/source/arch_lang/circuit_library.rst | 183 ++++++++++++++++++- docs/source/arch_lang/simulation_setting.rst | 1 - 2 files changed, 176 insertions(+), 8 deletions(-) diff --git a/docs/source/arch_lang/circuit_library.rst b/docs/source/arch_lang/circuit_library.rst index 138544bb2..04f0039b4 100644 --- a/docs/source/arch_lang/circuit_library.rst +++ b/docs/source/arch_lang/circuit_library.rst @@ -3,13 +3,16 @@ Circuit Library --------------- +For OpenFPGA using VPR7 +~~~~~~~~~~~~~~~~~~~~~~~ + To support FPGA Verilog/SPICE, Verily and Bitstream Generator, physical modules containing gate-level and transistor-level features are required for FPGA primitive blocks. The physical modules are defined in XML syntax, similar to the original VPR FPGA architecture description language. For each module that appears in the FPGA architecture, a circuit model should be defined. In the definition of a circuit model, the user can specify if the Verilog/SPICE netlist of the module is either auto-generated or user-defined. Circuit Model Attributes -~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: xml @@ -46,7 +49,7 @@ Circuit Model Attributes Design Technology-related Attributes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: xml @@ -68,12 +71,8 @@ Design Technology-related Attributes .. note:: Currently, the RRAM-based designs are only supported for multiplexers. -Input/Output Buffer -~~~~~~~~~~~~~~~~~~~ - Circuit Port Attributes -~~~~~~~~~~~~~~~~~~~~~~~ - +^^^^^^^^^^^^^^^^^^^^^^^ * input_buffer and output_buffer: @@ -112,3 +111,173 @@ Circuit Port Attributes .. note:: Different types of ``circuit_model`` have different XML syntax, with which users can highly customize their circuit topologies. See refer to examples of ``circuit_model`` for more details. .. note:: Note that we have a list of reserved port names, which indicate the usage of these ports when building FPGA fabrics. Please do not use ``mem_out``, ``mem_inv``, ``bl``, ``wl``, ``blb``, ``wlb``, ``ccff_head`` and ``ccff_tail``. + +For OpenFPGA using VPR8 +~~~~~~~~~~~~~~~~~~~~~~~ + +Circuit design is a dominant factor in Power, Performance, Area (P.P.A.) of FPGA fabrics. +Upon practical applications, the hardware engineers may select various circuits to implement their FPGA fabrics. +For instance, a ultra-low-power FPGA may be built with ulta-low-power circuit cells while a high-performance FPGA may use absolutely different circuit cells. +OpenFPGA provide enriched XML syntax for users to highly customize their circuits in FPGA fabric. + +In the XML file, users can define a library of circuits, each of which corresponds to a primitive module required in the FPGA architecture. +Users can specify if the Verilog/SPICE netlist of the module is either auto-generated by OpenFPGA or provided by themselves. +As such, OpenFPGA can support any circuit design, leading to high flexibility in building FPGA fabrics. + +In principle, a circuit library consists of a number of ````, each of which correspond to a circuit design. +OpenFPGA supports a wide range of circuit designs. +The ```` could be as small as a cornerstone cell, such as inverter, buffer *etc*., or as large as a hardware IP, such as Block RAM. + +.. code-block:: xml + + + + + + + + +Currently, OpenFPGA supports the following categories of circuits: + + - inverters/buffers + - pass-gate logic, including transmission gates and pass transistors + - standard cell logic gates, including AND, OR and MUX2 + - metal wires + - multiplexers + - flip-flops + - Look-Up Tables, including single-output and multi-output fracturable LUTs + - Statis Random Access Memory (SRAM) + - scan-chain flip-flops + - I/O pad + - hardware IPs + +Circuit Model +^^^^^^^^^^^^^ + +As OpenFPGA supports many types of circuit models and their circuit-level implementation could be really different, each type of circuit model has special syntax to customize their designs. +However, most circuit models share the common generality in XML language. +Here, we focus these common syntax and we will detail special syntax in :ref:`circuit_model_examples` + +.. code-block:: xml + + + + + + + + + + +.. option:: + + Specify the general attributes for a circuit model + + - ``type="inv_buf|pass_gate|gate|mux|wire|chan_wire|sram|lut|ff|ccff|hard_logic|iopad"`` Specify the type of circuit model. For the circuit models in the type of mux/wire/chan_wire/lut, FPGA-Verilog/SPICE can auto-generate Verilog/SPICE netlists. For the rest, FPGA-Verilog/SPICE requires a user-defined Verilog/SPICE netlist. + + - ``name=""`` Specify the name of this circuit model. The name should be unique and will be used to create the Verilog/SPICE module in Verilog/SPICE netlists. Note that for a customized Verilog/SPICE netlist, the name defined here MUST be the name in the customized Verilog/SPICE netlist. FPGA-Verilog/SPICE will check if the given name is conflicted with any reserved words. + + - ``prefix=""`` Specify the name of the ```` to shown in the auto-generated Verilog/SPICE netlists. The prefix can be the same as the name defined above. And again, the prefix should be unique + + - ``is_default="true|false"`` Specify this circuit model is the default one for those in the same types. If a primitive module in VPR architecture is not linked to any circuit model by users, FPGA-Verilog/SPICE will find the default circuit model defined in the same type. + + - ``spice_netlist=""`` Specify the path and file name of a customized SPICE netlist. For some modules such as SRAMs, FFs, I/O pads, FPGA-SPICE does not support auto-generation of the transistor-level sub-circuits because their circuit design is highly dependent on the technology nodes. These circuit designs should be specified by users. For the other modules that can be auto-generated by FPGA-SPICE, the user can also define a custom netlist. + + - ``verilog_netlist=""`` Specify the path and file name of a customized Verilog netlist. For some modules such as SRAMs, FFs, I/O pads, FPGA-Verilog does not support auto-generation of the transistor-level sub-circuits because their circuit design is highly dependent on the technology nodes. These circuit designs should be specified by users. For the other modules that can be auto-generated by FPGA-Verilog, the user can also define a custom netlist. + + - ``dump_structural_verilog="true|false"`` When the value of this keyword is set to be true, Verilog generator will output gate-level netlists of this module, instead of behavior-level. Gate-level netlists bring more opportunities in layout-level optimization while behavior-level is more suitable for high-speed formal verification and easier in debugging with HDL simulators. + +.. warning:: ``prefix`` may be deprecated soon + +.. note:: Multiplexers cannot be user-defined. + +.. note:: For a circuit model type, only one circuit model can be set as default. + +.. note:: If ```` or ```` are not specified, FPGA-Verilog/SPICE auto-generates the Verilog/SPICE netlists for multiplexers, wires, and LUTs. + +.. note:: The user-defined netlists, such as LUTs, the decoding methodology should comply with the auto-generated LUTs!!! + +Design Technology +^^^^^^^^^^^^^^^^^ + +.. option:: + + Specify the design technology applied to a ```` + + - ``type="cmos|rram"`` Specify the type of design technology of the ````. Currently, OpenFPGA supports CMOS and RRAM technology for circuit models. + CMOS technology can be applied to any types of ````, while RRAM technology is only applicable to multiplexers and SRAMs + +.. note:: Each ```` may have different technologies + +Input and Output Buffers +^^^^^^^^^^^^^^^^^^^^^^^^ + +.. option:: + + - ``exist="true|false"`` Define the existence of the input buffer. Note that the existence is valid for all the inputs. + + - ``circuit_model_name=""`` Specify the name of circuit model which is used to implement input buffer, the type of specified circuit model should be ``inv_buf``. + +.. option:: + + - ``exist="true|false"`` Define the existence of the output buffer. Note that the existence is valid for all the outputs. Note that if users want only part of the inputs (or outputs) to be buffered, this is not supported here. A solution can be building a user-defined Verilog/SPICE netlist. + + - ``circuit_model_name=""`` Specify the name of circuit model which is used to implement the output buffer, the type of specified circuit model should be ``inv_buf``. + +.. note:: If users want only part of the inputs (or outputs) to be buffered, this is not supported here. A solution can be building a user-defined Verilog/SPICE netlist. + +Pass Gate Logic +^^^^^^^^^^^^^^^ + +.. option:: + + - ``circuit_model_name=""`` Specify the name of the circuit model which is used to implement pass-gate logic, the type of specified circuit model should be ``pass_gate``. + +.. note:: pass-gate logic are used in building multiplexers and LUTs. + + +Circuit Port +^^^^^^^^^^^^ + +A circuit model may consist of a number of ports. The port list is mandatory in any ``circuit_model`` and must be consistent to any user-defined netlists. + +.. option:: + + Define the attributes for a port of a circuit model. + + - ``type="input|output|sram|clock"`` Specify the type of the port, i.e., the directionality and usage. For programmable modules, such as multiplexers and LUTs, SRAM ports MUST be defined. For registers, such as FFs and memory banks, clock ports MUST be defined. + + - ``prefix=`` the name of the port to appear in the autogenerated netlists. Each port will be shown as ``[i]`` in Verilog/SPICE netlists. + + - ``lib_name=`` the name of the port defined in standard cells or customized cells. If not specified, this attribute will be the same as ``prefix``. + + - ``size=`` bandwidth of the port. MUST be larger than zero. + + - ``default_val=""`` Specify default logic value for a port, which is used as the initial logic value of this port in testbench generation. Can be either 0 or 1. We assume each pin of this port has the same default value. + + - ``circuit_model_name=""`` Specify the name of the circuit model which is connected to this port. + + - ``mode_select="true|false"`` Specify if this port controls the mode switching in a configurable logic block. This is due to that a configurable logic block can operate in different modes, which is controlled by SRAM bits. + + - ``is_global="true|false"`` can be either ``true`` or ``false``. Specify if this port is a global port, which will be routed globally. Note that when multiple global ports are defined with the same name, these global ports will be short-wired together. + + - ``is_set="true|false"`` Specify if this port controls a set signal. All the set ports are connected to global set voltage stimuli in testbenches. + + - ``is_reset="true|false"`` Specify if this port controls a reset signal. All the reset ports are connected to a global reset voltage stimuli in testbenches. + + - ``is_config_enable="true|false"`` Specify if this port controls a configuration-enable signal. Only valid when ``is_global`` is ``true``. This port is only enabled during FPGA configuration, and always disabled during FPGA operation. All the ``config_enable`` ports are connected to global configuration-enable voltage stimuli in testbenches. + +.. note:: ``sram`` and ``clock`` ports are considered as inputs in terms of directionality + +.. note:: ``circuit_model_name`` is only valid when the type of this port is ``sram``. + +.. note:: ``mode_select`` is only valid when the type of this port is ``sram``. + +.. note:: ``is_set``, ``is_reset`` and ``is_config_enable`` are only valid when ``is_global`` is ``true``. + +.. note:: Different types of ``circuit_model`` have different XML syntax, with which users can highly customize their circuit topologies. See refer to examples of :ref:``circuit_model_example`` for more details. + +.. note:: Note that we have a list of reserved port names, which indicate the usage of these ports when building FPGA fabrics. Please do not use ``mem_out``, ``mem_inv``, ``bl``, ``wl``, ``blb``, ``wlb``, ``ccff_head`` and ``ccff_tail``. diff --git a/docs/source/arch_lang/simulation_setting.rst b/docs/source/arch_lang/simulation_setting.rst index c6d9c1545..d50067843 100644 --- a/docs/source/arch_lang/simulation_setting.rst +++ b/docs/source/arch_lang/simulation_setting.rst @@ -371,4 +371,3 @@ This is used by FPGA-SPICE in generating testbenches - ``slew_time=""`` specify the slew rate of an input or clock signal at the rising/falling edge. :numref:`fig_measure_edge` depicts the definition of the slew and delays of signals and the parameters that can be supported by FPGA-SPICE. - From 8db257946ca57826983b912a9caf376e88cc231c Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 10 Mar 2020 12:18:39 -0600 Subject: [PATCH 288/645] remove backport in travis setup. The link is dead now. Plus we no longer need the backport for a newer version of cmake --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f06eb8db1..0b5ce92fb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,7 +29,6 @@ matrix: apt: sources: - ubuntu-toolchain-r-test # For newer GCC - - george-edison55-precise-backports # For cmake - llvm_toolchain-trusty-7 packages: - autoconf From 7195564455886f7c19b5f6c88293b2724d94c810 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 10 Mar 2020 16:17:20 -0600 Subject: [PATCH 289/645] reworked circuit model examples in documentation. Now we are consistent to latest syntax --- docs/source/arch_lang/circuit_library.rst | 6 +- .../arch_lang/circuit_model_examples.rst | 601 +++++++++++------- 2 files changed, 374 insertions(+), 233 deletions(-) diff --git a/docs/source/arch_lang/circuit_library.rst b/docs/source/arch_lang/circuit_library.rst index 04f0039b4..b28acafea 100644 --- a/docs/source/arch_lang/circuit_library.rst +++ b/docs/source/arch_lang/circuit_library.rst @@ -250,11 +250,11 @@ A circuit model may consist of a number of ports. The port list is mandatory in - ``type="input|output|sram|clock"`` Specify the type of the port, i.e., the directionality and usage. For programmable modules, such as multiplexers and LUTs, SRAM ports MUST be defined. For registers, such as FFs and memory banks, clock ports MUST be defined. - - ``prefix=`` the name of the port to appear in the autogenerated netlists. Each port will be shown as ``[i]`` in Verilog/SPICE netlists. + - ``prefix=""`` the name of the port to appear in the autogenerated netlists. Each port will be shown as ``[i]`` in Verilog/SPICE netlists. - - ``lib_name=`` the name of the port defined in standard cells or customized cells. If not specified, this attribute will be the same as ``prefix``. + - ``lib_name=""`` the name of the port defined in standard cells or customized cells. If not specified, this attribute will be the same as ``prefix``. - - ``size=`` bandwidth of the port. MUST be larger than zero. + - ``size=""`` bandwidth of the port. MUST be larger than zero. - ``default_val=""`` Specify default logic value for a port, which is used as the initial logic value of this port in testbench generation. Can be either 0 or 1. We assume each pin of this port has the same default value. diff --git a/docs/source/arch_lang/circuit_model_examples.rst b/docs/source/arch_lang/circuit_model_examples.rst index 7e6a50229..a4472c01d 100644 --- a/docs/source/arch_lang/circuit_model_examples.rst +++ b/docs/source/arch_lang/circuit_model_examples.rst @@ -2,34 +2,36 @@ Circuit model examples ---------------------- -The next subsections are dedicated to detailed examples of each circuit model type. Through these examples, we give a global overview of the different implementations which are available for the user. +As circuit model in different types have various special syntax. +Here, we will provide detailed examples on each type of ``circuit_model``. +These examples may be considered as template for users to craft their own ``circuit_model``. Inverters and Buffers ~~~~~~~~~~~~~~~~~~~~~ +Template +```````` + .. code-block:: xml - - - - + + + + -.. note:: customized Verilog/SPICE netlists are not currently supported for inverters and buffers. +.. option:: -* design_technology: + - ``topology="inverter|buffer"`` Specify the type of this component, can be either an inverter or a buffer. - * **topology:** [``inverter`` | ``buffer``]. Specify the type of this component, can be either an inverter or a buffer. + - ``size=""`` Specify the driving strength of inverter/buffer. For a buffer, the size is the driving strength of the inverter at the second level. Note that we consider a two-level structure for a buffer here. - * **size:** Specify the driving strength of inverter/buffer. For a buffer, the size is the driving strength of the inverter at the second level. We consider a two-level structure for a buffer here. The support for multi-level structure of a buffer will be introduced in the tapered options. + - ``num_level=""`` Define the number of levels of a tapered inverter/buffer. This is required when users need an inverter or a buffer consisting of >2 stages - * **tapered:** [``on`` | ``off``]. Define if the buffer is a tapered (multi-level) buffer. When ``on`` is defined, the following parameter are required.* + - ``f_per_stage=""`` Define the ratio of driving strength between the levels of a tapered inverter/buffer. Default value is 4. - * **tap_drive_level:** Define the number of levels of a tapered buffer. This parameter is valid only when tapered is turned on. - - * **f_per_stage:** Define the ratio of driving strength between the levels of a tapered driver. This parameter is valid only when tapered is turned on. Default value is 4. - -**Inverter x1 example** +Inverter 1x Example +``````````````````` :numref:`fig_inv1` is the inverter symbol depicted in this example. @@ -37,9 +39,9 @@ Inverters and Buffers .. figure:: ./figures/Inverter_1.png :scale: 100% - :alt: classical inverter x1 symbol + :alt: classical inverter 1x symbol - Classical inverter x1 symbol. + Classical inverter 1x symbol. The XML code describing this inverter is: @@ -52,18 +54,20 @@ The XML code describing this inverter is: This example shows: - * The topology chosen as inverter - * Size of 1 for the output strength - * The tapered parameter is not declared and is off by default -**Power-gated Inverter x1 example** + - The topology chosen as inverter + - Size of 1 for the output strength + - The tapered parameter is not declared and is ``false`` by default + +Power-gated Inverter 1x example +``````````````````````````````` The XML code describing an inverter which can be power-gated by the control signals ``EN`` and ``ENB`` : .. code-block:: xml - + @@ -72,15 +76,16 @@ The XML code describing an inverter which can be power-gated by the control sign .. note:: For power-gated inverters: all the control signals must be set as ``config_enable`` so that the testbench generation will generate testing waveforms. If the power-gated inverters are auto-generated , all the ``config_enable`` signals must be ``global`` signals as well. If the pwoer-gated inverters come from user-defined netlists, restrictions on ``global`` signals are free. -**Buffer x2 example** +Buffer 2x example +````````````````` :numref:`fig_buff` is the buffer symbol depicted in this example. .. _fig_buff: .. figure:: ./figures/Buffer.png - :scale: 100% - :alt: buffer symbol composed by 2 inverter, its output strength equal 2 + :scale: 50% + :alt: buffer symbol composed by 2 inverter, its output strength equals to 2 Buffer made by two inverter, with an output strength of 2. @@ -95,19 +100,20 @@ The XML code describing this buffer is: This example shows: - * The topology chosen as buffer - * Size of 2 for the output strength - * The tapered parameter is not declared and is off by default + - The topology chosen as buffer + - Size of 2 for the output strength + - The tapered parameter is not declared and is ``false`` by default -**Tapered inverter x16 example** +Tapered inverter 16x example +```````````````````````````` :numref:`fig_invtap4` is the tapered inverter symbol depicted this example. .. _fig_invtap4: .. figure:: ./figures/Tapered_inverter.png - :scale: 100% + :scale: 50% :alt: tapered inverter composed by 3 inverter for an output strength = 16 Inverter with high output strength made by 3 stage of inverter. @@ -117,62 +123,65 @@ The XML code describing this inverter is: .. code-block:: xml - + This example shows: - * The topology chosen as inverter - * Size of 1 for the first stage output strength - * The tapered parameter is on. Then the required sub parameters are declared - * The number of stage is set to 3 by tap_drive_level - * f_per_stage is set to 4. Then 2nd stage output strength is 4* the 1st stage output strength (so 4*1 = 4) and the 3rd stage output strength is 4* the 2nd stage output strength (so 4*4 = 16). - + - The topology chosen as inverter + - Size of 1 for the first stage output strength + - The number of stage is set to 3 by + - f_per_stage is set to 4. Then 2nd stage output strength is 4* the 1st stage output strength (so 4*1 = 4) and the 3rd stage output strength is 4* the 2nd stage output strength (so 4*4 = 16). Pass-gate Logic ~~~~~~~~~~~~~~~ +Template +```````` + .. code-block:: xml - - - - - - + + + + + + -.. note:: customized Verilog/SPICE netlists are not currently supported for pass-gate logics. +.. note:: Please do not add input and output buffers to pass-gate logic. -* design_technology: +.. option:: - * **topology:** [``transmission_gate`` | ``pass_transistor``]. The transmission gate consists of a NMOS transistor and a PMOS transistor. The pass transistor consists of a NMOS transistor. + - ``topology="transmission_gate|pass_transistor"`` Specify the circuit topology for the pass-gate logic. A transmission gate consists of a *n*-type transistor and a *p*-type transistor. The pass transistor consists of only a *n*-type transistor. - * **nmos_size:** the size of NMOS transistor in a transmission gate or pass_transistor, expressed in terms of the min_width defined in XML node . + - ``nmos_size=""`` the size of *n*-type transistor in a transmission gate or pass_transistor, expressed in terms of the minimum width ``min_width`` defined in the transistor model in :ref:`technology_library`. - * **pmos_size:** the size of PMOS transistor in a transmission gate, expressed in terms of the min_width defined in XML node . + - ``pmos_size=""`` the size of *p*-type transistor in a transmission gate, expressed in terms of the minimum width ``min_width`` defined in the transistor model in :ref:`technology_library`. -**Transmission-gate example** +.. note:: ``nmos_size`` and ``pmos_size`` are required for FPGA-SPICE + +Transmission-gate Example +````````````````````````` :numref:`fig_passgate` is the pass-gate symbol depicted in this example. .. _fig_passgate: .. figure:: ./figures/pass-gate.png - :scale: 60% + :scale: 30% :alt: pmos and nmos transistortors forming a pass-gate - Pass-gate made by pmos ans nmos association. + Pass-gate made by a *p*-type and a *n*-type transistors. The XML code describing this pass-gate is: .. code-block:: xml - + @@ -180,18 +189,18 @@ The XML code describing this pass-gate is: This example shows: - * Topology is ``transmission_gate``, which means the component need entries for each transistor gate (pmos and nmos) - * 3 inputs considered, 1 for signal and 2 to control the transistors gates - * No input or output buffer used, these parameters can be uninitialized + - A ``transmission_gate`` built with a *n*-type transistor in the size of 1 and a *p*-type transistor in the size of 2. + - 3 inputs considered, 1 for datapath signal and 2 to turn on/off the transistors gates -**Pass-transistor example** +Pass-transistor Example +``````````````````````` :numref:`fig_passtran` is the pass-gate symbol depicted in this example. .. _fig_passtran: .. figure:: ./figures/pass_transistor.png - :scale: 50% + :scale: 30% :alt: nmos transistortor forming a pass-gate Pass-gate made by a nmos transistor. @@ -208,22 +217,23 @@ The XML code describing this pass-gate is: This example shows: - * Topology is ``pass_transistor``, which means the component need an entry for the transistor gate (nmos) - * 2 inputs considered, 1 for signal and 1 to control the transistor gate - * No input or output buffer used, these parameters can be uninitialized - + - A ``pass_transistor`` build with a *n*-type transistor in the size of 1 + - 2 inputs considered, 1 for datapath signal and 1 to turn on/off the transistor gate SRAMs ~~~~~ +Template +```````` + .. code-block:: xml - + - - - - + + + + .. note:: The circuit designs of SRAMs are highly dependent on the technology node and well optimized by engineers. Therefore, FPGA-Verilog/SPICE requires users to provide their customized SRAM Verilog/SPICE/Verilog netlists. A sample Verilog/SPICE netlist of SRAM can be found in the directory SpiceNetlists in the released package. FPGA-Verilog/SPICE assumes that all the LUTs and MUXes employ the SRAM circuit design. Therefore, currently only one SRAM type is allowed to be defined. @@ -235,64 +245,93 @@ SRAMs Logic gates ~~~~~~~~~~~ +The circuit model in the type of ``gate`` aims to support direct mapping to standard cells or customized cells provided by technology vendors or users. + +Template +```````` + .. code-block:: xml - - - - - - + + + + + + -.. note:: The circuit model in the type of gate aims to support direct mapping to standard cells or customized cells provided by technology vendors or users. -.. note:: The logic functionality of a gate can be defined through the XML keyword ``topology``. Currently, OpenFPGA supports AND, OR and MUX2 gates. As for standard cells, the size of each port is limited to 1. Currently, only 2-input and single-output logic gates are supported. +.. option:: + + - ``topology="AND|OR|MUX2"`` Specify the logic functionality of a gate. As for standard cells, the size of each port is limited to 1. Currently, only 2-input and single-output logic gates are supported. -.. note:: It may happen that the port sequence in generated Verilog netlists has conflicts with the port sequence in standard and customized cells. To avoid this, users can set the XML keyword ``dump_explicit_port_map`` to be true, which enables explicit port mapping are dumped. Users can specify the pin/port name in the standard cell library using the XML keyword ``lib_name``. +2-input OR Gate Example +``````````````````````` + +.. code-block:: xml + + + + + + + + + + 10e-12 8e-12 + + + 10e-12 7e-12 + + + +This example shows: + - A 2-input OR gate without any input and output buffers + - Propagation delay from input ``a`` to ``out`` is 10ps in rising edge and and 8ps in falling edge + - Propagation delay from input ``b`` to ``out`` is 10ps in rising edge and 7ps in falling edge Multiplexers ~~~~~~~~~~~~ +Template +```````` + .. code-block:: xml - - - - - - - - + + + + + + + + -.. note:: customized Verilog/SPICE netlists are not currently supported for multiplexers. +.. note:: user-defined Verilog/SPICE netlists are not currently supported for multiplexers. -* design_technology: +.. option:: - * **structure:** can be [``tree`` \| ``multi-level`` \| ``one-level``]. The structure options are valid for SRAM-based multiplexers. For RRAM-based multiplexers, currently we only support the circuit design in [5]. If ``multi-level`` the following parameter is required: + - ``structure="tree|multi-level|one-level"`` Specify the multiplexer structure for a multiplexer. The structure option is only valid for SRAM-based multiplexers. For RRAM-based multiplexers, currently we only support the one-level structure - * **num_level:** specify the number of levels when multi-level structure is selected, only. + - ``num_level=""`` Specify the number of levels when ``multi-level`` structure is selected. - * **add_const_input:** can be [``true`` \| ``false``]. When enabled, an extra input will be added to the multiplexer circuits defined in this ``circuit_model``. For example, an 4-input multiplexer will be turned to a 5-input multiplexer. The extra input will be wired to a constant value, which can be specified through the XML syntax ``const_input_val``. The constant value can be either 0 or 1 (By default it is 0). Note that adding such input will help reducing the leakage power of FPGA and parasitic signal activities, with a limited area overhead. + - ``add_const_input="true|false"`` Specify if an extra input should be added to the multiplexer circuits. For example, an 4-input multiplexer will be turned to a 5-input multiplexer. The extra input will be wired to a constant value, which can be specified through the XML syntax ``const_input_val``. - * **const_input_val:** specify the constant value, to which the extra input will be connected. This syntax is only valid when the ``add_const_input`` is set to true. + .. note:: Adding an extra constant input will help reducing the leakage power of FPGA and parasitic signal activities, with a limited area overhead. + + - ``const_input_val="0|1"`` Specify the constant value, to which the extra input will be connected. By default it is 0. This syntax is only valid when the ``add_const_input`` is set to true. - * **local_encoder:** can be [``true`` \| ``false``]. When enabled, an local encoder will be added to the multiplexer circuits defined in this ``circuit_model``. The local encoder will be interface the SRAM inputs of multiplexing structure and SRAMs. It can encode the one-hot codes (that drive the select port of multiplexing structure) to a binary code. For example, 8-bit ``00000001`` will be encoded to 3-bit ``000``. This will help reduce the number of SRAM cells used in FPGAs as well as configuration time (especially for scan-chain configuration protocols). But it may cost an area overhead. + - ``local_encoder="true|false"``. Specify if a local encoder should be added to the multiplexer circuits. The local encoder will interface the SRAM inputs of multiplexing structure and SRAMs. It can encode the one-hot codes (that drive the select port of multiplexing structure) to a binary code. For example, 8-bit ``00000001`` will be encoded to 3-bit ``000``. This will help reduce the number of SRAM cells used in FPGAs as well as configuration time (especially for scan-chain configuration protocols). But it may cost an area overhead. - .. note:: Local encoders are only applicable for one-level and multi-level multiplexers. Tree-like multiplexers are already encoded in their nature. + .. note:: Local encoders are only applicable for one-level and multi-level multiplexers. Tree-like multiplexers are already encoded in their nature. - * **prog_transistor_size:** valid only when the type of design technology is ``rram``. Specify the size of programming transistors used in the RRAM-based multiplexer, we use only n-type transistor and the size should be expressed in terms of the min_width defined in XML node ``transistors``. If type of design technology is ``rram``, then the following parameters are required: - - * **ron:** valid only when the type of design technology is rram. Specify the on-resistance of the RRAM device used in the RRAM-based multiplexer. - - * **roff:** valid only when the type of design technology is rram. Specify the off-resistance of the RRAM device used in the RRAM-based multiplexer. - -* port: for a multiplexer, the three types of ports, ``input``, ``output`` and ``sram`` should be defined. +.. note:: A multiplexer should have only three types of ports, ``input``, ``output`` and ``sram``, which are all mandatory. .. note:: For tree-like multiplexers, they can be built with standard cell MUX2. To enable this, users should define a ``circuit_model``, which describes a 2-input multiplexer (See details and examples in how to define a logic gate using ``circuit_model``. In this case, the ``circuit_model_name`` in the ``pass_gate_logic`` should be the name of MUX2 ``circuit_model``. -**Mux 1 level example** +One-level Mux Example +````````````````````` :numref:`fig_mux1` illustrates an example of multiplexer modelling, which consists of input/output buffers and a transmission-gate-based tree structure. @@ -318,12 +357,15 @@ The code describing this Multiplexer is: -**This example shows:** - * Each circuit model composing the Multiplexer - * The possibility to select the input or output buffers - * The possibility to select the pass-gate inside the Mux. +This example shows: + - A one-level 4-input CMOS multiplexer + - All the inputs will be buffered using the circuit model ``inv1x`` + - All the outputs will be buffered using the circuit model ``tapbuf4`` + - The multiplexer will be built by transmission gate using the circuit model ``tgate`` + - The multiplexer will have 4 inputs and 4 SRAMs to control which datapath to propagate -**Mux-tree example** +Tree-like Multiplexer Example +````````````````````````````` :numref:`fig_mux` illustrates an example of multiplexer modelling, which consists of input/output buffers and a transmission-gate-based tree structure. @@ -349,61 +391,101 @@ If we arbitrarily fix the number of Mux entries at 4, the following code could i -**This example shows:** - * The tree topology, 4 entries split in 2 2-to-1 Muxes then another one make the final selection. - * The possibility to select the input or output buffers - * The number of entries parametrized by ``size`` in input port-type. +This example shows: + - A tree-like 4-input CMOS multiplexer + - All the inputs will be buffered using the circuit model ``inv1x`` + - All the outputs will be buffered using the circuit model ``tapbuf4`` + - The multiplexer will be built by transmission gate using the circuit model ``tgate`` + - The multiplexer will have 4 inputs and 3 SRAMs to control which datapath to propagate Look-Up Tables ~~~~~~~~~~~~~~ +Template +```````` + .. code-block:: xml - - - - - - - - - - - + + + + + + + + + + + .. note:: The Verilog/SPICE netlists of LUT can be auto-generated or customized. The auto-generated LUTs are based on a tree-like multiplexer, whose gates of the transistors are used as the inputs of LUTs and the drains/sources of the transistors are used for configurable memories (SRAMs). The LUT provided in customized Verilog/SPICE netlist should have the same decoding methodology as the traditional LUT. -Additional design parameters for LUTs: +.. option:: -* **lut_input_buffer:** Define transistor-level description for the buffer for the inputs of a LUT (gates of the internal multiplexer). Use keyword circuit_model_name to specify the circuit_model that containing details of the circuit. + Define transistor-level description for the buffer for the inputs of a LUT (gates of the internal multiplexer). -* **lut_input_inverter:** Define transistor-level description for the inverter for the inputs of a LUT (gates of the internal multiplexer). Use keyword circuit_model_name to specify the circuit_model that containing details of the circuit. + - ``exist="true|false"`` Specify if the input buffer should exist for LUT inputs + - ``circuit_model_name=""`` Specify the ``circuit_model`` that will be used to build the input buffers -* **lut_intermediate_buffer:** Define transistor-level description for the buffer locating at intermediate stages of internal multiplexer of a LUT. Use keyword circuit_model_name to specify the circuit_model that containing details of the circuit. To customize the location, users can define an integer array in the XML keyword location_map. For example, "-1-1-" indicates buffer inseration to every two stages of the LUT multiplexer tree, considering a 6-input LUT. +.. note:: In the context of LUT, ``input_buffer`` corresponds to the buffer for the datapath inputs of multiplexers inside a LUT. ``lut_input_buffer`` corresponds to the buffer at the inputs of a LUT +.. option:: -Instructions of defining design parameters: + Define transistor-level description for the inverter for the inputs of a LUT (gates of the internal multiplexer). -* **input_buffer:** Specify the buffer/inverter that connects the SRAM outputs to the inputs of multiplexer. + - ``exist="true|false"`` Specify if the input buffer should exist for LUT inputs -* **pass_gate_logic:** Specify the pass-gates of the internal multiplexer, the same as the multiplexers. + - ``circuit_model_name=""`` Specify the ``circuit_model`` that will be used to build the input inverters -* **port:** three types of ports (input, output and sram) should be defined. If the user provides an customized Verilog/SPICE netlist, the bandwidth of ports should be defined to the same as the Verilog/SPICE netlist. To support customizable LUTs, each type of port contain special keywords. For input ports, the keyword tri_state_map aims to customize which inputs are fixed to constant values when the LUT is in fracturable modes. For example, ``tri_state_map`` ="----11" indicates that the last two inputs will be fixed to be logic '1' when a 6-input LUT is in fracturable modes. The circuit_model_name of input port is used to specify which logic gates will be used to tri-state the inputs in fracturable LUT modes. It is required to use an AND gate to force logic '0' or an OR gate to force logic '1' for the input ports. For output ports, the keyword lut_frac_level is used to specify the level in LUT multiplexer tree where the output port are wired to. For example, lut_frac_level="4" in a fracturable LUT6 means that the output are potentially wired to the 4th stage of a LUT multiplexer and it is an output of a LUT4. The keyword lut_output_mask describes which fracturable outputs are used. For instance, in a 6-LUT, there are potentially four LUT4 outputs can be wired out. lut_output_mask="0,2" indicates that only the first and the thrid LUT4 outputs will be used in fracturable mode. Note that the size of the output port should be consistent to the length of lut_output_mask. +.. option:: -* **SRAM port for mode selection:** To enable switch between different operating modes, the SRAM bits of a fracturable LUT consists of two parts: configuration memory and mode selecting. The SRAM port for mode selection is specified through the XML keyword mode_select. Note that the size of such SRAM port should be consistent to the number of 1s or 0s in the ``tri_state_map``. + Define transistor-level description for the buffer locating at intermediate stages of internal multiplexer of a LUT. -**LUT example** + - ``exist="true|false"`` Specify if the input buffer should exist at intermediate stages + + - ``circuit_model_name=""`` Specify the ``circuit_model`` that will be used to build these buffers + + - ``location_map="[1|-]"`` Customize the location of buffers in intermediate stages. Users can define an integer array consisting of '1' and '-'. For example, ``-1-1-`` indicates buffer inseration to every two stages of the LUT multiplexer tree, considering a 6-input LUT. + +.. note:: For a LUT, three types of ports (``input``, ``output`` and ``sram``) should be defined. If the user provides an customized Verilog/SPICE netlist, the bandwidth of ports should be defined to the same as the Verilog/SPICE netlist. To support customizable LUTs, each type of port contain special keywords. + +.. option:: + + - ``tri_state_map="[-|1]"`` Customize which inputs are fixed to constant values when the LUT is in fracturable modes. For example, ``tri_state_map="----11"`` indicates that the last two inputs will be fixed to be logic '1' when a 6-input LUT is in fracturable modes. + + - ``circuit_model_name=""`` Specify the circuit model to build logic gates in order to tri-state the inputs in fracturable LUT modes. It is required to use an ``AND`` gate to force logic '0' or an ``OR`` gate to force logic '1' for the input ports. + +.. option:: + + - ``lut_frac_level=""`` Specify the level in LUT multiplexer tree where the output port are wired to. For example, ``lut_frac_level="4"`` in a fracturable LUT6 means that the output are potentially wired to the 4th stage of a LUT multiplexer and it is an output of a LUT4. + + - ``lut_output_mask=""`` Describe which fracturable outputs are used. For instance, in a 6-LUT, there are potentially four LUT4 outputs can be wired out. ``lut_output_mask="0,2"`` indicates that only the first and the thrid LUT4 outputs will be used in fracturable mode. + +.. note:: The size of the output port should be consistent to the length of ``lut_output_mask``. + +.. option:: + + - ``mode_select="true|false"`` Specify if this port is used to switch the LUT between different operating modes, the SRAM bits of a fracturable LUT consists of two parts: configuration memory and mode selecting. + + - ``circuit_model_name=""`` Specify the circuit model to be drive the SRAM port. Typically, the circuit model should be in the type of ``ccff`` or ``sram``. + + - ``default_val="0|1"`` Specify the default value for the SRAM port. The default value will be used in generating testbenches for unused LUTs + +.. note:: The size of a mode-selection SRAM port should be consistent to the number of '1s' or '0s' in the ``tri_state_map``. + +Single-Output LUT Example +````````````````````````` :numref:`fig_lut` illustrates an example of LUT modeling, which consists of input/output buffers and a transmission-gate-based tree structure. .. _fig_lut: .. figure:: ./figures/lut.png - :scale: 100% + :scale: 80% :alt: Detailed LUT composition An example of a LUT with transistor-level design parameters. @@ -416,52 +498,90 @@ The code describing this LUT is: + -**This example shows:** - * The difference between ``input_buffer`` and ``lut_input_buffer`` and that they are independent. - * How each blocks is defined +This example shows: + - A 6-input LUT which is configurable by 64 SRAM cells. + - The multiplexer inside LUT will be built with transmission gate using circuuit model ``inv1x`` + - There are no internal buffered inserted to any intermediate stage of a LUT + +Fracturable LUT Example +````````````````````````` + +.. code-block:: xml + + + + + + + + + + + + + + + + +This example shows: + - Fracturable 6-input LUT which is configurable by 65 SRAM cells. + - Intermedate buffers are added to every two stages of the internal multiplexer + - There is a SRAM cell to switch the operating mode of this LUT, configured by a configuration-chain flip-flop ``ccff`` + - The last input ``in[5]`` of LUT will be tri-stated in dual-LUT5 mode. + - An 2-input OR gate will be wired to the last input ``in[5]`` to tri-state the input. The mode-select SRAM will be wired to an input of the OR gate. + It means that when the mode-selection bit is '1', the LUT will operate in dual-LUT5 mode. + - There will be two outputs wired to the 5th stage of routing multiplexer (the outputs of dual 5-input LUTs) + - By default, the mode-selection configuration bit will be '1', indicating that by default the LUT will operate in dual-LUT5 mode. Flip-Flops ~~~~~~~~~~ +Template +```````` + .. code-block:: xml - + - - - - - + + + + + .. note:: The circuit designs of flip-flops are highly dependent on the technology node and well optimized by engineers. Therefore, FPGA-Verilog/SPICE requires users to provide their customized FF Verilog/SPICE/Verilog netlists. A sample Verilog/SPICE netlist of FF can be found in the directory SpiceNetlists in the released package. - The information of input and output buffer should be clearly specified according to the customized Verilog/SPICE netlist! The existence of input/output buffers will influence the decision in creating testbenches, which may leads to larger errors in power analysis. + The information of input and output buffer should be clearly specified according to the customized SPICE netlist! The existence of input/output buffers will influence the decision in creating SPICE testbenches, which may leads to larger errors in power analysis. - FPGA-Verilog/SPICE currently support only one clock domain in the FPGA. Therefore there should be only one clock port to be defined and the size of the clock port should be 1. +.. note:: FPGA-Verilog/SPICE currently support only one clock domain in the FPGA. Therefore there should be only one clock port to be defined and the size of the clock port should be 1. -Instructions of defining design parameters: +.. option:: -* **circuit_model type:** can be ``ff`` or ``scff``. FF is typical Flip-Flop, SCFF is Scan-Chain Flip-Flop + - ``type="ccff|ff"`` Specify the type of a flip-flop. ``ff`` is a regular flip-flop while ``ccff`` denotes a configuration-chain flip-flop -* **port:** three types of ports (``input``, ``output`` and ``clock``) should be defined. If the user provides a customized Verilog/SPICE netlist, the bandwidth of ports should be defined to the same as the Verilog/SPICE netlist. +.. note:: A flip-flop should have three types of ports, ``input``, ``output`` and ``clock``. -.. note:: In a valid FPGA architecture, users should provide at least either a SCFF or a SRAM, so that the configurations can loaded to core logic. +.. note:: If the user provides a customized Verilog/SPICE netlist, the bandwidth of ports should be defined to the same as the Verilog/SPICE netlist. -**FF example** +.. note:: In a valid FPGA architecture, users should provide at least either a ``ccff`` or ``sram`` circuit model, so that the configurations can loaded to core logic. -:numref:`fig_ff` illustrates an example of LUT modeling, which consists of input/output buffers and a transmission-gate-based tree structure. +Flip-Flop example +````````````````` + +:numref:`fig_ff` illustrates an example of regular flip-flop. .. _fig_ff: .. figure:: ./figures/FF.png - :scale: 100% + :scale: 50% :alt: FF symbol An example of classical Flip-Flop. @@ -470,27 +590,28 @@ The code describing this FF is: .. code-block:: xml - - - - - - + + + + + + -**This example shows:** - * Circuit model type as ``ff`` - * The verilog netlist file associated to this component ``ff.v`` - * 3 ports, ``Set``, ``Reset`` and ``clk``, defined as global +This example shows: + - A regular flip-flop which is defined in a Verilog netlist ``ff.v`` and a SPICE netlist ``ff.sp`` + - The flip-flop has ``set`` and ``reset`` functionalities + - The flip-flop port names defined differently in standard cell library and VPR architecture. The ``lib_name`` capture the port name defined in standard cells, while ``prefix`` capture the port name defined in ``pb_type`` of VPR architecture file -**SCFF example** +Configuration-chain Flip-flop Example +````````````````````````````````````` -:numref:`fig_scff` illustrates an example of LUT modeling, which consists of input/output buffers and a transmission-gate-based tree structure. +:numref:`fig_ccff` illustrates an example of scan-chain flop-flop used to build a configuration chain. -.. _fig_scff: +.. _fig_ccff: .. figure:: ./figures/scff.png - :scale: 100% + :scale: 50% :alt: SCFF symbol An example of a Scan-Chain Flip-Flop. @@ -499,85 +620,103 @@ The code describing this FF is: .. code-block:: xml - + - + -**This example shows:** - * Circuit model type as ``scff`` - * The verilog netlist file associated to this component ``scff.v`` - * 1 port, ``clk``, defined as global +This example shows: + - A configuration-chain flip-flop which is defined in a Verilog netlist ``ccff.v`` and a SPICE netlist ``ccff.sp`` + - The flip-flop has a global clock port, ``CK``, which will be wired a global programming clock Hard Logics ~~~~~~~~~~~ +Template +```````` + .. code-block:: xml - + - - - - + + + + .. note:: Hard logics are defined for non-configurable resources in FPGA architectures, such as adders, multipliers and RAM blocks. Their circuit designs are highly dependent on the technology node and well optimized by engineers. - As more functional units are included in FPGA architecture, it is impossible to auto-generate these functional units [3]. - Therefore, FPGA-Verilog/SPICE requires users to provide their customized Verilog/SPICE netlists. A sample Verilog/SPICE netlist of a 1-bit adder can be found in the directory SpiceNetlists in the released package. + As more functional units are included in FPGA architecture, it is impossible to auto-generate these functional units. + Therefore, FPGA-Verilog/SPICE requires users to provide their customized Verilog/SPICE netlists. - The information of input and output buffer should be clearly specified according to the customized Verilog/SPICE netlist! The existence of input/output buffers will influence the decision in creating testbenches, which may leads to larger errors in power analysis. +.. note:: Examples can be found in hard_logic_example_link_ -Instructions of defining design parameters: +.. _hard_logic_example_link: https://github.com/LNIS-Projects/OpenFPGA/tree/master/openfpga_flow/VerilogNetlists -* **port:** two types of ports (``input`` and ``output``) should be defined. If the user provides a user-defined Verilog/SPICE netlist, the bandwidth of ports should be defined to the same as the Verilog/SPICE netlist. +.. note:: The information of input and output buffer should be clearly specified according to the customized Verilog/SPICE netlist! The existence of input/output buffers will influence the decision in creating SPICE testbenches, which may leads to larger errors in power analysis. + +1-bit Full Adder Example +```````````````````````` + +.. code-block:: xml + + + + + + + + + + + Routing Wire Segments ~~~~~~~~~~~~~~~~~~~~~ -FPGA-Verilog/SPICE provides two types of Verilog/SPICE models for the wire segments in FPGA architecture: +FPGA architecture requires two type of wire segments: - * One type is called ``wire``, which targets the local wires inside the logic blocks. The wire has one input and one output, directly connecting the output of a driver and the input of the downstream unit, respectively - * The other type is called ``chan_wire``, especially targeting the channel wires. The channel wires have one input and two outputs, one of which is connected to the inputs of Connection Boxes while the other is connected to the inputs of Switch Boxes. Two outputs are created because from the view of layout, the inputs of Connection Boxes are typically connected to the middle point of channel wires, which has less parasitic resistances and capacitances than connected to the ending point. + - ``wire``, which targets the local wires inside the logic blocks. The wire has one input and one output, directly connecting the output of a driver and the input of the downstream unit, respectively + - ``chan_wire``, especially targeting the channel wires. The channel wires have one input and two outputs, one of which is connected to the inputs of Connection Boxes while the other is connected to the inputs of Switch Boxes. Two outputs are created because from the view of layout, the inputs of Connection Boxes are typically connected to the middle point of channel wires, which has less parasitic resistances and capacitances than connected to the ending point. + +Template +```````` .. code-block:: xml - + - - - - - + + + + + .. note:: FPGA-Verilog/SPICE can auto-generate the Verilog/SPICE model for wires while also allows users to provide their customized Verilog/SPICE netlists. - The information of input and output buffer should be clearly specified according to the customized netlist! The existence of input/output buffers will influence the decision in creating testbenches, which may leads to larger errors in power analysis. +.. note:: The information of input and output buffer should be clearly specified according to the customized netlist! The existence of input/output buffers will influence the decision in creating testbenches, which may leads to larger errors in power analysis. -Instructions of defining design parameters: +.. option:: -* **type:** can be [``wire`` | ``chan_wire``]. The Verilog/SPICE model wire targets the local wire inside the logic block while the chan_wire targets the channel wires in global routing. + - ``model_type="pi|T"`` Specify the type of RC models for this wire segement. Currently, OpenFPGA supports the π-type and T-type multi-level RC models. + - ``R=""`` Specify the total resistance of the wire + - ``C=""`` Specify the total capacitance of the wire. + - ``num_level=""`` Specify the number of levels of the RC wire model. -* **port:** two types of ports (``input`` and ``output``) should be defined. If the user provides an customized Verilog/SPICE netlist, the bandwidth of ports should be defined to the same as the Verilog/SPICE netlist. +.. note:: wire parameters are essential for FPGA-SPICE to accurately model wire parasitics -* **wire_param:** - - * **model_type:** can be [``pi`` | ``T``], corresponding to the π-type and T-type RC wire models. - * **res_val:** specify the total resistance of the wire - * **cap_val:** specify the total capacitance of the wire. - * **level:** specify the number of levels of the RC wire model. - -**Chan-Wire example** +Routing Track Wire Example +`````````````````````````` :numref:`fig_wire` depicts the modeling for a length-2 channel wire. .. _fig_wire: .. figure:: ./figures/wire.png - :scale: 100% + :scale: 80% :alt: map to buried treasure An example of a length-2 channel wire modeling @@ -589,46 +728,45 @@ The code describing this wire is: - + -**This example shows** - * How to use the ``wire_param`` for a π-type RC wire model - * How to use this circuit_model to auto-generate the Verilog/SPICE netlist +This example shows + - A routing track wire has 1 input and output + - The routing wire will be modelled as a 1-level π-type RC wire model with a total resistance of 103.84Ohm and a total capacitance of 13.89fF I/O pads ~~~~~~~~ +Template +```````` + .. code-block:: xml - + - - - - - + + + + + .. note:: The circuit designs of I/O pads are highly dependent on the technology node and well optimized by engineers. Therefore, FPGA-Verilog/SPICE requires users to provide their customized Verilog/SPICE/Verilog netlists. A sample Verilog/SPICE netlist of an I/O pad can be found in the directory SpiceNetlists in the released package. - The information of input and output buffer should be clearly specified according to the customized netlist! The existence of input/output buffers will influence the decision in creating testbenches, which may leads to larger errors in power analysis. +.. note:: The information of input and output buffer should be clearly specified according to the customized netlist! The existence of input/output buffers will influence the decision in creating testbenches, which may leads to larger errors in power analysis. -Instructions of defining design parameters: +I/O Pad Example +``````````````` -* **port:** four types of ports (``input``, ``output``, ``inout`` and ``sram``) should be defined. If the user provides a user-defined Verilog/SPICE netlist, the bandwidth of ports should be defined to the same as the Verilog/SPICE netlist. - -**IO-pad example** - -:numref:`fig_iopad` depicts an IO-Pad. +:numref:`fig_iopad` depicts an I/O pad. .. _fig_iopad: .. figure:: ./figures/iopad.png - :scale: 100% + :scale: 50% :alt: IO-Pad symbol An example of an IO-Pad @@ -637,16 +775,19 @@ The code describing this IO-Pad is: .. code-block:: xml - + + + + + - - - - + + + -**This example shows** - - * The association of the verilog netlist file ``io.v`` - * The inout pad port_type, which means as inout as output. - * The instantiation of a SCFF as sram +This example shows + - A general purpose I/O cell defined in Verilog netlist ``io.sp`` and SPICE netlist ``io.sp`` + - The I/O cell has an ``inout`` port as the bi-directional port + - The directionality of I/O can be controlled by a configuration-chain flip-flop defined in circuit model ``ccff`` + - If unused, the I/O cell have be configured to '1' From 089cc5e86ecd71b7f872de7d2a3fc0fbf6db1e70 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 10 Mar 2020 16:51:50 -0600 Subject: [PATCH 290/645] update documentation on circuit model annotation on VPR architecture --- docs/source/arch_lang/annotate_vpr_arch.rst | 194 +++++++++----------- docs/source/arch_lang/circuit_library.rst | 18 +- 2 files changed, 102 insertions(+), 110 deletions(-) diff --git a/docs/source/arch_lang/annotate_vpr_arch.rst b/docs/source/arch_lang/annotate_vpr_arch.rst index 25b0e5d3e..205cfe84e 100644 --- a/docs/source/arch_lang/annotate_vpr_arch.rst +++ b/docs/source/arch_lang/annotate_vpr_arch.rst @@ -4,23 +4,16 @@ Bind circuit modules to VPR architecture ---------------------------------------- Each defined circuit model should be linked to an FPGA module defined in the original part of architecture descriptions. It helps FPGA-circuit creating the circuit netlists for logic/routing blocks. Since the original part lacks such support, we create a few XML properties to link to Circuit models. -SRAM -~~~~ - -To link the defined circuit model of SRAM into the FPGA architecture description, a new line in XML format should be added under the XML node device. The new XML node is named as sram, which defines the area of an SRAM and the name of the circuit model to be linked. An example is shown as follows: +Configuration Protocol +~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: xml - + + + - - - - - -* **area:** is expressed in terms of the number of minimum width transistors. The SRAM area defined in this line is used in the area estimation of global routing multiplexers. circuit_model_name should match the name of the circuit model that has been defined under XML node module_circuit_model. The type of the linked circuit model should be sram. - -* **organization:** [scan-chain|memory_bank|standalone], is the type of configuration circuits. + - ``type="scan_chain|memory_bank|standalone"`` Specify the type of configuration circuits. :numref:`fig_sram` illustrates an example where a memory organization using memory decoders and 6-transistor SRAMs. @@ -32,19 +25,12 @@ To link the defined circuit model of SRAM into the FPGA architecture description Example of a memory organization using memory decoders -.. note:: Currently circuit only supports standalone memory organization. +.. note:: Currently FPGA-SPICE only supports standalone memory organization. .. note:: Currently RRAM-based FPGA only supports memory-bank organization for Verilog Generator. -Here is an example. - -.. code-block:: xml - - - - -Switch Boxes -~~~~~~~~~~~~ +Switch Blocks +~~~~~~~~~~~~~ Original VPR architecture description contains an XML node called switchlist under which all the multiplexers of switch blocks are described. To link a defined circuit model to a multiplexer in the switch blocks, a new XML property circuit_model_name should be added to the descriptions. @@ -53,28 +39,27 @@ Here is an example: .. code-block:: xml - - - + + + -* **circuit_model_name:** should match a circuit model whose type is mux defined under module_circuit_models. + - ``circuit_model_name=""`` should match a circuit model whose type is ``mux`` defined in :ref:`circuit_library`. Connection Blocks ~~~~~~~~~~~~~~~~~ -To link the defined circuit model of the multiplexer to the Connection Blocks, a circuit_model_name should be added to the definition of Connection Blocks switches. However, the original architecture descriptions do not offer a switch description for connection boxes as they do for the switch blocks. -Therefore, FPGA-circuit requires a new XML node called **cblock** under the root XML node architecture, where a switch for connection blocks can be defined. +To link the defined circuit model of the multiplexer to the Connection Blocks, a ``circuit_model_name`` should be annotated to the definition of Connection Blocks switches. Here is the example: .. code-block:: xml - - - + + + -* **circuit_model_name:** should match a circuit model whose type is mux defined under module_circuit_models. + - ``circuit_model_name=""`` should match a circuit model whose type is ``mux`` defined in :ref:`circuit_library`. Channel Wire Segments ~~~~~~~~~~~~~~~~~~~~~ @@ -84,10 +69,10 @@ Similar to the Switch Boxes and Connection Blocks, the channel wire segments in .. code-block:: xml - + -* circuit_model_name: should match a circuit model whose type is chan_wire defined under module_circuit_models. + - ``circuit_model_name=""`` should match a circuit model whose type is ``chan_wire`` defined in :ref:`circuit_library`. Primitive Blocks inside Multi-mode Configurable Logic Blocks ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -97,85 +82,86 @@ Each primitive block, i.e., the leaf ``pb_types``, should be linked to a valid c The ``circuit_model_name`` should match the given name of a ``circuit_model`` defined by users. .. code-block:: xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +.. option:: -* **physical_mode_name:** tell the name of the mode that describes the physical implementation of the configurable block. This is critical in modeling actual circuit designs and architecture of an FPGA. Typically, only one physical_mode should be specified for each multi-mode ``pb_type``. + Specify a physical mode for multi-mode ``pb_type`` defined in VPR architecture. -* **idle_mode_name:** tell the name of the mode that the ``pb_type`` is configured to be by default. This is critical in building circuit netlists for unused logic blocks. + .. note:: This should be applied to non-primitive ``pb_type``, i.e., ``pb_type`` have child ``pb_type``. -* **circuit_model_name:** should match a circuit model defined under ``module_circuit_models``. The ``circuit_model_name`` is mandatory for every leaf ``pb_type`` in a physical_mode ``pb_type``. For the interconnection type direct, the type of the linked circuit model should be wire. For multiplexers, the type of linked circuit model should be ``mux``. For complete, the type of the linked circuit model can be either ``mux`` or ``wire``, depending on the case. + - ``name=""`` specifiy the full name of a ``pb_type`` in the hierarchy of VPR architecture. -* **mode_bits** specifies the configuration bits for the ``circuit_model`` when operating at an operating mode. The length of ``mode_bits`` should match the ``port`` size defined in ``circuit_model``. The ``mode_bits`` should be derived from circuit designs while users are responsible for its correctness. FPGA-Bitstreamm will add the ``mode_bits`` during bitstream generation. + - ``physical_mode_name=""`` Specify the name of the mode that describes the physical implementation of the configurable block. This is critical in modeling actual circuit designs and architecture of an FPGA. Typically, only one ``physical_mode`` should be specified for each multi-mode ``pb_type``. -* **physical_pb_type_name** creates the link on ``pb_type`` between operating and physical modes. This syntax is mandatory for every leaf ``pb_type`` in an operating mode ``pb_type``. It should be a valid name of leaf ``pb_type`` in physical mode. +.. note:: OpenFPGA will infer the physical mode for a single-mode ``pb_type`` defined in VPR architecture -* **physical_pb_type_index_factor** aims to align the indices for ``pb_type`` between operating and physical modes, especially when an operating mode contains multiple ``pb_type`` (``num_pb``>1) that are linked to the same physical ``pb_type``. When ``physical_pb_type_name`` is larger than 1, the index of ``pb_type`` will be multipled by the given factor. +.. option:: -* **physical_pb_type_index_offset** aims to align the indices for ``pb_type`` between operating and physical modes, especially when an operating mode contains multiple ``pb_type`` (``num_pb``>1) that are linked to the same physical ``pb_type``. When ``physical_pb_type_name`` is larger than 1, the index of ``pb_type`` will be shifted by the given factor. + Specify the physical implementation for a primitive ``pb_type`` in VPR architecture -* **physical_mode_pin** creates the linke on ``port`` of ``pb_type`` between operating and physical modes. This syntax is mandatory for every leaf ``pb_type`` in an operating mode ``pb_type``. It should be a valid ``port`` name of leaf ``pb_type`` in physical mode and the port size should also match. + .. note:: This should be applied to primitive ``pb_type``, i.e., ``pb_type`` have no children. -* **physical_mode_pin_rotate_offset** aims to align the pin indices for ``port`` of ``pb_type`` between operating and physical modes, especially when an operating mode contains multiple ``pb_type`` (``num_pb``>1) that are linked to the same physical ``pb_type``. When ``physical_mode_pin_rotate_offset`` is larger than zero, the pin index of ``pb_type`` (whose index is large than 1) will be shifted by the given offset. + - ``name=""`` specifiy the full name of a ``pb_type`` in the hierarchy of VPR architecture. + + - ``physical_pb_type_name=`` creates the link on ``pb_type`` between operating and physical modes. This syntax is mandatory for every primitive ``pb_type`` in an operating mode ``pb_type``. It should be a valid name of primitive ``pb_type`` in physical mode. + + - ``circuit_model_name=""`` Specify a circuit model to implement a ``pb_type`` in VPR architecture. The ``circuit_model_name`` is mandatory for every primitive``pb_type`` in a physical_mode ``pb_type``. + + - ``mode_bits=""`` Specify the configuration bits for the ``circuit_model`` when operating at an operating mode. The length of ``mode_bits`` should match the ``port`` size defined in ``circuit_model``. The ``mode_bits`` should be derived from circuit designs while users are responsible for its correctness. FPGA-Bitstreamm will add the ``mode_bits`` during bitstream generation. + + - ``physical_pb_type_index_factor=""`` aims to align the indices for ``pb_type`` between operating and physical modes, especially when an operating mode contains multiple ``pb_type`` (``num_pb``>1) that are linked to the same physical ``pb_type``. When ``physical_pb_type_name`` is larger than 1, the index of ``pb_type`` will be multipled by the given factor. + + - ``physical_pb_type_index_offset=`` aims to align the indices for ``pb_type`` between operating and physical modes, especially when an operating mode contains multiple ``pb_type`` (``num_pb``>1) that are linked to the same physical ``pb_type``. When ``physical_pb_type_name`` is larger than 1, the index of ``pb_type`` will be shifted by the given factor. + +.. option:: + + - ``name=""`` specifiy the name of a ``interconnect`` in VPR architecture. Different from ``pb_type``, hierarchical name is not required here. + + - ``circuit_model_name=""`` For the interconnection type direct, the type of the linked circuit model should be wire. For multiplexers, the type of linked circuit model should be ``mux``. For complete, the type of the linked circuit model can be either ``mux`` or ``wire``, depending on the case. + +.. option:: + + Link a port of an operating ``pb_type`` to a port of a physical ``pb_type`` + + - ``name=""`` specifiy the name of a ``port`` in VPR architecture. Different from ``pb_type``, hierarchical name is not required here. + + - ``physical_mode_pin="" creates the link of ``port`` of ``pb_type`` between operating and physical modes. This syntax is mandatory for every primitive ``pb_type`` in an operating mode ``pb_type``. It should be a valid ``port`` name of leaf ``pb_type`` in physical mode and the port size should also match. + + - ``physical_mode_pin_rotate_offset=""`` aims to align the pin indices for ``port`` of ``pb_type`` between operating and physical modes, especially when an operating mode contains multiple ``pb_type`` (``num_pb``>1) that are linked to the same physical ``pb_type``. When ``physical_mode_pin_rotate_offset`` is larger than zero, the pin index of ``pb_type`` (whose index is large than 1) will be shifted by the given offset. .. note:: It is highly recommended that only one physical mode is defined for a multi-mode configurable block. Try not to use nested physical mode definition. This will ease the debugging and lead to clean XML description. diff --git a/docs/source/arch_lang/circuit_library.rst b/docs/source/arch_lang/circuit_library.rst index b28acafea..52d906fea 100644 --- a/docs/source/arch_lang/circuit_library.rst +++ b/docs/source/arch_lang/circuit_library.rst @@ -250,18 +250,30 @@ A circuit model may consist of a number of ports. The port list is mandatory in - ``type="input|output|sram|clock"`` Specify the type of the port, i.e., the directionality and usage. For programmable modules, such as multiplexers and LUTs, SRAM ports MUST be defined. For registers, such as FFs and memory banks, clock ports MUST be defined. + .. note:: ``sram`` and ``clock`` ports are considered as inputs in terms of directionality + - ``prefix=""`` the name of the port to appear in the autogenerated netlists. Each port will be shown as ``[i]`` in Verilog/SPICE netlists. + .. note:: if the circuit model is binded to a ``pb_type`` in VPR architecture, ``prefix`` must match the port name defined in ``pb_type`` + - ``lib_name=""`` the name of the port defined in standard cells or customized cells. If not specified, this attribute will be the same as ``prefix``. + .. note:: if the circuit model comes from a standard cell library, using ``lib_name`` is recommended. This is because + - the port names defined in ``pb_type`` are very diffrerent from the standard cells + - the port sequence is very different + - ``size=""`` bandwidth of the port. MUST be larger than zero. - ``default_val=""`` Specify default logic value for a port, which is used as the initial logic value of this port in testbench generation. Can be either 0 or 1. We assume each pin of this port has the same default value. - ``circuit_model_name=""`` Specify the name of the circuit model which is connected to this port. + .. note:: ``circuit_model_name`` is only valid when the type of this port is ``sram``. + - ``mode_select="true|false"`` Specify if this port controls the mode switching in a configurable logic block. This is due to that a configurable logic block can operate in different modes, which is controlled by SRAM bits. + .. note:: ``mode_select`` is only valid when the type of this port is ``sram``. + - ``is_global="true|false"`` can be either ``true`` or ``false``. Specify if this port is a global port, which will be routed globally. Note that when multiple global ports are defined with the same name, these global ports will be short-wired together. - ``is_set="true|false"`` Specify if this port controls a set signal. All the set ports are connected to global set voltage stimuli in testbenches. @@ -270,12 +282,6 @@ A circuit model may consist of a number of ports. The port list is mandatory in - ``is_config_enable="true|false"`` Specify if this port controls a configuration-enable signal. Only valid when ``is_global`` is ``true``. This port is only enabled during FPGA configuration, and always disabled during FPGA operation. All the ``config_enable`` ports are connected to global configuration-enable voltage stimuli in testbenches. -.. note:: ``sram`` and ``clock`` ports are considered as inputs in terms of directionality - -.. note:: ``circuit_model_name`` is only valid when the type of this port is ``sram``. - -.. note:: ``mode_select`` is only valid when the type of this port is ``sram``. - .. note:: ``is_set``, ``is_reset`` and ``is_config_enable`` are only valid when ``is_global`` is ``true``. .. note:: Different types of ``circuit_model`` have different XML syntax, with which users can highly customize their circuit topologies. See refer to examples of :ref:``circuit_model_example`` for more details. From 0da6f00af5b115a96ba75d191d567e48ee4c6e8f Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 10 Mar 2020 17:29:35 -0600 Subject: [PATCH 291/645] start reworking the openfpga tool documentation --- docs/source/fpga_bitstream/command_line_usage.rst | 4 ++-- docs/source/fpga_bitstream/file_organization.rst | 2 +- docs/source/fpga_bitstream/index.rst | 2 ++ docs/source/fpga_spice/command_line_usage.rst | 4 ++-- docs/source/fpga_spice/customize_subckt.rst | 2 +- docs/source/fpga_spice/file_organization.rst | 2 +- docs/source/fpga_spice/index.rst | 3 +++ docs/source/fpga_spice/spice_simulation.rst | 2 +- docs/source/fpga_verilog/command_line_usage.rst | 4 ++-- docs/source/fpga_verilog/file_organization.rst | 2 +- docs/source/fpga_verilog/func_verify.rst | 2 +- docs/source/fpga_verilog/index.rst | 2 ++ docs/source/fpga_verilog/sc_flow.rst | 2 +- 13 files changed, 20 insertions(+), 13 deletions(-) diff --git a/docs/source/fpga_bitstream/command_line_usage.rst b/docs/source/fpga_bitstream/command_line_usage.rst index 278bee494..43fffe97e 100644 --- a/docs/source/fpga_bitstream/command_line_usage.rst +++ b/docs/source/fpga_bitstream/command_line_usage.rst @@ -1,5 +1,5 @@ -Command-line Options for FPGA Bitstream Generator -================================================= +Command-line Options +~~~~~~~~~~~~~~~~~~~~ All the command line options of FPGA-Bitstream can be shown by calling the help menu of VPR. Here are all the FPGA-Verilog-related options that you can find: diff --git a/docs/source/fpga_bitstream/file_organization.rst b/docs/source/fpga_bitstream/file_organization.rst index 1cc152d40..c8476d1f9 100644 --- a/docs/source/fpga_bitstream/file_organization.rst +++ b/docs/source/fpga_bitstream/file_organization.rst @@ -1,5 +1,5 @@ Bistream Output File Format -============================ +~~~~~~~~~~~~~~~~~~~~~~~~~~~ FPGA-Bitstream can generate two types of bitstreams: * Generic bitstreams, where configuration bits are organized out-of-order in a database. We output the generic bitstream to a XML format, which is easy to debug. As shown in the following XML code, configuration bits are organized block by block, where each block could be a LUT, a routing multiplexer `etc`. Each ``bitstream_block`` includes two sets of information: diff --git a/docs/source/fpga_bitstream/index.rst b/docs/source/fpga_bitstream/index.rst index 6380cc322..9fb992ace 100644 --- a/docs/source/fpga_bitstream/index.rst +++ b/docs/source/fpga_bitstream/index.rst @@ -1,3 +1,5 @@ +FPGA-Bitstream +-------------- .. _fpga_bitstream: FPGA-Bitstream diff --git a/docs/source/fpga_spice/command_line_usage.rst b/docs/source/fpga_spice/command_line_usage.rst index 129c05e05..e7c261ab3 100644 --- a/docs/source/fpga_spice/command_line_usage.rst +++ b/docs/source/fpga_spice/command_line_usage.rst @@ -1,5 +1,5 @@ -Command-line Options for FPGA SPICE Generator -================================================= +Command-line Options +~~~~~~~~~~~~~~~~~~~~ All the command line options of FPGA-SPICE can be shown by calling the help menu of VPR. Here are all the FPGA-SPICE-related options that you can find: FPGA-SPICE Supported Options:: diff --git a/docs/source/fpga_spice/customize_subckt.rst b/docs/source/fpga_spice/customize_subckt.rst index fca30912f..e62d3578b 100644 --- a/docs/source/fpga_spice/customize_subckt.rst +++ b/docs/source/fpga_spice/customize_subckt.rst @@ -1,5 +1,5 @@ Create Customized SPICE Modules -=============================== +------------------------------- To make sure the customized SPICE netlists can be correctly included in FPGA-SPICE, the following rules should be fully respected: 1. The customized SPICE netlists could contain multiple sub-circuits but the names of these sub-circuits should not be conflicted with any reserved words.. Here is an example of defining a sub-circuit in SPICE netlists. The should be a unique one, which should not be conflicted with any reserved words. diff --git a/docs/source/fpga_spice/file_organization.rst b/docs/source/fpga_spice/file_organization.rst index 4a099bc95..f9a2f8575 100644 --- a/docs/source/fpga_spice/file_organization.rst +++ b/docs/source/fpga_spice/file_organization.rst @@ -1,5 +1,5 @@ Hierarchy of SPICE Output Files -=============================== +------------------------------- All the generated SPICE netlists are located in the as you specify in the command-line options. Under the , FPGA-SPICE creates a number of folders: include, subckt, lut_tb, dff_tb, grid_tb, pb_mux_tb, cb_mux_tb, sb_mux_tb, top_tb, results. Under the , FPGA-SPICE also creates a shell script called run_hspice_sim.sh, which run all the simulations for all the testbenches. diff --git a/docs/source/fpga_spice/index.rst b/docs/source/fpga_spice/index.rst index c9c04568a..f09b1bea0 100644 --- a/docs/source/fpga_spice/index.rst +++ b/docs/source/fpga_spice/index.rst @@ -1,7 +1,10 @@ +FPGA-SPICE +---------- .. _fpga_spice: FPGA-SPICE .. toctree:: + :maxdepth: 2 command_line_usage diff --git a/docs/source/fpga_spice/spice_simulation.rst b/docs/source/fpga_spice/spice_simulation.rst index f0aa7e2d0..29173d0b8 100644 --- a/docs/source/fpga_spice/spice_simulation.rst +++ b/docs/source/fpga_spice/spice_simulation.rst @@ -1,5 +1,5 @@ Run SPICE simulation -==================== +-------------------- * Simulation results diff --git a/docs/source/fpga_verilog/command_line_usage.rst b/docs/source/fpga_verilog/command_line_usage.rst index 656b2ecef..2e963319d 100644 --- a/docs/source/fpga_verilog/command_line_usage.rst +++ b/docs/source/fpga_verilog/command_line_usage.rst @@ -1,5 +1,5 @@ -Command-line Options for FPGA-Verilog Generator -================================================= +Command-line Options +~~~~~~~~~~~~~~~~~~~~ All the command line options of FPGA-Verilog can be shown by calling the help menu of VPR. Here are all the FPGA-Verilog-related options that you can find: diff --git a/docs/source/fpga_verilog/file_organization.rst b/docs/source/fpga_verilog/file_organization.rst index 0c223a822..181be2295 100644 --- a/docs/source/fpga_verilog/file_organization.rst +++ b/docs/source/fpga_verilog/file_organization.rst @@ -1,5 +1,5 @@ Hierarchy of Verilog Output Files -================================= +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ All the generated Verilog Netlists are located in the /SRC as you specify in the command-line options. Under the /SRC, FPGA-Verilog creates the top file name_top.v and some folders: lb (logic blocks), routing and sub_modules. diff --git a/docs/source/fpga_verilog/func_verify.rst b/docs/source/fpga_verilog/func_verify.rst index cbb8af96c..a21a01c1e 100644 --- a/docs/source/fpga_verilog/func_verify.rst +++ b/docs/source/fpga_verilog/func_verify.rst @@ -1,5 +1,5 @@ Perform Functionality Verification -================================== +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If the --fpga_verilog_print_modelsim_autodeck option is selected, it is possible to directly generate scripts for Modelsim. Inside of the Verilog directory specified with --fpga_verilog_dir can be found name_runsim.tcl scripts which perform the functional verification onto the FPGA generated. diff --git a/docs/source/fpga_verilog/index.rst b/docs/source/fpga_verilog/index.rst index 2ea55c478..db8740fa0 100644 --- a/docs/source/fpga_verilog/index.rst +++ b/docs/source/fpga_verilog/index.rst @@ -1,3 +1,5 @@ +FPGA-Verilog +------------ .. _fpga_verilog: FPGA-Verilog diff --git a/docs/source/fpga_verilog/sc_flow.rst b/docs/source/fpga_verilog/sc_flow.rst index 03a9069fe..f89d71db2 100644 --- a/docs/source/fpga_verilog/sc_flow.rst +++ b/docs/source/fpga_verilog/sc_flow.rst @@ -1,5 +1,5 @@ From Verilog to Layout -====================== +~~~~~~~~~~~~~~~~~~~~~~ The generated Verilog code can be used through a semi-custom design flow to generate the layout. From 9f743f7f4ed3d42fae3b1c7128b9a6415280d5bb Mon Sep 17 00:00:00 2001 From: Xifan Tang Date: Tue, 10 Mar 2020 20:54:42 -0600 Subject: [PATCH 292/645] add openfpga shell documentation --- docs/source/index.rst | 2 + docs/source/openfpga_shell/index.rst | 14 ++ .../openfpga_shell/launch_openfpga_shell.rst | 21 ++ .../openfpga_shell/openfpga_commands.rst | 184 ++++++++++++++++++ .../source/openfpga_shell/openfpga_script.rst | 72 +++++++ 5 files changed, 293 insertions(+) create mode 100644 docs/source/openfpga_shell/index.rst create mode 100644 docs/source/openfpga_shell/launch_openfpga_shell.rst create mode 100644 docs/source/openfpga_shell/openfpga_commands.rst create mode 100644 docs/source/openfpga_shell/openfpga_script.rst diff --git a/docs/source/index.rst b/docs/source/index.rst index 34beae468..d3452b771 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -27,6 +27,8 @@ Welcome to OpenFPGA's documentation! :maxdepth: 2 :caption: OpenFPGA Tools + openfpga_shell/index + fpga_spice/index fpga_verilog/index diff --git a/docs/source/openfpga_shell/index.rst b/docs/source/openfpga_shell/index.rst new file mode 100644 index 000000000..0aa515e23 --- /dev/null +++ b/docs/source/openfpga_shell/index.rst @@ -0,0 +1,14 @@ +OpenFPGA Interface +------------------ + +.. _openfpga_shell: + OpenFPGA Shell + +.. toctree:: + :maxdepth: 2 + + launch_openfpga_shell + + openfpga_script + + openfpga_commands diff --git a/docs/source/openfpga_shell/launch_openfpga_shell.rst b/docs/source/openfpga_shell/launch_openfpga_shell.rst new file mode 100644 index 000000000..3ef6e9fb5 --- /dev/null +++ b/docs/source/openfpga_shell/launch_openfpga_shell.rst @@ -0,0 +1,21 @@ +.. _launch_openfpga_shell: + +Launch OpenFPGA Shell +--------------------- + +OpenFPGA employs a shell-like user interface, in order to integrate all the tools in a well-modularized way. +Currently, OpenFPGA shell is an unified platform to call ``vpr``, ``FPGA-Verilog``, ``FPGA-Bitstream``, ``FPGA-SDC`` and ``FPGA-SPICE``. +To launch OpenFPGA shell, users can choose two modes. + +.. option:: --interactive or -i + + Launch OpenFPGA in interactive mode where users type-in command by command and get runtime results + +.. option:: --file or -f + + Launch OpenFPGA in script mode where users write commands in scripts and FPGA will execute them + +.. option:: --help or -h + + Show the help desk + diff --git a/docs/source/openfpga_shell/openfpga_commands.rst b/docs/source/openfpga_shell/openfpga_commands.rst new file mode 100644 index 000000000..79c95d248 --- /dev/null +++ b/docs/source/openfpga_shell/openfpga_commands.rst @@ -0,0 +1,184 @@ +.. _openfpga_commands: + +Commands +-------- + +As OpenFPGA integrates various tools, the commands are categorized into different classes: + +Basic Commands +~~~~~~~~~~~~~~ + +.. option:: help + + Show help desk to list all the available commands + +.. option:: exit + + Exit OpenFPGA shell + +VPR +~~~ + +.. option:: vpr + + OpenFPGA allows users to call ``vpr`` in the standard way as documented in vtr project. + +Setup OpenFPGA +~~~~~~~~~~~~~~ + +.. option:: read_openfpga_arch + + Read the XML architecture file required by OpenFPGA + + - ``--file`` or ``-f`` Specify the file name + + - ``--verbose`` Show verbose log + +.. option:: write_openfpga_arch + + Write the OpenFPGA XML architecture file to a file + + - ``--file`` or ``-f`` Specify the file name + + - ``--verbose`` Show verbose log + +.. option:: link_openfpga_arch + + Annotate the OpenFPGA architecture to VPR data base + + - ``--activity_file`` Specify the signal activity file + + - ``--sort_gsb_chan_node_in_edges`` Sort the edges for the routing tracks in General Switch Blocks (GSBs). Strongly recommand to turn this on for uniquifying the routing modules + + - ``--verbose`` Show verbose log + +.. option:: check_netlist_naming_conflict + + Check and correct any naming conflicts in the BLIF netlist + This is strongly recommended. Otherwise, the outputted Verilog netlists may not be compiled successfully. + + .. note:: This command may be deprecated in future when merged to VPR upstream + + - ``--fix`` Apply fix-up to the names that violate the syntax + + - ``--report <.xml>`` Report the naming fix-up to a log file + +.. option:: pb_pin_fixup + + Apply fix-up to clustering nets based on routing results + This is strongly recommended. Otherwise, the bitstream generation may be wrong + + .. note:: This command may be deprecated in future when merged to VPR upstream + + - ``--verbose`` Show verbose log + +.. option:: lut_truth_table_fixup + + Apply fix-up to Look-Up Table truth tables based on packing results + + .. note:: This command may be deprecated in future when merged to VPR upstream + + - ``--verbose`` Show verbose log + +.. option:: build_fabric + + Build the module graph. This is a must-run command before launching FPGA-Verilog, FPGA-Bitstream, FPGA-SDC and FPGA-SPICE + + - ``--compress_routing`` Enable compression on routing architecture modules. Strongly recommend this as it will minimize the number of routing modules to be outputted. It can reduce the netlist size significantly. + + - ``--duplicate_grid_pin`` Enable pin duplication on grid modules. This is optional unless ultra-dense layout generation is needed + + - ``--verbose`` Show verbose log + +FPGA-Bitstream +~~~~~~~~~~~~~~ + +.. option:: repack + + Repack the netlist to physical pbs + This must be done before bitstream generator and testbench generation + Strongly recommend it is done after all the fix-up have been applied + + - ``--verbose`` Show verbose log + +.. option:: build_architecture_bitstream + + Decode VPR implementing results to an fabric-independent bitstream database + + - ``--file`` or ``-f`` Output the fabric-independent bitstream to an XML file + + - ``--verbose`` Show verbose log + +.. option:: build_fabric_bitstream + + Reorganize the bitstream database for a specific FPGA fabric + + - ``--verbose`` Show verbose log + +FPGA-Verilog +~~~~~~~~~~~~ + +.. option:: write_fabric_verilog + + Write the Verilog netlist for FPGA fabric based on module graph + + - ``--file`` or ``-f`` Specify the output directory for the Verilog netlists + + - ``--explict_port_mapping`` Use explict port mapping when writing the Verilog netlists + + - ``--include_timing`` Output timing information to Verilog netlists for primitive modules + + - ``--include_signal_init`` Output signal initialization to Verilog netlists for primitive modules + + - ``--support_icarus_simulator`` Output Verilog netlists with syntax that iVerilog simulatorcan accept + + - ``--print_user_defined_template`` Output a template Verilog netlist for all the user-defined ``circuit models`` in :ref:`circuit_library`. This aims to help engineers to check what is the port sequence required by top-level Verilog netlists + + - ``--verbose`` Show verbose log + +.. option:: write_verilog_testbench + + Write the Verilog testbench for FPGA fabric + + - ``--file`` or ``-f`` The output directory for all the testbench netlists. We suggest the use of same output directory as fabric Verilog netlists + + - ``--reference_benchmark_file_path`` Must specify the reference benchmark Verilog file if you want to output any testbenches + + - ``--print_top_testbench`` Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA + + - ``--print_formal_verification_top_netlist`` Generate a top-level module which can be used in formal verification + + - ``--print_preconfig_top_testbench`` Enable pre-configured top-level testbench which is a fast verification skipping programming phase + + - ``--print_simulation_ini`` Output an exchangeable simulation ini file, which is needed only when you need to interface different HDL simulators using openfpga flow-run scripts + +FPGA-SDC +~~~~~~~~ + +.. option:: write_pnr_sdc + + Write the SDC files for PnR backend + + - ``--file`` or ``-f`` Specify the output directory for SDC files + + - ``--constrain_global_port`` Constrain all the global ports of FPGA fabric + + - ``--constrain_grid`` Constrain all the grids of FPGA fabric + + - `--constrain_sb`` Constrain all the switch blocks of FPGA fabric + + - ``--constrain_cb`` Constrain all the connection blocks of FPGA fabric + + - ``--constrain_configurable_memory_outputs`` Constrain all the outputs of configurable memories of FPGA fabric + + - ``--constrain_routing_multiplexer_outputs`` Constrain all the outputs of routing multiplexer of FPGA fabric + + - ``--constrain_switch_block_outputs`` Constrain all the outputs of switch blocks of FPGA fabric + + - ``--verbose`` Enable verbose output + +.. option:: write_analysis_sdc + + Write the SDC to run timing analysis for a mapped FPGA fabric + + - ``--file`` or ``-f`` Specify the output directory for SDC files diff --git a/docs/source/openfpga_shell/openfpga_script.rst b/docs/source/openfpga_shell/openfpga_script.rst new file mode 100644 index 000000000..dde33a044 --- /dev/null +++ b/docs/source/openfpga_shell/openfpga_script.rst @@ -0,0 +1,72 @@ +.. _openfpga_script_format: + +OpenFPGA Script Format +---------------------- + +OpenFPGA accepts a simplified tcl-like script format. +Commented lines are started with `#`. +Note that comments can be added inline or as a new line. + +The following is an example. + +.. code-block:: python + + # Run VPR for the s298 design + vpr ./test_vpr_arch/k6_frac_N10_40nm.xml ./test_blif/and.blif --clock_modeling route #--write_rr_graph example_rr_graph.xml + + # Read OpenFPGA architecture definition + read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml + + # Write out the architecture XML as a proof + #write_openfpga_arch -f ./arch_echo.xml + + # Annotate the OpenFPGA architecture to VPR data base + link_openfpga_arch --activity_file ./test_blif/and.act --sort_gsb_chan_node_in_edges #--verbose + + # Check and correct any naming conflicts in the BLIF netlist + check_netlist_naming_conflict --fix --report ./netlist_renaming.xml + + # Apply fix-up to clustering nets based on routing results + pb_pin_fixup --verbose + + # Apply fix-up to Look-Up Table truth tables based on packing results + lut_truth_table_fixup #--verbose + + # Build the module graph + # - Enabled compression on routing architecture modules + # - Enable pin duplication on grid modules + build_fabric --compress_routing --duplicate_grid_pin #--verbose + + # Repack the netlist to physical pbs + # This must be done before bitstream generator and testbench generation + # Strongly recommend it is done after all the fix-up have been applied + repack #--verbose + + # Build the bitstream + # - Output the fabric-independent bitstream to a file + build_architecture_bitstream --verbose --file /var/tmp/xtang/openfpga_test_src/fabric_indepenent_bitstream.xml + + # Build fabric-dependent bitstream + build_fabric_bitstream --verbose + + # Write the Verilog netlist for FPGA fabric + # - Enable the use of explicit port mapping in Verilog netlist + write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src/SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose + + # Write the Verilog testbench for FPGA fabric + # - We suggest the use of same output directory as fabric Verilog netlists + # - Must specify the reference benchmark file if you want to output any testbenches + # - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA + # - Enable pre-configured top-level testbench which is a fast verification skipping programming phase + # - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts + write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src/SRC --reference_benchmark_file_path /var/tmp/xtang/and.v --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini /var/tmp/xtang/openfpga_test_src/simulation_deck.ini + + # Write the SDC files for PnR backend + # - Turn on every options here + write_pnr_sdc --file /var/tmp/xtang/openfpga_test_src/SDC + + # Write the SDC to run timing analysis for a mapped FPGA fabric + write_analysis_sdc --file /var/tmp/xtang/openfpga_test_src/SDC_analysis + + # Finish and exit OpenFPGA + exit From b941ac8a4abd8ba917ebecf4a9e0ea1eeca27b0a Mon Sep 17 00:00:00 2001 From: Xifan Tang Date: Tue, 10 Mar 2020 20:58:00 -0600 Subject: [PATCH 293/645] remove deprecated options --- .../fpga_bitstream/command_line_usage.rst | 14 ----- docs/source/fpga_bitstream/index.rst | 2 - .../fpga_verilog/command_line_usage.rst | 55 ------------------- docs/source/fpga_verilog/index.rst | 2 - 4 files changed, 73 deletions(-) delete mode 100644 docs/source/fpga_bitstream/command_line_usage.rst delete mode 100644 docs/source/fpga_verilog/command_line_usage.rst diff --git a/docs/source/fpga_bitstream/command_line_usage.rst b/docs/source/fpga_bitstream/command_line_usage.rst deleted file mode 100644 index 43fffe97e..000000000 --- a/docs/source/fpga_bitstream/command_line_usage.rst +++ /dev/null @@ -1,14 +0,0 @@ -Command-line Options -~~~~~~~~~~~~~~~~~~~~ - -All the command line options of FPGA-Bitstream can be shown by calling the help menu of VPR. Here are all the FPGA-Verilog-related options that you can find: - -FPGA-Verilog Supported Option:: - - --fpga_bitstream_generator - -.. csv-table:: Commmand-line Option of FPGA-Bitstream - :header: "Command Options", "Description" - :widths: 15, 30 - - "--fpga_bitstream_generator", "Turn on the FPGA-Bitstream and output a .bitstream file containing FPGA configuration." diff --git a/docs/source/fpga_bitstream/index.rst b/docs/source/fpga_bitstream/index.rst index 9fb992ace..6e1123899 100644 --- a/docs/source/fpga_bitstream/index.rst +++ b/docs/source/fpga_bitstream/index.rst @@ -6,8 +6,6 @@ FPGA-Bitstream .. toctree:: :maxdepth: 2 - command_line_usage - file_organization diff --git a/docs/source/fpga_verilog/command_line_usage.rst b/docs/source/fpga_verilog/command_line_usage.rst deleted file mode 100644 index 2e963319d..000000000 --- a/docs/source/fpga_verilog/command_line_usage.rst +++ /dev/null @@ -1,55 +0,0 @@ -Command-line Options -~~~~~~~~~~~~~~~~~~~~ - -All the command line options of FPGA-Verilog can be shown by calling the help menu of VPR. Here are all the FPGA-Verilog-related options that you can find: - -FPGA-Verilog Supported Options:: - - --fpga_verilog - --fpga_verilog_dir - --fpga_verilog_include_timing - --fpga_verilog_include_signal_init - --fpga_verilog_print_modelsim_autodeck - --fpga_verilog_print_top_testbench - --fpga_verilog_print_autocheck_top_testbench - --fpga_verilog_print_formal_verification_top_netlist - --fpga_verilog_include_icarus_simulator - - -.. csv-table:: Commmand-line Options of FPGA-Verilog - :header: "Command Options", "Description" - :widths: 15, 30 - - "--fpga_verilog", "Turn on the FPGA-Verilog." - "--fpga_verilog_dir ", "Specify the directory that all the Verilog files will be outputted to is the destination directory." - "--fpga_verilog_include_timing", "Includes the timings found in the XML file." - "--fpga_verilog_init_sim", "Initializes the simulation for ModelSim." - "--fpga_verilog_print_modelsim_autodeck", "Generates the scripts necessary to the ModelSim simulation." - "--fpga_verilog_modelsim_ini_path ", "Gives the path for the .ini necessary to ModelSim." - "--fpga_verilog_print_top_testbench", "Print the full-chip-level testbench for the FPGA. Determines the type of autodeck." - "--fpga_verilog_print_top_auto_testbench \ - ", "Prints the testbench associated with the given benchmark. Determines the type of autodeck." - "--fpga_verilog_dir ", "Specify the directory where all the Verilog files will be outputted to. is the destination directory." - "--fpga_verilog_include_timing", "Includes the timings found in the XML architecture description file." - "--fpga_verilog_include_signal_init", "Set all nets to random value to be close of a real power-on case" - "--fpga_verilog_print_modelsim_autodeck ", "Generates the scripts necessary to the ModelSim simulation and specify the path to modelsim.ini file." - "--fpga_verilog_print_top_testbench", "Prints the full-chip-level testbench for the FPGA, which includes programming phase and operationg phase (random patterns)." - "--fpga_verilog_print_autocheck_top_testbench \ - ", "Prints a testbench stimulating the generated FPGA and the initial benchmark to compare stimuli responses, which includes programming phase and operationg phase (random patterns)" - "--fpga_verilog_print_formal_verification_top_netlist", "Prints a Verilog top file compliant with formal verification tools. With this top file the FPGA is initialy programmed. It also prints a testbench with random patterns, which can be manually or automatically check regarding previous options." - "--fpga_verilog_include_icarus_simulator", "Activates waveforms .vcd file generation and simulation timeout, which are required for Icarus Verilog simulator" - "--fpga_verilog_print_input_blif_testbench", "Generates a Verilog test-bench to use with input blif file" - "--fpga_verilog_print_report_timing_tcl", "Generates tcl commands to run STA analysis with TO COMPLETE TOOL" - "--fpga_verilog_report_timing_rpt_path ", "Specifies path where report timing are written" - "--fpga_verilog_print_sdc_pnr", "Generates SDC constraints to PNR" - "--fpga_verilog_print_sdc_analysis", "Generates SDC to run timing analysis in PNR tool" - "--fpga_verilog_print_user_defined_template", "Generates a template of hierarchy modules and their port mapping" - -.. note:: The selected directory will contain the *Verilog top file* and three other folders. The folders are: - - * **sub_module:** contains each module verilog file and is more detailed in the next part *Verilog Output File Format*. - * **routing:** contains the Verilog for the connection blocks and the switch boxes. - * **lb:** contains the grids Verilog files. - - - diff --git a/docs/source/fpga_verilog/index.rst b/docs/source/fpga_verilog/index.rst index db8740fa0..e9f93cdf5 100644 --- a/docs/source/fpga_verilog/index.rst +++ b/docs/source/fpga_verilog/index.rst @@ -6,8 +6,6 @@ FPGA-Verilog .. toctree:: :maxdepth: 2 - command_line_usage - file_organization func_verify From 1d766d2a70cf4b375dea15a618365da4a0b4d861 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 11 Mar 2020 10:22:30 -0600 Subject: [PATCH 294/645] minor format fix on documentation --- docs/source/arch_lang/annotate_vpr_arch.rst | 8 ++++---- docs/source/fpga_spice/index.rst | 3 +++ docs/source/fpga_verilog/index.rst | 1 + docs/source/openfpga_shell/openfpga_commands.rst | 11 +++++++---- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/docs/source/arch_lang/annotate_vpr_arch.rst b/docs/source/arch_lang/annotate_vpr_arch.rst index 205cfe84e..ce570084d 100644 --- a/docs/source/arch_lang/annotate_vpr_arch.rst +++ b/docs/source/arch_lang/annotate_vpr_arch.rst @@ -13,7 +13,7 @@ Configuration Protocol - - ``type="scan_chain|memory_bank|standalone"`` Specify the type of configuration circuits. +- ``type="scan_chain|memory_bank|standalone"`` Specify the type of configuration circuits. :numref:`fig_sram` illustrates an example where a memory organization using memory decoders and 6-transistor SRAMs. @@ -43,7 +43,7 @@ Here is an example: - - ``circuit_model_name=""`` should match a circuit model whose type is ``mux`` defined in :ref:`circuit_library`. +- ``circuit_model_name=""`` should match a circuit model whose type is ``mux`` defined in :ref:`circuit_library`. Connection Blocks @@ -59,7 +59,7 @@ Here is the example: - - ``circuit_model_name=""`` should match a circuit model whose type is ``mux`` defined in :ref:`circuit_library`. +- ``circuit_model_name=""`` should match a circuit model whose type is ``mux`` defined in :ref:`circuit_library`. Channel Wire Segments ~~~~~~~~~~~~~~~~~~~~~ @@ -72,7 +72,7 @@ Similar to the Switch Boxes and Connection Blocks, the channel wire segments in - - ``circuit_model_name=""`` should match a circuit model whose type is ``chan_wire`` defined in :ref:`circuit_library`. +- ``circuit_model_name=""`` should match a circuit model whose type is ``chan_wire`` defined in :ref:`circuit_library`. Primitive Blocks inside Multi-mode Configurable Logic Blocks ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/source/fpga_spice/index.rst b/docs/source/fpga_spice/index.rst index f09b1bea0..4cdffcc0f 100644 --- a/docs/source/fpga_spice/index.rst +++ b/docs/source/fpga_spice/index.rst @@ -1,5 +1,8 @@ FPGA-SPICE ---------- + +.. warning:: FPGA-SPICE has not been integrated to VPR8 version yet. Please the following tool guide is for VPR7 version now + .. _fpga_spice: FPGA-SPICE diff --git a/docs/source/fpga_verilog/index.rst b/docs/source/fpga_verilog/index.rst index e9f93cdf5..85d278ae6 100644 --- a/docs/source/fpga_verilog/index.rst +++ b/docs/source/fpga_verilog/index.rst @@ -1,5 +1,6 @@ FPGA-Verilog ------------ + .. _fpga_verilog: FPGA-Verilog diff --git a/docs/source/openfpga_shell/openfpga_commands.rst b/docs/source/openfpga_shell/openfpga_commands.rst index 79c95d248..edb2bf0ad 100644 --- a/docs/source/openfpga_shell/openfpga_commands.rst +++ b/docs/source/openfpga_shell/openfpga_commands.rst @@ -57,7 +57,7 @@ Setup OpenFPGA Check and correct any naming conflicts in the BLIF netlist This is strongly recommended. Otherwise, the outputted Verilog netlists may not be compiled successfully. - .. note:: This command may be deprecated in future when merged to VPR upstream + .. warning:: This command may be deprecated in future when it is merged to VPR upstream - ``--fix`` Apply fix-up to the names that violate the syntax @@ -68,7 +68,7 @@ Setup OpenFPGA Apply fix-up to clustering nets based on routing results This is strongly recommended. Otherwise, the bitstream generation may be wrong - .. note:: This command may be deprecated in future when merged to VPR upstream + .. warning:: This command may be deprecated in future when it is merged to VPR upstream - ``--verbose`` Show verbose log @@ -76,19 +76,22 @@ Setup OpenFPGA Apply fix-up to Look-Up Table truth tables based on packing results - .. note:: This command may be deprecated in future when merged to VPR upstream + .. warning:: This command may be deprecated in future when it is merged to VPR upstream - ``--verbose`` Show verbose log .. option:: build_fabric - Build the module graph. This is a must-run command before launching FPGA-Verilog, FPGA-Bitstream, FPGA-SDC and FPGA-SPICE + Build the module graph. - ``--compress_routing`` Enable compression on routing architecture modules. Strongly recommend this as it will minimize the number of routing modules to be outputted. It can reduce the netlist size significantly. - ``--duplicate_grid_pin`` Enable pin duplication on grid modules. This is optional unless ultra-dense layout generation is needed - ``--verbose`` Show verbose log + + .. note:: This is a must-run command before launching FPGA-Verilog, FPGA-Bitstream, FPGA-SDC and FPGA-SPICE + FPGA-Bitstream ~~~~~~~~~~~~~~ From 2a260a05aaafdf6eaf8071c34ebb81c6119dcc31 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 11 Mar 2020 10:40:59 -0600 Subject: [PATCH 295/645] add a microbenchmark `and_latch` to test LUTs in wired mode --- openfpga/test_blif/and_latch.act | 5 ++ openfpga/test_blif/and_latch.blif | 11 ++++ openfpga/test_blif/and_latch.v | 23 ++++++++ openfpga/test_script/and_k6_frac.openfpga | 2 +- .../test_script/and_latch_k6_frac.openfpga | 59 +++++++++++++++++++ 5 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 openfpga/test_blif/and_latch.act create mode 100644 openfpga/test_blif/and_latch.blif create mode 100644 openfpga/test_blif/and_latch.v create mode 100644 openfpga/test_script/and_latch_k6_frac.openfpga diff --git a/openfpga/test_blif/and_latch.act b/openfpga/test_blif/and_latch.act new file mode 100644 index 000000000..8089a8969 --- /dev/null +++ b/openfpga/test_blif/and_latch.act @@ -0,0 +1,5 @@ +a 0.492800 0.201000 +b 0.502000 0.197200 +clk 0.500000 2.000000 +d 0.240200 0.171200 +c 0.240200 0.044100 diff --git a/openfpga/test_blif/and_latch.blif b/openfpga/test_blif/and_latch.blif new file mode 100644 index 000000000..a1925fffa --- /dev/null +++ b/openfpga/test_blif/and_latch.blif @@ -0,0 +1,11 @@ +# Benchmark "top" written by ABC on Wed Mar 11 10:36:28 2020 +.model top +.inputs a b clk +.outputs c d + +.latch c d re clk 0 + +.names a b c +11 1 + +.end diff --git a/openfpga/test_blif/and_latch.v b/openfpga/test_blif/and_latch.v new file mode 100644 index 000000000..893cdf7a4 --- /dev/null +++ b/openfpga/test_blif/and_latch.v @@ -0,0 +1,23 @@ +`timescale 1ns / 1ps + +module top( + clk, + a, + b, + c, + d); + +input wire clk; + +input wire a; +input wire b; +output wire c; +output reg d; + +assign c = a & b; + +always @(posedge clk) begin + d <= c; +end + +endmodule diff --git a/openfpga/test_script/and_k6_frac.openfpga b/openfpga/test_script/and_k6_frac.openfpga index 872af950d..63746cd71 100644 --- a/openfpga/test_script/and_k6_frac.openfpga +++ b/openfpga/test_script/and_k6_frac.openfpga @@ -1,4 +1,4 @@ -# Run VPR for the s298 design +# Run VPR for the 'and' design vpr ./test_vpr_arch/k6_frac_N10_40nm.xml ./test_blif/and.blif --clock_modeling route #--write_rr_graph example_rr_graph.xml # Read OpenFPGA architecture definition diff --git a/openfpga/test_script/and_latch_k6_frac.openfpga b/openfpga/test_script/and_latch_k6_frac.openfpga new file mode 100644 index 000000000..210bdcf28 --- /dev/null +++ b/openfpga/test_script/and_latch_k6_frac.openfpga @@ -0,0 +1,59 @@ +# Run VPR for the 'and_latch' design +vpr ./test_vpr_arch/k6_frac_N10_40nm.xml ./test_blif/and_latch.blif --clock_modeling route #--write_rr_graph example_rr_graph.xml + +# Read OpenFPGA architecture definition +read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml + +# Write out the architecture XML as a proof +#write_openfpga_arch -f ./arch_echo.xml + +# Annotate the OpenFPGA architecture to VPR data base +link_openfpga_arch --activity_file ./test_blif/and_latch.act --sort_gsb_chan_node_in_edges #--verbose + +# Check and correct any naming conflicts in the BLIF netlist +check_netlist_naming_conflict --fix --report ./netlist_renaming.xml + +# Apply fix-up to clustering nets based on routing results +pb_pin_fixup --verbose + +# Apply fix-up to Look-Up Table truth tables based on packing results +lut_truth_table_fixup #--verbose + +# Build the module graph +# - Enabled compression on routing architecture modules +# - Enable pin duplication on grid modules +build_fabric --compress_routing --duplicate_grid_pin #--verbose + +# Repack the netlist to physical pbs +# This must be done before bitstream generator and testbench generation +# Strongly recommend it is done after all the fix-up have been applied +repack #--verbose + +# Build the bitstream +# - Output the fabric-independent bitstream to a file +build_architecture_bitstream --verbose --file /var/tmp/xtang/openfpga_test_src/fabric_indepenent_bitstream.xml + +# Build fabric-dependent bitstream +build_fabric_bitstream --verbose + +# Write the Verilog netlist for FPGA fabric +# - Enable the use of explicit port mapping in Verilog netlist +write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src/SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose + +# Write the Verilog testbench for FPGA fabric +# - We suggest the use of same output directory as fabric Verilog netlists +# - Must specify the reference benchmark file if you want to output any testbenches +# - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA +# - Enable pre-configured top-level testbench which is a fast verification skipping programming phase +# - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts +write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src/SRC --reference_benchmark_file_path /var/tmp/xtang/and.v --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini /var/tmp/xtang/openfpga_test_src/simulation_deck.ini + +# Write the SDC files for PnR backend +# - Turn on every options here +write_pnr_sdc --file /var/tmp/xtang/openfpga_test_src/SDC + +# Write the SDC to run timing analysis for a mapped FPGA fabric +write_analysis_sdc --file /var/tmp/xtang/openfpga_test_src/SDC_analysis + +# Finish and exit OpenFPGA +exit From 8e796f152f47bb5b6a93704f3c640dec5e5a2f10 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 11 Mar 2020 21:05:06 -0600 Subject: [PATCH 296/645] add comments to lb_router about how-to-use --- openfpga/src/repack/lb_router.h | 44 +++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/openfpga/src/repack/lb_router.h b/openfpga/src/repack/lb_router.h index c1d82ca1c..698dcc26f 100644 --- a/openfpga/src/repack/lb_router.h +++ b/openfpga/src/repack/lb_router.h @@ -25,6 +25,50 @@ /* begin namespace openfpga */ namespace openfpga { +/******************************************************************** + * A connection-driven router for programmable logic blocks + * The router supports routing multiple nets on a LbRRGraph object + * which models the routing resources in a programmable logic block + * + * Note: + * - This router will not build/allocate a LbRRGraph object + * Users must do it OUTSIDE this object!!! + * - This router supports multiple sources for single net + * which is more capable the original VPR lb_router + * + * How to use the router: + * + * // Create your own routing resource graph + * LbRRGraph lb_rr_graph = (); + * + * // Create a router object + * LbRouter lb_router(lb_rr_graph); + * + * // Add nets to be routed + * std::vector source_nodes = (); + * std::vector sink_nodes = (); + * LbNetId net = lb_router.create_net_to_route(source_nodes, sink_nodes); + * // Add more nets + * + * // Initialize the modes to expand routing + * // This is a must-do before running the router in the purpose of repacking!!! + * lb_router.set_physical_pb_modes(lb_rr_graph, device_annotation); + * + * // Run the router + * bool route_success = lb_router.try_route(lb_rr_graph, atom_ctx.nlist, verbose); + * + * // Check routing status + * if (true == route_success) { + * // Succeed + * } + * + * // Read out routing results + * // Here is an example to check which nodes are mapped to the 'net' created before + * std::vector routed_nodes = lb_router.net_routed_nodes(net); + * + *******************************************************************/ + + class LbRouter { public: /* Strong ids */ struct net_id_tag; From 17a1c61b9d0bd81eb718502ee01191e066233265 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 11 Mar 2020 21:10:16 -0600 Subject: [PATCH 297/645] minor change in variable names in lb_router --- openfpga/src/repack/lb_router.cpp | 26 +++++++++---------- .../k6_frac_N10_40nm_openfpga.xml | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/openfpga/src/repack/lb_router.cpp b/openfpga/src/repack/lb_router.cpp index c1b67b804..faddbea8c 100644 --- a/openfpga/src/repack/lb_router.cpp +++ b/openfpga/src/repack/lb_router.cpp @@ -237,24 +237,24 @@ bool LbRouter::try_route(const LbRRGraph& lb_rr_graph, unsigned int inet; /* Iterate across all nets internal to logic block */ for (inet = 0; inet < lb_net_ids_.size() && !is_impossible; inet++) { - NetId idx = NetId(inet); - if (is_skip_route_net(lb_rr_graph, lb_net_rt_trees_[idx])) { + NetId net_idx = NetId(inet); + if (is_skip_route_net(lb_rr_graph, lb_net_rt_trees_[net_idx])) { continue; } - commit_remove_rt(lb_rr_graph, lb_net_rt_trees_[idx], RT_REMOVE, mode_map); - free_net_rt(lb_net_rt_trees_[idx]); - lb_net_rt_trees_[idx] = nullptr; - add_source_to_rt(idx); + commit_remove_rt(lb_rr_graph, lb_net_rt_trees_[net_idx], RT_REMOVE, mode_map); + free_net_rt(lb_net_rt_trees_[net_idx]); + lb_net_rt_trees_[net_idx] = nullptr; + add_source_to_rt(net_idx); /* Route each sink of net */ - for (unsigned int itarget = 1; itarget < lb_net_terminals_[idx].size() && !is_impossible; itarget++) { + for (unsigned int itarget = 1; itarget < lb_net_terminals_[net_idx].size() && !is_impossible; itarget++) { pq_.clear(); /* Get lowest cost next node, repeat until a path is found or if it is impossible to route */ - expand_rt(idx, idx); + expand_rt(net_idx, net_idx); - is_impossible = try_expand_nodes(atom_nlist, lb_rr_graph, idx, exp_node, itarget, mode_status_.expand_all_modes, verbosity); + is_impossible = try_expand_nodes(atom_nlist, lb_rr_graph, net_idx, exp_node, itarget, mode_status_.expand_all_modes, verbosity); if (is_impossible && !mode_status_.expand_all_modes) { mode_status_.try_expand_all_modes = true; @@ -262,15 +262,15 @@ bool LbRouter::try_route(const LbRRGraph& lb_rr_graph, break; } - if (exp_node.node_index == lb_net_terminals_[idx][itarget]) { + if (exp_node.node_index == lb_net_terminals_[net_idx][itarget]) { /* Net terminal is routed, add this to the route tree, clear data structures, and keep going */ - is_impossible = add_to_rt(lb_net_rt_trees_[idx], exp_node.node_index, idx); + is_impossible = add_to_rt(lb_net_rt_trees_[net_idx], exp_node.node_index, net_idx); } if (is_impossible) { VTR_LOG("Routing was impossible!\n"); } else if (mode_status_.expand_all_modes) { - is_impossible = route_has_conflict(lb_rr_graph, lb_net_rt_trees_[idx]); + is_impossible = route_has_conflict(lb_rr_graph, lb_net_rt_trees_[net_idx]); if (is_impossible) { VTR_LOG("Routing was impossible due to modes!\n"); } @@ -288,7 +288,7 @@ bool LbRouter::try_route(const LbRRGraph& lb_rr_graph, } if (!is_impossible) { - commit_remove_rt(lb_rr_graph, lb_net_rt_trees_[idx], RT_COMMIT, mode_map); + commit_remove_rt(lb_rr_graph, lb_net_rt_trees_[net_idx], RT_COMMIT, mode_map); if (mode_status_.is_mode_conflict) { is_impossible = true; } diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml index 9644530da..b750f9736 100644 --- a/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml @@ -218,7 +218,7 @@ - + From cd50155e2931252d5fc2ff00e55eef572a831fcc Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 12 Mar 2020 10:24:38 -0600 Subject: [PATCH 298/645] rename variables in lb router --- openfpga/src/repack/lb_router.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openfpga/src/repack/lb_router.cpp b/openfpga/src/repack/lb_router.cpp index faddbea8c..e5d656686 100644 --- a/openfpga/src/repack/lb_router.cpp +++ b/openfpga/src/repack/lb_router.cpp @@ -248,13 +248,13 @@ bool LbRouter::try_route(const LbRRGraph& lb_rr_graph, add_source_to_rt(net_idx); /* Route each sink of net */ - for (unsigned int itarget = 1; itarget < lb_net_terminals_[net_idx].size() && !is_impossible; itarget++) { + for (unsigned int isink = 1; isink < lb_net_terminals_[net_idx].size() && !is_impossible; isink++) { pq_.clear(); /* Get lowest cost next node, repeat until a path is found or if it is impossible to route */ expand_rt(net_idx, net_idx); - is_impossible = try_expand_nodes(atom_nlist, lb_rr_graph, net_idx, exp_node, itarget, mode_status_.expand_all_modes, verbosity); + is_impossible = try_expand_nodes(atom_nlist, lb_rr_graph, net_idx, exp_node, isink, mode_status_.expand_all_modes, verbosity); if (is_impossible && !mode_status_.expand_all_modes) { mode_status_.try_expand_all_modes = true; @@ -262,7 +262,7 @@ bool LbRouter::try_route(const LbRRGraph& lb_rr_graph, break; } - if (exp_node.node_index == lb_net_terminals_[net_idx][itarget]) { + if (exp_node.node_index == lb_net_terminals_[net_idx][isink]) { /* Net terminal is routed, add this to the route tree, clear data structures, and keep going */ is_impossible = add_to_rt(lb_net_rt_trees_[net_idx], exp_node.node_index, net_idx); } From a1f19e776e4d259d81e0f0706b113555500069a5 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 12 Mar 2020 11:05:38 -0600 Subject: [PATCH 299/645] Add comments to lb router and extract a private function for routing a single net --- openfpga/src/repack/lb_router.cpp | 129 ++++++++++++++++++------------ openfpga/src/repack/lb_router.h | 7 ++ 2 files changed, 83 insertions(+), 53 deletions(-) diff --git a/openfpga/src/repack/lb_router.cpp b/openfpga/src/repack/lb_router.cpp index e5d656686..c37c59b74 100644 --- a/openfpga/src/repack/lb_router.cpp +++ b/openfpga/src/repack/lb_router.cpp @@ -202,6 +202,80 @@ void LbRouter::set_physical_pb_modes(const LbRRGraph& lb_rr_graph, } } +bool LbRouter::try_route_net(const LbRRGraph& lb_rr_graph, + const AtomNetlist& atom_nlist, + const NetId& net_idx, + t_expansion_node& exp_node, + std::unordered_map& mode_map, + const int& verbosity) { + + bool is_impossible = false; + + if (is_skip_route_net(lb_rr_graph, lb_net_rt_trees_[net_idx])) { + return true; + } + + commit_remove_rt(lb_rr_graph, lb_net_rt_trees_[net_idx], RT_REMOVE, mode_map); + free_net_rt(lb_net_rt_trees_[net_idx]); + lb_net_rt_trees_[net_idx] = nullptr; + add_source_to_rt(net_idx); + + /* Route each sink of net */ + for (size_t isink = 1; isink < lb_net_terminals_[net_idx].size() && !is_impossible; ++isink) { + pq_.clear(); + /* Get lowest cost next node, repeat until a path is found or if it is impossible to route */ + + expand_rt(net_idx, net_idx); + + is_impossible = try_expand_nodes(atom_nlist, lb_rr_graph, net_idx, exp_node, isink, mode_status_.expand_all_modes, verbosity); + + if (is_impossible && !mode_status_.expand_all_modes) { + mode_status_.try_expand_all_modes = true; + mode_status_.expand_all_modes = true; + break; + } + + if (exp_node.node_index == lb_net_terminals_[net_idx][isink]) { + /* Net terminal is routed, add this to the route tree, clear data structures, and keep going */ + is_impossible = add_to_rt(lb_net_rt_trees_[net_idx], exp_node.node_index, net_idx); + } + + if (is_impossible) { + VTR_LOG("Routing was impossible!\n"); + } else if (mode_status_.expand_all_modes) { + is_impossible = route_has_conflict(lb_rr_graph, lb_net_rt_trees_[net_idx]); + if (is_impossible) { + VTR_LOG("Routing was impossible due to modes!\n"); + } + } + + explore_id_index_++; + if (explore_id_index_ > 2000000000) { + /* overflow protection */ + for (const LbRRNodeId& id : lb_rr_graph.nodes()) { + explored_node_tb_[id].explored_id = OPEN; + explored_node_tb_[id].enqueue_id = OPEN; + explore_id_index_ = 1; + } + } + } + + /* If routing succeed so far, we will try to save(commit) results + * to route tree. + * During this process, we will check if there is any + * nodes using different modes under the same pb_type + * If so, we have conflicts and routing is considered to be failure + */ + if (!is_impossible) { + commit_remove_rt(lb_rr_graph, lb_net_rt_trees_[net_idx], RT_COMMIT, mode_map); + if (mode_status_.is_mode_conflict) { + is_impossible = true; + } + } + + return !is_impossible; +} + bool LbRouter::try_route(const LbRRGraph& lb_rr_graph, const AtomNetlist& atom_nlist, const int& verbosity) { @@ -238,60 +312,9 @@ bool LbRouter::try_route(const LbRRGraph& lb_rr_graph, /* Iterate across all nets internal to logic block */ for (inet = 0; inet < lb_net_ids_.size() && !is_impossible; inet++) { NetId net_idx = NetId(inet); - if (is_skip_route_net(lb_rr_graph, lb_net_rt_trees_[net_idx])) { - continue; - } - commit_remove_rt(lb_rr_graph, lb_net_rt_trees_[net_idx], RT_REMOVE, mode_map); - free_net_rt(lb_net_rt_trees_[net_idx]); - lb_net_rt_trees_[net_idx] = nullptr; - add_source_to_rt(net_idx); - - /* Route each sink of net */ - for (unsigned int isink = 1; isink < lb_net_terminals_[net_idx].size() && !is_impossible; isink++) { - pq_.clear(); - /* Get lowest cost next node, repeat until a path is found or if it is impossible to route */ - - expand_rt(net_idx, net_idx); - - is_impossible = try_expand_nodes(atom_nlist, lb_rr_graph, net_idx, exp_node, isink, mode_status_.expand_all_modes, verbosity); - - if (is_impossible && !mode_status_.expand_all_modes) { - mode_status_.try_expand_all_modes = true; - mode_status_.expand_all_modes = true; - break; - } - - if (exp_node.node_index == lb_net_terminals_[net_idx][isink]) { - /* Net terminal is routed, add this to the route tree, clear data structures, and keep going */ - is_impossible = add_to_rt(lb_net_rt_trees_[net_idx], exp_node.node_index, net_idx); - } - - if (is_impossible) { - VTR_LOG("Routing was impossible!\n"); - } else if (mode_status_.expand_all_modes) { - is_impossible = route_has_conflict(lb_rr_graph, lb_net_rt_trees_[net_idx]); - if (is_impossible) { - VTR_LOG("Routing was impossible due to modes!\n"); - } - } - - explore_id_index_++; - if (explore_id_index_ > 2000000000) { - /* overflow protection */ - for (const LbRRNodeId& id : lb_rr_graph.nodes()) { - explored_node_tb_[id].explored_id = OPEN; - explored_node_tb_[id].enqueue_id = OPEN; - explore_id_index_ = 1; - } - } - } - - if (!is_impossible) { - commit_remove_rt(lb_rr_graph, lb_net_rt_trees_[net_idx], RT_COMMIT, mode_map); - if (mode_status_.is_mode_conflict) { - is_impossible = true; - } + if (false == try_route_net(lb_rr_graph, atom_nlist, net_idx, exp_node, mode_map, verbosity)) { + is_impossible = true; } } diff --git a/openfpga/src/repack/lb_router.h b/openfpga/src/repack/lb_router.h index 698dcc26f..0d7d4b9af 100644 --- a/openfpga/src/repack/lb_router.h +++ b/openfpga/src/repack/lb_router.h @@ -320,6 +320,13 @@ class LbRouter { const bool& try_other_modes, const int& verbosity); + bool try_route_net(const LbRRGraph& lb_rr_graph, + const AtomNetlist& atom_nlist, + const NetId& net_idx, + t_expansion_node& exp_node, + std::unordered_map& mode_map, + const int& verbosity); + private : /* Private validators */ /** * Validate if the rr_graph is the one we used to initialize the router From 689c50dff1bba28569dcffc8d04111c638a0a189 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 12 Mar 2020 11:36:31 -0600 Subject: [PATCH 300/645] label the routing status for each sink in lb_router --- openfpga/src/repack/lb_router.cpp | 45 +++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/openfpga/src/repack/lb_router.cpp b/openfpga/src/repack/lb_router.cpp index c37c59b74..80bb5a3f6 100644 --- a/openfpga/src/repack/lb_router.cpp +++ b/openfpga/src/repack/lb_router.cpp @@ -209,7 +209,8 @@ bool LbRouter::try_route_net(const LbRRGraph& lb_rr_graph, std::unordered_map& mode_map, const int& verbosity) { - bool is_impossible = false; + std::vector sink_routed(lb_net_terminals_[net_idx].size(), false); + sink_routed[0] = true; /* Deposite true for source node */ if (is_skip_route_net(lb_rr_graph, lb_net_rt_trees_[net_idx])) { return true; @@ -221,15 +222,28 @@ bool LbRouter::try_route_net(const LbRRGraph& lb_rr_graph, add_source_to_rt(net_idx); /* Route each sink of net */ - for (size_t isink = 1; isink < lb_net_terminals_[net_idx].size() && !is_impossible; ++isink) { + for (size_t isink = 1; isink < lb_net_terminals_[net_idx].size(); ++isink) { + /* Check if last sink failed in routing + * If failed, the routing is not possible for this net + */ + if (1 < isink) { + if (false == sink_routed[isink - 1]) { + break; + } + } + pq_.clear(); /* Get lowest cost next node, repeat until a path is found or if it is impossible to route */ expand_rt(net_idx, net_idx); - is_impossible = try_expand_nodes(atom_nlist, lb_rr_graph, net_idx, exp_node, isink, mode_status_.expand_all_modes, verbosity); + /* If we managed to expand the nodes to the sink, routing for this sink is done. + * If not, we failed in routing. + * Therefore, the output of try_expand_nodes() is inverted + */ + sink_routed[isink] = !try_expand_nodes(atom_nlist, lb_rr_graph, net_idx, exp_node, isink, mode_status_.expand_all_modes, verbosity); - if (is_impossible && !mode_status_.expand_all_modes) { + if (false == sink_routed[isink] && !mode_status_.expand_all_modes) { mode_status_.try_expand_all_modes = true; mode_status_.expand_all_modes = true; break; @@ -237,14 +251,14 @@ bool LbRouter::try_route_net(const LbRRGraph& lb_rr_graph, if (exp_node.node_index == lb_net_terminals_[net_idx][isink]) { /* Net terminal is routed, add this to the route tree, clear data structures, and keep going */ - is_impossible = add_to_rt(lb_net_rt_trees_[net_idx], exp_node.node_index, net_idx); + sink_routed[isink] = !add_to_rt(lb_net_rt_trees_[net_idx], exp_node.node_index, net_idx); } - if (is_impossible) { + if (false == sink_routed[isink]) { VTR_LOG("Routing was impossible!\n"); } else if (mode_status_.expand_all_modes) { - is_impossible = route_has_conflict(lb_rr_graph, lb_net_rt_trees_[net_idx]); - if (is_impossible) { + sink_routed[isink] = !route_has_conflict(lb_rr_graph, lb_net_rt_trees_[net_idx]); + if (false == sink_routed[isink]) { VTR_LOG("Routing was impossible due to modes!\n"); } } @@ -260,20 +274,29 @@ bool LbRouter::try_route_net(const LbRRGraph& lb_rr_graph, } } + /* Check the routing status for all the sinks */ + bool route_succeed = true; + for (const bool& sink_status : sink_routed) { + if (false == sink_status) { + route_succeed = false; + break; + } + } + /* If routing succeed so far, we will try to save(commit) results * to route tree. * During this process, we will check if there is any * nodes using different modes under the same pb_type * If so, we have conflicts and routing is considered to be failure */ - if (!is_impossible) { + if (true == route_succeed) { commit_remove_rt(lb_rr_graph, lb_net_rt_trees_[net_idx], RT_COMMIT, mode_map); if (mode_status_.is_mode_conflict) { - is_impossible = true; + route_succeed = false; } } - return !is_impossible; + return route_succeed; } bool LbRouter::try_route(const LbRRGraph& lb_rr_graph, From f1e8e784109c6ee1fe292bf5f3956ed8c0f252df Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 12 Mar 2020 11:47:42 -0600 Subject: [PATCH 301/645] minor code formatting --- openfpga/src/repack/lb_router.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openfpga/src/repack/lb_router.cpp b/openfpga/src/repack/lb_router.cpp index 80bb5a3f6..1098a8b46 100644 --- a/openfpga/src/repack/lb_router.cpp +++ b/openfpga/src/repack/lb_router.cpp @@ -212,7 +212,7 @@ bool LbRouter::try_route_net(const LbRRGraph& lb_rr_graph, std::vector sink_routed(lb_net_terminals_[net_idx].size(), false); sink_routed[0] = true; /* Deposite true for source node */ - if (is_skip_route_net(lb_rr_graph, lb_net_rt_trees_[net_idx])) { + if (true == is_skip_route_net(lb_rr_graph, lb_net_rt_trees_[net_idx])) { return true; } From c40675ca9d23426d01dd3986f49bdcd1f4fc5004 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 12 Mar 2020 11:55:25 -0600 Subject: [PATCH 302/645] minor code formatting --- openfpga/src/repack/lb_router.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openfpga/src/repack/lb_router.cpp b/openfpga/src/repack/lb_router.cpp index 1098a8b46..ac6d2a49d 100644 --- a/openfpga/src/repack/lb_router.cpp +++ b/openfpga/src/repack/lb_router.cpp @@ -243,7 +243,7 @@ bool LbRouter::try_route_net(const LbRRGraph& lb_rr_graph, */ sink_routed[isink] = !try_expand_nodes(atom_nlist, lb_rr_graph, net_idx, exp_node, isink, mode_status_.expand_all_modes, verbosity); - if (false == sink_routed[isink] && !mode_status_.expand_all_modes) { + if (false == sink_routed[isink] && false == mode_status_.expand_all_modes) { mode_status_.try_expand_all_modes = true; mode_status_.expand_all_modes = true; break; @@ -291,7 +291,7 @@ bool LbRouter::try_route_net(const LbRRGraph& lb_rr_graph, */ if (true == route_succeed) { commit_remove_rt(lb_rr_graph, lb_net_rt_trees_[net_idx], RT_COMMIT, mode_map); - if (mode_status_.is_mode_conflict) { + if (true == mode_status_.is_mode_conflict) { route_succeed = false; } } From f0b22aaa115682af36df60b04daaa73be3465f1a Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 12 Mar 2020 13:44:14 -0600 Subject: [PATCH 303/645] Make lb router support multiple sources to be routed --- openfpga/src/repack/lb_router.cpp | 254 +++++++++++++++++------------- openfpga/src/repack/lb_router.h | 31 +++- 2 files changed, 171 insertions(+), 114 deletions(-) diff --git a/openfpga/src/repack/lb_router.cpp b/openfpga/src/repack/lb_router.cpp index ac6d2a49d..1be68cc51 100644 --- a/openfpga/src/repack/lb_router.cpp +++ b/openfpga/src/repack/lb_router.cpp @@ -73,12 +73,14 @@ std::vector LbRouter::net_routed_nodes(const NetId& net) const { std::vector routed_nodes; - t_trace* rt_tree = lb_net_rt_trees_[net]; - if (nullptr == rt_tree) { - return routed_nodes; + for (size_t isrc = 0; isrc < lb_net_sources_[net].size(); ++isrc) { + t_trace* rt_tree = lb_net_rt_trees_[net][isrc]; + if (nullptr == rt_tree) { + return routed_nodes; + } + /* Walk through the routing tree of the net */ + rec_collect_trace_nodes(rt_tree, routed_nodes); } - /* Walk through the routing tree of the net */ - rec_collect_trace_nodes(rt_tree, routed_nodes); return routed_nodes; } @@ -152,13 +154,12 @@ LbRouter::NetId LbRouter::create_net_to_route(const LbRRNodeId& source, const st /* Allocate other attributes */ lb_net_atom_net_ids_.push_back(AtomNetId::INVALID()); - lb_net_atom_pins_.emplace_back(); + lb_net_atom_source_pins_.emplace_back(); + lb_net_atom_sink_pins_.emplace_back(); - std::vector net_terminals = terminals; - net_terminals.insert(net_terminals.begin(), source); - - lb_net_terminals_.push_back(net_terminals); - lb_net_rt_trees_.push_back(nullptr); + lb_net_sources_.push_back(std::vector(1, source)); + lb_net_sinks_.push_back(terminals); + lb_net_rt_trees_.push_back(std::vector(1, nullptr)); return net; } @@ -170,8 +171,8 @@ void LbRouter::add_net_atom_net_id(const NetId& net, const AtomNetId& atom_net) void LbRouter::add_net_atom_pins(const NetId& net, const AtomPinId& src_pin, const std::vector& terminal_pins) { VTR_ASSERT(true == valid_net_id(net)); - lb_net_atom_pins_[net] = terminal_pins; - lb_net_atom_pins_[net].insert(lb_net_atom_pins_[net].begin(), src_pin); + lb_net_atom_sink_pins_[net] = terminal_pins; + lb_net_atom_source_pins_[net] = std::vector(1, src_pin); } void LbRouter::set_physical_pb_modes(const LbRRGraph& lb_rr_graph, @@ -209,67 +210,69 @@ bool LbRouter::try_route_net(const LbRRGraph& lb_rr_graph, std::unordered_map& mode_map, const int& verbosity) { - std::vector sink_routed(lb_net_terminals_[net_idx].size(), false); - sink_routed[0] = true; /* Deposite true for source node */ + std::vector sink_routed(lb_net_sinks_[net_idx].size(), false); - if (true == is_skip_route_net(lb_rr_graph, lb_net_rt_trees_[net_idx])) { - return true; - } + for (size_t isrc = 0; isrc < lb_net_sources_[net_idx].size(); ++isrc) { - commit_remove_rt(lb_rr_graph, lb_net_rt_trees_[net_idx], RT_REMOVE, mode_map); - free_net_rt(lb_net_rt_trees_[net_idx]); - lb_net_rt_trees_[net_idx] = nullptr; - add_source_to_rt(net_idx); + if (true == is_skip_route_net(lb_rr_graph, lb_net_rt_trees_[net_idx][isrc])) { + return true; + } - /* Route each sink of net */ - for (size_t isink = 1; isink < lb_net_terminals_[net_idx].size(); ++isink) { - /* Check if last sink failed in routing - * If failed, the routing is not possible for this net - */ - if (1 < isink) { - if (false == sink_routed[isink - 1]) { + commit_remove_rt(lb_rr_graph, lb_net_rt_trees_[net_idx][isrc], RT_REMOVE, mode_map); + free_net_rt(lb_net_rt_trees_[net_idx][isrc]); + lb_net_rt_trees_[net_idx][isrc] = nullptr; + add_source_to_rt(net_idx, isrc); + + /* Route each sink of net */ + for (size_t isink = 0; isink < lb_net_sinks_[net_idx].size(); ++isink) { + /* Check if last sink failed in routing + * If failed, the routing is not possible for this net + */ + if (0 < isink) { + if (false == sink_routed[isink - 1]) { + break; + } + } + + pq_.clear(); + /* Get lowest cost next node, repeat until a path is found or if it is impossible to route */ + + expand_rt(net_idx, net_idx, isrc); + + /* If we managed to expand the nodes to the sink, routing for this sink is done. + * If not, we failed in routing. + * Therefore, the output of try_expand_nodes() is inverted + */ + sink_routed[isink] = !try_expand_nodes(atom_nlist, lb_rr_graph, net_idx, exp_node, isrc, isink, mode_status_.expand_all_modes, verbosity); + + if (false == sink_routed[isink] && false == mode_status_.expand_all_modes) { + mode_status_.try_expand_all_modes = true; + mode_status_.expand_all_modes = true; break; } - } - pq_.clear(); - /* Get lowest cost next node, repeat until a path is found or if it is impossible to route */ - - expand_rt(net_idx, net_idx); - - /* If we managed to expand the nodes to the sink, routing for this sink is done. - * If not, we failed in routing. - * Therefore, the output of try_expand_nodes() is inverted - */ - sink_routed[isink] = !try_expand_nodes(atom_nlist, lb_rr_graph, net_idx, exp_node, isink, mode_status_.expand_all_modes, verbosity); - - if (false == sink_routed[isink] && false == mode_status_.expand_all_modes) { - mode_status_.try_expand_all_modes = true; - mode_status_.expand_all_modes = true; - break; - } - - if (exp_node.node_index == lb_net_terminals_[net_idx][isink]) { - /* Net terminal is routed, add this to the route tree, clear data structures, and keep going */ - sink_routed[isink] = !add_to_rt(lb_net_rt_trees_[net_idx], exp_node.node_index, net_idx); - } - - if (false == sink_routed[isink]) { - VTR_LOG("Routing was impossible!\n"); - } else if (mode_status_.expand_all_modes) { - sink_routed[isink] = !route_has_conflict(lb_rr_graph, lb_net_rt_trees_[net_idx]); - if (false == sink_routed[isink]) { - VTR_LOG("Routing was impossible due to modes!\n"); + if (exp_node.node_index == lb_net_sinks_[net_idx][isink]) { + /* Net terminal is routed, add this to the route tree, clear data structures, and keep going */ + sink_routed[isink] = !add_to_rt(lb_net_rt_trees_[net_idx][isrc], exp_node.node_index, net_idx); } - } - explore_id_index_++; - if (explore_id_index_ > 2000000000) { - /* overflow protection */ - for (const LbRRNodeId& id : lb_rr_graph.nodes()) { - explored_node_tb_[id].explored_id = OPEN; - explored_node_tb_[id].enqueue_id = OPEN; - explore_id_index_ = 1; + if (false == sink_routed[isink]) { + VTR_LOG("Routing was impossible!\n"); + } else if (mode_status_.expand_all_modes) { + sink_routed[isink] = !route_has_conflict(lb_rr_graph, lb_net_rt_trees_[net_idx][isrc]); + if (false == sink_routed[isink]) { + VTR_LOG("Routing was impossible due to modes!\n"); + } + } + + explore_id_index_++; + if (explore_id_index_ > 2000000000) { + /* overflow protection */ + for (const LbRRNodeId& id : lb_rr_graph.nodes()) { + explored_node_tb_[id].explored_id = OPEN; + explored_node_tb_[id].enqueue_id = OPEN; + explore_id_index_ = 1; + } } } } @@ -290,9 +293,11 @@ bool LbRouter::try_route_net(const LbRRGraph& lb_rr_graph, * If so, we have conflicts and routing is considered to be failure */ if (true == route_succeed) { - commit_remove_rt(lb_rr_graph, lb_net_rt_trees_[net_idx], RT_COMMIT, mode_map); - if (true == mode_status_.is_mode_conflict) { - route_succeed = false; + for (size_t isrc = 1; isrc < lb_net_sources_[net_idx].size(); ++isrc) { + commit_remove_rt(lb_rr_graph, lb_net_rt_trees_[net_idx][isrc], RT_COMMIT, mode_map); + if (true == mode_status_.is_mode_conflict) { + route_succeed = false; + } } } @@ -351,14 +356,18 @@ bool LbRouter::try_route(const LbRRGraph& lb_rr_graph, atom_nlist.net_name(lb_net_atom_net_ids_[NetId(inet)]).c_str(), lb_type_->name); VTR_LOGV(verbosity < 3, - "\tNet source pin '%s'\n", - lb_rr_graph.node_pb_graph_pin(lb_net_terminals_[NetId(inet)][0])->to_string().c_str()); - VTR_LOGV(verbosity < 3, - "\tNet sink pins:\n"); - for (size_t isink = 1; isink < lb_net_terminals_[NetId(inet)].size(); ++isink) { + "\tNet source pin:\n"); + for (size_t isrc = 0; isrc < lb_net_sources_[NetId(inet)].size(); ++isrc) { VTR_LOGV(verbosity < 3, "\t\t%s\n", - lb_rr_graph.node_pb_graph_pin(lb_net_terminals_[NetId(inet)][isink])->to_string().c_str()); + lb_rr_graph.node_pb_graph_pin(lb_net_sources_[NetId(inet)][isrc])->to_string().c_str()); + } + VTR_LOGV(verbosity < 3, + "\tNet sink pins:\n"); + for (size_t isink = 0; isink < lb_net_sinks_[NetId(inet)].size(); ++isink) { + VTR_LOGV(verbosity < 3, + "\t\t%s\n", + lb_rr_graph.node_pb_graph_pin(lb_net_sinks_[NetId(inet)][isink])->to_string().c_str()); } VTR_LOGV(verbosity < 3, "Please check your architecture XML to see if it is routable\n"); @@ -385,8 +394,8 @@ void LbRouter::fix_duplicate_equivalent_pins(const AtomContext& atom_ctx, for (const NetId& ilb_net : lb_net_ids_) { //Collect all the sink terminals indicies which target a particular node std::map> duplicate_terminals; - for (size_t iterm = 1; iterm < lb_net_terminals_[ilb_net].size(); ++iterm) { - LbRRNodeId node = lb_net_terminals_[ilb_net][iterm]; + for (size_t iterm = 0; iterm < lb_net_sinks_[ilb_net].size(); ++iterm) { + LbRRNodeId node = lb_net_sinks_[ilb_net][iterm]; duplicate_terminals[node].push_back(iterm); } @@ -398,8 +407,8 @@ void LbRouter::fix_duplicate_equivalent_pins(const AtomContext& atom_ctx, for (size_t idup_term = 0; idup_term < kv.second.size(); ++idup_term) { int iterm = kv.second[idup_term]; //The index in terminals which is duplicated - VTR_ASSERT(lb_net_atom_pins_[ilb_net].size() == lb_net_terminals_[ilb_net].size()); - AtomPinId atom_pin = lb_net_atom_pins_[ilb_net][iterm]; + VTR_ASSERT(lb_net_atom_sink_pins_[ilb_net].size() == lb_net_sinks_[ilb_net].size()); + AtomPinId atom_pin = lb_net_atom_sink_pins_[ilb_net][iterm]; VTR_ASSERT(atom_pin); const t_pb_graph_pin* pb_graph_pin = find_pb_graph_pin(atom_ctx.nlist, atom_ctx.lookup, atom_pin); @@ -421,10 +430,10 @@ void LbRouter::fix_duplicate_equivalent_pins(const AtomContext& atom_ctx, VTR_ASSERT(1 == lb_rr_graph.node_out_edges(pin_index, &(pb_graph_pin->parent_node->pb_type->modes[0])).size()); LbRRNodeId sink_index = lb_rr_graph.edge_sink_node(lb_rr_graph.node_out_edges(pin_index, &(pb_graph_pin->parent_node->pb_type->modes[0]))[0]); VTR_ASSERT(LB_SINK == lb_rr_graph.node_type(sink_index)); - VTR_ASSERT_MSG(sink_index == lb_net_terminals_[ilb_net][iterm], "Remapped pin must be connected to original sink"); + VTR_ASSERT_MSG(sink_index == lb_net_sinks_[ilb_net][iterm], "Remapped pin must be connected to original sink"); //Change the target - lb_net_terminals_[ilb_net][iterm] = pin_index; + lb_net_sinks_[ilb_net][iterm] = pin_index; } } } @@ -590,11 +599,11 @@ bool LbRouter::add_to_rt(t_trace* rt, const LbRRNodeId& node_index, const NetId& return false; } -void LbRouter::add_source_to_rt(const NetId& inet) { +void LbRouter::add_source_to_rt(const NetId& inet, const size_t& isrc) { /* TODO: Validate net id */ - VTR_ASSERT(nullptr == lb_net_rt_trees_[inet]); - lb_net_rt_trees_[inet] = new t_trace; - lb_net_rt_trees_[inet]->current_node = lb_net_terminals_[inet][0]; + VTR_ASSERT(nullptr == lb_net_rt_trees_[inet][isrc]); + lb_net_rt_trees_[inet][isrc] = new t_trace; + lb_net_rt_trees_[inet][isrc]->current_node = lb_net_sources_[inet][isrc]; } void LbRouter::expand_rt_rec(t_trace* rt, @@ -620,10 +629,11 @@ void LbRouter::expand_rt_rec(t_trace* rt, } void LbRouter::expand_rt(const NetId& inet, - const NetId& irt_net) { + const NetId& irt_net, + const size_t& isrc) { VTR_ASSERT(pq_.empty()); - expand_rt_rec(lb_net_rt_trees_[inet], LbRRNodeId::INVALID(), irt_net, explore_id_index_); + expand_rt_rec(lb_net_rt_trees_[inet][isrc], LbRRNodeId::INVALID(), irt_net, explore_id_index_); } void LbRouter::expand_edges(const LbRRGraph& lb_rr_graph, @@ -758,6 +768,7 @@ bool LbRouter::try_expand_nodes(const AtomNetlist& atom_nlist, const LbRRGraph& lb_rr_graph, const NetId& lb_net, t_expansion_node& exp_node, + const int& isrc, const int& itarget, const bool& try_other_modes, const int& verbosity) { @@ -771,10 +782,10 @@ bool LbRouter::try_expand_nodes(const AtomNetlist& atom_nlist, if (verbosity > 3) { //Print detailed debug info AtomNetId net_id = lb_net_atom_net_ids_[lb_net]; - AtomPinId driver_pin = lb_net_atom_pins_[lb_net][0]; - AtomPinId sink_pin = lb_net_atom_pins_[lb_net][itarget]; - LbRRNodeId driver_rr_node = lb_net_terminals_[lb_net][0]; - LbRRNodeId sink_rr_node = lb_net_terminals_[lb_net][itarget]; + AtomPinId driver_pin = lb_net_atom_source_pins_[lb_net][isrc]; + AtomPinId sink_pin = lb_net_atom_sink_pins_[lb_net][itarget]; + LbRRNodeId driver_rr_node = lb_net_sources_[lb_net][isrc]; + LbRRNodeId sink_rr_node = lb_net_sinks_[lb_net][itarget]; VTR_LOG("\t\t\tNo possible routing path from %s to %s: needed for net '%s' from net pin '%s'", describe_lb_rr_node(lb_rr_graph, driver_rr_node).c_str(), @@ -796,16 +807,16 @@ bool LbRouter::try_expand_nodes(const AtomNetlist& atom_nlist, */ explored_node_tb_[exp_inode].explored_id = explore_id_index_; explored_node_tb_[exp_inode].prev_index = exp_node.prev_index; - if (exp_inode != lb_net_terminals_[lb_net][itarget]) { + if (exp_inode != lb_net_sinks_[lb_net][itarget]) { if (!try_other_modes) { - expand_node(lb_rr_graph, exp_node, lb_net_terminals_[lb_net].size() - 1); + expand_node(lb_rr_graph, exp_node, lb_net_sinks_[lb_net].size()); } else { - expand_node_all_modes(lb_rr_graph, exp_node, lb_net_terminals_[lb_net].size() - 1); + expand_node_all_modes(lb_rr_graph, exp_node, lb_net_sinks_[lb_net].size()); } } } } - } while (exp_node.node_index != lb_net_terminals_[lb_net][itarget] && !is_impossible); + } while (exp_node.node_index != lb_net_sinks_[lb_net][itarget] && !is_impossible); return is_impossible; } @@ -828,37 +839,62 @@ bool LbRouter::check_net(const LbRRGraph& lb_rr_graph, if (false == atom_nlist.valid_net_id(lb_net_atom_net_ids_[net])) { return false; } - if (lb_net_atom_pins_[net].size() != lb_net_terminals_[net].size()) { + if (lb_net_atom_sink_pins_[net].size() != lb_net_sinks_[net].size()) { VTR_LOGF_ERROR(__FILE__, __LINE__, "Net '%lu' has unmatched atom pins and terminals.\n", size_t(net)); return false; } /* We must have 1 source and >1 terminal */ - if (2 > lb_net_terminals_[net].size()) { + if (1 > lb_net_sources_[net].size()) { VTR_LOGF_ERROR(__FILE__, __LINE__, - "Net '%lu' has only %lu terminal.\n", - size_t(net), lb_net_terminals_[net].size()); + "Net '%lu' has only %lu sources.\n", + size_t(net), lb_net_sources_[net].size()); + return false; + } + + if (1 > lb_net_sinks_[net].size()) { + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Net '%lu' has only %lu sinks.\n", + size_t(net), lb_net_sinks_[net].size()); return false; } /* Each node must be valid */ - for (const LbRRNodeId& node : lb_net_terminals_[net]) { + for (const LbRRNodeId& node : lb_net_sources_[net]) { if (false == lb_rr_graph.valid_node_id(node)) { VTR_LOGF_ERROR(__FILE__, __LINE__, - "Net '%lu' has invalid terminal node in lb_rr_graph.\n", + "Net '%lu' has invalid sink node in lb_rr_graph.\n", size_t(net)); return false; } } + for (const LbRRNodeId& node : lb_net_sinks_[net]) { + if (false == lb_rr_graph.valid_node_id(node)) { + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Net '%lu' has invalid sink node in lb_rr_graph.\n", + size_t(net)); + return false; + } + } + /* Each atom pin must be valid */ - for (const AtomPinId& pin : lb_net_atom_pins_[net]) { + for (const AtomPinId& pin : lb_net_atom_source_pins_[net]) { if (false == atom_nlist.valid_pin_id(pin)) { VTR_LOGF_ERROR(__FILE__, __LINE__, - "Net '%lu' has invalid atom pin.\n", + "Net '%lu' has invalid source atom pin.\n", size_t(net)); return false; } } + for (const AtomPinId& pin : lb_net_atom_sink_pins_[net]) { + if (false == atom_nlist.valid_pin_id(pin)) { + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Net '%lu' has invalid sink_ atom pin.\n", + size_t(net)); + return false; + } + } + return true; } @@ -878,8 +914,10 @@ void LbRouter::reset_explored_node_tb() { void LbRouter::reset_net_rt() { for (const NetId& inet : lb_net_ids_) { - free_net_rt(lb_net_rt_trees_[inet]); - lb_net_rt_trees_[inet] = nullptr; + for (size_t isrc = 0; isrc < lb_net_sources_[inet].size(); ++isrc) { + free_net_rt(lb_net_rt_trees_[inet][isrc]); + lb_net_rt_trees_[inet][isrc] = nullptr; + } } } @@ -896,8 +934,10 @@ void LbRouter::clear_nets() { lb_net_ids_.clear(); lb_net_atom_net_ids_.clear(); - lb_net_atom_pins_.clear(); - lb_net_terminals_.clear(); + lb_net_atom_source_pins_.clear(); + lb_net_atom_sink_pins_.clear(); + lb_net_sources_.clear(); + lb_net_sinks_.clear(); lb_net_rt_trees_.clear(); } diff --git a/openfpga/src/repack/lb_router.h b/openfpga/src/repack/lb_router.h index 0d7d4b9af..600ddc4a4 100644 --- a/openfpga/src/repack/lb_router.h +++ b/openfpga/src/repack/lb_router.h @@ -294,13 +294,14 @@ class LbRouter { std::unordered_map& mode_map); bool is_skip_route_net(const LbRRGraph& lb_rr_graph, t_trace* rt); bool add_to_rt(t_trace* rt, const LbRRNodeId& node_index, const NetId& irt_net); - void add_source_to_rt(const NetId& inet); + void add_source_to_rt(const NetId& inet, const size_t& isrc); void expand_rt_rec(t_trace* rt, const LbRRNodeId& prev_index, const NetId& irt_net, const int& explore_id_index); void expand_rt(const NetId& inet, - const NetId& irt_net); + const NetId& irt_net, + const size_t& isrc); void expand_edges(const LbRRGraph& lb_rr_graph, t_mode* mode, const LbRRNodeId& cur_inode, @@ -316,6 +317,7 @@ class LbRouter { const LbRRGraph& lb_rr_graph, const NetId& lb_net, t_expansion_node& exp_node, + const int& isrc, const int& itarget, const bool& try_other_modes, const int& verbosity); @@ -355,11 +357,26 @@ class LbRouter { private : /* Stores all data needed by intra-logic cluster_ctx.blocks router */ /* Logical Netlist Info */ - vtr::vector lb_net_ids_; /* Pointer to vector of intra logic cluster_ctx.blocks nets and their connections */ - vtr::vector lb_net_atom_net_ids_; /* index of atom net this intra_lb_net represents */ - vtr::vector> lb_net_atom_pins_; /* AtomPin's associated with each terminal */ - vtr::vector> lb_net_terminals_; /* endpoints of the intra_lb_net, 0th position is the source, all others are sinks */ - vtr::vector lb_net_rt_trees_; /* Route tree head */ + /* Pointer to vector of intra logic cluster_ctx.blocks nets and their connections */ + vtr::vector lb_net_ids_; + + /* index of atom net this intra_lb_net represents */ + vtr::vector lb_net_atom_net_ids_; + + /* AtomPin's associated with each source nodes */ + vtr::vector> lb_net_atom_source_pins_; + + /* AtomPin's associated with each sink nodes */ + vtr::vector> lb_net_atom_sink_pins_; + + /* starting points of the intra_lb_net */ + vtr::vector> lb_net_sources_; + + /* end points of the intra_lb_net */ + vtr::vector> lb_net_sinks_; + + /* Route tree head for each source of each net */ + vtr::vector> lb_net_rt_trees_; /* Logical-to-physical mapping info */ vtr::vector routing_status_; /* [0..lb_type_graph->size()-1] Stats for each logic cluster_ctx.blocks rr node instance */ From 8921905becaeba7b950279be9fd0897e4bf816e4 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 12 Mar 2020 19:21:13 -0600 Subject: [PATCH 304/645] annotate multiple-source and multiple-sink nets from pb to lb router --- openfpga/src/repack/lb_router.cpp | 7 +- openfpga/src/repack/lb_router.h | 3 +- openfpga/src/repack/lb_router_utils.cpp | 4 +- openfpga/src/repack/lb_router_utils.h | 2 +- openfpga/src/repack/repack.cpp | 104 ++++++++++++++++++++---- 5 files changed, 98 insertions(+), 22 deletions(-) diff --git a/openfpga/src/repack/lb_router.cpp b/openfpga/src/repack/lb_router.cpp index 1be68cc51..7cc6d080e 100644 --- a/openfpga/src/repack/lb_router.cpp +++ b/openfpga/src/repack/lb_router.cpp @@ -147,7 +147,8 @@ void LbRouter::rec_collect_trace_nodes(const t_trace* trace, std::vector& terminals) { +LbRouter::NetId LbRouter::create_net_to_route(const std::vector& sources, + const std::vector& terminals) { /* Create an new id */ NetId net = NetId(lb_net_ids_.size()); lb_net_ids_.push_back(net); @@ -157,9 +158,9 @@ LbRouter::NetId LbRouter::create_net_to_route(const LbRRNodeId& source, const st lb_net_atom_source_pins_.emplace_back(); lb_net_atom_sink_pins_.emplace_back(); - lb_net_sources_.push_back(std::vector(1, source)); + lb_net_sources_.push_back(sources); lb_net_sinks_.push_back(terminals); - lb_net_rt_trees_.push_back(std::vector(1, nullptr)); + lb_net_rt_trees_.push_back(std::vector(sources.size(), nullptr)); return net; } diff --git a/openfpga/src/repack/lb_router.h b/openfpga/src/repack/lb_router.h index 600ddc4a4..dcd0dc610 100644 --- a/openfpga/src/repack/lb_router.h +++ b/openfpga/src/repack/lb_router.h @@ -234,7 +234,8 @@ class LbRouter { /** * Add net to be routed */ - NetId create_net_to_route(const LbRRNodeId& source, const std::vector& terminals); + NetId create_net_to_route(const std::vector& sources, + const std::vector& terminals); void add_net_atom_net_id(const NetId& net, const AtomNetId& atom_net); void add_net_atom_pins(const NetId& net, const AtomPinId& src_pin, const std::vector& terminal_pins); diff --git a/openfpga/src/repack/lb_router_utils.cpp b/openfpga/src/repack/lb_router_utils.cpp index c106cd9f7..362ce4a76 100644 --- a/openfpga/src/repack/lb_router_utils.cpp +++ b/openfpga/src/repack/lb_router_utils.cpp @@ -17,13 +17,13 @@ namespace openfpga { ***************************************************************************************/ LbRouter::NetId add_lb_router_net_to_route(LbRouter& lb_router, const LbRRGraph& lb_rr_graph, - const LbRRNodeId& source_node, + const std::vector& source_nodes, const std::vector& sink_nodes, const AtomContext& atom_ctx, const AtomNetId& atom_net_id) { VTR_ASSERT(0 < sink_nodes.size()); - LbRouter::NetId lb_net = lb_router.create_net_to_route(source_node, sink_nodes); + LbRouter::NetId lb_net = lb_router.create_net_to_route(source_nodes, sink_nodes); VTR_ASSERT(AtomNetId::INVALID() != atom_net_id); lb_router.add_net_atom_net_id(lb_net, atom_net_id); diff --git a/openfpga/src/repack/lb_router_utils.h b/openfpga/src/repack/lb_router_utils.h index 129796e7d..0f8384859 100644 --- a/openfpga/src/repack/lb_router_utils.h +++ b/openfpga/src/repack/lb_router_utils.h @@ -18,7 +18,7 @@ namespace openfpga { LbRouter::NetId add_lb_router_net_to_route(LbRouter& lb_router, const LbRRGraph& lb_rr_graph, - const LbRRNodeId& source_node, + const std::vector& source_node, const std::vector& sink_nodes, const AtomContext& atom_ctx, const AtomNetId& atom_net_id); diff --git a/openfpga/src/repack/repack.cpp b/openfpga/src/repack/repack.cpp index 6f312c0b7..417cb6b95 100644 --- a/openfpga/src/repack/repack.cpp +++ b/openfpga/src/repack/repack.cpp @@ -86,16 +86,30 @@ std::vector find_lb_net_physical_sink_lb_rr_nodes(const LbRRGraph& l /* if this is the root node, the physical pin is its self */ if (nullptr == physical_sink_pin) { - VTR_LOG("Fail to find a physical pin for operating pin '%s'!\n", - sink_pin->to_string().c_str()); - } + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Fail to find a physical pin for operating pin '%s'!\n", + sink_pin->to_string().c_str()); + } VTR_ASSERT(nullptr != physical_sink_pin); + + /* Sink nodes should NOT be any output pin of primitive pb_graph_node, + * warn that we will not include it in the sink nodes + */ + if ( (true == is_primitive_pb_type(physical_sink_pin->parent_node->pb_type)) + && (OUT_PORT == physical_sink_pin->port->type)) { + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Sink pin '%s' should NOT be an output from a primitive pb_type!\n", + sink_pin->to_string().c_str()); + } + LbRRNodeId sink_lb_rr_node = lb_rr_graph.find_node(LB_INTERMEDIATE, physical_sink_pin); if (true != lb_rr_graph.valid_node_id(sink_lb_rr_node)) { - VTR_LOG("Try to find the lb_rr_node for pb_graph_pin '%s'\n", - physical_sink_pin->to_string().c_str()); + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Fail to find the lb_rr_node for pb_graph_pin '%s'\n", + physical_sink_pin->to_string().c_str()); } VTR_ASSERT(true == lb_rr_graph.valid_node_id(sink_lb_rr_node)); + sink_nodes.push_back(sink_lb_rr_node); } @@ -134,6 +148,12 @@ void add_lb_router_nets(LbRouter& lb_router, /* Build the fast look-up between pb_pin_id and pb_graph_pin pointer */ t_pb_graph_pin** pb_graph_pin_lookup_from_index = alloc_and_load_pb_graph_pin_lookup_from_index(lb_type); + /* Cache all the source nodes and sinks node for each net + * net_terminal[net][0] is the list of source nodes + * net_terminal[net][1] is the list of sink nodes + */ + std::map, 2>> net_terminals; + /* Find the source nodes for the nets mapped to inputs of a clustered block */ for (int j = 0; j < lb_type->pb_type->num_pins; j++) { /* Find the net mapped to this pin in clustering results*/ @@ -150,6 +170,7 @@ void add_lb_router_nets(LbRouter& lb_router, /* Get the source pb_graph pin and find the rr_node in logical block routing resource graph */ const t_pb_graph_pin* source_pb_pin = get_pb_graph_node_pin_from_block_pin(block_id, j); VTR_ASSERT(source_pb_pin->parent_node == pb->pb_graph_node); + /* Bypass output pins */ if (OUT_PORT == source_pb_pin->port->type) { continue; @@ -169,11 +190,26 @@ void add_lb_router_nets(LbRouter& lb_router, std::vector sink_lb_rr_nodes = find_lb_net_physical_sink_lb_rr_nodes(lb_rr_graph, sink_pb_graph_pins, device_annotation); VTR_ASSERT(sink_lb_rr_nodes.size() == sink_pb_graph_pins.size()); - /* Add the net */ - add_lb_router_net_to_route(lb_router, lb_rr_graph, - source_lb_rr_node, sink_lb_rr_nodes, - atom_ctx, atom_net_id); - net_counter++; + /* Cache the net */ + if (0 < net_terminals.count(atom_net_id)) { + if (net_terminals[atom_net_id][0].end() == std::find(net_terminals[atom_net_id][0].begin(), + net_terminals[atom_net_id][0].end(), + source_lb_rr_node)) { + net_terminals[atom_net_id][0].push_back(source_lb_rr_node); + } + + for (const LbRRNodeId& sink_lb_rr_node : sink_lb_rr_nodes) { + if (net_terminals[atom_net_id][1].end() == std::find(net_terminals[atom_net_id][1].begin(), + net_terminals[atom_net_id][1].end(), + sink_lb_rr_node)) { + net_terminals[atom_net_id][1].push_back(sink_lb_rr_node); + } + } + } else { + net_terminals[atom_net_id][0].push_back(source_lb_rr_node); + net_terminals[atom_net_id][1] = sink_lb_rr_nodes; + net_counter++; + } } /* Find the source nodes for the nets mapped to outputs of primitive pb_graph_node */ @@ -216,16 +252,54 @@ void add_lb_router_nets(LbRouter& lb_router, std::vector sink_lb_rr_nodes = find_lb_net_physical_sink_lb_rr_nodes(lb_rr_graph, sink_pb_graph_pins, device_annotation); VTR_ASSERT(sink_lb_rr_nodes.size() == sink_pb_graph_pins.size()); - /* Add the net */ - add_lb_router_net_to_route(lb_router, lb_rr_graph, - source_lb_rr_node, sink_lb_rr_nodes, - atom_ctx, atom_net_id); - net_counter++; + /* Cache the net */ + if (0 < net_terminals.count(atom_net_id)) { + if (net_terminals[atom_net_id][0].end() == std::find(net_terminals[atom_net_id][0].begin(), + net_terminals[atom_net_id][0].end(), + source_lb_rr_node)) { + net_terminals[atom_net_id][0].push_back(source_lb_rr_node); + } + + for (const LbRRNodeId& sink_lb_rr_node : sink_lb_rr_nodes) { + if (net_terminals[atom_net_id][1].end() == std::find(net_terminals[atom_net_id][1].begin(), + net_terminals[atom_net_id][1].end(), + sink_lb_rr_node)) { + net_terminals[atom_net_id][1].push_back(sink_lb_rr_node); + } + } + } else { + net_terminals[atom_net_id][0].push_back(source_lb_rr_node); + net_terminals[atom_net_id][1] = sink_lb_rr_nodes; + net_counter++; + } } /* Free */ free_pb_graph_pin_lookup_from_index(pb_graph_pin_lookup_from_index); + /* Add all the nets */ + for (std::pair, 2>> net_terminal_pair : net_terminals) { + const AtomNetId& atom_net_id = net_terminal_pair.first; + + /* MUST have >1 source nodes and >1 sinks nodes */ + if (0 == net_terminal_pair.second[0].size()) { + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Net '%s' has 0 source nodes!", + atom_ctx.nlist.net_name(atom_net_id).c_str()); + } + + if (0 == net_terminal_pair.second[1].size()) { + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Net '%s' has 0 sink nodes!", + atom_ctx.nlist.net_name(atom_net_id).c_str()); + } + + /* Add the net */ + add_lb_router_net_to_route(lb_router, lb_rr_graph, + net_terminal_pair.second[0], net_terminal_pair.second[1], + atom_ctx, atom_net_id); + } + VTR_LOGV(verbose, "Added %lu nets to be routed.\n", net_counter); From 29450f34726c0dc5e8593443ab6357e8fe3f7bd4 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 12 Mar 2020 20:42:41 -0600 Subject: [PATCH 305/645] debugging multi-source lb router --- openfpga/src/repack/lb_router.cpp | 104 ++++++++++-------- openfpga/src/repack/lb_router.h | 4 +- .../test_script/and_latch_k6_frac.openfpga | 4 +- 3 files changed, 61 insertions(+), 51 deletions(-) diff --git a/openfpga/src/repack/lb_router.cpp b/openfpga/src/repack/lb_router.cpp index 7cc6d080e..70b8e67b0 100644 --- a/openfpga/src/repack/lb_router.cpp +++ b/openfpga/src/repack/lb_router.cpp @@ -209,7 +209,7 @@ bool LbRouter::try_route_net(const LbRRGraph& lb_rr_graph, const NetId& net_idx, t_expansion_node& exp_node, std::unordered_map& mode_map, - const int& verbosity) { + const bool& verbosity) { std::vector sink_routed(lb_net_sinks_[net_idx].size(), false); @@ -226,18 +226,15 @@ bool LbRouter::try_route_net(const LbRRGraph& lb_rr_graph, /* Route each sink of net */ for (size_t isink = 0; isink < lb_net_sinks_[net_idx].size(); ++isink) { - /* Check if last sink failed in routing - * If failed, the routing is not possible for this net - */ - if (0 < isink) { - if (false == sink_routed[isink - 1]) { - break; - } + + /* Skip routed nets */ + if (true == sink_routed[isink]) { + continue; } pq_.clear(); - /* Get lowest cost next node, repeat until a path is found or if it is impossible to route */ + /* Get lowest cost next node, repeat until a path is found or if it is impossible to route */ expand_rt(net_idx, net_idx, isrc); /* If we managed to expand the nodes to the sink, routing for this sink is done. @@ -246,10 +243,17 @@ bool LbRouter::try_route_net(const LbRRGraph& lb_rr_graph, */ sink_routed[isink] = !try_expand_nodes(atom_nlist, lb_rr_graph, net_idx, exp_node, isrc, isink, mode_status_.expand_all_modes, verbosity); + if (true == sink_routed[isink]) { + VTR_LOGV(verbosity, + "Succeed to expand routing tree from source pin '%s' to sink pin '%s'!\n", + lb_rr_graph.node_pb_graph_pin(lb_net_sources_[net_idx][isrc])->to_string().c_str(), + lb_rr_graph.node_pb_graph_pin(lb_net_sinks_[net_idx][isink])->to_string().c_str()); + } + if (false == sink_routed[isink] && false == mode_status_.expand_all_modes) { mode_status_.try_expand_all_modes = true; mode_status_.expand_all_modes = true; - break; + continue; } if (exp_node.node_index == lb_net_sinks_[net_idx][isink]) { @@ -258,14 +262,25 @@ bool LbRouter::try_route_net(const LbRRGraph& lb_rr_graph, } if (false == sink_routed[isink]) { - VTR_LOG("Routing was impossible!\n"); + VTR_LOGV(verbosity, + "Routing was impossible from source pin '%s' to sink pin '%s'!\n", + lb_rr_graph.node_pb_graph_pin(lb_net_sources_[net_idx][isrc])->to_string().c_str(), + lb_rr_graph.node_pb_graph_pin(lb_net_sinks_[net_idx][isink])->to_string().c_str()); } else if (mode_status_.expand_all_modes) { sink_routed[isink] = !route_has_conflict(lb_rr_graph, lb_net_rt_trees_[net_idx][isrc]); if (false == sink_routed[isink]) { - VTR_LOG("Routing was impossible due to modes!\n"); + VTR_LOGV(verbosity, + "Routing was impossible due to modes!\n"); } } + if (true == sink_routed[isink]) { + VTR_LOGV(verbosity, + "Routing succeeded from source pin '%s' to sink pin '%s'!\n", + lb_rr_graph.node_pb_graph_pin(lb_net_sources_[net_idx][isrc])->to_string().c_str(), + lb_rr_graph.node_pb_graph_pin(lb_net_sinks_[net_idx][isink])->to_string().c_str()); + } + explore_id_index_++; if (explore_id_index_ > 2000000000) { /* overflow protection */ @@ -275,39 +290,40 @@ bool LbRouter::try_route_net(const LbRRGraph& lb_rr_graph, explore_id_index_ = 1; } } + + /* If routing succeed so far, we will try to save(commit) results + * to route tree. + * During this process, we will check if there is any + * nodes using different modes under the same pb_type + * If so, we have conflicts and routing is considered to be failure + */ + if (true == sink_routed[isink]) { + commit_remove_rt(lb_rr_graph, lb_net_rt_trees_[net_idx][isrc], RT_COMMIT, mode_map); + if (true == mode_status_.is_mode_conflict) { + sink_routed[isink] = false; + } + } } } /* Check the routing status for all the sinks */ bool route_succeed = true; - for (const bool& sink_status : sink_routed) { - if (false == sink_status) { + for (size_t isink = 0; isink < sink_routed.size(); ++isink) { + if (false == sink_routed[isink]) { route_succeed = false; + VTR_LOGV(verbosity, + "Routing failed for sink pin '%s'!\n", + lb_rr_graph.node_pb_graph_pin(lb_net_sinks_[net_idx][isink])->to_string().c_str()); break; } } - /* If routing succeed so far, we will try to save(commit) results - * to route tree. - * During this process, we will check if there is any - * nodes using different modes under the same pb_type - * If so, we have conflicts and routing is considered to be failure - */ - if (true == route_succeed) { - for (size_t isrc = 1; isrc < lb_net_sources_[net_idx].size(); ++isrc) { - commit_remove_rt(lb_rr_graph, lb_net_rt_trees_[net_idx][isrc], RT_COMMIT, mode_map); - if (true == mode_status_.is_mode_conflict) { - route_succeed = false; - } - } - } - return route_succeed; } bool LbRouter::try_route(const LbRRGraph& lb_rr_graph, const AtomNetlist& atom_nlist, - const int& verbosity) { + const bool& verbosity) { /* Validate if the rr_graph is the one we used to initialize the router */ VTR_ASSERT(true == matched_lb_rr_graph(lb_rr_graph)); @@ -351,27 +367,21 @@ bool LbRouter::try_route(const LbRRGraph& lb_rr_graph, is_routed_ = is_route_success(lb_rr_graph); } else { --inet; - VTR_LOGV(verbosity < 3, - "Net %lu '%s' is impossible to route within proposed %s cluster\n", - inet, - atom_nlist.net_name(lb_net_atom_net_ids_[NetId(inet)]).c_str(), - lb_type_->name); - VTR_LOGV(verbosity < 3, - "\tNet source pin:\n"); + VTR_LOG("Net %lu '%s' is impossible to route within proposed %s cluster\n", + inet, + atom_nlist.net_name(lb_net_atom_net_ids_[NetId(inet)]).c_str(), + lb_type_->name); + VTR_LOG("\tNet source pin:\n"); for (size_t isrc = 0; isrc < lb_net_sources_[NetId(inet)].size(); ++isrc) { - VTR_LOGV(verbosity < 3, - "\t\t%s\n", - lb_rr_graph.node_pb_graph_pin(lb_net_sources_[NetId(inet)][isrc])->to_string().c_str()); + VTR_LOG("\t\t%s\n", + lb_rr_graph.node_pb_graph_pin(lb_net_sources_[NetId(inet)][isrc])->to_string().c_str()); } - VTR_LOGV(verbosity < 3, - "\tNet sink pins:\n"); + VTR_LOG("\tNet sink pins:\n"); for (size_t isink = 0; isink < lb_net_sinks_[NetId(inet)].size(); ++isink) { - VTR_LOGV(verbosity < 3, - "\t\t%s\n", - lb_rr_graph.node_pb_graph_pin(lb_net_sinks_[NetId(inet)][isink])->to_string().c_str()); + VTR_LOG("\t\t%s\n", + lb_rr_graph.node_pb_graph_pin(lb_net_sinks_[NetId(inet)][isink])->to_string().c_str()); } - VTR_LOGV(verbosity < 3, - "Please check your architecture XML to see if it is routable\n"); + VTR_LOG("Please check your architecture XML to see if it is routable\n"); is_routed_ = false; } diff --git a/openfpga/src/repack/lb_router.h b/openfpga/src/repack/lb_router.h index dcd0dc610..d865c75b9 100644 --- a/openfpga/src/repack/lb_router.h +++ b/openfpga/src/repack/lb_router.h @@ -256,7 +256,7 @@ class LbRouter { */ bool try_route(const LbRRGraph& lb_rr_graph, const AtomNetlist& atom_nlist, - const int& verbosity); + const bool& verbosity); private : /* Private accessors */ /** @@ -328,7 +328,7 @@ class LbRouter { const NetId& net_idx, t_expansion_node& exp_node, std::unordered_map& mode_map, - const int& verbosity); + const bool& verbosity); private : /* Private validators */ /** diff --git a/openfpga/test_script/and_latch_k6_frac.openfpga b/openfpga/test_script/and_latch_k6_frac.openfpga index 210bdcf28..b41ac4450 100644 --- a/openfpga/test_script/and_latch_k6_frac.openfpga +++ b/openfpga/test_script/and_latch_k6_frac.openfpga @@ -27,7 +27,7 @@ build_fabric --compress_routing --duplicate_grid_pin #--verbose # Repack the netlist to physical pbs # This must be done before bitstream generator and testbench generation # Strongly recommend it is done after all the fix-up have been applied -repack #--verbose +repack --verbose # Build the bitstream # - Output the fabric-independent bitstream to a file @@ -46,7 +46,7 @@ write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src/SRC --explicit_port # - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA # - Enable pre-configured top-level testbench which is a fast verification skipping programming phase # - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts -write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src/SRC --reference_benchmark_file_path /var/tmp/xtang/and.v --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini /var/tmp/xtang/openfpga_test_src/simulation_deck.ini +write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src/SRC --reference_benchmark_file_path /var/tmp/xtang/and_latch.v --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini /var/tmp/xtang/openfpga_test_src/simulation_deck.ini # Write the SDC files for PnR backend # - Turn on every options here From f90dc5c2969d99933fa2af2e2bfafbbb43f11812 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 12 Mar 2020 20:44:07 -0600 Subject: [PATCH 306/645] remove redundant XML codes --- openfpga/test_vpr_arch/k6_N10_40nm.xml | 9 --------- openfpga/test_vpr_arch/k6_frac_N10_40nm.xml | 9 --------- .../k6_frac_N10_frac_chain_depop50_mem32K_40nm.xml | 9 --------- 3 files changed, 27 deletions(-) diff --git a/openfpga/test_vpr_arch/k6_N10_40nm.xml b/openfpga/test_vpr_arch/k6_N10_40nm.xml index d2501969d..bbd694a79 100644 --- a/openfpga/test_vpr_arch/k6_N10_40nm.xml +++ b/openfpga/test_vpr_arch/k6_N10_40nm.xml @@ -296,13 +296,4 @@ - - - - - - - - - diff --git a/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml index f5a4b31fa..51f6824f9 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml @@ -435,13 +435,4 @@ - - - - - - - - - diff --git a/openfpga/test_vpr_arch/k6_frac_N10_frac_chain_depop50_mem32K_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_frac_chain_depop50_mem32K_40nm.xml index 4594c6496..af4612b61 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_frac_chain_depop50_mem32K_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_frac_chain_depop50_mem32K_40nm.xml @@ -1426,13 +1426,4 @@ - - - - - - - - - From 773e6da308f66252414a633cb95475181692ebc2 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 12 Mar 2020 22:53:17 -0600 Subject: [PATCH 307/645] Spot a bug in lb router where path finder fail to use low-occupancy node when expanding the tree --- openfpga/src/repack/lb_router.cpp | 89 ++++++++++++++++++++++++++----- openfpga/src/repack/lb_router.h | 2 +- openfpga/test_blif/and_latch.act | 1 + openfpga/test_blif/and_latch.blif | 5 +- 4 files changed, 81 insertions(+), 16 deletions(-) diff --git a/openfpga/src/repack/lb_router.cpp b/openfpga/src/repack/lb_router.cpp index 70b8e67b0..863be81ac 100644 --- a/openfpga/src/repack/lb_router.cpp +++ b/openfpga/src/repack/lb_router.cpp @@ -94,6 +94,11 @@ bool LbRouter::is_route_success(const LbRRGraph& lb_rr_graph) const { for (const LbRRNodeId& inode : lb_rr_graph.nodes()) { if (routing_status_[inode].occ > lb_rr_graph.node_capacity(inode)) { + VTR_LOGV(lb_rr_graph.node_pb_graph_pin(inode), + "Route failed due to overuse pin '%s': occupancy '%ld' > capacity '%ld'!\n", + lb_rr_graph.node_pb_graph_pin(inode)->to_string().c_str(), + routing_status_[inode].occ, + lb_rr_graph.node_capacity(inode)); return false; } } @@ -243,18 +248,22 @@ bool LbRouter::try_route_net(const LbRRGraph& lb_rr_graph, */ sink_routed[isink] = !try_expand_nodes(atom_nlist, lb_rr_graph, net_idx, exp_node, isrc, isink, mode_status_.expand_all_modes, verbosity); + /* TODO: Debug codes, to be removed if (true == sink_routed[isink]) { VTR_LOGV(verbosity, "Succeed to expand routing tree from source pin '%s' to sink pin '%s'!\n", lb_rr_graph.node_pb_graph_pin(lb_net_sources_[net_idx][isrc])->to_string().c_str(), lb_rr_graph.node_pb_graph_pin(lb_net_sinks_[net_idx][isink])->to_string().c_str()); } + */ + /* IMPORTANT: We do not need expand all the modes for physical repack if (false == sink_routed[isink] && false == mode_status_.expand_all_modes) { mode_status_.try_expand_all_modes = true; mode_status_.expand_all_modes = true; continue; } + */ if (exp_node.node_index == lb_net_sinks_[net_idx][isink]) { /* Net terminal is routed, add this to the route tree, clear data structures, and keep going */ @@ -274,33 +283,60 @@ bool LbRouter::try_route_net(const LbRRGraph& lb_rr_graph, } } + /* if (true == sink_routed[isink]) { VTR_LOGV(verbosity, "Routing succeeded from source pin '%s' to sink pin '%s'!\n", lb_rr_graph.node_pb_graph_pin(lb_net_sources_[net_idx][isrc])->to_string().c_str(), lb_rr_graph.node_pb_graph_pin(lb_net_sinks_[net_idx][isink])->to_string().c_str()); } + */ - explore_id_index_++; - if (explore_id_index_ > 2000000000) { - /* overflow protection */ + /* Increment explored node indices only when routing is successful */ + if (true == sink_routed[isink]) { + explore_id_index_++; + if (explore_id_index_ > 2000000000) { + /* overflow protection */ + for (const LbRRNodeId& id : lb_rr_graph.nodes()) { + explored_node_tb_[id].explored_id = OPEN; + explored_node_tb_[id].enqueue_id = OPEN; + explore_id_index_ = 1; + } + } + } else { + /* Route failed, reset the explore id index */ + reset_explored_node_tb(); for (const LbRRNodeId& id : lb_rr_graph.nodes()) { explored_node_tb_[id].explored_id = OPEN; explored_node_tb_[id].enqueue_id = OPEN; explore_id_index_ = 1; } } + } - /* If routing succeed so far, we will try to save(commit) results - * to route tree. - * During this process, we will check if there is any - * nodes using different modes under the same pb_type - * If so, we have conflicts and routing is considered to be failure - */ + /* If any sinks are managed to be routed, we will try to save(commit) results + * to route tree. + * During this process, we will check if there is any + * nodes using different modes under the same pb_type + * If so, we have conflicts and routing is considered to be failure + */ + bool any_sink_routed = false; + for (size_t isink = 0; isink < sink_routed.size(); ++isink) { if (true == sink_routed[isink]) { - commit_remove_rt(lb_rr_graph, lb_net_rt_trees_[net_idx][isrc], RT_COMMIT, mode_map); - if (true == mode_status_.is_mode_conflict) { - sink_routed[isink] = false; + any_sink_routed = true; + break; + } + } + if (true == any_sink_routed) { + commit_remove_rt(lb_rr_graph, lb_net_rt_trees_[net_idx][isrc], RT_COMMIT, mode_map); + if (true == mode_status_.is_mode_conflict) { + VTR_LOGV(verbosity, + "Route fail due to mode conflicts when commiting the routing tree!\n"); + for (size_t isink = 0; isink < sink_routed.size(); ++isink) { + /* Change routed sinks to failure */ + if (true == sink_routed[isink]) { + sink_routed[isink] = false; + } } } } @@ -526,6 +562,11 @@ void LbRouter::commit_remove_rt(const LbRRGraph& lb_rr_graph, explored_node_tb_[inode].inet = NetId::INVALID(); } + if (op == RT_COMMIT) { + VTR_LOGV(lb_rr_graph.node_pb_graph_pin(inode), + "Commit node '%s' to routing tree\n", + lb_rr_graph.node_pb_graph_pin(inode)->to_string().c_str()); + } routing_status_[inode].occ += incr; VTR_ASSERT(routing_status_[inode].occ >= 0); @@ -702,11 +743,23 @@ void LbRouter::expand_edges(const LbRRGraph& lb_rr_graph, if (explored_node_tb_[enode.node_index].enqueue_id == explore_id_index_) { if (enode.cost < explored_node_tb_[enode.node_index].enqueue_cost) { pq_.push(enode); + /* + if (nullptr != lb_rr_graph.node_pb_graph_pin(enode.node_index)) { + VTR_LOG("Added node '%s' to priority queue\n", + lb_rr_graph.node_pb_graph_pin(enode.node_index)->to_string().c_str()); + } + */ } } else { explored_node_tb_[enode.node_index].enqueue_id = explore_id_index_; explored_node_tb_[enode.node_index].enqueue_cost = enode.cost; pq_.push(enode); + /* + if (nullptr != lb_rr_graph.node_pb_graph_pin(enode.node_index)) { + VTR_LOG("Added node '%s' to priority queue\n", + lb_rr_graph.node_pb_graph_pin(enode.node_index)->to_string().c_str()); + } + */ } } } @@ -732,6 +785,14 @@ void LbRouter::expand_node(const LbRRGraph& lb_rr_graph, } } + /* + if (nullptr != mode) { + VTR_LOGV(lb_rr_graph.node_pb_graph_pin(cur_node), + "Expand node '%s' by considering mode '%s'\n", + lb_rr_graph.node_pb_graph_pin(cur_node)->to_string().c_str(), + mode->name); + } + */ expand_edges(lb_rr_graph, mode, cur_node, cur_cost, net_fanout); } @@ -782,7 +843,7 @@ bool LbRouter::try_expand_nodes(const AtomNetlist& atom_nlist, const int& isrc, const int& itarget, const bool& try_other_modes, - const int& verbosity) { + const bool& verbosity) { bool is_impossible = false; do { @@ -790,7 +851,7 @@ bool LbRouter::try_expand_nodes(const AtomNetlist& atom_nlist, /* No connection possible */ is_impossible = true; - if (verbosity > 3) { + if (true == verbosity) { //Print detailed debug info AtomNetId net_id = lb_net_atom_net_ids_[lb_net]; AtomPinId driver_pin = lb_net_atom_source_pins_[lb_net][isrc]; diff --git a/openfpga/src/repack/lb_router.h b/openfpga/src/repack/lb_router.h index d865c75b9..71020d1b4 100644 --- a/openfpga/src/repack/lb_router.h +++ b/openfpga/src/repack/lb_router.h @@ -321,7 +321,7 @@ class LbRouter { const int& isrc, const int& itarget, const bool& try_other_modes, - const int& verbosity); + const bool& verbosity); bool try_route_net(const LbRRGraph& lb_rr_graph, const AtomNetlist& atom_nlist, diff --git a/openfpga/test_blif/and_latch.act b/openfpga/test_blif/and_latch.act index 8089a8969..61bbe1fe8 100644 --- a/openfpga/test_blif/and_latch.act +++ b/openfpga/test_blif/and_latch.act @@ -3,3 +3,4 @@ b 0.502000 0.197200 clk 0.500000 2.000000 d 0.240200 0.171200 c 0.240200 0.044100 +n1 0.240200 0.044100 diff --git a/openfpga/test_blif/and_latch.blif b/openfpga/test_blif/and_latch.blif index a1925fffa..dbd863d9c 100644 --- a/openfpga/test_blif/and_latch.blif +++ b/openfpga/test_blif/and_latch.blif @@ -3,9 +3,12 @@ .inputs a b clk .outputs c d -.latch c d re clk 0 +.latch n1 d re clk 0 .names a b c 11 1 +.names c n1 +1 1 + .end From 81e5af464e7fddba3f6f6e6c1ab79cecc72c0afb Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 12 Mar 2020 23:58:56 -0600 Subject: [PATCH 308/645] improve lb_route to avoid routing combinational loops --- openfpga/src/repack/lb_router.cpp | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/openfpga/src/repack/lb_router.cpp b/openfpga/src/repack/lb_router.cpp index 863be81ac..e37c7fcdc 100644 --- a/openfpga/src/repack/lb_router.cpp +++ b/openfpga/src/repack/lb_router.cpp @@ -216,12 +216,24 @@ bool LbRouter::try_route_net(const LbRRGraph& lb_rr_graph, std::unordered_map& mode_map, const bool& verbosity) { + /* Quick check: if all the net can be skipped, we return route succeed */ + bool skip_route = true; + for (size_t isrc = 0; isrc < lb_net_sources_[net_idx].size(); ++isrc) { + if (false == is_skip_route_net(lb_rr_graph, lb_net_rt_trees_[net_idx][isrc])) { + skip_route = false; + break; + } + } + if (true == skip_route) { + return true; + } + std::vector sink_routed(lb_net_sinks_[net_idx].size(), false); for (size_t isrc = 0; isrc < lb_net_sources_[net_idx].size(); ++isrc) { if (true == is_skip_route_net(lb_rr_graph, lb_net_rt_trees_[net_idx][isrc])) { - return true; + continue; } commit_remove_rt(lb_rr_graph, lb_net_rt_trees_[net_idx][isrc], RT_REMOVE, mode_map); @@ -232,6 +244,17 @@ bool LbRouter::try_route_net(const LbRRGraph& lb_rr_graph, /* Route each sink of net */ for (size_t isink = 0; isink < lb_net_sinks_[net_idx].size(); ++isink) { + /* Do not route the sink if it share the same pb_type as source + * This is actually forbidden! This will definitely create a combinational loop + */ + if ( (nullptr != lb_rr_graph.node_pb_graph_pin(lb_net_sinks_[net_idx][isink])) + && (nullptr != lb_rr_graph.node_pb_graph_pin(lb_net_sources_[net_idx][isrc])) ) { + if (lb_rr_graph.node_pb_graph_pin(lb_net_sinks_[net_idx][isink])->parent_node + == lb_rr_graph.node_pb_graph_pin(lb_net_sources_[net_idx][isrc])->parent_node) { + continue; + } + } + /* Skip routed nets */ if (true == sink_routed[isink]) { continue; From 808853db0bd923c39b83cc3a7340a5de22d70509 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 13 Mar 2020 12:26:37 -0600 Subject: [PATCH 309/645] critical bug fixed for find proper pb_route traceback --- openfpga/src/repack/lb_router.cpp | 16 --- openfpga/src/repack/repack.cpp | 226 +++++++++++++++++++----------- 2 files changed, 142 insertions(+), 100 deletions(-) diff --git a/openfpga/src/repack/lb_router.cpp b/openfpga/src/repack/lb_router.cpp index e37c7fcdc..f7be853b1 100644 --- a/openfpga/src/repack/lb_router.cpp +++ b/openfpga/src/repack/lb_router.cpp @@ -244,17 +244,6 @@ bool LbRouter::try_route_net(const LbRRGraph& lb_rr_graph, /* Route each sink of net */ for (size_t isink = 0; isink < lb_net_sinks_[net_idx].size(); ++isink) { - /* Do not route the sink if it share the same pb_type as source - * This is actually forbidden! This will definitely create a combinational loop - */ - if ( (nullptr != lb_rr_graph.node_pb_graph_pin(lb_net_sinks_[net_idx][isink])) - && (nullptr != lb_rr_graph.node_pb_graph_pin(lb_net_sources_[net_idx][isrc])) ) { - if (lb_rr_graph.node_pb_graph_pin(lb_net_sinks_[net_idx][isink])->parent_node - == lb_rr_graph.node_pb_graph_pin(lb_net_sources_[net_idx][isrc])->parent_node) { - continue; - } - } - /* Skip routed nets */ if (true == sink_routed[isink]) { continue; @@ -585,11 +574,6 @@ void LbRouter::commit_remove_rt(const LbRRGraph& lb_rr_graph, explored_node_tb_[inode].inet = NetId::INVALID(); } - if (op == RT_COMMIT) { - VTR_LOGV(lb_rr_graph.node_pb_graph_pin(inode), - "Commit node '%s' to routing tree\n", - lb_rr_graph.node_pb_graph_pin(inode)->to_string().c_str()); - } routing_status_[inode].occ += incr; VTR_ASSERT(routing_status_[inode].occ >= 0); diff --git a/openfpga/src/repack/repack.cpp b/openfpga/src/repack/repack.cpp index 417cb6b95..2d0b2c3e8 100644 --- a/openfpga/src/repack/repack.cpp +++ b/openfpga/src/repack/repack.cpp @@ -21,15 +21,100 @@ namespace openfpga { /*************************************************************************************** - * Try find the pin id which is mapped to a given atom net id in the context of pb route + * Try find all the sink pins which is mapped to a routing trace in the context of pb route + * This function uses a recursive walk-through over the pb_route + * We will always start from the pb_route of the source pin + * For each sink, + * - if it is the end point of a routing tree, we add it to the sink list + * - An output of top-level pb_graph_node + * - An input of a primitive pb_graph_node + * - if it is not the end point of a routing tree, we visit the pb_route + * corresponds to the sink pin + * + * Note: when you call this function at the top-level, please provide an empty vector + * of sink_pb_pins!!! + ***************************************************************************************/ +static +void rec_find_routed_sink_pb_graph_pins(const t_pb* pb, + const t_pb_graph_pin* source_pb_pin, + const AtomNetId& atom_net_id, + t_pb_graph_pin** pb_graph_pin_lookup_from_index, + std::vector& sink_pb_pins) { + + /* Bypass unused pins */ + if (0 == pb->pb_route.count(source_pb_pin->pin_count_in_cluster)) { + return; + } + + /* Get the driver pb pin id, it must be valid */ + if (atom_net_id != pb->pb_route[source_pb_pin->pin_count_in_cluster].atom_net_id) { + return; + } + + /* Check each sink nodes, if pin belongs to an input of a primitive pb_graph_node, it is what we want */ + std::vector sink_pb_pins_to_search; + for (const int& sink_pb_pin_id : pb->pb_route[source_pb_pin->pin_count_in_cluster].sink_pb_pin_ids) { + t_pb_graph_pin* sink_pb_pin = pb_graph_pin_lookup_from_index[sink_pb_pin_id]; + VTR_ASSERT(nullptr != sink_pb_pin); + + /* We will update sink node list only + * - input pins of primitive nodes + * - output pins of top node + */ + if ( (true == is_primitive_pb_type(sink_pb_pin->parent_node->pb_type)) + && (IN_PORT == sink_pb_pin->port->type)) { + sink_pb_pins.push_back(sink_pb_pin); + continue; + } + + if ( (true == sink_pb_pin->parent_node->is_root()) + && (OUT_PORT == sink_pb_pin->port->type)) { + sink_pb_pins.push_back(sink_pb_pin); + continue; + } + + /* We should find the pb_route recursively */ + sink_pb_pins_to_search.push_back(sink_pb_pin); + } + + for (t_pb_graph_pin* sink_pb_pin : sink_pb_pins_to_search) { + rec_find_routed_sink_pb_graph_pins(pb, sink_pb_pin, atom_net_id, pb_graph_pin_lookup_from_index, sink_pb_pins); + } +} + +/*************************************************************************************** + * A wrapper for the recursive function rec_find_route_sink_pb_graph_pins(), + * we ensure that we provide a clear sink node lists ***************************************************************************************/ static std::vector find_routed_pb_graph_pins_atom_net(const t_pb* pb, + const t_pb_graph_pin* source_pb_pin, const AtomNetId& atom_net_id, t_pb_graph_pin** pb_graph_pin_lookup_from_index) { std::vector sink_pb_pins; - /* Find the sink nodes from top-level node */ + rec_find_routed_sink_pb_graph_pins(pb, source_pb_pin, atom_net_id, pb_graph_pin_lookup_from_index, sink_pb_pins); + + return sink_pb_pins; +} + +/*************************************************************************************** + * This function will find the actual source_pb_pin that is mapped by packed in the pb route + * As the inputs of clustered block may be renamed during routing, + * our pb_route results may lose consistency. + * It is possible that the source pb_pin may not be mapped during packing but + * be mapped during routing + * + * Note: this is ONLY applicable to the pb_pin of top-level pb_graph_node + ***************************************************************************************/ +static +int find_pb_route_remapped_source_pb_pin(const t_pb* pb, + const t_pb_graph_pin* source_pb_pin, + const AtomNetId& atom_net_id) { + VTR_ASSERT(true == source_pb_pin->parent_node->is_root()); + + std::vector pb_route_indices; + for (int pin = 0; pin < pb->pb_graph_node->total_pb_pins; ++pin) { /* Bypass unused pins */ if ((0 == pb->pb_route.count(pin)) || (AtomNetId::INVALID() == pb->pb_route[pin].atom_net_id)) { @@ -39,27 +124,15 @@ std::vector find_routed_pb_graph_pins_atom_net(const t_pb* pb, if (atom_net_id != pb->pb_route[pin].atom_net_id) { continue; } - /* Check each sink nodes, if pin belongs to an input of a primitive pb_graph_node, it is what we want */ - for (const int& sink_pb_pin_id : pb->pb_route[pin].sink_pb_pin_ids) { - t_pb_graph_pin* sink_pb_pin = pb_graph_pin_lookup_from_index[sink_pb_pin_id]; - VTR_ASSERT(nullptr != sink_pb_pin); - /* We care only - * - input pins of primitive nodes - * - output pins of top node - */ - if ( (true == is_primitive_pb_type(sink_pb_pin->parent_node->pb_type)) - && (IN_PORT == sink_pb_pin->port->type)) { - sink_pb_pins.push_back(sink_pb_pin); - } - - if ( (true == sink_pb_pin->parent_node->is_root()) - && (OUT_PORT == sink_pb_pin->port->type)) { - sink_pb_pins.push_back(sink_pb_pin); - } - } + /* Only care the pin that shares the same parent_node as source_pb_pin */ + if (source_pb_pin->parent_node == pb->pb_route[pin].pb_graph_pin->parent_node) { + pb_route_indices.push_back(pin); + } } - return sink_pb_pins; + VTR_ASSERT(1 == pb_route_indices.size()); + + return pb_route_indices[0]; } /*************************************************************************************** @@ -154,6 +227,9 @@ void add_lb_router_nets(LbRouter& lb_router, */ std::map, 2>> net_terminals; + /* A list showing that some sinks should be touched by some sources in a multi-source net */ + std::map>> invisible_sinks; + /* Find the source nodes for the nets mapped to inputs of a clustered block */ for (int j = 0; j < lb_type->pb_type->num_pins; j++) { /* Find the net mapped to this pin in clustering results*/ @@ -185,31 +261,36 @@ void add_lb_router_nets(LbRouter& lb_router, AtomNetId atom_net_id = atom_ctx.lookup.atom_net(cluster_net_id); VTR_ASSERT(AtomNetId::INVALID() != atom_net_id); + + int pb_route_index = find_pb_route_remapped_source_pb_pin(pb, source_pb_pin, atom_net_id); + t_pb_graph_pin* packing_source_pb_pin = get_pb_graph_node_pin_from_block_pin(block_id, pb_route_index); + VTR_ASSERT(nullptr != packing_source_pb_pin); + /* Find all the sink pins in the pb_route, we walk through the input pins and find the pin */ - std::vector sink_pb_graph_pins = find_routed_pb_graph_pins_atom_net(pb, atom_net_id, pb_graph_pin_lookup_from_index); + std::vector sink_pb_graph_pins = find_routed_pb_graph_pins_atom_net(pb, packing_source_pb_pin, atom_net_id, pb_graph_pin_lookup_from_index); std::vector sink_lb_rr_nodes = find_lb_net_physical_sink_lb_rr_nodes(lb_rr_graph, sink_pb_graph_pins, device_annotation); VTR_ASSERT(sink_lb_rr_nodes.size() == sink_pb_graph_pins.size()); - /* Cache the net */ - if (0 < net_terminals.count(atom_net_id)) { - if (net_terminals[atom_net_id][0].end() == std::find(net_terminals[atom_net_id][0].begin(), - net_terminals[atom_net_id][0].end(), - source_lb_rr_node)) { - net_terminals[atom_net_id][0].push_back(source_lb_rr_node); - } - - for (const LbRRNodeId& sink_lb_rr_node : sink_lb_rr_nodes) { - if (net_terminals[atom_net_id][1].end() == std::find(net_terminals[atom_net_id][1].begin(), - net_terminals[atom_net_id][1].end(), - sink_lb_rr_node)) { - net_terminals[atom_net_id][1].push_back(sink_lb_rr_node); - } - } - } else { - net_terminals[atom_net_id][0].push_back(source_lb_rr_node); - net_terminals[atom_net_id][1] = sink_lb_rr_nodes; - net_counter++; + /* Printf for debugging only, may be enabled if verbose is enabled + VTR_LOG("Pb route for Net %s:\n", + atom_ctx.nlist.net_name(atom_net_id).c_str()); + VTR_LOG("Source node:\n\t%s -> %s\n", + source_pb_pin->to_string().c_str(), + source_pb_pin->to_string().c_str()); + VTR_LOG("Sink nodes:\n"); + for (t_pb_graph_pin* sink_pb_pin : sink_pb_graph_pins) { + VTR_LOG("\t%s\n", + sink_pb_pin->to_string().c_str()); } + */ + + /* Add the net */ + add_lb_router_net_to_route(lb_router, lb_rr_graph, + std::vector(1, source_lb_rr_node), + sink_lb_rr_nodes, + atom_ctx, atom_net_id); + + net_counter++; } /* Find the source nodes for the nets mapped to outputs of primitive pb_graph_node */ @@ -248,58 +329,35 @@ void add_lb_router_nets(LbRouter& lb_router, VTR_ASSERT(AtomNetId::INVALID() != atom_net_id); /* Find all the sink pins in the pb_route */ - std::vector sink_pb_graph_pins = find_routed_pb_graph_pins_atom_net(pb, atom_net_id, pb_graph_pin_lookup_from_index); + std::vector sink_pb_graph_pins = find_routed_pb_graph_pins_atom_net(pb, source_pb_pin, atom_net_id, pb_graph_pin_lookup_from_index); + std::vector sink_lb_rr_nodes = find_lb_net_physical_sink_lb_rr_nodes(lb_rr_graph, sink_pb_graph_pins, device_annotation); VTR_ASSERT(sink_lb_rr_nodes.size() == sink_pb_graph_pins.size()); - /* Cache the net */ - if (0 < net_terminals.count(atom_net_id)) { - if (net_terminals[atom_net_id][0].end() == std::find(net_terminals[atom_net_id][0].begin(), - net_terminals[atom_net_id][0].end(), - source_lb_rr_node)) { - net_terminals[atom_net_id][0].push_back(source_lb_rr_node); - } - - for (const LbRRNodeId& sink_lb_rr_node : sink_lb_rr_nodes) { - if (net_terminals[atom_net_id][1].end() == std::find(net_terminals[atom_net_id][1].begin(), - net_terminals[atom_net_id][1].end(), - sink_lb_rr_node)) { - net_terminals[atom_net_id][1].push_back(sink_lb_rr_node); - } - } - } else { - net_terminals[atom_net_id][0].push_back(source_lb_rr_node); - net_terminals[atom_net_id][1] = sink_lb_rr_nodes; - net_counter++; + /* Printf for debugging only, may be enabled if verbose is enabled + VTR_LOG("Pb route for Net %s:\n", + atom_ctx.nlist.net_name(atom_net_id).c_str()); + VTR_LOG("Source node:\n\t%s -> %s\n", + source_pb_pin->to_string().c_str(), + physical_source_pb_pin->to_string().c_str()); + VTR_LOG("Sink nodes:\n"); + for (t_pb_graph_pin* sink_pb_pin : sink_pb_graph_pins) { + VTR_LOG("\t%s\n", + sink_pb_pin->to_string().c_str()); } + */ + + /* Add the net */ + add_lb_router_net_to_route(lb_router, lb_rr_graph, + std::vector(1, source_lb_rr_node), + sink_lb_rr_nodes, + atom_ctx, atom_net_id); + net_counter++; } /* Free */ free_pb_graph_pin_lookup_from_index(pb_graph_pin_lookup_from_index); - /* Add all the nets */ - for (std::pair, 2>> net_terminal_pair : net_terminals) { - const AtomNetId& atom_net_id = net_terminal_pair.first; - - /* MUST have >1 source nodes and >1 sinks nodes */ - if (0 == net_terminal_pair.second[0].size()) { - VTR_LOGF_ERROR(__FILE__, __LINE__, - "Net '%s' has 0 source nodes!", - atom_ctx.nlist.net_name(atom_net_id).c_str()); - } - - if (0 == net_terminal_pair.second[1].size()) { - VTR_LOGF_ERROR(__FILE__, __LINE__, - "Net '%s' has 0 sink nodes!", - atom_ctx.nlist.net_name(atom_net_id).c_str()); - } - - /* Add the net */ - add_lb_router_net_to_route(lb_router, lb_rr_graph, - net_terminal_pair.second[0], net_terminal_pair.second[1], - atom_ctx, atom_net_id); - } - VTR_LOGV(verbose, "Added %lu nets to be routed.\n", net_counter); From 3647548526bdcdbe72197d04ed78be415cbf7eb2 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 20 Mar 2020 11:07:45 -0600 Subject: [PATCH 310/645] clean up on the shell echo commands --- libopenfpga/libopenfpgashell/src/shell.tpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/libopenfpga/libopenfpgashell/src/shell.tpp b/libopenfpga/libopenfpgashell/src/shell.tpp index 5bd4ec56c..24ef13dea 100644 --- a/libopenfpga/libopenfpgashell/src/shell.tpp +++ b/libopenfpga/libopenfpgashell/src/shell.tpp @@ -320,19 +320,11 @@ void Shell::print_commands() const { /* Print the class name */ VTR_LOG("%s:\n", command_class_names_[cmd_class].c_str()); - size_t cnt = 0; for (const ShellCommandId& cmd : commands_by_classes_[cmd_class]) { /* Print the command names in this class * but limited4 command per line for a clean layout */ - VTR_LOG("%s", commands_[cmd].name().c_str()); - cnt++; - if (4 == cnt) { - VTR_LOG("\n"); - cnt = 0; - } else { - VTR_LOG("\t"); - } + VTR_LOG("\t%s\n", commands_[cmd].name().c_str()); } /* Put a new line in the end as a splitter */ From 8d57808d07029fb7955a868314aa211ae106e53f Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 20 Mar 2020 11:08:55 -0600 Subject: [PATCH 311/645] add missing files for micro benchmarks --- openfpga/test_blif/and.act | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 openfpga/test_blif/and.act diff --git a/openfpga/test_blif/and.act b/openfpga/test_blif/and.act new file mode 100644 index 000000000..0f77bc6b3 --- /dev/null +++ b/openfpga/test_blif/and.act @@ -0,0 +1,3 @@ +a 0.5 0.5 +b 0.5 0.5 +c 0.25 0.25 From a0b150f12e6c6682bb65bd8b275bca6659fa9e53 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 20 Mar 2020 14:18:59 -0600 Subject: [PATCH 312/645] adding micro architecture using adder chain --- libs/libarchfpga/src/read_xml_arch_file.cpp | 25 +- .../and_k6_frac_adder_chain.openfpga | 59 ++ openfpga/test_vpr_arch/k6_frac_N10_40nm.xml | 3 +- .../k6_frac_N10_adder_chain_40nm.xml | 630 ++++++++++++++++++ 4 files changed, 706 insertions(+), 11 deletions(-) create mode 100644 openfpga/test_script/and_k6_frac_adder_chain.openfpga create mode 100644 openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml diff --git a/libs/libarchfpga/src/read_xml_arch_file.cpp b/libs/libarchfpga/src/read_xml_arch_file.cpp index 1c2bba875..b3b4004b2 100644 --- a/libs/libarchfpga/src/read_xml_arch_file.cpp +++ b/libs/libarchfpga/src/read_xml_arch_file.cpp @@ -2900,21 +2900,26 @@ static void ProcessDevice(pugi::xml_node Node, t_arch* arch, t_default_fc_spec& "Unknown property %s for switch block type x\n", Prop); } - Prop = get_attribute(Cur, "sub_type", loc_data, BoolToReqOpt(false)).value(); - if (strcmp(Prop, "wilton") == 0) { - arch->SBSubType = WILTON; - } else if (strcmp(Prop, "universal") == 0) { - arch->SBSubType = UNIVERSAL; - } else if (strcmp(Prop, "subset") == 0) { - arch->SBSubType = SUBSET; + std::string sub_type_str = get_attribute(Cur, "sub_type", loc_data, BoolToReqOpt(false)).as_string(""); + /* If not specified, we set the same value as 'type' */ + if (!sub_type_str.empty()) { + if (sub_type_str == std::string("wilton")) { + arch->SBSubType = WILTON; + } else if (sub_type_str == std::string("universal")) { + arch->SBSubType = UNIVERSAL; + } else if (sub_type_str == std::string("subset")) { + arch->SBSubType = SUBSET; + } else { + archfpga_throw(loc_data.filename_c_str(), loc_data.line(Cur), + "Unknown property %s for switch block subtype x\n", Prop); + } } else { - archfpga_throw(loc_data.filename_c_str(), loc_data.line(Cur), - "Unknown property %s for switch block subtype x\n", Prop); + arch->SBSubType = arch->SBType; } ReqOpt CUSTOM_SWITCHBLOCK_REQD = BoolToReqOpt(!custom_switch_block); arch->Fs = get_attribute(Cur, "fs", loc_data, CUSTOM_SWITCHBLOCK_REQD).as_int(3); - arch->subFs = get_attribute(Cur, "sub_fs", loc_data, BoolToReqOpt(false)).as_int(3); + arch->subFs = get_attribute(Cur, "sub_fs", loc_data, BoolToReqOpt(false)).as_int(arch->Fs); Cur = get_single_child(Node, "default_fc", loc_data, ReqOpt::OPTIONAL); if (Cur) { diff --git a/openfpga/test_script/and_k6_frac_adder_chain.openfpga b/openfpga/test_script/and_k6_frac_adder_chain.openfpga new file mode 100644 index 000000000..2de7e14ab --- /dev/null +++ b/openfpga/test_script/and_k6_frac_adder_chain.openfpga @@ -0,0 +1,59 @@ +# Run VPR for the 'and' design +vpr ./test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml ./test_blif/and.blif --clock_modeling route #--write_rr_graph example_rr_graph.xml + +## Read OpenFPGA architecture definition +#read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml +# +## Write out the architecture XML as a proof +##write_openfpga_arch -f ./arch_echo.xml +# +## Annotate the OpenFPGA architecture to VPR data base +#link_openfpga_arch --activity_file ./test_blif/and.act --sort_gsb_chan_node_in_edges #--verbose +# +## Check and correct any naming conflicts in the BLIF netlist +#check_netlist_naming_conflict --fix --report ./netlist_renaming.xml +# +## Apply fix-up to clustering nets based on routing results +#pb_pin_fixup --verbose +# +## Apply fix-up to Look-Up Table truth tables based on packing results +#lut_truth_table_fixup #--verbose +# +## Build the module graph +## - Enabled compression on routing architecture modules +## - Enable pin duplication on grid modules +#build_fabric --compress_routing --duplicate_grid_pin #--verbose +# +## Repack the netlist to physical pbs +## This must be done before bitstream generator and testbench generation +## Strongly recommend it is done after all the fix-up have been applied +#repack #--verbose +# +## Build the bitstream +## - Output the fabric-independent bitstream to a file +#build_architecture_bitstream --verbose --file /var/tmp/xtang/openfpga_test_src/fabric_indepenent_bitstream.xml +# +## Build fabric-dependent bitstream +#build_fabric_bitstream --verbose +# +## Write the Verilog netlist for FPGA fabric +## - Enable the use of explicit port mapping in Verilog netlist +#write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src/SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose +# +## Write the Verilog testbench for FPGA fabric +## - We suggest the use of same output directory as fabric Verilog netlists +## - Must specify the reference benchmark file if you want to output any testbenches +## - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA +## - Enable pre-configured top-level testbench which is a fast verification skipping programming phase +## - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts +#write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src/SRC --reference_benchmark_file_path /var/tmp/xtang/and.v --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini /var/tmp/xtang/openfpga_test_src/simulation_deck.ini +# +## Write the SDC files for PnR backend +## - Turn on every options here +#write_pnr_sdc --file /var/tmp/xtang/openfpga_test_src/SDC +# +## Write the SDC to run timing analysis for a mapped FPGA fabric +#write_analysis_sdc --file /var/tmp/xtang/openfpga_test_src/SDC_analysis +# +# Finish and exit OpenFPGA +exit diff --git a/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml index 51f6824f9..8f58cb221 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml @@ -135,6 +135,7 @@ + 1 1 1 1 1 @@ -227,7 +228,7 @@ - + diff --git a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml new file mode 100644 index 000000000..4bce8ac18 --- /dev/null +++ b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml @@ -0,0 +1,630 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 235e-12 + 235e-12 + 235e-12 + 235e-12 + 235e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 195e-12 + 195e-12 + 195e-12 + 195e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 9837be618d25bd7a3ace45891df38343927a1f6c Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 20 Mar 2020 14:52:52 -0600 Subject: [PATCH 313/645] start debugging tile direct with micro architecture --- .../k6_frac_N10_adder_chain_40nm_openfpga.xml | 289 ++++++++++++++++++ .../and_k6_frac_adder_chain.openfpga | 108 +++---- .../k6_frac_N10_adder_chain_40nm.xml | 8 +- 3 files changed, 347 insertions(+), 58 deletions(-) create mode 100644 openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml new file mode 100644 index 000000000..ed0de93f1 --- /dev/null +++ b/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml @@ -0,0 +1,289 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + + + + 10e-12 5e-12 + + + 10e-12 5e-12 + + + + + + + + + + + + 10e-12 5e-12 5e-12 + + + 10e-12 5e-12 5e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga/test_script/and_k6_frac_adder_chain.openfpga b/openfpga/test_script/and_k6_frac_adder_chain.openfpga index 2de7e14ab..3f8b65f29 100644 --- a/openfpga/test_script/and_k6_frac_adder_chain.openfpga +++ b/openfpga/test_script/and_k6_frac_adder_chain.openfpga @@ -1,59 +1,59 @@ # Run VPR for the 'and' design vpr ./test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml ./test_blif/and.blif --clock_modeling route #--write_rr_graph example_rr_graph.xml -## Read OpenFPGA architecture definition -#read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml -# -## Write out the architecture XML as a proof -##write_openfpga_arch -f ./arch_echo.xml -# -## Annotate the OpenFPGA architecture to VPR data base -#link_openfpga_arch --activity_file ./test_blif/and.act --sort_gsb_chan_node_in_edges #--verbose -# -## Check and correct any naming conflicts in the BLIF netlist -#check_netlist_naming_conflict --fix --report ./netlist_renaming.xml -# -## Apply fix-up to clustering nets based on routing results -#pb_pin_fixup --verbose -# -## Apply fix-up to Look-Up Table truth tables based on packing results -#lut_truth_table_fixup #--verbose -# -## Build the module graph -## - Enabled compression on routing architecture modules -## - Enable pin duplication on grid modules -#build_fabric --compress_routing --duplicate_grid_pin #--verbose -# -## Repack the netlist to physical pbs -## This must be done before bitstream generator and testbench generation -## Strongly recommend it is done after all the fix-up have been applied -#repack #--verbose -# -## Build the bitstream -## - Output the fabric-independent bitstream to a file -#build_architecture_bitstream --verbose --file /var/tmp/xtang/openfpga_test_src/fabric_indepenent_bitstream.xml -# -## Build fabric-dependent bitstream -#build_fabric_bitstream --verbose -# -## Write the Verilog netlist for FPGA fabric -## - Enable the use of explicit port mapping in Verilog netlist -#write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src/SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose -# -## Write the Verilog testbench for FPGA fabric -## - We suggest the use of same output directory as fabric Verilog netlists -## - Must specify the reference benchmark file if you want to output any testbenches -## - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA -## - Enable pre-configured top-level testbench which is a fast verification skipping programming phase -## - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts -#write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src/SRC --reference_benchmark_file_path /var/tmp/xtang/and.v --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini /var/tmp/xtang/openfpga_test_src/simulation_deck.ini -# -## Write the SDC files for PnR backend -## - Turn on every options here -#write_pnr_sdc --file /var/tmp/xtang/openfpga_test_src/SDC -# -## Write the SDC to run timing analysis for a mapped FPGA fabric -#write_analysis_sdc --file /var/tmp/xtang/openfpga_test_src/SDC_analysis -# +# Read OpenFPGA architecture definition +read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml + +# Write out the architecture XML as a proof +#write_openfpga_arch -f ./arch_echo.xml + +# Annotate the OpenFPGA architecture to VPR data base +link_openfpga_arch --activity_file ./test_blif/and.act --sort_gsb_chan_node_in_edges #--verbose + +# Check and correct any naming conflicts in the BLIF netlist +check_netlist_naming_conflict --fix --report ./netlist_renaming.xml + +# Apply fix-up to clustering nets based on routing results +pb_pin_fixup --verbose + +# Apply fix-up to Look-Up Table truth tables based on packing results +lut_truth_table_fixup #--verbose + +# Build the module graph +# - Enabled compression on routing architecture modules +# - Enable pin duplication on grid modules +build_fabric --compress_routing --duplicate_grid_pin #--verbose + +# Repack the netlist to physical pbs +# This must be done before bitstream generator and testbench generation +# Strongly recommend it is done after all the fix-up have been applied +repack #--verbose + +# Build the bitstream +# - Output the fabric-independent bitstream to a file +build_architecture_bitstream --verbose --file /var/tmp/xtang/openfpga_test_src/fabric_indepenent_bitstream.xml + +# Build fabric-dependent bitstream +build_fabric_bitstream --verbose + +# Write the Verilog netlist for FPGA fabric +# - Enable the use of explicit port mapping in Verilog netlist +write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src/SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose + +# Write the Verilog testbench for FPGA fabric +# - We suggest the use of same output directory as fabric Verilog netlists +# - Must specify the reference benchmark file if you want to output any testbenches +# - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA +# - Enable pre-configured top-level testbench which is a fast verification skipping programming phase +# - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts +write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src/SRC --reference_benchmark_file_path /var/tmp/xtang/and.v --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini /var/tmp/xtang/openfpga_test_src/simulation_deck.ini + +# Write the SDC files for PnR backend +# - Turn on every options here +write_pnr_sdc --file /var/tmp/xtang/openfpga_test_src/SDC + +# Write the SDC to run timing analysis for a mapped FPGA fabric +write_analysis_sdc --file /var/tmp/xtang/openfpga_test_src/SDC_analysis + # Finish and exit OpenFPGA exit diff --git a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml index 4bce8ac18..ed928c98d 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml @@ -368,15 +368,15 @@ - + - + - + @@ -455,7 +455,7 @@ - + From a46fc9f028ab36c142314a544598c9d4ac522141 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 20 Mar 2020 14:59:46 -0600 Subject: [PATCH 314/645] add debugging information for tile direct builder --- openfpga/src/tile_direct/build_tile_direct.cpp | 6 +++++- .../k6_frac_N10_adder_chain_40nm_openfpga.xml | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/openfpga/src/tile_direct/build_tile_direct.cpp b/openfpga/src/tile_direct/build_tile_direct.cpp index 676fa8291..ba426ce17 100644 --- a/openfpga/src/tile_direct/build_tile_direct.cpp +++ b/openfpga/src/tile_direct/build_tile_direct.cpp @@ -656,7 +656,11 @@ TileDirect build_device_tile_direct(const DeviceContext& device_ctx, /* Walk through each direct definition in the VPR arch */ for (int idirect = 0; idirect < device_ctx.arch->num_directs; ++idirect) { ArchDirectId arch_direct_id = arch_direct.direct(std::string(device_ctx.arch->Directs[idirect].name)); - VTR_ASSERT(ArchDirectId::INVALID() != arch_direct_id); + if (ArchDirectId::INVALID() == arch_direct_id) { + VTR_LOG_ERROR("Unable to find an annotation in openfpga architecture XML for '%s'!\n", + device_ctx.arch->Directs[idirect].name); + exit(1); + } /* Build from original VPR arch definition */ build_inner_column_row_tile_direct(tile_direct, device_ctx.arch->Directs[idirect], diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml index ed0de93f1..2b340168e 100644 --- a/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml @@ -209,6 +209,9 @@ + + + From c5049a1ec83c25ee2282fa2ee38575b0a4ebf457 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 20 Mar 2020 15:10:00 -0600 Subject: [PATCH 315/645] keep debugging tile direct connections --- .../k6_frac_N10_adder_chain_40nm_openfpga.xml | 2 +- openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml index 2b340168e..98823698f 100644 --- a/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml @@ -210,7 +210,7 @@ - + diff --git a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml index ed928c98d..ed6e2defc 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml @@ -147,7 +147,14 @@ - + + + + clb.clk + clb.cin + clb.O[9:0] clb.I[19:0] + clb.cout clb.O[19:10] clb.I[39:20] + From 708fda9606ed1003a2e8be53a88874684e54005d Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 20 Mar 2020 16:38:58 -0600 Subject: [PATCH 316/645] fixed a bug in using tileable routing when directlist is enabled --- libs/libarchfpga/src/read_xml_arch_file.cpp | 2 +- .../and_k6_frac_adder_chain.openfpga | 2 +- .../k6_frac_N10_adder_chain_40nm.xml | 6 +++-- vpr/src/base/SetupVPR.cpp | 3 +++ vpr/src/base/place_and_route.cpp | 5 ++++ vpr/src/base/vpr_api.cpp | 2 +- vpr/src/base/vpr_types.h | 3 +++ vpr/src/route/route_common.cpp | 5 ++++ .../rr_graph_builder_utils.cpp | 14 +++++++++++ .../rr_graph_builder_utils.h | 3 +++ vpr/src/tileable_rr_graph/rr_gsb.cpp | 8 +++++++ .../tileable_rr_graph_gsb.cpp | 24 +++++++++++++++++-- 12 files changed, 70 insertions(+), 7 deletions(-) diff --git a/libs/libarchfpga/src/read_xml_arch_file.cpp b/libs/libarchfpga/src/read_xml_arch_file.cpp index b3b4004b2..cf7c97133 100644 --- a/libs/libarchfpga/src/read_xml_arch_file.cpp +++ b/libs/libarchfpga/src/read_xml_arch_file.cpp @@ -2536,7 +2536,7 @@ static void ProcessLayout(pugi::xml_node layout_tag, t_arch* arch, const pugiuti //Expect only tileable attributes on //expect_only_attributes(layout_tag, {"tileable"}, loc_data); - arch->tileable = get_attribute(layout_tag, "tileable", loc_data).as_bool(); + arch->tileable = get_attribute(layout_tag, "tileable", loc_data).as_bool(false); //Count the number of or tags size_t auto_layout_cnt = 0; diff --git a/openfpga/test_script/and_k6_frac_adder_chain.openfpga b/openfpga/test_script/and_k6_frac_adder_chain.openfpga index 3f8b65f29..156f49ff0 100644 --- a/openfpga/test_script/and_k6_frac_adder_chain.openfpga +++ b/openfpga/test_script/and_k6_frac_adder_chain.openfpga @@ -1,5 +1,5 @@ # Run VPR for the 'and' design -vpr ./test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml ./test_blif/and.blif --clock_modeling route #--write_rr_graph example_rr_graph.xml +vpr ./test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml ./test_blif/and.blif --route_chan_width 40 --clock_modeling route #--write_rr_graph example_rr_graph.xml # Read OpenFPGA architecture definition read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml diff --git a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml index ed6e2defc..e390112e7 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml @@ -160,13 +160,15 @@ - + + - + + - + diff --git a/vpr/src/tileable_rr_graph/rr_gsb.cpp b/vpr/src/tileable_rr_graph/rr_gsb.cpp index 200711e35..2d5c08e8c 100644 --- a/vpr/src/tileable_rr_graph/rr_gsb.cpp +++ b/vpr/src/tileable_rr_graph/rr_gsb.cpp @@ -874,8 +874,9 @@ void RRGSB::sort_chan_node_in_edges(const RRGraph& rr_graph) { SideManager side_manager(side); chan_node_in_edges_[side].resize(chan_node_[side].get_chan_width()); for (size_t track_id = 0; track_id < chan_node_[side].get_chan_width(); ++track_id) { - /* Only sort the output nodes */ - if (OUT_PORT == chan_node_direction_[side][track_id]) { + /* Only sort the output nodes and bypass passing wires */ + if ( (OUT_PORT == chan_node_direction_[side][track_id]) + && (false == is_sb_node_passing_wire(rr_graph, side_manager.get_side(), track_id)) ) { sort_chan_node_in_edges(rr_graph, side_manager.get_side(), track_id); } } From 05ec86430aa86b0cf09c1aba8a1946f9c1e9ed7d Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 20 Mar 2020 17:56:03 -0600 Subject: [PATCH 320/645] temp fix for direct connections. Should notify VPR team about this issue: delayless switch is used in direct connection but it is considered as configurable....which is actually NOT! --- openfpga/src/fabric/build_routing_modules.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/openfpga/src/fabric/build_routing_modules.cpp b/openfpga/src/fabric/build_routing_modules.cpp index da216ec75..b00cc5dc5 100644 --- a/openfpga/src/fabric/build_routing_modules.cpp +++ b/openfpga/src/fabric/build_routing_modules.cpp @@ -471,6 +471,18 @@ void build_connection_block_module_short_interc(ModuleManager& module_manager, VTR_ASSERT_SAFE(1 == driver_rr_nodes.size()); const RRNodeId& driver_rr_node = driver_rr_nodes[0]; + /* Xifan Tang: VPR considers delayless switch to be configurable + * As a result, the direct connection is considered to be configurable... + * Here, I simply kick out OPINs in CB connection because they should be built + * in the top mopdule. + * + * Note: this MUST BE reconsidered if we do have OPIN connected to IPINs + * through a programmable multiplexer!!! + */ + if (OPIN == rr_graph.node_type(driver_rr_node)) { + return; + } + VTR_ASSERT((CHANX == rr_graph.node_type(driver_rr_node)) || (CHANY == rr_graph.node_type(driver_rr_node))); /* Create port description for the routing track middle output */ From 682b667a3c6a394618b50af6dc1d43d8c75819ba Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 20 Mar 2020 21:44:01 -0600 Subject: [PATCH 321/645] minor bug fix for direct connection in FPGA-SDC --- .../src/fpga_sdc/pnr_sdc_routing_writer.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp b/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp index dcb5b6849..43572244c 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp +++ b/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp @@ -230,7 +230,22 @@ void print_pnr_sdc_constrain_cb_mux_timing(std::fstream& fp, * These connections should be handled by other functions in the compact_netlist.c * So we just return here for OPINs */ - if (0 == get_rr_graph_configurable_driver_nodes(rr_graph, output_rr_node).size()) { + std::vector input_rr_nodes = get_rr_graph_configurable_driver_nodes(rr_graph, output_rr_node); + + if (0 == input_rr_nodes.size()) { + return; + } + + /* Xifan Tang: VPR considers delayless switch to be configurable + * As a result, the direct connection is considered to be configurable... + * Here, I simply kick out OPINs in CB connection because they should be built + * in the top mopdule. + * + * Note: this MUST BE reconsidered if we do have OPIN connected to IPINs + * through a programmable multiplexer!!! + */ + if ( (1 == input_rr_nodes.size()) + && (OPIN == rr_graph.node_type(input_rr_nodes[0])) ) { return; } @@ -247,7 +262,7 @@ void print_pnr_sdc_constrain_cb_mux_timing(std::fstream& fp, rr_graph, rr_gsb, cb_type, - get_rr_graph_configurable_driver_nodes(rr_graph, output_rr_node)); + input_rr_nodes); /* Find timing constraints for each path (edge) */ std::map switch_delays; From 2ff2d65e583158d333452b04e5d078e1593a5427 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 20 Mar 2020 22:12:23 -0600 Subject: [PATCH 322/645] start debugging tileable routing using larger array size. Bug spotted in finding chan nodes --- openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml | 4 ++-- vpr/src/tileable_rr_graph/rr_gsb.cpp | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml index b1a588eb1..c3598bd2b 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml @@ -159,9 +159,9 @@ - + - + diff --git a/vpr/src/tileable_rr_graph/rr_gsb.cpp b/vpr/src/tileable_rr_graph/rr_gsb.cpp index 2d5c08e8c..d3ae2bcc5 100644 --- a/vpr/src/tileable_rr_graph/rr_gsb.cpp +++ b/vpr/src/tileable_rr_graph/rr_gsb.cpp @@ -444,6 +444,10 @@ bool RRGSB::is_sb_node_passing_wire(const RRGraph& rr_graph, /* Reach here it means that this will be a passing wire, * we should be able to find the node on the opposite side of the GSB! */ + if (true != is_sb_node_exist_opposite_side(rr_graph, track_node, node_side)) { + VTR_LOG("GSB[%lu][%lu] track node:\n", get_x(), get_y()); + rr_graph.print_node(track_node); + } VTR_ASSERT (true == is_sb_node_exist_opposite_side(rr_graph, track_node, node_side)); return true; From 28123b80520d77d3a5c83624ca8cb1eef7984f54 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 21 Mar 2020 11:38:39 -0600 Subject: [PATCH 323/645] remove the direct connected IPIN/OPIN from RR GSB builder --- openfpga/src/annotation/annotate_rr_graph.cpp | 24 +++++++++ .../src/tile_direct/build_tile_direct.cpp | 3 ++ .../k6_frac_N10_adder_chain_40nm.xml | 2 +- .../openfpga_rr_graph_utils.cpp | 52 +++++++++++++++++++ .../openfpga_rr_graph_utils.h | 6 +++ vpr/src/tileable_rr_graph/rr_gsb.cpp | 3 +- 6 files changed, 88 insertions(+), 2 deletions(-) diff --git a/openfpga/src/annotation/annotate_rr_graph.cpp b/openfpga/src/annotation/annotate_rr_graph.cpp index 4af681c71..eba72933f 100644 --- a/openfpga/src/annotation/annotate_rr_graph.cpp +++ b/openfpga/src/annotation/annotate_rr_graph.cpp @@ -12,6 +12,7 @@ /* Headers from vpr library */ #include "rr_graph_obj_util.h" +#include "openfpga_rr_graph_utils.h" #include "annotate_rr_graph.h" @@ -264,9 +265,18 @@ RRGSB build_rr_gsb(const DeviceContext& vpr_device_ctx, vpr_device_ctx.rr_graph.node_configurable_out_edges(inode).end())) { continue; } + + /* Do not consider OPINs that directly drive an IPIN + * they are supposed to be handled by direct connection + */ + if (true == is_opin_direct_connected_ipin(vpr_device_ctx.rr_graph, inode)) { + continue; + } + /* Grid[x+1][y+1] Bottom side outputs pins */ rr_gsb.add_opin_node(inode, side_manager.get_side()); } + for (const RRNodeId& inode : temp_opin_rr_nodes[1]) { /* Skip those has no configurable outgoing, they should NOT appear in the GSB connection * This is for those grid output pins used by direct connections @@ -276,6 +286,13 @@ RRGSB build_rr_gsb(const DeviceContext& vpr_device_ctx, continue; } + /* Do not consider OPINs that directly drive an IPIN + * they are supposed to be handled by direct connection + */ + if (true == is_opin_direct_connected_ipin(vpr_device_ctx.rr_graph, inode)) { + continue; + } + /* Grid[x+1][y] TOP side outputs pins */ rr_gsb.add_opin_node(inode, side_manager.get_side()); } @@ -356,6 +373,13 @@ RRGSB build_rr_gsb(const DeviceContext& vpr_device_ctx, ix, iy, IPIN, ipin_rr_node_grid_side); /* Fill the ipin nodes of RRGSB */ for (const RRNodeId& inode : temp_ipin_rr_nodes) { + /* Do not consider IPINs that are directly connected by an OPIN + * they are supposed to be handled by direct connection + */ + if (true == is_ipin_direct_connected_opin(vpr_device_ctx.rr_graph, inode)) { + continue; + } + rr_gsb.add_ipin_node(inode, side_manager.get_side()); } /* Clear the temp data */ diff --git a/openfpga/src/tile_direct/build_tile_direct.cpp b/openfpga/src/tile_direct/build_tile_direct.cpp index ba426ce17..2166b7a16 100644 --- a/openfpga/src/tile_direct/build_tile_direct.cpp +++ b/openfpga/src/tile_direct/build_tile_direct.cpp @@ -674,6 +674,9 @@ TileDirect build_device_tile_direct(const DeviceContext& device_ctx, arch_direct_id); } + VTR_LOG("Built %lu tile-to-tile direct connections\n", + std::distance(tile_direct.directs().begin(), tile_direct.directs().end())); + return tile_direct; } diff --git a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml index c3598bd2b..e66dc8b9c 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml @@ -159,7 +159,7 @@ - + diff --git a/vpr/src/tileable_rr_graph/openfpga_rr_graph_utils.cpp b/vpr/src/tileable_rr_graph/openfpga_rr_graph_utils.cpp index ba789b4da..a3b3da7fe 100644 --- a/vpr/src/tileable_rr_graph/openfpga_rr_graph_utils.cpp +++ b/vpr/src/tileable_rr_graph/openfpga_rr_graph_utils.cpp @@ -128,4 +128,56 @@ std::vector get_rr_graph_non_configurable_driver_nodes(const RRGraph& return driver_nodes; } +/************************************************************************ + * Check if an OPIN of a rr_graph is directly driving an IPIN + * To meet this requirement, the OPIN must: + * - Have only 1 fan-out + * - The only fan-out is an IPIN + ***********************************************************************/ +bool is_opin_direct_connected_ipin(const RRGraph& rr_graph, + const RRNodeId& node) { + /* We only accept OPIN */ + VTR_ASSERT(OPIN == rr_graph.node_type(node)); + + if (1 != rr_graph.node_out_edges(node).size()) { + return false; + } + + VTR_ASSERT(1 == rr_graph.node_out_edges(node).size()); + for (const RREdgeId& edge: rr_graph.node_out_edges(node)) { + const RRNodeId& sink_node = rr_graph.edge_sink_node(edge); + if (IPIN != rr_graph.node_type(sink_node)) { + return false; + } + } + + return true; +} + +/************************************************************************ + * Check if an IPIN of a rr_graph is directly connected to an OPIN + * To meet this requirement, the IPIN must: + * - Have only 1 fan-in + * - The only fan-in is an OPIN + ***********************************************************************/ +bool is_ipin_direct_connected_opin(const RRGraph& rr_graph, + const RRNodeId& node) { + /* We only accept IPIN */ + VTR_ASSERT(IPIN == rr_graph.node_type(node)); + + if (1 != rr_graph.node_in_edges(node).size()) { + return false; + } + + VTR_ASSERT(1 == rr_graph.node_in_edges(node).size()); + for (const RREdgeId& edge: rr_graph.node_in_edges(node)) { + const RRNodeId& src_node = rr_graph.edge_src_node(edge); + if (OPIN != rr_graph.node_type(src_node)) { + return false; + } + } + + return true; +} + } /* end namespace openfpga */ diff --git a/vpr/src/tileable_rr_graph/openfpga_rr_graph_utils.h b/vpr/src/tileable_rr_graph/openfpga_rr_graph_utils.h index 0466892f8..ebf741f8d 100644 --- a/vpr/src/tileable_rr_graph/openfpga_rr_graph_utils.h +++ b/vpr/src/tileable_rr_graph/openfpga_rr_graph_utils.h @@ -35,6 +35,12 @@ std::vector get_rr_graph_configurable_driver_nodes(const RRGraph& rr_g std::vector get_rr_graph_non_configurable_driver_nodes(const RRGraph& rr_graph, const RRNodeId& node); +bool is_opin_direct_connected_ipin(const RRGraph& rr_graph, + const RRNodeId& node); + +bool is_ipin_direct_connected_opin(const RRGraph& rr_graph, + const RRNodeId& node); + } /* end namespace openfpga */ #endif diff --git a/vpr/src/tileable_rr_graph/rr_gsb.cpp b/vpr/src/tileable_rr_graph/rr_gsb.cpp index d3ae2bcc5..b489182b2 100644 --- a/vpr/src/tileable_rr_graph/rr_gsb.cpp +++ b/vpr/src/tileable_rr_graph/rr_gsb.cpp @@ -445,7 +445,8 @@ bool RRGSB::is_sb_node_passing_wire(const RRGraph& rr_graph, * we should be able to find the node on the opposite side of the GSB! */ if (true != is_sb_node_exist_opposite_side(rr_graph, track_node, node_side)) { - VTR_LOG("GSB[%lu][%lu] track node:\n", get_x(), get_y()); + VTR_LOG("GSB[%lu][%lu] track node[%lu] at %s:\n", + get_x(), get_y(), track_id, SIDE_STRING[node_side]); rr_graph.print_node(track_node); } VTR_ASSERT (true == is_sb_node_exist_opposite_side(rr_graph, track_node, node_side)); From 8f35f191eb57e0cdd4a88e9797d17c2b844254c3 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 21 Mar 2020 11:42:00 -0600 Subject: [PATCH 324/645] use the formalized function in FPGA-SDC to identify direct connection --- openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp b/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp index 43572244c..dd0914d14 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp +++ b/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp @@ -244,8 +244,7 @@ void print_pnr_sdc_constrain_cb_mux_timing(std::fstream& fp, * Note: this MUST BE reconsidered if we do have OPIN connected to IPINs * through a programmable multiplexer!!! */ - if ( (1 == input_rr_nodes.size()) - && (OPIN == rr_graph.node_type(input_rr_nodes[0])) ) { + if (true == is_ipin_direct_connected_opin(rr_graph, output_rr_node)) { return; } From c0e8d98c6f28a0e3cb37689c1b74ca4f09023ce8 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 21 Mar 2020 12:43:56 -0600 Subject: [PATCH 325/645] bug fixed in tile direct builder --- openfpga/src/base/openfpga_link_arch.cpp | 3 +- .../src/fabric/build_top_module_directs.cpp | 3 + .../src/tile_direct/build_tile_direct.cpp | 356 ++++++++++++------ openfpga/src/tile_direct/build_tile_direct.h | 3 +- 4 files changed, 241 insertions(+), 124 deletions(-) diff --git a/openfpga/src/base/openfpga_link_arch.cpp b/openfpga/src/base/openfpga_link_arch.cpp index 5c0d6c582..34c98ee8d 100644 --- a/openfpga/src/base/openfpga_link_arch.cpp +++ b/openfpga/src/base/openfpga_link_arch.cpp @@ -123,7 +123,8 @@ void link_arch(OpenfpgaContext& openfpga_ctx, /* Build tile direct annotation */ openfpga_ctx.mutable_tile_direct() = build_device_tile_direct(g_vpr_ctx.device(), - openfpga_ctx.arch().arch_direct); + openfpga_ctx.arch().arch_direct, + cmd_context.option_enable(cmd, opt_verbose)); /* Annotate placement results */ annotate_mapped_blocks(g_vpr_ctx.device(), diff --git a/openfpga/src/fabric/build_top_module_directs.cpp b/openfpga/src/fabric/build_top_module_directs.cpp index 63d4a1882..314b5f2e8 100644 --- a/openfpga/src/fabric/build_top_module_directs.cpp +++ b/openfpga/src/fabric/build_top_module_directs.cpp @@ -95,6 +95,9 @@ void add_module_nets_tile_direct_connection(ModuleManager& module_manager, size_t src_pin_height = grids[src_clb_coord.x()][src_clb_coord.y()].type->pin_height_offset[src_tile_pin]; std::string src_port_name = generate_grid_port_name(src_clb_coord, src_pin_width, src_pin_height, src_pin_grid_side, src_tile_pin, false); ModulePortId src_port_id = module_manager.find_module_port(src_grid_module, src_port_name); + if (true != module_manager.valid_module_port_id(src_grid_module, src_port_id)) { + VTR_LOG("Fail to find port '%s'\n", src_port_name.c_str()); + } VTR_ASSERT(true == module_manager.valid_module_port_id(src_grid_module, src_port_id)); VTR_ASSERT(1 == module_manager.module_port(src_grid_module, src_port_id).get_width()); diff --git a/openfpga/src/tile_direct/build_tile_direct.cpp b/openfpga/src/tile_direct/build_tile_direct.cpp index 2166b7a16..5e33bc502 100644 --- a/openfpga/src/tile_direct/build_tile_direct.cpp +++ b/openfpga/src/tile_direct/build_tile_direct.cpp @@ -39,6 +39,24 @@ std::string parse_direct_tile_name(const std::string& direct_tile_inf) { return tokens[0]; } +/*************************************************************************************** + * Parse the pin name and port MSB/LSB from the direct definition + * The definition string should be in the following format: + * .[:] + ***************************************************************************************/ +static +std::string parse_direct_port(const std::string& direct_tile_inf) { + StringToken tokenizer(direct_tile_inf); + std::vector tokens = tokenizer.split('.'); + /* We should have only 2 elements and the first is tile name */ + if (2 != tokens.size()) { + VTR_LOG_ERROR("Invalid definition on direct tile '%s'!\n\tExpect .[:].\n", + direct_tile_inf.c_str()); + } + + return tokens[1]; +} + /*************************************************************************************** * Check if a pin is located on a given side of physical tile * If the given side is NUM_SIDES, we will search all the sides @@ -364,15 +382,16 @@ static void build_inner_column_row_tile_direct(TileDirect& tile_direct, const t_direct_inf& vpr_direct, const DeviceContext& device_ctx, - const ArchDirectId& arch_direct_id) { + const ArchDirectId& arch_direct_id, + const bool& verbose) { /* Get the source tile and pin information */ std::string from_tile_name = parse_direct_tile_name(std::string(vpr_direct.from_pin)); - PortParser from_tile_port_parser(std::string(vpr_direct.from_pin)); + PortParser from_tile_port_parser(parse_direct_port(std::string(vpr_direct.from_pin))); const BasicPort& from_tile_port = from_tile_port_parser.port(); /* Get the sink tile and pin information */ std::string to_tile_name = parse_direct_tile_name(std::string(vpr_direct.to_pin)); - PortParser to_tile_port_parser(std::string(vpr_direct.to_pin)); + PortParser to_tile_port_parser(parse_direct_port(std::string(vpr_direct.to_pin))); const BasicPort& to_tile_port = to_tile_port_parser.port(); /* Walk through the device fabric and find the grid that fit the source */ @@ -387,51 +406,80 @@ void build_inner_column_row_tile_direct(TileDirect& tile_direct, if (from_tile_name != std::string(device_ctx.grid[x][y].type->name)) { continue; } + + /* Search all the sides, the from pin may locate any side! + * Note: the vpr_direct.from_side is NUM_SIDES, which is unintialized + * This should be reported to VPR!!! + */ + for (const e_side& from_side : {TOP, RIGHT, BOTTOM, LEFT}) { - /* Try to find the pin in this tile */ - std::vector from_pins = find_physical_tile_pin_id(device_ctx.grid[x][y].type, - device_ctx.grid[x][y].width_offset, - device_ctx.grid[x][y].height_offset, - from_tile_port, - vpr_direct.from_side); - /* If nothing found, we can continue */ - if (0 == from_pins.size()) { - continue; - } - - /* We should try to the sink grid for inner-column/row direct connections */ - vtr::Point from_grid_coord(x, y); - vtr::Point to_grid_coord(x + vpr_direct.x_offset, y + vpr_direct.y_offset); - if (false == is_grid_coordinate_exist_in_device(device_ctx.grid, to_grid_coord)) { - continue; - } + /* Try to find the pin in this tile */ + std::vector from_pins = find_physical_tile_pin_id(device_ctx.grid[x][y].type, + device_ctx.grid[x][y].width_offset, + device_ctx.grid[x][y].height_offset, + from_tile_port, + from_side); + /* If nothing found, we can continue */ + if (0 == from_pins.size()) { + continue; + } + + /* We should try to the sink grid for inner-column/row direct connections */ + vtr::Point from_grid_coord(x, y); + vtr::Point to_grid_coord(x + vpr_direct.x_offset, y + vpr_direct.y_offset); + if (false == is_grid_coordinate_exist_in_device(device_ctx.grid, to_grid_coord)) { + continue; + } - /* Bypass the grid that does not fit the from_tile name */ - if (to_tile_name != std::string(device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].type->name)) { - continue; - } - /* Try to find the pin in this tile */ - std::vector to_pins = find_physical_tile_pin_id(device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].type, - device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].width_offset, - device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].height_offset, - to_tile_port, - vpr_direct.to_side); - /* If nothing found, we can continue */ - if (0 == to_pins.size()) { - continue; - } + /* Bypass the grid that does not fit the from_tile name */ + if (to_tile_name != std::string(device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].type->name)) { + continue; + } - /* If from port and to port do not match in sizes, error out */ - if (from_pins.size() != to_pins.size()) { - report_direct_from_port_and_to_port_mismatch(vpr_direct, from_tile_port, to_tile_port); - exit(1); - } + /* Search all the sides, the to pin may locate any side! + * Note: the vpr_direct.to_side is NUM_SIDES, which is unintialized + * This should be reported to VPR!!! + */ + for (const e_side& to_side : {TOP, RIGHT, BOTTOM, LEFT}) { - /* Now add the tile direct */ - for (size_t ipin = 0; ipin < from_pins.size(); ++ipin) { - TileDirectId tile_direct_id = tile_direct.add_direct(from_grid_coord, vpr_direct.from_side, from_pins[ipin], - to_grid_coord, vpr_direct.to_side, to_pins[ipin]); - tile_direct.set_arch_direct_id(tile_direct_id, arch_direct_id); + /* Try to find the pin in this tile */ + std::vector to_pins = find_physical_tile_pin_id(device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].type, + device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].width_offset, + device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].height_offset, + to_tile_port, + to_side); + /* If nothing found, we can continue */ + if (0 == to_pins.size()) { + continue; + } + + /* If from port and to port do not match in sizes, error out */ + if (from_pins.size() != to_pins.size()) { + report_direct_from_port_and_to_port_mismatch(vpr_direct, from_tile_port, to_tile_port); + exit(1); + } + + /* Now add the tile direct */ + for (size_t ipin = 0; ipin < from_pins.size(); ++ipin) { + VTR_LOGV(verbose, + "Built a inner-column/row tile-to-tile direct from %s[%lu][%lu].%s[%lu] at side '%s' to %s[%lu][%lu].%s[%lu] at side '%s'\n", + from_tile_name.c_str(), x, y, + from_tile_port.get_name().c_str(), from_pins[ipin], + SIDE_STRING[from_side], + to_tile_name.c_str(), + to_grid_coord.x(), to_grid_coord.y(), + to_tile_port.get_name().c_str(), to_pins[ipin], + SIDE_STRING[to_side] + ); + TileDirectId tile_direct_id = tile_direct.add_direct(from_grid_coord, + from_side, + from_pins[ipin], + to_grid_coord, + to_side, + to_pins[ipin]); + tile_direct.set_arch_direct_id(tile_direct_id, arch_direct_id); + } + } } } } @@ -475,16 +523,17 @@ void build_inter_column_row_tile_direct(TileDirect& tile_direct, const t_direct_inf& vpr_direct, const DeviceContext& device_ctx, const ArchDirect& arch_direct, - const ArchDirectId& arch_direct_id) { + const ArchDirectId& arch_direct_id, + const bool& verbose) { /* Get the source tile and pin information */ std::string from_tile_name = parse_direct_tile_name(std::string(vpr_direct.from_pin)); - PortParser from_tile_port_parser(std::string(vpr_direct.from_pin)); + PortParser from_tile_port_parser(parse_direct_port(std::string(vpr_direct.from_pin))); const BasicPort& from_tile_port = from_tile_port_parser.port(); /* Get the sink tile and pin information */ std::string to_tile_name = parse_direct_tile_name(std::string(vpr_direct.to_pin)); - PortParser to_tile_port_parser(std::string(vpr_direct.to_pin)); + PortParser to_tile_port_parser(parse_direct_port(std::string(vpr_direct.to_pin))); const BasicPort& to_tile_port = to_tile_port_parser.port(); /* Go through the direct connection list, see if we need intra-column/row connection here */ @@ -525,47 +574,77 @@ void build_inter_column_row_tile_direct(TileDirect& tile_direct, if (false == is_grid_coordinate_exist_in_device(device_ctx.grid, from_grid_coord)) { continue; } + + /* Search all the sides, the from pin may locate any side! + * Note: the vpr_direct.from_side is NUM_SIDES, which is unintialized + * This should be reported to VPR!!! + */ + for (const e_side& from_side : {TOP, RIGHT, BOTTOM, LEFT}) { - /* Try to find the pin in this tile */ - std::vector from_pins = find_physical_tile_pin_id(device_ctx.grid[from_grid_coord.x()][from_grid_coord.y()].type, - device_ctx.grid[from_grid_coord.x()][from_grid_coord.y()].width_offset, - device_ctx.grid[from_grid_coord.x()][from_grid_coord.y()].height_offset, - from_tile_port, - vpr_direct.from_side); - /* If nothing found, we can continue */ - if (0 == from_pins.size()) { - continue; - } + /* Try to find the pin in this tile */ + std::vector from_pins = find_physical_tile_pin_id(device_ctx.grid[from_grid_coord.x()][from_grid_coord.y()].type, + device_ctx.grid[from_grid_coord.x()][from_grid_coord.y()].width_offset, + device_ctx.grid[from_grid_coord.x()][from_grid_coord.y()].height_offset, + from_tile_port, + from_side); + /* If nothing found, we can continue */ + if (0 == from_pins.size()) { + continue; + } - /* For a valid coordinate, we can find the coordinate of the destination clb */ - vtr::Point to_grid_coord = find_inter_direct_destination_coordinate(device_ctx.grid, from_grid_coord, to_tile_name, arch_direct, arch_direct_id); - /* If destination clb is valid, we should add something */ - if (false == is_grid_coordinate_exist_in_device(device_ctx.grid, to_grid_coord)) { - continue; - } + /* For a valid coordinate, we can find the coordinate of the destination clb */ + vtr::Point to_grid_coord = find_inter_direct_destination_coordinate(device_ctx.grid, from_grid_coord, to_tile_name, arch_direct, arch_direct_id); + /* If destination clb is valid, we should add something */ + if (false == is_grid_coordinate_exist_in_device(device_ctx.grid, to_grid_coord)) { + continue; + } - /* Try to find the pin in this tile */ - std::vector to_pins = find_physical_tile_pin_id(device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].type, - device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].width_offset, - device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].height_offset, - to_tile_port, - vpr_direct.to_side); - /* If nothing found, we can continue */ - if (0 == to_pins.size()) { - continue; - } + /* Search all the sides, the to pin may locate any side! + * Note: the vpr_direct.to_side is NUM_SIDES, which is unintialized + * This should be reported to VPR!!! + */ + for (const e_side& to_side : {TOP, RIGHT, BOTTOM, LEFT}) { - /* If from port and to port do not match in sizes, error out */ - if (from_pins.size() != to_pins.size()) { - report_direct_from_port_and_to_port_mismatch(vpr_direct, from_tile_port, to_tile_port); - exit(1); - } + /* Try to find the pin in this tile */ + std::vector to_pins = find_physical_tile_pin_id(device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].type, + device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].width_offset, + device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].height_offset, + to_tile_port, + to_side); + /* If nothing found, we can continue */ + if (0 == to_pins.size()) { + continue; + } - /* Now add the tile direct */ - for (size_t ipin = 0; ipin < from_pins.size(); ++ipin) { - TileDirectId tile_direct_id = tile_direct.add_direct(from_grid_coord, vpr_direct.from_side, from_pins[ipin], - to_grid_coord, vpr_direct.to_side, to_pins[ipin]); - tile_direct.set_arch_direct_id(tile_direct_id, arch_direct_id); + /* If from port and to port do not match in sizes, error out */ + if (from_pins.size() != to_pins.size()) { + report_direct_from_port_and_to_port_mismatch(vpr_direct, from_tile_port, to_tile_port); + exit(1); + } + + /* Now add the tile direct */ + for (size_t ipin = 0; ipin < from_pins.size(); ++ipin) { + VTR_LOGV(verbose, + "Built a inter-column/row tile-to-tile direct from %s[%lu][%lu].%s[%lu] at side '%s' to %s[%lu][%lu].%s[%lu] at side '%s'\n", + from_tile_name.c_str(), + from_grid_coord.x(), from_grid_coord.y(), + from_tile_port.get_name().c_str(), from_pins[ipin], + SIDE_STRING[from_side], + to_tile_name.c_str(), + to_grid_coord.x(), to_grid_coord.y(), + to_tile_port.get_name().c_str(), to_pins[ipin], + SIDE_STRING[to_side] + ); + + TileDirectId tile_direct_id = tile_direct.add_direct(from_grid_coord, + from_side, + from_pins[ipin], + to_grid_coord, + to_side, + to_pins[ipin]); + tile_direct.set_arch_direct_id(tile_direct_id, arch_direct_id); + } + } } } return; /* Go to next direct type */ @@ -599,46 +678,76 @@ void build_inter_column_row_tile_direct(TileDirect& tile_direct, continue; } - /* Try to find the pin in this tile */ - std::vector from_pins = find_physical_tile_pin_id(device_ctx.grid[from_grid_coord.x()][from_grid_coord.y()].type, - device_ctx.grid[from_grid_coord.x()][from_grid_coord.y()].width_offset, - device_ctx.grid[from_grid_coord.x()][from_grid_coord.y()].height_offset, - from_tile_port, - vpr_direct.from_side); - /* If nothing found, we can continue */ - if (0 == from_pins.size()) { - continue; - } + /* Search all the sides, the from pin may locate any side! + * Note: the vpr_direct.from_side is NUM_SIDES, which is unintialized + * This should be reported to VPR!!! + */ + for (const e_side& from_side : {TOP, RIGHT, BOTTOM, LEFT}) { + + /* Try to find the pin in this tile */ + std::vector from_pins = find_physical_tile_pin_id(device_ctx.grid[from_grid_coord.x()][from_grid_coord.y()].type, + device_ctx.grid[from_grid_coord.x()][from_grid_coord.y()].width_offset, + device_ctx.grid[from_grid_coord.x()][from_grid_coord.y()].height_offset, + from_tile_port, + from_side); + /* If nothing found, we can continue */ + if (0 == from_pins.size()) { + continue; + } - /* For a valid coordinate, we can find the coordinate of the destination clb */ - vtr::Point to_grid_coord = find_inter_direct_destination_coordinate(device_ctx.grid, from_grid_coord, to_tile_name, arch_direct, arch_direct_id); - /* If destination clb is valid, we should add something */ - if (false == is_grid_coordinate_exist_in_device(device_ctx.grid, to_grid_coord)) { - continue; - } + /* For a valid coordinate, we can find the coordinate of the destination clb */ + vtr::Point to_grid_coord = find_inter_direct_destination_coordinate(device_ctx.grid, from_grid_coord, to_tile_name, arch_direct, arch_direct_id); + /* If destination clb is valid, we should add something */ + if (false == is_grid_coordinate_exist_in_device(device_ctx.grid, to_grid_coord)) { + continue; + } - /* Try to find the pin in this tile */ - std::vector to_pins = find_physical_tile_pin_id(device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].type, - device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].width_offset, - device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].height_offset, - to_tile_port, - vpr_direct.to_side); - /* If nothing found, we can continue */ - if (0 == to_pins.size()) { - continue; - } + /* Search all the sides, the to pin may locate any side! + * Note: the vpr_direct.to_side is NUM_SIDES, which is unintialized + * This should be reported to VPR!!! + */ + for (const e_side& to_side : {TOP, RIGHT, BOTTOM, LEFT}) { - /* If from port and to port do not match in sizes, error out */ - if (from_pins.size() != to_pins.size()) { - report_direct_from_port_and_to_port_mismatch(vpr_direct, from_tile_port, to_tile_port); - exit(1); - } + /* Try to find the pin in this tile */ + std::vector to_pins = find_physical_tile_pin_id(device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].type, + device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].width_offset, + device_ctx.grid[to_grid_coord.x()][to_grid_coord.y()].height_offset, + to_tile_port, + to_side); + /* If nothing found, we can continue */ + if (0 == to_pins.size()) { + continue; + } - /* Now add the tile direct */ - for (size_t ipin = 0; ipin < from_pins.size(); ++ipin) { - TileDirectId tile_direct_id = tile_direct.add_direct(from_grid_coord, vpr_direct.from_side, from_pins[ipin], - to_grid_coord, vpr_direct.to_side, to_pins[ipin]); - tile_direct.set_arch_direct_id(tile_direct_id, arch_direct_id); + /* If from port and to port do not match in sizes, error out */ + if (from_pins.size() != to_pins.size()) { + report_direct_from_port_and_to_port_mismatch(vpr_direct, from_tile_port, to_tile_port); + exit(1); + } + + /* Now add the tile direct */ + for (size_t ipin = 0; ipin < from_pins.size(); ++ipin) { + VTR_LOGV(verbose, + "Built a inter-column/row tile-to-tile direct from %s[%lu][%lu].%s[%lu] at side '%s' to %s[%lu][%lu].%s[%lu] at side '%s'\n", + from_tile_name.c_str(), + from_grid_coord.x(), from_grid_coord.y(), + from_tile_port.get_name().c_str(), from_pins[ipin], + SIDE_STRING[from_side], + to_tile_name.c_str(), + to_grid_coord.x(), to_grid_coord.y(), + to_tile_port.get_name().c_str(), to_pins[ipin], + SIDE_STRING[to_side] + ); + + TileDirectId tile_direct_id = tile_direct.add_direct(from_grid_coord, + from_side, + from_pins[ipin], + to_grid_coord, + to_side, + to_pins[ipin]); + tile_direct.set_arch_direct_id(tile_direct_id, arch_direct_id); + } + } } } } @@ -648,7 +757,8 @@ void build_inter_column_row_tile_direct(TileDirect& tile_direct, * between tiles (programmable blocks) ***************************************************************************************/ TileDirect build_device_tile_direct(const DeviceContext& device_ctx, - const ArchDirect& arch_direct) { + const ArchDirect& arch_direct, + const bool& verbose) { vtr::ScopedStartFinishTimer timer("Build the annotation about direct connection between tiles"); TileDirect tile_direct; @@ -665,13 +775,15 @@ TileDirect build_device_tile_direct(const DeviceContext& device_ctx, build_inner_column_row_tile_direct(tile_direct, device_ctx.arch->Directs[idirect], device_ctx, - arch_direct_id); + arch_direct_id, + verbose); /* Build from OpenFPGA arch definition */ build_inter_column_row_tile_direct(tile_direct, device_ctx.arch->Directs[idirect], device_ctx, arch_direct, - arch_direct_id); + arch_direct_id, + verbose); } VTR_LOG("Built %lu tile-to-tile direct connections\n", diff --git a/openfpga/src/tile_direct/build_tile_direct.h b/openfpga/src/tile_direct/build_tile_direct.h index 78ca2f6b0..ed9e0c3bc 100644 --- a/openfpga/src/tile_direct/build_tile_direct.h +++ b/openfpga/src/tile_direct/build_tile_direct.h @@ -17,7 +17,8 @@ namespace openfpga { TileDirect build_device_tile_direct(const DeviceContext& device_ctx, - const ArchDirect& arch_direct); + const ArchDirect& arch_direct, + const bool& verbose); } /* end namespace openfpga */ From 63c4669dbb5cd5787667f1f6fc219bededaaf701 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 21 Mar 2020 17:36:08 -0600 Subject: [PATCH 326/645] fixed bug in the fast look-up for tileable rr_graph --- .../k6_frac_N10_adder_chain_40nm.xml | 2 +- vpr/src/device/rr_graph_obj.cpp | 52 +++++++++++++++++-- vpr/src/device/rr_graph_obj.h | 24 ++++++++- .../tileable_rr_graph_node_builder.cpp | 8 +++ 4 files changed, 79 insertions(+), 7 deletions(-) diff --git a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml index e66dc8b9c..c3598bd2b 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml @@ -159,7 +159,7 @@ - + diff --git a/vpr/src/device/rr_graph_obj.cpp b/vpr/src/device/rr_graph_obj.cpp index 9124ffdc7..4a6ca1256 100644 --- a/vpr/src/device/rr_graph_obj.cpp +++ b/vpr/src/device/rr_graph_obj.cpp @@ -145,7 +145,7 @@ short RRGraph::node_capacity(const RRNodeId& node) const { short RRGraph::node_ptc_num(const RRNodeId& node) const { VTR_ASSERT_SAFE(valid_node_id(node)); - return node_ptc_nums_[node]; + return node_ptc_nums_[node][0]; } short RRGraph::node_pin_num(const RRNodeId& node) const { @@ -165,6 +165,13 @@ short RRGraph::node_class_num(const RRNodeId& node) const { return node_ptc_num(node); } +std::vector RRGraph::node_track_ids(const RRNodeId& node) const { + VTR_ASSERT_MSG(node_type(node) == CHANX || node_type(node) == CHANY, + "Track number valid only for CHANX/CHANY RR nodes"); + VTR_ASSERT_SAFE(valid_node_id(node)); + return node_ptc_nums_[node]; +} + short RRGraph::node_cost_index(const RRNodeId& node) const { VTR_ASSERT_SAFE(valid_node_id(node)); return node_cost_indices_[node]; @@ -823,7 +830,7 @@ RRNodeId RRGraph::create_node(const t_rr_type& type) { node_bounding_boxes_.emplace_back(-1, -1, -1, -1); node_capacities_.push_back(-1); - node_ptc_nums_.push_back(-1); + node_ptc_nums_.push_back(std::vector(1, -1)); node_cost_indices_.push_back(-1); node_directions_.push_back(NO_DIRECTION); node_sides_.push_back(NUM_SIDES); @@ -999,7 +1006,18 @@ void RRGraph::set_node_capacity(const RRNodeId& node, const short& capacity) { void RRGraph::set_node_ptc_num(const RRNodeId& node, const short& ptc) { VTR_ASSERT(valid_node_id(node)); - node_ptc_nums_[node] = ptc; + /* For CHANX and CHANY, we will resize the ptc num to length of the node + * For other nodes, we will always assign the first element + */ + if ((CHANX == node_type(node)) || (CHANY == node_type(node))) { + if ((size_t)node_length(node) + 1 != node_ptc_nums_[node].size()) { + node_ptc_nums_[node].resize(node_length(node) + 1); + } + std::fill(node_ptc_nums_[node].begin(), node_ptc_nums_[node].end(), ptc); + } else { + VTR_ASSERT(1 == node_ptc_nums_[node].size()); + node_ptc_nums_[node][0] = ptc; + } } void RRGraph::set_node_pin_num(const RRNodeId& node, const short& pin_id) { @@ -1023,6 +1041,22 @@ void RRGraph::set_node_class_num(const RRNodeId& node, const short& class_id) { set_node_ptc_num(node, class_id); } +void RRGraph::add_node_track_num(const RRNodeId& node, + const vtr::Point& node_offset, + const short& track_id) { + VTR_ASSERT(valid_node_id(node)); + VTR_ASSERT_MSG(node_type(node) == CHANX || node_type(node) == CHANY, "Track number valid only for CHANX/CHANY RR nodes"); + + if ((size_t)node_length(node) + 1 != node_ptc_nums_[node].size()) { + node_ptc_nums_[node].resize(node_length(node) + 1); + } + + size_t offset = node_offset.x() - node_xlow(node) + node_offset.y() - node_ylow(node); + VTR_ASSERT(offset < node_ptc_nums_[node].size()); + + node_ptc_nums_[node][offset] = track_id; +} + void RRGraph::set_node_cost_index(const RRNodeId& node, const short& cost_index) { VTR_ASSERT(valid_node_id(node)); node_cost_indices_[node] = cost_index; @@ -1235,10 +1269,18 @@ void RRGraph::build_fast_node_lookup() const { size_t itype = node_type(node); - size_t ptc = node_ptc_num(node); - for (const size_t& x : node_x) { for (const size_t& y : node_y) { + size_t ptc = node_ptc_num(node); + /* Routing channel nodes may have different ptc num + * Find the track ids using the x/y offset + */ + if (CHANX == node_type(node)) { + ptc = node_track_ids(node)[x - node_xlow(node)]; + } else if (CHANY == node_type(node)) { + ptc = node_track_ids(node)[y - node_ylow(node)]; + } + if (ptc >= node_lookup_[x][y][itype].size()) { node_lookup_[x][y][itype].resize(ptc + 1); } diff --git a/vpr/src/device/rr_graph_obj.h b/vpr/src/device/rr_graph_obj.h index 648934554..12a67c150 100644 --- a/vpr/src/device/rr_graph_obj.h +++ b/vpr/src/device/rr_graph_obj.h @@ -413,11 +413,24 @@ class RRGraph { * node_class_num() is designed for routing source and sinks, which are SOURCE and SINK nodes * * Due to a useful identifier, ptc_num is used in building fast look-up + * + * Note: routing channels CHANX and CHANY may have multiple ptc_num due to + * tileable routing architecture, where a routing track may bend + * when passing each SB + * By default, we always return the first ptc_num and track_num for + * these nodee, while the other ptc num is accessible through fast look-up + * when searching for a routing track in a channel + * + * For CHANX, the track id vector is the same as yhigh - ylow + 1 + * For CHANY, the track id vector is the same as xhigh - xlow + 1 + * The first track id is always the track id at (xlow, ylow) + * The last track id is always the track id at (xhigh, yhigh) */ short node_ptc_num(const RRNodeId& node) const; short node_pin_num(const RRNodeId& node) const; short node_track_num(const RRNodeId& node) const; short node_class_num(const RRNodeId& node) const; + std::vector node_track_ids(const RRNodeId& node) const; /* Get the index of cost data in the list of cost_indexed_data data structure * It contains the routing cost for different nodes in the RRGraph @@ -696,6 +709,14 @@ class RRGraph { */ void set_node_class_num(const RRNodeId& node, const short& class_id); + /* Add track id for a CHANX or a CHANY node. + * This is mainly used by tileable rr_graph where rr_node may + * have different track id in different channels + */ + void add_node_track_num(const RRNodeId& node, + const vtr::Point& node_offset, + const short& track_id); + /* Set the routing cost index for node, see node_cost_index() for details */ /* TODO: the cost index should be changed to a StrongId!!! */ void set_node_cost_index(const RRNodeId& node, const short& cost_index); @@ -851,7 +872,8 @@ class RRGraph { vtr::vector> node_bounding_boxes_; vtr::vector node_capacities_; - vtr::vector node_ptc_nums_; + + vtr::vector> node_ptc_nums_; vtr::vector node_cost_indices_; vtr::vector node_directions_; vtr::vector node_sides_; diff --git a/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp b/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp index d8c1fb23b..7b6360877 100644 --- a/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp +++ b/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp @@ -666,6 +666,12 @@ void load_one_chan_rr_nodes_basic_info(RRGraph& rr_graph, rr_graph.set_node_xlow(node, chan_coordinate.x()); rr_graph.set_node_ylow(node, chan_coordinate.y()); + + /* Deposit xhigh and yhigh as the same value as xlow and ylow + * We will update when this track ends + */ + rr_graph.set_node_xhigh(node, chan_coordinate.x()); + rr_graph.set_node_yhigh(node, chan_coordinate.y()); rr_graph.set_node_direction(node, chan_details.get_track_direction(itrack)); rr_graph.set_node_track_num(node, itrack); @@ -709,6 +715,7 @@ void load_one_chan_rr_nodes_basic_info(RRGraph& rr_graph, if ( (rr_graph.node_xhigh(rr_node_id) > rr_graph.node_xlow(rr_node_id)) || (rr_graph.node_yhigh(rr_node_id) > rr_graph.node_ylow(rr_node_id)) ) { rr_node_track_ids[rr_node_id].push_back(itrack); + rr_graph.add_node_track_num(rr_node_id, chan_coordinate, itrack); } /* Finish here, go to next */ } @@ -734,6 +741,7 @@ void load_one_chan_rr_nodes_basic_info(RRGraph& rr_graph, /* Update track_ids */ rr_node_track_ids[rr_node_id].push_back(itrack); + rr_graph.add_node_track_num(rr_node_id, chan_coordinate, itrack); /* Finish here, go to next */ } } From 9a518e8bb6748c408e21e01e0947a581ae264fd0 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 21 Mar 2020 18:07:00 -0600 Subject: [PATCH 327/645] bug fixed for tileable rr_graph builder for more 4x4 fabrics --- openfpga/src/base/openfpga_build_fabric.cpp | 21 +++++++++---------- .../and_k6_frac_adder_chain.openfpga | 2 +- .../k6_frac_N10_adder_chain_40nm.xml | 2 +- .../tileable_rr_graph_node_builder.cpp | 6 ++++++ 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/openfpga/src/base/openfpga_build_fabric.cpp b/openfpga/src/base/openfpga_build_fabric.cpp index a1e90055b..9e315500c 100644 --- a/openfpga/src/base/openfpga_build_fabric.cpp +++ b/openfpga/src/base/openfpga_build_fabric.cpp @@ -30,28 +30,27 @@ void compress_routing_hierarchy(OpenfpgaContext& openfpga_ctx, /* Report the stats */ VTR_LOGV(verbose_output, - "Detected %lu unique X-direction connection blocks from a total of %d (compression rate=%d%)\n", + "Detected %lu unique X-direction connection blocks from a total of %d (compression rate=%.2f%)\n", openfpga_ctx.device_rr_gsb().get_num_cb_unique_module(CHANX), find_device_rr_gsb_num_cb_modules(openfpga_ctx.device_rr_gsb(), CHANX), - 100 * (openfpga_ctx.device_rr_gsb().get_num_cb_unique_module(CHANX) / find_device_rr_gsb_num_cb_modules(openfpga_ctx.device_rr_gsb(), CHANX) - 1)); + 100. * (find_device_rr_gsb_num_cb_modules(openfpga_ctx.device_rr_gsb(), CHANX) / openfpga_ctx.device_rr_gsb().get_num_cb_unique_module(CHANX) - 1.)); VTR_LOGV(verbose_output, - "Detected %lu unique Y-direction connection blocks from a total of %d (compression rate=%d%)\n", + "Detected %lu unique Y-direction connection blocks from a total of %d (compression rate=%.2f%)\n", openfpga_ctx.device_rr_gsb().get_num_cb_unique_module(CHANY), find_device_rr_gsb_num_cb_modules(openfpga_ctx.device_rr_gsb(), CHANY), - 100 * (openfpga_ctx.device_rr_gsb().get_num_cb_unique_module(CHANY) / find_device_rr_gsb_num_cb_modules(openfpga_ctx.device_rr_gsb(), CHANY) - 1)); + 100. * (find_device_rr_gsb_num_cb_modules(openfpga_ctx.device_rr_gsb(), CHANY) / openfpga_ctx.device_rr_gsb().get_num_cb_unique_module(CHANY) - 1.)); VTR_LOGV(verbose_output, - "Detected %lu unique switch blocks from a total of %d (compression rate=%d%)\n", + "Detected %lu unique switch blocks from a total of %d (compression rate=%.2f%)\n", openfpga_ctx.device_rr_gsb().get_num_sb_unique_module(), find_device_rr_gsb_num_sb_modules(openfpga_ctx.device_rr_gsb()), - 100 * (openfpga_ctx.device_rr_gsb().get_num_sb_unique_module() / find_device_rr_gsb_num_sb_modules(openfpga_ctx.device_rr_gsb()) - 1)); + 100. * (find_device_rr_gsb_num_sb_modules(openfpga_ctx.device_rr_gsb()) / openfpga_ctx.device_rr_gsb().get_num_sb_unique_module() - 1)); - VTR_LOGV(verbose_output, - "Detected %lu unique general switch blocks from a total of %d (compression rate=%d%)\n", - openfpga_ctx.device_rr_gsb().get_num_gsb_unique_module(), - find_device_rr_gsb_num_gsb_modules(openfpga_ctx.device_rr_gsb()), - 100 * (openfpga_ctx.device_rr_gsb().get_num_gsb_unique_module() / find_device_rr_gsb_num_gsb_modules(openfpga_ctx.device_rr_gsb()) - 1)); + VTR_LOG("Detected %lu unique general switch blocks from a total of %d (compression rate=%.2f%)\n", + openfpga_ctx.device_rr_gsb().get_num_gsb_unique_module(), + find_device_rr_gsb_num_gsb_modules(openfpga_ctx.device_rr_gsb()), + 100. * (find_device_rr_gsb_num_gsb_modules(openfpga_ctx.device_rr_gsb()) / openfpga_ctx.device_rr_gsb().get_num_gsb_unique_module() - 1.)); } /******************************************************************** diff --git a/openfpga/test_script/and_k6_frac_adder_chain.openfpga b/openfpga/test_script/and_k6_frac_adder_chain.openfpga index 156f49ff0..eb762f384 100644 --- a/openfpga/test_script/and_k6_frac_adder_chain.openfpga +++ b/openfpga/test_script/and_k6_frac_adder_chain.openfpga @@ -22,7 +22,7 @@ lut_truth_table_fixup #--verbose # Build the module graph # - Enabled compression on routing architecture modules # - Enable pin duplication on grid modules -build_fabric --compress_routing --duplicate_grid_pin #--verbose +build_fabric --compress_routing --duplicate_grid_pin --verbose # Repack the netlist to physical pbs # This must be done before bitstream generator and testbench generation diff --git a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml index c3598bd2b..9610fef06 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml @@ -161,7 +161,7 @@ - + diff --git a/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp b/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp index 7b6360877..7191bc523 100644 --- a/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp +++ b/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp @@ -739,6 +739,12 @@ void load_one_chan_rr_nodes_basic_info(RRGraph& rr_graph, VTR_ASSERT(chan_type == rr_graph.node_type(rr_node_id)); VTR_ASSERT(chan_details.get_track_direction(itrack) == rr_graph.node_direction(rr_node_id)); + /* Deposit xhigh and yhigh using the current chan_coordinate + * We will update when this track ends + */ + rr_graph.set_node_xhigh(rr_node_id, chan_coordinate.x()); + rr_graph.set_node_yhigh(rr_node_id, chan_coordinate.y()); + /* Update track_ids */ rr_node_track_ids[rr_node_id].push_back(itrack); rr_graph.add_node_track_num(rr_node_id, chan_coordinate, itrack); From 637be076dceb2eaf09858a5d6db101ea062bdee4 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 21 Mar 2020 18:49:20 -0600 Subject: [PATCH 328/645] adding xml writer for device rr_gsb to help debugging the compress routing; current compress routing is not working --- .../annotation/write_xml_device_rr_gsb.cpp | 190 ++++++++++++++++++ .../src/annotation/write_xml_device_rr_gsb.h | 24 +++ openfpga/src/main.cpp | 4 + .../k6_frac_N10_adder_chain_40nm.xml | 2 +- 4 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 openfpga/src/annotation/write_xml_device_rr_gsb.cpp create mode 100644 openfpga/src/annotation/write_xml_device_rr_gsb.h diff --git a/openfpga/src/annotation/write_xml_device_rr_gsb.cpp b/openfpga/src/annotation/write_xml_device_rr_gsb.cpp new file mode 100644 index 000000000..cd3fb7b43 --- /dev/null +++ b/openfpga/src/annotation/write_xml_device_rr_gsb.cpp @@ -0,0 +1,190 @@ +/*************************************************************************************** + * Output internal structure of DeviceRRGSB to XML format + ***************************************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_log.h" +#include "vtr_assert.h" +#include "vtr_time.h" + +/* Headers from openfpgautil library */ +#include "openfpga_side_manager.h" +#include "openfpga_digest.h" + +#include "openfpga_naming.h" +#include "openfpga_rr_graph_utils.h" + +#include "write_xml_device_rr_gsb.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/*************************************************************************************** + * Output internal structure (only the switch block part) of a RRGSB to XML format + ***************************************************************************************/ +static +void write_rr_switch_block_to_xml(const std::string fname_prefix, + const RRGraph& rr_graph, + const RRGSB& rr_gsb) { + /* Prepare file name */ + std::string fname(fname_prefix); + vtr::Point gsb_coordinate(rr_gsb.get_sb_x(), rr_gsb.get_sb_y()); + fname += generate_switch_block_module_name(gsb_coordinate); + fname += ".xml"; + + VTR_LOG("Output internal structure of Switch Block to '%s'\r", fname.c_str()); + + /* Create a file handler*/ + std::fstream fp; + /* Open a file */ + fp.open(fname, std::fstream::out | std::fstream::trunc); + + /* Validate the file stream */ + check_file_stream(fname.c_str(), fp); + + /* Output location of the Switch Block */ + fp << "" << std::endl; + + /* Output each side */ + for (size_t side = 0; side < rr_gsb.get_num_sides(); ++side) { + SideManager gsb_side_manager(side); + enum e_side gsb_side = gsb_side_manager.get_side(); + + /* Output IPIN nodes */ + for (size_t inode = 0; inode < rr_gsb.get_num_ipin_nodes(gsb_side); ++inode) { + const RRNodeId& cur_rr_node = rr_gsb.get_ipin_node(gsb_side, inode); + /* General information of this IPIN */ + fp << "\t<" << rr_node_typename[rr_graph.node_type(cur_rr_node)] + << " side=\"" << gsb_side_manager.to_string() + << "\" index=\"" << inode + << "\" mux_size=\"" << get_rr_graph_configurable_driver_nodes(rr_graph, cur_rr_node).size() + << "\">" + << std::endl; + /* General information of each driving nodes */ + for (const RRNodeId& driver_node : get_rr_graph_configurable_driver_nodes(rr_graph, cur_rr_node)) { + /* Skip OPINs: they should be in direct connections */ + if (OPIN == rr_graph.node_type(driver_node)) { + continue; + } + + enum e_side chan_side = rr_gsb.get_cb_chan_side(gsb_side); + SideManager chan_side_manager(chan_side); + + /* For channel node, we do not know the node direction + * But we are pretty sure it is either IN_PORT or OUT_PORT + * So we just try and find what is valid + */ + int driver_node_index = rr_gsb.get_chan_node_index(chan_side, driver_node); + /* We must have a valide node index */ + VTR_ASSERT(-1 != driver_node_index); + + const RRSegmentId& des_segment_id = rr_gsb.get_chan_node_segment(chan_side, driver_node_index); + + fp << "\t\t" + << std::endl; + } + fp << "\t" + << std::endl; + } + + /* Output chan nodes */ + for (size_t inode = 0; inode < rr_gsb.get_chan_width(gsb_side); ++inode) { + /* We only care OUT_PORT */ + if (OUT_PORT != rr_gsb.get_chan_node_direction(gsb_side, inode)) { + continue; + } + /* Output drivers */ + const RRNodeId& cur_rr_node = rr_gsb.get_chan_node(gsb_side, inode); + std::vector driver_rr_nodes = get_rr_graph_configurable_driver_nodes(rr_graph, cur_rr_node); + + /* Output node information: location, index, side */ + const RRSegmentId& src_segment_id = rr_gsb.get_chan_node_segment(gsb_side, inode); + + /* Check if this node is directly connected to the node on the opposite side */ + if (true == rr_gsb.is_sb_node_passing_wire(rr_graph, gsb_side, inode)) { + driver_rr_nodes.clear(); + } + + fp << "\t<" << rr_node_typename[rr_graph.node_type(cur_rr_node)] + << " side=\"" << gsb_side_manager.to_string() + << "\" index=\"" << inode + << "\" segment_id=\"" << size_t(src_segment_id) + << "\" mux_size=\"" << driver_rr_nodes.size() + << "\">" + << std::endl; + + /* Direct connection: output the node on the opposite side */ + if (0 == driver_rr_nodes.size()) { + SideManager oppo_side = gsb_side_manager.get_opposite(); + fp << "\t\t" + << std::endl; + } else { + for (const RRNodeId& driver_rr_node : driver_rr_nodes) { + e_side driver_node_side = NUM_SIDES; + int driver_node_index = -1; + rr_gsb.get_node_side_and_index(rr_graph, driver_rr_node, IN_PORT, driver_node_side, driver_node_index); + VTR_ASSERT(-1 != driver_node_index); + SideManager driver_side(driver_node_side); + + if (OPIN == rr_graph.node_type(driver_rr_node)) { + SideManager grid_side(rr_graph.node_side(driver_rr_node)); + fp << "\t\t" + << std::endl; + } else { + const RRSegmentId& des_segment_id = rr_gsb.get_chan_node_segment(driver_node_side, driver_node_index); + fp << "\t\t" + << std::endl; + } + } + } + fp << "\t" + << std::endl; + } + } + + fp << "" + << std::endl; + + /* close a file */ + fp.close(); +} + +/*************************************************************************************** + * Output internal structure (only the switch block part) of all the RRGSBs + * in a DeviceRRGSB to XML format + ***************************************************************************************/ +void write_device_rr_gsb_to_xml(const char* sb_xml_dir, + const RRGraph& rr_graph, + const DeviceRRGSB& device_rr_gsb) { + std::string fname_prefix = format_dir_path(std::string(sb_xml_dir)); + + vtr::Point sb_range = device_rr_gsb.get_gsb_range(); + + /* For each switch block, an XML file will be outputted */ + for (size_t ix = 0; ix < sb_range.x(); ++ix) { + for (size_t iy = 0; iy < sb_range.y(); ++iy) { + const RRGSB& rr_gsb = device_rr_gsb.get_gsb(ix, iy); + write_rr_switch_block_to_xml(fname_prefix, rr_graph, rr_gsb); + } + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/annotation/write_xml_device_rr_gsb.h b/openfpga/src/annotation/write_xml_device_rr_gsb.h new file mode 100644 index 000000000..06c9a0a89 --- /dev/null +++ b/openfpga/src/annotation/write_xml_device_rr_gsb.h @@ -0,0 +1,24 @@ +#ifndef WRITE_XML_DEVICE_RR_GSB_H +#define WRITE_XML_DEVICE_RR_GSB_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include "rr_graph_obj.h" +#include "device_rr_gsb.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void write_device_rr_gsb_to_xml(const char* sb_xml_dir, + const RRGraph& rr_graph, + const DeviceRRGSB& device_rr_gsb); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/main.cpp b/openfpga/src/main.cpp index b2c9d2e6c..eb88c2c65 100644 --- a/openfpga/src/main.cpp +++ b/openfpga/src/main.cpp @@ -2,6 +2,7 @@ * Build the OpenFPGA shell interface *******************************************************************/ /* Header file from vtrutil library */ +#include "vtr_time.h" #include "vtr_log.h" /* Header file from libopenfpgashell library */ @@ -87,11 +88,14 @@ int main(int argc, char** argv) { } else { /* Parse succeed. Start a shell */ if (true == start_cmd_context.option_enable(start_cmd, opt_interactive)) { + + vtr::ScopedStartFinishTimer timer("OpenFPGA operating"); shell.run_interactive_mode(openfpga_context); return 0; } if (true == start_cmd_context.option_enable(start_cmd, opt_script_mode)) { + vtr::ScopedStartFinishTimer timer("OpenFPGA operating"); shell.run_script_mode(start_cmd_context.option_value(start_cmd, opt_script_mode).c_str(), openfpga_context); return 0; diff --git a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml index 9610fef06..eea76837f 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml @@ -161,7 +161,7 @@ - + From 7b9384f3b27e6de27b7af66d650d7524d549ed32 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 21 Mar 2020 19:40:26 -0600 Subject: [PATCH 329/645] add write_gsb command to shell interface --- .../annotation/write_xml_device_rr_gsb.cpp | 24 ++++++++--- .../src/annotation/write_xml_device_rr_gsb.h | 3 +- openfpga/src/base/openfpga_setup_command.cpp | 40 +++++++++++++++++ openfpga/src/base/openfpga_write_gsb.cpp | 43 +++++++++++++++++++ openfpga/src/base/openfpga_write_gsb.h | 23 ++++++++++ .../src/fabric/build_top_module_directs.cpp | 4 +- .../and_k6_frac_adder_chain.openfpga | 5 ++- 7 files changed, 134 insertions(+), 8 deletions(-) create mode 100644 openfpga/src/base/openfpga_write_gsb.cpp create mode 100644 openfpga/src/base/openfpga_write_gsb.h diff --git a/openfpga/src/annotation/write_xml_device_rr_gsb.cpp b/openfpga/src/annotation/write_xml_device_rr_gsb.cpp index cd3fb7b43..4fa4b1f1d 100644 --- a/openfpga/src/annotation/write_xml_device_rr_gsb.cpp +++ b/openfpga/src/annotation/write_xml_device_rr_gsb.cpp @@ -24,14 +24,17 @@ namespace openfpga { static void write_rr_switch_block_to_xml(const std::string fname_prefix, const RRGraph& rr_graph, - const RRGSB& rr_gsb) { + const RRGSB& rr_gsb, + const bool& verbose) { /* Prepare file name */ std::string fname(fname_prefix); vtr::Point gsb_coordinate(rr_gsb.get_sb_x(), rr_gsb.get_sb_y()); fname += generate_switch_block_module_name(gsb_coordinate); fname += ".xml"; - VTR_LOG("Output internal structure of Switch Block to '%s'\r", fname.c_str()); + VTR_LOGV(verbose, + "Output internal structure of Switch Block to '%s'\n", + fname.c_str()); /* Create a file handler*/ std::fstream fp; @@ -173,18 +176,29 @@ void write_rr_switch_block_to_xml(const std::string fname_prefix, ***************************************************************************************/ void write_device_rr_gsb_to_xml(const char* sb_xml_dir, const RRGraph& rr_graph, - const DeviceRRGSB& device_rr_gsb) { - std::string fname_prefix = format_dir_path(std::string(sb_xml_dir)); + const DeviceRRGSB& device_rr_gsb, + const bool& verbose) { + std::string xml_dir_name = format_dir_path(std::string(sb_xml_dir)); + + /* Create directories */ + create_dir_path(xml_dir_name.c_str()); vtr::Point sb_range = device_rr_gsb.get_gsb_range(); + size_t gsb_counter = 0; + /* For each switch block, an XML file will be outputted */ for (size_t ix = 0; ix < sb_range.x(); ++ix) { for (size_t iy = 0; iy < sb_range.y(); ++iy) { const RRGSB& rr_gsb = device_rr_gsb.get_gsb(ix, iy); - write_rr_switch_block_to_xml(fname_prefix, rr_graph, rr_gsb); + write_rr_switch_block_to_xml(xml_dir_name, rr_graph, rr_gsb, verbose); + gsb_counter++; } } + + VTR_LOG("Output %lu XML files to directory '%s'\n", + gsb_counter, + xml_dir_name.c_str()); } } /* end namespace openfpga */ diff --git a/openfpga/src/annotation/write_xml_device_rr_gsb.h b/openfpga/src/annotation/write_xml_device_rr_gsb.h index 06c9a0a89..a6655ab22 100644 --- a/openfpga/src/annotation/write_xml_device_rr_gsb.h +++ b/openfpga/src/annotation/write_xml_device_rr_gsb.h @@ -17,7 +17,8 @@ namespace openfpga { void write_device_rr_gsb_to_xml(const char* sb_xml_dir, const RRGraph& rr_graph, - const DeviceRRGSB& device_rr_gsb); + const DeviceRRGSB& device_rr_gsb, + const bool& verbose); } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_setup_command.cpp b/openfpga/src/base/openfpga_setup_command.cpp index 0bbc8b2a2..8f4b0cec3 100644 --- a/openfpga/src/base/openfpga_setup_command.cpp +++ b/openfpga/src/base/openfpga_setup_command.cpp @@ -9,6 +9,7 @@ #include "openfpga_lut_truth_table_fixup.h" #include "check_netlist_naming_conflict.h" #include "openfpga_build_fabric.h" +#include "openfpga_write_gsb.h" #include "openfpga_setup_command.h" /* begin namespace openfpga */ @@ -95,6 +96,35 @@ ShellCommandId add_openfpga_link_arch_command(openfpga::Shell& return shell_cmd_id; } +/******************************************************************** + * - Add a command to Shell environment: write_gsb_to_xml + * - Add associated options + * - Add command dependency + *******************************************************************/ +static +ShellCommandId add_openfpga_write_gsb_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id, + const std::vector& dependent_cmds) { + Command shell_cmd("write_gsb_to_xml"); + /* Add an option '--file' in short '-f'*/ + CommandOptionId opt_file = shell_cmd.add_option("file", true, "path to the directory that stores the XML files"); + shell_cmd.set_option_short_name(opt_file, "f"); + shell_cmd.set_option_require_value(opt_file, openfpga::OPT_STRING); + + /* Add an option '--verbose' */ + shell_cmd.add_option("verbose", false, "Show verbose outputs"); + + /* Add command 'write_openfpga_arch' to the Shell */ + ShellCommandId shell_cmd_id = shell.add_command(shell_cmd, "write internal structures of General Switch Blocks to XML file"); + shell.set_command_class(shell_cmd_id, cmd_class_id); + shell.set_command_const_execute_function(shell_cmd_id, write_gsb); + + /* Add command dependency to the Shell */ + shell.set_command_dependency(shell_cmd_id, dependent_cmds); + + return shell_cmd_id; +} + /******************************************************************** * - Add a command to Shell environment: check_netlist_naming_conflict * - Add associated options @@ -240,6 +270,16 @@ void add_openfpga_setup_commands(openfpga::Shell& shell) { ShellCommandId link_arch_cmd_id = add_openfpga_link_arch_command(shell, openfpga_setup_cmd_class, link_arch_dependent_cmds); + /******************************** + * Command 'write_gsb' + */ + /* The 'write_gsb' command should NOT be executed before 'link_openfpga_arch' */ + std::vector write_gsb_dependent_cmds; + write_gsb_dependent_cmds.push_back(link_arch_cmd_id); + add_openfpga_write_gsb_command(shell, + openfpga_setup_cmd_class, + write_gsb_dependent_cmds); + /******************************************* * Command 'check_netlist_naming_conflict' */ diff --git a/openfpga/src/base/openfpga_write_gsb.cpp b/openfpga/src/base/openfpga_write_gsb.cpp new file mode 100644 index 000000000..9c367ebfb --- /dev/null +++ b/openfpga/src/base/openfpga_write_gsb.cpp @@ -0,0 +1,43 @@ +/******************************************************************** + * This file includes functions to compress the hierachy of routing architecture + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_time.h" +#include "vtr_log.h" + +#include "write_xml_device_rr_gsb.h" + +#include "openfpga_write_gsb.h" + +/* Include global variables of VPR */ +#include "globals.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Write internal structrure of all the General Switch Blocks (GSBs) + * to an XML file + *******************************************************************/ +void write_gsb(const OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context) { + + /* Check the option '--file' is enabled or not + * Actually, it must be enabled as the shell interface will check + * before reaching this fuction + */ + CommandOptionId opt_file = cmd.option("file"); + VTR_ASSERT(true == cmd_context.option_enable(cmd, opt_file)); + VTR_ASSERT(false == cmd_context.option_value(cmd, opt_file).empty()); + + CommandOptionId opt_verbose = cmd.option("verbose"); + + std::string sb_file_name = cmd_context.option_value(cmd, opt_file); + + write_device_rr_gsb_to_xml(sb_file_name.c_str(), + g_vpr_ctx.device().rr_graph, + openfpga_ctx.device_rr_gsb(), + cmd_context.option_enable(cmd, opt_verbose)); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_write_gsb.h b/openfpga/src/base/openfpga_write_gsb.h new file mode 100644 index 000000000..4f7110717 --- /dev/null +++ b/openfpga/src/base/openfpga_write_gsb.h @@ -0,0 +1,23 @@ +#ifndef OPENFPGA_WRITE_GSB_H +#define OPENFPGA_WRITE_GSB_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "command.h" +#include "command_context.h" +#include "openfpga_context.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void write_gsb(const OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fabric/build_top_module_directs.cpp b/openfpga/src/fabric/build_top_module_directs.cpp index 314b5f2e8..c85fb50f1 100644 --- a/openfpga/src/fabric/build_top_module_directs.cpp +++ b/openfpga/src/fabric/build_top_module_directs.cpp @@ -96,7 +96,9 @@ void add_module_nets_tile_direct_connection(ModuleManager& module_manager, std::string src_port_name = generate_grid_port_name(src_clb_coord, src_pin_width, src_pin_height, src_pin_grid_side, src_tile_pin, false); ModulePortId src_port_id = module_manager.find_module_port(src_grid_module, src_port_name); if (true != module_manager.valid_module_port_id(src_grid_module, src_port_id)) { - VTR_LOG("Fail to find port '%s'\n", src_port_name.c_str()); + VTR_LOG_ERROR("Fail to find port '%s.%s'\n", + src_module_name.c_str(), + src_port_name.c_str()); } VTR_ASSERT(true == module_manager.valid_module_port_id(src_grid_module, src_port_id)); VTR_ASSERT(1 == module_manager.module_port(src_grid_module, src_port_id).get_width()); diff --git a/openfpga/test_script/and_k6_frac_adder_chain.openfpga b/openfpga/test_script/and_k6_frac_adder_chain.openfpga index eb762f384..d151b0089 100644 --- a/openfpga/test_script/and_k6_frac_adder_chain.openfpga +++ b/openfpga/test_script/and_k6_frac_adder_chain.openfpga @@ -10,6 +10,9 @@ read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga # Annotate the OpenFPGA architecture to VPR data base link_openfpga_arch --activity_file ./test_blif/and.act --sort_gsb_chan_node_in_edges #--verbose +# Write GSB to XML for debugging +write_gsb_to_xml --file /var/tmp/xtang/openfpga_test_src/gsb_xml + # Check and correct any naming conflicts in the BLIF netlist check_netlist_naming_conflict --fix --report ./netlist_renaming.xml @@ -22,7 +25,7 @@ lut_truth_table_fixup #--verbose # Build the module graph # - Enabled compression on routing architecture modules # - Enable pin duplication on grid modules -build_fabric --compress_routing --duplicate_grid_pin --verbose +build_fabric --compress_routing --duplicate_grid_pin #--verbose # Repack the netlist to physical pbs # This must be done before bitstream generator and testbench generation From fc6abc13fd868181437253a96f6d7e5ade842504 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 21 Mar 2020 21:02:47 -0600 Subject: [PATCH 330/645] add physical tile utils to identify pins that have Fc=0 --- .../build_grid_module_duplicated_pins.cpp | 9 +++-- .../fabric/build_top_module_connection.cpp | 3 +- .../src/fabric/build_top_module_directs.cpp | 4 +-- .../utils/openfpga_physical_tile_utils.cpp | 33 +++++++++++++++++++ .../src/utils/openfpga_physical_tile_utils.h | 24 ++++++++++++++ openfpga/test_vpr_arch/k6_frac_N10_40nm.xml | 6 ++-- .../k6_frac_N10_adder_chain_40nm.xml | 2 +- 7 files changed, 73 insertions(+), 8 deletions(-) create mode 100644 openfpga/src/utils/openfpga_physical_tile_utils.cpp create mode 100644 openfpga/src/utils/openfpga_physical_tile_utils.h diff --git a/openfpga/src/fabric/build_grid_module_duplicated_pins.cpp b/openfpga/src/fabric/build_grid_module_duplicated_pins.cpp index d63b85998..0f4a86934 100644 --- a/openfpga/src/fabric/build_grid_module_duplicated_pins.cpp +++ b/openfpga/src/fabric/build_grid_module_duplicated_pins.cpp @@ -8,6 +8,8 @@ * * Please follow this rules when creating new features! *******************************************************************/ +#include + /* Headers from vtrutil library */ #include "vtr_assert.h" @@ -17,6 +19,8 @@ #include "openfpga_naming.h" #include "openfpga_interconnect_types.h" +#include "openfpga_physical_tile_utils.h" + #include "build_grid_module_utils.h" #include "build_grid_module_duplicated_pins.h" @@ -88,7 +92,8 @@ void add_grid_module_duplicated_pb_type_ports(ModuleManager& module_manager, * we do not duplicate in these cases */ if ( (RECEIVER == pin_class_type) /* Xifan: I assume that each direct connection pin must have Fc=0. */ - || ( (DRIVER == pin_class_type) && (0. == grid_type_descriptor->fc_specs[ipin].fc_value) ) ) { + || ( (DRIVER == pin_class_type) + && (0. == find_physical_tile_pin_Fc(grid_type_descriptor, ipin)) ) ) { vtr::Point dummy_coordinate; std::string port_name = generate_grid_port_name(dummy_coordinate, iwidth, iheight, side, ipin, false); BasicPort grid_port(port_name, 0, 0); @@ -169,7 +174,7 @@ void add_grid_module_net_connect_duplicated_pb_graph_pin(ModuleManager& module_m * Follow the traditional recipe when adding nets! * Xifan: I assume that each direct connection pin must have Fc=0. */ - if (0. == grid_type_descriptor->fc_specs[grid_pin_index].fc_value) { + if (0. == find_physical_tile_pin_Fc(grid_type_descriptor, grid_pin_index)) { /* Create a net to connect the grid pin to child module pin */ ModuleNetId net = module_manager.create_module_net(grid_module); /* Find the port in grid_module */ diff --git a/openfpga/src/fabric/build_top_module_connection.cpp b/openfpga/src/fabric/build_top_module_connection.cpp index f1895305e..0af6e3409 100644 --- a/openfpga/src/fabric/build_top_module_connection.cpp +++ b/openfpga/src/fabric/build_top_module_connection.cpp @@ -12,6 +12,7 @@ #include "openfpga_naming.h" #include "pb_type_utils.h" #include "rr_gsb_utils.h" +#include "openfpga_physical_tile_utils.h" #include "build_top_module_utils.h" #include "build_top_module_connection.h" @@ -232,7 +233,7 @@ void add_top_module_nets_connect_grids_and_sb_with_duplicated_pins(ModuleManager * For other duplicated pins, we follow the new naming */ std::string src_grid_port_name; - if (0. == grids[grid_coordinate.x()][grid_coordinate.y()].type->fc_specs[src_grid_pin_index].fc_value) { + if (0. == find_physical_tile_pin_Fc(grids[grid_coordinate.x()][grid_coordinate.y()].type, src_grid_pin_index)) { src_grid_port_name = generate_grid_port_name(grid_coordinate, src_grid_pin_width, src_grid_pin_height, rr_graph.node_side(rr_gsb.get_opin_node(side_manager.get_side(), inode)), src_grid_pin_index, false); diff --git a/openfpga/src/fabric/build_top_module_directs.cpp b/openfpga/src/fabric/build_top_module_directs.cpp index c85fb50f1..81f589adb 100644 --- a/openfpga/src/fabric/build_top_module_directs.cpp +++ b/openfpga/src/fabric/build_top_module_directs.cpp @@ -96,8 +96,8 @@ void add_module_nets_tile_direct_connection(ModuleManager& module_manager, std::string src_port_name = generate_grid_port_name(src_clb_coord, src_pin_width, src_pin_height, src_pin_grid_side, src_tile_pin, false); ModulePortId src_port_id = module_manager.find_module_port(src_grid_module, src_port_name); if (true != module_manager.valid_module_port_id(src_grid_module, src_port_id)) { - VTR_LOG_ERROR("Fail to find port '%s.%s'\n", - src_module_name.c_str(), + VTR_LOG_ERROR("Fail to find port '%s[%lu][%lu].%s'\n", + src_module_name.c_str(), src_clb_coord.x(), src_clb_coord.y(), src_port_name.c_str()); } VTR_ASSERT(true == module_manager.valid_module_port_id(src_grid_module, src_port_id)); diff --git a/openfpga/src/utils/openfpga_physical_tile_utils.cpp b/openfpga/src/utils/openfpga_physical_tile_utils.cpp new file mode 100644 index 000000000..9b70f7f97 --- /dev/null +++ b/openfpga/src/utils/openfpga_physical_tile_utils.cpp @@ -0,0 +1,33 @@ +/*************************************************************************************** + * This file includes most utilized functions that are used to acquire data from + * VPR t_physical_tile_type + ***************************************************************************************/ + +/* Headers from vtrutil library */ +#include "vtr_log.h" +#include "vtr_assert.h" +#include "vtr_time.h" + +#include "openfpga_physical_tile_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Find the Fc of a pin in physical tile + *******************************************************************/ +float find_physical_tile_pin_Fc(t_physical_tile_type_ptr type, + const int& pin) { + for (const t_fc_specification& fc_spec : type->fc_specs) { + if (fc_spec.pins.end() != std::find(fc_spec.pins.begin(), fc_spec.pins.end(), pin)) { + return fc_spec.fc_value; + } + } + /* Every pin should have a Fc, give a wrong value */ + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Fail to find the Fc for %s.pin[%lu]\n", + type->name, pin); + exit(1); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/utils/openfpga_physical_tile_utils.h b/openfpga/src/utils/openfpga_physical_tile_utils.h new file mode 100644 index 000000000..451d931a8 --- /dev/null +++ b/openfpga/src/utils/openfpga_physical_tile_utils.h @@ -0,0 +1,24 @@ +#ifndef OPENFPGA_PHYSICAL_TILE_UTILS_H +#define OPENFPGA_PHYSICAL_TILE_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "physical_types.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +float find_physical_tile_pin_Fc(t_physical_tile_type_ptr type, + const int& pin); + + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml index 8f58cb221..2c528c13c 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml @@ -77,13 +77,15 @@ - + + - + + - + From 3958ac24948cac78791d2ac6233a64ed9771a577 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 22 Mar 2020 15:26:15 -0600 Subject: [PATCH 331/645] fix bugs in flow manager on default compress routing problems --- openfpga/src/base/openfpga_build_fabric.cpp | 2 ++ openfpga/src/base/openfpga_flow_manager.cpp | 8 ++++++++ openfpga/src/base/openfpga_flow_manager.h | 2 ++ openfpga/test_script/and_k6_frac_adder_chain.openfpga | 2 +- openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml | 2 +- vpr/src/tileable_rr_graph/rr_gsb.cpp | 2 +- 6 files changed, 15 insertions(+), 3 deletions(-) diff --git a/openfpga/src/base/openfpga_build_fabric.cpp b/openfpga/src/base/openfpga_build_fabric.cpp index 9e315500c..97c0c68be 100644 --- a/openfpga/src/base/openfpga_build_fabric.cpp +++ b/openfpga/src/base/openfpga_build_fabric.cpp @@ -65,6 +65,8 @@ void build_fabric(OpenfpgaContext& openfpga_ctx, if (true == cmd_context.option_enable(cmd, opt_compress_routing)) { compress_routing_hierarchy(openfpga_ctx, cmd_context.option_enable(cmd, opt_verbose)); + /* Update flow manager to enable compress routing */ + openfpga_ctx.mutable_flow_manager().set_compress_routing(true); } VTR_LOG("\n"); diff --git a/openfpga/src/base/openfpga_flow_manager.cpp b/openfpga/src/base/openfpga_flow_manager.cpp index bc97f291d..1a50524df 100644 --- a/openfpga/src/base/openfpga_flow_manager.cpp +++ b/openfpga/src/base/openfpga_flow_manager.cpp @@ -8,6 +8,14 @@ /* begin namespace openfpga */ namespace openfpga { +/************************************************** + * Public Constructor + *************************************************/ +FlowManager::FlowManager() { + /* Turn off compress_routing as default */ + compress_routing_ = false; +} + /************************************************** * Public Accessors *************************************************/ diff --git a/openfpga/src/base/openfpga_flow_manager.h b/openfpga/src/base/openfpga_flow_manager.h index 6a6ae7c75..e9f6f02c2 100644 --- a/openfpga/src/base/openfpga_flow_manager.h +++ b/openfpga/src/base/openfpga_flow_manager.h @@ -15,6 +15,8 @@ namespace openfpga { * *******************************************************************/ class FlowManager { + public: /* Public constructor */ + FlowManager(); public: /* Public accessors */ bool compress_routing() const; public: /* Public mutators */ diff --git a/openfpga/test_script/and_k6_frac_adder_chain.openfpga b/openfpga/test_script/and_k6_frac_adder_chain.openfpga index d151b0089..3cdebb2b1 100644 --- a/openfpga/test_script/and_k6_frac_adder_chain.openfpga +++ b/openfpga/test_script/and_k6_frac_adder_chain.openfpga @@ -25,7 +25,7 @@ lut_truth_table_fixup #--verbose # Build the module graph # - Enabled compression on routing architecture modules # - Enable pin duplication on grid modules -build_fabric --compress_routing --duplicate_grid_pin #--verbose +build_fabric --compress_routing --duplicate_grid_pin --verbose # Repack the netlist to physical pbs # This must be done before bitstream generator and testbench generation diff --git a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml index c3598bd2b..eea76837f 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml @@ -161,7 +161,7 @@ - + diff --git a/vpr/src/tileable_rr_graph/rr_gsb.cpp b/vpr/src/tileable_rr_graph/rr_gsb.cpp index b489182b2..f5d6ec6ca 100644 --- a/vpr/src/tileable_rr_graph/rr_gsb.cpp +++ b/vpr/src/tileable_rr_graph/rr_gsb.cpp @@ -975,7 +975,7 @@ bool RRGSB::is_sb_node_mirror(const RRGraph& rr_graph, /* Use unsorted/sorted edges */ std::vector node_in_edges = get_chan_node_in_edges(rr_graph, node_side, track_id); - std::vector cand_node_in_edges = cand. get_chan_node_in_edges(rr_graph, node_side, track_id); + std::vector cand_node_in_edges = cand.get_chan_node_in_edges(rr_graph, node_side, track_id); VTR_ASSERT(node_in_edges.size() == cand_node_in_edges.size()); for (size_t iedge = 0; iedge < node_in_edges.size(); ++iedge) { From fdf6a6bd3e853555f5c12b02b5923302e7f3f8aa Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 22 Mar 2020 15:48:11 -0600 Subject: [PATCH 332/645] use chan_node_in_edges from rr_gsb in XML writer --- openfpga/src/annotation/write_xml_device_rr_gsb.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openfpga/src/annotation/write_xml_device_rr_gsb.cpp b/openfpga/src/annotation/write_xml_device_rr_gsb.cpp index 4fa4b1f1d..906c71d70 100644 --- a/openfpga/src/annotation/write_xml_device_rr_gsb.cpp +++ b/openfpga/src/annotation/write_xml_device_rr_gsb.cpp @@ -103,26 +103,26 @@ void write_rr_switch_block_to_xml(const std::string fname_prefix, } /* Output drivers */ const RRNodeId& cur_rr_node = rr_gsb.get_chan_node(gsb_side, inode); - std::vector driver_rr_nodes = get_rr_graph_configurable_driver_nodes(rr_graph, cur_rr_node); + std::vector driver_rr_edges = rr_gsb.get_chan_node_in_edges(rr_graph, gsb_side, inode); /* Output node information: location, index, side */ const RRSegmentId& src_segment_id = rr_gsb.get_chan_node_segment(gsb_side, inode); /* Check if this node is directly connected to the node on the opposite side */ if (true == rr_gsb.is_sb_node_passing_wire(rr_graph, gsb_side, inode)) { - driver_rr_nodes.clear(); + driver_rr_edges.clear(); } fp << "\t<" << rr_node_typename[rr_graph.node_type(cur_rr_node)] << " side=\"" << gsb_side_manager.to_string() << "\" index=\"" << inode << "\" segment_id=\"" << size_t(src_segment_id) - << "\" mux_size=\"" << driver_rr_nodes.size() + << "\" mux_size=\"" << driver_rr_edges.size() << "\">" << std::endl; /* Direct connection: output the node on the opposite side */ - if (0 == driver_rr_nodes.size()) { + if (0 == driver_rr_edges.size()) { SideManager oppo_side = gsb_side_manager.get_opposite(); fp << "\t\t" << std::endl; } else { - for (const RRNodeId& driver_rr_node : driver_rr_nodes) { + for (const RREdgeId& driver_rr_edge : driver_rr_edges) { + const RRNodeId& driver_rr_node = rr_graph.edge_src_node(driver_rr_edge); e_side driver_node_side = NUM_SIDES; int driver_node_index = -1; rr_gsb.get_node_side_and_index(rr_graph, driver_rr_node, IN_PORT, driver_node_side, driver_node_index); From ff474d87de980607657b1f7827554d45dda3dd9e Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 22 Mar 2020 16:11:00 -0600 Subject: [PATCH 333/645] fixed critical bug in uniquifying GSBs. Now it can guarantee minimum number of unique GSBs --- openfpga/src/annotation/device_rr_gsb.cpp | 1 + openfpga/src/base/openfpga_build_fabric.cpp | 2 +- vpr/src/tileable_rr_graph/rr_gsb.cpp | 15 +++++++-------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/openfpga/src/annotation/device_rr_gsb.cpp b/openfpga/src/annotation/device_rr_gsb.cpp index 1fcb16109..c10a9df9c 100644 --- a/openfpga/src/annotation/device_rr_gsb.cpp +++ b/openfpga/src/annotation/device_rr_gsb.cpp @@ -267,6 +267,7 @@ void DeviceRRGSB::build_sb_unique_module(const RRGraph& rr_graph) { break; } } + /* Add to list if this is a unique mirror*/ if (true == is_unique_module) { sb_unique_module_.push_back(sb_coordinate); diff --git a/openfpga/src/base/openfpga_build_fabric.cpp b/openfpga/src/base/openfpga_build_fabric.cpp index 97c0c68be..0e16018bd 100644 --- a/openfpga/src/base/openfpga_build_fabric.cpp +++ b/openfpga/src/base/openfpga_build_fabric.cpp @@ -45,7 +45,7 @@ void compress_routing_hierarchy(OpenfpgaContext& openfpga_ctx, "Detected %lu unique switch blocks from a total of %d (compression rate=%.2f%)\n", openfpga_ctx.device_rr_gsb().get_num_sb_unique_module(), find_device_rr_gsb_num_sb_modules(openfpga_ctx.device_rr_gsb()), - 100. * (find_device_rr_gsb_num_sb_modules(openfpga_ctx.device_rr_gsb()) / openfpga_ctx.device_rr_gsb().get_num_sb_unique_module() - 1)); + 100. * (find_device_rr_gsb_num_sb_modules(openfpga_ctx.device_rr_gsb()) / openfpga_ctx.device_rr_gsb().get_num_sb_unique_module() - 1.)); VTR_LOG("Detected %lu unique general switch blocks from a total of %d (compression rate=%.2f%)\n", openfpga_ctx.device_rr_gsb().get_num_gsb_unique_module(), diff --git a/vpr/src/tileable_rr_graph/rr_gsb.cpp b/vpr/src/tileable_rr_graph/rr_gsb.cpp index f5d6ec6ca..fcb7eba11 100644 --- a/vpr/src/tileable_rr_graph/rr_gsb.cpp +++ b/vpr/src/tileable_rr_graph/rr_gsb.cpp @@ -952,8 +952,6 @@ bool RRGSB::is_sb_node_mirror(const RRGraph& rr_graph, const e_side& node_side, const size_t& track_id) const { /* Ensure rr_nodes are either the output of short-connection or multiplexer */ - RRNodeId node = this->get_chan_node(node_side, track_id); - RRNodeId cand_node = cand.get_chan_node(node_side, track_id); bool is_short_conkt = this->is_sb_node_passing_wire(rr_graph, node_side, track_id); if (is_short_conkt != cand.is_sb_node_passing_wire(rr_graph, node_side, track_id)) { @@ -968,14 +966,15 @@ bool RRGSB::is_sb_node_mirror(const RRGraph& rr_graph, return true; } - /* For non-passing wires, check driving rr_nodes */ - if (rr_graph.node_in_edges(node).size() != rr_graph.node_in_edges(cand_node).size()) { - return false; - } - /* Use unsorted/sorted edges */ std::vector node_in_edges = get_chan_node_in_edges(rr_graph, node_side, track_id); std::vector cand_node_in_edges = cand.get_chan_node_in_edges(rr_graph, node_side, track_id); + + /* For non-passing wires, check driving rr_nodes */ + if (node_in_edges.size() != cand_node_in_edges.size()) { + return false; + } + VTR_ASSERT(node_in_edges.size() == cand_node_in_edges.size()); for (size_t iedge = 0; iedge < node_in_edges.size(); ++iedge) { @@ -984,7 +983,7 @@ bool RRGSB::is_sb_node_mirror(const RRGraph& rr_graph, RRNodeId src_node = rr_graph.edge_src_node(src_edge); RRNodeId src_cand_node = rr_graph.edge_src_node(src_cand_edge); /* node type should be the same */ - if (rr_graph.node_type(src_node) != rr_graph.node_type(cand_node)) { + if (rr_graph.node_type(src_node) != rr_graph.node_type(src_cand_node)) { return false; } /* switch type should be the same */ From 9e4e12aae94d847f3a57b67037be20c7b33ebbe3 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 22 Mar 2020 16:13:04 -0600 Subject: [PATCH 334/645] fixed echo message in the compression rate of gsb uniquifying --- openfpga/src/base/openfpga_build_fabric.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openfpga/src/base/openfpga_build_fabric.cpp b/openfpga/src/base/openfpga_build_fabric.cpp index 0e16018bd..4e8b44916 100644 --- a/openfpga/src/base/openfpga_build_fabric.cpp +++ b/openfpga/src/base/openfpga_build_fabric.cpp @@ -33,24 +33,24 @@ void compress_routing_hierarchy(OpenfpgaContext& openfpga_ctx, "Detected %lu unique X-direction connection blocks from a total of %d (compression rate=%.2f%)\n", openfpga_ctx.device_rr_gsb().get_num_cb_unique_module(CHANX), find_device_rr_gsb_num_cb_modules(openfpga_ctx.device_rr_gsb(), CHANX), - 100. * (find_device_rr_gsb_num_cb_modules(openfpga_ctx.device_rr_gsb(), CHANX) / openfpga_ctx.device_rr_gsb().get_num_cb_unique_module(CHANX) - 1.)); + 100. * ((float)find_device_rr_gsb_num_cb_modules(openfpga_ctx.device_rr_gsb(), CHANX) / (float)openfpga_ctx.device_rr_gsb().get_num_cb_unique_module(CHANX) - 1.)); VTR_LOGV(verbose_output, "Detected %lu unique Y-direction connection blocks from a total of %d (compression rate=%.2f%)\n", openfpga_ctx.device_rr_gsb().get_num_cb_unique_module(CHANY), find_device_rr_gsb_num_cb_modules(openfpga_ctx.device_rr_gsb(), CHANY), - 100. * (find_device_rr_gsb_num_cb_modules(openfpga_ctx.device_rr_gsb(), CHANY) / openfpga_ctx.device_rr_gsb().get_num_cb_unique_module(CHANY) - 1.)); + 100. * ((float)find_device_rr_gsb_num_cb_modules(openfpga_ctx.device_rr_gsb(), CHANY) / (float)openfpga_ctx.device_rr_gsb().get_num_cb_unique_module(CHANY) - 1.)); VTR_LOGV(verbose_output, "Detected %lu unique switch blocks from a total of %d (compression rate=%.2f%)\n", openfpga_ctx.device_rr_gsb().get_num_sb_unique_module(), find_device_rr_gsb_num_sb_modules(openfpga_ctx.device_rr_gsb()), - 100. * (find_device_rr_gsb_num_sb_modules(openfpga_ctx.device_rr_gsb()) / openfpga_ctx.device_rr_gsb().get_num_sb_unique_module() - 1.)); + 100. * ((float)find_device_rr_gsb_num_sb_modules(openfpga_ctx.device_rr_gsb()) / (float)openfpga_ctx.device_rr_gsb().get_num_sb_unique_module() - 1.)); VTR_LOG("Detected %lu unique general switch blocks from a total of %d (compression rate=%.2f%)\n", openfpga_ctx.device_rr_gsb().get_num_gsb_unique_module(), find_device_rr_gsb_num_gsb_modules(openfpga_ctx.device_rr_gsb()), - 100. * (find_device_rr_gsb_num_gsb_modules(openfpga_ctx.device_rr_gsb()) / openfpga_ctx.device_rr_gsb().get_num_gsb_unique_module() - 1.)); + 100. * ((float)find_device_rr_gsb_num_gsb_modules(openfpga_ctx.device_rr_gsb()) / (float)openfpga_ctx.device_rr_gsb().get_num_gsb_unique_module() - 1.)); } /******************************************************************** From 75dfe6a045406746bcffd0c9ca463e0cfadf3254 Mon Sep 17 00:00:00 2001 From: Xifan Tang Date: Sun, 22 Mar 2020 16:21:35 -0600 Subject: [PATCH 335/645] update documentation for write_gsb_to_xml functionality --- docs/source/openfpga_shell/openfpga_commands.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/source/openfpga_shell/openfpga_commands.rst b/docs/source/openfpga_shell/openfpga_commands.rst index edb2bf0ad..6551b7421 100644 --- a/docs/source/openfpga_shell/openfpga_commands.rst +++ b/docs/source/openfpga_shell/openfpga_commands.rst @@ -52,6 +52,16 @@ Setup OpenFPGA - ``--verbose`` Show verbose log +.. option:: write_gsb_to_xml + + Write the internal structure of General Switch Blocks (GSBs) across a FPGA fabric, including the interconnection between the nodes and node-level details, to XML files + + - ``--file`` or ``-f`` Specify the output directory of the XML files. Each GSB will be written to an indepedent XML file + + - ``--verbose`` Show verbose log + + .. note:: This command is used to help users to study the difference between GSBs + .. option:: check_netlist_naming_conflict Check and correct any naming conflicts in the BLIF netlist From 7e3a8e5794c9c3fd448dd76c6ae7ca838bd9b615 Mon Sep 17 00:00:00 2001 From: Xifan Tang Date: Sun, 22 Mar 2020 16:27:12 -0600 Subject: [PATCH 336/645] typo fixed in fpga-bitstream documentation --- docs/source/fpga_bitstream/file_organization.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/source/fpga_bitstream/file_organization.rst b/docs/source/fpga_bitstream/file_organization.rst index c8476d1f9..d8190ac67 100644 --- a/docs/source/fpga_bitstream/file_organization.rst +++ b/docs/source/fpga_bitstream/file_organization.rst @@ -1,9 +1,12 @@ -Bistream Output File Format +Bitstream Output File Format ~~~~~~~~~~~~~~~~~~~~~~~~~~~ FPGA-Bitstream can generate two types of bitstreams: + * Generic bitstreams, where configuration bits are organized out-of-order in a database. We output the generic bitstream to a XML format, which is easy to debug. As shown in the following XML code, configuration bits are organized block by block, where each block could be a LUT, a routing multiplexer `etc`. Each ``bitstream_block`` includes two sets of information: + - ``hierarchy`` represents the location of this block in FPGA fabric. + - ``bitstream`` represents the configuration bits affiliated to this block. .. code-block:: xml From 08b46af7beb2d08a6dfd7f7eccdd1e82e2200105 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 24 Mar 2020 12:20:51 -0600 Subject: [PATCH 337/645] add micro architecture for heterogeneous FPGA with single-mode DPRAM --- .../k6_frac_N10_adder_chain_mem16K_40nm.xml | 734 ++++++++++++++++++ 1 file changed, 734 insertions(+) create mode 100644 openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml diff --git a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml new file mode 100644 index 000000000..850eb5450 --- /dev/null +++ b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml @@ -0,0 +1,734 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + + + + + + + + + + + + + + + + + + + clb.clk + clb.cin + clb.O[9:0] clb.I[19:0] + clb.cout clb.O[19:10] clb.I[39:20] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 235e-12 + 235e-12 + 235e-12 + 235e-12 + 235e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 195e-12 + 195e-12 + 195e-12 + 195e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 8a996ceae514e2b4add1c9308f15e9bd39a8c29a Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 24 Mar 2020 13:02:35 -0600 Subject: [PATCH 338/645] bug fixed in tileable routing when heterogeneous blocks are considered; VPR have special rules in checking the coordinates of SOURCE and SINK nodes, which is very different from the OPIN and IPIN nodes Show respect to it here. --- .../test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml | 2 +- .../tileable_rr_graph/tileable_rr_graph_node_builder.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml index 850eb5450..87e6ff937 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml @@ -193,7 +193,7 @@ - + diff --git a/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp b/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp index 7191bc523..da77d536e 100644 --- a/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp +++ b/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp @@ -492,8 +492,8 @@ void load_one_grid_source_nodes_basic_info(RRGraph& rr_graph, /* node bounding box */ rr_graph.set_node_bounding_box(node, vtr::Rect(grid_coordinate.x(), grid_coordinate.y(), - grid_coordinate.x(), - grid_coordinate.y())); + grid_coordinate.x() + cur_grid.type->width - 1, + grid_coordinate.y() + cur_grid.type->height - 1)); rr_graph.set_node_class_num(node, iclass); rr_graph.set_node_capacity(node, 1); @@ -543,8 +543,8 @@ void load_one_grid_sink_nodes_basic_info(RRGraph& rr_graph, /* node bounding box */ rr_graph.set_node_bounding_box(node, vtr::Rect(grid_coordinate.x(), grid_coordinate.y(), - grid_coordinate.x(), - grid_coordinate.y())); + grid_coordinate.x() + cur_grid.type->width - 1, + grid_coordinate.y() + cur_grid.type->height - 1)); rr_graph.set_node_class_num(node, iclass); rr_graph.set_node_capacity(node, 1); From 610c71671fabd9dd1fef1bb01250a01274a83e57 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 24 Mar 2020 16:47:45 -0600 Subject: [PATCH 339/645] experimentally developing through channels inside multi-width and multi-height grids. Still debugging. --- libs/libarchfpga/src/physical_types.h | 2 + libs/libarchfpga/src/read_xml_arch_file.cpp | 3 +- .../k6_frac_N10_adder_chain_mem16K_40nm.xml | 2 +- vpr/src/base/SetupVPR.cpp | 1 + vpr/src/base/place_and_route.cpp | 3 +- vpr/src/base/vpr_api.cpp | 3 +- vpr/src/base/vpr_types.h | 1 + vpr/src/route/route_common.cpp | 3 +- vpr/src/route/rr_graph.cpp | 1 + .../rr_graph_builder_utils.cpp | 53 +++--- .../rr_graph_builder_utils.h | 12 +- .../tileable_rr_graph_builder.cpp | 7 +- .../tileable_rr_graph_builder.h | 1 + .../tileable_rr_graph_node_builder.cpp | 160 ++++++++++++------ .../tileable_rr_graph_node_builder.h | 6 +- 15 files changed, 170 insertions(+), 88 deletions(-) diff --git a/libs/libarchfpga/src/physical_types.h b/libs/libarchfpga/src/physical_types.h index d1cfae62a..f5f2c3c7e 100644 --- a/libs/libarchfpga/src/physical_types.h +++ b/libs/libarchfpga/src/physical_types.h @@ -1590,7 +1590,9 @@ struct t_clock_arch_spec { struct t_arch { char* architecture_id; //Secure hash digest of the architecture file to uniquely identify this architecture + /* Xifan Tang: options for tileable routing architectures */ bool tileable; + bool through_channel; t_chan_width_dist Chans; enum e_switch_block_type SBType; diff --git a/libs/libarchfpga/src/read_xml_arch_file.cpp b/libs/libarchfpga/src/read_xml_arch_file.cpp index cf7c97133..cabb17bbb 100644 --- a/libs/libarchfpga/src/read_xml_arch_file.cpp +++ b/libs/libarchfpga/src/read_xml_arch_file.cpp @@ -2536,7 +2536,8 @@ static void ProcessLayout(pugi::xml_node layout_tag, t_arch* arch, const pugiuti //Expect only tileable attributes on //expect_only_attributes(layout_tag, {"tileable"}, loc_data); - arch->tileable = get_attribute(layout_tag, "tileable", loc_data).as_bool(false); + arch->tileable = get_attribute(layout_tag, "tileable", loc_data, ReqOpt::OPTIONAL).as_bool(false); + arch->through_channel = get_attribute(layout_tag, "through_channel", loc_data, ReqOpt::OPTIONAL).as_bool(false); //Count the number of or tags size_t auto_layout_cnt = 0; diff --git a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml index 87e6ff937..9422c3a09 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml @@ -193,7 +193,7 @@ - + diff --git a/vpr/src/base/SetupVPR.cpp b/vpr/src/base/SetupVPR.cpp index a43f5b80a..8e806d515 100644 --- a/vpr/src/base/SetupVPR.cpp +++ b/vpr/src/base/SetupVPR.cpp @@ -324,6 +324,7 @@ static void SetupRoutingArch(const t_arch& Arch, /* Copy the tileable routing setting */ RoutingArch->tileable = Arch.tileable; + RoutingArch->through_channel = Arch.through_channel; } static void SetupRouterOpts(const t_options& Options, t_router_opts* RouterOpts) { diff --git a/vpr/src/base/place_and_route.cpp b/vpr/src/base/place_and_route.cpp index c13a1e393..2d5946ad5 100644 --- a/vpr/src/base/place_and_route.cpp +++ b/vpr/src/base/place_and_route.cpp @@ -359,7 +359,8 @@ int binary_search_place_and_route(const t_placer_opts& placer_opts_ref, segment_inf, router_opts.base_cost_type, router_opts.trim_empty_channels, - router_opts.trim_obs_channels, + /* Xifan tang: The trimming on obstacle(through) channel inside multi-height and multi-width grids are not open to command-line options. OpenFPGA opens this options through an XML syntax */ + router_opts.trim_obs_channels || det_routing_arch->through_channel, router_opts.clock_modeling, arch->Directs, arch->num_directs, &warnings); diff --git a/vpr/src/base/vpr_api.cpp b/vpr/src/base/vpr_api.cpp index 16a6cc708..846c0779a 100644 --- a/vpr/src/base/vpr_api.cpp +++ b/vpr/src/base/vpr_api.cpp @@ -851,7 +851,8 @@ void vpr_create_rr_graph(t_vpr_setup& vpr_setup, const t_arch& arch, int chan_wi vpr_setup.Segments, router_opts.base_cost_type, router_opts.trim_empty_channels, - router_opts.trim_obs_channels, + /* Xifan tang: The trimming on obstacle(through) channel inside multi-height and multi-width grids are not open to command-line options. OpenFPGA opens this options through an XML syntax */ + router_opts.trim_obs_channels || det_routing_arch->through_channel, router_opts.clock_modeling, arch.Directs, arch.num_directs, &warnings); diff --git a/vpr/src/base/vpr_types.h b/vpr/src/base/vpr_types.h index 8d03979fd..f6696dd9f 100644 --- a/vpr/src/base/vpr_types.h +++ b/vpr/src/base/vpr_types.h @@ -1012,6 +1012,7 @@ struct t_det_routing_arch { /* Xifan Tang: tileable routing */ bool tileable; + bool through_channel; short global_route_switch; short delayless_switch; diff --git a/vpr/src/route/route_common.cpp b/vpr/src/route/route_common.cpp index 5ef55df61..4e1cda2ae 100644 --- a/vpr/src/route/route_common.cpp +++ b/vpr/src/route/route_common.cpp @@ -236,7 +236,8 @@ void try_graph(int width_fac, const t_router_opts& router_opts, t_det_routing_ar segment_inf, router_opts.base_cost_type, router_opts.trim_empty_channels, - router_opts.trim_obs_channels, + /* Xifan tang: The trimming on obstacle(through) channel inside multi-height and multi-width grids are not open to command-line options. OpenFPGA opens this options through an XML syntax */ + router_opts.trim_obs_channels || det_routing_arch->through_channel, router_opts.clock_modeling, directs, num_directs, &warning_count); diff --git a/vpr/src/route/rr_graph.cpp b/vpr/src/route/rr_graph.cpp index ebd45d7b0..3c8f2ea25 100644 --- a/vpr/src/route/rr_graph.cpp +++ b/vpr/src/route/rr_graph.cpp @@ -366,6 +366,7 @@ void create_rr_graph(const t_graph_type graph_type, base_cost_type, directs, num_directs, &det_routing_arch->wire_to_rr_ipin_switch, + trim_obs_channels, /* Allow/Prohibit through tracks across multi-height and multi-width grids */ false, /* Do not allow passing tracks to be wired to the same routing channels */ Warnings); } diff --git a/vpr/src/tileable_rr_graph/rr_graph_builder_utils.cpp b/vpr/src/tileable_rr_graph/rr_graph_builder_utils.cpp index 6ec00466b..acead971f 100644 --- a/vpr/src/tileable_rr_graph/rr_graph_builder_utils.cpp +++ b/vpr/src/tileable_rr_graph/rr_graph_builder_utils.cpp @@ -212,7 +212,8 @@ bool is_chany_exist(const DeviceGrid& grids, * +-----------------+ ***********************************************************************/ bool is_chanx_right_to_multi_height_grid(const DeviceGrid& grids, - const vtr::Point& chanx_coord) { + const vtr::Point& chanx_coord, + const bool& through_channel) { VTR_ASSERT(0 < chanx_coord.x()); if (1 == chanx_coord.x()) { /* This is already the LEFT side of FPGA fabric, @@ -221,10 +222,12 @@ bool is_chanx_right_to_multi_height_grid(const DeviceGrid& grids, return true; } - /* We check the left neighbor of chanx, if it does not exist, the chanx is left to a multi-height grid */ - vtr::Point left_chanx_coord(chanx_coord.x() - 1, chanx_coord.y()); - if (false == is_chanx_exist(grids, left_chanx_coord)) { - return true; + if (false == through_channel) { + /* We check the left neighbor of chanx, if it does not exist, the chanx is left to a multi-height grid */ + vtr::Point left_chanx_coord(chanx_coord.x() - 1, chanx_coord.y()); + if (false == is_chanx_exist(grids, left_chanx_coord)) { + return true; + } } return false; @@ -244,7 +247,8 @@ bool is_chanx_right_to_multi_height_grid(const DeviceGrid& grids, * +-----------------+ ***********************************************************************/ bool is_chanx_left_to_multi_height_grid(const DeviceGrid& grids, - const vtr::Point& chanx_coord) { + const vtr::Point& chanx_coord, + const bool& through_channel) { VTR_ASSERT(chanx_coord.x() < grids.width() - 1); if (grids.width() - 2 == chanx_coord.x()) { /* This is already the RIGHT side of FPGA fabric, @@ -253,10 +257,13 @@ bool is_chanx_left_to_multi_height_grid(const DeviceGrid& grids, return true; } - /* We check the right neighbor of chanx, if it does not exist, the chanx is left to a multi-height grid */ - vtr::Point right_chanx_coord(chanx_coord.x() + 1, chanx_coord.y()); - if (false == is_chanx_exist(grids, right_chanx_coord)) { - return true; + + if (false == through_channel) { + /* We check the right neighbor of chanx, if it does not exist, the chanx is left to a multi-height grid */ + vtr::Point right_chanx_coord(chanx_coord.x() + 1, chanx_coord.y()); + if (false == is_chanx_exist(grids, right_chanx_coord)) { + return true; + } } return false; @@ -281,7 +288,8 @@ bool is_chanx_left_to_multi_height_grid(const DeviceGrid& grids, * +-----------------+ ***********************************************************************/ bool is_chany_top_to_multi_width_grid(const DeviceGrid& grids, - const vtr::Point& chany_coord) { + const vtr::Point& chany_coord, + const bool& through_channel) { VTR_ASSERT(0 < chany_coord.y()); if (1 == chany_coord.y()) { /* This is already the BOTTOM side of FPGA fabric, @@ -290,10 +298,12 @@ bool is_chany_top_to_multi_width_grid(const DeviceGrid& grids, return true; } - /* We check the bottom neighbor of chany, if it does not exist, the chany is left to a multi-height grid */ - vtr::Point bottom_chany_coord(chany_coord.x(), chany_coord.y() - 1); - if (false == is_chany_exist(grids, bottom_chany_coord)) { - return true; + if (false == through_channel) { + /* We check the bottom neighbor of chany, if it does not exist, the chany is left to a multi-height grid */ + vtr::Point bottom_chany_coord(chany_coord.x(), chany_coord.y() - 1); + if (false == is_chany_exist(grids, bottom_chany_coord)) { + return true; + } } return false; @@ -318,7 +328,8 @@ bool is_chany_top_to_multi_width_grid(const DeviceGrid& grids, * ***********************************************************************/ bool is_chany_bottom_to_multi_width_grid(const DeviceGrid& grids, - const vtr::Point& chany_coord) { + const vtr::Point& chany_coord, + const bool& through_channel) { VTR_ASSERT(chany_coord.y() < grids.height() - 1); if (grids.height() - 2 == chany_coord.y()) { /* This is already the TOP side of FPGA fabric, @@ -327,10 +338,12 @@ bool is_chany_bottom_to_multi_width_grid(const DeviceGrid& grids, return true; } - /* We check the top neighbor of chany, if it does not exist, the chany is left to a multi-height grid */ - vtr::Point top_chany_coord(chany_coord.x(), chany_coord.y() + 1); - if (false == is_chany_exist(grids, top_chany_coord)) { - return true; + if (false == through_channel) { + /* We check the top neighbor of chany, if it does not exist, the chany is left to a multi-height grid */ + vtr::Point top_chany_coord(chany_coord.x(), chany_coord.y() + 1); + if (false == is_chany_exist(grids, top_chany_coord)) { + return true; + } } return false; diff --git a/vpr/src/tileable_rr_graph/rr_graph_builder_utils.h b/vpr/src/tileable_rr_graph/rr_graph_builder_utils.h index 8a903f97a..23e030d9b 100644 --- a/vpr/src/tileable_rr_graph/rr_graph_builder_utils.h +++ b/vpr/src/tileable_rr_graph/rr_graph_builder_utils.h @@ -46,16 +46,20 @@ bool is_chany_exist(const DeviceGrid& grids, const vtr::Point& chany_coord); bool is_chanx_right_to_multi_height_grid(const DeviceGrid& grids, - const vtr::Point& chanx_coord); + const vtr::Point& chanx_coord, + const bool& through_channel); bool is_chanx_left_to_multi_height_grid(const DeviceGrid& grids, - const vtr::Point& chanx_coord); + const vtr::Point& chanx_coord, + const bool& through_channel); bool is_chany_top_to_multi_width_grid(const DeviceGrid& grids, - const vtr::Point& chany_coord); + const vtr::Point& chany_coord, + const bool& through_channel); bool is_chany_bottom_to_multi_width_grid(const DeviceGrid& grids, - const vtr::Point& chany_coord); + const vtr::Point& chany_coord, + const bool& through_channel); short get_rr_node_actual_track_id(const RRGraph& rr_graph, const RRNodeId& track_rr_node, diff --git a/vpr/src/tileable_rr_graph/tileable_rr_graph_builder.cpp b/vpr/src/tileable_rr_graph/tileable_rr_graph_builder.cpp index f103fb545..23ce3b53c 100644 --- a/vpr/src/tileable_rr_graph/tileable_rr_graph_builder.cpp +++ b/vpr/src/tileable_rr_graph/tileable_rr_graph_builder.cpp @@ -83,6 +83,7 @@ void build_tileable_unidir_rr_graph(const std::vector& typ const t_direct_inf *directs, const int& num_directs, int* wire_to_rr_ipin_switch, + const bool& through_channel, const bool& wire_opposite_side, int *Warnings) { @@ -149,7 +150,8 @@ void build_tileable_unidir_rr_graph(const std::vector& typ rr_node_driver_switches, grids, device_chan_width, - segment_inf); + segment_inf, + through_channel); /************************ * Create all the rr_nodes @@ -161,7 +163,8 @@ void build_tileable_unidir_rr_graph(const std::vector& typ device_chan_width, segment_inf, wire_to_ipin_rr_switch, - delayless_rr_switch); + delayless_rr_switch, + through_channel); /************************************************************************ * Create the connectivity of OPINs diff --git a/vpr/src/tileable_rr_graph/tileable_rr_graph_builder.h b/vpr/src/tileable_rr_graph/tileable_rr_graph_builder.h index 601c7de18..46be3817d 100644 --- a/vpr/src/tileable_rr_graph/tileable_rr_graph_builder.h +++ b/vpr/src/tileable_rr_graph/tileable_rr_graph_builder.h @@ -30,6 +30,7 @@ void build_tileable_unidir_rr_graph(const std::vector& typ const t_direct_inf *directs, const int& num_directs, int* wire_to_rr_ipin_switch, + const bool& through_channel, const bool& wire_opposite_side, int *Warnings); diff --git a/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp b/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp index da77d536e..753c455ff 100644 --- a/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp +++ b/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.cpp @@ -177,26 +177,27 @@ size_t estimate_num_grid_rr_nodes_by_type(const DeviceGrid& grids, static size_t estimate_num_chanx_rr_nodes(const DeviceGrid& grids, const size_t& chan_width, - const std::vector& segment_infs) { + const std::vector& segment_infs, + const bool& through_channel) { size_t num_chanx_rr_nodes = 0; for (size_t iy = 0; iy < grids.height() - 1; ++iy) { for (size_t ix = 1; ix < grids.width() - 1; ++ix) { vtr::Point chanx_coord(ix, iy); - /* Bypass if the routing channel does not exist */ + /* Bypass if the routing channel does not exist when through channels are not allowed */ if (false == is_chanx_exist(grids, chanx_coord)) { continue; } bool force_start = false; bool force_end = false; - + /* All the tracks have to start when * - the routing channel touch the RIGHT side a heterogeneous block * - the routing channel touch the LEFT side of FPGA */ - if (true == is_chanx_right_to_multi_height_grid(grids, chanx_coord)) { + if (true == is_chanx_right_to_multi_height_grid(grids, chanx_coord, through_channel)) { force_start = true; } @@ -204,7 +205,7 @@ size_t estimate_num_chanx_rr_nodes(const DeviceGrid& grids, * - the routing channel touch the LEFT side a heterogeneous block * - the routing channel touch the RIGHT side of FPGA */ - if (true == is_chanx_left_to_multi_height_grid(grids, chanx_coord)) { + if (true == is_chanx_left_to_multi_height_grid(grids, chanx_coord, through_channel)) { force_end = true; } @@ -228,14 +229,15 @@ size_t estimate_num_chanx_rr_nodes(const DeviceGrid& grids, static size_t estimate_num_chany_rr_nodes(const DeviceGrid& grids, const size_t& chan_width, - const std::vector& segment_infs) { + const std::vector& segment_infs, + const bool& through_channel) { size_t num_chany_rr_nodes = 0; for (size_t ix = 0; ix < grids.width() - 1; ++ix) { for (size_t iy = 1; iy < grids.height() - 1; ++iy) { vtr::Point chany_coord(ix, iy); - /* Bypass if the routing channel does not exist */ + /* Bypass if the routing channel does not exist when through channels are not allowed */ if (false == is_chany_exist(grids, chany_coord)) { continue; } @@ -247,7 +249,7 @@ size_t estimate_num_chany_rr_nodes(const DeviceGrid& grids, * - the routing channel touch the TOP side a heterogeneous block * - the routing channel touch the BOTTOM side of FPGA */ - if (true == is_chany_top_to_multi_width_grid(grids, chany_coord)) { + if (true == is_chany_top_to_multi_width_grid(grids, chany_coord, through_channel)) { force_start = true; } @@ -255,7 +257,7 @@ size_t estimate_num_chany_rr_nodes(const DeviceGrid& grids, * - the routing channel touch the BOTTOM side a heterogeneous block * - the routing channel touch the TOP side of FPGA */ - if (true == is_chany_bottom_to_multi_width_grid(grids, chany_coord)) { + if (true == is_chany_bottom_to_multi_width_grid(grids, chany_coord, through_channel)) { force_end = true; } @@ -276,7 +278,8 @@ size_t estimate_num_chany_rr_nodes(const DeviceGrid& grids, static std::vector estimate_num_rr_nodes(const DeviceGrid& grids, const vtr::Point& chan_width, - const std::vector& segment_infs) { + const std::vector& segment_infs, + const bool& through_channel) { /* Reset the OPIN, IPIN, SOURCE, SINK counter to be zero */ std::vector num_rr_nodes_per_type(NUM_RR_TYPES, 0); @@ -304,8 +307,14 @@ std::vector estimate_num_rr_nodes(const DeviceGrid& grids, * in X-direction and Y-direction channels!!! * So we will load segment details for different channels */ - num_rr_nodes_per_type[CHANX] = estimate_num_chanx_rr_nodes(grids, chan_width.x(), segment_infs); - num_rr_nodes_per_type[CHANY] = estimate_num_chany_rr_nodes(grids, chan_width.y(), segment_infs); + num_rr_nodes_per_type[CHANX] = estimate_num_chanx_rr_nodes(grids, + chan_width.x(), + segment_infs, + through_channel); + num_rr_nodes_per_type[CHANY] = estimate_num_chany_rr_nodes(grids, + chan_width.y(), + segment_infs, + through_channel); return num_rr_nodes_per_type; } @@ -321,10 +330,14 @@ void alloc_tileable_rr_graph_nodes(RRGraph& rr_graph, vtr::vector& rr_node_driver_switches, const DeviceGrid& grids, const vtr::Point& chan_width, - const std::vector& segment_infs) { + const std::vector& segment_infs, + const bool& through_channel) { VTR_ASSERT(0 == rr_graph.nodes().size()); - std::vector num_rr_nodes_per_type = estimate_num_rr_nodes(grids, chan_width, segment_infs); + std::vector num_rr_nodes_per_type = estimate_num_rr_nodes(grids, + chan_width, + segment_infs, + through_channel); /* Reserve the number of node to be memory efficient */ size_t num_nodes = 0; @@ -764,7 +777,8 @@ void load_chanx_rr_nodes_basic_info(RRGraph& rr_graph, std::map>& rr_node_track_ids, const DeviceGrid& grids, const size_t& chan_width, - const std::vector& segment_infs) { + const std::vector& segment_infs, + const bool& through_channel) { /* For X-direction Channel: CHANX */ for (size_t iy = 0; iy < grids.height() - 1; ++iy) { @@ -774,8 +788,9 @@ void load_chanx_rr_nodes_basic_info(RRGraph& rr_graph, for (size_t ix = 1; ix < grids.width() - 1; ++ix) { vtr::Point chanx_coord(ix, iy); - /* Bypass if the routing channel does not exist */ - if (false == is_chanx_exist(grids, chanx_coord)) { + /* Bypass if the routing channel does not exist when through channels are not allowed */ + if ( (false == through_channel) + && (false == is_chanx_exist(grids, chanx_coord))) { continue; } @@ -786,7 +801,7 @@ void load_chanx_rr_nodes_basic_info(RRGraph& rr_graph, * - the routing channel touch the RIGHT side a heterogeneous block * - the routing channel touch the LEFT side of FPGA */ - if (true == is_chanx_right_to_multi_height_grid(grids, chanx_coord)) { + if (true == is_chanx_right_to_multi_height_grid(grids, chanx_coord, through_channel)) { force_start = true; } @@ -794,7 +809,7 @@ void load_chanx_rr_nodes_basic_info(RRGraph& rr_graph, * - the routing channel touch the LEFT side a heterogeneous block * - the routing channel touch the RIGHT side of FPGA */ - if (true == is_chanx_left_to_multi_height_grid(grids, chanx_coord)) { + if (true == is_chanx_left_to_multi_height_grid(grids, chanx_coord, through_channel)) { force_end = true; } @@ -810,21 +825,35 @@ void load_chanx_rr_nodes_basic_info(RRGraph& rr_graph, false, false, segment_infs); chanx_details_tt.set_track_node_ids(track_node_ids); - /* Rotate the chanx_details by an offset of ix - 1, the distance to the most left channel */ - /* For INC_DIRECTION, we use clockwise rotation - * node_id A ----> -----> node_id D - * node_id B ----> / ----> node_id A - * node_id C ----> / ----> node_id B - * node_id D ----> ----> node_id C + /* TODO: + * Do NOT rotate the tracks when the routing channel + * locates inside a multi-height and multi-width grid + * Let the routing channel passing through the grid (if through channel is allowed!) + * An example: + * + * +------------------------------ + * | | + * | Grid | + * track0 ----->+-----------------------------+----> track0 + * | | */ - chanx_details_tt.rotate_track_node_id(1, INC_DIRECTION, true); - /* For DEC_DIRECTION, we use clockwise rotation - * node_id A <----- <----- node_id B - * node_id B <----- \ <----- node_id C - * node_id C <----- \ <----- node_id D - * node_id D <----- <----- node_id A - */ - chanx_details_tt.rotate_track_node_id(1, DEC_DIRECTION, false); + if (true == is_chanx_exist(grids, chanx_coord)) { + /* Rotate the chanx_details by an offset of ix - 1, the distance to the most left channel */ + /* For INC_DIRECTION, we use clockwise rotation + * node_id A ----> -----> node_id D + * node_id B ----> / ----> node_id A + * node_id C ----> / ----> node_id B + * node_id D ----> ----> node_id C + */ + chanx_details_tt.rotate_track_node_id(1, INC_DIRECTION, true); + /* For DEC_DIRECTION, we use clockwise rotation + * node_id A <----- <----- node_id B + * node_id B <----- \ <----- node_id C + * node_id C <----- \ <----- node_id D + * node_id D <----- <----- node_id A + */ + chanx_details_tt.rotate_track_node_id(1, DEC_DIRECTION, false); + } track_node_ids = chanx_details_tt.get_track_node_ids(); chanx_details.set_track_node_ids(track_node_ids); @@ -855,7 +884,8 @@ void load_chany_rr_nodes_basic_info(RRGraph& rr_graph, std::map>& rr_node_track_ids, const DeviceGrid& grids, const size_t& chan_width, - const std::vector& segment_infs) { + const std::vector& segment_infs, + const bool& through_channel) { /* For Y-direction Channel: CHANY */ for (size_t ix = 0; ix < grids.width() - 1; ++ix) { @@ -865,8 +895,9 @@ void load_chany_rr_nodes_basic_info(RRGraph& rr_graph, for (size_t iy = 1; iy < grids.height() - 1; ++iy) { vtr::Point chany_coord(ix, iy); - /* Bypass if the routing channel does not exist */ - if (false == is_chany_exist(grids, chany_coord)) { + /* Bypass if the routing channel does not exist when through channel are not allowed */ + if ( (false == through_channel) + && (false == is_chany_exist(grids, chany_coord))) { continue; } @@ -877,7 +908,7 @@ void load_chany_rr_nodes_basic_info(RRGraph& rr_graph, * - the routing channel touch the TOP side a heterogeneous block * - the routing channel touch the BOTTOM side of FPGA */ - if (true == is_chany_top_to_multi_width_grid(grids, chany_coord)) { + if (true == is_chany_top_to_multi_width_grid(grids, chany_coord, through_channel)) { force_start = true; } @@ -885,7 +916,7 @@ void load_chany_rr_nodes_basic_info(RRGraph& rr_graph, * - the routing channel touch the BOTTOM side a heterogeneous block * - the routing channel touch the TOP side of FPGA */ - if (true == is_chany_bottom_to_multi_width_grid(grids, chany_coord)) { + if (true == is_chany_bottom_to_multi_width_grid(grids, chany_coord, through_channel)) { force_end = true; } @@ -901,21 +932,37 @@ void load_chany_rr_nodes_basic_info(RRGraph& rr_graph, false, false, segment_infs); chany_details_tt.set_track_node_ids(track_node_ids); - /* Rotate the chany_details by an offset of 1*/ - /* For INC_DIRECTION, we use clockwise rotation - * node_id A ----> -----> node_id D - * node_id B ----> / ----> node_id A - * node_id C ----> / ----> node_id B - * node_id D ----> ----> node_id C + + /* TODO: + * Do NOT rotate the tracks when the routing channel + * locates inside a multi-height and multi-width grid + * Let the routing channel passing through the grid (if through channel is allowed!) + * An example: + * + * +------------------------------ + * | | + * | Grid | + * track0 ----->+-----------------------------+----> track0 + * | | + * we should rotate only once at the bottom side of a grid */ - chany_details_tt.rotate_track_node_id(1, INC_DIRECTION, true); - /* For DEC_DIRECTION, we use clockwise rotation - * node_id A <----- <----- node_id B - * node_id B <----- \ <----- node_id C - * node_id C <----- \ <----- node_id D - * node_id D <----- <----- node_id A - */ - chany_details_tt.rotate_track_node_id(1, DEC_DIRECTION, false); + if (true == is_chany_exist(grids, chany_coord)) { + /* Rotate the chany_details by an offset of 1*/ + /* For INC_DIRECTION, we use clockwise rotation + * node_id A ----> -----> node_id D + * node_id B ----> / ----> node_id A + * node_id C ----> / ----> node_id B + * node_id D ----> ----> node_id C + */ + chany_details_tt.rotate_track_node_id(1, INC_DIRECTION, true); + /* For DEC_DIRECTION, we use clockwise rotation + * node_id A <----- <----- node_id B + * node_id B <----- \ <----- node_id C + * node_id C <----- \ <----- node_id D + * node_id D <----- <----- node_id A + */ + chany_details_tt.rotate_track_node_id(1, DEC_DIRECTION, false); + } track_node_ids = chany_details_tt.get_track_node_ids(); chany_details.set_track_node_ids(track_node_ids); @@ -969,7 +1016,8 @@ void create_tileable_rr_graph_nodes(RRGraph& rr_graph, const vtr::Point& chan_width, const std::vector& segment_infs, const RRSwitchId& wire_to_ipin_switch, - const RRSwitchId& delayless_switch) { + const RRSwitchId& delayless_switch, + const bool& through_channel) { load_grid_nodes_basic_info(rr_graph, rr_node_driver_switches, grids, @@ -981,14 +1029,16 @@ void create_tileable_rr_graph_nodes(RRGraph& rr_graph, rr_node_track_ids, grids, chan_width.x(), - segment_infs); + segment_infs, + through_channel); load_chany_rr_nodes_basic_info(rr_graph, rr_node_driver_switches, rr_node_track_ids, grids, chan_width.y(), - segment_infs); + segment_infs, + through_channel); reverse_dec_chan_rr_node_track_ids(rr_graph, rr_node_track_ids); diff --git a/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.h b/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.h index a69cd187c..de8e8f60b 100644 --- a/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.h +++ b/vpr/src/tileable_rr_graph/tileable_rr_graph_node_builder.h @@ -25,7 +25,8 @@ void alloc_tileable_rr_graph_nodes(RRGraph& rr_graph, vtr::vector& driver_switches, const DeviceGrid& grids, const vtr::Point& chan_width, - const std::vector& segment_infs); + const std::vector& segment_infs, + const bool& through_channel); void create_tileable_rr_graph_nodes(RRGraph& rr_graph, vtr::vector& rr_node_driver_switches, @@ -34,7 +35,8 @@ void create_tileable_rr_graph_nodes(RRGraph& rr_graph, const vtr::Point& chan_width, const std::vector& segment_infs, const RRSwitchId& wire_to_ipin_switch, - const RRSwitchId& delayless_switch); + const RRSwitchId& delayless_switch, + const bool& through_channel); } /* end namespace openfpga */ From b6bdf78d957b625fbe3ef0b5434201075cc63990 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 24 Mar 2020 17:39:26 -0600 Subject: [PATCH 340/645] bug fixed for heterogeneous block instances in top module --- .../build_grid_module_duplicated_pins.cpp | 2 +- openfpga/src/fabric/build_top_module.cpp | 14 + .../fabric/build_top_module_connection.cpp | 11 + openfpga/src/utils/pb_type_utils.cpp | 14 + ...c_N10_adder_chain_mem16K_40nm_openfpga.xml | 310 ++++++++++++++++++ .../and_k6_frac_adder_chain_mem16K.openfpga | 62 ++++ 6 files changed, 412 insertions(+), 1 deletion(-) create mode 100644 openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml create mode 100644 openfpga/test_script/and_k6_frac_adder_chain_mem16K.openfpga diff --git a/openfpga/src/fabric/build_grid_module_duplicated_pins.cpp b/openfpga/src/fabric/build_grid_module_duplicated_pins.cpp index 0f4a86934..e02bae403 100644 --- a/openfpga/src/fabric/build_grid_module_duplicated_pins.cpp +++ b/openfpga/src/fabric/build_grid_module_duplicated_pins.cpp @@ -163,7 +163,7 @@ void add_grid_module_net_connect_duplicated_pb_graph_pin(ModuleManager& module_m size_t grid_pin_index = pb_graph_pin->pin_count_in_cluster + child_instance * grid_type_descriptor->num_pins / grid_type_descriptor->capacity; - int pin_width = grid_type_descriptor->pin_height_offset[grid_pin_index]; + int pin_width = grid_type_descriptor->pin_width_offset[grid_pin_index]; int pin_height = grid_type_descriptor->pin_height_offset[grid_pin_index]; for (const e_side& side : grid_pin_sides) { if (true != grid_type_descriptor->pinloc[pin_width][pin_height][side][grid_pin_index]) { diff --git a/openfpga/src/fabric/build_top_module.cpp b/openfpga/src/fabric/build_top_module.cpp index 707ff8971..83a4f0147 100644 --- a/openfpga/src/fabric/build_top_module.cpp +++ b/openfpga/src/fabric/build_top_module.cpp @@ -105,6 +105,13 @@ vtr::Matrix add_top_module_grid_instances(ModuleManager& module_manager, /* Skip width or height > 1 tiles (mostly heterogeneous blocks) */ if ( (0 < grids[ix][iy].width_offset) || (0 < grids[ix][iy].height_offset)) { + /* Find the root of this grid, the instance id should be valid. + * We just copy it here + */ + vtr::Point root_grid_coord(ix - grids[ix][iy].width_offset, + iy - grids[ix][iy].height_offset); + VTR_ASSERT(size_t(-1) != grid_instance_ids[root_grid_coord.x()][root_grid_coord.y()]); + grid_instance_ids[ix][iy] = grid_instance_ids[root_grid_coord.x()][root_grid_coord.y()]; continue; } /* We should not meet any I/O grid */ @@ -153,6 +160,13 @@ vtr::Matrix add_top_module_grid_instances(ModuleManager& module_manager, /* Skip width, height > 1 tiles (mostly heterogeneous blocks) */ if ( (0 < grids[io_coordinate.x()][io_coordinate.y()].width_offset) || (0 < grids[io_coordinate.x()][io_coordinate.y()].height_offset)) { + /* Find the root of this grid, the instance id should be valid. + * We just copy it here + */ + vtr::Point root_grid_coord(io_coordinate.x() - grids[io_coordinate.x()][io_coordinate.y()].width_offset, + io_coordinate.y() - grids[io_coordinate.x()][io_coordinate.y()].height_offset); + VTR_ASSERT(size_t(-1) != grid_instance_ids[root_grid_coord.x()][root_grid_coord.y()]); + grid_instance_ids[io_coordinate.x()][io_coordinate.y()] = grid_instance_ids[root_grid_coord.x()][root_grid_coord.y()]; continue; } /* We should not meet any I/O grid */ diff --git a/openfpga/src/fabric/build_top_module_connection.cpp b/openfpga/src/fabric/build_top_module_connection.cpp index 0af6e3409..31163db69 100644 --- a/openfpga/src/fabric/build_top_module_connection.cpp +++ b/openfpga/src/fabric/build_top_module_connection.cpp @@ -63,6 +63,11 @@ void add_top_module_nets_connect_grids_and_sb(ModuleManager& module_manager, const vtr::Matrix& sb_instance_ids, const bool& compact_routing_hierarchy) { + /* Skip those Switch blocks that do not exist */ + if (false == rr_gsb.is_sb_exist()) { + return; + } + /* We could have two different coordinators, one is the instance, the other is the module */ vtr::Point instance_sb_coordinate(rr_gsb.get_sb_x(), rr_gsb.get_sb_y()); vtr::Point module_gsb_coordinate(rr_gsb.get_x(), rr_gsb.get_y()); @@ -179,6 +184,11 @@ void add_top_module_nets_connect_grids_and_sb_with_duplicated_pins(ModuleManager const vtr::Matrix& sb_instance_ids, const bool& compact_routing_hierarchy) { + /* Skip those Switch blocks that do not exist */ + if (false == rr_gsb.is_sb_exist()) { + return; + } + /* We could have two different coordinators, one is the instance, the other is the module */ vtr::Point instance_sb_coordinate(rr_gsb.get_sb_x(), rr_gsb.get_sb_y()); vtr::Point module_gsb_coordinate(rr_gsb.get_x(), rr_gsb.get_y()); @@ -642,6 +652,7 @@ void add_top_module_nets_connect_grids_and_gsbs(ModuleManager& module_manager, for (size_t iy = 0; iy < gsb_range.y(); ++iy) { vtr::Point gsb_coordinate(ix, iy); const RRGSB& rr_gsb = device_rr_gsb.get_gsb(ix, iy); + /* Connect the grid pins of the GSB to adjacent grids */ if (false == duplicate_grid_pin) { add_top_module_nets_connect_grids_and_sb(module_manager, top_module, diff --git a/openfpga/src/utils/pb_type_utils.cpp b/openfpga/src/utils/pb_type_utils.cpp index 13d7ccadb..9166a16e8 100644 --- a/openfpga/src/utils/pb_type_utils.cpp +++ b/openfpga/src/utils/pb_type_utils.cpp @@ -37,6 +37,20 @@ bool is_primitive_pb_type(t_pb_type* pb_type) { VTR_ASSERT( (std::string("wire") == std::string(pb_type->modes[0].name)) && (std::string(pb_type->name) == std::string(pb_type->modes[1].name))); return true; + } else if (MEMORY_CLASS == pb_type->class_type) { + /* The only primitive memory we recognize is the one which has a mode + * either named after 'memory_slice' or 'memory_slice_1bit' + * VPR contructed 1 default mode under a regular memory, and these children + * are labelled as LUT_CLASS as well. OpenFPGA does not consider + * them as primitive as they are for CAD usage only + */ + if (0 == pb_type->num_modes) { + return false; + } + VTR_ASSERT( (std::string("memory_slice") == std::string(pb_type->modes[0].name)) + || (std::string("memory_slice_1bit") == std::string(pb_type->modes[0].name))); + return true; + } return 0 == pb_type->num_modes; } diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml new file mode 100644 index 000000000..9f37c1293 --- /dev/null +++ b/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml @@ -0,0 +1,310 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + + + + 10e-12 5e-12 + + + 10e-12 5e-12 + + + + + + + + + + + + 10e-12 5e-12 5e-12 + + + 10e-12 5e-12 5e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga/test_script/and_k6_frac_adder_chain_mem16K.openfpga b/openfpga/test_script/and_k6_frac_adder_chain_mem16K.openfpga new file mode 100644 index 000000000..34ec11181 --- /dev/null +++ b/openfpga/test_script/and_k6_frac_adder_chain_mem16K.openfpga @@ -0,0 +1,62 @@ +# Run VPR for the 'and' design +vpr ./test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml ./test_blif/and.blif --route_chan_width 40 --clock_modeling route #--write_rr_graph example_rr_graph.xml + +# Read OpenFPGA architecture definition +read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml + +# Write out the architecture XML as a proof +#write_openfpga_arch -f ./arch_echo.xml + +# Annotate the OpenFPGA architecture to VPR data base +link_openfpga_arch --activity_file ./test_blif/and.act --sort_gsb_chan_node_in_edges #--verbose + +# Write GSB to XML for debugging +write_gsb_to_xml --file /var/tmp/xtang/openfpga_test_src/gsb_xml + +# Check and correct any naming conflicts in the BLIF netlist +check_netlist_naming_conflict --fix --report ./netlist_renaming.xml + +# Apply fix-up to clustering nets based on routing results +pb_pin_fixup --verbose + +# Apply fix-up to Look-Up Table truth tables based on packing results +lut_truth_table_fixup #--verbose + +# Build the module graph +# - Enabled compression on routing architecture modules +# - Enable pin duplication on grid modules +build_fabric --compress_routing --duplicate_grid_pin --verbose + +# Repack the netlist to physical pbs +# This must be done before bitstream generator and testbench generation +# Strongly recommend it is done after all the fix-up have been applied +repack #--verbose + +# Build the bitstream +# - Output the fabric-independent bitstream to a file +build_architecture_bitstream --verbose --file /var/tmp/xtang/openfpga_test_src/fabric_indepenent_bitstream.xml + +# Build fabric-dependent bitstream +build_fabric_bitstream --verbose + +# Write the Verilog netlist for FPGA fabric +# - Enable the use of explicit port mapping in Verilog netlist +write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src/SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose + +# Write the Verilog testbench for FPGA fabric +# - We suggest the use of same output directory as fabric Verilog netlists +# - Must specify the reference benchmark file if you want to output any testbenches +# - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA +# - Enable pre-configured top-level testbench which is a fast verification skipping programming phase +# - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts +write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src/SRC --reference_benchmark_file_path /var/tmp/xtang/and.v --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini /var/tmp/xtang/openfpga_test_src/simulation_deck.ini + +# Write the SDC files for PnR backend +# - Turn on every options here +write_pnr_sdc --file /var/tmp/xtang/openfpga_test_src/SDC + +# Write the SDC to run timing analysis for a mapped FPGA fabric +write_analysis_sdc --file /var/tmp/xtang/openfpga_test_src/SDC_analysis + +# Finish and exit OpenFPGA +exit From 787dc8ce83d5956bf85e7a7a3769a322d612ebe6 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 25 Mar 2020 11:16:04 -0600 Subject: [PATCH 341/645] added ASCII OpenFPGA logo in shell interface --- openfpga/src/base/openfpga_title.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openfpga/src/base/openfpga_title.cpp b/openfpga/src/base/openfpga_title.cpp index ae4ce80c4..41616c8bd 100644 --- a/openfpga/src/base/openfpga_title.cpp +++ b/openfpga/src/base/openfpga_title.cpp @@ -11,6 +11,13 @@ const char* create_openfpga_title() { std::string title; + title += std::string("\n"); + title += std::string(" ___ _____ ____ ____ _ \n"); + title += std::string(" / _ \\ _ __ ___ _ __ | ___| _ \\ / ___| / \\ \n"); + title += std::string(" | | | | '_ \\ / _ \\ '_ \\| |_ | |_) | | _ / _ \\ \n"); + title += std::string(" | |_| | |_) | __/ | | | _| | __/| |_| |/ ___ \\ \n"); + title += std::string(" \\___/| .__/ \\___|_| |_|_| |_| \\____/_/ \\_\\ \n"); + title += std::string(" |_| \n"); title += std::string("\n"); title += std::string(" OpenFPGA: An Open-source FPGA IP Generator\n"); title += std::string(" Versatile Place and Route (VPR)\n"); From c2e5d6b8e273f819259510102fad4e72a6d1b1e5 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 25 Mar 2020 14:38:13 -0600 Subject: [PATCH 342/645] add options to dsiable SDC for non-clock global ports --- openfpga/src/base/openfpga_sdc.cpp | 2 + openfpga/src/base/openfpga_sdc_command.cpp | 3 + openfpga/src/fpga_sdc/pnr_sdc_global_port.cpp | 192 ++++++++++++++++++ openfpga/src/fpga_sdc/pnr_sdc_global_port.h | 27 +++ openfpga/src/fpga_sdc/pnr_sdc_option.cpp | 9 + openfpga/src/fpga_sdc/pnr_sdc_option.h | 3 + openfpga/src/fpga_sdc/pnr_sdc_writer.cpp | 103 +--------- 7 files changed, 239 insertions(+), 100 deletions(-) create mode 100644 openfpga/src/fpga_sdc/pnr_sdc_global_port.cpp create mode 100644 openfpga/src/fpga_sdc/pnr_sdc_global_port.h diff --git a/openfpga/src/base/openfpga_sdc.cpp b/openfpga/src/base/openfpga_sdc.cpp index 8b708f133..a83d0b797 100644 --- a/openfpga/src/base/openfpga_sdc.cpp +++ b/openfpga/src/base/openfpga_sdc.cpp @@ -27,6 +27,7 @@ void write_pnr_sdc(OpenfpgaContext& openfpga_ctx, CommandOptionId opt_output_dir = cmd.option("file"); CommandOptionId opt_constrain_global_port = cmd.option("constrain_global_port"); + CommandOptionId opt_constrain_non_clock_global_port = cmd.option("constrain_non_clock_global_port"); CommandOptionId opt_constrain_grid = cmd.option("constrain_grid"); CommandOptionId opt_constrain_sb = cmd.option("constrain_sb"); CommandOptionId opt_constrain_cb = cmd.option("constrain_cb"); @@ -45,6 +46,7 @@ void write_pnr_sdc(OpenfpgaContext& openfpga_ctx, PnrSdcOption options(sdc_dir_path); options.set_constrain_global_port(cmd_context.option_enable(cmd, opt_constrain_global_port)); + options.set_constrain_non_clock_global_port(cmd_context.option_enable(cmd, opt_constrain_non_clock_global_port)); options.set_constrain_grid(cmd_context.option_enable(cmd, opt_constrain_grid)); options.set_constrain_sb(cmd_context.option_enable(cmd, opt_constrain_sb)); options.set_constrain_cb(cmd_context.option_enable(cmd, opt_constrain_cb)); diff --git a/openfpga/src/base/openfpga_sdc_command.cpp b/openfpga/src/base/openfpga_sdc_command.cpp index 91d65aa0a..7d194ba5b 100644 --- a/openfpga/src/base/openfpga_sdc_command.cpp +++ b/openfpga/src/base/openfpga_sdc_command.cpp @@ -29,6 +29,9 @@ ShellCommandId add_openfpga_write_pnr_sdc_command(openfpga::Shell +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_time.h" +#include "vtr_log.h" + +/* Headers from openfpgautil library */ +#include "openfpga_port.h" +#include "openfpga_digest.h" + +#include "sdc_writer_naming.h" +#include "sdc_writer_utils.h" +#include "pnr_sdc_global_port.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Print SDC constraint for a clock port + * This format is derived from the open-source SDC syntax, + * which is supposed to be generic + * + * This function is design to the SDC writer for any port + * wants to be treated as a clock port + *******************************************************************/ +static +void print_pnr_sdc_clock_port(std::fstream& fp, + const BasicPort& port_to_constrain, + const float& clock_period) { + valid_file_stream(fp); + + fp << "create_clock"; + fp << " -name " << generate_sdc_port(port_to_constrain); + fp << " -period " << std::setprecision(10) << clock_period; + fp << " -waveform {0 " << std::setprecision(10) << clock_period / 2 << "}"; + fp << " [get_ports{" << generate_sdc_port(port_to_constrain) << "}]"; + fp << std::endl; +} + +/******************************************************************** + * Print SDC constraints for the clock ports which are the global ports + * of FPGA fabric + * + * For programming clock, we give a fixed period, while for operating + * clock, we constrain with critical path delay + *******************************************************************/ +static +void print_pnr_sdc_global_clock_ports(std::fstream& fp, + const float& programming_critical_path_delay, + const float& operating_critical_path_delay, + const CircuitLibrary& circuit_lib, + const std::vector& global_ports) { + + valid_file_stream(fp); + + /* Get clock port from the global port */ + for (const CircuitPortId& clock_port : global_ports) { + if (CIRCUIT_MODEL_PORT_CLOCK != circuit_lib.port_type(clock_port)) { + continue; + } + /* Reach here, it means a clock port and we need print constraints */ + float clock_period = operating_critical_path_delay; + + /* For programming clock, we give a fixed period */ + if (true == circuit_lib.port_is_prog(clock_port)) { + clock_period = programming_critical_path_delay; + /* Print comments */ + fp << "##################################################" << std::endl; + fp << "# Create programmable clock " << std::endl; + fp << "##################################################" << std::endl; + } else { + /* Print comments */ + fp << "##################################################" << std::endl; + fp << "# Create clock " << std::endl; + fp << "##################################################" << std::endl; + } + + for (const size_t& pin : circuit_lib.pins(clock_port)) { + BasicPort port_to_constrain(circuit_lib.port_prefix(clock_port), pin, pin); + + print_pnr_sdc_clock_port(fp, + port_to_constrain, + clock_period); + } + } +} + +/******************************************************************** + * Print SDC constraints for the non-clock ports which are the global ports + * of FPGA fabric + * Here, we will the treat the non-clock ports as the clock ports + * in the CTS + * Note that, this may be applied to the reset, set and other global + * signals which do need very balanced delays to each sink + *******************************************************************/ +static +void print_pnr_sdc_global_non_clock_ports(std::fstream& fp, + const float& operating_critical_path_delay, + const CircuitLibrary& circuit_lib, + const std::vector& global_ports) { + + valid_file_stream(fp); + + /* For non-clock port from the global port: give a fixed period */ + for (const CircuitPortId& global_port : global_ports) { + if (CIRCUIT_MODEL_PORT_CLOCK == circuit_lib.port_type(global_port)) { + continue; + } + + /* Print comments */ + fp << "##################################################" << std::endl; + fp << "# Constrain other global ports " << std::endl; + fp << "##################################################" << std::endl; + + /* Reach here, it means a non-clock global port and we need print constraints */ + float clock_period = operating_critical_path_delay; + for (const size_t& pin : circuit_lib.pins(global_port)) { + BasicPort port_to_constrain(circuit_lib.port_prefix(global_port), pin, pin); + + print_pnr_sdc_clock_port(fp, + port_to_constrain, + clock_period); + } + } +} + +/******************************************************************** + * Print a SDC file to constrain the global ports of FPGA fabric + * in particular clock ports + * + * This ports to appear in this file will be treated in Clock Tree + * Synthesis (CTS) + * + * For non-clock global ports, we have an option to select if they + * should be treated in CTS or not + * In general, we do not recommend to do this + *******************************************************************/ +void print_pnr_sdc_global_ports(const std::string& sdc_dir, + const float& programming_critical_path_delay, + const float& operating_critical_path_delay, + const CircuitLibrary& circuit_lib, + const std::vector& global_ports, + const bool& constrain_non_clock_port) { + + /* Create the file name for Verilog netlist */ + std::string sdc_fname(sdc_dir + std::string(SDC_GLOBAL_PORTS_FILE_NAME)); + + /* Start time count */ + std::string timer_message = std::string("Write SDC for constraining clocks for P&R flow '") + sdc_fname + std::string("'"); + vtr::ScopedStartFinishTimer timer(timer_message); + + /* Create the file stream */ + std::fstream fp; + fp.open(sdc_fname, std::fstream::out | std::fstream::trunc); + + check_file_stream(sdc_fname.c_str(), fp); + + /* Generate the descriptions*/ + print_sdc_file_header(fp, std::string("Clock contraints for PnR")); + + print_pnr_sdc_global_clock_ports(fp, + programming_critical_path_delay, + operating_critical_path_delay, + circuit_lib, + global_ports); + + if (true == constrain_non_clock_port) { + print_pnr_sdc_global_non_clock_ports(fp, + operating_critical_path_delay, + circuit_lib, + global_ports); + + } + + /* Close file handler */ + fp.close(); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/pnr_sdc_global_port.h b/openfpga/src/fpga_sdc/pnr_sdc_global_port.h new file mode 100644 index 000000000..444482106 --- /dev/null +++ b/openfpga/src/fpga_sdc/pnr_sdc_global_port.h @@ -0,0 +1,27 @@ +#ifndef PNR_SDC_GLBOAL_PORT_H +#define PNR_SDC_GLBOAL_PORT_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "circuit_library.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_pnr_sdc_global_ports(const std::string& sdc_dir, + const float& programming_critical_path_delay, + const float& operating_critical_path_delay, + const CircuitLibrary& circuit_lib, + const std::vector& global_ports, + const bool& constrain_non_clock_port); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_sdc/pnr_sdc_option.cpp b/openfpga/src/fpga_sdc/pnr_sdc_option.cpp index 3a3906b50..a5143776e 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_option.cpp +++ b/openfpga/src/fpga_sdc/pnr_sdc_option.cpp @@ -12,6 +12,7 @@ namespace openfpga { PnrSdcOption::PnrSdcOption(const std::string& sdc_dir) { sdc_dir_ = sdc_dir; constrain_global_port_ = false; + constrain_non_clock_global_port_ = false; constrain_grid_ = false; constrain_sb_ = false; constrain_cb_ = false; @@ -41,6 +42,10 @@ bool PnrSdcOption::constrain_global_port() const { return constrain_global_port_; } +bool PnrSdcOption::constrain_non_clock_global_port() const { + return constrain_non_clock_global_port_; +} + bool PnrSdcOption::constrain_grid() const { return constrain_grid_; } @@ -86,6 +91,10 @@ void PnrSdcOption::set_constrain_global_port(const bool& constrain_global_port) constrain_global_port_ = constrain_global_port; } +void PnrSdcOption::set_constrain_non_clock_global_port(const bool& constrain_non_clock_global_port) { + constrain_non_clock_global_port_ = constrain_non_clock_global_port; +} + void PnrSdcOption::set_constrain_grid(const bool& constrain_grid) { constrain_grid_ = constrain_grid; } diff --git a/openfpga/src/fpga_sdc/pnr_sdc_option.h b/openfpga/src/fpga_sdc/pnr_sdc_option.h index a485a1946..b81bfaefe 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_option.h +++ b/openfpga/src/fpga_sdc/pnr_sdc_option.h @@ -18,6 +18,7 @@ class PnrSdcOption { std::string sdc_dir() const; bool generate_sdc_pnr() const; bool constrain_global_port() const; + bool constrain_non_clock_global_port() const; bool constrain_grid() const; bool constrain_sb() const; bool constrain_cb() const; @@ -28,6 +29,7 @@ class PnrSdcOption { void set_sdc_dir(const std::string& sdc_dir); void set_generate_sdc_pnr(const bool& generate_sdc_pnr); void set_constrain_global_port(const bool& constrain_global_port); + void set_constrain_non_clock_global_port(const bool& constrain_non_clock_global_port); void set_constrain_grid(const bool& constrain_grid); void set_constrain_sb(const bool& constrain_sb); void set_constrain_cb(const bool& constrain_cb); @@ -37,6 +39,7 @@ class PnrSdcOption { private: /* Internal data */ std::string sdc_dir_; bool constrain_global_port_; + bool constrain_non_clock_global_port_; bool constrain_grid_; bool constrain_sb_; bool constrain_cb_; diff --git a/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp b/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp index 9d1478037..b005ca6ce 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp +++ b/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp @@ -28,6 +28,7 @@ #include "sdc_writer_naming.h" #include "sdc_writer_utils.h" #include "sdc_memory_utils.h" +#include "pnr_sdc_global_port.h" #include "pnr_sdc_routing_writer.h" #include "pnr_sdc_grid_writer.h" #include "pnr_sdc_writer.h" @@ -35,105 +36,6 @@ /* begin namespace openfpga */ namespace openfpga { -/******************************************************************** - * Print a SDC file to constrain the global ports of FPGA fabric - * in particular clock ports - * - * For programming clock, we give a fixed period, while for operating - * clock, we constrain with critical path delay - *******************************************************************/ -static -void print_pnr_sdc_global_ports(const std::string& sdc_dir, - const float& programming_critical_path_delay, - const float& operating_critical_path_delay, - const CircuitLibrary& circuit_lib, - const std::vector& global_ports) { - - /* Create the file name for Verilog netlist */ - std::string sdc_fname(sdc_dir + std::string(SDC_GLOBAL_PORTS_FILE_NAME)); - - /* Start time count */ - std::string timer_message = std::string("Write SDC for constraining clocks for P&R flow '") + sdc_fname + std::string("'"); - vtr::ScopedStartFinishTimer timer(timer_message); - - /* Create the file stream */ - std::fstream fp; - fp.open(sdc_fname, std::fstream::out | std::fstream::trunc); - - check_file_stream(sdc_fname.c_str(), fp); - - /* Generate the descriptions*/ - print_sdc_file_header(fp, std::string("Clock contraints for PnR")); - - /* Get clock port from the global port */ - for (const CircuitPortId& clock_port : global_ports) { - if (CIRCUIT_MODEL_PORT_CLOCK != circuit_lib.port_type(clock_port)) { - continue; - } - /* Reach here, it means a clock port and we need print constraints */ - float clock_period = operating_critical_path_delay; - - /* For programming clock, we give a fixed period */ - if (true == circuit_lib.port_is_prog(clock_port)) { - clock_period = programming_critical_path_delay; - /* Print comments */ - fp << "##################################################" << std::endl; - fp << "# Create programmable clock " << std::endl; - fp << "##################################################" << std::endl; - } else { - /* Print comments */ - fp << "##################################################" << std::endl; - fp << "# Create clock " << std::endl; - fp << "##################################################" << std::endl; - } - - for (const size_t& pin : circuit_lib.pins(clock_port)) { - BasicPort port_to_constrain(circuit_lib.port_prefix(clock_port), pin, pin); - - fp << "create_clock "; - fp << generate_sdc_port(port_to_constrain) << "-period "; - fp << std::setprecision(10) << clock_period; - fp << " -waveform {0 "; - fp << std::setprecision(10) << clock_period / 2; - fp << "}" << std::endl; - - fp << std::endl; - } - } - - /* For non-clock port from the global port: give a fixed period */ - for (const CircuitPortId& global_port : global_ports) { - if (CIRCUIT_MODEL_PORT_CLOCK == circuit_lib.port_type(global_port)) { - continue; - } - - /* Print comments */ - fp << "##################################################" << std::endl; - fp << "# Constrain other global ports " << std::endl; - fp << "##################################################" << std::endl; - - /* Reach here, it means a non-clock global port and we need print constraints */ - float clock_period = operating_critical_path_delay; - for (const size_t& pin : circuit_lib.pins(global_port)) { - BasicPort port_to_constrain(circuit_lib.port_prefix(global_port), pin, pin); - fp << "create_clock "; - fp << generate_sdc_port(port_to_constrain) << "-period "; - fp << std::setprecision(10) << clock_period; - fp << " -waveform {0 "; - fp << std::setprecision(10) << clock_period / 2; - fp << "} "; - fp << "[list [get_ports { " << generate_sdc_port(port_to_constrain) << "}]]" << std::endl; - - fp << "set_drive 0 " << generate_sdc_port(port_to_constrain) << std::endl; - - fp << std::endl; - } - } - - /* Close file handler */ - fp.close(); -} - /******************************************************************** * Break combinational loops in FPGA fabric, which mainly come from * configurable memory cells. @@ -354,7 +256,8 @@ void print_pnr_sdc(const PnrSdcOption& sdc_options, print_pnr_sdc_global_ports(sdc_options.sdc_dir(), programming_critical_path_delay, operating_critical_path_delay, - circuit_lib, global_ports); + circuit_lib, global_ports, + sdc_options.constrain_non_clock_global_port()); } std::string top_module_name = generate_fpga_top_module_name(); From 62b6de8437bd10d9d298255488a9247b8d0df5c6 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 25 Mar 2020 14:44:42 -0600 Subject: [PATCH 343/645] update the SDC of VPR7+OpenFPGA to be even with VPR8+OpenFPGA --- .../backend_assistant/pnr_sdc_writer.cpp | 59 ++++++++++--------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/vpr7_x2p/vpr/SRC/fpga_x2p/backend_assistant/pnr_sdc_writer.cpp b/vpr7_x2p/vpr/SRC/fpga_x2p/backend_assistant/pnr_sdc_writer.cpp index eb2a33bc0..a333f95ff 100644 --- a/vpr7_x2p/vpr/SRC/fpga_x2p/backend_assistant/pnr_sdc_writer.cpp +++ b/vpr7_x2p/vpr/SRC/fpga_x2p/backend_assistant/pnr_sdc_writer.cpp @@ -45,7 +45,8 @@ static void print_pnr_sdc_global_ports(const std::string& sdc_dir, const float& critical_path_delay, const CircuitLibrary& circuit_lib, - const std::vector& global_ports) { + const std::vector& global_ports, + const bool& constrain_non_clock_ports) { /* Create the file name for Verilog netlist */ std::string sdc_fname(sdc_dir + std::string(SDC_GLOBAL_PORTS_FILE_NAME)); @@ -91,43 +92,45 @@ void print_pnr_sdc_global_ports(const std::string& sdc_dir, for (const size_t& pin : circuit_lib.pins(clock_port)) { BasicPort port_to_constrain(circuit_lib.port_prefix(clock_port), pin, pin); - fp << "create_clock "; - fp << generate_sdc_port(port_to_constrain) << "-period "; + fp << "create_clock -name "; + fp << generate_sdc_port(port_to_constrain) << " -period "; fp << std::setprecision(10) << clock_period; fp << " -waveform {0 "; fp << std::setprecision(10) << clock_period / 2; - fp << "}" << std::endl; + fp << "}"; + fp << "{get_ports {" << generate_sdc_port(port_to_constrain) << "}]"; + fp << std::endl; fp << std::endl; } } - /* For non-clock port from the global port: give a fixed period */ - for (const CircuitPortId& global_port : global_ports) { - if (SPICE_MODEL_PORT_CLOCK == circuit_lib.port_type(global_port)) { - continue; - } + if (true == constrain_non_clock_ports) { + /* For non-clock port from the global port: give a fixed period */ + for (const CircuitPortId& global_port : global_ports) { + if (SPICE_MODEL_PORT_CLOCK == circuit_lib.port_type(global_port)) { + continue; + } - /* Print comments */ - fp << "##################################################" << std::endl; - fp << "# Constrain other global ports " << std::endl; - fp << "##################################################" << std::endl; + /* Print comments */ + fp << "##################################################" << std::endl; + fp << "# Constrain other global ports " << std::endl; + fp << "##################################################" << std::endl; - /* Reach here, it means a non-clock global port and we need print constraints */ - float clock_period = SDC_FIXED_CLOCK_PERIOD; - for (const size_t& pin : circuit_lib.pins(global_port)) { - BasicPort port_to_constrain(circuit_lib.port_prefix(global_port), pin, pin); - fp << "create_clock "; - fp << generate_sdc_port(port_to_constrain) << "-period "; - fp << std::setprecision(10) << clock_period; - fp << " -waveform {0 "; - fp << std::setprecision(10) << clock_period / 2; - fp << "} "; - fp << "[list [get_ports { " << generate_sdc_port(port_to_constrain) << "}]]" << std::endl; + /* Reach here, it means a non-clock global port and we need print constraints */ + float clock_period = SDC_FIXED_CLOCK_PERIOD; + for (const size_t& pin : circuit_lib.pins(global_port)) { + BasicPort port_to_constrain(circuit_lib.port_prefix(global_port), pin, pin); + fp << "create_clock -name "; + fp << generate_sdc_port(port_to_constrain) << " -period "; + fp << std::setprecision(10) << clock_period; + fp << " -waveform {0 "; + fp << std::setprecision(10) << clock_period / 2; + fp << "} "; + fp << "[get_ports { " << generate_sdc_port(port_to_constrain) << "}]" << std::endl; - fp << "set_drive 0 " << generate_sdc_port(port_to_constrain) << std::endl; - - fp << std::endl; + fp << std::endl; + } } } @@ -397,7 +400,7 @@ void print_pnr_sdc(const SdcOption& sdc_options, /* Constrain global ports */ if (true == sdc_options.constrain_global_port()) { - print_pnr_sdc_global_ports(sdc_options.sdc_dir(), critical_path_delay, circuit_lib, global_ports); + print_pnr_sdc_global_ports(sdc_options.sdc_dir(), critical_path_delay, circuit_lib, global_ports, false); } std::string top_module_name = generate_fpga_top_module_name(); From 4a0128f240cca198aa4776cd55cbc2d278fd41e0 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 25 Mar 2020 14:46:31 -0600 Subject: [PATCH 344/645] minor fix on the SDC format --- openfpga/src/fpga_sdc/pnr_sdc_global_port.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openfpga/src/fpga_sdc/pnr_sdc_global_port.cpp b/openfpga/src/fpga_sdc/pnr_sdc_global_port.cpp index 6d56dae48..4e6ef4925 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_global_port.cpp +++ b/openfpga/src/fpga_sdc/pnr_sdc_global_port.cpp @@ -46,7 +46,7 @@ void print_pnr_sdc_clock_port(std::fstream& fp, fp << " -name " << generate_sdc_port(port_to_constrain); fp << " -period " << std::setprecision(10) << clock_period; fp << " -waveform {0 " << std::setprecision(10) << clock_period / 2 << "}"; - fp << " [get_ports{" << generate_sdc_port(port_to_constrain) << "}]"; + fp << " [get_ports {" << generate_sdc_port(port_to_constrain) << "}]"; fp << std::endl; } From 3a74fb7a04d1bd9e7b54d3a9efe314c79bb2dc68 Mon Sep 17 00:00:00 2001 From: Xifan Tang Date: Wed, 25 Mar 2020 15:23:21 -0600 Subject: [PATCH 345/645] update documentation for the new options --- docs/source/openfpga_shell/openfpga_commands.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/source/openfpga_shell/openfpga_commands.rst b/docs/source/openfpga_shell/openfpga_commands.rst index 6551b7421..fd44f6fbb 100644 --- a/docs/source/openfpga_shell/openfpga_commands.rst +++ b/docs/source/openfpga_shell/openfpga_commands.rst @@ -174,11 +174,15 @@ FPGA-SDC - ``--file`` or ``-f`` Specify the output directory for SDC files - - ``--constrain_global_port`` Constrain all the global ports of FPGA fabric + - ``--constrain_global_port`` Constrain all the global ports of FPGA fabric. + + - ``--constrain_non_clock_global_port`` Constrain all the non-clock global ports as clocks ports of FPGA fabric + + .. note:: ``constrain_global_port`` will treat these global ports in Clock Tree Synthesis (CTS), in purpose of balancing the delay to each sink. Be carefull to enable ``constrain_non_clock_global_port``, this may significanly increase the runtime of CTS as it is supposed to be routed before any other nets. This may cause routing congestion as well. - ``--constrain_grid`` Constrain all the grids of FPGA fabric - - `--constrain_sb`` Constrain all the switch blocks of FPGA fabric + - ``--constrain_sb`` Constrain all the switch blocks of FPGA fabric - ``--constrain_cb`` Constrain all the connection blocks of FPGA fabric From 329b0a9cf1d3b5ac9a8686b8017211c981222751 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 25 Mar 2020 15:55:30 -0600 Subject: [PATCH 346/645] add options to enable SDC constraints on zero-delay paths --- openfpga/src/base/openfpga_sdc.cpp | 2 + openfpga/src/base/openfpga_sdc_command.cpp | 3 + openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp | 42 ++++++++---- openfpga/src/fpga_sdc/pnr_sdc_grid_writer.h | 3 +- openfpga/src/fpga_sdc/pnr_sdc_option.cpp | 9 +++ openfpga/src/fpga_sdc/pnr_sdc_option.h | 3 + .../src/fpga_sdc/pnr_sdc_routing_writer.cpp | 66 +++++++++++++------ .../src/fpga_sdc/pnr_sdc_routing_writer.h | 12 ++-- openfpga/src/fpga_sdc/pnr_sdc_writer.cpp | 15 +++-- 9 files changed, 114 insertions(+), 41 deletions(-) diff --git a/openfpga/src/base/openfpga_sdc.cpp b/openfpga/src/base/openfpga_sdc.cpp index a83d0b797..cb2e1bf72 100644 --- a/openfpga/src/base/openfpga_sdc.cpp +++ b/openfpga/src/base/openfpga_sdc.cpp @@ -34,6 +34,7 @@ void write_pnr_sdc(OpenfpgaContext& openfpga_ctx, CommandOptionId opt_constrain_configurable_memory_outputs = cmd.option("constrain_configurable_memory_outputs"); CommandOptionId opt_constrain_routing_multiplexer_outputs = cmd.option("constrain_routing_multiplexer_outputs"); CommandOptionId opt_constrain_switch_block_outputs = cmd.option("constrain_switch_block_outputs"); + CommandOptionId opt_constrain_zero_delay_paths = cmd.option("constrain_zero_delay_paths"); /* This is an intermediate data structure which is designed to modularize the FPGA-SDC * Keep it independent from any other outside data structures @@ -53,6 +54,7 @@ void write_pnr_sdc(OpenfpgaContext& openfpga_ctx, options.set_constrain_configurable_memory_outputs(cmd_context.option_enable(cmd, opt_constrain_configurable_memory_outputs)); options.set_constrain_routing_multiplexer_outputs(cmd_context.option_enable(cmd, opt_constrain_routing_multiplexer_outputs)); options.set_constrain_switch_block_outputs(cmd_context.option_enable(cmd, opt_constrain_switch_block_outputs)); + options.set_constrain_zero_delay_paths(cmd_context.option_enable(cmd, opt_constrain_zero_delay_paths)); /* We first turn on default sdc option and then disable part of them by following users' options */ if (false == options.generate_sdc_pnr()) { diff --git a/openfpga/src/base/openfpga_sdc_command.cpp b/openfpga/src/base/openfpga_sdc_command.cpp index 7d194ba5b..551b37d2b 100644 --- a/openfpga/src/base/openfpga_sdc_command.cpp +++ b/openfpga/src/base/openfpga_sdc_command.cpp @@ -50,6 +50,9 @@ ShellCommandId add_openfpga_write_pnr_sdc_command(openfpga::Shellpin_number, des_pb_graph_pin->pin_number); + /* If we have a zero-delay path to contrain, we will skip unless users want so */ + if ( (false == constrain_zero_delay_paths) + && (0. == des_pb_graph_pin->input_edges[iedge]->delay_max) ) { + continue; + } + /* Print a SDC timing constraint */ print_pnr_sdc_constrain_max_delay(fp, src_instance_name, @@ -158,7 +165,8 @@ void print_pnr_sdc_constrain_pb_interc_timing(std::fstream& fp, const ModuleId& parent_module, t_pb_graph_node* des_pb_graph_node, const e_circuit_pb_port_type& pb_port_type, - t_mode* physical_mode) { + t_mode* physical_mode, + const bool& constrain_zero_delay_paths) { /* Validate file stream */ valid_file_stream(fp); @@ -171,7 +179,8 @@ void print_pnr_sdc_constrain_pb_interc_timing(std::fstream& fp, print_pnr_sdc_constrain_pb_pin_interc_timing(fp, module_manager, parent_module, &(des_pb_graph_node->input_pins[iport][ipin]), - physical_mode); + physical_mode, + constrain_zero_delay_paths); } } break; @@ -182,7 +191,8 @@ void print_pnr_sdc_constrain_pb_interc_timing(std::fstream& fp, print_pnr_sdc_constrain_pb_pin_interc_timing(fp, module_manager, parent_module, &(des_pb_graph_node->output_pins[iport][ipin]), - physical_mode); + physical_mode, + constrain_zero_delay_paths); } } break; @@ -209,7 +219,8 @@ static void print_pnr_sdc_constrain_pb_graph_node_timing(const std::string& sdc_dir, const ModuleManager& module_manager, t_pb_graph_node* parent_pb_graph_node, - t_mode* physical_mode) { + t_mode* physical_mode, + const bool& constrain_zero_delay_paths) { /* Get the pb_type definition related to the node */ t_pb_type* physical_pb_type = parent_pb_graph_node->pb_type; @@ -242,7 +253,8 @@ void print_pnr_sdc_constrain_pb_graph_node_timing(const std::string& sdc_dir, module_manager, pb_module, parent_pb_graph_node, CIRCUIT_PB_PORT_OUTPUT, - physical_mode); + physical_mode, + constrain_zero_delay_paths); /* We check input_pins of child_pb_graph_node and its the input_edges * Built the interconnections between inputs of cur_pb_graph_node and inputs of child_pb_graph_node @@ -259,7 +271,8 @@ void print_pnr_sdc_constrain_pb_graph_node_timing(const std::string& sdc_dir, module_manager, pb_module, child_pb_graph_node, CIRCUIT_PB_PORT_INPUT, - physical_mode); + physical_mode, + constrain_zero_delay_paths); /* Do NOT constrain clock here, it should be handled by Clock Tree Synthesis */ } } @@ -277,7 +290,8 @@ static void rec_print_pnr_sdc_constrain_pb_graph_timing(const std::string& sdc_dir, const ModuleManager& module_manager, const VprDeviceAnnotation& device_annotation, - t_pb_graph_node* parent_pb_graph_node) { + t_pb_graph_node* parent_pb_graph_node, + const bool& constrain_zero_delay_paths) { /* Validate pb_graph node */ if (nullptr == parent_pb_graph_node) { VTR_LOGF_ERROR(__FILE__, __LINE__, @@ -302,7 +316,8 @@ void rec_print_pnr_sdc_constrain_pb_graph_timing(const std::string& sdc_dir, print_pnr_sdc_constrain_pb_graph_node_timing(sdc_dir, module_manager, parent_pb_graph_node, - physical_mode); + physical_mode, + constrain_zero_delay_paths); /* Go recursively to the lower level in the pb_graph * Note that we assume a full hierarchical P&R, we will only visit pb_graph_node of unique pb_type @@ -310,7 +325,8 @@ void rec_print_pnr_sdc_constrain_pb_graph_timing(const std::string& sdc_dir, for (int ipb = 0; ipb < physical_mode->num_pb_type_children; ++ipb) { rec_print_pnr_sdc_constrain_pb_graph_timing(sdc_dir, module_manager, device_annotation, - &(parent_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ipb][0])); + &(parent_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ipb][0]), + constrain_zero_delay_paths); } } @@ -320,7 +336,8 @@ void rec_print_pnr_sdc_constrain_pb_graph_timing(const std::string& sdc_dir, void print_pnr_sdc_constrain_grid_timing(const std::string& sdc_dir, const DeviceContext& device_ctx, const VprDeviceAnnotation& device_annotation, - const ModuleManager& module_manager) { + const ModuleManager& module_manager, + const bool& constrain_zero_delay_paths) { /* Start time count */ vtr::ScopedStartFinishTimer timer("Write SDC for constraining grid timing for P&R flow"); @@ -338,7 +355,8 @@ void print_pnr_sdc_constrain_grid_timing(const std::string& sdc_dir, /* Special for I/O block, generate one module for each border side */ rec_print_pnr_sdc_constrain_pb_graph_timing(sdc_dir, module_manager, device_annotation, - pb_graph_head); + pb_graph_head, + constrain_zero_delay_paths); } } } diff --git a/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.h b/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.h index 91c1b7030..400ca1616 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.h +++ b/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.h @@ -20,7 +20,8 @@ namespace openfpga { void print_pnr_sdc_constrain_grid_timing(const std::string& sdc_dir, const DeviceContext& device_ctx, const VprDeviceAnnotation& device_annotation, - const ModuleManager& module_manager); + const ModuleManager& module_manager, + const bool& constrain_zero_delay_paths); } /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/pnr_sdc_option.cpp b/openfpga/src/fpga_sdc/pnr_sdc_option.cpp index a5143776e..153ac4fac 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_option.cpp +++ b/openfpga/src/fpga_sdc/pnr_sdc_option.cpp @@ -19,6 +19,7 @@ PnrSdcOption::PnrSdcOption(const std::string& sdc_dir) { constrain_configurable_memory_outputs_ = false; constrain_routing_multiplexer_outputs_ = false; constrain_switch_block_outputs_ = false; + constrain_zero_delay_paths_ = false; } /******************************************************************** @@ -70,6 +71,10 @@ bool PnrSdcOption::constrain_switch_block_outputs() const { return constrain_switch_block_outputs_; } +bool PnrSdcOption::constrain_zero_delay_paths() const { + return constrain_zero_delay_paths_; +} + /******************************************************************** * Public mutators ********************************************************************/ @@ -119,4 +124,8 @@ void PnrSdcOption::set_constrain_switch_block_outputs(const bool& constrain_sb_o constrain_switch_block_outputs_ = constrain_sb_outputs; } +void PnrSdcOption::set_constrain_zero_delay_paths(const bool& constrain_zero_delay_paths) { + constrain_zero_delay_paths_ = constrain_zero_delay_paths; +} + } /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/pnr_sdc_option.h b/openfpga/src/fpga_sdc/pnr_sdc_option.h index b81bfaefe..71dc1a806 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_option.h +++ b/openfpga/src/fpga_sdc/pnr_sdc_option.h @@ -25,6 +25,7 @@ class PnrSdcOption { bool constrain_configurable_memory_outputs() const; bool constrain_routing_multiplexer_outputs() const; bool constrain_switch_block_outputs() const; + bool constrain_zero_delay_paths() const; public: /* Public mutators */ void set_sdc_dir(const std::string& sdc_dir); void set_generate_sdc_pnr(const bool& generate_sdc_pnr); @@ -36,6 +37,7 @@ class PnrSdcOption { void set_constrain_configurable_memory_outputs(const bool& constrain_config_mem_outputs); void set_constrain_routing_multiplexer_outputs(const bool& constrain_routing_mux_outputs); void set_constrain_switch_block_outputs(const bool& constrain_sb_outputs); + void set_constrain_zero_delay_paths(const bool& constrain_zero_delay_paths); private: /* Internal data */ std::string sdc_dir_; bool constrain_global_port_; @@ -46,6 +48,7 @@ class PnrSdcOption { bool constrain_configurable_memory_outputs_; bool constrain_routing_multiplexer_outputs_; bool constrain_switch_block_outputs_; + bool constrain_zero_delay_paths_; }; } /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp b/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp index dd0914d14..d4f1ce522 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp +++ b/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp @@ -54,7 +54,8 @@ void print_pnr_sdc_constrain_sb_mux_timing(std::fstream& fp, const RRGraph& rr_graph, const RRGSB& rr_gsb, const e_side& output_node_side, - const RRNodeId& output_rr_node) { + const RRNodeId& output_rr_node, + const bool& constrain_zero_delay_paths) { /* Validate file stream */ valid_file_stream(fp); @@ -89,6 +90,11 @@ void print_pnr_sdc_constrain_sb_mux_timing(std::fstream& fp, /* Find the starting points */ for (const ModulePortId& module_input_port : module_input_ports) { + /* If we have a zero-delay path to contrain, we will skip unless users want so */ + if ( (false == constrain_zero_delay_paths) + && (0. == switch_delays[module_input_port]) ) { + continue; + } /* Constrain a path */ print_pnr_sdc_constrain_port2port_timing(fp, module_manager, @@ -110,7 +116,8 @@ static void print_pnr_sdc_constrain_sb_timing(const std::string& sdc_dir, const ModuleManager& module_manager, const RRGraph& rr_graph, - const RRGSB& rr_gsb) { + const RRGSB& rr_gsb, + const bool& constrain_zero_delay_paths) { /* Create the file name for Verilog netlist */ vtr::Point gsb_coordinate(rr_gsb.get_sb_x(), rr_gsb.get_sb_y()); @@ -148,7 +155,8 @@ void print_pnr_sdc_constrain_sb_timing(const std::string& sdc_dir, rr_graph, rr_gsb, side_manager.get_side(), - chan_rr_node); + chan_rr_node, + constrain_zero_delay_paths); } } @@ -163,7 +171,8 @@ void print_pnr_sdc_constrain_sb_timing(const std::string& sdc_dir, void print_pnr_sdc_flatten_routing_constrain_sb_timing(const std::string& sdc_dir, const ModuleManager& module_manager, const RRGraph& rr_graph, - const DeviceRRGSB& device_rr_gsb) { + const DeviceRRGSB& device_rr_gsb, + const bool& constrain_zero_delay_paths) { /* Start time count */ vtr::ScopedStartFinishTimer timer("Write SDC for constrain Switch Block timing for P&R flow"); @@ -180,7 +189,8 @@ void print_pnr_sdc_flatten_routing_constrain_sb_timing(const std::string& sdc_di print_pnr_sdc_constrain_sb_timing(sdc_dir, module_manager, rr_graph, - rr_gsb); + rr_gsb, + constrain_zero_delay_paths); } } } @@ -192,7 +202,8 @@ void print_pnr_sdc_flatten_routing_constrain_sb_timing(const std::string& sdc_di void print_pnr_sdc_compact_routing_constrain_sb_timing(const std::string& sdc_dir, const ModuleManager& module_manager, const RRGraph& rr_graph, - const DeviceRRGSB& device_rr_gsb) { + const DeviceRRGSB& device_rr_gsb, + const bool& constrain_zero_delay_paths) { /* Start time count */ vtr::ScopedStartFinishTimer timer("Write SDC for constrain Switch Block timing for P&R flow"); @@ -205,7 +216,8 @@ void print_pnr_sdc_compact_routing_constrain_sb_timing(const std::string& sdc_di print_pnr_sdc_constrain_sb_timing(sdc_dir, module_manager, rr_graph, - rr_gsb); + rr_gsb, + constrain_zero_delay_paths); } } @@ -220,7 +232,8 @@ void print_pnr_sdc_constrain_cb_mux_timing(std::fstream& fp, const RRGraph& rr_graph, const RRGSB& rr_gsb, const t_rr_type& cb_type, - const RRNodeId& output_rr_node) { + const RRNodeId& output_rr_node, + const bool& constrain_zero_delay_paths) { /* Validate file stream */ valid_file_stream(fp); @@ -275,6 +288,12 @@ void print_pnr_sdc_constrain_cb_mux_timing(std::fstream& fp, /* Find the starting points */ for (const ModulePortId& module_input_port : module_input_ports) { + /* If we have a zero-delay path to contrain, we will skip unless users want so */ + if ( (false == constrain_zero_delay_paths) + && (0. == switch_delays[module_input_port]) ) { + continue; + } + /* Constrain a path */ print_pnr_sdc_constrain_port2port_timing(fp, module_manager, @@ -284,7 +303,6 @@ void print_pnr_sdc_constrain_cb_mux_timing(std::fstream& fp, } } - /******************************************************************** * Print SDC timing constraints for a Connection block * This function is designed for compact routing hierarchy @@ -294,7 +312,8 @@ void print_pnr_sdc_constrain_cb_timing(const std::string& sdc_dir, const ModuleManager& module_manager, const RRGraph& rr_graph, const RRGSB& rr_gsb, - const t_rr_type& cb_type) { + const t_rr_type& cb_type, + const bool& constrain_zero_delay_paths) { /* Create the netlist */ vtr::Point gsb_coordinate(rr_gsb.get_cb_x(cb_type), rr_gsb.get_cb_y(cb_type)); @@ -325,7 +344,8 @@ void print_pnr_sdc_constrain_cb_timing(const std::string& sdc_dir, print_pnr_sdc_constrain_cb_mux_timing(fp, module_manager, cb_module, rr_graph, rr_gsb, cb_type, - ipin_rr_node); + ipin_rr_node, + constrain_zero_delay_paths); } } @@ -342,7 +362,8 @@ void print_pnr_sdc_flatten_routing_constrain_cb_timing(const std::string& sdc_di const ModuleManager& module_manager, const RRGraph& rr_graph, const DeviceRRGSB& device_rr_gsb, - const t_rr_type& cb_type) { + const t_rr_type& cb_type, + const bool& constrain_zero_delay_paths) { /* Build unique X-direction connection block modules */ vtr::Point cb_range = device_rr_gsb.get_gsb_range(); @@ -360,7 +381,8 @@ void print_pnr_sdc_flatten_routing_constrain_cb_timing(const std::string& sdc_di module_manager, rr_graph, rr_gsb, - cb_type); + cb_type, + constrain_zero_delay_paths); } } @@ -373,7 +395,8 @@ void print_pnr_sdc_flatten_routing_constrain_cb_timing(const std::string& sdc_di void print_pnr_sdc_flatten_routing_constrain_cb_timing(const std::string& sdc_dir, const ModuleManager& module_manager, const RRGraph& rr_graph, - const DeviceRRGSB& device_rr_gsb) { + const DeviceRRGSB& device_rr_gsb, + const bool& constrain_zero_delay_paths) { /* Start time count */ vtr::ScopedStartFinishTimer timer("Write SDC for constrain Connection Block timing for P&R flow"); @@ -381,12 +404,14 @@ void print_pnr_sdc_flatten_routing_constrain_cb_timing(const std::string& sdc_di print_pnr_sdc_flatten_routing_constrain_cb_timing(sdc_dir, module_manager, rr_graph, device_rr_gsb, - CHANX); + CHANX, + constrain_zero_delay_paths); print_pnr_sdc_flatten_routing_constrain_cb_timing(sdc_dir, module_manager, rr_graph, device_rr_gsb, - CHANY); + CHANY, + constrain_zero_delay_paths); } /******************************************************************** @@ -396,7 +421,8 @@ void print_pnr_sdc_flatten_routing_constrain_cb_timing(const std::string& sdc_di void print_pnr_sdc_compact_routing_constrain_cb_timing(const std::string& sdc_dir, const ModuleManager& module_manager, const RRGraph& rr_graph, - const DeviceRRGSB& device_rr_gsb) { + const DeviceRRGSB& device_rr_gsb, + const bool& constrain_zero_delay_paths) { /* Start time count */ vtr::ScopedStartFinishTimer timer("Write SDC for constrain Connection Block timing for P&R flow"); @@ -408,7 +434,8 @@ void print_pnr_sdc_compact_routing_constrain_cb_timing(const std::string& sdc_di module_manager, rr_graph, unique_mirror, - CHANX); + CHANX, + constrain_zero_delay_paths); } /* Print SDC for unique Y-direction connection block modules */ @@ -418,7 +445,8 @@ void print_pnr_sdc_compact_routing_constrain_cb_timing(const std::string& sdc_di module_manager, rr_graph, unique_mirror, - CHANY); + CHANY, + constrain_zero_delay_paths); } } diff --git a/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.h b/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.h index 65ce60ca8..0ced07938 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.h +++ b/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.h @@ -20,22 +20,26 @@ namespace openfpga { void print_pnr_sdc_flatten_routing_constrain_sb_timing(const std::string& sdc_dir, const ModuleManager& module_manager, const RRGraph& rr_graph, - const DeviceRRGSB& device_rr_gsb); + const DeviceRRGSB& device_rr_gsb, + const bool& constrain_zero_delay_paths); void print_pnr_sdc_compact_routing_constrain_sb_timing(const std::string& sdc_dir, const ModuleManager& module_manager, const RRGraph& rr_graph, - const DeviceRRGSB& device_rr_gsb); + const DeviceRRGSB& device_rr_gsb, + const bool& constrain_zero_delay_paths); void print_pnr_sdc_flatten_routing_constrain_cb_timing(const std::string& sdc_dir, const ModuleManager& module_manager, const RRGraph& rr_graph, - const DeviceRRGSB& device_rr_gsb); + const DeviceRRGSB& device_rr_gsb, + const bool& constrain_zero_delay_paths); void print_pnr_sdc_compact_routing_constrain_cb_timing(const std::string& sdc_dir, const ModuleManager& module_manager, const RRGraph& rr_graph, - const DeviceRRGSB& device_rr_gsb); + const DeviceRRGSB& device_rr_gsb, + const bool& constrain_zero_delay_paths); } /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp b/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp index b005ca6ce..b7ac5bb43 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp +++ b/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp @@ -296,13 +296,15 @@ void print_pnr_sdc(const PnrSdcOption& sdc_options, print_pnr_sdc_compact_routing_constrain_sb_timing(sdc_options.sdc_dir(), module_manager, device_ctx.rr_graph, - device_rr_gsb); + device_rr_gsb, + sdc_options.constrain_zero_delay_paths()); } else { VTR_ASSERT_SAFE (false == compact_routing_hierarchy); print_pnr_sdc_flatten_routing_constrain_sb_timing(sdc_options.sdc_dir(), module_manager, device_ctx.rr_graph, - device_rr_gsb); + device_rr_gsb, + sdc_options.constrain_zero_delay_paths()); } } @@ -312,13 +314,15 @@ void print_pnr_sdc(const PnrSdcOption& sdc_options, print_pnr_sdc_compact_routing_constrain_cb_timing(sdc_options.sdc_dir(), module_manager, device_ctx.rr_graph, - device_rr_gsb); + device_rr_gsb, + sdc_options.constrain_zero_delay_paths()); } else { VTR_ASSERT_SAFE (false == compact_routing_hierarchy); print_pnr_sdc_flatten_routing_constrain_cb_timing(sdc_options.sdc_dir(), module_manager, device_ctx.rr_graph, - device_rr_gsb); + device_rr_gsb, + sdc_options.constrain_zero_delay_paths()); } } @@ -327,7 +331,8 @@ void print_pnr_sdc(const PnrSdcOption& sdc_options, print_pnr_sdc_constrain_grid_timing(sdc_options.sdc_dir(), device_ctx, device_annotation, - module_manager); + module_manager, + sdc_options.constrain_zero_delay_paths()); } } From cb6afea07c87293fb4c0e0bc7080b3de28c7f39e Mon Sep 17 00:00:00 2001 From: Xifan Tang Date: Wed, 25 Mar 2020 16:00:25 -0600 Subject: [PATCH 347/645] update documentation on a new option in FPGA-SDC to constrain zero-delay paths --- docs/source/openfpga_shell/openfpga_commands.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/source/openfpga_shell/openfpga_commands.rst b/docs/source/openfpga_shell/openfpga_commands.rst index fd44f6fbb..8f71cdb54 100644 --- a/docs/source/openfpga_shell/openfpga_commands.rst +++ b/docs/source/openfpga_shell/openfpga_commands.rst @@ -191,6 +191,10 @@ FPGA-SDC - ``--constrain_routing_multiplexer_outputs`` Constrain all the outputs of routing multiplexer of FPGA fabric - ``--constrain_switch_block_outputs`` Constrain all the outputs of switch blocks of FPGA fabric + + - ``--constrain_zero_delay_paths`` Constrain all the zero-delay paths in FPGA fabric + + .. note:: Zero-delay path may cause errors in some PnR tools as it is considered illegal - ``--verbose`` Enable verbose output From b4221e94bbeb8864e37646f499a56b70351164a8 Mon Sep 17 00:00:00 2001 From: Xifan Tang Date: Wed, 25 Mar 2020 16:52:42 -0600 Subject: [PATCH 348/645] add documentation on the tileable routing and thru channel support --- docs/source/arch_lang/addon_vpr_syntax.rst | 23 +++++++++++++++++- .../source/arch_lang/figures/thru_channel.png | Bin 0 -> 28647 bytes .../fpga_bitstream/file_organization.rst | 2 +- docs/source/z_reference.bib | 9 +++++++ 4 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 docs/source/arch_lang/figures/thru_channel.png diff --git a/docs/source/arch_lang/addon_vpr_syntax.rst b/docs/source/arch_lang/addon_vpr_syntax.rst index 7122c9870..3f51e005d 100644 --- a/docs/source/arch_lang/addon_vpr_syntax.rst +++ b/docs/source/arch_lang/addon_vpr_syntax.rst @@ -14,7 +14,28 @@ Each ```` should contain a ```` that describe the physical implem .. option:: tileable="" - Turn on/off tileable routing resource graph generator + Turn ``on``/``off`` tileable routing resource graph generator. + + Tileable routing architecture can minimize the number of unique modules in FPGA fabric to be physically implemented. + + Technical details can be found in :cite:`XTang_FPT_2019`. + + .. note:: Strongly recommend to enable the tileable routing architecture when you want to PnR large FPGA fabrics, which can effectively reduce the runtime. + +.. option:: through_channel="" + + Allow routing channels to pass through multi-width and multi-height programable blocks. This is mainly used in heterogeneous FPGAs to increase routability, as illustrated in :numref:`fig_thru_channel`. + By default, it is ``off``. + + .. _fig_thru_channel: + + .. figure:: ./figures/thru_channel.png + :scale: 80% + :alt: Impact of through channel + + Impact on routing architecture when through channel in multi-width and multi-height programmable blocks: (a) disabled; (b) enabled. + + .. warning:: Do NOT enable if you are not using the tileable routing resource graph generator! ```` may include addition syntax to enable different connectivity for pass tracks diff --git a/docs/source/arch_lang/figures/thru_channel.png b/docs/source/arch_lang/figures/thru_channel.png new file mode 100644 index 0000000000000000000000000000000000000000..26ba72ac7790539c59db15f1abee3a9d684448cc GIT binary patch literal 28647 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 literal 0 HcmV?d00001 diff --git a/docs/source/fpga_bitstream/file_organization.rst b/docs/source/fpga_bitstream/file_organization.rst index d8190ac67..91975fcc7 100644 --- a/docs/source/fpga_bitstream/file_organization.rst +++ b/docs/source/fpga_bitstream/file_organization.rst @@ -1,5 +1,5 @@ Bitstream Output File Format -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ FPGA-Bitstream can generate two types of bitstreams: diff --git a/docs/source/z_reference.bib b/docs/source/z_reference.bib index d087acd7d..3758761f8 100644 --- a/docs/source/z_reference.bib +++ b/docs/source/z_reference.bib @@ -95,3 +95,12 @@ doi={10.1109/FPL.2019.00065}, ISSN={1946-147X}, month={Sep.},} +@INPROCEEDINGS{XTang_FPT_2019, +author={X. Tang and E. Giacomin and A. Alacchi and P. Gaillardon}, +booktitle={2019 International Conference on Field-Programmable Technology (ICFPT)}, +title={A Study on Switch Block Patterns for Tileable FPGA Routing Architectures}, +year={2019}, +volume={}, +number={}, +doi={10.1109/ICFPT47387.2019.00039}, +pages={247-250},} From 3b3c39454b22977ce0999e979c7ffeffb95aa977 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 25 Mar 2020 17:55:28 -0600 Subject: [PATCH 349/645] update print_route() in VPR to show correct track_id when tileable routing is used --- vpr/src/route/route_common.cpp | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/vpr/src/route/route_common.cpp b/vpr/src/route/route_common.cpp index 4e1cda2ae..8fd661d05 100644 --- a/vpr/src/route/route_common.cpp +++ b/vpr/src/route/route_common.cpp @@ -1490,13 +1490,36 @@ void print_route(FILE* fp, const vtr::vector& traceba } else { /* IO Pad. */ fprintf(fp, " Pin: "); } + fprintf(fp, "%d ", device_ctx.rr_graph.node_pin_num(inode)); break; case CHANX: case CHANY: fprintf(fp, " Track: "); + /* Xifan Tang: + * The routing track id depends on the direction + * A routing track may have multiple track ids + * The track id is the starting point of a routing track + * - INC_DIRECTION: the first track id in the list + * - DEC_DIRECTION: the last track id in the list + * + * This is because (xlow, ylow) is always < (xhigh, yhigh) + * which is even true for DEC_DIRECTION routing tracks + */ + if (1 < device_ctx.rr_graph.node_track_ids(inode).size()) { + fprintf(fp, "("); + for (size_t itrack = 0; itrack < device_ctx.rr_graph.node_track_ids(inode).size(); ++itrack) { + if (0 < itrack) { + fprintf(fp, ", "); + } + fprintf(fp, "%d", device_ctx.rr_graph.node_track_ids(inode)[itrack]); + } + fprintf(fp, ") "); + } else { + VTR_ASSERT(1 == device_ctx.rr_graph.node_track_ids(inode).size()); + fprintf(fp, "%d ", device_ctx.rr_graph.node_track_num(inode)); + } break; - case SOURCE: case SINK: if (is_io_type(device_ctx.grid[ilow][jlow].type)) { @@ -1504,6 +1527,7 @@ void print_route(FILE* fp, const vtr::vector& traceba } else { /* IO Pad. */ fprintf(fp, " Class: "); } + fprintf(fp, "%d ", device_ctx.rr_graph.node_class_num(inode)); break; default: @@ -1513,7 +1537,7 @@ void print_route(FILE* fp, const vtr::vector& traceba break; } - fprintf(fp, "%d ", device_ctx.rr_graph.node_ptc_num(inode)); + //fprintf(fp, "%d ", device_ctx.rr_graph.node_ptc_num(inode)); if (!is_io_type(device_ctx.grid[ilow][jlow].type) && (rr_type == IPIN || rr_type == OPIN)) { int pin_num = device_ctx.rr_graph.node_ptc_num(inode); From 91a618466db0bb57e2f5151b3ca30c39e8b49c70 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 27 Mar 2020 10:52:48 -0600 Subject: [PATCH 350/645] bug fixing for rr_graph.clear() function --- openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml | 2 +- vpr/src/device/rr_graph_obj.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml index 9422c3a09..7e4cf8e7a 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml @@ -193,7 +193,7 @@ - + diff --git a/vpr/src/device/rr_graph_obj.cpp b/vpr/src/device/rr_graph_obj.cpp index 4a6ca1256..07395cee5 100644 --- a/vpr/src/device/rr_graph_obj.cpp +++ b/vpr/src/device/rr_graph_obj.cpp @@ -1500,6 +1500,7 @@ void RRGraph::clear_nodes() { node_sides_.clear(); node_Rs_.clear(); node_Cs_.clear(); + node_rc_data_indices_.clear(); node_segments_.clear(); node_num_in_edges_.clear(); From 5ce078fe60190d0f88816bae66e27902b2b0fcff Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 27 Mar 2020 11:26:14 -0600 Subject: [PATCH 351/645] minor fix on rr_graph.clear() --- vpr/src/device/rr_graph_obj.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/vpr/src/device/rr_graph_obj.cpp b/vpr/src/device/rr_graph_obj.cpp index 07395cee5..de8706de7 100644 --- a/vpr/src/device/rr_graph_obj.cpp +++ b/vpr/src/device/rr_graph_obj.cpp @@ -1011,7 +1011,7 @@ void RRGraph::set_node_ptc_num(const RRNodeId& node, const short& ptc) { */ if ((CHANX == node_type(node)) || (CHANY == node_type(node))) { if ((size_t)node_length(node) + 1 != node_ptc_nums_[node].size()) { - node_ptc_nums_[node].resize(node_length(node) + 1); + node_ptc_nums_[node].resize((size_t)node_length(node) + 1); } std::fill(node_ptc_nums_[node].begin(), node_ptc_nums_[node].end(), ptc); } else { @@ -1048,7 +1048,7 @@ void RRGraph::add_node_track_num(const RRNodeId& node, VTR_ASSERT_MSG(node_type(node) == CHANX || node_type(node) == CHANY, "Track number valid only for CHANX/CHANY RR nodes"); if ((size_t)node_length(node) + 1 != node_ptc_nums_[node].size()) { - node_ptc_nums_[node].resize(node_length(node) + 1); + node_ptc_nums_[node].resize((size_t)node_length(node) + 1); } size_t offset = node_offset.x() - node_xlow(node) + node_offset.y() - node_ylow(node); @@ -1540,4 +1540,14 @@ void RRGraph::clear() { clear_edges(); clear_switches(); clear_segments(); + + invalidate_fast_node_lookup(); + + /* Clear invalid node list */ + invalid_node_ids_.clear(); + + /* Clear invalid edge list */ + invalid_edge_ids_.clear(); + + clear_dirty(); } From e47a0a442285b2b458e2d6a95ca7b581bf11d14f Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 27 Mar 2020 11:32:44 -0600 Subject: [PATCH 352/645] add through channel architecture example --- .../k6_frac_N10_adder_chain_mem16K_40nm.xml | 2 +- ...0_adder_chain_mem16K_thru_channel_40nm.xml | 734 ++++++++++++++++++ 2 files changed, 735 insertions(+), 1 deletion(-) create mode 100644 openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_thru_channel_40nm.xml diff --git a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml index 7e4cf8e7a..9422c3a09 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml @@ -193,7 +193,7 @@ - + diff --git a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_thru_channel_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_thru_channel_40nm.xml new file mode 100644 index 000000000..7e4cf8e7a --- /dev/null +++ b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_thru_channel_40nm.xml @@ -0,0 +1,734 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + + + + + + + + + + + + + + + + + + + clb.clk + clb.cin + clb.O[9:0] clb.I[19:0] + clb.cout clb.O[19:10] clb.I[39:20] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 235e-12 + 235e-12 + 235e-12 + 235e-12 + 235e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 195e-12 + 195e-12 + 195e-12 + 195e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 78964ce71c66362f1c389c64a44d941aade00db3 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 27 Mar 2020 11:34:39 -0600 Subject: [PATCH 353/645] update documentation on the through channel --- docs/source/arch_lang/addon_vpr_syntax.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source/arch_lang/addon_vpr_syntax.rst b/docs/source/arch_lang/addon_vpr_syntax.rst index 3f51e005d..1e2da988f 100644 --- a/docs/source/arch_lang/addon_vpr_syntax.rst +++ b/docs/source/arch_lang/addon_vpr_syntax.rst @@ -37,6 +37,8 @@ Each ```` should contain a ```` that describe the physical implem .. warning:: Do NOT enable if you are not using the tileable routing resource graph generator! + .. warning:: Current through channel supports only a fixed routing channel! + ```` may include addition syntax to enable different connectivity for pass tracks .. option:: sub_type="" From b09b051249090a090d890d934ae96f1c99b71880 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 27 Mar 2020 13:58:35 -0600 Subject: [PATCH 354/645] add all the test cases considering tileable, carry chain, direct connection and memory blocks --- .../test_script/and_k6_frac_tileable.openfpga | 59 + .../and_k6_frac_tileable_adder_chain.openfpga | 62 + ..._frac_tileable_adder_chain_mem16K.openfpga | 62 + ...e_thru_channel_adder_chain_mem16K.openfpga | 62 + .../and_latch_k6_frac_tileable.openfpga | 59 + ...atch_k6_frac_tileable_adder_chain.openfpga | 62 + ..._frac_tileable_adder_chain_mem16K.openfpga | 62 + openfpga/test_vpr_arch/k6_N10_40nm.xml | 2 +- .../test_vpr_arch/k6_N10_tileable_40nm.xml | 299 ++++ openfpga/test_vpr_arch/k6_frac_N10_40nm.xml | 2 +- .../k6_frac_N10_adder_chain_40nm.xml | 2 +- .../k6_frac_N10_adder_chain_mem16K_40nm.xml | 2 +- ...rac_N10_frac_chain_depop50_mem32K_40nm.xml | 1429 ----------------- .../k6_frac_N10_tileable_40nm.xml | 441 +++++ .../k6_frac_N10_tileable_adder_chain_40nm.xml | 639 ++++++++ ...c_N10_tileable_adder_chain_mem16K_40nm.xml | 734 +++++++++ ..._thru_channel_adder_chain_mem16K_40nm.xml} | 0 17 files changed, 2545 insertions(+), 1433 deletions(-) create mode 100644 openfpga/test_script/and_k6_frac_tileable.openfpga create mode 100644 openfpga/test_script/and_k6_frac_tileable_adder_chain.openfpga create mode 100644 openfpga/test_script/and_k6_frac_tileable_adder_chain_mem16K.openfpga create mode 100644 openfpga/test_script/and_k6_frac_tileable_thru_channel_adder_chain_mem16K.openfpga create mode 100644 openfpga/test_script/and_latch_k6_frac_tileable.openfpga create mode 100644 openfpga/test_script/and_latch_k6_frac_tileable_adder_chain.openfpga create mode 100644 openfpga/test_script/and_latch_k6_frac_tileable_adder_chain_mem16K.openfpga create mode 100644 openfpga/test_vpr_arch/k6_N10_tileable_40nm.xml delete mode 100644 openfpga/test_vpr_arch/k6_frac_N10_frac_chain_depop50_mem32K_40nm.xml create mode 100644 openfpga/test_vpr_arch/k6_frac_N10_tileable_40nm.xml create mode 100644 openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_40nm.xml create mode 100644 openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_40nm.xml rename openfpga/test_vpr_arch/{k6_frac_N10_adder_chain_mem16K_thru_channel_40nm.xml => k6_frac_N10_tileable_thru_channel_adder_chain_mem16K_40nm.xml} (100%) diff --git a/openfpga/test_script/and_k6_frac_tileable.openfpga b/openfpga/test_script/and_k6_frac_tileable.openfpga new file mode 100644 index 000000000..0731e6543 --- /dev/null +++ b/openfpga/test_script/and_k6_frac_tileable.openfpga @@ -0,0 +1,59 @@ +# Run VPR for the 'and' design +vpr ./test_vpr_arch/k6_frac_N10_tileable_40nm.xml ./test_blif/and.blif --clock_modeling route #--write_rr_graph example_rr_graph.xml + +# Read OpenFPGA architecture definition +read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml + +# Write out the architecture XML as a proof +#write_openfpga_arch -f ./arch_echo.xml + +# Annotate the OpenFPGA architecture to VPR data base +link_openfpga_arch --activity_file ./test_blif/and.act --sort_gsb_chan_node_in_edges #--verbose + +# Check and correct any naming conflicts in the BLIF netlist +check_netlist_naming_conflict --fix --report ./netlist_renaming.xml + +# Apply fix-up to clustering nets based on routing results +pb_pin_fixup --verbose + +# Apply fix-up to Look-Up Table truth tables based on packing results +lut_truth_table_fixup #--verbose + +# Build the module graph +# - Enabled compression on routing architecture modules +# - Enable pin duplication on grid modules +build_fabric --compress_routing --duplicate_grid_pin #--verbose + +# Repack the netlist to physical pbs +# This must be done before bitstream generator and testbench generation +# Strongly recommend it is done after all the fix-up have been applied +repack #--verbose + +# Build the bitstream +# - Output the fabric-independent bitstream to a file +build_architecture_bitstream --verbose --file /var/tmp/xtang/openfpga_test_src/fabric_indepenent_bitstream.xml + +# Build fabric-dependent bitstream +build_fabric_bitstream --verbose + +# Write the Verilog netlist for FPGA fabric +# - Enable the use of explicit port mapping in Verilog netlist +write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src/SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose + +# Write the Verilog testbench for FPGA fabric +# - We suggest the use of same output directory as fabric Verilog netlists +# - Must specify the reference benchmark file if you want to output any testbenches +# - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA +# - Enable pre-configured top-level testbench which is a fast verification skipping programming phase +# - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts +write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src/SRC --reference_benchmark_file_path /var/tmp/xtang/and.v --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini /var/tmp/xtang/openfpga_test_src/simulation_deck.ini + +# Write the SDC files for PnR backend +# - Turn on every options here +write_pnr_sdc --file /var/tmp/xtang/openfpga_test_src/SDC + +# Write the SDC to run timing analysis for a mapped FPGA fabric +write_analysis_sdc --file /var/tmp/xtang/openfpga_test_src/SDC_analysis + +# Finish and exit OpenFPGA +exit diff --git a/openfpga/test_script/and_k6_frac_tileable_adder_chain.openfpga b/openfpga/test_script/and_k6_frac_tileable_adder_chain.openfpga new file mode 100644 index 000000000..77fa29aa1 --- /dev/null +++ b/openfpga/test_script/and_k6_frac_tileable_adder_chain.openfpga @@ -0,0 +1,62 @@ +# Run VPR for the 'and' design +vpr ./test_vpr_arch/k6_frac_N10_tileable_adder_chain_40nm.xml ./test_blif/and.blif --route_chan_width 40 --clock_modeling route #--write_rr_graph example_rr_graph.xml + +# Read OpenFPGA architecture definition +read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml + +# Write out the architecture XML as a proof +#write_openfpga_arch -f ./arch_echo.xml + +# Annotate the OpenFPGA architecture to VPR data base +link_openfpga_arch --activity_file ./test_blif/and.act --sort_gsb_chan_node_in_edges #--verbose + +# Write GSB to XML for debugging +write_gsb_to_xml --file /var/tmp/xtang/openfpga_test_src/gsb_xml + +# Check and correct any naming conflicts in the BLIF netlist +check_netlist_naming_conflict --fix --report ./netlist_renaming.xml + +# Apply fix-up to clustering nets based on routing results +pb_pin_fixup --verbose + +# Apply fix-up to Look-Up Table truth tables based on packing results +lut_truth_table_fixup #--verbose + +# Build the module graph +# - Enabled compression on routing architecture modules +# - Enable pin duplication on grid modules +build_fabric --compress_routing --duplicate_grid_pin --verbose + +# Repack the netlist to physical pbs +# This must be done before bitstream generator and testbench generation +# Strongly recommend it is done after all the fix-up have been applied +repack #--verbose + +# Build the bitstream +# - Output the fabric-independent bitstream to a file +build_architecture_bitstream --verbose --file /var/tmp/xtang/openfpga_test_src/fabric_indepenent_bitstream.xml + +# Build fabric-dependent bitstream +build_fabric_bitstream --verbose + +# Write the Verilog netlist for FPGA fabric +# - Enable the use of explicit port mapping in Verilog netlist +write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src/SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose + +# Write the Verilog testbench for FPGA fabric +# - We suggest the use of same output directory as fabric Verilog netlists +# - Must specify the reference benchmark file if you want to output any testbenches +# - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA +# - Enable pre-configured top-level testbench which is a fast verification skipping programming phase +# - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts +write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src/SRC --reference_benchmark_file_path /var/tmp/xtang/and.v --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini /var/tmp/xtang/openfpga_test_src/simulation_deck.ini + +# Write the SDC files for PnR backend +# - Turn on every options here +write_pnr_sdc --file /var/tmp/xtang/openfpga_test_src/SDC + +# Write the SDC to run timing analysis for a mapped FPGA fabric +write_analysis_sdc --file /var/tmp/xtang/openfpga_test_src/SDC_analysis + +# Finish and exit OpenFPGA +exit diff --git a/openfpga/test_script/and_k6_frac_tileable_adder_chain_mem16K.openfpga b/openfpga/test_script/and_k6_frac_tileable_adder_chain_mem16K.openfpga new file mode 100644 index 000000000..ed0d8cc5a --- /dev/null +++ b/openfpga/test_script/and_k6_frac_tileable_adder_chain_mem16K.openfpga @@ -0,0 +1,62 @@ +# Run VPR for the 'and' design +vpr ./test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_40nm.xml ./test_blif/and.blif --route_chan_width 40 --clock_modeling route #--write_rr_graph example_rr_graph.xml + +# Read OpenFPGA architecture definition +read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml + +# Write out the architecture XML as a proof +#write_openfpga_arch -f ./arch_echo.xml + +# Annotate the OpenFPGA architecture to VPR data base +link_openfpga_arch --activity_file ./test_blif/and.act --sort_gsb_chan_node_in_edges #--verbose + +# Write GSB to XML for debugging +write_gsb_to_xml --file /var/tmp/xtang/openfpga_test_src/gsb_xml + +# Check and correct any naming conflicts in the BLIF netlist +check_netlist_naming_conflict --fix --report ./netlist_renaming.xml + +# Apply fix-up to clustering nets based on routing results +pb_pin_fixup --verbose + +# Apply fix-up to Look-Up Table truth tables based on packing results +lut_truth_table_fixup #--verbose + +# Build the module graph +# - Enabled compression on routing architecture modules +# - Enable pin duplication on grid modules +build_fabric --compress_routing --duplicate_grid_pin --verbose + +# Repack the netlist to physical pbs +# This must be done before bitstream generator and testbench generation +# Strongly recommend it is done after all the fix-up have been applied +repack #--verbose + +# Build the bitstream +# - Output the fabric-independent bitstream to a file +build_architecture_bitstream --verbose --file /var/tmp/xtang/openfpga_test_src/fabric_indepenent_bitstream.xml + +# Build fabric-dependent bitstream +build_fabric_bitstream --verbose + +# Write the Verilog netlist for FPGA fabric +# - Enable the use of explicit port mapping in Verilog netlist +write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src/SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose + +# Write the Verilog testbench for FPGA fabric +# - We suggest the use of same output directory as fabric Verilog netlists +# - Must specify the reference benchmark file if you want to output any testbenches +# - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA +# - Enable pre-configured top-level testbench which is a fast verification skipping programming phase +# - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts +write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src/SRC --reference_benchmark_file_path /var/tmp/xtang/and.v --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini /var/tmp/xtang/openfpga_test_src/simulation_deck.ini + +# Write the SDC files for PnR backend +# - Turn on every options here +write_pnr_sdc --file /var/tmp/xtang/openfpga_test_src/SDC + +# Write the SDC to run timing analysis for a mapped FPGA fabric +write_analysis_sdc --file /var/tmp/xtang/openfpga_test_src/SDC_analysis + +# Finish and exit OpenFPGA +exit diff --git a/openfpga/test_script/and_k6_frac_tileable_thru_channel_adder_chain_mem16K.openfpga b/openfpga/test_script/and_k6_frac_tileable_thru_channel_adder_chain_mem16K.openfpga new file mode 100644 index 000000000..5b1177586 --- /dev/null +++ b/openfpga/test_script/and_k6_frac_tileable_thru_channel_adder_chain_mem16K.openfpga @@ -0,0 +1,62 @@ +# Run VPR for the 'and' design +vpr ./test_vpr_arch/k6_frac_N10_tileable_thru_channel_adder_chain_mem16K_40nm.xml ./test_blif/and.blif --route_chan_width 40 --clock_modeling route #--write_rr_graph example_rr_graph.xml + +# Read OpenFPGA architecture definition +read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml + +# Write out the architecture XML as a proof +#write_openfpga_arch -f ./arch_echo.xml + +# Annotate the OpenFPGA architecture to VPR data base +link_openfpga_arch --activity_file ./test_blif/and.act --sort_gsb_chan_node_in_edges #--verbose + +# Write GSB to XML for debugging +write_gsb_to_xml --file /var/tmp/xtang/openfpga_test_src/gsb_xml + +# Check and correct any naming conflicts in the BLIF netlist +check_netlist_naming_conflict --fix --report ./netlist_renaming.xml + +# Apply fix-up to clustering nets based on routing results +pb_pin_fixup --verbose + +# Apply fix-up to Look-Up Table truth tables based on packing results +lut_truth_table_fixup #--verbose + +# Build the module graph +# - Enabled compression on routing architecture modules +# - Enable pin duplication on grid modules +build_fabric --compress_routing --duplicate_grid_pin --verbose + +# Repack the netlist to physical pbs +# This must be done before bitstream generator and testbench generation +# Strongly recommend it is done after all the fix-up have been applied +repack #--verbose + +# Build the bitstream +# - Output the fabric-independent bitstream to a file +build_architecture_bitstream --verbose --file /var/tmp/xtang/openfpga_test_src/fabric_indepenent_bitstream.xml + +# Build fabric-dependent bitstream +build_fabric_bitstream --verbose + +# Write the Verilog netlist for FPGA fabric +# - Enable the use of explicit port mapping in Verilog netlist +write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src/SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose + +# Write the Verilog testbench for FPGA fabric +# - We suggest the use of same output directory as fabric Verilog netlists +# - Must specify the reference benchmark file if you want to output any testbenches +# - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA +# - Enable pre-configured top-level testbench which is a fast verification skipping programming phase +# - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts +write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src/SRC --reference_benchmark_file_path /var/tmp/xtang/and.v --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini /var/tmp/xtang/openfpga_test_src/simulation_deck.ini + +# Write the SDC files for PnR backend +# - Turn on every options here +write_pnr_sdc --file /var/tmp/xtang/openfpga_test_src/SDC + +# Write the SDC to run timing analysis for a mapped FPGA fabric +write_analysis_sdc --file /var/tmp/xtang/openfpga_test_src/SDC_analysis + +# Finish and exit OpenFPGA +exit diff --git a/openfpga/test_script/and_latch_k6_frac_tileable.openfpga b/openfpga/test_script/and_latch_k6_frac_tileable.openfpga new file mode 100644 index 000000000..c89174d2d --- /dev/null +++ b/openfpga/test_script/and_latch_k6_frac_tileable.openfpga @@ -0,0 +1,59 @@ +# Run VPR for the 'and_latch' design +vpr ./test_vpr_arch/k6_frac_N10_tileable_40nm.xml ./test_blif/and_latch.blif --clock_modeling route #--write_rr_graph example_rr_graph.xml + +# Read OpenFPGA architecture definition +read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml + +# Write out the architecture XML as a proof +#write_openfpga_arch -f ./arch_echo.xml + +# Annotate the OpenFPGA architecture to VPR data base +link_openfpga_arch --activity_file ./test_blif/and_latch.act --sort_gsb_chan_node_in_edges #--verbose + +# Check and correct any naming conflicts in the BLIF netlist +check_netlist_naming_conflict --fix --report ./netlist_renaming.xml + +# Apply fix-up to clustering nets based on routing results +pb_pin_fixup --verbose + +# Apply fix-up to Look-Up Table truth tables based on packing results +lut_truth_table_fixup #--verbose + +# Build the module graph +# - Enabled compression on routing architecture modules +# - Enable pin duplication on grid modules +build_fabric --compress_routing --duplicate_grid_pin #--verbose + +# Repack the netlist to physical pbs +# This must be done before bitstream generator and testbench generation +# Strongly recommend it is done after all the fix-up have been applied +repack --verbose + +# Build the bitstream +# - Output the fabric-independent bitstream to a file +build_architecture_bitstream --verbose --file /var/tmp/xtang/openfpga_test_src/fabric_indepenent_bitstream.xml + +# Build fabric-dependent bitstream +build_fabric_bitstream --verbose + +# Write the Verilog netlist for FPGA fabric +# - Enable the use of explicit port mapping in Verilog netlist +write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src/SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose + +# Write the Verilog testbench for FPGA fabric +# - We suggest the use of same output directory as fabric Verilog netlists +# - Must specify the reference benchmark file if you want to output any testbenches +# - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA +# - Enable pre-configured top-level testbench which is a fast verification skipping programming phase +# - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts +write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src/SRC --reference_benchmark_file_path /var/tmp/xtang/and_latch.v --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini /var/tmp/xtang/openfpga_test_src/simulation_deck.ini + +# Write the SDC files for PnR backend +# - Turn on every options here +write_pnr_sdc --file /var/tmp/xtang/openfpga_test_src/SDC + +# Write the SDC to run timing analysis for a mapped FPGA fabric +write_analysis_sdc --file /var/tmp/xtang/openfpga_test_src/SDC_analysis + +# Finish and exit OpenFPGA +exit diff --git a/openfpga/test_script/and_latch_k6_frac_tileable_adder_chain.openfpga b/openfpga/test_script/and_latch_k6_frac_tileable_adder_chain.openfpga new file mode 100644 index 000000000..ef49426d0 --- /dev/null +++ b/openfpga/test_script/and_latch_k6_frac_tileable_adder_chain.openfpga @@ -0,0 +1,62 @@ +# Run VPR for the 'and' design +vpr ./test_vpr_arch/k6_frac_N10_tileable_adder_chain_40nm.xml ./test_blif/and_latch.blif --route_chan_width 40 --clock_modeling route #--write_rr_graph example_rr_graph.xml + +# Read OpenFPGA architecture definition +read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml + +# Write out the architecture XML as a proof +#write_openfpga_arch -f ./arch_echo.xml + +# Annotate the OpenFPGA architecture to VPR data base +link_openfpga_arch --activity_file ./test_blif/and_latch.act --sort_gsb_chan_node_in_edges #--verbose + +# Write GSB to XML for debugging +write_gsb_to_xml --file /var/tmp/xtang/openfpga_test_src/gsb_xml + +# Check and correct any naming conflicts in the BLIF netlist +check_netlist_naming_conflict --fix --report ./netlist_renaming.xml + +# Apply fix-up to clustering nets based on routing results +pb_pin_fixup --verbose + +# Apply fix-up to Look-Up Table truth tables based on packing results +lut_truth_table_fixup #--verbose + +# Build the module graph +# - Enabled compression on routing architecture modules +# - Enable pin duplication on grid modules +build_fabric --compress_routing --duplicate_grid_pin --verbose + +# Repack the netlist to physical pbs +# This must be done before bitstream generator and testbench generation +# Strongly recommend it is done after all the fix-up have been applied +repack #--verbose + +# Build the bitstream +# - Output the fabric-independent bitstream to a file +build_architecture_bitstream --verbose --file /var/tmp/xtang/openfpga_test_src/fabric_indepenent_bitstream.xml + +# Build fabric-dependent bitstream +build_fabric_bitstream --verbose + +# Write the Verilog netlist for FPGA fabric +# - Enable the use of explicit port mapping in Verilog netlist +write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src/SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose + +# Write the Verilog testbench for FPGA fabric +# - We suggest the use of same output directory as fabric Verilog netlists +# - Must specify the reference benchmark file if you want to output any testbenches +# - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA +# - Enable pre-configured top-level testbench which is a fast verification skipping programming phase +# - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts +write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src/SRC --reference_benchmark_file_path /var/tmp/xtang/and_latch.v --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini /var/tmp/xtang/openfpga_test_src/simulation_deck.ini + +# Write the SDC files for PnR backend +# - Turn on every options here +write_pnr_sdc --file /var/tmp/xtang/openfpga_test_src/SDC + +# Write the SDC to run timing analysis for a mapped FPGA fabric +write_analysis_sdc --file /var/tmp/xtang/openfpga_test_src/SDC_analysis + +# Finish and exit OpenFPGA +exit diff --git a/openfpga/test_script/and_latch_k6_frac_tileable_adder_chain_mem16K.openfpga b/openfpga/test_script/and_latch_k6_frac_tileable_adder_chain_mem16K.openfpga new file mode 100644 index 000000000..87c69c880 --- /dev/null +++ b/openfpga/test_script/and_latch_k6_frac_tileable_adder_chain_mem16K.openfpga @@ -0,0 +1,62 @@ +# Run VPR for the 'and' design +vpr ./test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_40nm.xml ./test_blif/and_latch.blif --route_chan_width 40 --clock_modeling route #--write_rr_graph example_rr_graph.xml + +# Read OpenFPGA architecture definition +read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml + +# Write out the architecture XML as a proof +#write_openfpga_arch -f ./arch_echo.xml + +# Annotate the OpenFPGA architecture to VPR data base +link_openfpga_arch --activity_file ./test_blif/and_latch.act --sort_gsb_chan_node_in_edges #--verbose + +# Write GSB to XML for debugging +write_gsb_to_xml --file /var/tmp/xtang/openfpga_test_src/gsb_xml + +# Check and correct any naming conflicts in the BLIF netlist +check_netlist_naming_conflict --fix --report ./netlist_renaming.xml + +# Apply fix-up to clustering nets based on routing results +pb_pin_fixup --verbose + +# Apply fix-up to Look-Up Table truth tables based on packing results +lut_truth_table_fixup #--verbose + +# Build the module graph +# - Enabled compression on routing architecture modules +# - Enable pin duplication on grid modules +build_fabric --compress_routing --duplicate_grid_pin --verbose + +# Repack the netlist to physical pbs +# This must be done before bitstream generator and testbench generation +# Strongly recommend it is done after all the fix-up have been applied +repack #--verbose + +# Build the bitstream +# - Output the fabric-independent bitstream to a file +build_architecture_bitstream --verbose --file /var/tmp/xtang/openfpga_test_src/fabric_indepenent_bitstream.xml + +# Build fabric-dependent bitstream +build_fabric_bitstream --verbose + +# Write the Verilog netlist for FPGA fabric +# - Enable the use of explicit port mapping in Verilog netlist +write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src/SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose + +# Write the Verilog testbench for FPGA fabric +# - We suggest the use of same output directory as fabric Verilog netlists +# - Must specify the reference benchmark file if you want to output any testbenches +# - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA +# - Enable pre-configured top-level testbench which is a fast verification skipping programming phase +# - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts +write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src/SRC --reference_benchmark_file_path /var/tmp/xtang/and_latch.v --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini /var/tmp/xtang/openfpga_test_src/simulation_deck.ini + +# Write the SDC files for PnR backend +# - Turn on every options here +write_pnr_sdc --file /var/tmp/xtang/openfpga_test_src/SDC + +# Write the SDC to run timing analysis for a mapped FPGA fabric +write_analysis_sdc --file /var/tmp/xtang/openfpga_test_src/SDC_analysis + +# Finish and exit OpenFPGA +exit diff --git a/openfpga/test_vpr_arch/k6_N10_40nm.xml b/openfpga/test_vpr_arch/k6_N10_40nm.xml index bbd694a79..630011e84 100644 --- a/openfpga/test_vpr_arch/k6_N10_40nm.xml +++ b/openfpga/test_vpr_arch/k6_N10_40nm.xml @@ -63,7 +63,7 @@ - + diff --git a/openfpga/test_vpr_arch/k6_N10_tileable_40nm.xml b/openfpga/test_vpr_arch/k6_N10_tileable_40nm.xml new file mode 100644 index 000000000..d86d7aa62 --- /dev/null +++ b/openfpga/test_vpr_arch/k6_N10_tileable_40nm.xml @@ -0,0 +1,299 @@ + + + + + + + + + + + + + + + + + + + + + + + + + io.outpad io.inpad io.clock + io.outpad io.inpad io.clock + io.outpad io.inpad io.clock + io.outpad io.inpad io.clock + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml index 2c528c13c..ef9268810 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml @@ -76,7 +76,7 @@ - + diff --git a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml index eea76837f..bbe041562 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml @@ -159,7 +159,7 @@ - + diff --git a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml index 9422c3a09..850eb5450 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml @@ -193,7 +193,7 @@ - + diff --git a/openfpga/test_vpr_arch/k6_frac_N10_frac_chain_depop50_mem32K_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_frac_chain_depop50_mem32K_40nm.xml deleted file mode 100644 index af4612b61..000000000 --- a/openfpga/test_vpr_arch/k6_frac_N10_frac_chain_depop50_mem32K_40nm.xml +++ /dev/null @@ -1,1429 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - io.outpad io.inpad io.clock - io.outpad io.inpad io.clock - io.outpad io.inpad io.clock - io.outpad io.inpad io.clock - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 1 1 1 1 - 1 1 1 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 235e-12 - 235e-12 - 235e-12 - 235e-12 - 235e-12 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 195e-12 - 195e-12 - 195e-12 - 195e-12 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 261e-12 - 261e-12 - 261e-12 - 261e-12 - 261e-12 - 261e-12 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/openfpga/test_vpr_arch/k6_frac_N10_tileable_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_tileable_40nm.xml new file mode 100644 index 000000000..2c528c13c --- /dev/null +++ b/openfpga/test_vpr_arch/k6_frac_N10_tileable_40nm.xml @@ -0,0 +1,441 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 235e-12 + 235e-12 + 235e-12 + 235e-12 + 235e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_40nm.xml new file mode 100644 index 000000000..eea76837f --- /dev/null +++ b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_40nm.xml @@ -0,0 +1,639 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + + + + + + + + + + + + + + + + + + + clb.clk + clb.cin + clb.O[9:0] clb.I[19:0] + clb.cout clb.O[19:10] clb.I[39:20] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 235e-12 + 235e-12 + 235e-12 + 235e-12 + 235e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 195e-12 + 195e-12 + 195e-12 + 195e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_40nm.xml new file mode 100644 index 000000000..9422c3a09 --- /dev/null +++ b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_40nm.xml @@ -0,0 +1,734 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + + + + + + + + + + + + + + + + + + + clb.clk + clb.cin + clb.O[9:0] clb.I[19:0] + clb.cout clb.O[19:10] clb.I[39:20] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 235e-12 + 235e-12 + 235e-12 + 235e-12 + 235e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 195e-12 + 195e-12 + 195e-12 + 195e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_thru_channel_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_tileable_thru_channel_adder_chain_mem16K_40nm.xml similarity index 100% rename from openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_thru_channel_40nm.xml rename to openfpga/test_vpr_arch/k6_frac_N10_tileable_thru_channel_adder_chain_mem16K_40nm.xml From 7c9c2451f27463998d02bc12e0578adcc4ec4581 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 27 Mar 2020 16:03:42 -0600 Subject: [PATCH 355/645] debugging multiple io_types; bug fixed to support I/Os in more flexible location of FPGA fabric --- openfpga/src/base/openfpga_pb_pin_fixup.cpp | 2 +- openfpga/src/fabric/build_grid_modules.cpp | 17 +- .../utils/openfpga_physical_tile_utils.cpp | 65 ++ .../src/utils/openfpga_physical_tile_utils.h | 4 + ...er_chain_mem16K_multi_io_capacity.openfpga | 62 ++ ...er_chain_mem16K_multi_io_capacity_40nm.xml | 768 ++++++++++++++++++ 6 files changed, 913 insertions(+), 5 deletions(-) create mode 100644 openfpga/test_script/and_k6_frac_tileable_adder_chain_mem16K_multi_io_capacity.openfpga create mode 100644 openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_multi_io_capacity_40nm.xml diff --git a/openfpga/src/base/openfpga_pb_pin_fixup.cpp b/openfpga/src/base/openfpga_pb_pin_fixup.cpp index 901dbaa5f..4d1950678 100644 --- a/openfpga/src/base/openfpga_pb_pin_fixup.cpp +++ b/openfpga/src/base/openfpga_pb_pin_fixup.cpp @@ -64,7 +64,7 @@ void update_cluster_pin_with_post_routing_results(const DeviceContext& device_ct const bool& verbose) { /* Handle each pin */ auto logical_block = clustering_ctx.clb_nlist.block_type(blk_id); - auto physical_tile = pick_best_physical_type(logical_block); + auto physical_tile = device_ctx.grid[grid_coord.x()][grid_coord.y()].type; for (int j = 0; j < logical_block->pb_type->num_pins; j++) { /* Get the ptc num for the pin in rr_graph, we need t consider the z offset here diff --git a/openfpga/src/fabric/build_grid_modules.cpp b/openfpga/src/fabric/build_grid_modules.cpp index a59afa242..e55640834 100644 --- a/openfpga/src/fabric/build_grid_modules.cpp +++ b/openfpga/src/fabric/build_grid_modules.cpp @@ -18,6 +18,7 @@ #include "openfpga_reserved_words.h" #include "openfpga_naming.h" #include "openfpga_interconnect_types.h" +#include "openfpga_physical_tile_utils.h" #include "pb_type_utils.h" #include "pb_graph_utils.h" #include "module_manager_utils.h" @@ -1093,13 +1094,21 @@ void build_grid_modules(ModuleManager& module_manager, if (true == is_empty_type(&physical_tile)) { continue; } else if (true == is_io_type(&physical_tile)) { - /* Special for I/O block, generate one module for each border side */ - for (int iside = 0; iside < NUM_SIDES; iside++) { - SideManager side_manager(iside); + /* Special for I/O block: + * We will search the grids and see where the I/O blocks are located: + * - If a I/O block locates on border sides of FPGA fabric: + * i.e., one or more from {TOP, RIGHT, BOTTOM, LEFT}, + * we will generate one module for each border side + * - If a I/O block locates in the center of FPGA fabric: + * we will generate one module with NUM_SIDES (same treatment as regular grids) + */ + std::set io_type_sides = find_physical_io_tile_located_sides(device_ctx.grid, + &physical_tile); + for (const e_side& io_type_side : io_type_sides) { build_physical_tile_module(module_manager, circuit_lib, sram_orgz_type, sram_model, &physical_tile, - side_manager.get_side(), + io_type_side, duplicate_grid_pin, verbose); } diff --git a/openfpga/src/utils/openfpga_physical_tile_utils.cpp b/openfpga/src/utils/openfpga_physical_tile_utils.cpp index 9b70f7f97..375c70a19 100644 --- a/openfpga/src/utils/openfpga_physical_tile_utils.cpp +++ b/openfpga/src/utils/openfpga_physical_tile_utils.cpp @@ -30,4 +30,69 @@ float find_physical_tile_pin_Fc(t_physical_tile_type_ptr type, exit(1); } +/******************************************************************** + * Find sides/locations of a I/O physical tile in the context of a FPGA fabric + * The I/O grid may locate at + * - one or more border of a FPGA (TOP, RIGHT, BOTTOM, LEFT) + * We will collect each side that the I/O locates + * - the center of a FPGA + * We will add NUM_SIDEs for these I/Os + *******************************************************************/ +std::set find_physical_io_tile_located_sides(const DeviceGrid& grids, + t_physical_tile_type_ptr physical_tile) { + std::set io_sides; + bool center_io = false; + + /* Search the core part */ + for (size_t ix = 1; ix < grids.width() - 1; ++ix) { + for (size_t iy = 1; iy < grids.height() - 1; ++iy) { + /* If located in center, we add a NUM_SIDES and finish */ + if (physical_tile == grids[ix][iy].type) { + io_sides.insert(NUM_SIDES); + center_io = true; + break; + } + } + if (true == center_io) { + break; + } + } + + /* Search the border side */ + /* Create the coordinate range for each side of FPGA fabric */ + std::vector fpga_sides{TOP, RIGHT, BOTTOM, LEFT}; + std::map>> io_coordinates; + + /* TOP side*/ + for (size_t ix = 1; ix < grids.width() - 1; ++ix) { + io_coordinates[TOP].push_back(vtr::Point(ix, grids.height() - 1)); + } + + /* RIGHT side */ + for (size_t iy = 1; iy < grids.height() - 1; ++iy) { + io_coordinates[RIGHT].push_back(vtr::Point(grids.width() - 1, iy)); + } + + /* BOTTOM side*/ + for (size_t ix = 1; ix < grids.width() - 1; ++ix) { + io_coordinates[BOTTOM].push_back(vtr::Point(ix, 0)); + } + + /* LEFT side */ + for (size_t iy = 1; iy < grids.height() - 1; ++iy) { + io_coordinates[LEFT].push_back(vtr::Point(0, iy)); + } + for (const e_side& fpga_side : fpga_sides) { + for (const vtr::Point& io_coordinate : io_coordinates[fpga_side]) { + /* If located in center, we add a NUM_SIDES and finish */ + if (physical_tile == grids[io_coordinate.x()][io_coordinate.y()].type) { + io_sides.insert(fpga_side); + break; + } + } + } + + return io_sides; +} + } /* end namespace openfpga */ diff --git a/openfpga/src/utils/openfpga_physical_tile_utils.h b/openfpga/src/utils/openfpga_physical_tile_utils.h index 451d931a8..b2b39ab65 100644 --- a/openfpga/src/utils/openfpga_physical_tile_utils.h +++ b/openfpga/src/utils/openfpga_physical_tile_utils.h @@ -6,6 +6,8 @@ *******************************************************************/ #include #include +#include +#include "device_grid.h" #include "physical_types.h" /******************************************************************** @@ -18,6 +20,8 @@ namespace openfpga { float find_physical_tile_pin_Fc(t_physical_tile_type_ptr type, const int& pin); +std::set find_physical_io_tile_located_sides(const DeviceGrid& grids, + t_physical_tile_type_ptr physical_tile); } /* end namespace openfpga */ diff --git a/openfpga/test_script/and_k6_frac_tileable_adder_chain_mem16K_multi_io_capacity.openfpga b/openfpga/test_script/and_k6_frac_tileable_adder_chain_mem16K_multi_io_capacity.openfpga new file mode 100644 index 000000000..29830f1db --- /dev/null +++ b/openfpga/test_script/and_k6_frac_tileable_adder_chain_mem16K_multi_io_capacity.openfpga @@ -0,0 +1,62 @@ +# Run VPR for the 'and' design +vpr ./test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_multi_io_capacity_40nm.xml ./test_blif/and.blif --route_chan_width 40 --clock_modeling route #--write_rr_graph example_rr_graph.xml + +# Read OpenFPGA architecture definition +read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml + +# Write out the architecture XML as a proof +#write_openfpga_arch -f ./arch_echo.xml + +# Annotate the OpenFPGA architecture to VPR data base +link_openfpga_arch --activity_file ./test_blif/and.act --sort_gsb_chan_node_in_edges #--verbose + +# Write GSB to XML for debugging +write_gsb_to_xml --file /var/tmp/xtang/openfpga_test_src/gsb_xml + +# Check and correct any naming conflicts in the BLIF netlist +check_netlist_naming_conflict --fix --report ./netlist_renaming.xml + +# Apply fix-up to clustering nets based on routing results +pb_pin_fixup --verbose + +# Apply fix-up to Look-Up Table truth tables based on packing results +lut_truth_table_fixup #--verbose + +# Build the module graph +# - Enabled compression on routing architecture modules +# - Enable pin duplication on grid modules +build_fabric --compress_routing --duplicate_grid_pin --verbose + +# Repack the netlist to physical pbs +# This must be done before bitstream generator and testbench generation +# Strongly recommend it is done after all the fix-up have been applied +repack #--verbose + +# Build the bitstream +# - Output the fabric-independent bitstream to a file +build_architecture_bitstream --verbose --file /var/tmp/xtang/openfpga_test_src/fabric_indepenent_bitstream.xml + +# Build fabric-dependent bitstream +build_fabric_bitstream --verbose + +# Write the Verilog netlist for FPGA fabric +# - Enable the use of explicit port mapping in Verilog netlist +write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src/SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose + +# Write the Verilog testbench for FPGA fabric +# - We suggest the use of same output directory as fabric Verilog netlists +# - Must specify the reference benchmark file if you want to output any testbenches +# - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA +# - Enable pre-configured top-level testbench which is a fast verification skipping programming phase +# - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts +write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src/SRC --reference_benchmark_file_path /var/tmp/xtang/and.v --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini /var/tmp/xtang/openfpga_test_src/simulation_deck.ini + +# Write the SDC files for PnR backend +# - Turn on every options here +write_pnr_sdc --file /var/tmp/xtang/openfpga_test_src/SDC + +# Write the SDC to run timing analysis for a mapped FPGA fabric +write_analysis_sdc --file /var/tmp/xtang/openfpga_test_src/SDC_analysis + +# Finish and exit OpenFPGA +exit diff --git a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_multi_io_capacity_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_multi_io_capacity_40nm.xml new file mode 100644 index 000000000..8eb6f32bf --- /dev/null +++ b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_multi_io_capacity_40nm.xml @@ -0,0 +1,768 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + io_top.outpad io_top.inpad + + + + + + + + + + + io_right.outpad io_right.inpad + + + + + + + + + + + io_bottom.outpad io_bottom.inpad + + + + + + + + + + + io_left.outpad io_left.inpad + + + + + + + + + + + + + + + + + + + + clb.clk + clb.cin + clb.O[9:0] clb.I[19:0] + clb.cout clb.O[19:10] clb.I[39:20] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 235e-12 + 235e-12 + 235e-12 + 235e-12 + 235e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 195e-12 + 195e-12 + 195e-12 + 195e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 4bf0a63ae65527d6e310a474c44e7c8f441b94a8 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 27 Mar 2020 16:32:15 -0600 Subject: [PATCH 356/645] bug fixed for multiple io types defined in FPGA architectures --- .../src/fabric/build_top_module_connection.cpp | 6 ++++-- openfpga/src/fpga_verilog/verilog_grid.cpp | 17 +++++++++++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/openfpga/src/fabric/build_top_module_connection.cpp b/openfpga/src/fabric/build_top_module_connection.cpp index 31163db69..629129669 100644 --- a/openfpga/src/fabric/build_top_module_connection.cpp +++ b/openfpga/src/fabric/build_top_module_connection.cpp @@ -115,9 +115,10 @@ void add_top_module_nets_connect_grids_and_sb(ModuleManager& module_manager, /* Collect sink-related information */ vtr::Point sink_sb_port_coord(rr_graph.node_xlow(module_sb.get_opin_node(side_manager.get_side(), inode)), rr_graph.node_ylow(module_sb.get_opin_node(side_manager.get_side(), inode))); + size_t sink_grid_pin_index = rr_graph.node_pin_num(module_sb.get_opin_node(side_manager.get_side(), inode)); std::string sink_sb_port_name = generate_sb_module_grid_port_name(side_manager.get_side(), rr_graph.node_side(module_sb.get_opin_node(side_manager.get_side(), inode)), - src_grid_pin_index); + sink_grid_pin_index); ModulePortId sink_sb_port_id = module_manager.find_module_port(sink_sb_module, sink_sb_port_name); VTR_ASSERT(true == module_manager.valid_module_port_id(sink_sb_module, sink_sb_port_id)); BasicPort sink_sb_port = module_manager.module_port(sink_sb_module, sink_sb_port_id); @@ -259,9 +260,10 @@ void add_top_module_nets_connect_grids_and_sb_with_duplicated_pins(ModuleManager /* Collect sink-related information */ vtr::Point sink_sb_port_coord(rr_graph.node_xlow(module_sb.get_opin_node(side_manager.get_side(), inode)), rr_graph.node_ylow(module_sb.get_opin_node(side_manager.get_side(), inode))); + size_t sink_grid_pin_index = rr_graph.node_pin_num(module_sb.get_opin_node(side_manager.get_side(), inode)); std::string sink_sb_port_name = generate_sb_module_grid_port_name(side_manager.get_side(), rr_graph.node_side(module_sb.get_opin_node(side_manager.get_side(), inode)), - src_grid_pin_index); + sink_grid_pin_index); ModulePortId sink_sb_port_id = module_manager.find_module_port(sink_sb_module, sink_sb_port_name); VTR_ASSERT(true == module_manager.valid_module_port_id(sink_sb_module, sink_sb_port_id)); BasicPort sink_sb_port = module_manager.module_port(sink_sb_module, sink_sb_port_id); diff --git a/openfpga/src/fpga_verilog/verilog_grid.cpp b/openfpga/src/fpga_verilog/verilog_grid.cpp index 73e69ca29..20cdd02f4 100644 --- a/openfpga/src/fpga_verilog/verilog_grid.cpp +++ b/openfpga/src/fpga_verilog/verilog_grid.cpp @@ -23,6 +23,7 @@ #include "openfpga_reserved_words.h" #include "openfpga_naming.h" +#include "openfpga_physical_tile_utils.h" #include "pb_type_utils.h" #include "circuit_library_utils.h" #include "module_manager_utils.h" @@ -375,13 +376,21 @@ void print_verilog_grids(const ModuleManager& module_manager, if (true == is_empty_type(&physical_tile)) { continue; } else if (true == is_io_type(&physical_tile)) { - /* Special for I/O block, generate one module for each border side */ - for (int iside = 0; iside < NUM_SIDES; iside++) { - SideManager side_manager(iside); + /* Special for I/O block: + * We will search the grids and see where the I/O blocks are located: + * - If a I/O block locates on border sides of FPGA fabric: + * i.e., one or more from {TOP, RIGHT, BOTTOM, LEFT}, + * we will generate one module for each border side + * - If a I/O block locates in the center of FPGA fabric: + * we will generate one module with NUM_SIDES (same treatment as regular grids) + */ + std::set io_type_sides = find_physical_io_tile_located_sides(device_ctx.grid, + &physical_tile); + for (const e_side& io_type_side : io_type_sides) { print_verilog_physical_tile_netlist(module_manager, netlist_names, verilog_dir, subckt_dir, &physical_tile, - side_manager.get_side(), + io_type_side, use_explicit_mapping); } continue; From 34a1b61ecbed6a803583965695b0a85ec57b4460 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 27 Mar 2020 18:45:27 -0600 Subject: [PATCH 357/645] add an example FPGA architecture with AIB interface at the right side of I/Os --- ...0_tileable_adder_chain_mem16K_aib_40nm.xml | 800 ++++++++++++++++++ 1 file changed, 800 insertions(+) create mode 100644 openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_aib_40nm.xml diff --git a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_aib_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_aib_40nm.xml new file mode 100644 index 000000000..e196cb83c --- /dev/null +++ b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_aib_40nm.xml @@ -0,0 +1,800 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + + + + + + + + + + + + + + aib.tx_clk aib.tx_data aib.rx_clk aib.rx_data + + + + + + + + + + + + + + + + + + + clb.clk + clb.cin + clb.O[9:0] clb.I[19:0] + clb.cout clb.O[19:10] clb.I[39:20] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 235e-12 + 235e-12 + 235e-12 + 235e-12 + 235e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 195e-12 + 195e-12 + 195e-12 + 195e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From e601a648cc5ca9646b805d802fcf02cd37b95ebb Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 27 Mar 2020 19:07:34 -0600 Subject: [PATCH 358/645] relax asseration to allow AIB (non-I/O) blocks on the side of FPGA fabrics --- openfpga/src/base/openfpga_pb_pin_fixup.cpp | 2 - openfpga/src/fabric/build_top_module.cpp | 3 +- .../fpga_bitstream/build_grid_bitstream.cpp | 2 - .../src/fpga_sdc/analysis_sdc_grid_writer.cpp | 3 - ...0_adder_chain_mem16K_aib_40nm_openfpga.xml | 322 ++++++++++++++++++ ...c_tileable_adder_chain_mem16K_aib.openfpga | 62 ++++ 6 files changed, 385 insertions(+), 9 deletions(-) create mode 100644 openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml create mode 100644 openfpga/test_script/and_k6_frac_tileable_adder_chain_mem16K_aib.openfpga diff --git a/openfpga/src/base/openfpga_pb_pin_fixup.cpp b/openfpga/src/base/openfpga_pb_pin_fixup.cpp index 4d1950678..3e6fec2f7 100644 --- a/openfpga/src/base/openfpga_pb_pin_fixup.cpp +++ b/openfpga/src/base/openfpga_pb_pin_fixup.cpp @@ -224,8 +224,6 @@ void update_pb_pin_with_post_routing_results(const DeviceContext& device_ctx, if (true == is_empty_type(device_ctx.grid[io_coord.x()][io_coord.y()].type)) { continue; } - /* We must have an I/O type here */ - VTR_ASSERT(true == is_io_type(device_ctx.grid[io_coord.x()][io_coord.y()].type)); /* Get the mapped blocks to this grid */ for (const ClusterBlockId& cluster_blk_id : placement_ctx.grid_blocks[io_coord.x()][io_coord.y()].blocks) { /* Skip invalid ids */ diff --git a/openfpga/src/fabric/build_top_module.cpp b/openfpga/src/fabric/build_top_module.cpp index 83a4f0147..112b32a52 100644 --- a/openfpga/src/fabric/build_top_module.cpp +++ b/openfpga/src/fabric/build_top_module.cpp @@ -169,8 +169,7 @@ vtr::Matrix add_top_module_grid_instances(ModuleManager& module_manager, grid_instance_ids[io_coordinate.x()][io_coordinate.y()] = grid_instance_ids[root_grid_coord.x()][root_grid_coord.y()]; continue; } - /* We should not meet any I/O grid */ - VTR_ASSERT(true == is_io_type(grids[io_coordinate.x()][io_coordinate.y()].type)); + /* Add a grid module to top_module*/ grid_instance_ids[io_coordinate.x()][io_coordinate.y()] = add_top_module_grid_instance(module_manager, top_module, grids[io_coordinate.x()][io_coordinate.y()].type, io_side, io_coordinate); diff --git a/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp b/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp index f69b57ae6..204b80260 100644 --- a/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp +++ b/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp @@ -685,8 +685,6 @@ void build_grid_bitstream(BitstreamManager& bitstream_manager, || (0 < grids[io_coordinate.x()][io_coordinate.y()].height_offset) ) { continue; } - /* We should not meet any I/O grid */ - VTR_ASSERT(true == is_io_type(grids[io_coordinate.x()][io_coordinate.y()].type)); build_physical_block_bitstream(bitstream_manager, top_block, module_manager, circuit_lib, mux_lib, device_annotation, cluster_annotation, diff --git a/openfpga/src/fpga_sdc/analysis_sdc_grid_writer.cpp b/openfpga/src/fpga_sdc/analysis_sdc_grid_writer.cpp index d9a85280b..aa25c7e33 100644 --- a/openfpga/src/fpga_sdc/analysis_sdc_grid_writer.cpp +++ b/openfpga/src/fpga_sdc/analysis_sdc_grid_writer.cpp @@ -638,9 +638,6 @@ void print_analysis_sdc_disable_unused_grids(std::fstream& fp, /* Add instances of I/O grids to top_module */ for (const e_side& io_side : io_sides) { for (const vtr::Point& io_coordinate : io_coordinates[io_side]) { - /* We should not meet any I/O grid */ - VTR_ASSERT(true == is_io_type(grids[io_coordinate.x()][io_coordinate.y()].type)); - print_analysis_sdc_disable_unused_grid(fp, io_coordinate, grids, device_annotation, cluster_annotation, place_annotation, module_manager, io_side); diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml new file mode 100644 index 000000000..6d5bf7798 --- /dev/null +++ b/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml @@ -0,0 +1,322 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + + + + 10e-12 5e-12 + + + 10e-12 5e-12 + + + + + + + + + + + + 10e-12 5e-12 5e-12 + + + 10e-12 5e-12 5e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga/test_script/and_k6_frac_tileable_adder_chain_mem16K_aib.openfpga b/openfpga/test_script/and_k6_frac_tileable_adder_chain_mem16K_aib.openfpga new file mode 100644 index 000000000..492c70ea6 --- /dev/null +++ b/openfpga/test_script/and_k6_frac_tileable_adder_chain_mem16K_aib.openfpga @@ -0,0 +1,62 @@ +# Run VPR for the 'and' design +vpr ./test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_aib_40nm.xml ./test_blif/and.blif --route_chan_width 40 --clock_modeling route #--write_rr_graph example_rr_graph.xml + +# Read OpenFPGA architecture definition +read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml + +# Write out the architecture XML as a proof +#write_openfpga_arch -f ./arch_echo.xml + +# Annotate the OpenFPGA architecture to VPR data base +link_openfpga_arch --activity_file ./test_blif/and.act --sort_gsb_chan_node_in_edges #--verbose + +# Write GSB to XML for debugging +write_gsb_to_xml --file /var/tmp/xtang/openfpga_test_src/gsb_xml + +# Check and correct any naming conflicts in the BLIF netlist +check_netlist_naming_conflict --fix --report ./netlist_renaming.xml + +# Apply fix-up to clustering nets based on routing results +pb_pin_fixup --verbose + +# Apply fix-up to Look-Up Table truth tables based on packing results +lut_truth_table_fixup #--verbose + +# Build the module graph +# - Enabled compression on routing architecture modules +# - Enable pin duplication on grid modules +build_fabric --compress_routing --duplicate_grid_pin --verbose + +# Repack the netlist to physical pbs +# This must be done before bitstream generator and testbench generation +# Strongly recommend it is done after all the fix-up have been applied +repack #--verbose + +# Build the bitstream +# - Output the fabric-independent bitstream to a file +build_architecture_bitstream --verbose --file /var/tmp/xtang/openfpga_test_src/fabric_indepenent_bitstream.xml + +# Build fabric-dependent bitstream +build_fabric_bitstream --verbose + +# Write the Verilog netlist for FPGA fabric +# - Enable the use of explicit port mapping in Verilog netlist +write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src/SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose + +# Write the Verilog testbench for FPGA fabric +# - We suggest the use of same output directory as fabric Verilog netlists +# - Must specify the reference benchmark file if you want to output any testbenches +# - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA +# - Enable pre-configured top-level testbench which is a fast verification skipping programming phase +# - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts +write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src/SRC --reference_benchmark_file_path /var/tmp/xtang/and.v --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini /var/tmp/xtang/openfpga_test_src/simulation_deck.ini + +# Write the SDC files for PnR backend +# - Turn on every options here +write_pnr_sdc --file /var/tmp/xtang/openfpga_test_src/SDC + +# Write the SDC to run timing analysis for a mapped FPGA fabric +write_analysis_sdc --file /var/tmp/xtang/openfpga_test_src/SDC_analysis + +# Finish and exit OpenFPGA +exit From ff9cc50527b653127f2fa7db87d803b982f6b57b Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 27 Mar 2020 20:09:50 -0600 Subject: [PATCH 359/645] relax I/O circuit model checking to fit AIB interface. Adapt testbench generation for multiple types of I/O pads --- .../src/check_circuit_library.cpp | 4 +- openfpga/src/fpga_sdc/analysis_sdc_writer.cpp | 137 ++++++++--------- .../fpga_verilog/verilog_testbench_utils.cpp | 139 +++++++++--------- ...0_adder_chain_mem16K_aib_40nm_openfpga.xml | 3 +- 4 files changed, 144 insertions(+), 139 deletions(-) diff --git a/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp b/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp index 93d5ae737..afe6e2173 100644 --- a/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp +++ b/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp @@ -462,7 +462,9 @@ void check_circuit_library(const CircuitLibrary& circuit_lib) { iopad_port_types_required.push_back(CIRCUIT_MODEL_PORT_INPUT); iopad_port_types_required.push_back(CIRCUIT_MODEL_PORT_OUTPUT); iopad_port_types_required.push_back(CIRCUIT_MODEL_PORT_INOUT); - iopad_port_types_required.push_back(CIRCUIT_MODEL_PORT_SRAM); + /* Some I/Os may not have SRAM port, such as AIB interface + * iopad_port_types_required.push_back(CIRCUIT_MODEL_PORT_SRAM); + */ num_err += check_circuit_model_port_required(circuit_lib, CIRCUIT_MODEL_IOPAD, iopad_port_types_required); diff --git a/openfpga/src/fpga_sdc/analysis_sdc_writer.cpp b/openfpga/src/fpga_sdc/analysis_sdc_writer.cpp index de9dfb55d..aec9fc2e7 100644 --- a/openfpga/src/fpga_sdc/analysis_sdc_writer.cpp +++ b/openfpga/src/fpga_sdc/analysis_sdc_writer.cpp @@ -88,83 +88,84 @@ void print_analysis_sdc_io_delays(std::fstream& fp, VTR_ASSERT(1 == operating_clock_ports.size()); /* In this function, we support only 1 type of I/Os */ - VTR_ASSERT(1 == module_manager.module_ports_by_type(top_module, ModuleManager::MODULE_GPIO_PORT).size()); - BasicPort module_io_port = module_manager.module_ports_by_type(top_module, ModuleManager::MODULE_GPIO_PORT)[0]; + std::vector module_io_ports = module_manager.module_ports_by_type(top_module, ModuleManager::MODULE_GPIO_PORT); - /* Keep tracking which I/Os have been used */ - std::vector io_used(module_io_port.get_width(), false); + for (const BasicPort& module_io_port : module_io_ports) { + /* Keep tracking which I/Os have been used */ + std::vector io_used(module_io_port.get_width(), false); - /* Find clock ports in benchmark */ - std::vector benchmark_clock_port_names = find_atom_netlist_clock_port_names(atom_ctx.nlist, netlist_annotation); + /* Find clock ports in benchmark */ + std::vector benchmark_clock_port_names = find_atom_netlist_clock_port_names(atom_ctx.nlist, netlist_annotation); - /* Print comments */ - fp << "##################################################" << std::endl; - fp << "# Create input and output delays for used I/Os " << std::endl; - fp << "##################################################" << std::endl; + /* Print comments */ + fp << "##################################################" << std::endl; + fp << "# Create input and output delays for used I/Os " << std::endl; + fp << "##################################################" << std::endl; - for (const AtomBlockId& atom_blk : atom_ctx.nlist.blocks()) { - /* Bypass non-I/O atom blocks ! */ - if ( (AtomBlockType::INPAD != atom_ctx.nlist.block_type(atom_blk)) - && (AtomBlockType::OUTPAD != atom_ctx.nlist.block_type(atom_blk)) ) { - continue; + for (const AtomBlockId& atom_blk : atom_ctx.nlist.blocks()) { + /* Bypass non-I/O atom blocks ! */ + if ( (AtomBlockType::INPAD != atom_ctx.nlist.block_type(atom_blk)) + && (AtomBlockType::OUTPAD != atom_ctx.nlist.block_type(atom_blk)) ) { + continue; + } + + /* clock net or constant generator should be disabled in timing analysis */ + if (benchmark_clock_port_names.end() != std::find(benchmark_clock_port_names.begin(), benchmark_clock_port_names.end(), atom_ctx.nlist.block_name(atom_blk))) { + continue; + } + + /* Find the index of the mapped GPIO in top-level FPGA fabric */ + size_t io_index = io_location_map.io_index(place_ctx.block_locs[atom_ctx.lookup.atom_clb(atom_blk)].loc.x, + place_ctx.block_locs[atom_ctx.lookup.atom_clb(atom_blk)].loc.y, + place_ctx.block_locs[atom_ctx.lookup.atom_clb(atom_blk)].loc.z); + + /* Ensure that IO index is in range */ + BasicPort module_mapped_io_port = module_io_port; + /* Set the port pin index */ + VTR_ASSERT(io_index < module_mapped_io_port.get_width()); + module_mapped_io_port.set_width(io_index, io_index); + + /* For input I/O, we set an input delay constraint correlated to the operating clock + * For output I/O, we set an output delay constraint correlated to the operating clock + */ + if (AtomBlockType::INPAD == atom_ctx.nlist.block_type(atom_blk)) { + print_sdc_set_port_input_delay(fp, module_mapped_io_port, + operating_clock_ports[0], critical_path_delay); + } else { + VTR_ASSERT(AtomBlockType::OUTPAD == atom_ctx.nlist.block_type(atom_blk)); + print_sdc_set_port_output_delay(fp, module_mapped_io_port, + operating_clock_ports[0], critical_path_delay); + } + + /* Mark this I/O has been used/wired */ + io_used[io_index] = true; } - /* clock net or constant generator should be disabled in timing analysis */ - if (benchmark_clock_port_names.end() != std::find(benchmark_clock_port_names.begin(), benchmark_clock_port_names.end(), atom_ctx.nlist.block_name(atom_blk))) { - continue; + /* Add an empty line as a splitter */ + fp << std::endl; + + /* Print comments */ + fp << "##################################################" << std::endl; + fp << "# Disable timing for unused I/Os " << std::endl; + fp << "##################################################" << std::endl; + + /* Wire the unused iopads to a constant */ + for (size_t io_index = 0; io_index < io_used.size(); ++io_index) { + /* Bypass used iopads */ + if (true == io_used[io_index]) { + continue; + } + + /* Wire to a contant */ + BasicPort module_unused_io_port = module_io_port; + /* Set the port pin index */ + module_unused_io_port.set_width(io_index, io_index); + print_sdc_disable_port_timing(fp, module_unused_io_port); } - /* Find the index of the mapped GPIO in top-level FPGA fabric */ - size_t io_index = io_location_map.io_index(place_ctx.block_locs[atom_ctx.lookup.atom_clb(atom_blk)].loc.x, - place_ctx.block_locs[atom_ctx.lookup.atom_clb(atom_blk)].loc.y, - place_ctx.block_locs[atom_ctx.lookup.atom_clb(atom_blk)].loc.z); - - /* Ensure that IO index is in range */ - BasicPort module_mapped_io_port = module_io_port; - /* Set the port pin index */ - VTR_ASSERT(io_index < module_mapped_io_port.get_width()); - module_mapped_io_port.set_width(io_index, io_index); - - /* For input I/O, we set an input delay constraint correlated to the operating clock - * For output I/O, we set an output delay constraint correlated to the operating clock - */ - if (AtomBlockType::INPAD == atom_ctx.nlist.block_type(atom_blk)) { - print_sdc_set_port_input_delay(fp, module_mapped_io_port, - operating_clock_ports[0], critical_path_delay); - } else { - VTR_ASSERT(AtomBlockType::OUTPAD == atom_ctx.nlist.block_type(atom_blk)); - print_sdc_set_port_output_delay(fp, module_mapped_io_port, - operating_clock_ports[0], critical_path_delay); - } - - /* Mark this I/O has been used/wired */ - io_used[io_index] = true; + /* Add an empty line as a splitter */ + fp << std::endl; } - - /* Add an empty line as a splitter */ - fp << std::endl; - - /* Print comments */ - fp << "##################################################" << std::endl; - fp << "# Disable timing for unused I/Os " << std::endl; - fp << "##################################################" << std::endl; - - /* Wire the unused iopads to a constant */ - for (size_t io_index = 0; io_index < io_used.size(); ++io_index) { - /* Bypass used iopads */ - if (true == io_used[io_index]) { - continue; - } - - /* Wire to a contant */ - BasicPort module_unused_io_port = module_io_port; - /* Set the port pin index */ - module_unused_io_port.set_width(io_index, io_index); - print_sdc_disable_port_timing(fp, module_unused_io_port); - } - - /* Add an empty line as a splitter */ - fp << std::endl; } /******************************************************************** diff --git a/openfpga/src/fpga_verilog/verilog_testbench_utils.cpp b/openfpga/src/fpga_verilog/verilog_testbench_utils.cpp index e952d13c4..dae42f042 100644 --- a/openfpga/src/fpga_verilog/verilog_testbench_utils.cpp +++ b/openfpga/src/fpga_verilog/verilog_testbench_utils.cpp @@ -132,86 +132,87 @@ void print_verilog_testbench_connect_fpga_ios(std::fstream& fp, valid_file_stream(fp); /* In this function, we support only 1 type of I/Os */ - VTR_ASSERT(1 == module_manager.module_ports_by_type(top_module, ModuleManager::MODULE_GPIO_PORT).size()); - BasicPort module_io_port = module_manager.module_ports_by_type(top_module, ModuleManager::MODULE_GPIO_PORT)[0]; + std::vector module_io_ports = module_manager.module_ports_by_type(top_module, ModuleManager::MODULE_GPIO_PORT); /* Keep tracking which I/Os have been used */ - std::vector io_used(module_io_port.get_width(), false); + for (const BasicPort& module_io_port : module_io_ports) { + std::vector io_used(module_io_port.get_width(), false); - /* See if this I/O should be wired to a benchmark input/output */ - /* Add signals from blif benchmark and short-wire them to FPGA I/O PADs - * This brings convenience to checking functionality - */ - print_verilog_comment(fp, std::string("----- Link BLIF Benchmark I/Os to FPGA I/Os -----")); - - for (const AtomBlockId& atom_blk : atom_ctx.nlist.blocks()) { - /* Bypass non-I/O atom blocks ! */ - if ( (AtomBlockType::INPAD != atom_ctx.nlist.block_type(atom_blk)) - && (AtomBlockType::OUTPAD != atom_ctx.nlist.block_type(atom_blk)) ) { - continue; - } - - /* Find the index of the mapped GPIO in top-level FPGA fabric */ - size_t io_index = io_location_map.io_index(place_ctx.block_locs[atom_ctx.lookup.atom_clb(atom_blk)].loc.x, - place_ctx.block_locs[atom_ctx.lookup.atom_clb(atom_blk)].loc.y, - place_ctx.block_locs[atom_ctx.lookup.atom_clb(atom_blk)].loc.z); - - /* Ensure that IO index is in range */ - BasicPort module_mapped_io_port = module_io_port; - /* Set the port pin index */ - VTR_ASSERT(io_index < module_mapped_io_port.get_width()); - module_mapped_io_port.set_width(io_index, io_index); - - /* The block may be renamed as it contains special characters which violate Verilog syntax */ - std::string block_name = atom_ctx.nlist.block_name(atom_blk); - if (true == netlist_annotation.is_block_renamed(atom_blk)) { - block_name = netlist_annotation.block_name(atom_blk); - } - - /* Create the port for benchmark I/O, due to BLIF benchmark, each I/O always has a size of 1 - * In addition, the input and output ports may have different postfix in naming - * due to verification context! Here, we give full customization on naming + /* See if this I/O should be wired to a benchmark input/output */ + /* Add signals from blif benchmark and short-wire them to FPGA I/O PADs + * This brings convenience to checking functionality */ - BasicPort benchmark_io_port; - if (AtomBlockType::INPAD == atom_ctx.nlist.block_type(atom_blk)) { - benchmark_io_port.set_name(std::string(block_name + io_input_port_name_postfix)); - benchmark_io_port.set_width(1); - print_verilog_comment(fp, std::string("----- Blif Benchmark input " + block_name + " is mapped to FPGA IOPAD " + module_mapped_io_port.get_name() + "[" + std::to_string(io_index) + "] -----")); - print_verilog_wire_connection(fp, module_mapped_io_port, benchmark_io_port, false); - } else { - VTR_ASSERT(AtomBlockType::OUTPAD == atom_ctx.nlist.block_type(atom_blk)); - benchmark_io_port.set_name(std::string(block_name + io_output_port_name_postfix)); - benchmark_io_port.set_width(1); - print_verilog_comment(fp, std::string("----- Blif Benchmark output " + block_name + " is mapped to FPGA IOPAD " + module_mapped_io_port.get_name() + "[" + std::to_string(io_index) + "] -----")); - print_verilog_wire_connection(fp, benchmark_io_port, module_mapped_io_port, false); + print_verilog_comment(fp, std::string("----- Link BLIF Benchmark I/Os to FPGA I/Os -----")); + + for (const AtomBlockId& atom_blk : atom_ctx.nlist.blocks()) { + /* Bypass non-I/O atom blocks ! */ + if ( (AtomBlockType::INPAD != atom_ctx.nlist.block_type(atom_blk)) + && (AtomBlockType::OUTPAD != atom_ctx.nlist.block_type(atom_blk)) ) { + continue; + } + + /* Find the index of the mapped GPIO in top-level FPGA fabric */ + size_t io_index = io_location_map.io_index(place_ctx.block_locs[atom_ctx.lookup.atom_clb(atom_blk)].loc.x, + place_ctx.block_locs[atom_ctx.lookup.atom_clb(atom_blk)].loc.y, + place_ctx.block_locs[atom_ctx.lookup.atom_clb(atom_blk)].loc.z); + + /* Ensure that IO index is in range */ + BasicPort module_mapped_io_port = module_io_port; + /* Set the port pin index */ + VTR_ASSERT(io_index < module_mapped_io_port.get_width()); + module_mapped_io_port.set_width(io_index, io_index); + + /* The block may be renamed as it contains special characters which violate Verilog syntax */ + std::string block_name = atom_ctx.nlist.block_name(atom_blk); + if (true == netlist_annotation.is_block_renamed(atom_blk)) { + block_name = netlist_annotation.block_name(atom_blk); + } + + /* Create the port for benchmark I/O, due to BLIF benchmark, each I/O always has a size of 1 + * In addition, the input and output ports may have different postfix in naming + * due to verification context! Here, we give full customization on naming + */ + BasicPort benchmark_io_port; + if (AtomBlockType::INPAD == atom_ctx.nlist.block_type(atom_blk)) { + benchmark_io_port.set_name(std::string(block_name + io_input_port_name_postfix)); + benchmark_io_port.set_width(1); + print_verilog_comment(fp, std::string("----- Blif Benchmark input " + block_name + " is mapped to FPGA IOPAD " + module_mapped_io_port.get_name() + "[" + std::to_string(io_index) + "] -----")); + print_verilog_wire_connection(fp, module_mapped_io_port, benchmark_io_port, false); + } else { + VTR_ASSERT(AtomBlockType::OUTPAD == atom_ctx.nlist.block_type(atom_blk)); + benchmark_io_port.set_name(std::string(block_name + io_output_port_name_postfix)); + benchmark_io_port.set_width(1); + print_verilog_comment(fp, std::string("----- Blif Benchmark output " + block_name + " is mapped to FPGA IOPAD " + module_mapped_io_port.get_name() + "[" + std::to_string(io_index) + "] -----")); + print_verilog_wire_connection(fp, benchmark_io_port, module_mapped_io_port, false); + } + + /* Mark this I/O has been used/wired */ + io_used[io_index] = true; } - /* Mark this I/O has been used/wired */ - io_used[io_index] = true; - } + /* Add an empty line as a splitter */ + fp << std::endl; - /* Add an empty line as a splitter */ - fp << std::endl; + /* Wire the unused iopads to a constant */ + print_verilog_comment(fp, std::string("----- Wire unused FPGA I/Os to constants -----")); + for (size_t io_index = 0; io_index < io_used.size(); ++io_index) { + /* Bypass used iopads */ + if (true == io_used[io_index]) { + continue; + } - /* Wire the unused iopads to a constant */ - print_verilog_comment(fp, std::string("----- Wire unused FPGA I/Os to constants -----")); - for (size_t io_index = 0; io_index < io_used.size(); ++io_index) { - /* Bypass used iopads */ - if (true == io_used[io_index]) { - continue; + /* Wire to a contant */ + BasicPort module_unused_io_port = module_io_port; + /* Set the port pin index */ + module_unused_io_port.set_width(io_index, io_index); + + std::vector default_values(module_unused_io_port.get_width(), unused_io_value); + print_verilog_wire_constant_values(fp, module_unused_io_port, default_values); } - /* Wire to a contant */ - BasicPort module_unused_io_port = module_io_port; - /* Set the port pin index */ - module_unused_io_port.set_width(io_index, io_index); - - std::vector default_values(module_unused_io_port.get_width(), unused_io_value); - print_verilog_wire_constant_values(fp, module_unused_io_port, default_values); + /* Add an empty line as a splitter */ + fp << std::endl; } - - /* Add an empty line as a splitter */ - fp << std::endl; } /******************************************************************** diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml index 6d5bf7798..ea56fe036 100644 --- a/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml @@ -209,7 +209,7 @@ - + @@ -218,6 +218,7 @@ + From 07e1979498db390bb709c364336ce58fc2f3cf87 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 28 Mar 2020 15:41:26 -0600 Subject: [PATCH 360/645] add architecture examples on wide memory blocks (width=2). tileable routing is working --- ..._tileable_adder_chain_wide_mem16K.openfpga | 62 ++ ..._tileable_adder_chain_wide_mem16K_40nm.xml | 734 ++++++++++++++++++ .../rr_graph_builder_utils.cpp | 6 +- 3 files changed, 799 insertions(+), 3 deletions(-) create mode 100644 openfpga/test_script/and_k6_frac_tileable_adder_chain_wide_mem16K.openfpga create mode 100644 openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_wide_mem16K_40nm.xml diff --git a/openfpga/test_script/and_k6_frac_tileable_adder_chain_wide_mem16K.openfpga b/openfpga/test_script/and_k6_frac_tileable_adder_chain_wide_mem16K.openfpga new file mode 100644 index 000000000..466c3bcd0 --- /dev/null +++ b/openfpga/test_script/and_k6_frac_tileable_adder_chain_wide_mem16K.openfpga @@ -0,0 +1,62 @@ +# Run VPR for the 'and' design +vpr ./test_vpr_arch/k6_frac_N10_tileable_adder_chain_wide_mem16K_40nm.xml ./test_blif/and.blif --route_chan_width 40 --clock_modeling route #--write_rr_graph example_rr_graph.xml + +# Read OpenFPGA architecture definition +read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml + +# Write out the architecture XML as a proof +#write_openfpga_arch -f ./arch_echo.xml + +# Annotate the OpenFPGA architecture to VPR data base +link_openfpga_arch --activity_file ./test_blif/and.act --sort_gsb_chan_node_in_edges #--verbose + +# Write GSB to XML for debugging +write_gsb_to_xml --file /var/tmp/xtang/openfpga_test_src/gsb_xml + +# Check and correct any naming conflicts in the BLIF netlist +check_netlist_naming_conflict --fix --report ./netlist_renaming.xml + +# Apply fix-up to clustering nets based on routing results +pb_pin_fixup --verbose + +# Apply fix-up to Look-Up Table truth tables based on packing results +lut_truth_table_fixup #--verbose + +# Build the module graph +# - Enabled compression on routing architecture modules +# - Enable pin duplication on grid modules +build_fabric --compress_routing --duplicate_grid_pin --verbose + +# Repack the netlist to physical pbs +# This must be done before bitstream generator and testbench generation +# Strongly recommend it is done after all the fix-up have been applied +repack #--verbose + +# Build the bitstream +# - Output the fabric-independent bitstream to a file +build_architecture_bitstream --verbose --file /var/tmp/xtang/openfpga_test_src/fabric_indepenent_bitstream.xml + +# Build fabric-dependent bitstream +build_fabric_bitstream --verbose + +# Write the Verilog netlist for FPGA fabric +# - Enable the use of explicit port mapping in Verilog netlist +write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src/SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose + +# Write the Verilog testbench for FPGA fabric +# - We suggest the use of same output directory as fabric Verilog netlists +# - Must specify the reference benchmark file if you want to output any testbenches +# - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA +# - Enable pre-configured top-level testbench which is a fast verification skipping programming phase +# - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts +write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src/SRC --reference_benchmark_file_path /var/tmp/xtang/and.v --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini /var/tmp/xtang/openfpga_test_src/simulation_deck.ini + +# Write the SDC files for PnR backend +# - Turn on every options here +write_pnr_sdc --file /var/tmp/xtang/openfpga_test_src/SDC + +# Write the SDC to run timing analysis for a mapped FPGA fabric +write_analysis_sdc --file /var/tmp/xtang/openfpga_test_src/SDC_analysis + +# Finish and exit OpenFPGA +exit diff --git a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_wide_mem16K_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_wide_mem16K_40nm.xml new file mode 100644 index 000000000..f07f16c80 --- /dev/null +++ b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_wide_mem16K_40nm.xml @@ -0,0 +1,734 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + + + + + + + + + + + + + + + + + + + clb.clk + clb.cin + clb.O[9:0] clb.I[19:0] + clb.cout clb.O[19:10] clb.I[39:20] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 235e-12 + 235e-12 + 235e-12 + 235e-12 + 235e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 195e-12 + 195e-12 + 195e-12 + 195e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vpr/src/tileable_rr_graph/rr_graph_builder_utils.cpp b/vpr/src/tileable_rr_graph/rr_graph_builder_utils.cpp index acead971f..a599c8194 100644 --- a/vpr/src/tileable_rr_graph/rr_graph_builder_utils.cpp +++ b/vpr/src/tileable_rr_graph/rr_graph_builder_utils.cpp @@ -195,7 +195,7 @@ bool is_chanx_exist(const DeviceGrid& grids, ***********************************************************************/ bool is_chany_exist(const DeviceGrid& grids, const vtr::Point& chany_coord) { - return (grids[chany_coord.x()][chany_coord.y()].width_offset == grids[chany_coord.y()][chany_coord.y()].type->width - 1); + return (grids[chany_coord.x()][chany_coord.y()].width_offset == grids[chany_coord.x()][chany_coord.y()].type->width - 1); } /************************************************************************ @@ -299,7 +299,7 @@ bool is_chany_top_to_multi_width_grid(const DeviceGrid& grids, } if (false == through_channel) { - /* We check the bottom neighbor of chany, if it does not exist, the chany is left to a multi-height grid */ + /* We check the bottom neighbor of chany, if it does not exist, the chany is top to a multi-height grid */ vtr::Point bottom_chany_coord(chany_coord.x(), chany_coord.y() - 1); if (false == is_chany_exist(grids, bottom_chany_coord)) { return true; @@ -317,7 +317,7 @@ bool is_chany_top_to_multi_width_grid(const DeviceGrid& grids, * | | * | | * | Grid | - * | [x+1][y] | + * | [x][y+1] | * | | * | | * +-----------------+ From 5d12f499f0adbf968f5d48662fe2337c7365db57 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 29 Mar 2020 16:26:23 -0600 Subject: [PATCH 361/645] hotfix on undriven pins on the connection blocks --- vpr7_x2p/vpr/SRC/fpga_x2p/base/fpga_x2p_unique_routing.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vpr7_x2p/vpr/SRC/fpga_x2p/base/fpga_x2p_unique_routing.c b/vpr7_x2p/vpr/SRC/fpga_x2p/base/fpga_x2p_unique_routing.c index c2f520d4d..d3f3539e5 100644 --- a/vpr7_x2p/vpr/SRC/fpga_x2p/base/fpga_x2p_unique_routing.c +++ b/vpr7_x2p/vpr/SRC/fpga_x2p/base/fpga_x2p_unique_routing.c @@ -1149,6 +1149,11 @@ RRGSB build_rr_gsb(DeviceCoordinator& device_range, LL_num_rr_nodes, LL_rr_node, LL_rr_node_indices); /* Fill the ipin nodes of RRGSB */ for (int inode = 0; inode < num_temp_ipin_rr_nodes; ++inode) { + /* Skip Fc = 0 pins, they should NOT appear in the GSB connection */ + if (0. == grid[temp_ipin_rr_node[inode]->xlow][temp_ipin_rr_node[inode]->ylow].type->Fc[temp_ipin_rr_node[inode]->ptc_num]) { + continue; + } + rr_gsb.add_ipin_node(temp_ipin_rr_node[inode], side_manager.get_side(), ipin_rr_node_grid_side); } /* Free */ From 63306ce3a09e057733e2332fc743dfe1decebdfc Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 1 Apr 2020 11:05:30 -0600 Subject: [PATCH 362/645] add comments to explain the memory organization in the top-level module --- .../src/fabric/build_top_module_memory.cpp | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/openfpga/src/fabric/build_top_module_memory.cpp b/openfpga/src/fabric/build_top_module_memory.cpp index 37f67f0be..21f7a5e6c 100644 --- a/openfpga/src/fabric/build_top_module_memory.cpp +++ b/openfpga/src/fabric/build_top_module_memory.cpp @@ -177,7 +177,29 @@ void organize_top_module_tile_memory_modules(ModuleManager& module_manager, * the sequence of memory_modules and memory_instances will follow * a chain of tiles considering their physical location * - * Inter tile connection: + * Inter-tile connection: + * + * Inter-tile connection always start from the I/O peripherals + * and the core tiles (CLBs and heterogeneous blocks). + * The sequence of configuration memory will be organized as follows: + * - I/O peripherals + * - BOTTOM side (From left to right) + * - RIGHT side (From bottom to top) + * - TOP side (From left to right) + * - LEFT side (From top to bottom) + * - Core tiles + * - Tiles at the bottom row, i.e., Tile[0..i] (From left to right) + * - One row upper, i.e. Tile[i+1 .. j] (From right to left) + * - Repeat until we finish all the rows + * + * Note: the tail may not always be on the top-right corner as shown in the figure. + * It may exit at the top-left corner. + * This really depends on the number of rows your have in the core tile array. + * + * Note: the organization of inter-tile aims to reduce the wire length + * to connect between tiles. Therefore, it is organized as a snake + * where we can avoid long wires between rows and columns + * * +--------------------------------------------------------+ * | +------+------+-----+------+ | * | | I/O | I/O | ... | I/O | | @@ -210,7 +232,20 @@ void organize_top_module_tile_memory_modules(ModuleManager& module_manager, * +------+------+-----+------+ | * head >-----------------------------------------------+ * - * Inner tile connection + * Inner tile connection: + * + * Inside each tile, the configuration memory will be organized + * in the following sequence: + * - Switch Block (SB) + * - X-directional Connection Block (CBX) + * - Y-directional Connection Block (CBY) + * - Configurable Logic Block (CLB), which could also be heterogeneous blocks + * + * Note: + * Due to multi-column and multi-width hetergeoenous blocks, + * each tile may not have one or more of SB, CBX, CBY, CLB + * In such case, the sequence will be respected. + * The missing block will just be skipped when organizing the configuration memories. * * Tile * +---------------+----------+ From fd8248d9dd02fd05c2fac0b47e0fffcae5c0da23 Mon Sep 17 00:00:00 2001 From: Xifan Tang Date: Wed, 1 Apr 2020 12:35:52 -0600 Subject: [PATCH 363/645] update documentation: the addon syntax on VPR and configuration protocols --- docs/source/arch_lang/addon_vpr_syntax.rst | 54 +++++++++++++-- docs/source/arch_lang/annotate_vpr_arch.rst | 65 ++++++++++++++++-- .../arch_lang/circuit_model_examples.rst | 4 +- docs/source/arch_lang/figures/ccff_fpga.png | Bin 0 -> 78496 bytes 4 files changed, 109 insertions(+), 14 deletions(-) create mode 100644 docs/source/arch_lang/figures/ccff_fpga.png diff --git a/docs/source/arch_lang/addon_vpr_syntax.rst b/docs/source/arch_lang/addon_vpr_syntax.rst index 1e2da988f..c6ac221a2 100644 --- a/docs/source/arch_lang/addon_vpr_syntax.rst +++ b/docs/source/arch_lang/addon_vpr_syntax.rst @@ -5,11 +5,18 @@ Additional Syntax to Original VPR XML .. warning:: Note this is only applicable to VPR8! +Models, Complex blocks and Physical Tiles +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Each ```` should contain a ```` that describe the physical implementation of the ````. Note that this is fully compatible to the VPR architecture XML syntax. ```` should include the models that describe the primitive ```` in physical mode. +.. note:: Currently, OpenFPGA only supports 1 ```` to be defined under each ```` + +Layout +~~~~~~ + ```` may include additioinal syntax to enable tileable routing resource graph generation .. option:: tileable="" @@ -35,15 +42,27 @@ Each ```` should contain a ```` that describe the physical implem Impact on routing architecture when through channel in multi-width and multi-height programmable blocks: (a) disabled; (b) enabled. - .. warning:: Do NOT enable if you are not using the tileable routing resource graph generator! + .. warning:: Do NOT enable ``through_channel`` if you are not using the tileable routing resource graph generator! + + .. warning:: Currently ``through_channel`` supports only a fixed routing channel width! - .. warning:: Current through channel supports only a fixed routing channel! + +A quick example to show tileable routing is enabled and through channels are disabled: + +.. code-block:: xml + + + + +Switch Block +~~~~~~~~~~~~ ```` may include addition syntax to enable different connectivity for pass tracks .. option:: sub_type="" Connecting type for pass tracks in each switch block + The supported connecting patterns are ``subset``, ``universal`` and ``wilton``, being the same as VPR capability If not specified, the pass tracks will the same connecting patterns as start/end tracks, which are defined in ``type`` .. option:: sub_Fs="" @@ -51,10 +70,31 @@ Each ```` should contain a ```` that describe the physical implem Connectivity parameter for pass tracks in each switch block. Must be a multiple of 3. If not specified, the pass tracks will the same connectivity as start/end tracks, which are defined in ``fs`` +A quick example which defines a switch block + - Starting/ending routing tracks are connected in the ``wilton`` pattern + - Each starting/ending routing track can drive 3 other starting/ending routing tracks + - Passing routing tracks are connected in the ``subset`` pattern + - Each passing routing track can drive 6 other starting/ending routing tracks + +.. code-block:: xml + + + + + +Routing Segments +~~~~~~~~~~~~~~~~ + +OpenFPGA suggests users to give explicit names for each routing segement in ```` +This is used to link ``circuit_model`` to routing segments. + +A quick example which defines a length-4 uni-directional routing segment called ``L4`` : + +.. code-block:: xml + + + + + .. note:: Currently, OpenFPGA only supports uni-directional routing architectures -.. note:: Currently, OpenFPGA only supports 1 ```` to be defined under each ```` - -.. note:: OpenFPGA require explicit names to be defined for each routing segement in ```` - - diff --git a/docs/source/arch_lang/annotate_vpr_arch.rst b/docs/source/arch_lang/annotate_vpr_arch.rst index ce570084d..69aed061e 100644 --- a/docs/source/arch_lang/annotate_vpr_arch.rst +++ b/docs/source/arch_lang/annotate_vpr_arch.rst @@ -7,27 +7,82 @@ Each defined circuit model should be linked to an FPGA module defined in the ori Configuration Protocol ~~~~~~~~~~~~~~~~~~~~~~ +Configuration protocol is the circuitry designed to program an FPGA. +As an interface, configuration protocol could be really different in FPGAs, depending on the application context. + +Template +```````` + .. code-block:: xml -- ``type="scan_chain|memory_bank|standalone"`` Specify the type of configuration circuits. +.. option:: type="scan_chain|memory_bank|standalone" -:numref:`fig_sram` illustrates an example where a memory organization using memory decoders and 6-transistor SRAMs. + Specify the type of configuration circuits. + + OpenFPGA supports different types of configuration protocols to program FPGA fabrics: + - ``scan_chain``: configurable memories are connected in a chain. Bitstream is loaded serially to program a FPGA + - ``memory_bank``: configurable memories are organized in an array, where each element can be accessed by an unique address to the BL/WL decoders + - ``standalone``: configurable memories are directly accessed through ports of FPGA fabrics. In other words, there are no protocol to control the memories. This allows full customization on the configuration protocol for hardware engineers. + + .. note:: Avoid to use ``standalone`` when designing an FPGA chip. It will causes a huge number of I/Os required, far beyond any package size. It is well applicable to eFPGAs, where designers do need customized protocols between FPGA and processors. + +.. warning:: Currently FPGA-SPICE only supports standalone memory organization. + +.. warning:: Currently RRAM-based FPGA only supports memory-bank organization for Verilog Generator. + +.. option:: circuit_model_name="" + + Specify the name of circuit model to be used as configurable memory. + - ``scan_chain`` requires a circuit model type of ``ccff`` + - ``memory_bank`` requires a circuit model type of ``sram`` + - ``standalone`` requires a circuit model type of ``sram`` + +Configuration Chain Example +``````````````````````````` +The following XML code describes a scan-chain circuitry to configure the core logic of FPGA, as illustrated in :numref:`fig_ccff_fpga`. +It will use the circuit model defined in :ref:`circuit_model_examples`. + +.. code-block:: xml + + + + + +.. _fig_ccff_fpga: + +.. figure:: figures/ccff_fpga.png + :scale: 60% + :alt: map to buried treasure + + Example of a configuration chain to program core logic of a FPGA + +Memory bank Example +``````````````````` +The following XML code describes a memory-bank circuitry to configure the core logic of FPGA, as illustrated in :numref:`fig_sram`. +It will use the circuit model defined in :ref:`circuit_model_examples`. + +.. code-block:: xml + + + + .. _fig_sram: .. figure:: figures/sram.png - :scale: 100% + :scale: 60% :alt: map to buried treasure Example of a memory organization using memory decoders -.. note:: Currently FPGA-SPICE only supports standalone memory organization. +Standalone SRAM Example +``````````````````````` -.. note:: Currently RRAM-based FPGA only supports memory-bank organization for Verilog Generator. +.. warning:: TO BE CONSTRUCTED Switch Blocks ~~~~~~~~~~~~~ diff --git a/docs/source/arch_lang/circuit_model_examples.rst b/docs/source/arch_lang/circuit_model_examples.rst index a4472c01d..3a749e49d 100644 --- a/docs/source/arch_lang/circuit_model_examples.rst +++ b/docs/source/arch_lang/circuit_model_examples.rst @@ -734,7 +734,7 @@ The code describing this wire is: This example shows - A routing track wire has 1 input and output - - The routing wire will be modelled as a 1-level π-type RC wire model with a total resistance of 103.84Ohm and a total capacitance of 13.89fF + - The routing wire will be modelled as a 1-level π-type RC wire model with a total resistance of :math:`103.84\Omega` and a total capacitance of :math:`13.89fF` I/O pads ~~~~~~~~ @@ -790,4 +790,4 @@ This example shows - A general purpose I/O cell defined in Verilog netlist ``io.sp`` and SPICE netlist ``io.sp`` - The I/O cell has an ``inout`` port as the bi-directional port - The directionality of I/O can be controlled by a configuration-chain flip-flop defined in circuit model ``ccff`` - - If unused, the I/O cell have be configured to '1' + - If unused, the I/O cell will be configured to ``1`` diff --git a/docs/source/arch_lang/figures/ccff_fpga.png b/docs/source/arch_lang/figures/ccff_fpga.png new file mode 100644 index 0000000000000000000000000000000000000000..fa4a6e9abe2bab351c80008fffd73eb3db5e59a7 GIT binary patch literal 78496 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 literal 0 HcmV?d00001 From 6ce0fe4ef29eccaa7d911399ddfaf492befb95ad Mon Sep 17 00:00:00 2001 From: Xifan Tang Date: Wed, 1 Apr 2020 12:57:28 -0600 Subject: [PATCH 364/645] doc update for FPGA-bitstream to better motivate the different types of bitstream --- .../fabric_dependent_bitstream.rst | 7 ++++++ ...organization.rst => generic_bitstream.rst} | 22 ++++++++++++++----- docs/source/fpga_bitstream/index.rst | 9 +++++--- 3 files changed, 29 insertions(+), 9 deletions(-) create mode 100644 docs/source/fpga_bitstream/fabric_dependent_bitstream.rst rename docs/source/fpga_bitstream/{file_organization.rst => generic_bitstream.rst} (53%) diff --git a/docs/source/fpga_bitstream/fabric_dependent_bitstream.rst b/docs/source/fpga_bitstream/fabric_dependent_bitstream.rst new file mode 100644 index 000000000..280768c1a --- /dev/null +++ b/docs/source/fpga_bitstream/fabric_dependent_bitstream.rst @@ -0,0 +1,7 @@ +Fabric-dependent Bitstream +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Fabric-dependent bitstream is design to be loadable to the configuration protocols of FPGAs. +The bitstream just sets an order to the configuration bits in the database, without duplicating the database. +OpenFPGA framework provides a fabric-dependent bitstream generator which is aligned to our Verilog netlists. +The fabric-dependent bitstream can be found in autogenerated Verilog testbenches. diff --git a/docs/source/fpga_bitstream/file_organization.rst b/docs/source/fpga_bitstream/generic_bitstream.rst similarity index 53% rename from docs/source/fpga_bitstream/file_organization.rst rename to docs/source/fpga_bitstream/generic_bitstream.rst index 91975fcc7..78fbd3de4 100644 --- a/docs/source/fpga_bitstream/file_organization.rst +++ b/docs/source/fpga_bitstream/generic_bitstream.rst @@ -1,9 +1,21 @@ -Bitstream Output File Format -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Generic Bitstream +~~~~~~~~~~~~~~~~~ -FPGA-Bitstream can generate two types of bitstreams: +Usage +````` -* Generic bitstreams, where configuration bits are organized out-of-order in a database. We output the generic bitstream to a XML format, which is easy to debug. As shown in the following XML code, configuration bits are organized block by block, where each block could be a LUT, a routing multiplexer `etc`. Each ``bitstream_block`` includes two sets of information: +Generic bitstream is a fabric-independent bitstream where configuration bits are organized out-of-order in a database. +This can be regarded as a raw bitstream used for + - ``debugging``: Hardware engineers can validate if their configuration memories across the FPGA fabric are assigned to expected values + - ``an exchangeable file format for bitstream assembler``: Software engineers can use the raw bitstream to build a bitstream assembler which organize the bitstream in the loadable formate to FPGA chips. + - ``creation of artificial bitstream``: Test engineers can craft artificial bitstreams to test each element of the FPGA fabric, which is typically not synthesizable by VPR. + +.. note:: The fabric-independent bitstream cannot be directly loaded to FPGA fabrics + +File Format +``````````` + +OpenFPGA can output the generic bitstream to an XML format, which is easy to debug. As shown in the following XML code, configuration bits are organized block by block, where each block could be a LUT, a routing multiplexer `etc`. Each ``bitstream_block`` includes two sets of information: - ``hierarchy`` represents the location of this block in FPGA fabric. @@ -37,5 +49,3 @@ FPGA-Bitstream can generate two types of bitstreams: - -* Fabric-dependent bitstreams, where configuration bits are organized to be loadable to the configuration protocols of FPGAs. The bitstream just sets an order to the configuration bits in the database, without duplicating the database. OpenFPGA framework provides a fabric-dependent bitstream generator which is aligned to our Verilog netlists. The fabric-dependent bitstream can be found in autogenerated Verilog testbenches. diff --git a/docs/source/fpga_bitstream/index.rst b/docs/source/fpga_bitstream/index.rst index 6e1123899..e30f89354 100644 --- a/docs/source/fpga_bitstream/index.rst +++ b/docs/source/fpga_bitstream/index.rst @@ -1,11 +1,14 @@ FPGA-Bitstream -------------- + +FPGA-Bitstream can generate two types of bitstreams: + .. _fpga_bitstream: FPGA-Bitstream .. toctree:: :maxdepth: 2 - file_organization - - + generic_bitstream + + fabric_dependent_bitstream From 32c74ad8112d9cd92b16cb332c9a27f2bd3e11bc Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 1 Apr 2020 15:46:38 -0600 Subject: [PATCH 365/645] added FPGA architecture with I/Os on the left and right sides --- ...ble_adder_chain_mem16K_reduced_io.openfpga | 62 ++ ...ble_adder_chain_mem16K_reduced_io_40nm.xml | 737 ++++++++++++++++++ 2 files changed, 799 insertions(+) create mode 100644 openfpga/test_script/and_k6_frac_tileable_adder_chain_mem16K_reduced_io.openfpga create mode 100644 openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_reduced_io_40nm.xml diff --git a/openfpga/test_script/and_k6_frac_tileable_adder_chain_mem16K_reduced_io.openfpga b/openfpga/test_script/and_k6_frac_tileable_adder_chain_mem16K_reduced_io.openfpga new file mode 100644 index 000000000..22a658a73 --- /dev/null +++ b/openfpga/test_script/and_k6_frac_tileable_adder_chain_mem16K_reduced_io.openfpga @@ -0,0 +1,62 @@ +# Run VPR for the 'and' design +vpr ./test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_reduced_io_40nm.xml ./test_blif/and.blif --route_chan_width 40 --clock_modeling route #--write_rr_graph example_rr_graph.xml + +# Read OpenFPGA architecture definition +read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml + +# Write out the architecture XML as a proof +#write_openfpga_arch -f ./arch_echo.xml + +# Annotate the OpenFPGA architecture to VPR data base +link_openfpga_arch --activity_file ./test_blif/and.act --sort_gsb_chan_node_in_edges #--verbose + +# Write GSB to XML for debugging +write_gsb_to_xml --file /var/tmp/xtang/openfpga_test_src/gsb_xml + +# Check and correct any naming conflicts in the BLIF netlist +check_netlist_naming_conflict --fix --report ./netlist_renaming.xml + +# Apply fix-up to clustering nets based on routing results +pb_pin_fixup --verbose + +# Apply fix-up to Look-Up Table truth tables based on packing results +lut_truth_table_fixup #--verbose + +# Build the module graph +# - Enabled compression on routing architecture modules +# - Enable pin duplication on grid modules +build_fabric --compress_routing --duplicate_grid_pin --verbose + +# Repack the netlist to physical pbs +# This must be done before bitstream generator and testbench generation +# Strongly recommend it is done after all the fix-up have been applied +repack #--verbose + +# Build the bitstream +# - Output the fabric-independent bitstream to a file +build_architecture_bitstream --verbose --file /var/tmp/xtang/openfpga_test_src/fabric_indepenent_bitstream.xml + +# Build fabric-dependent bitstream +build_fabric_bitstream --verbose + +# Write the Verilog netlist for FPGA fabric +# - Enable the use of explicit port mapping in Verilog netlist +write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src/SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose + +# Write the Verilog testbench for FPGA fabric +# - We suggest the use of same output directory as fabric Verilog netlists +# - Must specify the reference benchmark file if you want to output any testbenches +# - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA +# - Enable pre-configured top-level testbench which is a fast verification skipping programming phase +# - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts +write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src/SRC --reference_benchmark_file_path /var/tmp/xtang/and.v --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini /var/tmp/xtang/openfpga_test_src/simulation_deck.ini + +# Write the SDC files for PnR backend +# - Turn on every options here +write_pnr_sdc --file /var/tmp/xtang/openfpga_test_src/SDC + +# Write the SDC to run timing analysis for a mapped FPGA fabric +write_analysis_sdc --file /var/tmp/xtang/openfpga_test_src/SDC_analysis + +# Finish and exit OpenFPGA +exit diff --git a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_reduced_io_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_reduced_io_40nm.xml new file mode 100644 index 000000000..6f8c4cfae --- /dev/null +++ b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_reduced_io_40nm.xml @@ -0,0 +1,737 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + + + + + + + + + + + + + + + + + + + clb.clk + clb.cin + clb.O[9:0] clb.I[19:0] + clb.cout clb.O[19:10] clb.I[39:20] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 235e-12 + 235e-12 + 235e-12 + 235e-12 + 235e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 195e-12 + 195e-12 + 195e-12 + 195e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From d1d3446568113b1f8439cab4b65d1b985d4469a3 Mon Sep 17 00:00:00 2001 From: ganeshgore Date: Sun, 5 Apr 2020 11:36:24 -0600 Subject: [PATCH 366/645] backedup partial upgrade for fpga_flow script --- openfpga_flow/scripts/run_fpga_flow.py | 82 ++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/openfpga_flow/scripts/run_fpga_flow.py b/openfpga_flow/scripts/run_fpga_flow.py index fe5d535dd..1d67d3059 100644 --- a/openfpga_flow/scripts/run_fpga_flow.py +++ b/openfpga_flow/scripts/run_fpga_flow.py @@ -646,6 +646,88 @@ def run_vpr(): ExecTime["VPREnd"] = time.time() +def run_openfpga_shell(bench_blif, fixed_chan_width, logfile, route_only=False): + runfile = open("run.openfpga", 'w+'): + command = ["vpr", + args.arch_file, + bench_blif, + "--net_file", args.top_module+"_vpr.net", + "--place_file", args.top_module+"_vpr.place", + "--route_file", args.top_module+"_vpr.route", + "--full_stats", + "--activity_file", args.top_module+"_ace_out.act"] + # Other VPR options + if args.vpr_place_clb_pin_remap: + command += ["--place_clb_pin_remap"] + if args.vpr_route_breadthfirst: + command += ["--router_algorithm", "breadth_first"] + if args.vpr_max_router_iteration: + command += ["--max_router_iterations", args.vpr_max_router_iteration] + runfile.write(" ".join(command)+os.linesep) + + command = ["read_openfpga_arch", "-f", args.arch_file] + runfile.write(" ".join(command)+os.linesep) + + command = ["link_openfpga_arch", "--activity_file", + args.top_module+"_ace_out.act"] + runfile.write(" ".join(command)+os.linesep) + + command = ["check_netlist_naming_conflict", + "--fix", "--report", + (args.arch_file).replace(".xml", "_renaming.xml")] + runfile.write(" ".join(command)+os.linesep) + + runfile.write(" ".join(["pb_pin_fixup", "--verbose"])+os.linesep) + runfile.write(" ".join(["lut_truth_table_fixup"])+os.linesep) + + command = ["build_fabric", "--compress_routing", "--duplicate_grid_pin"] + runfile.write(" ".join(command)+os.linesep) + + runfile.write(" ".join(["repack"])+os.linesep) + + if args.vpr_fpga_verilog: + command += ["write_verilog_testbench", "--fpga_verilog"] + if args.vpr_fpga_verilog_dir: + command += ["--fpga_verilog_dir", args.vpr_fpga_verilog_dir] + if args.vpr_fpga_verilog_print_top_tb: + command += ["--fpga_verilog_print_top_testbench"] + if args.vpr_fpga_verilog_print_input_blif_tb: + command += ["--fpga_verilog_print_input_blif_testbench"] + if args.vpr_fpga_verilog_print_autocheck_top_testbench: + command += ["--fpga_verilog_print_autocheck_top_testbench", + os.path.join(args.run_dir, args.top_module+"_output_verilog.v")] + if args.vpr_fpga_verilog_include_timing: + command += ["--fpga_verilog_include_timing"] + if args.vpr_fpga_verilog_explicit_mapping: + command += ["--fpga_verilog_explicit_mapping"] + if args.vpr_fpga_x2p_duplicate_grid_pin: + command += ["--fpga_x2p_duplicate_grid_pin"] + if args.vpr_fpga_verilog_include_signal_init: + command += ["--fpga_verilog_include_signal_init"] + if args.vpr_fpga_verilog_formal_verification_top_netlist: + command += ["--fpga_verilog_print_formal_verification_top_netlist"] + if args.vpr_fpga_verilog_print_simulation_ini: + command += ["--fpga_verilog_print_simulation_ini"] + if args.vpr_fpga_verilog_include_icarus_simulator: + command += ["--fpga_verilog_include_icarus_simulator"] + if args.vpr_fpga_verilog_print_report_timing_tcl: + command += ["--fpga_verilog_print_report_timing_tcl"] + if args.vpr_fpga_verilog_report_timing_rpt_path: + command += ["--fpga_verilog_report_timing_rpt_path", + args.vpr_fpga_verilog_report_timing_rpt_path] + if args.vpr_fpga_verilog_print_sdc_pnr: + command += ["--fpga_verilog_print_sdc_pnr"] + if args.vpr_fpga_verilog_print_user_defined_template: + command += ["--fpga_verilog_print_user_defined_template"] + if args.vpr_fpga_verilog_print_sdc_analysis: + command += ["--fpga_verilog_print_sdc_analysis"] + runfile.write(" ".join(command)+os.linesep) + + if args.vpr_fpga_verilog_print_sdc_analysis: + command = ["write_pnr_sdc", "--fpga_bitstream_generator"] + runfile.write(" ".join(command)+os.linesep) + + def run_standard_vpr(bench_blif, fixed_chan_width, logfile, route_only=False): command = [cad_tools["vpr_path"], args.arch_file, From 836f722f2038cec4120900d8dfc1ec009e1341ac Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 5 Apr 2020 15:19:46 -0600 Subject: [PATCH 367/645] start supporting global output ports in module manager --- openfpga/src/fabric/module_manager.cpp | 2 +- openfpga/src/fabric/module_manager.h | 1 + .../src/fpga_verilog/verilog_writer_utils.cpp | 3 + openfpga/src/utils/module_manager_utils.cpp | 166 +++++++++++++++++- openfpga/src/utils/module_manager_utils.h | 6 + 5 files changed, 170 insertions(+), 8 deletions(-) diff --git a/openfpga/src/fabric/module_manager.cpp b/openfpga/src/fabric/module_manager.cpp index 40f672194..7f444318e 100644 --- a/openfpga/src/fabric/module_manager.cpp +++ b/openfpga/src/fabric/module_manager.cpp @@ -120,7 +120,7 @@ std::string ModuleManager::module_name(const ModuleId& module_id) const { /* Get the string of a module port type */ std::string ModuleManager::module_port_type_str(const enum e_module_port_type& port_type) const { - std::array MODULE_PORT_TYPE_STRING = {{"GLOBAL PORTS", "GPIO PORTS", "INOUT PORTS", "INPUT PORTS", "OUTPUT PORTS", "CLOCK PORTS"}}; + std::array MODULE_PORT_TYPE_STRING = {{"GLOBAL PORTS", "SPY PORTS", "GPIO PORTS", "INOUT PORTS", "INPUT PORTS", "OUTPUT PORTS", "CLOCK PORTS"}}; return MODULE_PORT_TYPE_STRING[port_type]; } diff --git a/openfpga/src/fabric/module_manager.h b/openfpga/src/fabric/module_manager.h index 425b7a072..1bfbd8b49 100644 --- a/openfpga/src/fabric/module_manager.h +++ b/openfpga/src/fabric/module_manager.h @@ -28,6 +28,7 @@ class ModuleManager { public: /* Private data structures */ enum e_module_port_type { MODULE_GLOBAL_PORT, /* Global inputs */ + MODULE_SPY_PORT, /* Global outputs, which is designed for spypads */ MODULE_GPIO_PORT, /* General-purpose IOs, which are data IOs of the fabric */ MODULE_INOUT_PORT, /* Normal (non-global) inout ports */ MODULE_INPUT_PORT, /* Normal (non-global) input ports */ diff --git a/openfpga/src/fpga_verilog/verilog_writer_utils.cpp b/openfpga/src/fpga_verilog/verilog_writer_utils.cpp index 95f34ab1f..b771f4752 100644 --- a/openfpga/src/fpga_verilog/verilog_writer_utils.cpp +++ b/openfpga/src/fpga_verilog/verilog_writer_utils.cpp @@ -129,6 +129,7 @@ void print_verilog_module_definition(std::fstream& fp, /* port type2type mapping */ std::map port_type2type_map; port_type2type_map[ModuleManager::MODULE_GLOBAL_PORT] = VERILOG_PORT_CONKT; + port_type2type_map[ModuleManager::MODULE_SPY_PORT] = VERILOG_PORT_CONKT; port_type2type_map[ModuleManager::MODULE_GPIO_PORT] = VERILOG_PORT_CONKT; port_type2type_map[ModuleManager::MODULE_INOUT_PORT] = VERILOG_PORT_CONKT; port_type2type_map[ModuleManager::MODULE_INPUT_PORT] = VERILOG_PORT_CONKT; @@ -188,6 +189,7 @@ void print_verilog_module_ports(std::fstream& fp, /* port type2type mapping */ std::map port_type2type_map; port_type2type_map[ModuleManager::MODULE_GLOBAL_PORT] = VERILOG_PORT_INPUT; + port_type2type_map[ModuleManager::MODULE_SPY_PORT] = VERILOG_PORT_OUTPUT; port_type2type_map[ModuleManager::MODULE_GPIO_PORT] = VERILOG_PORT_INOUT; port_type2type_map[ModuleManager::MODULE_INOUT_PORT] = VERILOG_PORT_INOUT; port_type2type_map[ModuleManager::MODULE_INPUT_PORT] = VERILOG_PORT_INPUT; @@ -344,6 +346,7 @@ void print_verilog_module_instance(std::fstream& fp, /* port type2type mapping */ std::map port_type2type_map; port_type2type_map[ModuleManager::MODULE_GLOBAL_PORT] = VERILOG_PORT_CONKT; + port_type2type_map[ModuleManager::MODULE_SPY_PORT] = VERILOG_PORT_CONKT; port_type2type_map[ModuleManager::MODULE_GPIO_PORT] = VERILOG_PORT_CONKT; port_type2type_map[ModuleManager::MODULE_INOUT_PORT] = VERILOG_PORT_CONKT; port_type2type_map[ModuleManager::MODULE_INPUT_PORT] = VERILOG_PORT_CONKT; diff --git a/openfpga/src/utils/module_manager_utils.cpp b/openfpga/src/utils/module_manager_utils.cpp index 9797763b0..5c3f4a41b 100644 --- a/openfpga/src/utils/module_manager_utils.cpp +++ b/openfpga/src/utils/module_manager_utils.cpp @@ -36,10 +36,20 @@ ModuleId add_circuit_model_to_module_manager(ModuleManager& module_manager, VTR_ASSERT(ModuleId::INVALID() != module); /* Add ports */ - /* Find global ports and add one by one */ + /* Find global ports and add one by one + * Global input ports will be considered as global port in the context of module manager + * Global output ports will be considered as spy port in the context of module manager + */ for (const auto& port : circuit_lib.model_global_ports(circuit_model, false)) { BasicPort port_info(circuit_lib.port_prefix(port), circuit_lib.port_size(port)); - module_manager.add_port(module, port_info, ModuleManager::MODULE_GLOBAL_PORT); + if (CIRCUIT_MODEL_PORT_INPUT == circuit_lib.port_type(port)) { + module_manager.add_port(module, port_info, ModuleManager::MODULE_GLOBAL_PORT); + } else if (CIRCUIT_MODEL_PORT_CLOCK == circuit_lib.port_type(port)) { + module_manager.add_port(module, port_info, ModuleManager::MODULE_GLOBAL_PORT); + } else { + VTR_ASSERT(CIRCUIT_MODEL_PORT_OUTPUT == circuit_lib.port_type(port)); + module_manager.add_port(module, port_info, ModuleManager::MODULE_SPY_PORT); + } } /* Find other ports and add one by one */ @@ -1011,18 +1021,30 @@ void add_module_gpio_ports_from_child_modules(ModuleManager& module_manager, } /******************************************************************** - * Add global ports to the module: + * Add global input ports to the module: * In this function, the following tasks are done: - * 1. find all the global ports from the child modules and build a list of it, - * 2. add the ports to the pb_module + * 1. find all the global input ports from the child modules and build a list of it, + * 2. add the input ports to the pb_module * 3. add the module nets to connect the pb_module global ports to those of child modules * + * Module + * +-------------------------- + * | child[0] + * input_portA[0] ----+-+---->+---------- + * | | | + * | | +---------- + * | | + * | | child[1] + * | +---->+---------- + * | | + * | +---------- + * * Note: This function should be call ONLY after all the sub modules (instances) * have been added to the pb_module! * Otherwise, some global ports of the sub modules may be missed! *******************************************************************/ -void add_module_global_ports_from_child_modules(ModuleManager& module_manager, - const ModuleId& module_id) { +void add_module_global_input_ports_from_child_modules(ModuleManager& module_manager, + const ModuleId& module_id) { std::vector global_ports_to_add; /* Iterate over the child modules */ @@ -1078,6 +1100,136 @@ void add_module_global_ports_from_child_modules(ModuleManager& module_manager, } } +/******************************************************************** + * Add global output ports to the module: + * In this function, the following tasks are done: + * 1. find all the global output ports from the child modules and build a list of it, + * 2. add the output ports to the pb_module + * 3. add the module nets to connect the pb_module global ports to those of child modules + * + * Module + * ----------------------+ + * | + * child[0] | + * -----------+ | + * |----------+----> outputA[0] + * -----------+ | + * | + * child[1] | + * -----------+ | + * |----------+----> outputA[1] + * -----------+ | + * + * Note: This function should be call ONLY after all the sub modules (instances) + * have been added to the pb_module! + * Otherwise, some global ports of the sub modules may be missed! + *******************************************************************/ +void add_module_global_output_ports_from_child_modules(ModuleManager& module_manager, + const ModuleId& module_id) { + std::vector global_ports_to_add; + + /* Iterate over the child modules */ + for (const ModuleId& child : module_manager.child_modules(module_id)) { + /* Iterate over the child instances */ + for (size_t i = 0; i < module_manager.num_instance(module_id, child); ++i) { + /* Find all the global ports, whose port type is special */ + for (BasicPort global_port : module_manager.module_ports_by_type(child, ModuleManager::MODULE_SPY_PORT)) { + /* Search in the global port list to be added, if this is unique, we update the list */ + std::vector::iterator it = std::find(global_ports_to_add.begin(), global_ports_to_add.end(), global_port); + if (it != global_ports_to_add.end()) { + /* Found in the global port with the same name, increase the port size */ + it->expand(global_port.get_width()); + continue; /* Finish for the port already in the list */ + } + /* Reach here, this is an unique global port, update the list */ + global_ports_to_add.push_back(global_port); + } + } + } + + /* Record the port id for each type of global port */ + std::vector global_port_ids; + /* Add the global ports for the module */ + for (const BasicPort& global_port_to_add : global_ports_to_add) { + ModulePortId port_id = module_manager.add_port(module_id, global_port_to_add, ModuleManager::MODULE_SPY_PORT); + global_port_ids.push_back(port_id); + } + + /* Add module nets to connect the global ports of the module to the global ports of the sub module */ + /* Create a counter for each global port to record the current LSB */ + std::vector global_port_lsbs(global_port_ids.size(), 0); + + /* Iterate over the child modules */ + for (const ModuleId& child : module_manager.child_modules(module_id)) { + /* Iterate over the child instances */ + for (const size_t& child_instance : module_manager.child_module_instances(module_id, child)) { + /* Find all the global ports, whose port type is special */ + for (ModulePortId child_global_port_id : module_manager.module_port_ids_by_type(child, ModuleManager::MODULE_SPY_PORT)) { + /* Find the global port from the child module */ + BasicPort child_global_port = module_manager.module_port(child, child_global_port_id); + /* Search in the global port list to be added, find the port id */ + std::vector::iterator it = std::find(global_ports_to_add.begin(), global_ports_to_add.end(), child_global_port); + VTR_ASSERT(it != global_ports_to_add.end()); + + /* Find the global port from the parent module */ + size_t module_global_port_offset = it - global_ports_to_add.begin(); + ModulePortId module_global_port_id = global_port_ids[module_global_port_offset]; + BasicPort module_global_port = module_manager.module_port(module_id, module_global_port_id); + /* Current LSB should be in range */ + VTR_ASSERT(module_global_port.get_width() > global_port_lsbs[module_global_port_offset]); + /* Set the global port from the parent module as the LSB recorded */ + module_global_port.set_width(global_port_lsbs[module_global_port_offset], global_port_lsbs[module_global_port_offset]); + /* Update the LSB */ + global_port_lsbs[module_global_port_offset]++; + + /* The global ports should match in size */ + VTR_ASSERT(module_global_port.get_width() == child_global_port.get_width()); + /* For each pin of the child port, create a net and do wiring */ + for (size_t pin_id = 0; pin_id < child_global_port.pins().size(); ++pin_id) { + /* Reach here, it means this is the port we want, create a net and configure its source and sink */ + ModuleNetId net = module_manager.create_module_net(module_id); + module_manager.add_module_net_source(module_id, net, child, child_instance, child_global_port_id, child_global_port.pins()[pin_id]); + module_manager.add_module_net_sink(module_id, net, module_id, 0, module_global_port_id, module_global_port.pins()[pin_id]); + /* We finish for this child gpio port */ + } + } + } + } + + /* Find check: all the LSBs of global ports should match the MSB */ + for (size_t iport = 0; iport < global_port_ids.size(); ++iport) { + BasicPort module_global_port = module_manager.module_port(module_id, global_port_ids[iport]); + VTR_ASSERT(module_global_port.get_width() == global_port_lsbs[iport]); + } +} + +/******************************************************************** + * Add global ports to the module: + * In this function, we will add global input ports and global output ports + * which are collected from the child modules + * + * - Input ports: the input ports will be uniquified by names + * Ports with the same name will be merged to the same pin + * See details inside the function + * + * - Output ports: the output ports will be uniquified by names + * Different from the input ports, output ports + * with the same name will be merged but will have indepedent pins + * See details inside the function + * + * Note: This function should be call ONLY after all the sub modules (instances) + * have been added to the pb_module! + * Otherwise, some global ports of the sub modules may be missed! + *******************************************************************/ +void add_module_global_ports_from_child_modules(ModuleManager& module_manager, + const ModuleId& module_id) { + /* Input ports */ + add_module_global_input_ports_from_child_modules(module_manager, module_id); + + /* Output ports */ + add_module_global_output_ports_from_child_modules(module_manager, module_id); +} + /******************************************************************** * Find the number of shared configuration bits for a module * by selected the maximum number of shared configuration bits of child modules diff --git a/openfpga/src/utils/module_manager_utils.h b/openfpga/src/utils/module_manager_utils.h index 6198701ad..fcc95b8ba 100644 --- a/openfpga/src/utils/module_manager_utils.h +++ b/openfpga/src/utils/module_manager_utils.h @@ -108,6 +108,12 @@ size_t find_module_num_config_bits(const ModuleManager& module_manager, const CircuitModelId& sram_model, const e_config_protocol_type& sram_orgz_type); +void add_module_global_input_ports_from_child_modules(ModuleManager& module_manager, + const ModuleId& module_id); + +void add_module_global_output_ports_from_child_modules(ModuleManager& module_manager, + const ModuleId& module_id); + void add_module_global_ports_from_child_modules(ModuleManager& module_manager, const ModuleId& module_id); From 3b63ad66576548967d9cee45d9e2099235213144 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 5 Apr 2020 15:23:07 -0600 Subject: [PATCH 368/645] add test openfpga arch XML with spy pad --- .../k6_frac_N10_spyio_40nm_openfpga.xml | 268 ++++++++++++++++++ 1 file changed, 268 insertions(+) create mode 100644 openfpga/test_openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml new file mode 100644 index 000000000..9d81c5b3e --- /dev/null +++ b/openfpga/test_openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml @@ -0,0 +1,268 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + + + + 10e-12 5e-12 + + + 10e-12 5e-12 + + + + + + + + + + + + 10e-12 5e-12 5e-12 + + + 10e-12 5e-12 5e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From ca45efd13df7c3d99459671f2a1b629104952b20 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 5 Apr 2020 15:24:40 -0600 Subject: [PATCH 369/645] add testing script for the spy io --- .../and_k6_frac_tileable_spyio.openfpga | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 openfpga/test_script/and_k6_frac_tileable_spyio.openfpga diff --git a/openfpga/test_script/and_k6_frac_tileable_spyio.openfpga b/openfpga/test_script/and_k6_frac_tileable_spyio.openfpga new file mode 100644 index 000000000..7b9fa407c --- /dev/null +++ b/openfpga/test_script/and_k6_frac_tileable_spyio.openfpga @@ -0,0 +1,59 @@ +# Run VPR for the 'and' design +vpr ./test_vpr_arch/k6_frac_N10_tileable_40nm.xml ./test_blif/and.blif --clock_modeling route #--write_rr_graph example_rr_graph.xml + +# Read OpenFPGA architecture definition +read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml + +# Write out the architecture XML as a proof +#write_openfpga_arch -f ./arch_echo.xml + +# Annotate the OpenFPGA architecture to VPR data base +link_openfpga_arch --activity_file ./test_blif/and.act --sort_gsb_chan_node_in_edges #--verbose + +# Check and correct any naming conflicts in the BLIF netlist +check_netlist_naming_conflict --fix --report ./netlist_renaming.xml + +# Apply fix-up to clustering nets based on routing results +pb_pin_fixup --verbose + +# Apply fix-up to Look-Up Table truth tables based on packing results +lut_truth_table_fixup #--verbose + +# Build the module graph +# - Enabled compression on routing architecture modules +# - Enable pin duplication on grid modules +build_fabric --compress_routing --duplicate_grid_pin #--verbose + +# Repack the netlist to physical pbs +# This must be done before bitstream generator and testbench generation +# Strongly recommend it is done after all the fix-up have been applied +repack #--verbose + +# Build the bitstream +# - Output the fabric-independent bitstream to a file +build_architecture_bitstream --verbose --file /var/tmp/xtang/openfpga_test_src/fabric_indepenent_bitstream.xml + +# Build fabric-dependent bitstream +build_fabric_bitstream --verbose + +# Write the Verilog netlist for FPGA fabric +# - Enable the use of explicit port mapping in Verilog netlist +write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src/SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose + +# Write the Verilog testbench for FPGA fabric +# - We suggest the use of same output directory as fabric Verilog netlists +# - Must specify the reference benchmark file if you want to output any testbenches +# - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA +# - Enable pre-configured top-level testbench which is a fast verification skipping programming phase +# - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts +write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src/SRC --reference_benchmark_file_path /var/tmp/xtang/and.v --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini /var/tmp/xtang/openfpga_test_src/simulation_deck.ini + +# Write the SDC files for PnR backend +# - Turn on every options here +write_pnr_sdc --file /var/tmp/xtang/openfpga_test_src/SDC + +# Write the SDC to run timing analysis for a mapped FPGA fabric +write_analysis_sdc --file /var/tmp/xtang/openfpga_test_src/SDC_analysis + +# Finish and exit OpenFPGA +exit From 8b583b79179a57afb1fe0c241aa5133cad41b966 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 5 Apr 2020 16:01:25 -0600 Subject: [PATCH 370/645] debugging spy port builder in module manager --- .../src/check_circuit_library.cpp | 7 ++++--- openfpga/src/utils/module_manager_utils.cpp | 19 +++++++++++-------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp b/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp index afe6e2173..2e8c1a214 100644 --- a/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp +++ b/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp @@ -308,8 +308,9 @@ size_t check_circuit_library_ports(const CircuitLibrary& circuit_lib) { /* Check global ports: make sure all the global ports are input ports */ for (const auto& port : circuit_lib.ports()) { if ( (circuit_lib.port_is_global(port)) - && (!circuit_lib.is_input_port(port)) ) { - VTR_LOG_ERROR("Circuit port (type=%s) of model (name=%s) is defined as global but not an input port!\n", + && (!circuit_lib.is_input_port(port)) + && (!circuit_lib.is_output_port(port)) ) { + VTR_LOG_ERROR("Circuit port (type=%s) of model (name=%s) is defined as global but not an input/output port!\n", CIRCUIT_MODEL_PORT_TYPE_STRING[size_t(circuit_lib.port_type(port))], circuit_lib.model_name(port).c_str()); num_err++; @@ -322,7 +323,7 @@ size_t check_circuit_library_ports(const CircuitLibrary& circuit_lib) { || (circuit_lib.port_is_reset(port)) || (circuit_lib.port_is_config_enable(port)) ) && (!circuit_lib.port_is_global(port)) ) { - VTR_LOG_ERROR("Circuit port (type=%s) of model (name=%s) is defined as a set/reset/config_enable port but it is not global!\n", + VTR_LOG_ERROR("Circuit port (type=%s) of model (name=%s) is defined as a set/reset/config_enable port but it is not global!\n", CIRCUIT_MODEL_PORT_TYPE_STRING[size_t(circuit_lib.port_type(port))], circuit_lib.model_name(port).c_str()); num_err++; diff --git a/openfpga/src/utils/module_manager_utils.cpp b/openfpga/src/utils/module_manager_utils.cpp index 5c3f4a41b..8806003b7 100644 --- a/openfpga/src/utils/module_manager_utils.cpp +++ b/openfpga/src/utils/module_manager_utils.cpp @@ -1127,6 +1127,7 @@ void add_module_global_input_ports_from_child_modules(ModuleManager& module_mana void add_module_global_output_ports_from_child_modules(ModuleManager& module_manager, const ModuleId& module_id) { std::vector global_ports_to_add; + std::vector global_port_names; /* Iterate over the child modules */ for (const ModuleId& child : module_manager.child_modules(module_id)) { @@ -1135,14 +1136,15 @@ void add_module_global_output_ports_from_child_modules(ModuleManager& module_man /* Find all the global ports, whose port type is special */ for (BasicPort global_port : module_manager.module_ports_by_type(child, ModuleManager::MODULE_SPY_PORT)) { /* Search in the global port list to be added, if this is unique, we update the list */ - std::vector::iterator it = std::find(global_ports_to_add.begin(), global_ports_to_add.end(), global_port); - if (it != global_ports_to_add.end()) { + std::vector::iterator it = std::find(global_port_names.begin(), global_port_names.end(), global_port.get_name()); + if (it != global_port_names.end()) { /* Found in the global port with the same name, increase the port size */ - it->expand(global_port.get_width()); + global_ports_to_add[it - global_port_names.begin()].expand(global_port.get_width()); continue; /* Finish for the port already in the list */ } /* Reach here, this is an unique global port, update the list */ global_ports_to_add.push_back(global_port); + global_port_names.push_back(global_port.get_name()); } } } @@ -1168,19 +1170,19 @@ void add_module_global_output_ports_from_child_modules(ModuleManager& module_man /* Find the global port from the child module */ BasicPort child_global_port = module_manager.module_port(child, child_global_port_id); /* Search in the global port list to be added, find the port id */ - std::vector::iterator it = std::find(global_ports_to_add.begin(), global_ports_to_add.end(), child_global_port); - VTR_ASSERT(it != global_ports_to_add.end()); + std::vector::iterator it = std::find(global_port_names.begin(), global_port_names.end(), child_global_port.get_name()); + VTR_ASSERT(it != global_port_names.end()); /* Find the global port from the parent module */ - size_t module_global_port_offset = it - global_ports_to_add.begin(); + size_t module_global_port_offset = it - global_port_names.begin(); ModulePortId module_global_port_id = global_port_ids[module_global_port_offset]; BasicPort module_global_port = module_manager.module_port(module_id, module_global_port_id); /* Current LSB should be in range */ VTR_ASSERT(module_global_port.get_width() > global_port_lsbs[module_global_port_offset]); /* Set the global port from the parent module as the LSB recorded */ - module_global_port.set_width(global_port_lsbs[module_global_port_offset], global_port_lsbs[module_global_port_offset]); + module_global_port.set_width(global_port_lsbs[module_global_port_offset], global_port_lsbs[module_global_port_offset] + child_global_port.get_width() - 1); /* Update the LSB */ - global_port_lsbs[module_global_port_offset]++; + global_port_lsbs[module_global_port_offset] += child_global_port.get_width(); /* The global ports should match in size */ VTR_ASSERT(module_global_port.get_width() == child_global_port.get_width()); @@ -1199,6 +1201,7 @@ void add_module_global_output_ports_from_child_modules(ModuleManager& module_man /* Find check: all the LSBs of global ports should match the MSB */ for (size_t iport = 0; iport < global_port_ids.size(); ++iport) { BasicPort module_global_port = module_manager.module_port(module_id, global_port_ids[iport]); + if (module_global_port.get_width() != global_port_lsbs[iport]) VTR_ASSERT(module_global_port.get_width() == global_port_lsbs[iport]); } } From bc47b3ca94befd33e9b78990aa26a77bad5b5693 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 5 Apr 2020 16:04:13 -0600 Subject: [PATCH 371/645] update verilog module writer to the global spy ports --- openfpga/src/fpga_verilog/verilog_module_writer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/openfpga/src/fpga_verilog/verilog_module_writer.cpp b/openfpga/src/fpga_verilog/verilog_module_writer.cpp index 34c2c4be7..0d6103beb 100644 --- a/openfpga/src/fpga_verilog/verilog_module_writer.cpp +++ b/openfpga/src/fpga_verilog/verilog_module_writer.cpp @@ -396,6 +396,7 @@ void write_verilog_instance_to_file(std::fstream& fp, /* port type2type mapping */ std::map port_type2type_map; port_type2type_map[ModuleManager::MODULE_GLOBAL_PORT] = VERILOG_PORT_CONKT; + port_type2type_map[ModuleManager::MODULE_SPY_PORT] = VERILOG_PORT_CONKT; port_type2type_map[ModuleManager::MODULE_GPIO_PORT] = VERILOG_PORT_CONKT; port_type2type_map[ModuleManager::MODULE_INOUT_PORT] = VERILOG_PORT_CONKT; port_type2type_map[ModuleManager::MODULE_INPUT_PORT] = VERILOG_PORT_CONKT; From 5f4e7dc5d413fc143c64e91b451f7eb30a732516 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 5 Apr 2020 16:52:21 -0600 Subject: [PATCH 372/645] support gpinput and gpoutput ports in module manager and circuit library --- .../libarchopenfpga/src/circuit_library.cpp | 16 ++ .../libarchopenfpga/src/circuit_library.h | 4 + .../src/read_xml_circuit_library.cpp | 7 + openfpga/src/fabric/module_manager.cpp | 2 +- openfpga/src/fabric/module_manager.h | 3 +- .../fpga_verilog/verilog_module_writer.cpp | 3 +- .../src/fpga_verilog/verilog_writer_utils.cpp | 9 +- openfpga/src/utils/module_manager_utils.cpp | 188 +++++++----------- .../k6_frac_N10_spyio_40nm_openfpga.xml | 2 + 9 files changed, 107 insertions(+), 127 deletions(-) diff --git a/libopenfpga/libarchopenfpga/src/circuit_library.cpp b/libopenfpga/libarchopenfpga/src/circuit_library.cpp index 0ea7139df..cf8b8714f 100644 --- a/libopenfpga/libarchopenfpga/src/circuit_library.cpp +++ b/libopenfpga/libarchopenfpga/src/circuit_library.cpp @@ -892,6 +892,12 @@ size_t CircuitLibrary::port_default_value(const CircuitPortId& circuit_port_id) return port_default_values_[circuit_port_id]; } +/* Return a flag if the port is used in mode-selection purpuse of a circuit model */ +bool CircuitLibrary::port_is_io(const CircuitPortId& circuit_port_id) const { + /* validate the circuit_port_id */ + VTR_ASSERT(valid_circuit_port_id(circuit_port_id)); + return port_is_io_[circuit_port_id]; +} /* Return a flag if the port is used in mode-selection purpuse of a circuit model */ bool CircuitLibrary::port_is_mode_select(const CircuitPortId& circuit_port_id) const { @@ -1344,6 +1350,7 @@ CircuitPortId CircuitLibrary::add_model_port(const CircuitModelId& model_id, port_lib_names_.emplace_back(); port_inv_prefix_.emplace_back(); port_default_values_.push_back(-1); + port_is_io_.push_back(false); port_is_mode_select_.push_back(false); port_is_global_.push_back(false); port_is_reset_.push_back(false); @@ -1414,6 +1421,15 @@ void CircuitLibrary::set_port_default_value(const CircuitPortId& circuit_port_id return; } +/* Set the is_mode_select for a port of a circuit model */ +void CircuitLibrary::set_port_is_io(const CircuitPortId& circuit_port_id, + const bool& is_io) { + /* validate the circuit_port_id */ + VTR_ASSERT(valid_circuit_port_id(circuit_port_id)); + port_is_io_[circuit_port_id] = is_io; + return; +} + /* Set the is_mode_select for a port of a circuit model */ void CircuitLibrary::set_port_is_mode_select(const CircuitPortId& circuit_port_id, const bool& is_mode_select) { diff --git a/libopenfpga/libarchopenfpga/src/circuit_library.h b/libopenfpga/libarchopenfpga/src/circuit_library.h index 8f2781349..c6a62dc27 100644 --- a/libopenfpga/libarchopenfpga/src/circuit_library.h +++ b/libopenfpga/libarchopenfpga/src/circuit_library.h @@ -275,6 +275,7 @@ class CircuitLibrary { std::string port_lib_name(const CircuitPortId& circuit_port_id) const; std::string port_inv_prefix(const CircuitPortId& circuit_port_id) const; size_t port_default_value(const CircuitPortId& circuit_port_id) const; + bool port_is_io(const CircuitPortId& circuit_port_id) const; bool port_is_mode_select(const CircuitPortId& circuit_port_id) const; bool port_is_global(const CircuitPortId& circuit_port_id) const; bool port_is_reset(const CircuitPortId& circuit_port_id) const; @@ -346,6 +347,8 @@ class CircuitLibrary { const std::string& inv_prefix); void set_port_default_value(const CircuitPortId& circuit_port_id, const size_t& default_val); + void set_port_is_io(const CircuitPortId& circuit_port_id, + const bool& is_io); void set_port_is_mode_select(const CircuitPortId& circuit_port_id, const bool& is_mode_select); void set_port_is_global(const CircuitPortId& circuit_port_id, @@ -529,6 +532,7 @@ class CircuitLibrary { vtr::vector port_lib_names_; vtr::vector port_inv_prefix_; vtr::vector port_default_values_; + vtr::vector port_is_io_; vtr::vector port_is_mode_select_; vtr::vector port_is_global_; vtr::vector port_is_reset_; diff --git a/libopenfpga/libarchopenfpga/src/read_xml_circuit_library.cpp b/libopenfpga/libarchopenfpga/src/read_xml_circuit_library.cpp index af12297d1..cd8ffd1c5 100644 --- a/libopenfpga/libarchopenfpga/src/read_xml_circuit_library.cpp +++ b/libopenfpga/libarchopenfpga/src/read_xml_circuit_library.cpp @@ -486,6 +486,13 @@ void read_xml_circuit_port(pugi::xml_node& xml_port, /* Parse the port size, by default it will be 1 */ circuit_lib.set_port_size(port, get_attribute(xml_port, "size", loc_data).as_int(1)); + /* Identify if the port is for io, this is only applicable to INPUT ports. + * By default, it will NOT be a mode selection port + */ + if (CIRCUIT_MODEL_PORT_INPUT == circuit_lib.port_type(port)) { + circuit_lib.set_port_is_io(port, get_attribute(xml_port, "io", loc_data, pugiutil::ReqOpt::OPTIONAL).as_bool(false)); + } + /* Identify if the port is for mode selection, this is only applicable to SRAM ports. * By default, it will NOT be a mode selection port */ diff --git a/openfpga/src/fabric/module_manager.cpp b/openfpga/src/fabric/module_manager.cpp index 7f444318e..d03af54b0 100644 --- a/openfpga/src/fabric/module_manager.cpp +++ b/openfpga/src/fabric/module_manager.cpp @@ -120,7 +120,7 @@ std::string ModuleManager::module_name(const ModuleId& module_id) const { /* Get the string of a module port type */ std::string ModuleManager::module_port_type_str(const enum e_module_port_type& port_type) const { - std::array MODULE_PORT_TYPE_STRING = {{"GLOBAL PORTS", "SPY PORTS", "GPIO PORTS", "INOUT PORTS", "INPUT PORTS", "OUTPUT PORTS", "CLOCK PORTS"}}; + std::array MODULE_PORT_TYPE_STRING = {{"GLOBAL PORTS", "GPIN PORTS", "GPOUT PORTS", "GPIO PORTS", "INOUT PORTS", "INPUT PORTS", "OUTPUT PORTS", "CLOCK PORTS"}}; return MODULE_PORT_TYPE_STRING[port_type]; } diff --git a/openfpga/src/fabric/module_manager.h b/openfpga/src/fabric/module_manager.h index 1bfbd8b49..cd79bde8c 100644 --- a/openfpga/src/fabric/module_manager.h +++ b/openfpga/src/fabric/module_manager.h @@ -28,7 +28,8 @@ class ModuleManager { public: /* Private data structures */ enum e_module_port_type { MODULE_GLOBAL_PORT, /* Global inputs */ - MODULE_SPY_PORT, /* Global outputs, which is designed for spypads */ + MODULE_GPIN_PORT, /* General-purpose input */ + MODULE_GPOUT_PORT, /* General-purpose outputs, could be used for spypads */ MODULE_GPIO_PORT, /* General-purpose IOs, which are data IOs of the fabric */ MODULE_INOUT_PORT, /* Normal (non-global) inout ports */ MODULE_INPUT_PORT, /* Normal (non-global) input ports */ diff --git a/openfpga/src/fpga_verilog/verilog_module_writer.cpp b/openfpga/src/fpga_verilog/verilog_module_writer.cpp index 0d6103beb..7d6883236 100644 --- a/openfpga/src/fpga_verilog/verilog_module_writer.cpp +++ b/openfpga/src/fpga_verilog/verilog_module_writer.cpp @@ -396,7 +396,8 @@ void write_verilog_instance_to_file(std::fstream& fp, /* port type2type mapping */ std::map port_type2type_map; port_type2type_map[ModuleManager::MODULE_GLOBAL_PORT] = VERILOG_PORT_CONKT; - port_type2type_map[ModuleManager::MODULE_SPY_PORT] = VERILOG_PORT_CONKT; + port_type2type_map[ModuleManager::MODULE_GPIN_PORT] = VERILOG_PORT_CONKT; + port_type2type_map[ModuleManager::MODULE_GPOUT_PORT] = VERILOG_PORT_CONKT; port_type2type_map[ModuleManager::MODULE_GPIO_PORT] = VERILOG_PORT_CONKT; port_type2type_map[ModuleManager::MODULE_INOUT_PORT] = VERILOG_PORT_CONKT; port_type2type_map[ModuleManager::MODULE_INPUT_PORT] = VERILOG_PORT_CONKT; diff --git a/openfpga/src/fpga_verilog/verilog_writer_utils.cpp b/openfpga/src/fpga_verilog/verilog_writer_utils.cpp index b771f4752..90ee95e9c 100644 --- a/openfpga/src/fpga_verilog/verilog_writer_utils.cpp +++ b/openfpga/src/fpga_verilog/verilog_writer_utils.cpp @@ -129,7 +129,8 @@ void print_verilog_module_definition(std::fstream& fp, /* port type2type mapping */ std::map port_type2type_map; port_type2type_map[ModuleManager::MODULE_GLOBAL_PORT] = VERILOG_PORT_CONKT; - port_type2type_map[ModuleManager::MODULE_SPY_PORT] = VERILOG_PORT_CONKT; + port_type2type_map[ModuleManager::MODULE_GPIN_PORT] = VERILOG_PORT_CONKT; + port_type2type_map[ModuleManager::MODULE_GPOUT_PORT] = VERILOG_PORT_CONKT; port_type2type_map[ModuleManager::MODULE_GPIO_PORT] = VERILOG_PORT_CONKT; port_type2type_map[ModuleManager::MODULE_INOUT_PORT] = VERILOG_PORT_CONKT; port_type2type_map[ModuleManager::MODULE_INPUT_PORT] = VERILOG_PORT_CONKT; @@ -189,7 +190,8 @@ void print_verilog_module_ports(std::fstream& fp, /* port type2type mapping */ std::map port_type2type_map; port_type2type_map[ModuleManager::MODULE_GLOBAL_PORT] = VERILOG_PORT_INPUT; - port_type2type_map[ModuleManager::MODULE_SPY_PORT] = VERILOG_PORT_OUTPUT; + port_type2type_map[ModuleManager::MODULE_GPIN_PORT] = VERILOG_PORT_INPUT; + port_type2type_map[ModuleManager::MODULE_GPOUT_PORT] = VERILOG_PORT_OUTPUT; port_type2type_map[ModuleManager::MODULE_GPIO_PORT] = VERILOG_PORT_INOUT; port_type2type_map[ModuleManager::MODULE_INOUT_PORT] = VERILOG_PORT_INOUT; port_type2type_map[ModuleManager::MODULE_INPUT_PORT] = VERILOG_PORT_INPUT; @@ -346,7 +348,8 @@ void print_verilog_module_instance(std::fstream& fp, /* port type2type mapping */ std::map port_type2type_map; port_type2type_map[ModuleManager::MODULE_GLOBAL_PORT] = VERILOG_PORT_CONKT; - port_type2type_map[ModuleManager::MODULE_SPY_PORT] = VERILOG_PORT_CONKT; + port_type2type_map[ModuleManager::MODULE_GPIN_PORT] = VERILOG_PORT_CONKT; + port_type2type_map[ModuleManager::MODULE_GPOUT_PORT] = VERILOG_PORT_CONKT; port_type2type_map[ModuleManager::MODULE_GPIO_PORT] = VERILOG_PORT_CONKT; port_type2type_map[ModuleManager::MODULE_INOUT_PORT] = VERILOG_PORT_CONKT; port_type2type_map[ModuleManager::MODULE_INPUT_PORT] = VERILOG_PORT_CONKT; diff --git a/openfpga/src/utils/module_manager_utils.cpp b/openfpga/src/utils/module_manager_utils.cpp index 8806003b7..3c1ef2de2 100644 --- a/openfpga/src/utils/module_manager_utils.cpp +++ b/openfpga/src/utils/module_manager_utils.cpp @@ -42,13 +42,17 @@ ModuleId add_circuit_model_to_module_manager(ModuleManager& module_manager, */ for (const auto& port : circuit_lib.model_global_ports(circuit_model, false)) { BasicPort port_info(circuit_lib.port_prefix(port), circuit_lib.port_size(port)); - if (CIRCUIT_MODEL_PORT_INPUT == circuit_lib.port_type(port)) { + if ( (CIRCUIT_MODEL_PORT_INPUT == circuit_lib.port_type(port)) + && (false == circuit_lib.port_is_io(port)) ) { module_manager.add_port(module, port_info, ModuleManager::MODULE_GLOBAL_PORT); } else if (CIRCUIT_MODEL_PORT_CLOCK == circuit_lib.port_type(port)) { module_manager.add_port(module, port_info, ModuleManager::MODULE_GLOBAL_PORT); + } else if ( (CIRCUIT_MODEL_PORT_INPUT == circuit_lib.port_type(port)) + && (false == circuit_lib.port_is_io(port)) ) { + module_manager.add_port(module, port_info, ModuleManager::MODULE_GPIN_PORT); } else { VTR_ASSERT(CIRCUIT_MODEL_PORT_OUTPUT == circuit_lib.port_type(port)); - module_manager.add_port(module, port_info, ModuleManager::MODULE_SPY_PORT); + module_manager.add_port(module, port_info, ModuleManager::MODULE_GPOUT_PORT); } } @@ -931,19 +935,35 @@ size_t find_module_num_config_bits(const ModuleManager& module_manager, } /******************************************************************** - * Add GPIO ports to the module: + * Add General purpose I/O ports to the module: * In this function, the following tasks are done: - * 1. find all the GPIO ports from the child modules and build a list of it, - * 2. Merge all the GPIO ports with the same name + * 1. find all the I/O ports from the child modules and build a list of it, + * 2. Merge all the I/O ports with the same name * 3. add the ports to the pb_module * 4. add module nets to connect to the GPIO ports of each sub module + * + * Module + * ----------------------+ + * | + * child[0] | + * -----------+ | + * |----------+----> outputA[0] + * -----------+ | + * | + * child[1] | + * -----------+ | + * |----------+----> outputA[1] + * -----------+ | + * * Note: This function should be call ONLY after all the sub modules (instances) * have been added to the pb_module! * Otherwise, some GPIO ports of the sub modules may be missed! *******************************************************************/ -void add_module_gpio_ports_from_child_modules(ModuleManager& module_manager, - const ModuleId& module_id) { +static +void add_module_io_ports_from_child_modules(ModuleManager& module_manager, + const ModuleId& module_id, + const ModuleManager::e_module_port_type& module_port_type) { std::vector gpio_ports_to_add; /* Iterate over the child modules */ @@ -951,7 +971,7 @@ void add_module_gpio_ports_from_child_modules(ModuleManager& module_manager, /* Iterate over the child instances */ for (size_t i = 0; i < module_manager.num_instance(module_id, child); ++i) { /* Find all the global ports, whose port type is special */ - for (BasicPort gpio_port : module_manager.module_ports_by_type(child, ModuleManager::MODULE_GPIO_PORT)) { + for (BasicPort gpio_port : module_manager.module_ports_by_type(child, module_port_type)) { /* If this port is not mergeable, we update the list */ bool is_mergeable = false; for (BasicPort& gpio_port_to_add : gpio_ports_to_add) { @@ -979,7 +999,7 @@ void add_module_gpio_ports_from_child_modules(ModuleManager& module_manager, std::vector gpio_port_ids; /* Add the gpio ports for the module */ for (const BasicPort& gpio_port_to_add : gpio_ports_to_add) { - ModulePortId port_id = module_manager.add_port(module_id, gpio_port_to_add, ModuleManager::MODULE_GPIO_PORT); + ModulePortId port_id = module_manager.add_port(module_id, gpio_port_to_add, module_port_type); gpio_port_ids.push_back(port_id); } @@ -990,7 +1010,7 @@ void add_module_gpio_ports_from_child_modules(ModuleManager& module_manager, /* Iterate over the child instances */ for (const size_t& child_instance : module_manager.child_module_instances(module_id, child)) { /* Find all the global ports, whose port type is special */ - for (ModulePortId child_gpio_port_id : module_manager.module_port_ids_by_type(child, ModuleManager::MODULE_GPIO_PORT)) { + for (ModulePortId child_gpio_port_id : module_manager.module_port_ids_by_type(child, module_port_type)) { BasicPort child_gpio_port = module_manager.module_port(child, child_gpio_port_id); /* Find the port with the same name! */ for (size_t iport = 0; iport < gpio_ports_to_add.size(); ++iport) { @@ -1001,8 +1021,22 @@ void add_module_gpio_ports_from_child_modules(ModuleManager& module_manager, for (const size_t& pin_id : child_gpio_port.pins()) { /* Reach here, it means this is the port we want, create a net and configure its source and sink */ ModuleNetId net = module_manager.create_module_net(module_id); - module_manager.add_module_net_source(module_id, net, module_id, 0, gpio_port_ids[iport], gpio_port_lsb[iport]); - module_manager.add_module_net_sink(module_id, net, child, child_instance, child_gpio_port_id, pin_id); + /* - For GPIO and GPIN ports + * the source of the net is the current module + * the sink of the net is the child module + * - For GPOUT ports + * the source of the net is the child module + * the sink of the net is the current module + */ + if ( (ModuleManager::MODULE_GPIO_PORT == module_port_type) + || (ModuleManager::MODULE_GPIN_PORT == module_port_type) ) { + module_manager.add_module_net_source(module_id, net, module_id, 0, gpio_port_ids[iport], gpio_port_lsb[iport]); + module_manager.add_module_net_sink(module_id, net, child, child_instance, child_gpio_port_id, pin_id); + } else { + VTR_ASSERT(ModuleManager::MODULE_GPOUT_PORT == module_port_type); + module_manager.add_module_net_sink(module_id, net, module_id, 0, gpio_port_ids[iport], gpio_port_lsb[iport]); + module_manager.add_module_net_source(module_id, net, child, child_instance, child_gpio_port_id, pin_id); + } /* Update the LSB counter */ gpio_port_lsb[iport]++; } @@ -1020,6 +1054,27 @@ void add_module_gpio_ports_from_child_modules(ModuleManager& module_manager, } } +/******************************************************************** + * Add GPIO ports to the module: + * In this function, the following tasks are done: + * 1. find all the GPIO ports from the child modules and build a list of it, + * 2. Merge all the GPIO ports with the same name + * 3. add the ports to the pb_module + * 4. add module nets to connect to the GPIO ports of each sub module + * + * Note: This function should be call ONLY after all the sub modules (instances) + * have been added to the pb_module! + * Otherwise, some GPIO ports of the sub modules may be missed! + *******************************************************************/ +void add_module_gpio_ports_from_child_modules(ModuleManager& module_manager, + const ModuleId& module_id) { + add_module_io_ports_from_child_modules(module_manager, module_id, ModuleManager::MODULE_GPIO_PORT); + + add_module_io_ports_from_child_modules(module_manager, module_id, ModuleManager::MODULE_GPIN_PORT); + + add_module_io_ports_from_child_modules(module_manager, module_id, ModuleManager::MODULE_GPOUT_PORT); +} + /******************************************************************** * Add global input ports to the module: * In this function, the following tasks are done: @@ -1100,112 +1155,6 @@ void add_module_global_input_ports_from_child_modules(ModuleManager& module_mana } } -/******************************************************************** - * Add global output ports to the module: - * In this function, the following tasks are done: - * 1. find all the global output ports from the child modules and build a list of it, - * 2. add the output ports to the pb_module - * 3. add the module nets to connect the pb_module global ports to those of child modules - * - * Module - * ----------------------+ - * | - * child[0] | - * -----------+ | - * |----------+----> outputA[0] - * -----------+ | - * | - * child[1] | - * -----------+ | - * |----------+----> outputA[1] - * -----------+ | - * - * Note: This function should be call ONLY after all the sub modules (instances) - * have been added to the pb_module! - * Otherwise, some global ports of the sub modules may be missed! - *******************************************************************/ -void add_module_global_output_ports_from_child_modules(ModuleManager& module_manager, - const ModuleId& module_id) { - std::vector global_ports_to_add; - std::vector global_port_names; - - /* Iterate over the child modules */ - for (const ModuleId& child : module_manager.child_modules(module_id)) { - /* Iterate over the child instances */ - for (size_t i = 0; i < module_manager.num_instance(module_id, child); ++i) { - /* Find all the global ports, whose port type is special */ - for (BasicPort global_port : module_manager.module_ports_by_type(child, ModuleManager::MODULE_SPY_PORT)) { - /* Search in the global port list to be added, if this is unique, we update the list */ - std::vector::iterator it = std::find(global_port_names.begin(), global_port_names.end(), global_port.get_name()); - if (it != global_port_names.end()) { - /* Found in the global port with the same name, increase the port size */ - global_ports_to_add[it - global_port_names.begin()].expand(global_port.get_width()); - continue; /* Finish for the port already in the list */ - } - /* Reach here, this is an unique global port, update the list */ - global_ports_to_add.push_back(global_port); - global_port_names.push_back(global_port.get_name()); - } - } - } - - /* Record the port id for each type of global port */ - std::vector global_port_ids; - /* Add the global ports for the module */ - for (const BasicPort& global_port_to_add : global_ports_to_add) { - ModulePortId port_id = module_manager.add_port(module_id, global_port_to_add, ModuleManager::MODULE_SPY_PORT); - global_port_ids.push_back(port_id); - } - - /* Add module nets to connect the global ports of the module to the global ports of the sub module */ - /* Create a counter for each global port to record the current LSB */ - std::vector global_port_lsbs(global_port_ids.size(), 0); - - /* Iterate over the child modules */ - for (const ModuleId& child : module_manager.child_modules(module_id)) { - /* Iterate over the child instances */ - for (const size_t& child_instance : module_manager.child_module_instances(module_id, child)) { - /* Find all the global ports, whose port type is special */ - for (ModulePortId child_global_port_id : module_manager.module_port_ids_by_type(child, ModuleManager::MODULE_SPY_PORT)) { - /* Find the global port from the child module */ - BasicPort child_global_port = module_manager.module_port(child, child_global_port_id); - /* Search in the global port list to be added, find the port id */ - std::vector::iterator it = std::find(global_port_names.begin(), global_port_names.end(), child_global_port.get_name()); - VTR_ASSERT(it != global_port_names.end()); - - /* Find the global port from the parent module */ - size_t module_global_port_offset = it - global_port_names.begin(); - ModulePortId module_global_port_id = global_port_ids[module_global_port_offset]; - BasicPort module_global_port = module_manager.module_port(module_id, module_global_port_id); - /* Current LSB should be in range */ - VTR_ASSERT(module_global_port.get_width() > global_port_lsbs[module_global_port_offset]); - /* Set the global port from the parent module as the LSB recorded */ - module_global_port.set_width(global_port_lsbs[module_global_port_offset], global_port_lsbs[module_global_port_offset] + child_global_port.get_width() - 1); - /* Update the LSB */ - global_port_lsbs[module_global_port_offset] += child_global_port.get_width(); - - /* The global ports should match in size */ - VTR_ASSERT(module_global_port.get_width() == child_global_port.get_width()); - /* For each pin of the child port, create a net and do wiring */ - for (size_t pin_id = 0; pin_id < child_global_port.pins().size(); ++pin_id) { - /* Reach here, it means this is the port we want, create a net and configure its source and sink */ - ModuleNetId net = module_manager.create_module_net(module_id); - module_manager.add_module_net_source(module_id, net, child, child_instance, child_global_port_id, child_global_port.pins()[pin_id]); - module_manager.add_module_net_sink(module_id, net, module_id, 0, module_global_port_id, module_global_port.pins()[pin_id]); - /* We finish for this child gpio port */ - } - } - } - } - - /* Find check: all the LSBs of global ports should match the MSB */ - for (size_t iport = 0; iport < global_port_ids.size(); ++iport) { - BasicPort module_global_port = module_manager.module_port(module_id, global_port_ids[iport]); - if (module_global_port.get_width() != global_port_lsbs[iport]) - VTR_ASSERT(module_global_port.get_width() == global_port_lsbs[iport]); - } -} - /******************************************************************** * Add global ports to the module: * In this function, we will add global input ports and global output ports @@ -1228,9 +1177,6 @@ void add_module_global_ports_from_child_modules(ModuleManager& module_manager, const ModuleId& module_id) { /* Input ports */ add_module_global_input_ports_from_child_modules(module_manager, module_id); - - /* Output ports */ - add_module_global_output_ports_from_child_modules(module_manager, module_id); } /******************************************************************** diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml index 9d81c5b3e..b8f5fc249 100644 --- a/openfpga/test_openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml @@ -181,6 +181,8 @@ + + From bcb86801faf0ac0022b0d5fa7dbf564217d8defa Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 5 Apr 2020 17:26:44 -0600 Subject: [PATCH 373/645] bug fixed in gpio naming for module manager ports --- openfpga/src/base/openfpga_naming.cpp | 5 +- openfpga/src/base/openfpga_naming.h | 3 +- openfpga/src/fabric/build_grid_modules.cpp | 68 ++++++++++++++++--- openfpga/src/utils/module_manager_utils.cpp | 2 +- .../k6_frac_N10_spyio_40nm_openfpga.xml | 2 +- 5 files changed, 65 insertions(+), 15 deletions(-) diff --git a/openfpga/src/base/openfpga_naming.cpp b/openfpga/src/base/openfpga_naming.cpp index 530db8cc6..99d59afdb 100644 --- a/openfpga/src/base/openfpga_naming.cpp +++ b/openfpga/src/base/openfpga_naming.cpp @@ -1339,10 +1339,13 @@ std::string generate_pb_type_port_name(t_port* pb_type_port) { ********************************************************************/ std::string generate_fpga_global_io_port_name(const std::string& prefix, const CircuitLibrary& circuit_lib, - const CircuitModelId& circuit_model) { + const CircuitModelId& circuit_model, + const CircuitPortId& circuit_port) { std::string port_name(prefix); port_name += circuit_lib.model_name(circuit_model); + port_name += std::string("_"); + port_name += circuit_lib.port_prefix(circuit_port); return port_name; } diff --git a/openfpga/src/base/openfpga_naming.h b/openfpga/src/base/openfpga_naming.h index e4f1ee8f8..40d7f05e9 100644 --- a/openfpga/src/base/openfpga_naming.h +++ b/openfpga/src/base/openfpga_naming.h @@ -242,7 +242,8 @@ std::string generate_pb_type_port_name(t_port* pb_type_port); std::string generate_fpga_global_io_port_name(const std::string& prefix, const CircuitLibrary& circuit_lib, - const CircuitModelId& circuit_model); + const CircuitModelId& circuit_model, + const CircuitPortId& circuit_port); std::string generate_fpga_top_module_name(); diff --git a/openfpga/src/fabric/build_grid_modules.cpp b/openfpga/src/fabric/build_grid_modules.cpp index e55640834..741632d1c 100644 --- a/openfpga/src/fabric/build_grid_modules.cpp +++ b/openfpga/src/fabric/build_grid_modules.cpp @@ -144,6 +144,38 @@ void add_grid_module_nets_connect_pb_type_ports(ModuleManager& module_manager, } } +/******************************************************************** + *******************************************************************/ +static +void add_primitive_module_fpga_global_io_port(ModuleManager& module_manager, + const ModuleId& primitive_module, + const ModuleId& logic_module, + const size_t& logic_instance_id, + const ModuleManager::e_module_port_type& module_io_port_type, + const CircuitLibrary& circuit_lib, + const CircuitModelId& primitive_model, + const CircuitPortId& circuit_port) { + BasicPort module_port(generate_fpga_global_io_port_name(std::string(GIO_INOUT_PREFIX), circuit_lib, primitive_model, circuit_port), circuit_lib.port_size(circuit_port)); + ModulePortId primitive_io_port_id = module_manager.add_port(primitive_module, module_port, module_io_port_type); + ModulePortId logic_io_port_id = module_manager.find_module_port(logic_module, circuit_lib.port_prefix(circuit_port)); + BasicPort logic_io_port = module_manager.module_port(logic_module, logic_io_port_id); + VTR_ASSERT(logic_io_port.get_width() == module_port.get_width()); + + /* Wire the GPIO port form primitive_module to the logic module!*/ + for (size_t pin_id = 0; pin_id < module_port.pins().size(); ++pin_id) { + ModuleNetId net = module_manager.create_module_net(primitive_module); + if ( (ModuleManager::MODULE_GPIO_PORT == module_io_port_type) + || (ModuleManager::MODULE_GPIN_PORT == module_io_port_type) ) { + module_manager.add_module_net_source(primitive_module, net, primitive_module, 0, primitive_io_port_id, module_port.pins()[pin_id]); + module_manager.add_module_net_sink(primitive_module, net, logic_module, logic_instance_id, logic_io_port_id, logic_io_port.pins()[pin_id]); + } else { + VTR_ASSERT(ModuleManager::MODULE_GPOUT_PORT == module_io_port_type); + module_manager.add_module_net_source(primitive_module, net, logic_module, logic_instance_id, logic_io_port_id, logic_io_port.pins()[pin_id]); + module_manager.add_module_net_sink(primitive_module, net, primitive_module, 0, primitive_io_port_id, module_port.pins()[pin_id]); + } + } +} + /******************************************************************** * Print Verilog modules of a primitive node in the pb_graph_node graph * This generic function can support all the different types of primitive nodes @@ -275,18 +307,32 @@ void build_primitive_block_module(ModuleManager& module_manager, if (CIRCUIT_MODEL_IOPAD == circuit_lib.model_type(primitive_model)) { std::vector primitive_model_inout_ports = circuit_lib.model_ports_by_type(primitive_model, CIRCUIT_MODEL_PORT_INOUT); for (auto port : primitive_model_inout_ports) { - BasicPort module_port(generate_fpga_global_io_port_name(std::string(GIO_INOUT_PREFIX), circuit_lib, primitive_model), circuit_lib.port_size(port)); - ModulePortId primitive_gpio_port_id = module_manager.add_port(primitive_module, module_port, ModuleManager::MODULE_GPIO_PORT); - ModulePortId logic_gpio_port_id = module_manager.find_module_port(logic_module, circuit_lib.port_prefix(port)); - BasicPort logic_gpio_port = module_manager.module_port(logic_module, logic_gpio_port_id); - VTR_ASSERT(logic_gpio_port.get_width() == module_port.get_width()); + add_primitive_module_fpga_global_io_port(module_manager, primitive_module, + logic_module, logic_instance_id, + ModuleManager::MODULE_GPIO_PORT, + circuit_lib, + primitive_model, + port); + } + } - /* Wire the GPIO port form primitive_module to the logic module!*/ - for (size_t pin_id = 0; pin_id < module_port.pins().size(); ++pin_id) { - ModuleNetId net = module_manager.create_module_net(primitive_module); - module_manager.add_module_net_source(primitive_module, net, primitive_module, 0, primitive_gpio_port_id, module_port.pins()[pin_id]); - module_manager.add_module_net_sink(primitive_module, net, logic_module, logic_instance_id, logic_gpio_port_id, logic_gpio_port.pins()[pin_id]); - } + /* Find the other i/o ports required by the primitive node, and add them to the module */ + for (const auto& port : circuit_lib.model_global_ports(primitive_model, false)) { + if ( (CIRCUIT_MODEL_PORT_INPUT == circuit_lib.port_type(port)) + && (true == circuit_lib.port_is_io(port)) ) { + add_primitive_module_fpga_global_io_port(module_manager, primitive_module, + logic_module, logic_instance_id, + ModuleManager::MODULE_GPIN_PORT, + circuit_lib, + primitive_model, + port); + } else if (CIRCUIT_MODEL_PORT_OUTPUT == circuit_lib.port_type(port)) { + add_primitive_module_fpga_global_io_port(module_manager, primitive_module, + logic_module, logic_instance_id, + ModuleManager::MODULE_GPOUT_PORT, + circuit_lib, + primitive_model, + port); } } diff --git a/openfpga/src/utils/module_manager_utils.cpp b/openfpga/src/utils/module_manager_utils.cpp index 3c1ef2de2..64bef2e88 100644 --- a/openfpga/src/utils/module_manager_utils.cpp +++ b/openfpga/src/utils/module_manager_utils.cpp @@ -48,7 +48,7 @@ ModuleId add_circuit_model_to_module_manager(ModuleManager& module_manager, } else if (CIRCUIT_MODEL_PORT_CLOCK == circuit_lib.port_type(port)) { module_manager.add_port(module, port_info, ModuleManager::MODULE_GLOBAL_PORT); } else if ( (CIRCUIT_MODEL_PORT_INPUT == circuit_lib.port_type(port)) - && (false == circuit_lib.port_is_io(port)) ) { + && (true == circuit_lib.port_is_io(port)) ) { module_manager.add_port(module, port_info, ModuleManager::MODULE_GPIN_PORT); } else { VTR_ASSERT(CIRCUIT_MODEL_PORT_OUTPUT == circuit_lib.port_type(port)); diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml index b8f5fc249..2b432ce86 100644 --- a/openfpga/test_openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml @@ -181,7 +181,7 @@ - + From decc1dc4b25fa88a0bc69a88d9b9e32a61f8abcf Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 5 Apr 2020 17:39:30 -0600 Subject: [PATCH 374/645] debugged global gp input/output port support --- openfpga/src/fpga_sdc/analysis_sdc_writer.cpp | 11 +++++++++++ .../src/fpga_verilog/verilog_top_testbench.cpp | 17 ++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/openfpga/src/fpga_sdc/analysis_sdc_writer.cpp b/openfpga/src/fpga_sdc/analysis_sdc_writer.cpp index aec9fc2e7..c188dedd8 100644 --- a/openfpga/src/fpga_sdc/analysis_sdc_writer.cpp +++ b/openfpga/src/fpga_sdc/analysis_sdc_writer.cpp @@ -192,6 +192,17 @@ void print_analysis_sdc_disable_global_ports(std::fstream& fp, continue; } + /* Skip any gpio port here! */ + if ( (CIRCUIT_MODEL_PORT_INPUT == circuit_lib.port_type(global_port)) + && (true == circuit_lib.port_is_io(global_port)) ) { + continue; + } + + /* Skip any gpio port here! */ + if (CIRCUIT_MODEL_PORT_OUTPUT == circuit_lib.port_type(global_port)) { + continue; + } + ModulePortId module_port = module_manager.find_module_port(top_module, circuit_lib.port_prefix(global_port)); BasicPort port_to_disable = module_manager.module_port(top_module, module_port); diff --git a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp index 911c6defb..95f88bf92 100644 --- a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp +++ b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp @@ -18,6 +18,7 @@ #include "bitstream_manager_utils.h" +#include "openfpga_reserved_words.h" #include "openfpga_naming.h" #include "simulation_utils.h" #include "openfpga_atom_netlist_utils.h" @@ -272,9 +273,23 @@ void print_verilog_top_testbench_global_ports_stimuli(std::fstream& fp, continue; } + /* Bypass gp output signals, they do not need any drivers */ + if (CIRCUIT_MODEL_PORT_OUTPUT == circuit_lib.port_type(model_global_port)) { + continue; + } + + /* Find the port name, gpio port has special names */ + std::string port_name; + if (true == circuit_lib.port_is_io(model_global_port)) { + port_name = generate_fpga_global_io_port_name(std::string(GIO_INOUT_PREFIX), circuit_lib, circuit_lib.port_parent_model(model_global_port), model_global_port); + } else { + VTR_ASSERT_SAFE(false == circuit_lib.port_is_io(model_global_port)); + port_name = circuit_lib.port_prefix(model_global_port); + } + /* Reach here, it means we have a port to deal with */ /* Find the module port and wire it to constant values */ - ModulePortId module_global_port = module_manager.find_module_port(top_module, circuit_lib.port_prefix(model_global_port)); + ModulePortId module_global_port = module_manager.find_module_port(top_module, port_name); VTR_ASSERT(true == module_manager.valid_module_port_id(top_module, module_global_port)); BasicPort module_port = module_manager.module_port(top_module, module_global_port); From 3369d724e9c9a066cb870aef38735ed171d48b0b Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 5 Apr 2020 17:50:11 -0600 Subject: [PATCH 375/645] bug fixing in Verilog top-level testbench generation --- openfpga/src/fpga_verilog/verilog_top_testbench.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp index 95f88bf92..98d69c337 100644 --- a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp +++ b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp @@ -353,6 +353,18 @@ void print_verilog_top_testbench_ports(std::fstream& fp, /* Add an empty line as a splitter */ fp << std::endl; + for (const BasicPort& module_port : module_manager.module_ports_by_type(top_module, ModuleManager::MODULE_GPIN_PORT)) { + fp << generate_verilog_port(VERILOG_PORT_WIRE, module_port) << ";" << std::endl; + } + /* Add an empty line as a splitter */ + fp << std::endl; + + for (const BasicPort& module_port : module_manager.module_ports_by_type(top_module, ModuleManager::MODULE_GPOUT_PORT)) { + fp << generate_verilog_port(VERILOG_PORT_WIRE, module_port) << ";" << std::endl; + } + /* Add an empty line as a splitter */ + fp << std::endl; + /* Add local wires/registers that drive stimulus * We create these general purpose ports here, * and then wire them to the ports of FPGA fabric depending on their usage From 1a3a748dd2d6b8242f9a0a99e722577c9c815f0b Mon Sep 17 00:00:00 2001 From: Xifan Tang Date: Sun, 5 Apr 2020 20:12:28 -0600 Subject: [PATCH 376/645] update documentation with the support on spypads and global I/O ports --- docs/source/arch_lang/circuit_library.rst | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/source/arch_lang/circuit_library.rst b/docs/source/arch_lang/circuit_library.rst index 52d906fea..2cab83abd 100644 --- a/docs/source/arch_lang/circuit_library.rst +++ b/docs/source/arch_lang/circuit_library.rst @@ -270,11 +270,20 @@ A circuit model may consist of a number of ports. The port list is mandatory in .. note:: ``circuit_model_name`` is only valid when the type of this port is ``sram``. + - ``io="true|false"`` Specify if this port should be treated as an I/O port of an FPGA fabric. When this is enabled, this port of each circuit model instanciated in FPGA will be added as an I/O of an FPGA. + + .. note:: ``io`` is only valid for ``input`` ports + - ``mode_select="true|false"`` Specify if this port controls the mode switching in a configurable logic block. This is due to that a configurable logic block can operate in different modes, which is controlled by SRAM bits. .. note:: ``mode_select`` is only valid when the type of this port is ``sram``. - - ``is_global="true|false"`` can be either ``true`` or ``false``. Specify if this port is a global port, which will be routed globally. Note that when multiple global ports are defined with the same name, these global ports will be short-wired together. + - ``is_global="true|false"`` can be either ``true`` or ``false``. Specify if this port is a global port, which will be routed globally. + + .. note:: For input ports, when multiple global input ports are defined with the same name, by default, these global ports will be short-wired together. When ``io`` is turned on for this port, these global ports will be independent in the FPGA fabric. + + .. note:: For output ports, the global ports will be independent in the FPGA fabric + - ``is_set="true|false"`` Specify if this port controls a set signal. All the set ports are connected to global set voltage stimuli in testbenches. From eb3b02277a6b382fe0dd182f39a200cf23dfcbd3 Mon Sep 17 00:00:00 2001 From: ganeshgore Date: Mon, 6 Apr 2020 00:32:06 -0600 Subject: [PATCH 377/645] Added XML and benchmarks for testing --- .../arch/vpr_only_templates/k6_N10_40nm.xml | 299 +++++++ .../k6_N10_tileable_40nm.xml | 299 +++++++ .../vpr_only_templates/k6_frac_N10_40nm.xml | 441 ++++++++++ .../k6_frac_N10_adder_chain_40nm.xml | 639 ++++++++++++++ .../k6_frac_N10_adder_chain_mem16K_40nm.xml | 734 ++++++++++++++++ .../k6_frac_N10_tileable_40nm.xml | 441 ++++++++++ .../k6_frac_N10_tileable_adder_chain_40nm.xml | 639 ++++++++++++++ ...c_N10_tileable_adder_chain_mem16K_40nm.xml | 734 ++++++++++++++++ ...0_tileable_adder_chain_mem16K_aib_40nm.xml | 800 ++++++++++++++++++ ...er_chain_mem16K_multi_io_capacity_40nm.xml | 768 +++++++++++++++++ ...ble_adder_chain_mem16K_reduced_io_40nm.xml | 737 ++++++++++++++++ ..._tileable_adder_chain_wide_mem16K_40nm.xml | 734 ++++++++++++++++ ...e_thru_channel_adder_chain_mem16K_40nm.xml | 734 ++++++++++++++++ .../benchmarks/micro_benchmark/and.act | 3 + .../benchmarks/micro_benchmark/and.blif | 8 + .../benchmarks/micro_benchmark/and.v | 14 + .../benchmarks/micro_benchmark/and_latch.act | 6 + .../benchmarks/micro_benchmark/and_latch.blif | 14 + .../benchmarks/micro_benchmark/and_latch.v | 23 + .../openfpga_arch/k6_N10_40nm_openfpga.xml | 234 +++++ .../k6_frac_N10_40nm_openfpga.xml | 268 ++++++ .../k6_frac_N10_adder_chain_40nm_openfpga.xml | 292 +++++++ ...c_N10_adder_chain_mem16K_40nm_openfpga.xml | 310 +++++++ ...0_adder_chain_mem16K_aib_40nm_openfpga.xml | 323 +++++++ .../k6_frac_N10_spyio_40nm_openfpga.xml | 270 ++++++ 25 files changed, 9764 insertions(+) create mode 100644 openfpga_flow/arch/vpr_only_templates/k6_N10_40nm.xml create mode 100644 openfpga_flow/arch/vpr_only_templates/k6_N10_tileable_40nm.xml create mode 100644 openfpga_flow/arch/vpr_only_templates/k6_frac_N10_40nm.xml create mode 100644 openfpga_flow/arch/vpr_only_templates/k6_frac_N10_adder_chain_40nm.xml create mode 100644 openfpga_flow/arch/vpr_only_templates/k6_frac_N10_adder_chain_mem16K_40nm.xml create mode 100644 openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml create mode 100644 openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_40nm.xml create mode 100644 openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_40nm.xml create mode 100644 openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_aib_40nm.xml create mode 100644 openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_multi_io_capacity_40nm.xml create mode 100644 openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_reduced_io_40nm.xml create mode 100644 openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_wide_mem16K_40nm.xml create mode 100644 openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_thru_channel_adder_chain_mem16K_40nm.xml create mode 100644 openfpga_flow/benchmarks/micro_benchmark/and.act create mode 100644 openfpga_flow/benchmarks/micro_benchmark/and.blif create mode 100644 openfpga_flow/benchmarks/micro_benchmark/and.v create mode 100644 openfpga_flow/benchmarks/micro_benchmark/and_latch.act create mode 100644 openfpga_flow/benchmarks/micro_benchmark/and_latch.blif create mode 100644 openfpga_flow/benchmarks/micro_benchmark/and_latch.v create mode 100644 openfpga_flow/openfpga_arch/k6_N10_40nm_openfpga.xml create mode 100644 openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml create mode 100644 openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml create mode 100644 openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml create mode 100644 openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml create mode 100644 openfpga_flow/openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml diff --git a/openfpga_flow/arch/vpr_only_templates/k6_N10_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_N10_40nm.xml new file mode 100644 index 000000000..630011e84 --- /dev/null +++ b/openfpga_flow/arch/vpr_only_templates/k6_N10_40nm.xml @@ -0,0 +1,299 @@ + + + + + + + + + + + + + + + + + + + + + + + + + io.outpad io.inpad io.clock + io.outpad io.inpad io.clock + io.outpad io.inpad io.clock + io.outpad io.inpad io.clock + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/arch/vpr_only_templates/k6_N10_tileable_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_N10_tileable_40nm.xml new file mode 100644 index 000000000..d86d7aa62 --- /dev/null +++ b/openfpga_flow/arch/vpr_only_templates/k6_N10_tileable_40nm.xml @@ -0,0 +1,299 @@ + + + + + + + + + + + + + + + + + + + + + + + + + io.outpad io.inpad io.clock + io.outpad io.inpad io.clock + io.outpad io.inpad io.clock + io.outpad io.inpad io.clock + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_40nm.xml new file mode 100644 index 000000000..aa9ca9d98 --- /dev/null +++ b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_40nm.xml @@ -0,0 +1,441 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 235e-12 + 235e-12 + 235e-12 + 235e-12 + 235e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_adder_chain_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_adder_chain_40nm.xml new file mode 100644 index 000000000..bbe041562 --- /dev/null +++ b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_adder_chain_40nm.xml @@ -0,0 +1,639 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + + + + + + + + + + + + + + + + + + + clb.clk + clb.cin + clb.O[9:0] clb.I[19:0] + clb.cout clb.O[19:10] clb.I[39:20] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 235e-12 + 235e-12 + 235e-12 + 235e-12 + 235e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 195e-12 + 195e-12 + 195e-12 + 195e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_adder_chain_mem16K_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_adder_chain_mem16K_40nm.xml new file mode 100644 index 000000000..850eb5450 --- /dev/null +++ b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_adder_chain_mem16K_40nm.xml @@ -0,0 +1,734 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + + + + + + + + + + + + + + + + + + + clb.clk + clb.cin + clb.O[9:0] clb.I[19:0] + clb.cout clb.O[19:10] clb.I[39:20] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 235e-12 + 235e-12 + 235e-12 + 235e-12 + 235e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 195e-12 + 195e-12 + 195e-12 + 195e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml new file mode 100644 index 000000000..2c528c13c --- /dev/null +++ b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml @@ -0,0 +1,441 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 235e-12 + 235e-12 + 235e-12 + 235e-12 + 235e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_40nm.xml new file mode 100644 index 000000000..eea76837f --- /dev/null +++ b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_40nm.xml @@ -0,0 +1,639 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + + + + + + + + + + + + + + + + + + + clb.clk + clb.cin + clb.O[9:0] clb.I[19:0] + clb.cout clb.O[19:10] clb.I[39:20] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 235e-12 + 235e-12 + 235e-12 + 235e-12 + 235e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 195e-12 + 195e-12 + 195e-12 + 195e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_40nm.xml new file mode 100644 index 000000000..9422c3a09 --- /dev/null +++ b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_40nm.xml @@ -0,0 +1,734 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + + + + + + + + + + + + + + + + + + + clb.clk + clb.cin + clb.O[9:0] clb.I[19:0] + clb.cout clb.O[19:10] clb.I[39:20] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 235e-12 + 235e-12 + 235e-12 + 235e-12 + 235e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 195e-12 + 195e-12 + 195e-12 + 195e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_aib_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_aib_40nm.xml new file mode 100644 index 000000000..e196cb83c --- /dev/null +++ b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_aib_40nm.xml @@ -0,0 +1,800 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + + + + + + + + + + + + + + aib.tx_clk aib.tx_data aib.rx_clk aib.rx_data + + + + + + + + + + + + + + + + + + + clb.clk + clb.cin + clb.O[9:0] clb.I[19:0] + clb.cout clb.O[19:10] clb.I[39:20] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 235e-12 + 235e-12 + 235e-12 + 235e-12 + 235e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 195e-12 + 195e-12 + 195e-12 + 195e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_multi_io_capacity_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_multi_io_capacity_40nm.xml new file mode 100644 index 000000000..8eb6f32bf --- /dev/null +++ b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_multi_io_capacity_40nm.xml @@ -0,0 +1,768 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + io_top.outpad io_top.inpad + + + + + + + + + + + io_right.outpad io_right.inpad + + + + + + + + + + + io_bottom.outpad io_bottom.inpad + + + + + + + + + + + io_left.outpad io_left.inpad + + + + + + + + + + + + + + + + + + + + clb.clk + clb.cin + clb.O[9:0] clb.I[19:0] + clb.cout clb.O[19:10] clb.I[39:20] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 235e-12 + 235e-12 + 235e-12 + 235e-12 + 235e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 195e-12 + 195e-12 + 195e-12 + 195e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_reduced_io_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_reduced_io_40nm.xml new file mode 100644 index 000000000..6f8c4cfae --- /dev/null +++ b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_reduced_io_40nm.xml @@ -0,0 +1,737 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + + + + + + + + + + + + + + + + + + + clb.clk + clb.cin + clb.O[9:0] clb.I[19:0] + clb.cout clb.O[19:10] clb.I[39:20] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 235e-12 + 235e-12 + 235e-12 + 235e-12 + 235e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 195e-12 + 195e-12 + 195e-12 + 195e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_wide_mem16K_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_wide_mem16K_40nm.xml new file mode 100644 index 000000000..f07f16c80 --- /dev/null +++ b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_wide_mem16K_40nm.xml @@ -0,0 +1,734 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + + + + + + + + + + + + + + + + + + + clb.clk + clb.cin + clb.O[9:0] clb.I[19:0] + clb.cout clb.O[19:10] clb.I[39:20] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 235e-12 + 235e-12 + 235e-12 + 235e-12 + 235e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 195e-12 + 195e-12 + 195e-12 + 195e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_thru_channel_adder_chain_mem16K_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_thru_channel_adder_chain_mem16K_40nm.xml new file mode 100644 index 000000000..7e4cf8e7a --- /dev/null +++ b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_thru_channel_adder_chain_mem16K_40nm.xml @@ -0,0 +1,734 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + + + + + + + + + + + + + + + + + + + clb.clk + clb.cin + clb.O[9:0] clb.I[19:0] + clb.cout clb.O[19:10] clb.I[39:20] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 235e-12 + 235e-12 + 235e-12 + 235e-12 + 235e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 195e-12 + 195e-12 + 195e-12 + 195e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/benchmarks/micro_benchmark/and.act b/openfpga_flow/benchmarks/micro_benchmark/and.act new file mode 100644 index 000000000..0f77bc6b3 --- /dev/null +++ b/openfpga_flow/benchmarks/micro_benchmark/and.act @@ -0,0 +1,3 @@ +a 0.5 0.5 +b 0.5 0.5 +c 0.25 0.25 diff --git a/openfpga_flow/benchmarks/micro_benchmark/and.blif b/openfpga_flow/benchmarks/micro_benchmark/and.blif new file mode 100644 index 000000000..67d978741 --- /dev/null +++ b/openfpga_flow/benchmarks/micro_benchmark/and.blif @@ -0,0 +1,8 @@ +.model top +.inputs a b +.outputs c + +.names a b c +11 1 + +.end diff --git a/openfpga_flow/benchmarks/micro_benchmark/and.v b/openfpga_flow/benchmarks/micro_benchmark/and.v new file mode 100644 index 000000000..876f1c6fe --- /dev/null +++ b/openfpga_flow/benchmarks/micro_benchmark/and.v @@ -0,0 +1,14 @@ +`timescale 1ns / 1ps + +module top( + a, + b, + c); + +input wire a; +input wire b; +output wire c; + +assign c = a & b; + +endmodule diff --git a/openfpga_flow/benchmarks/micro_benchmark/and_latch.act b/openfpga_flow/benchmarks/micro_benchmark/and_latch.act new file mode 100644 index 000000000..61bbe1fe8 --- /dev/null +++ b/openfpga_flow/benchmarks/micro_benchmark/and_latch.act @@ -0,0 +1,6 @@ +a 0.492800 0.201000 +b 0.502000 0.197200 +clk 0.500000 2.000000 +d 0.240200 0.171200 +c 0.240200 0.044100 +n1 0.240200 0.044100 diff --git a/openfpga_flow/benchmarks/micro_benchmark/and_latch.blif b/openfpga_flow/benchmarks/micro_benchmark/and_latch.blif new file mode 100644 index 000000000..dbd863d9c --- /dev/null +++ b/openfpga_flow/benchmarks/micro_benchmark/and_latch.blif @@ -0,0 +1,14 @@ +# Benchmark "top" written by ABC on Wed Mar 11 10:36:28 2020 +.model top +.inputs a b clk +.outputs c d + +.latch n1 d re clk 0 + +.names a b c +11 1 + +.names c n1 +1 1 + +.end diff --git a/openfpga_flow/benchmarks/micro_benchmark/and_latch.v b/openfpga_flow/benchmarks/micro_benchmark/and_latch.v new file mode 100644 index 000000000..893cdf7a4 --- /dev/null +++ b/openfpga_flow/benchmarks/micro_benchmark/and_latch.v @@ -0,0 +1,23 @@ +`timescale 1ns / 1ps + +module top( + clk, + a, + b, + c, + d); + +input wire clk; + +input wire a; +input wire b; +output wire c; +output reg d; + +assign c = a & b; + +always @(posedge clk) begin + d <= c; +end + +endmodule diff --git a/openfpga_flow/openfpga_arch/k6_N10_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_N10_40nm_openfpga.xml new file mode 100644 index 000000000..d93495f6b --- /dev/null +++ b/openfpga_flow/openfpga_arch/k6_N10_40nm_openfpga.xml @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + + + + + 10e-12 5e-12 5e-12 + + + 10e-12 5e-12 5e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml new file mode 100644 index 000000000..afb45dc1f --- /dev/null +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml @@ -0,0 +1,268 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + + + + 10e-12 5e-12 + + + 10e-12 5e-12 + + + + + + + + + + + + 10e-12 5e-12 5e-12 + + + 10e-12 5e-12 5e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml new file mode 100644 index 000000000..98823698f --- /dev/null +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml @@ -0,0 +1,292 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + + + + 10e-12 5e-12 + + + 10e-12 5e-12 + + + + + + + + + + + + 10e-12 5e-12 5e-12 + + + 10e-12 5e-12 5e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml new file mode 100644 index 000000000..9f37c1293 --- /dev/null +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml @@ -0,0 +1,310 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + + + + 10e-12 5e-12 + + + 10e-12 5e-12 + + + + + + + + + + + + 10e-12 5e-12 5e-12 + + + 10e-12 5e-12 5e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml new file mode 100644 index 000000000..ea56fe036 --- /dev/null +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml @@ -0,0 +1,323 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + + + + 10e-12 5e-12 + + + 10e-12 5e-12 + + + + + + + + + + + + 10e-12 5e-12 5e-12 + + + 10e-12 5e-12 5e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml new file mode 100644 index 000000000..2b432ce86 --- /dev/null +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml @@ -0,0 +1,270 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + + + + 10e-12 5e-12 + + + 10e-12 5e-12 + + + + + + + + + + + + 10e-12 5e-12 5e-12 + + + 10e-12 5e-12 5e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 7f98ecc8a67820aca775b7af4b6553c2042a3721 Mon Sep 17 00:00:00 2001 From: ganeshgore Date: Mon, 6 Apr 2020 00:32:43 -0600 Subject: [PATCH 378/645] OpenFPGA shell run test script template --- .../example_script.openfpga | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 openfpga_flow/OpenFPGAShellScripts/example_script.openfpga diff --git a/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga new file mode 100644 index 000000000..5a79a3910 --- /dev/null +++ b/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga @@ -0,0 +1,58 @@ +# Run VPR for the 'and' design +#--write_rr_graph example_rr_graph.xml +vpr ${VPR_ARCH_FILE} ${VPR_TESTBENCH_BLIF} --clock_modeling route + +# Read OpenFPGA architecture definition +read_openfpga_arch -f ${OPENFPGA_ARCH_FILE} + +# Annotate the OpenFPGA architecture to VPR data base +# to debug use --verbose options +link_openfpga_arch --activity_file ${ACTIVITY_FILE} --sort_gsb_chan_node_in_edges + +# Check and correct any naming conflicts in the BLIF netlist +check_netlist_naming_conflict --fix --report ./netlist_renaming.xml + +# Apply fix-up to clustering nets based on routing results +pb_pin_fixup --verbose + +# Apply fix-up to Look-Up Table truth tables based on packing results +lut_truth_table_fixup + +# Build the module graph +# - Enabled compression on routing architecture modules +# - Enable pin duplication on grid modules +build_fabric --compress_routing --duplicate_grid_pin #--verbose + +# Repack the netlist to physical pbs +# This must be done before bitstream generator and testbench generation +# Strongly recommend it is done after all the fix-up have been applied +repack #--verbose + +# Build the bitstream +# - Output the fabric-independent bitstream to a file +build_architecture_bitstream --verbose --file fabric_indepenent_bitstream.xml + +# Build fabric-dependent bitstream +build_fabric_bitstream --verbose + +# Write the Verilog netlist for FPGA fabric +# - Enable the use of explicit port mapping in Verilog netlist +write_fabric_verilog --file ./VerilogNetlist/SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose + +# Write the Verilog testbench for FPGA fabric +# - We suggest the use of same output directory as fabric Verilog netlists +# - Must specify the reference benchmark file if you want to output any testbenches +# - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA +# - Enable pre-configured top-level testbench which is a fast verification skipping programming phase +# - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts +write_verilog_testbench --file ./VerilogNetlist/SRC --reference_benchmark_file_path ${REFERENCE_VERILOG_TESTBENCH} --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini ./SimulationDeck/simulation_deck.ini + +# Write the SDC files for PnR backend +# - Turn on every options here +write_pnr_sdc --file ./VerilogNetlist/SDC + +# Write the SDC to run timing analysis for a mapped FPGA fabric +write_analysis_sdc --file ./VerilogNetlist/SDC_analysis + +# Finish and exit OpenFPGA +exit From ea4122a8a426c00284740a782958e988f936b5bf Mon Sep 17 00:00:00 2001 From: ganeshgore Date: Mon, 6 Apr 2020 00:34:36 -0600 Subject: [PATCH 379/645] Updated openfpga_flow and task file to support sheel run --- .../misc/fpgaflow_default_tool_path.conf | 1 + openfpga_flow/scripts/run_fpga_flow.py | 121 ++++++------------ openfpga_flow/scripts/run_fpga_task.py | 7 + 3 files changed, 47 insertions(+), 82 deletions(-) diff --git a/openfpga_flow/misc/fpgaflow_default_tool_path.conf b/openfpga_flow/misc/fpgaflow_default_tool_path.conf index 88d4504ab..5b89052e3 100644 --- a/openfpga_flow/misc/fpgaflow_default_tool_path.conf +++ b/openfpga_flow/misc/fpgaflow_default_tool_path.conf @@ -1,5 +1,6 @@ # Standard Configuration Example [CAD_TOOLS_PATH] +openfpga_shell_path = ${PATH:OPENFPGA_PATH}/openfpga/openfpga yosys_path = ${PATH:OPENFPGA_PATH}/yosys/yosys misc_dir = ${PATH:OPENFPGA_PATH}/openfpga_flow/misc odin2_path = ${PATH:OPENFPGA_PATH}/openfpga_flow/not_used_atm/odin2.exe diff --git a/openfpga_flow/scripts/run_fpga_flow.py b/openfpga_flow/scripts/run_fpga_flow.py index 1d67d3059..cd9a80f81 100644 --- a/openfpga_flow/scripts/run_fpga_flow.py +++ b/openfpga_flow/scripts/run_fpga_flow.py @@ -47,6 +47,7 @@ task_script_dir = os.path.dirname(os.path.abspath(__file__)) script_env_vars = ({"PATH": { "OPENFPGA_FLOW_PATH": task_script_dir, "ARCH_PATH": os.path.join("${PATH:OPENFPGA_PATH}", "arch"), + "OPENFPGA_SHELLSCRIPT_PATH": os.path.join("${PATH:OPENFPGA_PATH}", "OpenFPGAShellScripts"), "BENCH_PATH": os.path.join("${PATH:OPENFPGA_PATH}", "benchmarks"), "TECH_PATH": os.path.join("${PATH:OPENFPGA_PATH}", "tech"), "SPICENETLIST_PATH": os.path.join("${PATH:OPENFPGA_PATH}", "SpiceNetlists"), @@ -78,6 +79,13 @@ parser.add_argument('--flow_config', type=str, parser.add_argument('--run_dir', type=str, default=os.path.join(openfpga_base_dir, 'tmp'), help="Directory to store intermidiate file & final results") +parser.add_argument('--openfpga_shell_template', type=str, + default=os.path.join(openfpga_base_dir, 'openfpga_flow', + 'OpenFPGAShellScripts', + 'example_script.openfpga'), + help="Sample openfpga shell script") +parser.add_argument('--openfpga_arch_file', type=str, + help="Openfpga architecture file for shell") parser.add_argument('--yosys_tmpl', type=str, help="Alternate yosys template, generates top_module.blif") parser.add_argument('--disp', action="store_true", @@ -249,7 +257,10 @@ def main(): # run_abc_vtr() # if (args.fpga_flow == "vtr_standard"): # run_abc_for_standarad() - run_vpr() + if args.openfpga_shell_template: + run_openfpga_shell() + else: + run_vpr() if args.end_flow_with_test: run_netlists_verification() @@ -323,10 +334,10 @@ def validate_command_line_arguments(): clean_up_and_exit("'%s' argument depends on (%s) argumets" % (eacharg, ", ".join(dependent).replace("|", " or "))) - # Filter provided architecrue files + # Filter provided architecture files args.arch_file = os.path.abspath(args.arch_file) if not os.path.isfile(args.arch_file): - clean_up_and_exit("Architecure file not found. -%s", args.arch_file) + clean_up_and_exit("Architecture file not found. -%s", args.arch_file) # Filter provided benchmark files for index, everyinput in enumerate(args.benchmark_files): @@ -579,6 +590,15 @@ def collect_files_for_vpr(): clean_up_and_exit("Provided base_verilog file not found") shutil.copy(args.base_verilog, args.top_module+"_output_verilog.v") + # Sanitize provided openshell template, if provided + if not os.path.isfile(args.openfpga_shell_template or ""): + logger.error("Openfpga shell file - %s" % args.openfpga_shell_template) + clean_up_and_exit("Provided openfpga_shell_template" + + f" {args.openfpga_shell_template} file not found") + else: + shutil.copy(args.openfpga_shell_template, + args.top_module+"_template.openfpga") + def run_vpr(): ExecTime["VPRStart"] = time.time() @@ -646,86 +666,23 @@ def run_vpr(): ExecTime["VPREnd"] = time.time() -def run_openfpga_shell(bench_blif, fixed_chan_width, logfile, route_only=False): - runfile = open("run.openfpga", 'w+'): - command = ["vpr", - args.arch_file, - bench_blif, - "--net_file", args.top_module+"_vpr.net", - "--place_file", args.top_module+"_vpr.place", - "--route_file", args.top_module+"_vpr.route", - "--full_stats", - "--activity_file", args.top_module+"_ace_out.act"] - # Other VPR options - if args.vpr_place_clb_pin_remap: - command += ["--place_clb_pin_remap"] - if args.vpr_route_breadthfirst: - command += ["--router_algorithm", "breadth_first"] - if args.vpr_max_router_iteration: - command += ["--max_router_iterations", args.vpr_max_router_iteration] - runfile.write(" ".join(command)+os.linesep) +def run_openfpga_shell(): + # bench_blif, fixed_chan_width, logfile, route_only=False + tmpl = Template(open(args.top_module+"_template.openfpga", + encoding='utf-8').read()) - command = ["read_openfpga_arch", "-f", args.arch_file] - runfile.write(" ".join(command)+os.linesep) - - command = ["link_openfpga_arch", "--activity_file", - args.top_module+"_ace_out.act"] - runfile.write(" ".join(command)+os.linesep) - - command = ["check_netlist_naming_conflict", - "--fix", "--report", - (args.arch_file).replace(".xml", "_renaming.xml")] - runfile.write(" ".join(command)+os.linesep) - - runfile.write(" ".join(["pb_pin_fixup", "--verbose"])+os.linesep) - runfile.write(" ".join(["lut_truth_table_fixup"])+os.linesep) - - command = ["build_fabric", "--compress_routing", "--duplicate_grid_pin"] - runfile.write(" ".join(command)+os.linesep) - - runfile.write(" ".join(["repack"])+os.linesep) - - if args.vpr_fpga_verilog: - command += ["write_verilog_testbench", "--fpga_verilog"] - if args.vpr_fpga_verilog_dir: - command += ["--fpga_verilog_dir", args.vpr_fpga_verilog_dir] - if args.vpr_fpga_verilog_print_top_tb: - command += ["--fpga_verilog_print_top_testbench"] - if args.vpr_fpga_verilog_print_input_blif_tb: - command += ["--fpga_verilog_print_input_blif_testbench"] - if args.vpr_fpga_verilog_print_autocheck_top_testbench: - command += ["--fpga_verilog_print_autocheck_top_testbench", - os.path.join(args.run_dir, args.top_module+"_output_verilog.v")] - if args.vpr_fpga_verilog_include_timing: - command += ["--fpga_verilog_include_timing"] - if args.vpr_fpga_verilog_explicit_mapping: - command += ["--fpga_verilog_explicit_mapping"] - if args.vpr_fpga_x2p_duplicate_grid_pin: - command += ["--fpga_x2p_duplicate_grid_pin"] - if args.vpr_fpga_verilog_include_signal_init: - command += ["--fpga_verilog_include_signal_init"] - if args.vpr_fpga_verilog_formal_verification_top_netlist: - command += ["--fpga_verilog_print_formal_verification_top_netlist"] - if args.vpr_fpga_verilog_print_simulation_ini: - command += ["--fpga_verilog_print_simulation_ini"] - if args.vpr_fpga_verilog_include_icarus_simulator: - command += ["--fpga_verilog_include_icarus_simulator"] - if args.vpr_fpga_verilog_print_report_timing_tcl: - command += ["--fpga_verilog_print_report_timing_tcl"] - if args.vpr_fpga_verilog_report_timing_rpt_path: - command += ["--fpga_verilog_report_timing_rpt_path", - args.vpr_fpga_verilog_report_timing_rpt_path] - if args.vpr_fpga_verilog_print_sdc_pnr: - command += ["--fpga_verilog_print_sdc_pnr"] - if args.vpr_fpga_verilog_print_user_defined_template: - command += ["--fpga_verilog_print_user_defined_template"] - if args.vpr_fpga_verilog_print_sdc_analysis: - command += ["--fpga_verilog_print_sdc_analysis"] - runfile.write(" ".join(command)+os.linesep) - - if args.vpr_fpga_verilog_print_sdc_analysis: - command = ["write_pnr_sdc", "--fpga_bitstream_generator"] - runfile.write(" ".join(command)+os.linesep) + path_variables = script_env_vars["PATH"] + path_variables["VPR_ARCH_FILE"] = args.arch_file + path_variables["OPENFPGA_ARCH_FILE"] = args.openfpga_arch_file + path_variables["VPR_TESTBENCH_BLIF"] = args.top_module+".blif" + path_variables["ACTIVITY_FILE"] = args.top_module+"_ace_out.act" + path_variables["REFERENCE_VERILOG_TESTBENCH"] = args.top_module + \ + "_output_verilog.v" + with open(args.top_module+"_run.openfpga", 'w', encoding='utf-8') as archfile: + archfile.write(tmpl.substitute(path_variables)) + command = [cad_tools["openfpga_shell_path"], "-f", + args.top_module+"_run.openfpga"] + run_command("OpenFPGA Shell Run", "openfpgashell.log", command) def run_standard_vpr(bench_blif, fixed_chan_width, logfile, route_only=False): diff --git a/openfpga_flow/scripts/run_fpga_task.py b/openfpga_flow/scripts/run_fpga_task.py index bcb4b6a44..9567c4fe5 100644 --- a/openfpga_flow/scripts/run_fpga_task.py +++ b/openfpga_flow/scripts/run_fpga_task.py @@ -71,6 +71,7 @@ task_script_dir = os.path.dirname(os.path.abspath(__file__)) script_env_vars = ({"PATH": { "OPENFPGA_FLOW_PATH": task_script_dir, "ARCH_PATH": os.path.join("${PATH:OPENFPGA_PATH}", "arch"), + "OPENFPGA_SHELLSCRIPT_PATH": os.path.join("${PATH:OPENFPGA_PATH}", "OpenFPGAShellScripts"), "BENCH_PATH": os.path.join("${PATH:OPENFPGA_PATH}", "benchmarks"), "TECH_PATH": os.path.join("${PATH:OPENFPGA_PATH}", "tech"), "SPICENETLIST_PATH": os.path.join("${PATH:OPENFPGA_PATH}", "SpiceNetlists"), @@ -349,6 +350,12 @@ def create_run_command(curr_job_dir, archfile, benchmark_obj, param, task_conf): if task_gc.get("fpga_flow"): command += ["--fpga_flow", task_gc.get("fpga_flow")] + if task_gc.get("run_engine") == "openfpga_shell": + command += ["--openfpga_shell_template", + task_gc.get("openfpga_shell_template")] + command += ["--openfpga_arch_file", + task_gc.get("openfpga_arch_file")] + if benchmark_obj.get("activity_file"): command += ["--activity_file", benchmark_obj.get("activity_file")] From e1db4df74438a5c0415e6506380549c387208c8f Mon Sep 17 00:00:00 2001 From: ganeshgore Date: Mon, 6 Apr 2020 00:35:07 -0600 Subject: [PATCH 380/645] Created task for FPGA shell run --- .../OpenFPGAShell_Example/config/task.conf | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 openfpga_flow/tasks/OpenFPGAShell_Example/config/task.conf diff --git a/openfpga_flow/tasks/OpenFPGAShell_Example/config/task.conf b/openfpga_flow/tasks/OpenFPGAShell_Example/config/task.conf new file mode 100644 index 000000000..54d8e8a4a --- /dev/null +++ b/openfpga_flow/tasks/OpenFPGAShell_Example/config/task.conf @@ -0,0 +1,33 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif + +[SYNTHESIS_PARAM] +bench0_top = top +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_chan_width = 300 + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +min_route_chan_width=1.3 \ No newline at end of file From 87c25a6084dd5505c477ee8407f317a7190277a2 Mon Sep 17 00:00:00 2001 From: ganeshgore Date: Mon, 6 Apr 2020 00:35:28 -0600 Subject: [PATCH 381/645] Added GCC paths to source --- openfpga.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openfpga.sh b/openfpga.sh index 02f2ba5fd..3ae8c992d 100755 --- a/openfpga.sh +++ b/openfpga.sh @@ -4,6 +4,12 @@ # for several simple operations in OpenFPGA project #author : Ganesh Gore #============================================================================== +# Enviroment variables +export PATH=$PATH:/usr/local/stow/gcc/amd64_linux26/gcc-8.4.0/bin +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/stow/gcc/amd64_linux26/gcc-8.4.0/lib64 +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/stow/boost/boost_1_67_0/lib/ +export CC=$(which gcc) +export CXX=$(which g++) export OPENFPGA_PATH="$(pwd)" export OPENFPGA_SCRIPT_PATH="$(pwd)/openfpga_flow/scripts" @@ -82,4 +88,5 @@ fi TaskList=$(ls -tdalh ${OPENFPGA_TASK_PATH}/* | awk '{system("basename " $9)}' | awk '{printf("%s ",$1)}') complete -W "${TaskList}" goto-task complete -W "${TaskList}" run-task +complete -W "${TaskList}" run-shell-task complete -W "${TaskList}" run-modelsim From 6eb125ec2ae08d61bc55aae511960a4af6320c9e Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 6 Apr 2020 14:09:52 -0600 Subject: [PATCH 382/645] Now cross-column/row is optional to direct annotation in OpenFPGA architecture XML --- .../src/read_xml_routing_circuit.cpp | 7 +- ...c_N10_adder_column_chain_40nm_openfpga.xml | 292 ++++++++++++++++++ ..._frac_tileable_adder_column_chain.openfpga | 62 ++++ 3 files changed, 360 insertions(+), 1 deletion(-) create mode 100644 openfpga/test_openfpga_arch/k6_frac_N10_adder_column_chain_40nm_openfpga.xml create mode 100644 openfpga/test_script/and_k6_frac_tileable_adder_column_chain.openfpga diff --git a/libopenfpga/libarchopenfpga/src/read_xml_routing_circuit.cpp b/libopenfpga/libarchopenfpga/src/read_xml_routing_circuit.cpp index 3114276ae..5daef1310 100644 --- a/libopenfpga/libarchopenfpga/src/read_xml_routing_circuit.cpp +++ b/libopenfpga/libarchopenfpga/src/read_xml_routing_circuit.cpp @@ -262,7 +262,12 @@ ArchDirect read_xml_direct_circuit(pugi::xml_node& Node, arch_direct.set_circuit_model(direct, direct_model); /* Add more information*/ - std::string direct_type_name = get_attribute(xml_direct, "type", loc_data).as_string(); + std::string direct_type_name = get_attribute(xml_direct, "type", loc_data, pugiutil::ReqOpt::OPTIONAL).as_string("none"); + /* If not defined, we go to the next */ + if (std::string("none") == direct_type_name) { + continue; + } + e_direct_type direct_type = string_to_direct_type(direct_type_name); if (NUM_DIRECT_TYPES == direct_type) { diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_adder_column_chain_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_adder_column_chain_40nm_openfpga.xml new file mode 100644 index 000000000..34f44d153 --- /dev/null +++ b/openfpga/test_openfpga_arch/k6_frac_N10_adder_column_chain_40nm_openfpga.xml @@ -0,0 +1,292 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + + + + 10e-12 5e-12 + + + 10e-12 5e-12 + + + + + + + + + + + + 10e-12 5e-12 5e-12 + + + 10e-12 5e-12 5e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga/test_script/and_k6_frac_tileable_adder_column_chain.openfpga b/openfpga/test_script/and_k6_frac_tileable_adder_column_chain.openfpga new file mode 100644 index 000000000..fbcdda185 --- /dev/null +++ b/openfpga/test_script/and_k6_frac_tileable_adder_column_chain.openfpga @@ -0,0 +1,62 @@ +# Run VPR for the 'and' design +vpr ./test_vpr_arch/k6_frac_N10_tileable_adder_chain_40nm.xml ./test_blif/and.blif --route_chan_width 40 --clock_modeling route #--write_rr_graph example_rr_graph.xml + +# Read OpenFPGA architecture definition +read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_adder_column_chain_40nm_openfpga.xml + +# Write out the architecture XML as a proof +#write_openfpga_arch -f ./arch_echo.xml + +# Annotate the OpenFPGA architecture to VPR data base +link_openfpga_arch --activity_file ./test_blif/and.act --sort_gsb_chan_node_in_edges #--verbose + +# Write GSB to XML for debugging +write_gsb_to_xml --file /var/tmp/xtang/openfpga_test_src/gsb_xml + +# Check and correct any naming conflicts in the BLIF netlist +check_netlist_naming_conflict --fix --report ./netlist_renaming.xml + +# Apply fix-up to clustering nets based on routing results +pb_pin_fixup --verbose + +# Apply fix-up to Look-Up Table truth tables based on packing results +lut_truth_table_fixup #--verbose + +# Build the module graph +# - Enabled compression on routing architecture modules +# - Enable pin duplication on grid modules +build_fabric --compress_routing --duplicate_grid_pin --verbose + +# Repack the netlist to physical pbs +# This must be done before bitstream generator and testbench generation +# Strongly recommend it is done after all the fix-up have been applied +repack #--verbose + +# Build the bitstream +# - Output the fabric-independent bitstream to a file +build_architecture_bitstream --verbose --file /var/tmp/xtang/openfpga_test_src/fabric_indepenent_bitstream.xml + +# Build fabric-dependent bitstream +build_fabric_bitstream --verbose + +# Write the Verilog netlist for FPGA fabric +# - Enable the use of explicit port mapping in Verilog netlist +write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src/SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose + +# Write the Verilog testbench for FPGA fabric +# - We suggest the use of same output directory as fabric Verilog netlists +# - Must specify the reference benchmark file if you want to output any testbenches +# - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA +# - Enable pre-configured top-level testbench which is a fast verification skipping programming phase +# - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts +write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src/SRC --reference_benchmark_file_path /var/tmp/xtang/and.v --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini /var/tmp/xtang/openfpga_test_src/simulation_deck.ini + +# Write the SDC files for PnR backend +# - Turn on every options here +write_pnr_sdc --file /var/tmp/xtang/openfpga_test_src/SDC + +# Write the SDC to run timing analysis for a mapped FPGA fabric +write_analysis_sdc --file /var/tmp/xtang/openfpga_test_src/SDC_analysis + +# Finish and exit OpenFPGA +exit From 13cd48c119b8f1198ad42de8c74aa34642df9f14 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 6 Apr 2020 16:07:49 -0600 Subject: [PATCH 383/645] add support on packable/unpackable modes in VPR architecture --- libs/libarchfpga/src/physical_types.h | 3 +++ libs/libarchfpga/src/read_xml_arch_file.cpp | 14 ++++++++++++++ openfpga/test_vpr_arch/k6_N10_40nm.xml | 2 +- openfpga/test_vpr_arch/k6_N10_tileable_40nm.xml | 2 +- openfpga/test_vpr_arch/k6_frac_N10_40nm.xml | 4 ++-- .../test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml | 4 ++-- .../k6_frac_N10_adder_chain_mem16K_40nm.xml | 4 ++-- .../test_vpr_arch/k6_frac_N10_tileable_40nm.xml | 4 ++-- .../k6_frac_N10_tileable_adder_chain_40nm.xml | 4 ++-- ...6_frac_N10_tileable_adder_chain_mem16K_40nm.xml | 4 ++-- ...ac_N10_tileable_adder_chain_mem16K_aib_40nm.xml | 4 ++-- ...e_adder_chain_mem16K_multi_io_capacity_40nm.xml | 4 ++-- ...tileable_adder_chain_mem16K_reduced_io_40nm.xml | 4 ++-- ...c_N10_tileable_adder_chain_wide_mem16K_40nm.xml | 4 ++-- vpr/src/pack/cluster.cpp | 5 +++++ vpr/src/pack/cluster_router.cpp | 4 ++++ vpr/src/pack/prepack.cpp | 14 +++++++++++++- 17 files changed, 61 insertions(+), 23 deletions(-) diff --git a/libs/libarchfpga/src/physical_types.h b/libs/libarchfpga/src/physical_types.h index f5f2c3c7e..c9b44cd92 100644 --- a/libs/libarchfpga/src/physical_types.h +++ b/libs/libarchfpga/src/physical_types.h @@ -822,6 +822,9 @@ struct t_mode { t_pb_type* parent_pb_type = nullptr; int index = 0; + /* Xifan Tang: Specify if the mode is packable or not */ + bool packable = true; + /* Power related members */ t_mode_power* mode_power = nullptr; diff --git a/libs/libarchfpga/src/read_xml_arch_file.cpp b/libs/libarchfpga/src/read_xml_arch_file.cpp index cabb17bbb..cf2dd72c3 100644 --- a/libs/libarchfpga/src/read_xml_arch_file.cpp +++ b/libs/libarchfpga/src/read_xml_arch_file.cpp @@ -1956,6 +1956,20 @@ static void ProcessMode(pugi::xml_node Parent, t_mode* mode, const bool timing_e mode->name = vtr::strdup(Prop); } + /* Xifan Tang: parse XML about if this mode is packable or not */ + mode->packable = true; + /* If the parent mode is not packable, all the child mode should be unpackable as well */ + if (nullptr != mode->parent_pb_type->parent_mode) { + mode->packable = mode->parent_pb_type->parent_mode->packable; + } + /* Override if user specify */ + mode->packable = get_attribute(Parent, "packable", loc_data, ReqOpt::OPTIONAL).as_bool(mode->packable); + if (false == mode->packable) { + VTR_LOG("mode '%s[%s]' is defined by user to be not packable\n", + mode->parent_pb_type->name, + mode->name); + } + mode->num_pb_type_children = count_children(Parent, "pb_type", loc_data, ReqOpt::OPTIONAL); if (mode->num_pb_type_children > 0) { mode->pb_type_children = new t_pb_type[mode->num_pb_type_children]; diff --git a/openfpga/test_vpr_arch/k6_N10_40nm.xml b/openfpga/test_vpr_arch/k6_N10_40nm.xml index 630011e84..83b4948a8 100644 --- a/openfpga/test_vpr_arch/k6_N10_40nm.xml +++ b/openfpga/test_vpr_arch/k6_N10_40nm.xml @@ -139,7 +139,7 @@ - + diff --git a/openfpga/test_vpr_arch/k6_N10_tileable_40nm.xml b/openfpga/test_vpr_arch/k6_N10_tileable_40nm.xml index d86d7aa62..ceacbb3f2 100644 --- a/openfpga/test_vpr_arch/k6_N10_tileable_40nm.xml +++ b/openfpga/test_vpr_arch/k6_N10_tileable_40nm.xml @@ -139,7 +139,7 @@ - + diff --git a/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml index ef9268810..8476a5155 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml @@ -158,7 +158,7 @@ - + @@ -230,7 +230,7 @@ - + diff --git a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml index bbe041562..504431eb0 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml @@ -245,7 +245,7 @@ - + @@ -321,7 +321,7 @@ - + diff --git a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml index 850eb5450..3ecd20733 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml @@ -282,7 +282,7 @@ - + @@ -358,7 +358,7 @@ - + diff --git a/openfpga/test_vpr_arch/k6_frac_N10_tileable_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_tileable_40nm.xml index 2c528c13c..146a170e5 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_tileable_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_tileable_40nm.xml @@ -158,7 +158,7 @@ - + @@ -230,7 +230,7 @@ - + diff --git a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_40nm.xml index eea76837f..19b27b88b 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_40nm.xml @@ -245,7 +245,7 @@ - + @@ -321,7 +321,7 @@ - + diff --git a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_40nm.xml index 9422c3a09..4c0751db4 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_40nm.xml @@ -282,7 +282,7 @@ - + @@ -358,7 +358,7 @@ - + diff --git a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_aib_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_aib_40nm.xml index e196cb83c..37dbb2b93 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_aib_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_aib_40nm.xml @@ -348,7 +348,7 @@ - + @@ -424,7 +424,7 @@ - + diff --git a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_multi_io_capacity_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_multi_io_capacity_40nm.xml index 8eb6f32bf..ff7c3278b 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_multi_io_capacity_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_multi_io_capacity_40nm.xml @@ -316,7 +316,7 @@ - + @@ -392,7 +392,7 @@ - + diff --git a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_reduced_io_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_reduced_io_40nm.xml index 6f8c4cfae..d9adc9056 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_reduced_io_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_reduced_io_40nm.xml @@ -285,7 +285,7 @@ - + @@ -361,7 +361,7 @@ - + diff --git a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_wide_mem16K_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_wide_mem16K_40nm.xml index f07f16c80..7eda2aa2f 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_wide_mem16K_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_wide_mem16K_40nm.xml @@ -282,7 +282,7 @@ - + @@ -358,7 +358,7 @@ - + diff --git a/vpr/src/pack/cluster.cpp b/vpr/src/pack/cluster.cpp index fa9d4d8f8..51de31f7e 100644 --- a/vpr/src/pack/cluster.cpp +++ b/vpr/src/pack/cluster.cpp @@ -1458,6 +1458,11 @@ static enum e_block_pack_status try_place_atom_block_rec(const t_pb_graph_node* } pb_type = pb_graph_node->pb_type; + /* Xifan Tang: bypass unpackable modes */ + if (false == pb_type->parent_mode->packable) { + return BLK_FAILED_FEASIBLE; + } + is_primitive = (pb_type->num_modes == 0); if (is_primitive) { diff --git a/vpr/src/pack/cluster_router.cpp b/vpr/src/pack/cluster_router.cpp index d94691053..7f2cd37d9 100644 --- a/vpr/src/pack/cluster_router.cpp +++ b/vpr/src/pack/cluster_router.cpp @@ -1173,6 +1173,10 @@ static void expand_node_all_modes(t_lb_router_data* router_data, t_expansion_nod if (cur_mode != -1 && mode != cur_mode) { continue; } + /* Xifan Tang: Do not expand in unpackable modes */ + if (false == pin->parent_node->pb_type->parent_mode->packable) { + continue; + } /* Check whether a mode is illegal. If it is then the node will not be expanded */ bool is_illegal = false; diff --git a/vpr/src/pack/prepack.cpp b/vpr/src/pack/prepack.cpp index 28e8c2bc5..cf9ccdd6d 100644 --- a/vpr/src/pack/prepack.cpp +++ b/vpr/src/pack/prepack.cpp @@ -791,6 +791,13 @@ t_pack_molecule* alloc_and_load_pack_molecules(t_pack_patterns* list_of_pack_pat * TODO: Need to investigate better mapping strategies than first-fit */ for (i = 0; i < num_packing_patterns; i++) { + + /* Xifan Tang: skip patterns that belong to unpackable modes */ + if ( (nullptr != list_of_pack_patterns[i].root_block->pb_type->parent_mode) + && (false == list_of_pack_patterns[i].root_block->pb_type->parent_mode->packable) ) { + continue; + } + best_pattern = 0; for (j = 1; j < num_packing_patterns; j++) { if (is_used[best_pattern]) { @@ -799,7 +806,7 @@ t_pack_molecule* alloc_and_load_pack_molecules(t_pack_patterns* list_of_pack_pat best_pattern = j; } } - VTR_ASSERT(is_used[best_pattern] == false); + VTR_ASSERT(is_used[best_pattern] == false); is_used[best_pattern] = true; auto blocks = atom_ctx.nlist.blocks(); @@ -1213,6 +1220,11 @@ static t_pb_graph_node* get_expected_lowest_cost_primitive_for_atom_block_in_pb_ } } else { for (i = 0; i < curr_pb_graph_node->pb_type->num_modes; i++) { + /* Xifan Tang: early fail if this primitive in a unpackable mode */ + if (false == curr_pb_graph_node->pb_type->modes[i].packable) { + continue; + } + for (j = 0; j < curr_pb_graph_node->pb_type->modes[i].num_pb_type_children; j++) { *cost = UNDEFINED; cur = get_expected_lowest_cost_primitive_for_atom_block_in_pb_graph_node(blk_id, &curr_pb_graph_node->child_pb_graph_nodes[i][j][0], cost); From 7a4137fdcfb6c55710417d7e5790d3eea38d65f2 Mon Sep 17 00:00:00 2001 From: Xifan Tang Date: Mon, 6 Apr 2020 18:37:05 -0600 Subject: [PATCH 384/645] doc update for packable XML syntax in VPR --- docs/source/arch_lang/addon_vpr_syntax.rst | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/source/arch_lang/addon_vpr_syntax.rst b/docs/source/arch_lang/addon_vpr_syntax.rst index c6ac221a2..18f61703a 100644 --- a/docs/source/arch_lang/addon_vpr_syntax.rst +++ b/docs/source/arch_lang/addon_vpr_syntax.rst @@ -10,10 +10,18 @@ Models, Complex blocks and Physical Tiles Each ```` should contain a ```` that describe the physical implementation of the ````. Note that this is fully compatible to the VPR architecture XML syntax. -```` should include the models that describe the primitive ```` in physical mode. - +.. note:: ```` should include the models that describe the primitive ```` in physical mode. + .. note:: Currently, OpenFPGA only supports 1 ```` to be defined under each ```` +.. option:: /> + + OpenFPGA allows users to define it a mode is packable for VPR. + By default, the packable is set to ``true``. + This is mainly used for the mode that describes the physical implementation, which is typically not packable. Disable it in the packing and signficantly accelerate the packing runtime. + + .. note:: Once a mode is set to unpackable, its child modes will be unpackable as well. + Layout ~~~~~~ From 92a3a444f93b0d4ae50071e6034b9cfec96919ef Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 6 Apr 2020 20:44:00 -0600 Subject: [PATCH 385/645] update VPR7 to support global I/O ports --- openfpga/src/fabric/build_grid_modules.cpp | 2 + .../SRC/check_circuit_library.cpp | 5 +- .../libarchfpgavpr7/SRC/circuit_library.cpp | 16 ++++ .../libarchfpgavpr7/SRC/circuit_library.h | 4 + vpr7_x2p/libarchfpgavpr7/SRC/read_xml_spice.c | 6 ++ vpr7_x2p/libarchfpgavpr7/SRC/spice_types.h | 1 + .../vpr/SRC/fpga_x2p/base/fpga_x2p_naming.cpp | 5 +- .../vpr/SRC/fpga_x2p/base/fpga_x2p_naming.h | 3 +- .../vpr/SRC/fpga_x2p/base/module_manager.cpp | 2 +- .../vpr/SRC/fpga_x2p/base/module_manager.h | 2 + .../fpga_x2p/base/module_manager_utils.cpp | 84 ++++++++++++++++--- .../module_builder/build_grid_modules.cpp | 71 +++++++++++++--- .../fpga_x2p/verilog/verilog_writer_utils.cpp | 6 ++ 13 files changed, 180 insertions(+), 27 deletions(-) diff --git a/openfpga/src/fabric/build_grid_modules.cpp b/openfpga/src/fabric/build_grid_modules.cpp index 741632d1c..95e435f6f 100644 --- a/openfpga/src/fabric/build_grid_modules.cpp +++ b/openfpga/src/fabric/build_grid_modules.cpp @@ -145,6 +145,8 @@ void add_grid_module_nets_connect_pb_type_ports(ModuleManager& module_manager, } /******************************************************************** + * Add module nets between primitive module and its internal circuit module + * This is only applicable to the primitive module of a grid *******************************************************************/ static void add_primitive_module_fpga_global_io_port(ModuleManager& module_manager, diff --git a/vpr7_x2p/libarchfpgavpr7/SRC/check_circuit_library.cpp b/vpr7_x2p/libarchfpgavpr7/SRC/check_circuit_library.cpp index e6f49a1c0..f0ed79a1c 100644 --- a/vpr7_x2p/libarchfpgavpr7/SRC/check_circuit_library.cpp +++ b/vpr7_x2p/libarchfpgavpr7/SRC/check_circuit_library.cpp @@ -352,9 +352,10 @@ size_t check_circuit_library_ports(const CircuitLibrary& circuit_lib) { /* Check global ports: make sure all the global ports are input ports */ for (const auto& port : circuit_lib.ports()) { if ( (circuit_lib.port_is_global(port)) - && (!circuit_lib.is_input_port(port)) ) { + && (!circuit_lib.is_input_port(port)) + && (!circuit_lib.is_output_port(port)) ) { vpr_printf(TIO_MESSAGE_ERROR, - "Circuit port (type=%s) of model (name=%s) is defined as global but not an input port!\n", + "Circuit port (type=%s) of model (name=%s) is defined as global but not an input/output port!\n", CIRCUIT_MODEL_PORT_TYPE_STRING[size_t(circuit_lib.port_type(port))], circuit_lib.model_name(port).c_str()); num_err++; diff --git a/vpr7_x2p/libarchfpgavpr7/SRC/circuit_library.cpp b/vpr7_x2p/libarchfpgavpr7/SRC/circuit_library.cpp index 5b7c7b62d..2a5154e44 100644 --- a/vpr7_x2p/libarchfpgavpr7/SRC/circuit_library.cpp +++ b/vpr7_x2p/libarchfpgavpr7/SRC/circuit_library.cpp @@ -767,6 +767,12 @@ size_t CircuitLibrary::port_default_value(const CircuitPortId& circuit_port_id) return port_default_values_[circuit_port_id]; } +/* Return a flag if the port is used in mode-selection purpuse of a circuit model */ +bool CircuitLibrary::port_is_io(const CircuitPortId& circuit_port_id) const { + /* validate the circuit_port_id */ + VTR_ASSERT(valid_circuit_port_id(circuit_port_id)); + return port_is_io_[circuit_port_id]; +} /* Return a flag if the port is used in mode-selection purpuse of a circuit model */ bool CircuitLibrary::port_is_mode_select(const CircuitPortId& circuit_port_id) const { @@ -1212,6 +1218,7 @@ CircuitPortId CircuitLibrary::add_model_port(const CircuitModelId& model_id, port_lib_names_.emplace_back(); port_inv_prefix_.emplace_back(); port_default_values_.push_back(-1); + port_is_io_.push_back(false); port_is_mode_select_.push_back(false); port_is_global_.push_back(false); port_is_reset_.push_back(false); @@ -1282,6 +1289,15 @@ void CircuitLibrary::set_port_default_value(const CircuitPortId& circuit_port_id return; } +/* Set the is_mode_select for a port of a circuit model */ +void CircuitLibrary::set_port_is_io(const CircuitPortId& circuit_port_id, + const bool& is_io) { + /* validate the circuit_port_id */ + VTR_ASSERT(valid_circuit_port_id(circuit_port_id)); + port_is_io_[circuit_port_id] = is_io; + return; +} + /* Set the is_mode_select for a port of a circuit model */ void CircuitLibrary::set_port_is_mode_select(const CircuitPortId& circuit_port_id, const bool& is_mode_select) { diff --git a/vpr7_x2p/libarchfpgavpr7/SRC/circuit_library.h b/vpr7_x2p/libarchfpgavpr7/SRC/circuit_library.h index 8080d2ea3..b76e2be63 100644 --- a/vpr7_x2p/libarchfpgavpr7/SRC/circuit_library.h +++ b/vpr7_x2p/libarchfpgavpr7/SRC/circuit_library.h @@ -281,6 +281,7 @@ class CircuitLibrary { std::string port_lib_name(const CircuitPortId& circuit_port_id) const; std::string port_inv_prefix(const CircuitPortId& circuit_port_id) const; size_t port_default_value(const CircuitPortId& circuit_port_id) const; + bool port_is_io(const CircuitPortId& circuit_port_id) const; bool port_is_mode_select(const CircuitPortId& circuit_port_id) const; bool port_is_global(const CircuitPortId& circuit_port_id) const; bool port_is_reset(const CircuitPortId& circuit_port_id) const; @@ -351,6 +352,8 @@ class CircuitLibrary { const std::string& inv_prefix); void set_port_default_value(const CircuitPortId& circuit_port_id, const size_t& default_val); + void set_port_is_io(const CircuitPortId& circuit_port_id, + const bool& is_io); void set_port_is_mode_select(const CircuitPortId& circuit_port_id, const bool& is_mode_select); void set_port_is_global(const CircuitPortId& circuit_port_id, @@ -534,6 +537,7 @@ class CircuitLibrary { vtr::vector port_lib_names_; vtr::vector port_inv_prefix_; vtr::vector port_default_values_; + vtr::vector port_is_io_; vtr::vector port_is_mode_select_; vtr::vector port_is_global_; vtr::vector port_is_reset_; diff --git a/vpr7_x2p/libarchfpgavpr7/SRC/read_xml_spice.c b/vpr7_x2p/libarchfpgavpr7/SRC/read_xml_spice.c index c630dc37f..4c613e076 100644 --- a/vpr7_x2p/libarchfpgavpr7/SRC/read_xml_spice.c +++ b/vpr7_x2p/libarchfpgavpr7/SRC/read_xml_spice.c @@ -783,6 +783,11 @@ static void ProcessSpiceModelPort(ezxml_t Node, /* Output mast of a fracturable LUT, which is to identify which intermediate LUT output will be connected to outputs */ ProcessSpiceModelPortLutOutputMask(Node, port); + if (SPICE_MODEL_PORT_INPUT == port->type) { + port->is_io = GetBooleanProperty(Node, "io", FALSE, FALSE); + ezxml_set_attr(Node, "io", NULL); + } + /* See if this is a global signal * We assume that global signals are shared by all the SPICE Model/blocks. * We need to check if other SPICE model has the same port name @@ -1748,6 +1753,7 @@ CircuitLibrary build_circuit_library(int num_spice_model, t_spice_model* spice_m circuit_lib.set_port_default_value(port_id, spice_models[imodel].ports[iport].default_val); + circuit_lib.set_port_is_io(port_id, TRUE == spice_models[imodel].ports[iport].is_io); circuit_lib.set_port_is_mode_select(port_id, TRUE == spice_models[imodel].ports[iport].mode_select); circuit_lib.set_port_is_global(port_id, TRUE == spice_models[imodel].ports[iport].is_global); circuit_lib.set_port_is_reset(port_id, TRUE == spice_models[imodel].ports[iport].is_reset); diff --git a/vpr7_x2p/libarchfpgavpr7/SRC/spice_types.h b/vpr7_x2p/libarchfpgavpr7/SRC/spice_types.h index 26f30b164..97e1b02dc 100644 --- a/vpr7_x2p/libarchfpgavpr7/SRC/spice_types.h +++ b/vpr7_x2p/libarchfpgavpr7/SRC/spice_types.h @@ -164,6 +164,7 @@ struct s_spice_model_port { boolean mode_select; int default_val; /* Global port properties */ + boolean is_io; boolean is_global; boolean is_reset; boolean is_set; diff --git a/vpr7_x2p/vpr/SRC/fpga_x2p/base/fpga_x2p_naming.cpp b/vpr7_x2p/vpr/SRC/fpga_x2p/base/fpga_x2p_naming.cpp index 4f1e21d80..7a61ae6c8 100644 --- a/vpr7_x2p/vpr/SRC/fpga_x2p/base/fpga_x2p_naming.cpp +++ b/vpr7_x2p/vpr/SRC/fpga_x2p/base/fpga_x2p_naming.cpp @@ -1343,10 +1343,13 @@ std::string generate_pb_type_port_name(t_port* pb_type_port) { ********************************************************************/ std::string generate_fpga_global_io_port_name(const std::string& prefix, const CircuitLibrary& circuit_lib, - const CircuitModelId& circuit_model) { + const CircuitModelId& circuit_model, + const CircuitPortId& circuit_port) { std::string port_name(prefix); port_name += circuit_lib.model_name(circuit_model); + port_name += std::string("_"); + port_name += circuit_lib.port_prefix(circuit_port); return port_name; } diff --git a/vpr7_x2p/vpr/SRC/fpga_x2p/base/fpga_x2p_naming.h b/vpr7_x2p/vpr/SRC/fpga_x2p/base/fpga_x2p_naming.h index ad0a5932e..a62523f59 100644 --- a/vpr7_x2p/vpr/SRC/fpga_x2p/base/fpga_x2p_naming.h +++ b/vpr7_x2p/vpr/SRC/fpga_x2p/base/fpga_x2p_naming.h @@ -232,7 +232,8 @@ std::string generate_pb_type_port_name(t_port* pb_type_port); std::string generate_fpga_global_io_port_name(const std::string& prefix, const CircuitLibrary& circuit_lib, - const CircuitModelId& circuit_model); + const CircuitModelId& circuit_model, + const CircuitPortId& circuit_port); std::string generate_fpga_top_module_name(); diff --git a/vpr7_x2p/vpr/SRC/fpga_x2p/base/module_manager.cpp b/vpr7_x2p/vpr/SRC/fpga_x2p/base/module_manager.cpp index 0281a5a5a..2cce352a3 100644 --- a/vpr7_x2p/vpr/SRC/fpga_x2p/base/module_manager.cpp +++ b/vpr7_x2p/vpr/SRC/fpga_x2p/base/module_manager.cpp @@ -117,7 +117,7 @@ std::string ModuleManager::module_name(const ModuleId& module_id) const { /* Get the string of a module port type */ std::string ModuleManager::module_port_type_str(const enum e_module_port_type& port_type) const { - std::array MODULE_PORT_TYPE_STRING = {{"GLOBAL PORTS", "GPIO PORTS", "INOUT PORTS", "INPUT PORTS", "OUTPUT PORTS", "CLOCK PORTS"}}; + std::array MODULE_PORT_TYPE_STRING = {{"GLOBAL PORTS", "GPIN PORTS", "GPOUT PORTS", "GPIO PORTS", "INOUT PORTS", "INPUT PORTS", "OUTPUT PORTS", "CLOCK PORTS"}}; return MODULE_PORT_TYPE_STRING[port_type]; } diff --git a/vpr7_x2p/vpr/SRC/fpga_x2p/base/module_manager.h b/vpr7_x2p/vpr/SRC/fpga_x2p/base/module_manager.h index 9097dd603..7aaf528f3 100644 --- a/vpr7_x2p/vpr/SRC/fpga_x2p/base/module_manager.h +++ b/vpr7_x2p/vpr/SRC/fpga_x2p/base/module_manager.h @@ -26,6 +26,8 @@ class ModuleManager { public: /* Private data structures */ enum e_module_port_type { MODULE_GLOBAL_PORT, /* Global inputs */ + MODULE_GPIN_PORT, /* General-purpose input */ + MODULE_GPOUT_PORT, /* General-purpose outputs, could be used for spypads */ MODULE_GPIO_PORT, /* General-purpose IOs, which are data IOs of the fabric */ MODULE_INOUT_PORT, /* Normal (non-global) inout ports */ MODULE_INPUT_PORT, /* Normal (non-global) input ports */ diff --git a/vpr7_x2p/vpr/SRC/fpga_x2p/base/module_manager_utils.cpp b/vpr7_x2p/vpr/SRC/fpga_x2p/base/module_manager_utils.cpp index f8c79a238..927506096 100644 --- a/vpr7_x2p/vpr/SRC/fpga_x2p/base/module_manager_utils.cpp +++ b/vpr7_x2p/vpr/SRC/fpga_x2p/base/module_manager_utils.cpp @@ -37,7 +37,18 @@ ModuleId add_circuit_model_to_module_manager(ModuleManager& module_manager, /* Find global ports and add one by one */ for (const auto& port : circuit_lib.model_global_ports(circuit_model, false)) { BasicPort port_info(circuit_lib.port_prefix(port), circuit_lib.port_size(port)); - module_manager.add_port(module, port_info, ModuleManager::MODULE_GLOBAL_PORT); + if ( (SPICE_MODEL_PORT_INPUT == circuit_lib.port_type(port)) + && (false == circuit_lib.port_is_io(port)) ) { + module_manager.add_port(module, port_info, ModuleManager::MODULE_GLOBAL_PORT); + } else if (SPICE_MODEL_PORT_CLOCK == circuit_lib.port_type(port)) { + module_manager.add_port(module, port_info, ModuleManager::MODULE_GLOBAL_PORT); + } else if ( (SPICE_MODEL_PORT_INPUT == circuit_lib.port_type(port)) + && (true == circuit_lib.port_is_io(port)) ) { + module_manager.add_port(module, port_info, ModuleManager::MODULE_GPIN_PORT); + } else { + VTR_ASSERT(SPICE_MODEL_PORT_OUTPUT == circuit_lib.port_type(port)); + module_manager.add_port(module, port_info, ModuleManager::MODULE_GPOUT_PORT); + } } /* Find other ports and add one by one */ @@ -925,19 +936,35 @@ size_t find_module_num_config_bits(const ModuleManager& module_manager, } /******************************************************************** - * Add GPIO ports to the module: + * Add General purpose I/O ports to the module: * In this function, the following tasks are done: - * 1. find all the GPIO ports from the child modules and build a list of it, - * 2. Merge all the GPIO ports with the same name + * 1. find all the I/O ports from the child modules and build a list of it, + * 2. Merge all the I/O ports with the same name * 3. add the ports to the pb_module * 4. add module nets to connect to the GPIO ports of each sub module + * + * Module + * ----------------------+ + * | + * child[0] | + * -----------+ | + * |----------+----> outputA[0] + * -----------+ | + * | + * child[1] | + * -----------+ | + * |----------+----> outputA[1] + * -----------+ | + * * Note: This function should be call ONLY after all the sub modules (instances) * have been added to the pb_module! * Otherwise, some GPIO ports of the sub modules may be missed! *******************************************************************/ -void add_module_gpio_ports_from_child_modules(ModuleManager& module_manager, - const ModuleId& module_id) { +static +void add_module_io_ports_from_child_modules(ModuleManager& module_manager, + const ModuleId& module_id, + const ModuleManager::e_module_port_type& module_port_type) { std::vector gpio_ports_to_add; /* Iterate over the child modules */ @@ -945,7 +972,7 @@ void add_module_gpio_ports_from_child_modules(ModuleManager& module_manager, /* Iterate over the child instances */ for (size_t i = 0; i < module_manager.num_instance(module_id, child); ++i) { /* Find all the global ports, whose port type is special */ - for (BasicPort gpio_port : module_manager.module_ports_by_type(child, ModuleManager::MODULE_GPIO_PORT)) { + for (BasicPort gpio_port : module_manager.module_ports_by_type(child, module_port_type)) { /* If this port is not mergeable, we update the list */ bool is_mergeable = false; for (BasicPort& gpio_port_to_add : gpio_ports_to_add) { @@ -973,7 +1000,7 @@ void add_module_gpio_ports_from_child_modules(ModuleManager& module_manager, std::vector gpio_port_ids; /* Add the gpio ports for the module */ for (const BasicPort& gpio_port_to_add : gpio_ports_to_add) { - ModulePortId port_id = module_manager.add_port(module_id, gpio_port_to_add, ModuleManager::MODULE_GPIO_PORT); + ModulePortId port_id = module_manager.add_port(module_id, gpio_port_to_add, module_port_type); gpio_port_ids.push_back(port_id); } @@ -984,7 +1011,7 @@ void add_module_gpio_ports_from_child_modules(ModuleManager& module_manager, /* Iterate over the child instances */ for (const size_t& child_instance : module_manager.child_module_instances(module_id, child)) { /* Find all the global ports, whose port type is special */ - for (ModulePortId child_gpio_port_id : module_manager.module_port_ids_by_type(child, ModuleManager::MODULE_GPIO_PORT)) { + for (ModulePortId child_gpio_port_id : module_manager.module_port_ids_by_type(child, module_port_type)) { BasicPort child_gpio_port = module_manager.module_port(child, child_gpio_port_id); /* Find the port with the same name! */ for (size_t iport = 0; iport < gpio_ports_to_add.size(); ++iport) { @@ -995,8 +1022,22 @@ void add_module_gpio_ports_from_child_modules(ModuleManager& module_manager, for (const size_t& pin_id : child_gpio_port.pins()) { /* Reach here, it means this is the port we want, create a net and configure its source and sink */ ModuleNetId net = module_manager.create_module_net(module_id); - module_manager.add_module_net_source(module_id, net, module_id, 0, gpio_port_ids[iport], gpio_port_lsb[iport]); - module_manager.add_module_net_sink(module_id, net, child, child_instance, child_gpio_port_id, pin_id); + /* - For GPIO and GPIN ports + * the source of the net is the current module + * the sink of the net is the child module + * - For GPOUT ports + * the source of the net is the child module + * the sink of the net is the current module + */ + if ( (ModuleManager::MODULE_GPIO_PORT == module_port_type) + || (ModuleManager::MODULE_GPIN_PORT == module_port_type) ) { + module_manager.add_module_net_source(module_id, net, module_id, 0, gpio_port_ids[iport], gpio_port_lsb[iport]); + module_manager.add_module_net_sink(module_id, net, child, child_instance, child_gpio_port_id, pin_id); + } else { + VTR_ASSERT(ModuleManager::MODULE_GPOUT_PORT == module_port_type); + module_manager.add_module_net_sink(module_id, net, module_id, 0, gpio_port_ids[iport], gpio_port_lsb[iport]); + module_manager.add_module_net_source(module_id, net, child, child_instance, child_gpio_port_id, pin_id); + } /* Update the LSB counter */ gpio_port_lsb[iport]++; } @@ -1014,6 +1055,27 @@ void add_module_gpio_ports_from_child_modules(ModuleManager& module_manager, } } +/******************************************************************** + * Add GPIO ports to the module: + * In this function, the following tasks are done: + * 1. find all the GPIO ports from the child modules and build a list of it, + * 2. Merge all the GPIO ports with the same name + * 3. add the ports to the pb_module + * 4. add module nets to connect to the GPIO ports of each sub module + * + * Note: This function should be call ONLY after all the sub modules (instances) + * have been added to the pb_module! + * Otherwise, some GPIO ports of the sub modules may be missed! + *******************************************************************/ +void add_module_gpio_ports_from_child_modules(ModuleManager& module_manager, + const ModuleId& module_id) { + add_module_io_ports_from_child_modules(module_manager, module_id, ModuleManager::MODULE_GPIO_PORT); + + add_module_io_ports_from_child_modules(module_manager, module_id, ModuleManager::MODULE_GPIN_PORT); + + add_module_io_ports_from_child_modules(module_manager, module_id, ModuleManager::MODULE_GPOUT_PORT); +} + /******************************************************************** * Add global ports to the module: * In this function, the following tasks are done: diff --git a/vpr7_x2p/vpr/SRC/fpga_x2p/module_builder/build_grid_modules.cpp b/vpr7_x2p/vpr/SRC/fpga_x2p/module_builder/build_grid_modules.cpp index a0ead11dd..c7578292a 100644 --- a/vpr7_x2p/vpr/SRC/fpga_x2p/module_builder/build_grid_modules.cpp +++ b/vpr7_x2p/vpr/SRC/fpga_x2p/module_builder/build_grid_modules.cpp @@ -140,6 +140,41 @@ void add_grid_module_nets_connect_pb_type_ports(ModuleManager& module_manager, } } +/******************************************************************** + * Add module nets between primitive module and its internal circuit module + * This is only applicable to the primitive module of a grid + *******************************************************************/ +static +void add_primitive_module_fpga_global_io_port(ModuleManager& module_manager, + const ModuleId& primitive_module, + const ModuleId& logic_module, + const size_t& logic_instance_id, + const ModuleManager::e_module_port_type& module_io_port_type, + const CircuitLibrary& circuit_lib, + const CircuitModelId& primitive_model, + const CircuitPortId& circuit_port) { + BasicPort module_port(generate_fpga_global_io_port_name(std::string(gio_inout_prefix), circuit_lib, primitive_model, circuit_port), circuit_lib.port_size(circuit_port)); + ModulePortId primitive_io_port_id = module_manager.add_port(primitive_module, module_port, module_io_port_type); + ModulePortId logic_io_port_id = module_manager.find_module_port(logic_module, circuit_lib.port_prefix(circuit_port)); + BasicPort logic_io_port = module_manager.module_port(logic_module, logic_io_port_id); + VTR_ASSERT(logic_io_port.get_width() == module_port.get_width()); + + /* Wire the GPIO port form primitive_module to the logic module!*/ + for (size_t pin_id = 0; pin_id < module_port.pins().size(); ++pin_id) { + ModuleNetId net = module_manager.create_module_net(primitive_module); + if ( (ModuleManager::MODULE_GPIO_PORT == module_io_port_type) + || (ModuleManager::MODULE_GPIN_PORT == module_io_port_type) ) { + module_manager.add_module_net_source(primitive_module, net, primitive_module, 0, primitive_io_port_id, module_port.pins()[pin_id]); + module_manager.add_module_net_sink(primitive_module, net, logic_module, logic_instance_id, logic_io_port_id, logic_io_port.pins()[pin_id]); + } else { + VTR_ASSERT(ModuleManager::MODULE_GPOUT_PORT == module_io_port_type); + module_manager.add_module_net_source(primitive_module, net, logic_module, logic_instance_id, logic_io_port_id, logic_io_port.pins()[pin_id]); + module_manager.add_module_net_sink(primitive_module, net, primitive_module, 0, primitive_io_port_id, module_port.pins()[pin_id]); + } + } +} + + /******************************************************************** * Print Verilog modules of a primitive node in the pb_graph_node graph * This generic function can support all the different types of primitive nodes @@ -274,18 +309,32 @@ void build_primitive_block_module(ModuleManager& module_manager, if (SPICE_MODEL_IOPAD == circuit_lib.model_type(primitive_model)) { std::vector primitive_model_inout_ports = circuit_lib.model_ports_by_type(primitive_model, SPICE_MODEL_PORT_INOUT); for (auto port : primitive_model_inout_ports) { - BasicPort module_port(generate_fpga_global_io_port_name(std::string(gio_inout_prefix), circuit_lib, primitive_model), circuit_lib.port_size(port)); - ModulePortId primitive_gpio_port_id = module_manager.add_port(primitive_module, module_port, ModuleManager::MODULE_GPIO_PORT); - ModulePortId logic_gpio_port_id = module_manager.find_module_port(logic_module, circuit_lib.port_prefix(port)); - BasicPort logic_gpio_port = module_manager.module_port(logic_module, logic_gpio_port_id); - VTR_ASSERT(logic_gpio_port.get_width() == module_port.get_width()); + add_primitive_module_fpga_global_io_port(module_manager, primitive_module, + logic_module, logic_instance_id, + ModuleManager::MODULE_GPIO_PORT, + circuit_lib, + primitive_model, + port); + } + } - /* Wire the GPIO port form primitive_module to the logic module!*/ - for (size_t pin_id = 0; pin_id < module_port.pins().size(); ++pin_id) { - ModuleNetId net = module_manager.create_module_net(primitive_module); - module_manager.add_module_net_source(primitive_module, net, primitive_module, 0, primitive_gpio_port_id, module_port.pins()[pin_id]); - module_manager.add_module_net_sink(primitive_module, net, logic_module, logic_instance_id, logic_gpio_port_id, logic_gpio_port.pins()[pin_id]); - } + /* Find the other i/o ports required by the primitive node, and add them to the module */ + for (const auto& port : circuit_lib.model_global_ports(primitive_model, false)) { + if ( (SPICE_MODEL_PORT_INPUT == circuit_lib.port_type(port)) + && (true == circuit_lib.port_is_io(port)) ) { + add_primitive_module_fpga_global_io_port(module_manager, primitive_module, + logic_module, logic_instance_id, + ModuleManager::MODULE_GPIN_PORT, + circuit_lib, + primitive_model, + port); + } else if (SPICE_MODEL_PORT_OUTPUT == circuit_lib.port_type(port)) { + add_primitive_module_fpga_global_io_port(module_manager, primitive_module, + logic_module, logic_instance_id, + ModuleManager::MODULE_GPOUT_PORT, + circuit_lib, + primitive_model, + port); } } } diff --git a/vpr7_x2p/vpr/SRC/fpga_x2p/verilog/verilog_writer_utils.cpp b/vpr7_x2p/vpr/SRC/fpga_x2p/verilog/verilog_writer_utils.cpp index 4d892f05f..19c7e59a4 100644 --- a/vpr7_x2p/vpr/SRC/fpga_x2p/verilog/verilog_writer_utils.cpp +++ b/vpr7_x2p/vpr/SRC/fpga_x2p/verilog/verilog_writer_utils.cpp @@ -125,6 +125,8 @@ void print_verilog_module_definition(std::fstream& fp, /* port type2type mapping */ std::map port_type2type_map; port_type2type_map[ModuleManager::MODULE_GLOBAL_PORT] = VERILOG_PORT_CONKT; + port_type2type_map[ModuleManager::MODULE_GPIN_PORT] = VERILOG_PORT_CONKT; + port_type2type_map[ModuleManager::MODULE_GPOUT_PORT] = VERILOG_PORT_CONKT; port_type2type_map[ModuleManager::MODULE_GPIO_PORT] = VERILOG_PORT_CONKT; port_type2type_map[ModuleManager::MODULE_INOUT_PORT] = VERILOG_PORT_CONKT; port_type2type_map[ModuleManager::MODULE_INPUT_PORT] = VERILOG_PORT_CONKT; @@ -184,6 +186,8 @@ void print_verilog_module_ports(std::fstream& fp, /* port type2type mapping */ std::map port_type2type_map; port_type2type_map[ModuleManager::MODULE_GLOBAL_PORT] = VERILOG_PORT_INPUT; + port_type2type_map[ModuleManager::MODULE_GPIN_PORT] = VERILOG_PORT_INPUT; + port_type2type_map[ModuleManager::MODULE_GPOUT_PORT] = VERILOG_PORT_OUTPUT; port_type2type_map[ModuleManager::MODULE_GPIO_PORT] = VERILOG_PORT_INOUT; port_type2type_map[ModuleManager::MODULE_INOUT_PORT] = VERILOG_PORT_INOUT; port_type2type_map[ModuleManager::MODULE_INPUT_PORT] = VERILOG_PORT_INPUT; @@ -340,6 +344,8 @@ void print_verilog_module_instance(std::fstream& fp, /* port type2type mapping */ std::map port_type2type_map; port_type2type_map[ModuleManager::MODULE_GLOBAL_PORT] = VERILOG_PORT_CONKT; + port_type2type_map[ModuleManager::MODULE_GPIN_PORT] = VERILOG_PORT_CONKT; + port_type2type_map[ModuleManager::MODULE_GPOUT_PORT] = VERILOG_PORT_CONKT; port_type2type_map[ModuleManager::MODULE_GPIO_PORT] = VERILOG_PORT_CONKT; port_type2type_map[ModuleManager::MODULE_INOUT_PORT] = VERILOG_PORT_CONKT; port_type2type_map[ModuleManager::MODULE_INPUT_PORT] = VERILOG_PORT_CONKT; From d39d7a68cee9ae63af0034d170e56e9d564c9fe8 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 7 Apr 2020 10:46:49 -0600 Subject: [PATCH 386/645] add test cases for using tree-like multiplexer --- .../k6_frac_N10_tree_mux_40nm_openfpga.xml | 256 ++++++++++++++++++ .../and_k6_frac_tileable_tree_mux.openfpga | 59 ++++ 2 files changed, 315 insertions(+) create mode 100644 openfpga/test_openfpga_arch/k6_frac_N10_tree_mux_40nm_openfpga.xml create mode 100644 openfpga/test_script/and_k6_frac_tileable_tree_mux.openfpga diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_tree_mux_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_tree_mux_40nm_openfpga.xml new file mode 100644 index 000000000..dc0fa4c27 --- /dev/null +++ b/openfpga/test_openfpga_arch/k6_frac_N10_tree_mux_40nm_openfpga.xml @@ -0,0 +1,256 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + + + + 10e-12 5e-12 + + + 10e-12 5e-12 + + + + + + + + + + + + 10e-12 5e-12 5e-12 + + + 10e-12 5e-12 5e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga/test_script/and_k6_frac_tileable_tree_mux.openfpga b/openfpga/test_script/and_k6_frac_tileable_tree_mux.openfpga new file mode 100644 index 000000000..95cd2a3c3 --- /dev/null +++ b/openfpga/test_script/and_k6_frac_tileable_tree_mux.openfpga @@ -0,0 +1,59 @@ +# Run VPR for the 'and' design +vpr ./test_vpr_arch/k6_frac_N10_tileable_40nm.xml ./test_blif/and.blif --clock_modeling route #--write_rr_graph example_rr_graph.xml + +# Read OpenFPGA architecture definition +read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_tree_mux_40nm_openfpga.xml + +# Write out the architecture XML as a proof +#write_openfpga_arch -f ./arch_echo.xml + +# Annotate the OpenFPGA architecture to VPR data base +link_openfpga_arch --activity_file ./test_blif/and.act --sort_gsb_chan_node_in_edges #--verbose + +# Check and correct any naming conflicts in the BLIF netlist +check_netlist_naming_conflict --fix --report ./netlist_renaming.xml + +# Apply fix-up to clustering nets based on routing results +pb_pin_fixup --verbose + +# Apply fix-up to Look-Up Table truth tables based on packing results +lut_truth_table_fixup #--verbose + +# Build the module graph +# - Enabled compression on routing architecture modules +# - Enable pin duplication on grid modules +build_fabric --compress_routing --duplicate_grid_pin #--verbose + +# Repack the netlist to physical pbs +# This must be done before bitstream generator and testbench generation +# Strongly recommend it is done after all the fix-up have been applied +repack #--verbose + +# Build the bitstream +# - Output the fabric-independent bitstream to a file +build_architecture_bitstream --verbose --file /var/tmp/xtang/openfpga_test_src/fabric_indepenent_bitstream.xml + +# Build fabric-dependent bitstream +build_fabric_bitstream --verbose + +# Write the Verilog netlist for FPGA fabric +# - Enable the use of explicit port mapping in Verilog netlist +write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src/SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose + +# Write the Verilog testbench for FPGA fabric +# - We suggest the use of same output directory as fabric Verilog netlists +# - Must specify the reference benchmark file if you want to output any testbenches +# - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA +# - Enable pre-configured top-level testbench which is a fast verification skipping programming phase +# - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts +write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src/SRC --reference_benchmark_file_path /var/tmp/xtang/and.v --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini /var/tmp/xtang/openfpga_test_src/simulation_deck.ini + +# Write the SDC files for PnR backend +# - Turn on every options here +write_pnr_sdc --file /var/tmp/xtang/openfpga_test_src/SDC + +# Write the SDC to run timing analysis for a mapped FPGA fabric +write_analysis_sdc --file /var/tmp/xtang/openfpga_test_src/SDC_analysis + +# Finish and exit OpenFPGA +exit From 6d6295ef93f93a50a151a32d1bff878a7ab2cbc1 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 7 Apr 2020 11:12:47 -0600 Subject: [PATCH 387/645] Add test cases about using standard cell mux2 --- .../k6_frac_N10_stdcell_mux_40nm_openfpga.xml | 252 ++++++++++++++++++ ...and_k6_frac_tileable_stdcell_mux2.openfpga | 59 ++++ 2 files changed, 311 insertions(+) create mode 100644 openfpga/test_openfpga_arch/k6_frac_N10_stdcell_mux_40nm_openfpga.xml create mode 100644 openfpga/test_script/and_k6_frac_tileable_stdcell_mux2.openfpga diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_stdcell_mux_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_stdcell_mux_40nm_openfpga.xml new file mode 100644 index 000000000..d3e3d1f6c --- /dev/null +++ b/openfpga/test_openfpga_arch/k6_frac_N10_stdcell_mux_40nm_openfpga.xml @@ -0,0 +1,252 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + + + + 10e-12 5e-12 + + + 10e-12 5e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga/test_script/and_k6_frac_tileable_stdcell_mux2.openfpga b/openfpga/test_script/and_k6_frac_tileable_stdcell_mux2.openfpga new file mode 100644 index 000000000..04ff99d8c --- /dev/null +++ b/openfpga/test_script/and_k6_frac_tileable_stdcell_mux2.openfpga @@ -0,0 +1,59 @@ +# Run VPR for the 'and' design +vpr ./test_vpr_arch/k6_frac_N10_tileable_40nm.xml ./test_blif/and.blif --clock_modeling route #--write_rr_graph example_rr_graph.xml + +# Read OpenFPGA architecture definition +read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_stdcell_mux_40nm_openfpga.xml + +# Write out the architecture XML as a proof +#write_openfpga_arch -f ./arch_echo.xml + +# Annotate the OpenFPGA architecture to VPR data base +link_openfpga_arch --activity_file ./test_blif/and.act --sort_gsb_chan_node_in_edges #--verbose + +# Check and correct any naming conflicts in the BLIF netlist +check_netlist_naming_conflict --fix --report ./netlist_renaming.xml + +# Apply fix-up to clustering nets based on routing results +pb_pin_fixup --verbose + +# Apply fix-up to Look-Up Table truth tables based on packing results +lut_truth_table_fixup #--verbose + +# Build the module graph +# - Enabled compression on routing architecture modules +# - Enable pin duplication on grid modules +build_fabric --compress_routing --duplicate_grid_pin #--verbose + +# Repack the netlist to physical pbs +# This must be done before bitstream generator and testbench generation +# Strongly recommend it is done after all the fix-up have been applied +repack #--verbose + +# Build the bitstream +# - Output the fabric-independent bitstream to a file +build_architecture_bitstream --verbose --file /var/tmp/xtang/openfpga_test_src/fabric_indepenent_bitstream.xml + +# Build fabric-dependent bitstream +build_fabric_bitstream --verbose + +# Write the Verilog netlist for FPGA fabric +# - Enable the use of explicit port mapping in Verilog netlist +write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src/SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose + +# Write the Verilog testbench for FPGA fabric +# - We suggest the use of same output directory as fabric Verilog netlists +# - Must specify the reference benchmark file if you want to output any testbenches +# - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA +# - Enable pre-configured top-level testbench which is a fast verification skipping programming phase +# - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts +write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src/SRC --reference_benchmark_file_path /var/tmp/xtang/and.v --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini /var/tmp/xtang/openfpga_test_src/simulation_deck.ini + +# Write the SDC files for PnR backend +# - Turn on every options here +write_pnr_sdc --file /var/tmp/xtang/openfpga_test_src/SDC + +# Write the SDC to run timing analysis for a mapped FPGA fabric +write_analysis_sdc --file /var/tmp/xtang/openfpga_test_src/SDC_analysis + +# Finish and exit OpenFPGA +exit From 0eeb8e5317e1fb8eadce40560f4bae814ce8949f Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 7 Apr 2020 11:24:42 -0600 Subject: [PATCH 388/645] clean up example architecture XML by removing redundant syntax --- openfpga/test_openfpga_arch/k6_N10_40nm_openfpga.xml | 6 ------ .../test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml | 6 ------ .../k6_frac_N10_adder_chain_40nm_openfpga.xml | 7 ------- .../k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml | 8 -------- .../k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml | 9 --------- .../k6_frac_N10_adder_column_chain_40nm_openfpga.xml | 7 ------- .../k6_frac_N10_spyio_40nm_openfpga.xml | 6 ------ .../k6_frac_N10_tree_mux_40nm_openfpga.xml | 5 ----- 8 files changed, 54 deletions(-) diff --git a/openfpga/test_openfpga_arch/k6_N10_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_N10_40nm_openfpga.xml index d93495f6b..17eb8b550 100644 --- a/openfpga/test_openfpga_arch/k6_N10_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_N10_40nm_openfpga.xml @@ -96,7 +96,6 @@ - @@ -106,7 +105,6 @@ - @@ -116,7 +114,6 @@ - @@ -127,7 +124,6 @@ - @@ -150,7 +146,6 @@ - @@ -161,7 +156,6 @@ - diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml index b750f9736..c43614ae8 100644 --- a/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml @@ -110,7 +110,6 @@ - @@ -120,7 +119,6 @@ - @@ -130,7 +128,6 @@ - @@ -141,7 +138,6 @@ - @@ -167,7 +163,6 @@ - @@ -178,7 +173,6 @@ - diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml index 98823698f..cf619cd27 100644 --- a/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml @@ -110,7 +110,6 @@ - @@ -120,7 +119,6 @@ - @@ -130,7 +128,6 @@ - @@ -141,7 +138,6 @@ - @@ -168,7 +164,6 @@ - @@ -179,7 +174,6 @@ - @@ -189,7 +183,6 @@ - diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml index 9f37c1293..4ddf1dd0b 100644 --- a/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml @@ -110,7 +110,6 @@ - @@ -120,7 +119,6 @@ - @@ -130,7 +128,6 @@ - @@ -141,7 +138,6 @@ - @@ -168,7 +164,6 @@ - @@ -179,7 +174,6 @@ - @@ -189,7 +183,6 @@ - @@ -200,7 +193,6 @@ - diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml index ea56fe036..f0e84e167 100644 --- a/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml @@ -110,7 +110,6 @@ - @@ -120,7 +119,6 @@ - @@ -130,7 +128,6 @@ - @@ -141,7 +138,6 @@ - @@ -168,7 +164,6 @@ - @@ -179,7 +174,6 @@ - @@ -189,7 +183,6 @@ - @@ -200,7 +193,6 @@ - @@ -213,7 +205,6 @@ - diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_adder_column_chain_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_adder_column_chain_40nm_openfpga.xml index 34f44d153..d70ebcb2d 100644 --- a/openfpga/test_openfpga_arch/k6_frac_N10_adder_column_chain_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_frac_N10_adder_column_chain_40nm_openfpga.xml @@ -110,7 +110,6 @@ - @@ -120,7 +119,6 @@ - @@ -130,7 +128,6 @@ - @@ -141,7 +138,6 @@ - @@ -168,7 +164,6 @@ - @@ -179,7 +174,6 @@ - @@ -189,7 +183,6 @@ - diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml index 2b432ce86..547c742c8 100644 --- a/openfpga/test_openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml @@ -110,7 +110,6 @@ - @@ -120,7 +119,6 @@ - @@ -130,7 +128,6 @@ - @@ -141,7 +138,6 @@ - @@ -167,7 +163,6 @@ - @@ -178,7 +173,6 @@ - diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_tree_mux_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_tree_mux_40nm_openfpga.xml index dc0fa4c27..bcbcb7e00 100644 --- a/openfpga/test_openfpga_arch/k6_frac_N10_tree_mux_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_frac_N10_tree_mux_40nm_openfpga.xml @@ -110,7 +110,6 @@ - @@ -120,7 +119,6 @@ - @@ -131,7 +129,6 @@ - @@ -157,7 +154,6 @@ - @@ -168,7 +164,6 @@ - From e61e7167b3fab389b0d51a2269b7cbcd684e0d2f Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 7 Apr 2020 11:27:16 -0600 Subject: [PATCH 389/645] update circuit model names in the example tree-like MUX architecture --- .../k6_frac_N10_tree_mux_40nm_openfpga.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_tree_mux_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_tree_mux_40nm_openfpga.xml index bcbcb7e00..8052fb2dc 100644 --- a/openfpga/test_openfpga_arch/k6_frac_N10_tree_mux_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_frac_N10_tree_mux_40nm_openfpga.xml @@ -75,7 +75,7 @@ 10e-12 5e-12 - + @@ -110,7 +110,7 @@ - + @@ -119,7 +119,7 @@ - + @@ -142,7 +142,7 @@ - + From 55e68896d6de137b8deb3043e2a3c117db681675 Mon Sep 17 00:00:00 2001 From: Xifan Tang Date: Tue, 7 Apr 2020 12:01:13 -0600 Subject: [PATCH 390/645] doc update for the support on std cell MUX2 and examples --- .../arch_lang/circuit_model_examples.rst | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/docs/source/arch_lang/circuit_model_examples.rst b/docs/source/arch_lang/circuit_model_examples.rst index 3a749e49d..9b689d2f7 100644 --- a/docs/source/arch_lang/circuit_model_examples.rst +++ b/docs/source/arch_lang/circuit_model_examples.rst @@ -260,7 +260,6 @@ Template - .. option:: - ``topology="AND|OR|MUX2"`` Specify the logic functionality of a gate. As for standard cells, the size of each port is limited to 1. Currently, only 2-input and single-output logic gates are supported. @@ -290,6 +289,29 @@ This example shows: - Propagation delay from input ``a`` to ``out`` is 10ps in rising edge and and 8ps in falling edge - Propagation delay from input ``b`` to ``out`` is 10ps in rising edge and 7ps in falling edge +MUX2 Gate Example +``````````````````````` + +.. code-block:: xml + + + + + + + + + + + +This example shows: + - A 2-input MUX gate with two inputs ``in0`` and ``in1``, a select port ``sel`` and an output port ``out`` + - The Verilog of MUX2 gate is provided by the user in the netlist ``sc_mux.v`` + - The use of ``lib_name`` to bind to a Verilog module with different port names. + - When binding to the Verilog module, the inputs will be swapped. In other words, ``in0`` of the circuit model will be wired to the input ``B`` of the MUX2 cell, while ``in1`` of the circuit model will be wired to the input ``A`` of the MUX2 cell. + +.. note:: OpenFPGA requires a fixed truth table for the ``MUX2`` gate. When the select signal sel is enabled, the first input, i.e., ``in0``, will be propagated to the output, i.e., ``out``. If your standard cell provider does not offer the exact truth table, you can simply swap the inputs as shown in the example. + Multiplexers ~~~~~~~~~~~~ @@ -330,6 +352,8 @@ Template .. note:: For tree-like multiplexers, they can be built with standard cell MUX2. To enable this, users should define a ``circuit_model``, which describes a 2-input multiplexer (See details and examples in how to define a logic gate using ``circuit_model``. In this case, the ``circuit_model_name`` in the ``pass_gate_logic`` should be the name of MUX2 ``circuit_model``. +.. note:: When multiplexers are not provided by users, the size of ports do not have to be consistent with actual numbers in the architecture. + One-level Mux Example ````````````````````` @@ -398,6 +422,26 @@ This example shows: - The multiplexer will be built by transmission gate using the circuit model ``tgate`` - The multiplexer will have 4 inputs and 3 SRAMs to control which datapath to propagate +Standard Cell Multiplexer Example +````````````````````````````````` +.. code-block:: xml + + + + + + + + + + + +This example shows: + - A tree-like 4-input CMOS multiplexer built by the standard cell ``MUX2`` + - All the inputs will be buffered using the circuit model ``inv1x`` + - All the outputs will be buffered using the circuit model ``tapbuf4`` + - The multiplexer will have 4 inputs and 3 SRAMs to control which datapath to propagate + Look-Up Tables ~~~~~~~~~~~~~~ From 307ef58c45ef00d3e5511b960d98c0f0156c636f Mon Sep 17 00:00:00 2001 From: Xifan Tang Date: Tue, 7 Apr 2020 12:30:23 -0600 Subject: [PATCH 391/645] update README to remove dead links --- README.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index efc0d0ad3..b6b67116c 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,10 @@ [![Documentation Status](https://readthedocs.org/projects/openfpga/badge/?version=master)](https://openfpga.readthedocs.io/en/master/?badge=master) ## Introduction -The OpenFPGA framework is the **first open-source FPGA IP generator** supporting highly-customizable homogeneous FPGA architectures. OpenFPGA provides a full set of EDA support for customized FPGAs, including Verilog-to-bitstream generation and self-testing verification [testbenches/scripts](./testbenches/scripts) OpenFPGA opens the door to democratizing FPGA technology and EDA techniques, with agile prototyping approaches and constantly evolving EDA tools for chip designers and researchers. +The OpenFPGA framework is the **first open-source FPGA IP generator** supporting highly-customizable homogeneous FPGA architectures. OpenFPGA provides a full set of EDA support for customized FPGAs, including Verilog-to-bitstream generation and self-testing verification. OpenFPGA opens the door to democratizing FPGA technology and EDA techniques, with agile prototyping approaches and constantly evolving EDA tools for chip designers and researchers. ## Compilation -Dependencies and help using docker can be found [**here**](./docs/source/tutorials/building.rst). +Dependencies and help using docker can be found [**here**](./docs/source/tutorials/compile.rst). **Compilation Steps:** ```bash @@ -31,7 +31,6 @@ python3 openfpga_flow/scripts/run_fpga_task.py compilation_verification --debug We currently target OpenFPGA for: 1. Ubuntu 18.04 2. Red Hat 7.5 - 3. MacOS Mojave 10.14.4 *The tool was tested with these operating systems. It might work with earlier versions and other distributions.* @@ -39,7 +38,4 @@ We currently target OpenFPGA for: OpenFPGA's [full documentation](https://openfpga.readthedocs.io/en/master/) includes tutorials, descriptions of the design flow, and tool options. ## Tutorials -You can find some tutorials in the [**./tutorials**](./tutorials) folder. This will help you get more familiar with the tool and use OpenFPGA under different configurations. - -Through those tutorials, users can learn how to use the flow and install the different dependencies. -The [tutorial index](./tutorials/tutorial_index.md) will guide you through training and explain the folder oraganization as well as introducing some tips and commonly used keywords. +You can find some tutorials in the [**./tutorials**](./docs/source/tutorials/) folder. This will help you get more familiar with the tool and use OpenFPGA under different configurations. From 26d1261c1f726d456db130d9429a20c27f74fa30 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 7 Apr 2020 15:09:10 -0600 Subject: [PATCH 392/645] add test cases using shift registers --- openfpga/src/repack/check_lb_rr_graph.cpp | 4 +- ...N10_adder_register_chain_40nm_openfpga.xml | 288 ++++++++ ...N10_tileable_adder_register_chain_40nm.xml | 696 ++++++++++++++++++ 3 files changed, 986 insertions(+), 2 deletions(-) create mode 100644 openfpga/test_openfpga_arch/k6_frac_N10_adder_register_chain_40nm_openfpga.xml create mode 100644 openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_register_chain_40nm.xml diff --git a/openfpga/src/repack/check_lb_rr_graph.cpp b/openfpga/src/repack/check_lb_rr_graph.cpp index 9b3adbf8f..64b286b62 100644 --- a/openfpga/src/repack/check_lb_rr_graph.cpp +++ b/openfpga/src/repack/check_lb_rr_graph.cpp @@ -79,8 +79,8 @@ static bool check_lb_rr_graph_dangling_nodes(const LbRRGraph& lb_rr_graph) { if ((0 == lb_rr_graph.node_in_edges(node).size()) && (0 == lb_rr_graph.node_out_edges(node).size())) { /* Print a warning! */ - VTR_LOG_WARN("Node %s is dangling (zero fan-in and zero fan-out)!\n", - node); + VTR_LOG_WARN("Node %lu is dangling (zero fan-in and zero fan-out)!\n", + size_t(node)); VTR_LOG_WARN("Node details for debugging:\n"); print_lb_rr_node(lb_rr_graph, node); no_dangling = false; diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_adder_register_chain_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_adder_register_chain_40nm_openfpga.xml new file mode 100644 index 000000000..81a9582dc --- /dev/null +++ b/openfpga/test_openfpga_arch/k6_frac_N10_adder_register_chain_40nm_openfpga.xml @@ -0,0 +1,288 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + + + + 10e-12 5e-12 + + + 10e-12 5e-12 + + + + + + + + + + + + 10e-12 5e-12 5e-12 + + + 10e-12 5e-12 5e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_register_chain_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_register_chain_40nm.xml new file mode 100644 index 000000000..77dedbcb0 --- /dev/null +++ b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_register_chain_40nm.xml @@ -0,0 +1,696 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + + + + + + + + + + + + + + + + + + + + + + + clb.clk + clb.cin clb.regin + clb.O[9:0] clb.I[19:0] + clb.cout clb.regout clb.O[19:10] clb.I[39:20] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 235e-12 + 235e-12 + 235e-12 + 235e-12 + 235e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 195e-12 + 195e-12 + 195e-12 + 195e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 628ea3b6546b08177b7444f5d59d18e53da0e8ca Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 7 Apr 2020 15:39:37 -0600 Subject: [PATCH 393/645] improve adder chain arch XML to support sequential output for sumout --- .../k6_frac_N10_adder_chain_40nm.xml | 27 +++++++++++-------- .../k6_frac_N10_adder_chain_mem16K_40nm.xml | 27 +++++++++++-------- .../k6_frac_N10_tileable_adder_chain_40nm.xml | 27 +++++++++++-------- ...c_N10_tileable_adder_chain_mem16K_40nm.xml | 27 +++++++++++-------- ...0_tileable_adder_chain_mem16K_aib_40nm.xml | 27 +++++++++++-------- ...er_chain_mem16K_multi_io_capacity_40nm.xml | 27 +++++++++++-------- ...ble_adder_chain_mem16K_reduced_io_40nm.xml | 26 ++++++++++-------- ..._tileable_adder_chain_wide_mem16K_40nm.xml | 27 +++++++++++-------- 8 files changed, 127 insertions(+), 88 deletions(-) diff --git a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml index 504431eb0..1fb82be72 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_40nm.xml @@ -371,21 +371,26 @@ - - - - - - - - - - + + + + + + + + + + + + + + + - + diff --git a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml index 3ecd20733..e0d7ce812 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_adder_chain_mem16K_40nm.xml @@ -408,21 +408,26 @@ - - - - - - - - - - + + + + + + + + + + + + + + + - + diff --git a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_40nm.xml index 19b27b88b..8f1dfd10e 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_40nm.xml @@ -371,21 +371,26 @@ - - - - - - - - - - + + + + + + + + + + + + + + + - + diff --git a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_40nm.xml index 4c0751db4..8254a0583 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_40nm.xml @@ -408,21 +408,26 @@ - - - - - - - - - - + + + + + + + + + + + + + + + - + diff --git a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_aib_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_aib_40nm.xml index 37dbb2b93..35eedb327 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_aib_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_aib_40nm.xml @@ -474,21 +474,26 @@ - - - - - - - - - - + + + + + + + + + + + + + + + - + diff --git a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_multi_io_capacity_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_multi_io_capacity_40nm.xml index ff7c3278b..d23c34960 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_multi_io_capacity_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_multi_io_capacity_40nm.xml @@ -442,21 +442,26 @@ - - - - - - - - - - + + + + + + + + + + + + + + + - + diff --git a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_reduced_io_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_reduced_io_40nm.xml index d9adc9056..66d0c9515 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_reduced_io_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_reduced_io_40nm.xml @@ -411,21 +411,25 @@ - - - - - - - - - - + + + + + + + + + + + + + + - + diff --git a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_wide_mem16K_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_wide_mem16K_40nm.xml index 7eda2aa2f..1bb8ffe23 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_wide_mem16K_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_wide_mem16K_40nm.xml @@ -408,21 +408,26 @@ - - - - - - - - - - + + + + + + + + + + + + + + + - + From 6daee8c2c84673382402063d191fe27ebcdc0cd9 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 7 Apr 2020 16:03:34 -0600 Subject: [PATCH 394/645] bug fixed in the example architecture --- .../k6_frac_N10_tileable_adder_chain_mem16K_reduced_io_40nm.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_reduced_io_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_reduced_io_40nm.xml index 66d0c9515..bb06c5f39 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_reduced_io_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_chain_mem16K_reduced_io_40nm.xml @@ -418,6 +418,7 @@ + From 5a04da2082563d2b1d3e7469dfce1d37831f6942 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 7 Apr 2020 16:14:41 -0600 Subject: [PATCH 395/645] fix memory leakage in openfpga title --- openfpga/src/base/openfpga_title.cpp | 4 ++-- openfpga/src/base/openfpga_title.h | 2 +- openfpga/src/main.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openfpga/src/base/openfpga_title.cpp b/openfpga/src/base/openfpga_title.cpp index 41616c8bd..ca39fe2e6 100644 --- a/openfpga/src/base/openfpga_title.cpp +++ b/openfpga/src/base/openfpga_title.cpp @@ -8,7 +8,7 @@ * Generate a string of openfpga title introduction * This is mainly used when launching OpenFPGA shell *******************************************************************/ -const char* create_openfpga_title() { +std::string create_openfpga_title() { std::string title; title += std::string("\n"); @@ -49,5 +49,5 @@ const char* create_openfpga_title() { title += std::string("THE SOFTWARE.\n"); title += std::string("\n"); - return title.c_str(); + return title; } diff --git a/openfpga/src/base/openfpga_title.h b/openfpga/src/base/openfpga_title.h index b4826964a..8f4ab1e90 100644 --- a/openfpga/src/base/openfpga_title.h +++ b/openfpga/src/base/openfpga_title.h @@ -9,6 +9,6 @@ /******************************************************************** * Function declaration *******************************************************************/ -const char* create_openfpga_title(); +std::string create_openfpga_title(); #endif diff --git a/openfpga/src/main.cpp b/openfpga/src/main.cpp index eb88c2c65..e1f813e9b 100644 --- a/openfpga/src/main.cpp +++ b/openfpga/src/main.cpp @@ -48,7 +48,7 @@ int main(int argc, char** argv) { */ openfpga::Shell shell("OpenFPGA"); - shell.add_title(create_openfpga_title()); + shell.add_title(create_openfpga_title().c_str()); /* Add vpr commands */ openfpga::add_vpr_commands(shell); From cbcd1d20d465d0fd77a7032c6a4cafc621e1390b Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 7 Apr 2020 16:24:04 -0600 Subject: [PATCH 396/645] fixed memory leakage in pb_pin fixup --- openfpga/src/base/openfpga_pb_pin_fixup.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openfpga/src/base/openfpga_pb_pin_fixup.cpp b/openfpga/src/base/openfpga_pb_pin_fixup.cpp index 3e6fec2f7..cc91bdebe 100644 --- a/openfpga/src/base/openfpga_pb_pin_fixup.cpp +++ b/openfpga/src/base/openfpga_pb_pin_fixup.cpp @@ -121,7 +121,8 @@ void update_cluster_pin_with_post_routing_results(const DeviceContext& device_ct } /* Ignore used in local cluster only, reserved one CLB pin */ - if (false == clustering_ctx.clb_nlist.net_sinks(cluster_net_id).size()) { + if ( (ClusterNetId::INVALID() != cluster_net_id) + && (0 == clustering_ctx.clb_nlist.net_sinks(cluster_net_id).size())) { continue; } From 50bb04d49621154a1baebf0404dc63219629e94b Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 7 Apr 2020 16:50:41 -0600 Subject: [PATCH 397/645] add scan-chain test case. Debugging on the way --- ...dder_register_scan_chain_40nm_openfpga.xml | 294 +++++++ ...ileable_adder_register_scan_chain.openfpga | 62 ++ ...ileable_adder_register_scan_chain_40nm.xml | 735 ++++++++++++++++++ 3 files changed, 1091 insertions(+) create mode 100644 openfpga/test_openfpga_arch/k6_frac_N10_adder_register_scan_chain_40nm_openfpga.xml create mode 100644 openfpga/test_script/and_k6_frac_tileable_adder_register_scan_chain.openfpga create mode 100644 openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_register_scan_chain_40nm.xml diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_adder_register_scan_chain_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_adder_register_scan_chain_40nm_openfpga.xml new file mode 100644 index 000000000..840a2e3bb --- /dev/null +++ b/openfpga/test_openfpga_arch/k6_frac_N10_adder_register_scan_chain_40nm_openfpga.xml @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + + + + 10e-12 5e-12 + + + 10e-12 5e-12 + + + + + + + + + + + + 10e-12 5e-12 5e-12 + + + 10e-12 5e-12 5e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga/test_script/and_k6_frac_tileable_adder_register_scan_chain.openfpga b/openfpga/test_script/and_k6_frac_tileable_adder_register_scan_chain.openfpga new file mode 100644 index 000000000..0ef91ab9d --- /dev/null +++ b/openfpga/test_script/and_k6_frac_tileable_adder_register_scan_chain.openfpga @@ -0,0 +1,62 @@ +# Run VPR for the 'and' design +vpr ./test_vpr_arch/k6_frac_N10_tileable_adder_register_scan_chain_40nm.xml ./test_blif/and.blif --route_chan_width 40 --clock_modeling route #--write_rr_graph example_rr_graph.xml + +# Read OpenFPGA architecture definition +read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_adder_register_scan_chain_40nm_openfpga.xml + +# Write out the architecture XML as a proof +#write_openfpga_arch -f ./arch_echo.xml + +# Annotate the OpenFPGA architecture to VPR data base +link_openfpga_arch --activity_file ./test_blif/and.act --sort_gsb_chan_node_in_edges #--verbose + +# Write GSB to XML for debugging +write_gsb_to_xml --file /var/tmp/xtang/openfpga_test_src/gsb_xml + +# Check and correct any naming conflicts in the BLIF netlist +check_netlist_naming_conflict --fix --report ./netlist_renaming.xml + +# Apply fix-up to clustering nets based on routing results +pb_pin_fixup --verbose + +# Apply fix-up to Look-Up Table truth tables based on packing results +lut_truth_table_fixup #--verbose + +# Build the module graph +# - Enabled compression on routing architecture modules +# - Enable pin duplication on grid modules +build_fabric --compress_routing --duplicate_grid_pin --verbose + +# Repack the netlist to physical pbs +# This must be done before bitstream generator and testbench generation +# Strongly recommend it is done after all the fix-up have been applied +repack #--verbose + +# Build the bitstream +# - Output the fabric-independent bitstream to a file +build_architecture_bitstream --verbose --file /var/tmp/xtang/openfpga_test_src/fabric_indepenent_bitstream.xml + +# Build fabric-dependent bitstream +build_fabric_bitstream --verbose + +# Write the Verilog netlist for FPGA fabric +# - Enable the use of explicit port mapping in Verilog netlist +write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src/SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose + +# Write the Verilog testbench for FPGA fabric +# - We suggest the use of same output directory as fabric Verilog netlists +# - Must specify the reference benchmark file if you want to output any testbenches +# - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA +# - Enable pre-configured top-level testbench which is a fast verification skipping programming phase +# - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts +write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src/SRC --reference_benchmark_file_path /var/tmp/xtang/and.v --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini /var/tmp/xtang/openfpga_test_src/simulation_deck.ini + +# Write the SDC files for PnR backend +# - Turn on every options here +write_pnr_sdc --file /var/tmp/xtang/openfpga_test_src/SDC + +# Write the SDC to run timing analysis for a mapped FPGA fabric +write_analysis_sdc --file /var/tmp/xtang/openfpga_test_src/SDC_analysis + +# Finish and exit OpenFPGA +exit diff --git a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_register_scan_chain_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_register_scan_chain_40nm.xml new file mode 100644 index 000000000..3b79b1df0 --- /dev/null +++ b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_register_scan_chain_40nm.xml @@ -0,0 +1,735 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + + + + + + + + + + + + + + + + + + + + + + + + + + + clb.clk + clb.cin clb.regin clb.scin + clb.O[9:0] clb.I[19:0] + clb.cout clb.regout clb.scout clb.O[19:10] clb.I[39:20] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 235e-12 + 235e-12 + 235e-12 + 235e-12 + 235e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 195e-12 + 195e-12 + 195e-12 + 195e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 2342d7cdc6c54f9cdeccc59315640e8392f041fa Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 7 Apr 2020 17:03:44 -0600 Subject: [PATCH 398/645] minor tweak on the scan-chain support in VPR8 as well as architecture file Do NOT use pack patterns for the scan-chain. It will cause searching root chain in VPR8 to fail Actually, we do not use scan-chain in mapping designs. Disable the pack pattern has no impact --- ...k6_frac_N10_tileable_adder_register_scan_chain_40nm.xml | 3 --- vpr/src/pack/prepack.cpp | 7 +++++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_register_scan_chain_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_register_scan_chain_40nm.xml index 3b79b1df0..0bce05d74 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_register_scan_chain_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_register_scan_chain_40nm.xml @@ -720,13 +720,10 @@ - - - diff --git a/vpr/src/pack/prepack.cpp b/vpr/src/pack/prepack.cpp index cf9ccdd6d..dd9d1da3f 100644 --- a/vpr/src/pack/prepack.cpp +++ b/vpr/src/pack/prepack.cpp @@ -1557,6 +1557,13 @@ static t_pb_graph_pin* get_connected_primitive_pin(const t_pb_graph_pin* cluster * will be only one pin connected to the very first adder in the cluster. */ static void get_all_connected_primitive_pins(const t_pb_graph_pin* cluster_input_pin, std::vector& connected_primitive_pins) { + + /* Xifan Tang: Skip pins belong to unpackable modes */ + if ( (nullptr != cluster_input_pin->parent_node->pb_type->parent_mode) + && (false == cluster_input_pin->parent_node->pb_type->parent_mode->packable) ) { + return; + } + for (int iedge = 0; iedge < cluster_input_pin->num_output_edges; iedge++) { const auto& output_edge = cluster_input_pin->output_edges[iedge]; for (int ipin = 0; ipin < output_edge->num_output_pins; ipin++) { From ff7ea9938154a939c617ad92c65d9be18c727842 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 7 Apr 2020 17:06:16 -0600 Subject: [PATCH 399/645] bug fixed in register scan-chain architecture --- ...rac_N10_tileable_adder_register_scan_chain_40nm.xml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_register_scan_chain_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_register_scan_chain_40nm.xml index 0bce05d74..f83919c7c 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_register_scan_chain_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_register_scan_chain_40nm.xml @@ -439,10 +439,12 @@ - - - - + + + + + + From dd4f83a374b0b2ef91832abe9d0b2b7bbdc854a6 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 7 Apr 2020 18:28:19 -0600 Subject: [PATCH 400/645] bug fixing to constant string to display interconnect names --- libs/libarchfpga/src/physical_types.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/libarchfpga/src/physical_types.h b/libs/libarchfpga/src/physical_types.h index c9b44cd92..3d97fee47 100644 --- a/libs/libarchfpga/src/physical_types.h +++ b/libs/libarchfpga/src/physical_types.h @@ -166,7 +166,7 @@ enum e_interconnect { NUM_INTERC_TYPES /* Xifan Tang - Invalid types for interconnect */ }; /* Xifan Tang - String versions of interconnection type */ -constexpr std::array INTERCONNECT_TYPE_STRING = {{"complete", "direct", "mux"}}; +constexpr std::array INTERCONNECT_TYPE_STRING = {{"unknown", "complete", "direct", "mux"}}; /* Orientations. */ enum e_side : unsigned char { From 62276f9e2865a915b211a7a17333dd013c35b15d Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 7 Apr 2020 18:43:11 -0600 Subject: [PATCH 401/645] minor code format --- openfpga/src/annotation/annotate_pb_graph.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openfpga/src/annotation/annotate_pb_graph.cpp b/openfpga/src/annotation/annotate_pb_graph.cpp index 2b78f2f0e..c930a20b6 100644 --- a/openfpga/src/annotation/annotate_pb_graph.cpp +++ b/openfpga/src/annotation/annotate_pb_graph.cpp @@ -89,7 +89,9 @@ void rec_build_vpr_pb_graph_interconnect_physical_type_annotation(t_pb_graph_nod } VTR_LOGV(verbose_output, "Infer physical type '%s' of interconnect '%s' (was '%s')\n", - INTERCONNECT_TYPE_STRING[interc_physical_type], interc->name, INTERCONNECT_TYPE_STRING[interc->type]); + INTERCONNECT_TYPE_STRING[interc_physical_type], + interc->name, + INTERCONNECT_TYPE_STRING[interc->type]); vpr_device_annotation.add_interconnect_physical_type(interc, interc_physical_type); } } From 0b1c8ac139ea2ba29d366e5492bb1c349055b52b Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 7 Apr 2020 19:46:42 -0600 Subject: [PATCH 402/645] bug fixed in identifying the physical interconnect for pb_graph nodes --- openfpga/src/annotation/annotate_pb_graph.cpp | 130 ++++++++++++++++-- openfpga/test_script/and_k6_frac.openfpga | 2 +- 2 files changed, 119 insertions(+), 13 deletions(-) diff --git a/openfpga/src/annotation/annotate_pb_graph.cpp b/openfpga/src/annotation/annotate_pb_graph.cpp index c930a20b6..081acf900 100644 --- a/openfpga/src/annotation/annotate_pb_graph.cpp +++ b/openfpga/src/annotation/annotate_pb_graph.cpp @@ -49,17 +49,48 @@ void rec_build_vpr_pb_graph_interconnect_physical_type_annotation(t_pb_graph_nod VTR_ASSERT(nullptr != child_physical_mode); std::map interc_num_inputs; - /* Initialize the counter */ - for (t_interconnect* interc : pb_mode_interconnects(child_physical_mode)) { - interc_num_inputs[interc] = 0; + + /* Find all the interconnects sourced from the input and clock pins */ + for (int iport = 0; iport < pb_graph_node->num_input_ports; ++iport) { + for (int ipin = 0; ipin < pb_graph_node->num_input_pins[iport]; ++ipin) { + for (int iedge = 0; iedge < pb_graph_node->input_pins[iport][ipin].num_input_edges; ++iedge) { + t_interconnect* interc = pb_graph_node->input_pins[iport][ipin].input_edges[iedge]->interconnect; + /* Ensure that the interconnect is unique in the list */ + if (0 < interc_num_inputs.count(interc)) { + continue; + } + /* Unique interconnect, initialize the counter to be zero */ + interc_num_inputs[interc] = 0; + } + } + } + + for (int iport = 0; iport < pb_graph_node->num_clock_ports; ++iport) { + for (int ipin = 0; ipin < pb_graph_node->num_clock_pins[iport]; ++ipin) { + for (int iedge = 0; iedge < pb_graph_node->clock_pins[iport][ipin].num_input_edges; ++iedge) { + t_interconnect* interc = pb_graph_node->clock_pins[iport][ipin].input_edges[iedge]->interconnect; + /* Ensure that the interconnect is unique in the list */ + if (0 < interc_num_inputs.count(interc)) { + continue; + } + /* Unique interconnect, initialize the counter to be zero */ + interc_num_inputs[interc] = 0; + } + } + } + + /* Check: all the element should be initialized to 0 */ + for (const auto& pair : interc_num_inputs) { + VTR_ASSERT(nullptr != pair.first); + VTR_ASSERT(0 == pair.second); } /* We only care input and clock pins */ for (int iport = 0; iport < pb_graph_node->num_input_ports; ++iport) { for (int ipin = 0; ipin < pb_graph_node->num_input_pins[iport]; ++ipin) { /* For each interconnect, we count the total number of inputs */ - for (t_interconnect* interc : pb_mode_interconnects(child_physical_mode)) { - interc_num_inputs[interc] += pb_graph_pin_inputs(&(pb_graph_node->input_pins[iport][ipin]), interc).size(); + for (const auto& pair : interc_num_inputs) { + interc_num_inputs[pair.first] += pb_graph_pin_inputs(&(pb_graph_node->input_pins[iport][ipin]), pair.first).size(); } } } @@ -67,22 +98,24 @@ void rec_build_vpr_pb_graph_interconnect_physical_type_annotation(t_pb_graph_nod for (int iport = 0; iport < pb_graph_node->num_clock_ports; ++iport) { for (int ipin = 0; ipin < pb_graph_node->num_clock_pins[iport]; ++ipin) { /* For each interconnect, we count the total number of inputs */ - for (t_interconnect* interc : pb_mode_interconnects(child_physical_mode)) { - interc_num_inputs[interc] += pb_graph_pin_inputs(&(pb_graph_node->clock_pins[iport][ipin]), interc).size(); + for (const auto& pair : interc_num_inputs) { + interc_num_inputs[pair.first] += pb_graph_pin_inputs(&(pb_graph_node->clock_pins[iport][ipin]), pair.first).size(); } } } /* For each interconnect that has more than 1 input, we can infer the physical type */ - for (t_interconnect* interc : pb_mode_interconnects(child_physical_mode)) { + for (const auto& pair : interc_num_inputs) { + t_interconnect* interc = pair.first; + size_t actual_interc_num_inputs = pair.second; /* If the number inputs for an interconnect is zero, this is a 0-driver pin * we just set 1 to use direct wires */ - if (0 == interc_num_inputs[interc]) { - interc_num_inputs[interc] = 1; + if (0 == actual_interc_num_inputs) { + actual_interc_num_inputs = 1; } - e_interconnect interc_physical_type = pb_interconnect_physical_type(interc, interc_num_inputs[interc]); + e_interconnect interc_physical_type = pb_interconnect_physical_type(interc, actual_interc_num_inputs); if (interc_physical_type == vpr_device_annotation.interconnect_physical_type(interc)) { /* Skip annotation if we have already done! */ continue; @@ -101,9 +134,82 @@ void rec_build_vpr_pb_graph_interconnect_physical_type_annotation(t_pb_graph_nod return; } - /* Recursively visit all the child pb_graph_nodes */ + /* Find the physical mode of current pb_graph node */ t_mode* physical_mode = vpr_device_annotation.physical_mode(pb_graph_node->pb_type); VTR_ASSERT(nullptr != physical_mode); + + /* Before going recursive, we should check the interconnect between output pins + * Note that this is NOT applicable to primitive pb_graph nodes!!! + * + * pb_graph_node + * -------------------------------+ + * | + * child_pb_graph_node | + * -------------------+ | + * | | + * output_pin +<----------+ output_pin + * | | + * -------------------+ | + * + */ + { /* Use a code block to use local variables freely */ + std::map interc_num_inputs; + /* Find all the interconnects sourced from the output pins */ + for (int iport = 0; iport < pb_graph_node->num_output_ports; ++iport) { + for (int ipin = 0; ipin < pb_graph_node->num_output_pins[iport]; ++ipin) { + for (int iedge = 0; iedge < pb_graph_node->output_pins[iport][ipin].num_input_edges; ++iedge) { + t_interconnect* interc = pb_graph_node->output_pins[iport][ipin].input_edges[iedge]->interconnect; + /* Ensure that the interconnect is unique in the list */ + if (0 < interc_num_inputs.count(interc)) { + continue; + } + /* Unique interconnect, initialize the counter to be zero */ + interc_num_inputs[interc] = 0; + } + } + } + + /* Check: all the element should be initialized to 0 */ + for (const auto& pair : interc_num_inputs) { + VTR_ASSERT(nullptr != pair.first); + VTR_ASSERT(0 == pair.second); + } + + /* We only care input and clock pins */ + for (int iport = 0; iport < pb_graph_node->num_output_ports; ++iport) { + for (int ipin = 0; ipin < pb_graph_node->num_output_pins[iport]; ++ipin) { + /* For each interconnect, we count the total number of inputs */ + for (const auto& pair : interc_num_inputs) { + interc_num_inputs[pair.first] += pb_graph_pin_inputs(&(pb_graph_node->output_pins[iport][ipin]), pair.first).size(); + } + } + } + /* For each interconnect that has more than 1 input, we can infer the physical type */ + for (const auto& pair : interc_num_inputs) { + t_interconnect* interc = pair.first; + size_t actual_interc_num_inputs = pair.second; + /* If the number inputs for an interconnect is zero, this is a 0-driver pin + * we just set 1 to use direct wires + */ + if (0 == actual_interc_num_inputs) { + actual_interc_num_inputs = 1; + } + + e_interconnect interc_physical_type = pb_interconnect_physical_type(interc, actual_interc_num_inputs); + if (interc_physical_type == vpr_device_annotation.interconnect_physical_type(interc)) { + /* Skip annotation if we have already done! */ + continue; + } + VTR_LOGV(verbose_output, + "Infer physical type '%s' of interconnect '%s' (was '%s')\n", + INTERCONNECT_TYPE_STRING[interc_physical_type], + interc->name, + INTERCONNECT_TYPE_STRING[interc->type]); + vpr_device_annotation.add_interconnect_physical_type(interc, interc_physical_type); + } + } + + /* Recursively visit all the child pb_graph_nodes */ for (int ipb = 0; ipb < physical_mode->num_pb_type_children; ++ipb) { /* Each child may exist multiple times in the hierarchy*/ for (int jpb = 0; jpb < physical_mode->pb_type_children[ipb].num_pb; ++jpb) { diff --git a/openfpga/test_script/and_k6_frac.openfpga b/openfpga/test_script/and_k6_frac.openfpga index 63746cd71..e14811f49 100644 --- a/openfpga/test_script/and_k6_frac.openfpga +++ b/openfpga/test_script/and_k6_frac.openfpga @@ -8,7 +8,7 @@ read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml #write_openfpga_arch -f ./arch_echo.xml # Annotate the OpenFPGA architecture to VPR data base -link_openfpga_arch --activity_file ./test_blif/and.act --sort_gsb_chan_node_in_edges #--verbose +link_openfpga_arch --activity_file ./test_blif/and.act --sort_gsb_chan_node_in_edges --verbose # Check and correct any naming conflicts in the BLIF netlist check_netlist_naming_conflict --fix --report ./netlist_renaming.xml From 33315f0521a0d6b4054f3b66291525ee8febbf93 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 7 Apr 2020 20:46:45 -0600 Subject: [PATCH 403/645] now openfpga shell allow empty space at beginning and end of each line in script mode --- libopenfpga/libopenfpgashell/src/shell.tpp | 8 ++++++++ ...nd_k6_frac_tileable_adder_register_scan_chain.openfpga | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/libopenfpga/libopenfpgashell/src/shell.tpp b/libopenfpga/libopenfpgashell/src/shell.tpp index 24ef13dea..2bc4a503a 100644 --- a/libopenfpga/libopenfpgashell/src/shell.tpp +++ b/libopenfpga/libopenfpgashell/src/shell.tpp @@ -302,6 +302,14 @@ void Shell::run_script_mode(const char* script_file_name, T& context) { if (cmd_end_pos != std::string::npos) { cmd_part = line.substr(0, cmd_end_pos); } + /* Remove the space at the beginning of the line */ + cmd_part.erase(cmd_part.begin(), std::find_if(cmd_part.begin(), cmd_part.end(), [](int ch) { + return !std::isspace(ch); + })); + /* Remove the space at the end of the line */ + cmd_part.erase(std::find_if(cmd_part.rbegin(), cmd_part.rend(), [](int ch) { + return !std::isspace(ch); + }).base(), cmd_part.end()); /* Process the command only when the line is not empty */ if (!cmd_part.empty()) { execute_command(cmd_part.c_str(), context); diff --git a/openfpga/test_script/and_k6_frac_tileable_adder_register_scan_chain.openfpga b/openfpga/test_script/and_k6_frac_tileable_adder_register_scan_chain.openfpga index 0ef91ab9d..ae10ebb34 100644 --- a/openfpga/test_script/and_k6_frac_tileable_adder_register_scan_chain.openfpga +++ b/openfpga/test_script/and_k6_frac_tileable_adder_register_scan_chain.openfpga @@ -28,8 +28,8 @@ lut_truth_table_fixup #--verbose build_fabric --compress_routing --duplicate_grid_pin --verbose # Repack the netlist to physical pbs -# This must be done before bitstream generator and testbench generation -# Strongly recommend it is done after all the fix-up have been applied + # This must be done before bitstream generator and testbench generation + # Strongly recommend it is done after all the fix-up have been applied repack #--verbose # Build the bitstream From e31dc1f2f21f76eac7b49e991cf0ed87c525e636 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 7 Apr 2020 21:27:51 -0600 Subject: [PATCH 404/645] openfpga shell now support continued line charactor '\' --- libopenfpga/libopenfpgashell/src/shell.tpp | 47 +++++++++++++++---- ...ileable_adder_register_scan_chain.openfpga | 7 +-- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/libopenfpga/libopenfpgashell/src/shell.tpp b/libopenfpga/libopenfpgashell/src/shell.tpp index 2bc4a503a..5fe043f8e 100644 --- a/libopenfpga/libopenfpgashell/src/shell.tpp +++ b/libopenfpga/libopenfpgashell/src/shell.tpp @@ -289,6 +289,11 @@ void Shell::run_script_mode(const char* script_file_name, T& context) { return; } + /* Consider that each line may not end due to the continued line charactor + * Use cmd_line to conjunct multiple lines + */ + std::string cmd_line; + /* Read line by line */ while (getline(fp, line)) { /* If the line that starts with '#', it is commented, we can skip */ @@ -302,17 +307,43 @@ void Shell::run_script_mode(const char* script_file_name, T& context) { if (cmd_end_pos != std::string::npos) { cmd_part = line.substr(0, cmd_end_pos); } - /* Remove the space at the beginning of the line */ - cmd_part.erase(cmd_part.begin(), std::find_if(cmd_part.begin(), cmd_part.end(), [](int ch) { - return !std::isspace(ch); - })); - /* Remove the space at the end of the line */ + + /* Remove the space at the end of the line + * So that we can check easily if there is a continued line in the end + */ cmd_part.erase(std::find_if(cmd_part.rbegin(), cmd_part.rend(), [](int ch) { return !std::isspace(ch); }).base(), cmd_part.end()); - /* Process the command only when the line is not empty */ - if (!cmd_part.empty()) { - execute_command(cmd_part.c_str(), context); + + /* If the line ends with '\', this is a continued line, parse the next until it ends */ + if ('\\' == cmd_part.back()) { + /* Pop up the last charactor and conjunct to cmd_line */ + cmd_part.pop_back(); + + if (!cmd_part.empty()) { + cmd_line += cmd_part; + } + /* Not finished yet. Parse the next line */ + continue; + } else { + /* End of this line, if cmd_line is empty, + * there is no previous lines, cache the part we have + * and then execute the command + */ + cmd_line += cmd_part; + } + + /* Remove the space at the beginning of the line */ + cmd_line.erase(cmd_line.begin(), std::find_if(cmd_line.begin(), cmd_line.end(), [](int ch) { + return !std::isspace(ch); + })); + + /* Process the command only when the full command line in ended */ + if (!cmd_line.empty()) { + VTR_LOG("\nCommand line to execute: %s\n", cmd_line.c_str()); + execute_command(cmd_line.c_str(), context); + /* Empty the line ready to start a new line */ + cmd_line.clear(); } } fp.close(); diff --git a/openfpga/test_script/and_k6_frac_tileable_adder_register_scan_chain.openfpga b/openfpga/test_script/and_k6_frac_tileable_adder_register_scan_chain.openfpga index ae10ebb34..864fea78d 100644 --- a/openfpga/test_script/and_k6_frac_tileable_adder_register_scan_chain.openfpga +++ b/openfpga/test_script/and_k6_frac_tileable_adder_register_scan_chain.openfpga @@ -28,8 +28,8 @@ lut_truth_table_fixup #--verbose build_fabric --compress_routing --duplicate_grid_pin --verbose # Repack the netlist to physical pbs - # This must be done before bitstream generator and testbench generation - # Strongly recommend it is done after all the fix-up have been applied +# This must be done before bitstream generator and testbench generation +# Strongly recommend it is done after all the fix-up have been applied repack #--verbose # Build the bitstream @@ -56,7 +56,8 @@ write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src/SRC --reference_ write_pnr_sdc --file /var/tmp/xtang/openfpga_test_src/SDC # Write the SDC to run timing analysis for a mapped FPGA fabric -write_analysis_sdc --file /var/tmp/xtang/openfpga_test_src/SDC_analysis +write_analysis_sdc \ + --file /var/tmp/xtang/openfpga_test_src/SDC_analysis # Finish and exit OpenFPGA exit From 583a4d87671de2f93dbe58afa458a7c3d62664c9 Mon Sep 17 00:00:00 2001 From: ganeshgore Date: Wed, 8 Apr 2020 12:04:08 -0600 Subject: [PATCH 405/645] Fixed bug in openfpga_flow script --- openfpga_flow/scripts/run_fpga_flow.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/openfpga_flow/scripts/run_fpga_flow.py b/openfpga_flow/scripts/run_fpga_flow.py index cd9a80f81..434163410 100644 --- a/openfpga_flow/scripts/run_fpga_flow.py +++ b/openfpga_flow/scripts/run_fpga_flow.py @@ -80,9 +80,6 @@ parser.add_argument('--run_dir', type=str, default=os.path.join(openfpga_base_dir, 'tmp'), help="Directory to store intermidiate file & final results") parser.add_argument('--openfpga_shell_template', type=str, - default=os.path.join(openfpga_base_dir, 'openfpga_flow', - 'OpenFPGAShellScripts', - 'example_script.openfpga'), help="Sample openfpga shell script") parser.add_argument('--openfpga_arch_file', type=str, help="Openfpga architecture file for shell") @@ -258,6 +255,7 @@ def main(): # if (args.fpga_flow == "vtr_standard"): # run_abc_for_standarad() if args.openfpga_shell_template: + logger.info("Runing OpenFPGA Shell Engine ") run_openfpga_shell() else: run_vpr() @@ -591,13 +589,15 @@ def collect_files_for_vpr(): shutil.copy(args.base_verilog, args.top_module+"_output_verilog.v") # Sanitize provided openshell template, if provided - if not os.path.isfile(args.openfpga_shell_template or ""): - logger.error("Openfpga shell file - %s" % args.openfpga_shell_template) - clean_up_and_exit("Provided openfpga_shell_template" + - f" {args.openfpga_shell_template} file not found") - else: - shutil.copy(args.openfpga_shell_template, - args.top_module+"_template.openfpga") + if (args.openfpga_shell_template): + if not os.path.isfile(args.openfpga_shell_template or ""): + logger.error("Openfpga shell file - %s" % + args.openfpga_shell_template) + clean_up_and_exit("Provided openfpga_shell_template" + + f" {args.openfpga_shell_template} file not found") + else: + shutil.copy(args.openfpga_shell_template, + args.top_module+"_template.openfpga") def run_vpr(): From 1fb37f4c719a494af835feeda7911304471bc358 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 8 Apr 2020 12:55:09 -0600 Subject: [PATCH 406/645] improve directory creator to support same functionality as 'mkdir -p' --- .../libopenfpgautil/src/openfpga_digest.cpp | 137 ++++++++++++++---- .../libopenfpgautil/src/openfpga_digest.h | 2 +- .../annotation/write_xml_device_rr_gsb.cpp | 2 +- openfpga/src/base/openfpga_bitstream.cpp | 2 +- openfpga/src/base/openfpga_sdc.cpp | 4 +- openfpga/src/fpga_verilog/verilog_api.cpp | 10 +- 6 files changed, 121 insertions(+), 36 deletions(-) diff --git a/libopenfpga/libopenfpgautil/src/openfpga_digest.cpp b/libopenfpga/libopenfpgautil/src/openfpga_digest.cpp index 4714700e4..3ae2083df 100644 --- a/libopenfpga/libopenfpgautil/src/openfpga_digest.cpp +++ b/libopenfpga/libopenfpgautil/src/openfpga_digest.cpp @@ -3,6 +3,7 @@ * in OpenFPGA framework *******************************************************************/ #include +#include #include /* Headers from vtrutil library */ @@ -117,37 +118,121 @@ std::string find_path_dir_name(const std::string& file_name) { /******************************************************************** * Create a directory with a given path ********************************************************************/ -bool create_dir_path(const char* dir_path) { - /* Give up if the path is empty */ - if (nullptr == dir_path) { - VTR_LOG_ERROR("dir_path is empty and nothing is created.\n"); - return false; - } +static +bool create_dir_path(const std::string& dir_path, + const bool& verbose) { + /* Give up if the path is empty */ + if (true == dir_path.empty()) { + VTR_LOG_ERROR("Directory path is empty and nothing will be created.\n"); + return false; + } - /* Try to create a directory */ - int ret = mkdir(dir_path, S_IRWXU|S_IRWXG|S_IROTH|S_IXOTH); + /* Try to create a directory */ + int ret = mkdir(dir_path.c_str(), S_IRWXU|S_IRWXG|S_IROTH|S_IXOTH); - /* Analyze the return flag and output status */ - switch (ret) { - case 0: - VTR_LOG("Succeed to create directory '%s'\n", - dir_path); - return true; - case -1: - if (EEXIST == errno) { - VTR_LOG_WARN("Directory '%s' already exists. Will overwrite contents\n", - dir_path); - return true; - } - break; - default: - VTR_LOG_ERROR("Create directory '%s'...Failed!\n", - dir_path); - exit(1); - return false; + /* Analyze the return flag and output status */ + switch (ret) { + case 0: + VTR_LOGV(verbose, + "Succeed to create directory '%s'\n", + dir_path.c_str()); + return true; + case -1: + if (EEXIST == errno) { + VTR_LOGV_WARN(verbose, + "Directory '%s' already exists. Will overwrite contents\n", + dir_path.c_str()); + return true; + } + VTR_LOG_ERROR("Create directory '%s'...Failed!\n", + dir_path.c_str()); + exit(1); + break; + default: + VTR_LOG_ERROR("Create directory '%s'...Failed!\n", + dir_path.c_str()); + exit(1); + return false; } return false; } +/******************************************************************** + * Recursively create a directory with a given path + * The create_dir_path() function will only try to create a directory + * in the last level. If any parent directory is not created, it will + * always fail. + * This function will try to create all the parent directory before + * creating the last level. + ********************************************************************/ +static +bool rec_create_dir_path(const std::string& dir_path) { + /* Give up if the path is empty */ + if (true == dir_path.empty()) { + VTR_LOG_ERROR("Directory path is empty and nothing will be created.\n"); + return false; + } + + /* Try to find the positions of all the slashes + * which are the splitter between directories + */ + char back_slash = '/'; + +#ifdef _WIN32 +/* For windows OS, replace any '/' with '\' */ + char back_slash = '\\'; +#endif + + std::vector slash_pos; + + /* Keep searching until we reach the end of the string */ + for (size_t pos = 0; pos < dir_path.size(); ++pos) { + /* Skip the pos = 0, we should avoid creating any root directory */ + if ( (back_slash == dir_path.at(pos)) + && (0 != pos)) { + slash_pos.push_back(pos); + } + } + + /* Create directory by following the position of back slash + * For each back slash, create a sub string from the beginning + * and try to create directory + */ + for (const size_t& pos : slash_pos) { + std::string sub_dir = dir_path.substr(0, pos); + + /* Turn on verbose output only for the last position: the leaf directory */ + if (false == create_dir_path(sub_dir, &pos == &slash_pos.back())) { + return false; + } + } + + return true; +} + +/******************************************************************** + * Top function to create a directory with a given path + * Allow users to select if use the recursive way or not + * + * Strongly recommend to use the recursive way, as it can maximum + * guarantee the success in creation of directories + ********************************************************************/ +void create_directory(const std::string& dir_path, const bool& recursive) { + std::string formatted_dir_path = format_dir_path(dir_path); + bool status = false; + + if (true == recursive) { + status = rec_create_dir_path(formatted_dir_path); + } else { + status = create_dir_path(formatted_dir_path, true); + } + + if (false == status) { + VTR_LOG_ERROR("Fail to create directory '%s'\n", + formatted_dir_path.c_str()); + exit(1); + } +} + } /* namespace openfpga ends */ diff --git a/libopenfpga/libopenfpgautil/src/openfpga_digest.h b/libopenfpga/libopenfpgautil/src/openfpga_digest.h index 90af3dc4e..0e1cd5d76 100644 --- a/libopenfpga/libopenfpgautil/src/openfpga_digest.h +++ b/libopenfpga/libopenfpgautil/src/openfpga_digest.h @@ -23,7 +23,7 @@ std::string find_path_file_name(const std::string& file_name); std::string find_path_dir_name(const std::string& file_name); -bool create_dir_path(const char* dir_path); +void create_directory(const std::string& dir_path, const bool& recursive = true); } /* namespace openfpga ends */ diff --git a/openfpga/src/annotation/write_xml_device_rr_gsb.cpp b/openfpga/src/annotation/write_xml_device_rr_gsb.cpp index 906c71d70..63c5454a7 100644 --- a/openfpga/src/annotation/write_xml_device_rr_gsb.cpp +++ b/openfpga/src/annotation/write_xml_device_rr_gsb.cpp @@ -182,7 +182,7 @@ void write_device_rr_gsb_to_xml(const char* sb_xml_dir, std::string xml_dir_name = format_dir_path(std::string(sb_xml_dir)); /* Create directories */ - create_dir_path(xml_dir_name.c_str()); + create_directory(xml_dir_name); vtr::Point sb_range = device_rr_gsb.get_gsb_range(); diff --git a/openfpga/src/base/openfpga_bitstream.cpp b/openfpga/src/base/openfpga_bitstream.cpp index e732dee92..e472e7618 100644 --- a/openfpga/src/base/openfpga_bitstream.cpp +++ b/openfpga/src/base/openfpga_bitstream.cpp @@ -36,7 +36,7 @@ void fpga_bitstream(OpenfpgaContext& openfpga_ctx, std::string src_dir_path = find_path_dir_name(cmd_context.option_value(cmd, opt_file)); /* Create directories */ - create_dir_path(src_dir_path.c_str()); + create_directory(src_dir_path); write_arch_independent_bitstream_to_xml_file(openfpga_ctx.bitstream_manager(), cmd_context.option_value(cmd, opt_file)); diff --git a/openfpga/src/base/openfpga_sdc.cpp b/openfpga/src/base/openfpga_sdc.cpp index cb2e1bf72..06b594e73 100644 --- a/openfpga/src/base/openfpga_sdc.cpp +++ b/openfpga/src/base/openfpga_sdc.cpp @@ -42,7 +42,7 @@ void write_pnr_sdc(OpenfpgaContext& openfpga_ctx, std::string sdc_dir_path = format_dir_path(cmd_context.option_value(cmd, opt_output_dir)); /* Create directories */ - create_dir_path(sdc_dir_path.c_str()); + create_directory(sdc_dir_path); PnrSdcOption options(sdc_dir_path); @@ -96,7 +96,7 @@ void write_analysis_sdc(OpenfpgaContext& openfpga_ctx, std::string sdc_dir_path = format_dir_path(cmd_context.option_value(cmd, opt_output_dir)); /* Create directories */ - create_dir_path(sdc_dir_path.c_str()); + create_directory(sdc_dir_path); AnalysisSdcOption options(sdc_dir_path); options.set_generate_sdc_analysis(true); diff --git a/openfpga/src/fpga_verilog/verilog_api.cpp b/openfpga/src/fpga_verilog/verilog_api.cpp index dc332db58..c37b68888 100644 --- a/openfpga/src/fpga_verilog/verilog_api.cpp +++ b/openfpga/src/fpga_verilog/verilog_api.cpp @@ -63,19 +63,19 @@ void fpga_fabric_verilog(ModuleManager& module_manager, std::string src_dir_path = format_dir_path(options.output_directory()); /* Create directories */ - create_dir_path(src_dir_path.c_str()); + create_directory(src_dir_path); /* Sub directory under SRC directory to contain all the primitive block netlists */ std::string submodule_dir_path = src_dir_path + std::string(DEFAULT_SUBMODULE_DIR_NAME); - create_dir_path(submodule_dir_path.c_str()); + create_directory(submodule_dir_path); /* Sub directory under SRC directory to contain all the logic block netlists */ std::string lb_dir_path = src_dir_path + std::string(DEFAULT_LB_DIR_NAME); - create_dir_path(lb_dir_path.c_str()); + create_directory(lb_dir_path); /* Sub directory under SRC directory to contain all the routing block netlists */ std::string rr_dir_path = src_dir_path + std::string(DEFAULT_RR_DIR_NAME); - create_dir_path(rr_dir_path.c_str()); + create_directory(rr_dir_path); /* Print Verilog files containing preprocessing flags */ print_verilog_preprocessing_flags_netlist(std::string(src_dir_path), @@ -153,7 +153,7 @@ void fpga_verilog_testbench(const ModuleManager& module_manager, std::string netlist_name = atom_ctx.nlist.netlist_name(); /* Create directories */ - create_dir_path(src_dir_path.c_str()); + create_directory(src_dir_path); /* TODO: check if this works here. This function was in fabric generator */ print_verilog_simulation_preprocessing_flags(std::string(src_dir_path), From b9ade3fcb6d4421afc4aca80d4b7ebc4e27ff811 Mon Sep 17 00:00:00 2001 From: Xifan Tang Date: Wed, 8 Apr 2020 14:13:28 -0600 Subject: [PATCH 407/645] documentation update to introduce new features in script mode of OpenFPGA shell --- .../openfpga_shell/launch_openfpga_shell.rst | 2 + .../openfpga_shell/openfpga_commands.rst | 2 +- .../source/openfpga_shell/openfpga_script.rst | 39 +++++++++++++++---- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/docs/source/openfpga_shell/launch_openfpga_shell.rst b/docs/source/openfpga_shell/launch_openfpga_shell.rst index 3ef6e9fb5..8ef7e3858 100644 --- a/docs/source/openfpga_shell/launch_openfpga_shell.rst +++ b/docs/source/openfpga_shell/launch_openfpga_shell.rst @@ -11,6 +11,8 @@ To launch OpenFPGA shell, users can choose two modes. Launch OpenFPGA in interactive mode where users type-in command by command and get runtime results + .. warning:: Currently OpenFPGA does not support continued lines and comments + .. option:: --file or -f Launch OpenFPGA in script mode where users write commands in scripts and FPGA will execute them diff --git a/docs/source/openfpga_shell/openfpga_commands.rst b/docs/source/openfpga_shell/openfpga_commands.rst index 8f71cdb54..3712cea0d 100644 --- a/docs/source/openfpga_shell/openfpga_commands.rst +++ b/docs/source/openfpga_shell/openfpga_commands.rst @@ -194,7 +194,7 @@ FPGA-SDC - ``--constrain_zero_delay_paths`` Constrain all the zero-delay paths in FPGA fabric - .. note:: Zero-delay path may cause errors in some PnR tools as it is considered illegal + .. note:: Zero-delay path may cause errors in some PnR tools as it is considered illegal - ``--verbose`` Enable verbose output diff --git a/docs/source/openfpga_shell/openfpga_script.rst b/docs/source/openfpga_shell/openfpga_script.rst index dde33a044..7c9bd5548 100644 --- a/docs/source/openfpga_shell/openfpga_script.rst +++ b/docs/source/openfpga_shell/openfpga_script.rst @@ -4,8 +4,20 @@ OpenFPGA Script Format ---------------------- OpenFPGA accepts a simplified tcl-like script format. -Commented lines are started with `#`. -Note that comments can be added inline or as a new line. + +.. option:: Comments + + Any content after a ``#`` will be treated as comments. + Comments will not be executed. + + .. note:: comments can be added inline or as a new line. See the example below + +.. option:: Continued line + + Lines to be continued should be finished with ``\``. + Continued lines will be conjuncted and executed as one line + + .. note:: please ensure necessary spaces. Otherwise it may cause command parser fail. The following is an example. @@ -21,7 +33,8 @@ The following is an example. #write_openfpga_arch -f ./arch_echo.xml # Annotate the OpenFPGA architecture to VPR data base - link_openfpga_arch --activity_file ./test_blif/and.act --sort_gsb_chan_node_in_edges #--verbose + link_openfpga_arch --activity_file ./test_blif/and.act \ + --sort_gsb_chan_node_in_edges #--verbose # Check and correct any naming conflicts in the BLIF netlist check_netlist_naming_conflict --fix --report ./netlist_renaming.xml @@ -35,7 +48,8 @@ The following is an example. # Build the module graph # - Enabled compression on routing architecture modules # - Enable pin duplication on grid modules - build_fabric --compress_routing --duplicate_grid_pin #--verbose + build_fabric --compress_routing \ + --duplicate_grid_pin #--verbose # Repack the netlist to physical pbs # This must be done before bitstream generator and testbench generation @@ -44,14 +58,21 @@ The following is an example. # Build the bitstream # - Output the fabric-independent bitstream to a file - build_architecture_bitstream --verbose --file /var/tmp/xtang/openfpga_test_src/fabric_indepenent_bitstream.xml + build_architecture_bitstream --verbose \ + --file /var/tmp/xtang/openfpga_test_src/fabric_indepenent_bitstream.xml # Build fabric-dependent bitstream build_fabric_bitstream --verbose # Write the Verilog netlist for FPGA fabric # - Enable the use of explicit port mapping in Verilog netlist - write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src/SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose + write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src/SRC \ + --explicit_port_mapping \ + --include_timing \ + --include_signal_init \ + --support_icarus_simulator \ + --print_user_defined_template \ + --verbose # Write the Verilog testbench for FPGA fabric # - We suggest the use of same output directory as fabric Verilog netlists @@ -59,7 +80,11 @@ The following is an example. # - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA # - Enable pre-configured top-level testbench which is a fast verification skipping programming phase # - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts - write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src/SRC --reference_benchmark_file_path /var/tmp/xtang/and.v --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini /var/tmp/xtang/openfpga_test_src/simulation_deck.ini + write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src/SRC \ + --reference_benchmark_file_path /var/tmp/xtang/and.v \ + --print_top_testbench \ + --print_preconfig_top_testbench \ + --print_simulation_ini /var/tmp/xtang/openfpga_test_src/simulation_deck.ini # Write the SDC files for PnR backend # - Turn on every options here From b9dab2baaf760d7ba5bb440e8467758349f4b2b1 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 8 Apr 2020 16:18:05 -0600 Subject: [PATCH 408/645] add exit codes to command execution in shell context --- .../libopenfpgashell/src/command_exit_codes.h | 27 +++++ libopenfpga/libopenfpgashell/src/shell.h | 55 ++++++++--- libopenfpga/libopenfpgashell/src/shell.tpp | 98 ++++++++++++++----- .../libopenfpgashell/test/test_shell.cpp | 10 +- .../check_netlist_naming_conflict.cpp | 20 +++- .../check_netlist_naming_conflict.h | 4 +- openfpga/src/base/openfpga_bitstream.cpp | 17 +++- openfpga/src/base/openfpga_bitstream.h | 8 +- openfpga/src/base/openfpga_build_fabric.cpp | 10 +- openfpga/src/base/openfpga_build_fabric.h | 4 +- openfpga/src/base/openfpga_link_arch.cpp | 12 ++- openfpga/src/base/openfpga_link_arch.h | 4 +- .../base/openfpga_lut_truth_table_fixup.cpp | 11 ++- .../src/base/openfpga_lut_truth_table_fixup.h | 4 +- openfpga/src/base/openfpga_pb_pin_fixup.cpp | 10 +- openfpga/src/base/openfpga_pb_pin_fixup.h | 4 +- openfpga/src/base/openfpga_read_arch.cpp | 17 +++- openfpga/src/base/openfpga_read_arch.h | 8 +- openfpga/src/base/openfpga_repack.cpp | 10 +- openfpga/src/base/openfpga_repack.h | 4 +- openfpga/src/base/openfpga_sdc.cpp | 17 +++- openfpga/src/base/openfpga_sdc.h | 8 +- openfpga/src/base/openfpga_verilog.cpp | 17 +++- openfpga/src/base/openfpga_verilog.h | 8 +- openfpga/src/base/openfpga_write_gsb.cpp | 10 +- openfpga/src/base/openfpga_write_gsb.h | 4 +- 26 files changed, 293 insertions(+), 108 deletions(-) create mode 100644 libopenfpga/libopenfpgashell/src/command_exit_codes.h diff --git a/libopenfpga/libopenfpgashell/src/command_exit_codes.h b/libopenfpga/libopenfpgashell/src/command_exit_codes.h new file mode 100644 index 000000000..f1490e533 --- /dev/null +++ b/libopenfpga/libopenfpgashell/src/command_exit_codes.h @@ -0,0 +1,27 @@ +#ifndef COMMAND_EXIT_CODES_H +#define COMMAND_EXIT_CODES_H + +/* Begin namespace openfpga */ +namespace openfpga { + +/********************************************************************* + * Exit codes to signal success/failure status of command execution + * Depend on the exit code, OpenFPGA shell will decide to continue or abort + ********************************************************************/ + +/* Never been executed */ +constexpr int CMD_EXEC_NONE = -1; + +/* Everything OK */ +constexpr int CMD_EXEC_SUCCESS = 0; + +/* Fatal error occurred, we have to abort and do not execute the rest of commands */ +constexpr int CMD_EXEC_FATAL_ERROR = 1; + +/* See minor errors but it will not impact the downsteam. We can continue to execute the rest of commands */ +constexpr int CMD_EXEC_MINOR_ERROR = 2; + + +} /* End namespace openfpga */ + +#endif diff --git a/libopenfpga/libopenfpgashell/src/shell.h b/libopenfpga/libopenfpgashell/src/shell.h index 427580010..1a49b313c 100644 --- a/libopenfpga/libopenfpgashell/src/shell.h +++ b/libopenfpga/libopenfpgashell/src/shell.h @@ -10,6 +10,7 @@ #include "vtr_range.h" #include "command.h" #include "command_context.h" +#include "command_exit_codes.h" #include "shell_fwd.h" /* Begin namespace openfpga */ @@ -88,25 +89,49 @@ class Shell { ShellCommandId add_command(const Command& cmd, const char* descr); void set_command_class(const ShellCommandId& cmd_id, const ShellCommandClassId& cmd_class_id); /* Link the execute function to a command - * We support here three types of functions to be executed in the shell - * 1. Standard function, including the data exchange and commands - * 2. Short function, including only the data exchange - * 3. Built-in function, including only the shell envoriment variables - * 4. Marco function, which directly call a macro function without command parsing + * We support here different types of functions to be executed in the shell * Users just need to specify the function object and its type will be automatically inferred + * + * Note that all the function should return exit codes complying to the shell_exit_code.h + * execept the internal functions + */ + + /* Standard function, including the data exchange and commands + * This function requires the data exchange to be constant + * This is designed for outputting functions requiring external data than the */ void set_command_const_execute_function(const ShellCommandId& cmd_id, - std::function exec_func); + std::function exec_func); + + /* Standard function, including the data exchange and commands + * This function allows modification to the data exchange + * This is designed for implementing functions requiring external data than the + */ void set_command_execute_function(const ShellCommandId& cmd_id, - std::function exec_func); + std::function exec_func); + + /* Short function, including only the data exchange + * This function requires the data exchange to be constant + * This is designed for outputting functions without external data than the + */ void set_command_const_execute_function(const ShellCommandId& cmd_id, - std::function exec_func); + std::function exec_func); + + /* Short function, including only the data exchange + * This function allows modification to the data exchange + * This is designed for internal implementing functions without external data than the + */ void set_command_execute_function(const ShellCommandId& cmd_id, - std::function exec_func); + std::function exec_func); + + /* Built-in function, including only the shell envoriment variables */ void set_command_execute_function(const ShellCommandId& cmd_id, std::function exec_func); + + /* Marco function, which directly call a macro function without command parsing */ void set_command_execute_function(const ShellCommandId& cmd_id, std::function exec_func); + void set_command_dependency(const ShellCommandId& cmd_id, const std::vector& cmd_dependency); ShellCommandClassId add_command_class(const char* name); @@ -126,7 +151,7 @@ class Shell { /* Execute a command, the command line is the user's input to launch a command * The common_context is the data structure to exchange data between commands */ - void execute_command(const char* cmd_line, T& common_context); + int execute_command(const char* cmd_line, T& common_context); private: /* Internal data */ /* Name of the shell, this will appear in the interactive mode */ std::string name_; @@ -162,10 +187,10 @@ class Shell { * 3. Built-in function, including only the shell envoriment variables * 4. Marco function, which directly call a macro function without command parsing */ - vtr::vector> command_const_execute_functions_; - vtr::vector> command_standard_execute_functions_; - vtr::vector> command_short_const_execute_functions_; - vtr::vector> command_short_execute_functions_; + vtr::vector> command_const_execute_functions_; + vtr::vector> command_standard_execute_functions_; + vtr::vector> command_short_const_execute_functions_; + vtr::vector> command_short_execute_functions_; vtr::vector> command_builtin_execute_functions_; vtr::vector> command_macro_execute_functions_; @@ -175,7 +200,7 @@ class Shell { vtr::vector command_execute_function_types_; /* A flag to indicate if the command has been executed */ - vtr::vector command_status_; + vtr::vector command_status_; /* Dependency graph for different commands, * This helps the shell interface to check if a command need other commands to be run before its execution diff --git a/libopenfpga/libopenfpgashell/src/shell.tpp b/libopenfpga/libopenfpgashell/src/shell.tpp index 5fe043f8e..69ec89ab9 100644 --- a/libopenfpga/libopenfpgashell/src/shell.tpp +++ b/libopenfpga/libopenfpgashell/src/shell.tpp @@ -131,7 +131,7 @@ ShellCommandId Shell::add_command(const Command& cmd, const char* descr) { command_short_execute_functions_.emplace_back(); command_builtin_execute_functions_.emplace_back(); command_macro_execute_functions_.emplace_back(); - command_status_.push_back(false); /* By default, the command should be marked as never executed */ + command_status_.push_back(CMD_EXEC_NONE); /* By default, the command should be marked as fatal error as it has been never executed */ command_dependencies_.emplace_back(); /* Register the name in the name2id map */ @@ -155,7 +155,7 @@ void Shell::set_command_class(const ShellCommandId& cmd_id, const ShellComman template void Shell::set_command_const_execute_function(const ShellCommandId& cmd_id, - std::function exec_func) { + std::function exec_func) { VTR_ASSERT(true == valid_command_id(cmd_id)); command_execute_function_types_[cmd_id] = CONST_STANDARD; command_const_execute_functions_[cmd_id] = exec_func; @@ -163,7 +163,7 @@ void Shell::set_command_const_execute_function(const ShellCommandId& cmd_id, template void Shell::set_command_execute_function(const ShellCommandId& cmd_id, - std::function exec_func) { + std::function exec_func) { VTR_ASSERT(true == valid_command_id(cmd_id)); command_execute_function_types_[cmd_id] = STANDARD; command_standard_execute_functions_[cmd_id] = exec_func; @@ -171,7 +171,7 @@ void Shell::set_command_execute_function(const ShellCommandId& cmd_id, template void Shell::set_command_const_execute_function(const ShellCommandId& cmd_id, - std::function exec_func) { + std::function exec_func) { VTR_ASSERT(true == valid_command_id(cmd_id)); command_execute_function_types_[cmd_id] = CONST_SHORT; command_short_const_execute_functions_[cmd_id] = exec_func; @@ -179,7 +179,7 @@ void Shell::set_command_const_execute_function(const ShellCommandId& cmd_id, template void Shell::set_command_execute_function(const ShellCommandId& cmd_id, - std::function exec_func) { + std::function exec_func) { VTR_ASSERT(true == valid_command_id(cmd_id)); command_execute_function_types_[cmd_id] = SHORT; command_short_execute_functions_[cmd_id] = exec_func; @@ -341,9 +341,15 @@ void Shell::run_script_mode(const char* script_file_name, T& context) { /* Process the command only when the full command line in ended */ if (!cmd_line.empty()) { VTR_LOG("\nCommand line to execute: %s\n", cmd_line.c_str()); - execute_command(cmd_line.c_str(), context); + int status = execute_command(cmd_line.c_str(), context); /* Empty the line ready to start a new line */ cmd_line.clear(); + + /* Check the execution status of the command, if fatal error happened, we should abort immediately */ + if (CMD_EXEC_FATAL_ERROR == status) { + VTR_LOG("Fatal error occurred!\nAbort and enter interactive mode\n"); + break; + } } } fp.close(); @@ -376,16 +382,49 @@ void Shell::print_commands() const { template void Shell::exit() const { + /* Check all the command status, if we see fatal errors or minor errors, we drop an error code */ + int exit_code = 0; + for (const int& status : command_status_) { + if ( (status == CMD_EXEC_FATAL_ERROR) + || (status == CMD_EXEC_MINOR_ERROR) ) { + exit_code = 1; + break; + } + } + + /* Show error message if we detect any errors */ + int num_err = 0; + if (0 != exit_code) { + VTR_LOG("\n"); + for (const ShellCommandId& cmd : commands()) { + if (command_status_[cmd] == CMD_EXEC_FATAL_ERROR) { + VTR_LOG_ERROR("Command '%s' execution has fatal errors\n", + commands_[cmd].name().c_str()); + num_err++; + } + + if (command_status_[cmd] == CMD_EXEC_MINOR_ERROR) { + VTR_LOG_ERROR("Command '%s' execution has minor errors\n", + commands_[cmd].name().c_str()); + num_err++; + } + } + } + + VTR_LOG("\nFinish execution with %d errors\n", + num_err); + VTR_LOG("\nThank you for using %s!\n", name().c_str()); - std::exit(0); + + std::exit(exit_code); } /************************************************************************ * Private executors ***********************************************************************/ template -void Shell::execute_command(const char* cmd_line, +int Shell::execute_command(const char* cmd_line, T& common_context) { /* Tokenize the line */ openfpga::StringToken tokenizer(cmd_line); @@ -396,17 +435,18 @@ void Shell::execute_command(const char* cmd_line, if (ShellCommandId::INVALID() == cmd_id) { VTR_LOG("Try to call a command '%s' which is not defined!\n", tokens[0].c_str()); - return; + return CMD_EXEC_FATAL_ERROR; } /* Check the dependency graph to see if all the prequistics have been met */ for (const ShellCommandId& dep_cmd : command_dependencies_[cmd_id]) { - if (false == command_status_[dep_cmd]) { + if ( (CMD_EXEC_NONE == command_status_[dep_cmd]) + || (CMD_EXEC_FATAL_ERROR == command_status_[dep_cmd]) ) { VTR_LOG("Command '%s' is required to be executed before command '%s'!\n", commands_[dep_cmd].name().c_str(), commands_[cmd_id].name().c_str()); /* Echo the command help desk */ print_command_options(commands_[cmd_id]); - return; + return CMD_EXEC_FATAL_ERROR; } } @@ -421,25 +461,22 @@ void Shell::execute_command(const char* cmd_line, argv[itok] = (char*)malloc((tokens[itok].length() + 1) * sizeof(char)); strcpy(argv[itok], tokens[itok].c_str()); } - /* Execute the marco function */ - command_macro_execute_functions_[cmd_id](tokens.size(), argv); + /* Execute the marco function and record the execution status */ + command_status_[cmd_id] = command_macro_execute_functions_[cmd_id](tokens.size(), argv); /* Free the argv */ for (size_t itok = 0; itok < tokens.size(); ++itok) { free(argv[itok]); } free(argv); - /* Change the status of the command */ - command_status_[cmd_id] = true; - /* Finish for macro command, return */ - return; + return command_status_[cmd_id]; } if (false == parse_command(tokens, commands_[cmd_id], command_contexts_[cmd_id])) { /* Echo the command */ print_command_options(commands_[cmd_id]); - return; + return CMD_EXEC_FATAL_ERROR; } /* Parse succeed. Let user to confirm selected options */ @@ -448,31 +485,38 @@ void Shell::execute_command(const char* cmd_line, /* Execute the command depending on the type of function ! */ switch (command_execute_function_types_[cmd_id]) { case CONST_STANDARD: - command_const_execute_functions_[cmd_id](common_context, commands_[cmd_id], command_contexts_[cmd_id]); + command_status_[cmd_id] = command_const_execute_functions_[cmd_id](common_context, commands_[cmd_id], command_contexts_[cmd_id]); break; case STANDARD: - command_standard_execute_functions_[cmd_id](common_context, commands_[cmd_id], command_contexts_[cmd_id]); + command_status_[cmd_id] = command_standard_execute_functions_[cmd_id](common_context, commands_[cmd_id], command_contexts_[cmd_id]); break; case CONST_SHORT: - command_short_const_execute_functions_[cmd_id](common_context); + command_status_[cmd_id] = command_short_const_execute_functions_[cmd_id](common_context); break; case SHORT: - command_short_execute_functions_[cmd_id](common_context); + command_status_[cmd_id] = command_short_execute_functions_[cmd_id](common_context); break; case BUILTIN: command_builtin_execute_functions_[cmd_id](); + /* Built-in execution is always correct */ + command_status_[cmd_id] = CMD_EXEC_SUCCESS; break; /* MACRO should be executed eariler in this function. It should not appear here */ default: /* This is not allowed! Error out */ - VTR_LOG("Invalid type of execute function for command '%s'!\n", - commands_[cmd_id].name().c_str()); + VTR_LOG_ERROR("Invalid type of execute function for command '%s'!\n", + commands_[cmd_id].name().c_str()); /* Exit the shell using the exit() function inside this class! */ - exit(); + return CMD_EXEC_FATAL_ERROR; } - /* Change the status of the command */ - command_status_[cmd_id] = true; + /* Forbid users to return the status CMD_EXEC_NONE */ + if (CMD_EXEC_NONE == command_status_[cmd_id]) { + VTR_LOG_ERROR("It is illegal to return never-executed status for an executed command!\n"); + return CMD_EXEC_FATAL_ERROR; + } + + return command_status_[cmd_id]; } /************************************************************************ diff --git a/libopenfpga/libopenfpgashell/test/test_shell.cpp b/libopenfpga/libopenfpgashell/test/test_shell.cpp index 649859d85..0edc257dd 100644 --- a/libopenfpga/libopenfpgashell/test/test_shell.cpp +++ b/libopenfpga/libopenfpgashell/test/test_shell.cpp @@ -15,16 +15,20 @@ class ShellContext { }; static -void shell_execute_set(ShellContext& context, +int shell_execute_set(ShellContext& context, const Command& cmd, const CommandContext& cmd_context) { CommandOptionId opt_id = cmd.option("value"); /* Get the value of a in the command context */ context.a = std::atoi(cmd_context.option_value(cmd, opt_id).c_str()); + + return CMD_EXEC_SUCCESS; } static -void shell_execute_print(ShellContext& context) { +int shell_execute_print(ShellContext& context) { VTR_LOG("a=%d\n", context.a); + + return CMD_EXEC_SUCCESS; } static @@ -35,7 +39,7 @@ int shell_execute_print_macro(int argc, char** argv) { VTR_LOG("\t[%d]: %s\n", iarg, argv[iarg]); } - return 0; + return CMD_EXEC_SUCCESS; } int main(int argc, char** argv) { diff --git a/openfpga/src/annotation/check_netlist_naming_conflict.cpp b/openfpga/src/annotation/check_netlist_naming_conflict.cpp index 3470c9f0a..a8ab94384 100644 --- a/openfpga/src/annotation/check_netlist_naming_conflict.cpp +++ b/openfpga/src/annotation/check_netlist_naming_conflict.cpp @@ -11,6 +11,9 @@ #include "vtr_assert.h" #include "vtr_log.h" +/* Headers from openfpgashell library */ +#include "command_exit_codes.h" + /* Headers from archopenfpga library */ #include "write_xml_utils.h" @@ -213,8 +216,8 @@ void print_netlist_naming_fix_report(const std::string& fname, * in the users' BLIF netlist that violates the syntax of OpenFPGA * fabric generator, i.e., Verilog generator and SPICE generator *******************************************************************/ -void check_netlist_naming_conflict(OpenfpgaContext& openfpga_context, - const Command& cmd, const CommandContext& cmd_context) { +int check_netlist_naming_conflict(OpenfpgaContext& openfpga_context, + const Command& cmd, const CommandContext& cmd_context) { vtr::ScopedStartFinishTimer timer("Check naming violations of netlist blocks and nets"); /* By default, we replace all the illegal characters with '_' */ @@ -231,7 +234,15 @@ void check_netlist_naming_conflict(OpenfpgaContext& openfpga_context, num_conflicts); VTR_LOGV(0 == num_conflicts, "Check naming conflicts in the netlist passed.\n"); - return; + + /* If we see conflicts, report minor error */ + if (0 < num_conflicts) { + return CMD_EXEC_MINOR_ERROR; + } + + /* Otherwise, we should see zero conflicts */ + VTR_ASSERT(0 == num_conflicts); + return CMD_EXEC_SUCCESS; } /* If the auto correction is enabled, we apply a fix */ @@ -248,6 +259,9 @@ void check_netlist_naming_conflict(OpenfpgaContext& openfpga_context, cmd_context.option_value(cmd, opt_report).c_str()); } } + + /* TODO: should identify the error code from internal function execution */ + return CMD_EXEC_SUCCESS; } } /* end namespace openfpga */ diff --git a/openfpga/src/annotation/check_netlist_naming_conflict.h b/openfpga/src/annotation/check_netlist_naming_conflict.h index 54d86bca4..cad6c3963 100644 --- a/openfpga/src/annotation/check_netlist_naming_conflict.h +++ b/openfpga/src/annotation/check_netlist_naming_conflict.h @@ -15,8 +15,8 @@ /* begin namespace openfpga */ namespace openfpga { -void check_netlist_naming_conflict(OpenfpgaContext& openfpga_context, - const Command& cmd, const CommandContext& cmd_context); +int check_netlist_naming_conflict(OpenfpgaContext& openfpga_context, + const Command& cmd, const CommandContext& cmd_context); } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_bitstream.cpp b/openfpga/src/base/openfpga_bitstream.cpp index e472e7618..7e2a19fc4 100644 --- a/openfpga/src/base/openfpga_bitstream.cpp +++ b/openfpga/src/base/openfpga_bitstream.cpp @@ -5,6 +5,9 @@ #include "vtr_time.h" #include "vtr_log.h" +/* Headers from openfpgashell library */ +#include "command_exit_codes.h" + /* Headers from openfpgautil library */ #include "openfpga_digest.h" @@ -22,8 +25,8 @@ namespace openfpga { /******************************************************************** * A wrapper function to call the build_device_bitstream() in FPGA bitstream *******************************************************************/ -void fpga_bitstream(OpenfpgaContext& openfpga_ctx, - const Command& cmd, const CommandContext& cmd_context) { +int fpga_bitstream(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context) { CommandOptionId opt_verbose = cmd.option("verbose"); CommandOptionId opt_file = cmd.option("file"); @@ -41,19 +44,25 @@ void fpga_bitstream(OpenfpgaContext& openfpga_ctx, write_arch_independent_bitstream_to_xml_file(openfpga_ctx.bitstream_manager(), cmd_context.option_value(cmd, opt_file)); } + + /* TODO: should identify the error code from internal function execution */ + return CMD_EXEC_SUCCESS; } /******************************************************************** * A wrapper function to call the build_fabric_bitstream() in FPGA bitstream *******************************************************************/ -void build_fabric_bitstream(OpenfpgaContext& openfpga_ctx, - const Command& cmd, const CommandContext& cmd_context) { +int build_fabric_bitstream(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context) { CommandOptionId opt_verbose = cmd.option("verbose"); openfpga_ctx.mutable_fabric_bitstream() = build_fabric_dependent_bitstream(openfpga_ctx.bitstream_manager(), openfpga_ctx.module_graph(), cmd_context.option_enable(cmd, opt_verbose)); + + /* TODO: should identify the error code from internal function execution */ + return CMD_EXEC_SUCCESS; } } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_bitstream.h b/openfpga/src/base/openfpga_bitstream.h index 9dc2ad471..58f1c7e86 100644 --- a/openfpga/src/base/openfpga_bitstream.h +++ b/openfpga/src/base/openfpga_bitstream.h @@ -15,11 +15,11 @@ /* begin namespace openfpga */ namespace openfpga { -void fpga_bitstream(OpenfpgaContext& openfpga_ctx, - const Command& cmd, const CommandContext& cmd_context); +int fpga_bitstream(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context); -void build_fabric_bitstream(OpenfpgaContext& openfpga_ctx, - const Command& cmd, const CommandContext& cmd_context); +int build_fabric_bitstream(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context); } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_build_fabric.cpp b/openfpga/src/base/openfpga_build_fabric.cpp index 4e8b44916..67203216a 100644 --- a/openfpga/src/base/openfpga_build_fabric.cpp +++ b/openfpga/src/base/openfpga_build_fabric.cpp @@ -5,6 +5,9 @@ #include "vtr_time.h" #include "vtr_log.h" +/* Headers from openfpgashell library */ +#include "command_exit_codes.h" + #include "device_rr_gsb.h" #include "device_rr_gsb_utils.h" #include "build_device_module.h" @@ -56,8 +59,8 @@ void compress_routing_hierarchy(OpenfpgaContext& openfpga_ctx, /******************************************************************** * Build the module graph for FPGA device *******************************************************************/ -void build_fabric(OpenfpgaContext& openfpga_ctx, - const Command& cmd, const CommandContext& cmd_context) { +int build_fabric(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context) { CommandOptionId opt_compress_routing = cmd.option("compress_routing"); CommandOptionId opt_duplicate_grid_pin = cmd.option("duplicate_grid_pin"); @@ -77,6 +80,9 @@ void build_fabric(OpenfpgaContext& openfpga_ctx, cmd_context.option_enable(cmd, opt_compress_routing), cmd_context.option_enable(cmd, opt_duplicate_grid_pin), cmd_context.option_enable(cmd, opt_verbose)); + + /* TODO: should identify the error code from internal function execution */ + return CMD_EXEC_SUCCESS; } } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_build_fabric.h b/openfpga/src/base/openfpga_build_fabric.h index 824ed63a1..3b63c7ab2 100644 --- a/openfpga/src/base/openfpga_build_fabric.h +++ b/openfpga/src/base/openfpga_build_fabric.h @@ -15,8 +15,8 @@ /* begin namespace openfpga */ namespace openfpga { -void build_fabric(OpenfpgaContext& openfpga_ctx, - const Command& cmd, const CommandContext& cmd_context); +int build_fabric(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context); } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_link_arch.cpp b/openfpga/src/base/openfpga_link_arch.cpp index 34c98ee8d..65aef7cf8 100644 --- a/openfpga/src/base/openfpga_link_arch.cpp +++ b/openfpga/src/base/openfpga_link_arch.cpp @@ -8,6 +8,9 @@ #include "vtr_assert.h" #include "vtr_log.h" +/* Headers from openfpgashell library */ +#include "command_exit_codes.h" + /* Headers from vpr library */ #include "read_activity.h" @@ -59,8 +62,8 @@ bool is_vpr_rr_graph_supported(const RRGraph& rr_graph) { * - physical pb_graph nodes and pb_graph pins * - circuit models for global routing architecture *******************************************************************/ -void link_arch(OpenfpgaContext& openfpga_ctx, - const Command& cmd, const CommandContext& cmd_context) { +int link_arch(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context) { vtr::ScopedStartFinishTimer timer("Link OpenFPGA architecture to VPR architecture"); @@ -105,7 +108,7 @@ void link_arch(OpenfpgaContext& openfpga_ctx, * - DeviceRRGSB */ if (false == is_vpr_rr_graph_supported(g_vpr_ctx.device().rr_graph)) { - return; + return CMD_EXEC_FATAL_ERROR; } annotate_device_rr_gsb(g_vpr_ctx.device(), @@ -147,6 +150,9 @@ void link_arch(OpenfpgaContext& openfpga_ctx, annotate_simulation_setting(g_vpr_ctx.atom(), openfpga_ctx.net_activity(), openfpga_ctx.mutable_arch().sim_setting); + + /* TODO: should identify the error code from internal function execution */ + return CMD_EXEC_SUCCESS; } } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_link_arch.h b/openfpga/src/base/openfpga_link_arch.h index 742c29541..8eba64386 100644 --- a/openfpga/src/base/openfpga_link_arch.h +++ b/openfpga/src/base/openfpga_link_arch.h @@ -15,8 +15,8 @@ /* begin namespace openfpga */ namespace openfpga { -void link_arch(OpenfpgaContext& openfpga_context, - const Command& cmd, const CommandContext& cmd_context); +int link_arch(OpenfpgaContext& openfpga_context, + const Command& cmd, const CommandContext& cmd_context); } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_lut_truth_table_fixup.cpp b/openfpga/src/base/openfpga_lut_truth_table_fixup.cpp index 45ecbca31..396842dcd 100644 --- a/openfpga/src/base/openfpga_lut_truth_table_fixup.cpp +++ b/openfpga/src/base/openfpga_lut_truth_table_fixup.cpp @@ -7,6 +7,9 @@ #include "vtr_assert.h" #include "vtr_log.h" +/* Headers from openfpgashell library */ +#include "command_exit_codes.h" + /* Headers from vpr library */ #include "vpr_utils.h" @@ -182,7 +185,6 @@ void update_lut_tt_with_post_packing_results(const AtomContext& atom_ctx, } } - /******************************************************************** * Top-level function to fix up the lut truth table results after packing is done * The problem comes from a mismatch between the packing results and @@ -194,8 +196,8 @@ void update_lut_tt_with_post_packing_results(const AtomContext& atom_ctx, * This function aims to fix the mess after packing so that the truth table * can be synchronized *******************************************************************/ -void lut_truth_table_fixup(OpenfpgaContext& openfpga_context, - const Command& cmd, const CommandContext& cmd_context) { +int lut_truth_table_fixup(OpenfpgaContext& openfpga_context, + const Command& cmd, const CommandContext& cmd_context) { vtr::ScopedStartFinishTimer timer("Fix up LUT truth tables after packing optimization"); @@ -206,6 +208,9 @@ void lut_truth_table_fixup(OpenfpgaContext& openfpga_context, g_vpr_ctx.clustering(), openfpga_context.mutable_vpr_clustering_annotation(), cmd_context.option_enable(cmd, opt_verbose)); + + /* TODO: should identify the error code from internal function execution */ + return CMD_EXEC_SUCCESS; } } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_lut_truth_table_fixup.h b/openfpga/src/base/openfpga_lut_truth_table_fixup.h index f11abc7fb..d685a4b44 100644 --- a/openfpga/src/base/openfpga_lut_truth_table_fixup.h +++ b/openfpga/src/base/openfpga_lut_truth_table_fixup.h @@ -15,8 +15,8 @@ /* begin namespace openfpga */ namespace openfpga { -void lut_truth_table_fixup(OpenfpgaContext& openfpga_context, - const Command& cmd, const CommandContext& cmd_context); +int lut_truth_table_fixup(OpenfpgaContext& openfpga_context, + const Command& cmd, const CommandContext& cmd_context); } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_pb_pin_fixup.cpp b/openfpga/src/base/openfpga_pb_pin_fixup.cpp index cc91bdebe..2a8686190 100644 --- a/openfpga/src/base/openfpga_pb_pin_fixup.cpp +++ b/openfpga/src/base/openfpga_pb_pin_fixup.cpp @@ -7,6 +7,9 @@ #include "vtr_assert.h" #include "vtr_log.h" +/* Headers from openfpgashell library */ +#include "command_exit_codes.h" + /* Headers from vpr library */ #include "vpr_utils.h" @@ -254,8 +257,8 @@ void update_pb_pin_with_post_routing_results(const DeviceContext& device_ctx, * This function aims to fix the mess after routing so that the net mapping * can be synchronized *******************************************************************/ -void pb_pin_fixup(OpenfpgaContext& openfpga_context, - const Command& cmd, const CommandContext& cmd_context) { +int pb_pin_fixup(OpenfpgaContext& openfpga_context, + const Command& cmd, const CommandContext& cmd_context) { vtr::ScopedStartFinishTimer timer("Fix up pb pin mapping results after routing optimization"); @@ -268,6 +271,9 @@ void pb_pin_fixup(OpenfpgaContext& openfpga_context, openfpga_context.vpr_routing_annotation(), openfpga_context.mutable_vpr_clustering_annotation(), cmd_context.option_enable(cmd, opt_verbose)); + + /* TODO: should identify the error code from internal function execution */ + return CMD_EXEC_SUCCESS; } } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_pb_pin_fixup.h b/openfpga/src/base/openfpga_pb_pin_fixup.h index e924c6067..bab4e9565 100644 --- a/openfpga/src/base/openfpga_pb_pin_fixup.h +++ b/openfpga/src/base/openfpga_pb_pin_fixup.h @@ -15,8 +15,8 @@ /* begin namespace openfpga */ namespace openfpga { -void pb_pin_fixup(OpenfpgaContext& openfpga_context, - const Command& cmd, const CommandContext& cmd_context); +int pb_pin_fixup(OpenfpgaContext& openfpga_context, + const Command& cmd, const CommandContext& cmd_context); } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_read_arch.cpp b/openfpga/src/base/openfpga_read_arch.cpp index 1a36e85a3..214af1612 100644 --- a/openfpga/src/base/openfpga_read_arch.cpp +++ b/openfpga/src/base/openfpga_read_arch.cpp @@ -5,6 +5,9 @@ /* Headers from vtrutil library */ #include "vtr_log.h" +/* Headers from openfpgashell library */ +#include "command_exit_codes.h" + /* Headers from archopenfpga library */ #include "read_xml_openfpga_arch.h" #include "check_circuit_library.h" @@ -22,8 +25,8 @@ namespace openfpga { * The command will accept an option '--file' which is the architecture * file provided by users *******************************************************************/ -void read_arch(OpenfpgaContext& openfpga_context, - const Command& cmd, const CommandContext& cmd_context) { +int read_arch(OpenfpgaContext& openfpga_context, + const Command& cmd, const CommandContext& cmd_context) { /* Check the option '--file' is enabled or not * Actually, it must be enabled as the shell interface will check * before reaching this fuction @@ -44,6 +47,9 @@ void read_arch(OpenfpgaContext& openfpga_context, * 3. Simulation settings (TODO) */ check_circuit_library(openfpga_context.arch().circuit_lib); + + /* TODO: should identify the error code from internal function execution */ + return CMD_EXEC_SUCCESS; } /******************************************************************** @@ -53,8 +59,8 @@ void read_arch(OpenfpgaContext& openfpga_context, * The command will accept an option '--file' which is the architecture * file provided by users *******************************************************************/ -void write_arch(const OpenfpgaContext& openfpga_context, - const Command& cmd, const CommandContext& cmd_context) { +int write_arch(const OpenfpgaContext& openfpga_context, + const Command& cmd, const CommandContext& cmd_context) { /* Check the option '--file' is enabled or not * Actually, it must be enabled as the shell interface will check * before reaching this fuction @@ -68,6 +74,9 @@ void write_arch(const OpenfpgaContext& openfpga_context, VTR_LOG("Writing XML architecture to '%s'...\n", arch_file_name.c_str()); write_xml_openfpga_arch(arch_file_name.c_str(), openfpga_context.arch()); + + /* TODO: should identify the error code from internal function execution */ + return CMD_EXEC_SUCCESS; } } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_read_arch.h b/openfpga/src/base/openfpga_read_arch.h index dcc04d77a..b664a7b3e 100644 --- a/openfpga/src/base/openfpga_read_arch.h +++ b/openfpga/src/base/openfpga_read_arch.h @@ -15,11 +15,11 @@ /* begin namespace openfpga */ namespace openfpga { -void read_arch(OpenfpgaContext& openfpga_context, - const Command& cmd, const CommandContext& cmd_context); +int read_arch(OpenfpgaContext& openfpga_context, + const Command& cmd, const CommandContext& cmd_context); -void write_arch(const OpenfpgaContext& openfpga_context, - const Command& cmd, const CommandContext& cmd_context); +int write_arch(const OpenfpgaContext& openfpga_context, + const Command& cmd, const CommandContext& cmd_context); } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_repack.cpp b/openfpga/src/base/openfpga_repack.cpp index 91c6b4798..fe0529d04 100644 --- a/openfpga/src/base/openfpga_repack.cpp +++ b/openfpga/src/base/openfpga_repack.cpp @@ -5,6 +5,9 @@ #include "vtr_time.h" #include "vtr_log.h" +/* Headers from openfpgashell library */ +#include "command_exit_codes.h" + #include "build_physical_truth_table.h" #include "repack.h" #include "openfpga_repack.h" @@ -18,8 +21,8 @@ namespace openfpga { /******************************************************************** * A wrapper function to call the fabric_verilog function of FPGA-Verilog *******************************************************************/ -void repack(OpenfpgaContext& openfpga_ctx, - const Command& cmd, const CommandContext& cmd_context) { +int repack(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context) { CommandOptionId opt_verbose = cmd.option("verbose"); @@ -35,6 +38,9 @@ void repack(OpenfpgaContext& openfpga_ctx, g_vpr_ctx.clustering(), openfpga_ctx.vpr_device_annotation(), openfpga_ctx.arch().circuit_lib); + + /* TODO: should identify the error code from internal function execution */ + return CMD_EXEC_SUCCESS; } } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_repack.h b/openfpga/src/base/openfpga_repack.h index 0fce62f78..83e4bce63 100644 --- a/openfpga/src/base/openfpga_repack.h +++ b/openfpga/src/base/openfpga_repack.h @@ -15,8 +15,8 @@ /* begin namespace openfpga */ namespace openfpga { -void repack(OpenfpgaContext& openfpga_ctx, - const Command& cmd, const CommandContext& cmd_context); +int repack(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context); } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_sdc.cpp b/openfpga/src/base/openfpga_sdc.cpp index 06b594e73..21a3ab8fb 100644 --- a/openfpga/src/base/openfpga_sdc.cpp +++ b/openfpga/src/base/openfpga_sdc.cpp @@ -5,6 +5,9 @@ #include "vtr_time.h" #include "vtr_log.h" +/* Headers from openfpgashell library */ +#include "command_exit_codes.h" + /* Headers from openfpgautil library */ #include "openfpga_digest.h" @@ -22,8 +25,8 @@ namespace openfpga { /******************************************************************** * A wrapper function to call the PnR SDC generator of FPGA-SDC *******************************************************************/ -void write_pnr_sdc(OpenfpgaContext& openfpga_ctx, - const Command& cmd, const CommandContext& cmd_context) { +int write_pnr_sdc(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context) { CommandOptionId opt_output_dir = cmd.option("file"); CommandOptionId opt_constrain_global_port = cmd.option("constrain_global_port"); @@ -80,13 +83,16 @@ void write_pnr_sdc(OpenfpgaContext& openfpga_ctx, global_ports, openfpga_ctx.flow_manager().compress_routing()); } + + /* TODO: should identify the error code from internal function execution */ + return CMD_EXEC_SUCCESS; } /******************************************************************** * A wrapper function to call the analysis SDC generator of FPGA-SDC *******************************************************************/ -void write_analysis_sdc(OpenfpgaContext& openfpga_ctx, - const Command& cmd, const CommandContext& cmd_context) { +int write_analysis_sdc(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context) { CommandOptionId opt_output_dir = cmd.option("file"); @@ -114,6 +120,9 @@ void write_analysis_sdc(OpenfpgaContext& openfpga_ctx, global_ports, openfpga_ctx.flow_manager().compress_routing()); } + + /* TODO: should identify the error code from internal function execution */ + return CMD_EXEC_SUCCESS; } } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_sdc.h b/openfpga/src/base/openfpga_sdc.h index 110a8cd72..99a87f899 100644 --- a/openfpga/src/base/openfpga_sdc.h +++ b/openfpga/src/base/openfpga_sdc.h @@ -15,11 +15,11 @@ /* begin namespace openfpga */ namespace openfpga { -void write_pnr_sdc(OpenfpgaContext& openfpga_ctx, - const Command& cmd, const CommandContext& cmd_context); +int write_pnr_sdc(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context); -void write_analysis_sdc(OpenfpgaContext& openfpga_ctx, - const Command& cmd, const CommandContext& cmd_context); +int write_analysis_sdc(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context); } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_verilog.cpp b/openfpga/src/base/openfpga_verilog.cpp index 1ce23a1d4..c79b68fc2 100644 --- a/openfpga/src/base/openfpga_verilog.cpp +++ b/openfpga/src/base/openfpga_verilog.cpp @@ -5,6 +5,9 @@ #include "vtr_time.h" #include "vtr_log.h" +/* Headers from openfpgashell library */ +#include "command_exit_codes.h" + #include "verilog_api.h" #include "openfpga_verilog.h" @@ -17,8 +20,8 @@ namespace openfpga { /******************************************************************** * A wrapper function to call the fabric Verilog generator of FPGA-Verilog *******************************************************************/ -void write_fabric_verilog(OpenfpgaContext& openfpga_ctx, - const Command& cmd, const CommandContext& cmd_context) { +int write_fabric_verilog(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context) { CommandOptionId opt_output_dir = cmd.option("file"); CommandOptionId opt_explicit_port_mapping = cmd.option("explicit_port_mapping"); @@ -48,13 +51,16 @@ void write_fabric_verilog(OpenfpgaContext& openfpga_ctx, openfpga_ctx.vpr_device_annotation(), openfpga_ctx.device_rr_gsb(), options); + + /* TODO: should identify the error code from internal function execution */ + return CMD_EXEC_SUCCESS; } /******************************************************************** * A wrapper function to call the Verilog testbench generator of FPGA-Verilog *******************************************************************/ -void write_verilog_testbench(OpenfpgaContext& openfpga_ctx, - const Command& cmd, const CommandContext& cmd_context) { +int write_verilog_testbench(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context) { CommandOptionId opt_output_dir = cmd.option("file"); CommandOptionId opt_reference_benchmark = cmd.option("reference_benchmark_file_path"); @@ -87,6 +93,9 @@ void write_verilog_testbench(OpenfpgaContext& openfpga_ctx, openfpga_ctx.arch().sim_setting, openfpga_ctx.arch().config_protocol.type(), options); + + /* TODO: should identify the error code from internal function execution */ + return CMD_EXEC_SUCCESS; } } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_verilog.h b/openfpga/src/base/openfpga_verilog.h index faf8cf6b0..096039aab 100644 --- a/openfpga/src/base/openfpga_verilog.h +++ b/openfpga/src/base/openfpga_verilog.h @@ -15,11 +15,11 @@ /* begin namespace openfpga */ namespace openfpga { -void write_fabric_verilog(OpenfpgaContext& openfpga_ctx, - const Command& cmd, const CommandContext& cmd_context); +int write_fabric_verilog(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context); -void write_verilog_testbench(OpenfpgaContext& openfpga_ctx, - const Command& cmd, const CommandContext& cmd_context); +int write_verilog_testbench(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context); } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_write_gsb.cpp b/openfpga/src/base/openfpga_write_gsb.cpp index 9c367ebfb..cdb833c75 100644 --- a/openfpga/src/base/openfpga_write_gsb.cpp +++ b/openfpga/src/base/openfpga_write_gsb.cpp @@ -5,6 +5,9 @@ #include "vtr_time.h" #include "vtr_log.h" +/* Headers from openfpgashell library */ +#include "command_exit_codes.h" + #include "write_xml_device_rr_gsb.h" #include "openfpga_write_gsb.h" @@ -19,8 +22,8 @@ namespace openfpga { * Write internal structrure of all the General Switch Blocks (GSBs) * to an XML file *******************************************************************/ -void write_gsb(const OpenfpgaContext& openfpga_ctx, - const Command& cmd, const CommandContext& cmd_context) { +int write_gsb(const OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context) { /* Check the option '--file' is enabled or not * Actually, it must be enabled as the shell interface will check @@ -38,6 +41,9 @@ void write_gsb(const OpenfpgaContext& openfpga_ctx, g_vpr_ctx.device().rr_graph, openfpga_ctx.device_rr_gsb(), cmd_context.option_enable(cmd, opt_verbose)); + + /* TODO: should identify the error code from internal function execution */ + return CMD_EXEC_SUCCESS; } } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_write_gsb.h b/openfpga/src/base/openfpga_write_gsb.h index 4f7110717..206e6cd5a 100644 --- a/openfpga/src/base/openfpga_write_gsb.h +++ b/openfpga/src/base/openfpga_write_gsb.h @@ -15,8 +15,8 @@ /* begin namespace openfpga */ namespace openfpga { -void write_gsb(const OpenfpgaContext& openfpga_ctx, - const Command& cmd, const CommandContext& cmd_context); +int write_gsb(const OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context); } /* end namespace openfpga */ From e6c896d58312d1b7f2bcf9c518681d1e46b41a78 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 8 Apr 2020 16:54:08 -0600 Subject: [PATCH 409/645] now inout must be global port and I/O port so that it will appear in the top-level module --- .../libarchopenfpga/src/check_circuit_library.cpp | 12 ++++++++++++ .../src/read_xml_circuit_library.cpp | 4 +--- openfpga/src/fabric/build_grid_modules.cpp | 15 +++++---------- openfpga/src/fpga_sdc/analysis_sdc_writer.cpp | 6 ++++++ openfpga/src/utils/module_manager_utils.cpp | 12 ++++++++---- .../test_openfpga_arch/k6_N10_40nm_openfpga.xml | 2 +- .../k6_frac_N10_40nm_openfpga.xml | 2 +- .../k6_frac_N10_adder_chain_40nm_openfpga.xml | 2 +- ..._frac_N10_adder_chain_mem16K_40nm_openfpga.xml | 2 +- ...c_N10_adder_chain_mem16K_aib_40nm_openfpga.xml | 4 ++-- ..._frac_N10_adder_column_chain_40nm_openfpga.xml | 2 +- ...rac_N10_adder_register_chain_40nm_openfpga.xml | 2 +- ...10_adder_register_scan_chain_40nm_openfpga.xml | 2 +- .../k6_frac_N10_spyio_40nm_openfpga.xml | 8 ++++---- .../k6_frac_N10_stdcell_mux_40nm_openfpga.xml | 2 +- .../k6_frac_N10_tree_mux_40nm_openfpga.xml | 2 +- 16 files changed, 47 insertions(+), 32 deletions(-) diff --git a/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp b/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp index 2e8c1a214..0abc16a0e 100644 --- a/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp +++ b/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp @@ -317,6 +317,18 @@ size_t check_circuit_library_ports(const CircuitLibrary& circuit_lib) { } } + /* Check global output ports: make sure they are all I/Os */ + for (const auto& port : circuit_lib.ports()) { + if ( (circuit_lib.port_is_global(port)) + && (CIRCUIT_MODEL_PORT_OUTPUT == circuit_lib.port_type(port)) + && (false == circuit_lib.port_is_io(port)) ) { + VTR_LOG_ERROR("Circuit port (type=%s) of model (name=%s) is defined as global output port but not an I/O!\n", + CIRCUIT_MODEL_PORT_TYPE_STRING[size_t(circuit_lib.port_type(port))], + circuit_lib.model_name(port).c_str()); + num_err++; + } + } + /* Check set/reset/config_enable ports: make sure they are all global ports */ for (const auto& port : circuit_lib.ports()) { if ( ( (circuit_lib.port_is_set(port)) diff --git a/libopenfpga/libarchopenfpga/src/read_xml_circuit_library.cpp b/libopenfpga/libarchopenfpga/src/read_xml_circuit_library.cpp index cd8ffd1c5..ba6e6af7f 100644 --- a/libopenfpga/libarchopenfpga/src/read_xml_circuit_library.cpp +++ b/libopenfpga/libarchopenfpga/src/read_xml_circuit_library.cpp @@ -489,9 +489,7 @@ void read_xml_circuit_port(pugi::xml_node& xml_port, /* Identify if the port is for io, this is only applicable to INPUT ports. * By default, it will NOT be a mode selection port */ - if (CIRCUIT_MODEL_PORT_INPUT == circuit_lib.port_type(port)) { - circuit_lib.set_port_is_io(port, get_attribute(xml_port, "io", loc_data, pugiutil::ReqOpt::OPTIONAL).as_bool(false)); - } + circuit_lib.set_port_is_io(port, get_attribute(xml_port, "is_io", loc_data, pugiutil::ReqOpt::OPTIONAL).as_bool(false)); /* Identify if the port is for mode selection, this is only applicable to SRAM ports. * By default, it will NOT be a mode selection port diff --git a/openfpga/src/fabric/build_grid_modules.cpp b/openfpga/src/fabric/build_grid_modules.cpp index 95e435f6f..0b693fcec 100644 --- a/openfpga/src/fabric/build_grid_modules.cpp +++ b/openfpga/src/fabric/build_grid_modules.cpp @@ -306,22 +306,17 @@ void build_primitive_block_module(ModuleManager& module_manager, /* Find the inout ports required by the primitive node, and add them to the module * This is mainly due to the I/O blocks, which have inout ports for the top-level fabric */ - if (CIRCUIT_MODEL_IOPAD == circuit_lib.model_type(primitive_model)) { - std::vector primitive_model_inout_ports = circuit_lib.model_ports_by_type(primitive_model, CIRCUIT_MODEL_PORT_INOUT); - for (auto port : primitive_model_inout_ports) { + for (const auto& port : circuit_lib.model_global_ports(primitive_model, false)) { + if ( (CIRCUIT_MODEL_PORT_INOUT == circuit_lib.port_type(port)) + && (true == circuit_lib.port_is_io(port)) ) { add_primitive_module_fpga_global_io_port(module_manager, primitive_module, logic_module, logic_instance_id, ModuleManager::MODULE_GPIO_PORT, circuit_lib, primitive_model, port); - } - } - - /* Find the other i/o ports required by the primitive node, and add them to the module */ - for (const auto& port : circuit_lib.model_global_ports(primitive_model, false)) { - if ( (CIRCUIT_MODEL_PORT_INPUT == circuit_lib.port_type(port)) - && (true == circuit_lib.port_is_io(port)) ) { + } else if ( (CIRCUIT_MODEL_PORT_INPUT == circuit_lib.port_type(port)) + && (true == circuit_lib.port_is_io(port)) ) { add_primitive_module_fpga_global_io_port(module_manager, primitive_module, logic_module, logic_instance_id, ModuleManager::MODULE_GPIN_PORT, diff --git a/openfpga/src/fpga_sdc/analysis_sdc_writer.cpp b/openfpga/src/fpga_sdc/analysis_sdc_writer.cpp index c188dedd8..584f6e63f 100644 --- a/openfpga/src/fpga_sdc/analysis_sdc_writer.cpp +++ b/openfpga/src/fpga_sdc/analysis_sdc_writer.cpp @@ -203,6 +203,12 @@ void print_analysis_sdc_disable_global_ports(std::fstream& fp, continue; } + /* Skip any gpio port here! */ + if ( (CIRCUIT_MODEL_PORT_INOUT == circuit_lib.port_type(global_port)) + && (true == circuit_lib.port_is_io(global_port)) ) { + continue; + } + ModulePortId module_port = module_manager.find_module_port(top_module, circuit_lib.port_prefix(global_port)); BasicPort port_to_disable = module_manager.module_port(top_module, module_port); diff --git a/openfpga/src/utils/module_manager_utils.cpp b/openfpga/src/utils/module_manager_utils.cpp index 64bef2e88..a57e97261 100644 --- a/openfpga/src/utils/module_manager_utils.cpp +++ b/openfpga/src/utils/module_manager_utils.cpp @@ -37,8 +37,9 @@ ModuleId add_circuit_model_to_module_manager(ModuleManager& module_manager, /* Add ports */ /* Find global ports and add one by one - * Global input ports will be considered as global port in the context of module manager - * Global output ports will be considered as spy port in the context of module manager + * Non-I/O Global input ports will be considered as global port to be shorted wired in the context of module manager + * I/O Global output ports will be considered as general purpose output port in the context of module manager + * I/O Global inout ports will be considered as general purpose i/o port in the context of module manager */ for (const auto& port : circuit_lib.model_global_ports(circuit_model, false)) { BasicPort port_info(circuit_lib.port_prefix(port), circuit_lib.port_size(port)); @@ -50,9 +51,12 @@ ModuleId add_circuit_model_to_module_manager(ModuleManager& module_manager, } else if ( (CIRCUIT_MODEL_PORT_INPUT == circuit_lib.port_type(port)) && (true == circuit_lib.port_is_io(port)) ) { module_manager.add_port(module, port_info, ModuleManager::MODULE_GPIN_PORT); - } else { - VTR_ASSERT(CIRCUIT_MODEL_PORT_OUTPUT == circuit_lib.port_type(port)); + } else if (CIRCUIT_MODEL_PORT_OUTPUT == circuit_lib.port_type(port)) { + VTR_ASSERT(true == circuit_lib.port_is_io(port)); module_manager.add_port(module, port_info, ModuleManager::MODULE_GPOUT_PORT); + } else if ( (CIRCUIT_MODEL_PORT_INOUT == circuit_lib.port_type(port)) + && (true == circuit_lib.port_is_io(port)) ) { + module_manager.add_port(module, port_info, ModuleManager::MODULE_GPIO_PORT); } } diff --git a/openfpga/test_openfpga_arch/k6_N10_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_N10_40nm_openfpga.xml index 17eb8b550..de0602e1e 100644 --- a/openfpga/test_openfpga_arch/k6_N10_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_N10_40nm_openfpga.xml @@ -156,7 +156,7 @@ - + diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml index c43614ae8..51e250a8a 100644 --- a/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml @@ -173,7 +173,7 @@ - + diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml index cf619cd27..ae08c8250 100644 --- a/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml @@ -174,7 +174,7 @@ - + diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml index 4ddf1dd0b..cb145e06d 100644 --- a/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml @@ -174,7 +174,7 @@ - + diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml index f0e84e167..e65851291 100644 --- a/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml @@ -174,7 +174,7 @@ - + @@ -209,7 +209,7 @@ - + diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_adder_column_chain_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_adder_column_chain_40nm_openfpga.xml index d70ebcb2d..65117d199 100644 --- a/openfpga/test_openfpga_arch/k6_frac_N10_adder_column_chain_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_frac_N10_adder_column_chain_40nm_openfpga.xml @@ -174,7 +174,7 @@ - + diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_adder_register_chain_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_adder_register_chain_40nm_openfpga.xml index 81a9582dc..779880dea 100644 --- a/openfpga/test_openfpga_arch/k6_frac_N10_adder_register_chain_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_frac_N10_adder_register_chain_40nm_openfpga.xml @@ -174,7 +174,7 @@ - + diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_adder_register_scan_chain_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_adder_register_scan_chain_40nm_openfpga.xml index 840a2e3bb..621847439 100644 --- a/openfpga/test_openfpga_arch/k6_frac_N10_adder_register_scan_chain_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_frac_N10_adder_register_scan_chain_40nm_openfpga.xml @@ -179,7 +179,7 @@ - + diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml index 547c742c8..159214507 100644 --- a/openfpga/test_openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml @@ -173,11 +173,11 @@ - + - - - + + + diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_stdcell_mux_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_stdcell_mux_40nm_openfpga.xml index d3e3d1f6c..59f493a13 100644 --- a/openfpga/test_openfpga_arch/k6_frac_N10_stdcell_mux_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_frac_N10_stdcell_mux_40nm_openfpga.xml @@ -165,7 +165,7 @@ - + diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_tree_mux_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_tree_mux_40nm_openfpga.xml index 8052fb2dc..d04318510 100644 --- a/openfpga/test_openfpga_arch/k6_frac_N10_tree_mux_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_frac_N10_tree_mux_40nm_openfpga.xml @@ -164,7 +164,7 @@ - + From d99776b260ea94880573814d6279d8e4dd7436a0 Mon Sep 17 00:00:00 2001 From: Xifan Tang Date: Wed, 8 Apr 2020 18:18:53 -0600 Subject: [PATCH 410/645] update documentation on the global I/O ports --- docs/source/arch_lang/circuit_library.rst | 112 +++++++++++++++++- .../arch_lang/figures/global_inout_ports.png | Bin 0 -> 24210 bytes .../arch_lang/figures/global_input_ports.png | Bin 0 -> 24062 bytes docs/source/arch_lang/figures/gpin_ports.png | Bin 0 -> 24052 bytes docs/source/arch_lang/figures/gpio_ports.png | Bin 0 -> 24674 bytes docs/source/arch_lang/figures/gpout_ports.png | Bin 0 -> 23998 bytes 6 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 docs/source/arch_lang/figures/global_inout_ports.png create mode 100644 docs/source/arch_lang/figures/global_input_ports.png create mode 100644 docs/source/arch_lang/figures/gpin_ports.png create mode 100644 docs/source/arch_lang/figures/gpio_ports.png create mode 100644 docs/source/arch_lang/figures/gpout_ports.png diff --git a/docs/source/arch_lang/circuit_library.rst b/docs/source/arch_lang/circuit_library.rst index 2cab83abd..bf32fc113 100644 --- a/docs/source/arch_lang/circuit_library.rst +++ b/docs/source/arch_lang/circuit_library.rst @@ -272,7 +272,7 @@ A circuit model may consist of a number of ports. The port list is mandatory in - ``io="true|false"`` Specify if this port should be treated as an I/O port of an FPGA fabric. When this is enabled, this port of each circuit model instanciated in FPGA will be added as an I/O of an FPGA. - .. note:: ``io`` is only valid for ``input`` ports + .. note:: global ``output`` ports must be ``io`` ports - ``mode_select="true|false"`` Specify if this port controls the mode switching in a configurable logic block. This is due to that a configurable logic block can operate in different modes, which is controlled by SRAM bits. @@ -296,3 +296,113 @@ A circuit model may consist of a number of ports. The port list is mandatory in .. note:: Different types of ``circuit_model`` have different XML syntax, with which users can highly customize their circuit topologies. See refer to examples of :ref:``circuit_model_example`` for more details. .. note:: Note that we have a list of reserved port names, which indicate the usage of these ports when building FPGA fabrics. Please do not use ``mem_out``, ``mem_inv``, ``bl``, ``wl``, ``blb``, ``wlb``, ``ccff_head`` and ``ccff_tail``. + +FPGA I/O Port +^^^^^^^^^^^^^ + +The ``circuit_model`` support not only highly customizable circuit-level modeling but also flexible I/O connection in the FPGA fabric. +Typically, circuit ports appear in the primitive modules of a FPGA fabric. +However, it is also very common that some circuit ports should be I/O of a FPGA fabric. +Using syntax ``is_global`` and ``is_io``, users can freely define how these ports are connected as FPGA I/Os. + +In principle, when ``is_global`` is set ``true``, the port will appear as an FPGA I/O. +The syntax ``is_io`` is applicable when ``is_global`` is ``true``. +When ``is_io`` is ``true``, the port from different instances will be treated as independent I/Os. +When ``is_io`` is ``false``, the port from different instances will be treated as the same I/Os, which are short-wired. + +To beef up, the following examples will explain how to use ``is_global`` and ``is_io`` to achieve different types of connections to FPGA I/Os. + +.. option:: Global short-wired inputs + +.. code-block:: xml + + + +The global inputs are short wired across different instances. +These inputs are widely seen in FPGAs, such as clock ports, which are shared between sequential elements. + +:numref:`fig_global_input_ports` shows an example on how the global inputs are wired inside FPGA fabric. + +.. _fig_global_input_ports: + +.. figure:: ./figures/global_input_ports.png + :scale: 100% + :alt: classical inverter 1x symbol + + Short-wired global inputs as an FPGA I/O + +.. option:: Global short-wired inouts + +.. code-block:: xml + + + +The global inouts are short wired across different instances. + +:numref:`fig_global_ioput_ports` shows an example on how the global inouts are wired inside FPGA fabric. + +.. _fig_global_inout_ports: + +.. figure:: ./figures/global_inout_ports.png + :scale: 100% + :alt: classical inverter 1x symbol + + Short-wired global inouts as an FPGA I/O + +.. option:: General-purpose inputs + +.. code-block:: xml + + + +The general-purpose inputs are independent wired from different instances to separated FPGA I/Os. +For example, power-gating signals can be applied to each tile of a FPGA. + +:numref:`fig_gpin_ports` shows an example on how the general-purpose inputs are wired inside FPGA fabric. + +.. _fig_gpin_ports: + +.. figure:: ./figures/gpin_ports.png + :scale: 100% + :alt: classical inverter 1x symbol + + General-purpose inputs as separated FPGA I/Os + +.. option:: General-purpose I/O + +.. code-block:: xml + + + +The general-purpose I/O are independent wired from different instances to separated FPGA I/Os. +In practice, inout of GPIO cell is typically wired like this. + +:numref:`fig_gpin_ports` shows an example on how the general-purpose inouts are wired inside FPGA fabric. + +.. _fig_gpio_ports: + +.. figure:: ./figures/gpio_ports.png + :scale: 100% + :alt: classical inverter 1x symbol + + General-purpose inouts as separated FPGA I/Os + +.. option:: General-purpose outputs + +.. code-block:: xml + + + +The general-purpose outputs are independent wired from different instances to separated FPGA outputs. +In practice, these outputs are typically spypads to probe internal signals of a FPGA. + +:numref:`fig_gpout_ports` shows an example on how the general-purpose outputs are wired inside FPGA fabric. + +.. _fig_gpout_ports: + +.. figure:: ./figures/gpout_ports.png + :scale: 100% + :alt: classical inverter 1x symbol + + General-purpose outputs as separated FPGA I/Os + diff --git a/docs/source/arch_lang/figures/global_inout_ports.png b/docs/source/arch_lang/figures/global_inout_ports.png new file mode 100644 index 0000000000000000000000000000000000000000..9dd7d2b8966d9de5291c8fcd93fa2e81f4506ba8 GIT binary patch literal 24210 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 literal 0 HcmV?d00001 diff --git a/docs/source/arch_lang/figures/global_input_ports.png b/docs/source/arch_lang/figures/global_input_ports.png new file mode 100644 index 0000000000000000000000000000000000000000..4c9025ab3ecfbd8c2430ede774848e38b6b27738 GIT binary patch literal 24062 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%0AKYa=E+^QJdprJ6 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 IEtk0e0qN>RwEzGB literal 0 HcmV?d00001 diff --git a/docs/source/arch_lang/figures/gpout_ports.png b/docs/source/arch_lang/figures/gpout_ports.png new file mode 100644 index 0000000000000000000000000000000000000000..e5140bd99b67d1c761ac7a8cf361b0eb3ff62df2 GIT binary patch literal 23998 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�nvK0n9$8c{i)zl`5j2Noh%U@1cUFv$3_a$NF&O|| z2#|_9WGhC*4RizP5rc@)DlcdD2mz;>3O1vV*=a+<43HTN zGIC@STCg{t+pye)@Jy{3g3fPxkeo#_GvCNq7;g-OeV&~xh!+oe^nwMQWaoRS3@ini z*$*#a4?tlo;e#~_+Y0lh*u}42e#fL8ex!^n-b9kMxXL8c%fgG`71aV{O z-23kI7S-mM&m-GXI7!5%jQCW7-3s(h*mr<7q8GwEIBWZ+Q{oa(PBBA({Va4+-Lu0C zZQ%B0_W*Lu83LYW^KvF>#VP_3BfyDSv?(spu{|NLzVddFN=~^+fCXB!>)j<$y09U4 g!MFsGQ%(^0fAm<~%N~ab#{d8T07*qoM6N<$g6reffB*mh literal 0 HcmV?d00001 From 8ea272dc2c51466772f52873fa6111a71af2002c Mon Sep 17 00:00:00 2001 From: ganeshgore Date: Wed, 8 Apr 2020 21:28:14 -0600 Subject: [PATCH 411/645] Patched the OpenFPGA shell execution bug --- openfpga_flow/scripts/run_fpga_flow.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openfpga_flow/scripts/run_fpga_flow.py b/openfpga_flow/scripts/run_fpga_flow.py index 434163410..d0bbd6dd0 100644 --- a/openfpga_flow/scripts/run_fpga_flow.py +++ b/openfpga_flow/scripts/run_fpga_flow.py @@ -667,6 +667,7 @@ def run_vpr(): def run_openfpga_shell(): + ExecTime["VPRStart"] = time.time() # bench_blif, fixed_chan_width, logfile, route_only=False tmpl = Template(open(args.top_module+"_template.openfpga", encoding='utf-8').read()) @@ -683,6 +684,8 @@ def run_openfpga_shell(): command = [cad_tools["openfpga_shell_path"], "-f", args.top_module+"_run.openfpga"] run_command("OpenFPGA Shell Run", "openfpgashell.log", command) + ExecTime["VPREnd"] = time.time() + extract_vpr_stats("vpr_stdout.log") def run_standard_vpr(bench_blif, fixed_chan_width, logfile, route_only=False): From b4542ea34b0b09338ca15bd368f023583ac4030c Mon Sep 17 00:00:00 2001 From: Xifan Tang Date: Thu, 9 Apr 2020 17:10:04 -0600 Subject: [PATCH 412/645] minor fix on doc about the global and general purpose port --- docs/source/arch_lang/circuit_library.rst | 4 ++++ .../arch_lang/figures/global_inout_ports.png | Bin 24210 -> 16240 bytes .../arch_lang/figures/global_input_ports.png | Bin 24062 -> 16035 bytes docs/source/arch_lang/figures/gpin_ports.png | Bin 24052 -> 16129 bytes docs/source/arch_lang/figures/gpio_ports.png | Bin 24674 -> 16474 bytes docs/source/arch_lang/figures/gpout_ports.png | Bin 23998 -> 16042 bytes 6 files changed, 4 insertions(+) diff --git a/docs/source/arch_lang/circuit_library.rst b/docs/source/arch_lang/circuit_library.rst index bf32fc113..cde9b5fbc 100644 --- a/docs/source/arch_lang/circuit_library.rst +++ b/docs/source/arch_lang/circuit_library.rst @@ -406,3 +406,7 @@ In practice, these outputs are typically spypads to probe internal signals of a General-purpose outputs as separated FPGA I/Os + +.. warning:: The general-purpose inputs/inouts/outputs are not applicable to routing multiplexer outputs + + diff --git a/docs/source/arch_lang/figures/global_inout_ports.png b/docs/source/arch_lang/figures/global_inout_ports.png index 9dd7d2b8966d9de5291c8fcd93fa2e81f4506ba8..090952714e04eefdb211fbc1b63ddb3d460dff4b 100644 GIT binary patch literal 16240 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 literal 24210 zcmd?R1yo(#vMv}bKtj*}0fGdA6Fhi;Ai*WLJ8Ycb?ruSYO9&cVg1fr}cb5>{9eVEk z|GDR!`)d_+@ z10+~*#h*}F1^k1tl@k$oR6O)@8+?g0R1q_hl6ph~J|jJP4D5@2Em|NO%IlUwOQ-TY8hF)eMCHYgt&g>njij*vgpp}gw$y<6> zdPY*-CnO{!JT?YKTyjDm{(c;Mdq-+)XZMMVfx*$yk=~Jo-pU5Tz{JVP$-v0Wz|8yx zlz3z7Y-y+K^v2Sb?5|4xQIC+Jt-g)PCp!}>OA=_kx_VajcJD|@p$+}V&tLttGco#S zOP02O4+{*C0eXjliJp<+Kk5dL@<6Y0$=H|}f|jB6^D^=LDfw^L{@Ko7xS=t%ee6j&E^5<#hzn}i!FY%x2k~6fmGPj35tY~Rs$NP^8{?q0E{FI=T1!&aP z7TV*tf4BLs*ZwZg!vLMGf0@3&*5=PuusFO=co_a;p?RMSe+~(G^oakFn9zF#Cz!od zM7LPs>uxClv<%0r&{NVzq!$?9p9JZ^U_?|bFL56z#Gb^qI|Qy$hq!#3hY1Ps@gY$` zhZCiwA`!)iM1>J3$+&AaFmRh$Fn1VZo4Ke?FCE}^Z#ro@8DKft<;K9J*QgO7#z2RG z{`SzoMyV=B_F??_hVC5-|JuopILHeL{2EZhAg9rF7PSfS!-HQNFMd9AfyA?tIOAq%1WB=uV>9l-pJ>K4I>>t_eLS}vi9ax6{HtMeFq!XkL{Ky}At52{o$Imt_31X~XO+oVm)+h@N%MZLQdnCmmpwDnlKX`w zAJaP@ScWz--7YOs#^V%UZ+DfyAmHWi97yr=wNKaN1j5Vz9LgRB*&F=TN&dD9ymq*>iQbPxg7inXBEb~1*8~yQ+4@0UkTk+Z+CQpC= z%tV|ouKS%I+cKMA6U5JjtPwSIDo#Y=y*F1qRj3++c+*E)GXKLQMyk_Gz2=kdEe4O1 zbUN<>O42<78dLMtCIJ=;RV$VPMpbm;%5;@Q@^`+aKvY5-9PeYtiv>r5^!oj}P^7El z4e)Trk$2jw$y!^8oe)G|jUuPl8O-Bi2Hr2*{rOq~tA-=(him1>n~7gkGa$HBvMq7b zW%?pDig~i$4yrPoLL@9m#u43N9}{l|FMAYHvDuV7x*~|RD@_&Ei88m8wcWAtuGB@I z(g9J<<|sH&8HbF$~#_@;5sl$5jYpmD8JV}r( zP`j)Tm*2b6a_3I#cZKQV522#_CmD);d!t!5p7;`p;*HPC_Zuj)p!hTPc^&+ttF3e_ zyOTloWQmR!h110O$|Z|GOvcP<()~X2Ct@Y|vD%*R&cwH9blVu}>5&dd5z;6ZOQMGc zvI*|!yoBuiF#h??z<=&K*ISpwkMH)d{X;l@867eV$MkALfp=JMI$!g5zAqvJwur0C z?QJDe2>~v%KWu`$h+)-CPv9uqeFj`GhMmC)g`dlo>2Iho7Sp}Oo&h| z$vWZ%Q=8DuMe{>AnqexB^Js^VH_Q8bESK0eLvf#6^=eCn1f<>6gwZP_+Dz$GgF&4oGKUL+dSdY8wy_pKcisyB6lDKY4 zU`g=iMLTi>6o`WTcOe)rhR#slEXu$(+G@+5%;#m+i6Tdbp~c zK&|e6v(u3t0Zib`?sxgz&qu6mnv%m&k_I)N5BKB}aS0?@@hllK)f2BvElDAL!ncf$ z6wEtLfoQ~Cx;b46kJ&STY{KcsrTdxt-5&QdRB=qAsOD6}OC#G3G55ojXwXj5P!!K zt;Wb}6Ma#oD&9rS_JC)EQYl61)#bECe%F0pXn(r8DYZieQ|CwLr+97Sa*>?SBbSdJ zMN`MWkYP|M^(s^~FGDDuY02xJtvhnQ-p*yB)l2R8&{F1nV$@W&&75Xrtix~J#bkLd z`~pvvk%_bPov#}{ubWe)>BQ(s1&TcBd1b5~jc@yS_h;YnZppaRN*fA;Iit}mF}B!e zxv}tQaIC6$cVEG-Wb55us896?+z^r{C)IF2n_yETLFI1XBEUvk;b*S4HW#s)DbKdw znPd$?%P=uAwyi>?)TO%%d&pNT_=SrzUt>T^n{7HRDQ9D#^G=>d;NFsgq%{V+(Ml2T zhDV;p1&-EA>5%9Ca!s;JwaZ#5=v9Kd`KjVsFq>C9C;VJb2NRC?GXl1bm`dD9^%nut z#Jr!Dy)-GxkH(f6kG=eIcne7qCEX^w6ZyK3hSEcAwoxiAPhW)fr@No4uKD&9Di*|j zeDX0NjHwQ}e2qE>E2ohyMu*TF3zY^^j~N6TVjm!tY!twTn2uV3A&MRo_DV2dnnP*P zRiXWZXm6C*ndEnltNQR3E2RcYGY6dN_`5BKz1fdcC)#W_7TblT>eZFY<-8n>a;tN6 zLnMpd_YU}JAhcd^N(oylIt))T9VU5T zgqw2%Gb&yYY5I;cJw}+P9N@{LiF;00nXT}Q#z;%nkHhZ`-&AM!y61WihTY8Rcue6b zhPMI|Bi|^c84_bq^I_Z5uIXSz0zq|(te)2%7|f>QRHvAyuWqFRkIce2Z8v3^0`#kn zRENgg3nriCSW`4|T#iv|!ghn>^*a(Lo41LXz0OymS)4Yod-ycXYzl zO&&(m-d*J8BXT)xMU*lu%*N~LzNLFi?n|8W_1nq^kyp<7$QBq~-g_k<-%8#OCcP!# zcA%3og1-prB^UT%z1F1=mY&PlptmNX#!`9lc|b2mGgcqhVzI&5$kyNIQJZB<+5Kv? zWSG0NxL5E~>E|K19%6=l zaapY0oc1#%Dk|cRT3Z~OpCYlEi1Bs?o5;cXGV*0*KsY*GYKoDLf&T=KyiJ9c5UP;0 zJqtzl6Q@g-w?zDEAtPaa3Oz1U&$+Dr~^(1bq;irJJ%_mi?z z@y}Q#DGK*k`v@O{oJL}nyo}LDZiKP!MPbT4)X|F)upXus)>2AqUd0RSLC-xmQ_OD@ zT8cn!WYNQ#ilqrtCzh;KEeP3 zw4?%iIX`n?S_wr5eqmnlGCd;&I?yUUQNkb-IiA<6y@3Ufy&@xN#bQ3CQGvpSc`<&U z?$oA@KogjHtlzcs?Yc4*jwoOztCP>u0t5V&EdF}c7mc?@zZ;d{htVLuxWU$Uu^0+k<>DXryQ_BFqn)t= zqdY+((I0R>UyqTQmAeP- z-&*kUd(qNdpJd(C5wH$%G>Uy45L@GsukSyw<8#_%NtqGSDity^T|qt8*&impzL&sq z3+&>#hdz7I@BSRMK9V7bO`|~47E3L^Ig}cj2^_|040OB8!p`PvP+lID9Rj`Tfq(#`DFD(*5`H+$$bl#N zdaJ%OZ2-Uq`z9AR0++TsoZupm@77!5c)r$_5|uRrK*X)#bmC_@7IRfW?Ba%So=l3jV?7;}I*4tD5iv{R z9flUFY<}hB$HlORnRxUg9*nxX`?;z3`lw&hUdlv)^4i)fD@lr0kIel@cwOHlY(m1j z63@F(iP%z|laFo~=-!7=M`_~DEYOLCG@aA7Yy6RLYdsCy5A&Jw#SHHoSt>r%O6C^2 z`9#eASZd!E4*TB<%eC3hV=P{0p_&DI-mVdsC5uug-2$bjA0I+>tr<@59+C+TVo{G`}SeP8x9d{;y_WkwiX?jHBAY*> z!PI49-pP$H36D<)#}mrh7+5UEO2s@hE&z3|>1z$Q^G4RPBH+omh3WT1z7k~+ z>aVhxW6bJ`kshdpNa7CF*j@wQ6K5cOh_vNkP_q`;bbTsbdLy}vIGKzl zYf{F5QZcF~W~T}IE(CyL4fRhJ*|3422;Jmo_DVm@riwSuJ3o7|PpSy#gKghX(XH|X zJ;QRLE{GIUk0MYChCukT)ZXt^%3yf<6TAW#k3M}r(&NWB>{-vPFH<9tJIz~@cTosU`$ix%1zaxy!v;Z{;nZO_KNmW`xq0-$<6NE2*Wh$WS(} z?*mk0mE!_^Yd%g4NGw)$GPxi5N)P$mY+hL6)KOOZ)4_!%Qf#lFWCCBkjk$hBfgU4dDzK)Z(|BB>mc9q?Gw2lT2 zO$4ekBm-C2Yq1Yi2j9$<-<$B@jgVVkzfpKBtAN89!EgSkd8g00NG_Zu-Rs8@Fa?{BW@BpzM@B*R8y2@@SY9|Mf~}j3|3W;-<3zTT{RopoEOsV2Yw%sYM;Lv zj|0}OS8kq05<0I!;Ks$DGC}4uU;>jE6_@cToPph9io%U9f4JCWop@t470zoNVv zX^=QsVF`eUD)kA$Ge>%{rY-9{_PGA+5w;yznDv>M1MkJ_Av`2`RYMZ64kq4s#KZ@D zsxcH&w<3%gJMjz;rt>v$uID@~CUPW4?Q}!_y6tU+!ZZ1t7;VW#*MeWH|IB3%&6c;H zpDGpwiA`0ZmQ><1{j<5oLV*$+s6@a3i74FhV-XX3nkyr+NOyPKf3h`XsM$&*_D5o{ zp!a`0tH@%z@kRSg#FK!%@f`)aPCZ>vbx>yOrdID9UrkC+-;%Is0(TVUu1OO694Jtd zex#oKH~q4Kg(Ndm{(t#yhr;~$5ezhV>kw_`1JCcz)_jzkZ|Hsd8t9TG6|?AR#c=-! z9U~YlOs2R<2YxsKACKvHuIo8UGEc2=wPn=DkJ~+xVo+a?1cC)tpzgjul~Sf&tMUG= z`*l_-H?h;zMxXtTk>{C3wCamPhH?yfn z`ZP&UfdIP@EMA=}AajP`fd{0aR$&5(oQDpF1eO0lb&^rCXrk=z=s*LDY!eN8RI#fH+jFV4#i=g^2zY1oBvU~n-q z^DX7OYpMD*)b#h%&Rj7mqT(*QJ0(&BN#qj6_^iKHHV5U!sXYI#PvXGum1h05o0+}2 z-Tm%}p#9 z@7%7u(Pda-G-R1`;C3TEmRYRgAwxb_RQ(&j_aGu_Lz&(78jMS)e z8~o&H$a?>j%|Jqi#hfLdjluY6cEk^J=i#?g^Y0A%0y;YKf`UZUD}SpNO#ZOn(bB3Z z7618d&GlRePEg~n#+4m&=e{dEP|J~U2B?-|QlQE5_s`AE-P{^-r#7bWETWdqVZ3mrW1>KWUTeNutC3=(pF3t?cdoh@1ZCc+oyIISIugF)^1N{n zdd?X+H@8qX;c+4bk0uw`pKLm=12L9M=dH22|3y}Q{cBP^J)QevyN1K3SgiQb`|BVt z2`J%CPMF#{$m6ko>BR*{@i`%(hMUQ^V(9B`Df6$xMOWpU^~L4U5xDzZ{erkx9JvZE z%bp@QENmUO!|juXYR#dqRPt=y)^O@ghS4A!Af+L_LuMUsQ&lIvd zeN$;FrBeC(dzA${A;(CK24{5XirwbzRY3^0nUjh0p2qR0Mt&`|50W32_Y$S=$~a9W z`3~|+ZoA!+YF^@Tb6GZRpZ-tG(^c8Almrsy)C;`M%ugLDK7XvrDblv0zaZi^Lcx>k zdw)-g9tWRw%YQMXs&XrCiMcqWNqsls;a{ARbpj8@59HYY5cE)c24kKZrd zA2yyuREA|xn!oBDPAe4`Zvn&ymeu-mJ^I#rfuNSO9>u+yB*HAx;~C-@~DxK05;gZ47UTK4z2iVa%8k81(v?gvz> z5>zvaec5DR6~YZ6sy5P)eLkw8B%4iD4xTxuBq^xcT@p!@Yu|)XKdSmQOPcz2`EC+s zbEMMc>@P#eKSWLvPPuiiS!c@2Gb1#e*h!AXAp^$-N%>LRD5J>8rZ6+;wKu&z*1#D) zhLv(Keq?;ST>;50)KsrG=)Lw%d|x6h%3*WOns~@ebW;UeIO`Nn(7YBbdhd?Scei3V zk@K}rI3)Jzeb(a$ae%_hIf1J%)8MPnNJm=gHdR~&0DTJEU<1lH)grW_Ly`3SysB|<|70wF6Y4{mxrt2!>bdsrlZFtmhD)*Lh$!o4G;Gs1mA z0U`IX<*vHf)c*NOpcrKMc+!;g5I5#lTJGTGIvK1kSPaSmE`>ocZkQov(YMwl*0%=7 zhsxSt=w!dHDJo+G#^ZkyisYp4k-S$Ny)_# z7NS4c}aseSBKNX1TsDE3K?AvvJSRw7fnt-S>r)lhtEOdX~?&J%F<54riro)f@52 zR{xaI?sRxCZba#g-rY5ybaI!s)zinQLgz2!F4!!?!c-gd5#udr6n`&$g|R%S{(SFp zsB;mFwX`#1JJyaymOVYh)GL3zX}~!o#4j|1gp{TovxEwhkCPu41u-;a6Fu` zF}cneln0WbAz^OxY&g`%qdo|quI6@U948APS~@JHAIT&_97WaH85AolbJr$>LyPGY zg0O_k{MVK_3Nbwn&Fpj*tuzmh!(ZNvnn8waLiwG5Ly3HFZ!f$f_l}eGr~_H6FF2Bs z1Dz%NTc$I$eOsA+!NtCCEz^AiD;JekLp=T$1a6c2ebW)IH6aNNwZfCc2;_IEZp%Kw zUgNEKvk(L45dIffU=d$97ON*2RY^*kxz};h1-8#l?$1GjV?%>i;LUE2Rz?VSh+}=% zgv$K(73-^{I-LtA_x7l?zCPNzAVH8q=R;mKAVJ+d`b-T((h8`>wtCNrx={FhY+4*_ zjI!6x72W9XCyce48h&~YXniI(9P;nS!ZsZ8JUOh6=iu-S+;~fu6~2X7pfp3g!;z`V zE)ccRv=Q$?do7_y+TA)qtJwD;QV?E40H_LbLc*azp^#}?zP;WI*6FmD_%ZBOOV?)@ znEla)!#Qw>4n&;HUv=A=nV5=b6cU;|?#3(4oReFsli6dQV7-+dp(eocrSKCYU<&Ng zbW4WI8IHAKR(;3ZS;pz-nTw3ob$7XD7K&?|LWLN%y0&$ayE#}Ez^MK{GoC@j?krDr zOvVjJz6$1uoVtobK`6eVH0mHWUT<%Py~LMt%0S-9{0YA12O{*B$g_FoGwk(tx`u@( ze#qy)7(ve3{w-CJiN0GKzD>iN&{5V~0{gVoC7u*=TR+=S z@G{@AvaGrMp##}WN{0qu|LYDRhx-{XF%FOUro3kxCW3mTu#dx^WGH5Q(me=RZs&a_ zj>!gl<#u=fV&`NZ)UN2LRAf^5@#&a4hnay) zl%rHWdGZdX>f-7o57AT{;DK-mc3=_9B%*yZyN=5DwPCU^jqA$qVVW4nDc<)V#c{DI zpkg4oLN(k{r>Ra5^1$qm?X>UmWtpxS?yl*IGw!FqVk#h5c8BBC=@{$O|I}W+TJXHP z6QN(Yej8|qE^G3g2NIdgV0t#qd^rA0@~>L+oK(j#>{cOkFavn*H^E;?pc?i2ixodr zvx%2~z>jw%bjvy8=KUcXmgzxn_J3PdDnXH_fHcsS>zZGm`~lzffR(Q)Xr54mK8M5! zKwYGxH_ab(wGVZri|>`p{(!c10H*{N5%T|_@tROC_=x&z^&%+6{omBsSAC56iKUtR z%1!f4b47ARK&+5FMG{@>h3%nfi{U?^AAHwnmx>IpW@izFTuUQ?A<6)3)HZqEsR;?A zTh}3LmK-LO0VDy2lIQwk5tQ}teklkB+imxShUO2-d=I|;|MnhfcJR!er;QeR+<42d5cFL97k{*~oDTtB@DoPul|w zRkZqgLizm;U6`Uo*j|CTPPX&Qe+8Xg%U3{N;?k73|Hw-+Fe<$igc+uP#7rG3X4$zD z(oiw$p#ZW}E+&$P4+ZYaI6yirw@oVlFjM8oKm^T4G4cccpnrcLXX-9$d(WYZX$~~$ z|Jmk#XW8t z?)0oHyn@{dHGh+uoYQrGey%^xg3pa3Z~nO|yoM-nO6hk`!_1GbPPS&+R!VfDeD)5^ zmrVF`lcday3}D`GR{kH57q%|D$yUc3eU-aSH*Nvfr;^mYU2uqx2NRCu!Y33>LcX*BLF3Wf`UY;d&>m7c|CV*?`bBA{Vumh zN(7I}JLN-CobTVzDqNN-m#i{0j%TR7MOk+seuvL(sZn314>>y^R?iM`tkpi-!A-Qu zY*<@cle8hFHZj(i?kG5{+T#_ye0Et^!+CH28!RfjG(oD}yyXMmVXztQny`C-TJabP2 ziPVKH*yYzawW-{H#da*S3pVbIJG=KrXW4Fzgg4DK;IZ%RC^z3PTQ$Avr6uHMqm$p< z|1Ph5u)qzBx-z$Y8_WKbwng+17DZ`}@@ws;IQ=dZi#dmzD6-5rdiN~De%IpK&3$Tl zw$~INgoSu!^A(5HDszg|FOJ*%6+>_cmntBu!B{~B#Eu6a^cI$Cjz)Hh$<*D2S`@)n zYHP07PG@Jx{?PTfqVKsQ@6e{NqIz}P!fy5$#%wklMC#K4r;FVHi1gGni^aSNL@kmO z)tQ2fN0UqSec^UKrwe#=Qe|nOQALO0@RHN#X7}nOC=Y{u@v;Nt%uJ6nXkN<-THEx`p=7_3)}NqfMF7MHwzvcr*I06*RM=B@Ef+O|1124<>JfXx2_lE4+>OPS{>NO<~HfPhEH+q zj7Y1IYP`V&5x8*5&n6Vbjmw#4$p6lI-hEAmB4hVT>)OxNk>uH>S|ckb7AMOq8Fp0a5o(Ul?ksI-ve`Q2M$ z=_*?mD8td(*HR<1-1H;OVRN37M}{0RQNhoJGI%J}b>aSQ=i=bOVT=N+b(4SH;S22) z-ABFqDhm~?gdcHdBWrA*PbRb$D8d!;SiA{hU_tzOfUEbbLsV$`V~#=MM?^oD#^4{fPZsK9+N0%xxGRV*;lbeH z))cl+%*O(z%sq)c*V@JBB$bQkr%d`gZo|FVS#pU`%2P zJs680lGvv@5hK&@b!;r29&oik*%+H|^rgJSZ?>g;lU%FYjL0_T6S zVaDRH+tnd-+pN-jA2F6=y*%gR4OhfdQ*EtpFtAF<9W;_7mwo>XfBD>F=T`|LnnOCr zQDfRW_IJ08av^&uDZ#RpINpVS;_V%ZT3wjSqOyMQflf9y#s^&X99}vHM{7vYy!Jcy zYjrzR{V$$=dO^i-x}IwCF)C`fvx`(%ORdyvqcltesWEBT5IU^)YHZo*u{oetXI1D z5ed{FA)whz`ENqO67S*L)iHuF1dx#_KbO-CbB2+*Pp@ZFj{k77_@!VCV za<+kNp0zjE^=75L;j8OjlY7<7hMpcrE54`OKn6D()h9&o6!JoWgGI7mOjVIODsTv6 z0WuK_4!dm+K)K?XIvH@VvC?iwlBu(uonlz9V(4v3b3 zbZ|ArJVy7s>+fb$HXnunD=_9HInMDuaBXeGkI{r|bEGB|^4x;cu7^n3^fR8JEz*ND z7Dd?2QTG%M0}+7WFqz)V65_mzm&a)=tnis zuXk*|%8YRFxMiw;D;49;t5uOBR%_E}I^p86$g^lDTWS2Wxyrb;;7M1JiZ?=Q|5M!P z-+Px%LqD3b9rh^ae&F$X-oH!t9J)t$<%x>=KD^F2?qT?olzF9N6ojW{D#SHsJx!}< zk$=ID+S%97d_+2*@3Om_i{eB-_ONICpts7PR?$|fU8B)^}&97m8B>Ul88a} zxZ3j0xIl^M-lTpUIm=`%M?sYzIR7~ON*FW#{70||mnV78{(BF#DY7BF4W`nScz~{T!8U zTE?kRVrRmK0W#BcKakjyr7)V6CjAMSlA@~tH#TFVWE6_GTI|8L(3sjVErlv-zc`?y zjIRpp{-|a`kfAqaUyDx%MQIrzqZLc-o<;j`B)e*7mx&lsxfdlnf3W_CSmQBLy@>O_Ig z^Z%uLJt4c4i^@^I$YE2u)N>bU~zW8*=bkMO70nb1TM z5qo7LB?r-|VhN+6fdR}|$0`ah`{47da(guYj~if+A0as<)X`kqYw>IHJCA#gG!ty` zp<#ei#kg_SeF2TQ*h~SCdUEGM>v|?qGxY&qrskq_mJ+(mGhv`F=991X^M)de1>*)Z zRzFt-lz&M%&1bm~n1P|;MgZiN+#|Asf!8G8V-oE=ht*R&A+MAjpi|C$7SR9O1_V>$ zD>)>R30^g*_Nn_Lya&4kz4W;vRB(hy?!b6Phh;hczFhS zL-!^@`h}SFzU4`Bo`*F5rzSe)Os$&e(Wf>Io-5OT#k&KZLEZ`h@uvjvo43rb7UIRw zGMtB744*bUpzVQddup176x4#PEV^cWpotFBJaUh~F`gr=qyUnK&M4i0_=Es)8mJ`V zKqV0&vb{8DP2@9L9*fRuP;hWPR(q+lX*9TP1O=p$b-Jk+lj+r;J>2ZlX>UsXSsq zp)QA}r_D=(8BcK~eq{^rOyFFVlr35P^Ic4HwF>x){Yb}T2h$LEQ+wM29a~x8p#4dV z!y3BdKJ>?p^SB}}&aQ?YGl$Zcr;JzE8Oj{nV_b;O^NpKdrKc0>! z;@!mj6Q{dEd9o)r_ib-3J&|Q=M=IGyG|<+2EEzmtz#yfSXaN~dkR@WCMLMHseZ_VXfLhx1Xq>2-&4f5 zgY2)Jv-P_J4EyHa+5i6C=!(c{ercL3?bLO5UGRS^UqX_{8rJ%3ZGSe$$YsIa zpa9@+kmeRqPzeAhvRyme=jTU$f!T=bv6nnly{NAf}w5nOD&CAhB3jo1z_Bctqq;h>j@ zn|!)xL#Q)aJW+u7f@Q*31<%gups_&9ldx0IuG-RG{7YSv=>%%;q!}5veUjUpg}&?Q zucgwAh3MK}^dDjI4wv>9e){d_DO&*#7BC9Gp{?2)Ges`0q=PlcPyz`AbL}L#Ig`X~-*RO6 zpaPO<)i_yu0-`bZ^S9qlHUH-+6b@DdAXh^1$^S~OMEH_OcG4HpApQi5OLyiMM$hDJ zp~g{XE>7;fIbB({-OYM0$hPJ9Fq2f}(O5h0x|_gcAWDsR6$*k;0Nuqc{QmucP;8Rs zGiPDS8`O#u&&zS@af~(R(RRaBm?7b zM+?O^8%gBi14~tVV+IwlZd^(QN<5zTTbqO2jw@}z7mWBEL5w}^kvu35!ZUEtXx~?? z3BM&|y7{VS6->oU^fgwqtRDQP?WHCj>@#h9#nkduL|lFkK?qMvZ9|tT&DOsOGk|ks zG_Q1LiZuZdz^C6H03`xo9flZ;Ax9b= zldQl(cQolsNh=gcV`Cc&d)km3htP$v=x-D~oDoA=q;W*Q>LiEhnN43!vRq{OOU&>I z&X16I$F|cOq1SVk@WyZSTwYIe&&;#n3yvq{ar!Q|p*`oURI|b_N&kvH8CCVj@C4x0~xpH|RZwPvf!u3-tBycy(>{v1ErzmM}LvatE_oqLc4W*VmmHZ|fM^GWJcn` zA}4ac{q(z)4i-Xf^lzO_>}2hn7~VpH^(Y6IG@s#rN}<9TeZTRwhjdvQR?W--(j2H* zAkXz?{MuFpHL$X1p?a9DUPBUvQe`ayEpT7u;p@wnlp*AHC9{aQ#mg#V41rsO841l8i znZGgvra8bFY;Zsf97Z)FO@H~}5w+g%-4DvZ(Z{Yf#*&K-?mJT=qI42*?~+kpE_cGf z;j__UVU6Vr_;2q_$!ONcvDh>%G$bk{?#K}sJ`T&UhkA1xcXFbom zH_`7?DI{N9tVNW((HgM+_L4C~COM0u5#jH;O+XX@zYfqe z3v$`Jy%~VSJR3e}Mnn|xVWUw$AEeh*!Q0`{ZF{CzP&{1$aT`tx@B2b#XMVAtqB483 z7BN#~lC*nrl=K$F4t5j^CQnW_kLZ2#g+uQvg`a9EGFo;%p+f>mnvn`t-QBi1XMctM z2SGZ}!PTwpdEoR~aG0b!MPSA`Z75C@GG!d9#Z)oKudO-5fKmP{>~C2S`ghoW3^RG` zH5^YVb802h1ag)N-Y%bYn;KSvQYljfFB3++7s!Xh`b=ESU@GW58<`N~^*&R;SdmI& z8d89k&gCC%XMeQ%FCl-;x=YEO6;-2-Ads)iH=F+@&~ujF9@b>GhFbA&|6fta;gxIy zKg>Oksc({8fq~eXYUj}?ejysjyY%OywZC=FDv!UVp=!Ymn0*Zv7MjakbNg8KgeA45 zP%3#pkqeu<4tKWtLUkJWr0FT|lE3=qN?(PM$oxy%(>dv(Zi!am_(#ecHTZ+;GQLyM z{ z{=bn5zxKv6HV%G|4q_x0fB4!J>O~78%GQR}fptGYCNr8nw#G*f;JIo1lzz*civdvB zmhUSsW;*^vx2$8I|`9HNZwR&9rmXy@z2TbbP=A!c4A07i9plg9gn!#R@n@^TYGOqmRpBcN!cW zbHh>l%I^)TzCr0TA1>6r9F<_Oc5J#_4NY8l8FA~};LNt`3$oGWB9QJGmRxq;#IQ~K z4VkBV-aD(qNMqPNM%d>C2NuB@P^nvNTBVmfP9tO%T*TxN^8)sXGiU-vOM%~ccdf0h zEoOgEtCW&d3q_F%gEyWKll-<@?1hQLbYjiY0dEok11E5be*QKM;VI5Q+PgX4*NJ;T zL&|_thP>b`J}WM()8XxzmYj-eh3F);3OTa92O67bd2w6Jq8@syj^;@BUVM= z9^9R&*wVf~#>J!{C(o{>aKAY>baMtTe*woHUzj79XOuLatAV3f9$?X+Z&$IHJ3sJz zsDIWLbuZGJsE$8BzlG$q9}dnh@*Z_yapYOxANTGezLGy^S0Hwv__|Q`{PDL93DuILi4th0oKY6uEH?oS(e} z@BFwpmzfB}$WZt$ha%TuK*aC9=z8!Dl7Gr4J~V$@BkzLVbboDTs#s;N8j$94_Irhh z-7+QJt&N##BJd6j!EfMjrG0GdmUXGON_NuqYFc6kDIY75bY!7WAbDM~2v9FI5LV^BVjV?;&#@Q^(A*ke4Xci(+?^XAQ& zwV4J3&U}dW&O7h4YSoJIlt&yfQkR#%Q3Ir7)P{HZGTf#8`t@Ty%5?P5p+o&7aR&KeXrrAtae^lbt(454dg>{<(gtzQ z+<*W5pe|apXmoTm3n@ST_@fQ=?+D~BA_ugky?gg^^U(Xu0PwLlQmT*A)~#C^Bj^J7 zFysE>i!XS((N1dDuDyKua8LvzZlP6E^+_^IoJ4Txd6)K?AA|3*1nu!w#BIYaHsPsNV zF+;b<(na8%NE28d`_rHPlqXLfQ+ogY{TXK%MgQ@SfAB1`PQZ=lS$SFuF9uu&6vDV8 zdH9_&5Mje3L?1K6@S%@Q7JvKO-$-jd+^=|22E!up3l}cTB|GFM^vyTlWHhH*pk%}$ z2=PXSD2zyjZ@&4)Wa#mI>#et5%089q8xHGuPNHS&06fR-TZ9!KuPcb?8XK3P>Iodz}`A;rcrl2i$j$OepMB;LA0bWs$(doDTef`p@a_rc#EbNfVbS1grrXLac&QvMQ z6a6zJ2cz-uppq?Yix)4BgK@#$C*11zP;-V_hIlGKd!0pFH|_G!cmlbL7|{%t(W6H* zS*9%3)@`1tK1#cH@4orwo3Fn5Y7#P*@i;T?RH;&hOMsIPQj^vp@=cz1-g&3FZ79z8 z_o39~_0tX#R0Hd87S2j;~# z!09mKG9#MV#$-Te;!oc*4r z{PWK^5De&2h+je()0s+}4-bh=>)%21NP=2J`!A)QjTYq;pGZFPw=L)Sq3?OjOwMLN zK@ghTmzg7TdE$5M*wI8lhvSlA#axmz6LWmn6v2nNDiZ{z4Bj7ZVv__75+TPoni&q} z;NE+}CMH$&5In}8{)4&M+2$VQ*`~=Ri2Bs2Q-@HdUB-uRGtFoAVSLOK#t0tc!#!3^LJ_e{4rmc7EOUz= ze)xg36ogXi5!vVC8VEykpRl?0-FM%SCrMs-;RTAY_X(rzk|j&ue>dXhF1nrH=z=|oLsU(_&6+HMLQ;Es?unG15 zAecBzJyFGQv;4rQVu~<9L4Est5TL}|f=b$~7&>?~e2&XDj!%cr`!Lnp0ABWVFZtn6UK+R230pK z5M{U#V9r$L=0=^go(LYN4q^R`!`$DQx5qPQ&KxRv#~pW=;#kW9^ybZ*&7~Po`t<2z zYsE8cR6uSfo*dvJ)fm=^X+B0%Mo}w$@S+Gy$^MR)VEDh5~Otnx@*cD*@4n6xMm>T-|X5onB zPDR`l6rkVmvM+4$DsEqDmZkh>_= zN69>!Pd@pit&G#*NALvlAULy1r#qzwfxDV#fyIAj=x7f}`W-uVq@gUI_hG8H0Rw{f zV{UxVM+2`0a~EzKltQ0L$dCnXX7kKJ;Xr#CguP?>6g))pkI2lb|L})D{5F;)|7X@A zX3Uu3U5X!{haP%}sg)f_ea7Sw=BYGN#wgZA%hjnxt$V(Ek1l$9dJYsNVC z0O%fP=?vWVlAcBq!-O^>a+s2^scV<)Y&7H(ZpI5%$*rf;L0YC2PM`F+S)#Pb8Sj|d zm`i#PhliaQHw1Sr(|^W9#EDIqZ5<5g%-@mj3?-(LG=bYJz+W}7O+_knGTT6ge)`&8 z`ga7*(0LnBLu`{!2zUz6&zRW&ob@ZjCleQzzbVvqlm7~2*be{rea229==Mtl~;jfyz*85EQ!$^gqtpyqi;-Y{^Z z@dS7mnS%TyGUGP$4>JXE){VJ^^U}YYfrT7c*7F!hZK{NX1iFd&fwA#p4aO`&5Fr=; zSfpkVi5U=^|7=~-Y!qt55AxP#flPCbUuI}9!x^d!t7nwKDu<2F90a#nowS~Q9FQfH zJp(^+{0EVP|Ku!cvmuPGhwNoB4I%82VSIc%W1saz|HI8PkA8_L`$~Y%IDHUqwG>rR9o=hATK>Xhl&a47w$bG`fxJ_%e<>_cvswtiM zDNhgri3s>$fM8?#cVyIEl>SF4rJKau%zhpO{|(HjP^Mhlw{K@H9fg?nC>ArAF*B8B zfh+w*q z62@l5unsA5U>(zXBA-w)?4^eTxkY>wH|vk6Ll?G}sXEMd83UGyNhMjUF$*9zEe&A` zOzZ5Ln+f8^rF8nkuB#eFm`N*ZDa0{5x*p4k#GweAc}6)>T46v5!#>28LN_7#%q7Ds zHx1gAb7gPl%$YQb{x)%_JeLgO=8}Cl21mQcV>|<4Xkw=4+vgGo88X*lXmjrEnO_D3 z2${_%?EQei#0t@cwrY0K16*nggrT`l7@M$UGXxMqP{oJ&DU&wbb~}VS(3pfcw_z+d zpG$2vG(C<~A0;!-MSbyEiqZSIJxu5y|M*8|qeA{5&`sR8bg`{G{f?|fyR|+6I(*)T zf!qcG=I=k6pQSdYv)oarI3@?S(%CO}ZoJu?eJ-`7iiq7=`qX1(o?Sz3437cR7Ub<< zU6Ik_+H0@%4r0B|ND#yXR;Jz&@VTh*I6pL)-3yF~UwP#fGi$Sj&^%;!kLG#nYjUY5-q-H3bISS4Yy zMM0FpI6|H_j(>+)qeMl;$^zR;Ha0=YC}1jS($WM%xlK{;ymO;0w&+%_Nu65UrdQ6^~hicea7}Al}#;Ff&a?1ZFn|6kUKYK;>e3LL8QExL={l zh7E3fs38^^@TAw+4`k1Y1zh@w0g-(;WREEP7EH82FAS?Xl! zO-yDmJbp$7FcariH>wXi;Z%c6nBigbYU)WF+2{9o+?+KJ%I)V-!ZL;D!l-D>8qM?; zQ(7~RG^JoXjif|t#H^H|+3bO%>mw-%3hjfy5)1b{JqR~8V|bQ9hg!@{MRx>9$^U(r z>TM8UDSo1H1F(h3V@@2N0xZsPFiB#E)uiG^LZa|cAWh+qZby?Dop{)p2XLSJzmro% zgkcoJ0=>*|f$q+YM=5-`pP78K`QoSYHi$=wpDHuJS|97+S)gkexVW{fr>DcGIP&mh zEWm(@x-tYXma#O)#+OqzQUgp3n1Gwy5qFFo4BzB~3c@7Rqg2!m9)6Ef*iU0{G%-oS zHWI`=;`q5l!ZCmvNlPZYzX2;LYqnt$@~8s^iRh7!5i<;Ptk*LrlL=jdr!yKu27=Hw zgh34*li|Y|C(UjsImNIo1=<)HGt${mqpP7inmBAH@C2H28Q|y|o0eYl0LVfRnkWSL zumNsX2Dp!np&FS4!Z6KkWHw{#=5$aElPCQ}#mssR5f}ov>v#f#Dv{X$syV+;Vu+-7 z0BF@j`qvm+J4?rHOcPBhei))cQ9a@_eKEQeZe+aEl8}X5eDLSPNR6PSgK*o~rM<(G zqx|+o=OS@~YNr3lDLqP$KmNG0T;sNOk0wuU27VM_WD+ur- ze$VY;>W^JJb1$a({+Za&RPRG#`ne5kK52F!Y`iBKOnGgFEHWc?xOHhDBOzm{QT=H$ zQj=)@@8m!bu?WvFXdee0OrY7jvUNz+f#Shaz=)A*OrH_1@agN}$q>ez$awlGiM~gA zBgv7^9ytVliELn(7vVRaAiS-3mPaZY`)SPKG zsyc$lY)xhVwy1_F5nz8212YyC(e_vYqfc2ML-9{3zWhZ%1VTW-DTTb<$XjE=%OV2{ z0_c61Z+S<{=00qSP-@bAHD`m$EC!;<5{<=>Y~CDI&kuoN!-hGZBG!b{tUdcF zML{AU0_h>(XY(vQjM9g6`FYj0$OUzyyBI!2n0fam1u?xKQXaD4b^Xx{nSc9SxJDMOY@rnKZP;@wL9v5 tqA4h|2}tl{Heuy20wN#+B5)aj{|7()-nE|+4AKAq002ovPDHLkV1nY-W2XQB diff --git a/docs/source/arch_lang/figures/global_input_ports.png b/docs/source/arch_lang/figures/global_input_ports.png index 4c9025ab3ecfbd8c2430ede774848e38b6b27738..9b09ac3ee3f7a1703c788e1d5cd7faa7b46df05f 100644 GIT binary patch literal 16035 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

XggXNgH$k{xZCo!LK-Yl)@)0 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 NyNI-Kg`lqQ{{u44#=`&r literal 24674 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%0AKYa=E+^QJdprJ6 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 IEtk0e0qN>RwEzGB diff --git a/docs/source/arch_lang/figures/gpout_ports.png b/docs/source/arch_lang/figures/gpout_ports.png index e5140bd99b67d1c761ac7a8cf361b0eb3ff62df2..76b24955a5b87de9ef011975ca7f0d894c446805 100644 GIT binary patch literal 16042 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 literal 23998 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�nvK0n9$8c{i)zl`5j2Noh%U@1cUFv$3_a$NF&O|| z2#|_9WGhC*4RizP5rc@)DlcdD2mz;>3O1vV*=a+<43HTN zGIC@STCg{t+pye)@Jy{3g3fPxkeo#_GvCNq7;g-OeV&~xh!+oe^nwMQWaoRS3@ini z*$*#a4?tlo;e#~_+Y0lh*u}42e#fL8ex!^n-b9kMxXL8c%fgG`71aV{O z-23kI7S-mM&m-GXI7!5%jQCW7-3s(h*mr<7q8GwEIBWZ+Q{oa(PBBA({Va4+-Lu0C zZQ%B0_W*Lu83LYW^KvF>#VP_3BfyDSv?(spu{|NLzVddFN=~^+fCXB!>)j<$y09U4 g!MFsGQ%(^0fAm<~%N~ab#{d8T07*qoM6N<$g6reffB*mh From f6b3c5854ac6bbce93014cb84cd0f29c6f990d1b Mon Sep 17 00:00:00 2001 From: ganeshgore Date: Sat, 11 Apr 2020 16:45:22 -0600 Subject: [PATCH 413/645] Bugfix : + OpenFPGA template variables update + Default path for the verilog netlist --- .../OpenFPGAShellScripts/example_script.openfpga | 11 +++++++---- openfpga_flow/scripts/run_fpga_flow.py | 12 ++++++++++-- .../tasks/OpenFPGAShell_Example/config/task.conf | 2 +- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga index 5a79a3910..f584f39d8 100644 --- a/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga @@ -37,7 +37,7 @@ build_fabric_bitstream --verbose # Write the Verilog netlist for FPGA fabric # - Enable the use of explicit port mapping in Verilog netlist -write_fabric_verilog --file ./VerilogNetlist/SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose +write_fabric_verilog --file ./SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose # Write the Verilog testbench for FPGA fabric # - We suggest the use of same output directory as fabric Verilog netlists @@ -45,14 +45,17 @@ write_fabric_verilog --file ./VerilogNetlist/SRC --explicit_port_mapping --inclu # - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA # - Enable pre-configured top-level testbench which is a fast verification skipping programming phase # - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts -write_verilog_testbench --file ./VerilogNetlist/SRC --reference_benchmark_file_path ${REFERENCE_VERILOG_TESTBENCH} --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini ./SimulationDeck/simulation_deck.ini +write_verilog_testbench --file ./SRC --reference_benchmark_file_path ${REFERENCE_VERILOG_TESTBENCH} --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini ./SimulationDeck/simulation_deck.ini # Write the SDC files for PnR backend # - Turn on every options here -write_pnr_sdc --file ./VerilogNetlist/SDC +write_pnr_sdc --file ./SDC # Write the SDC to run timing analysis for a mapped FPGA fabric -write_analysis_sdc --file ./VerilogNetlist/SDC_analysis +write_analysis_sdc --file ./SDC_analysis # Finish and exit OpenFPGA exit + +# Note : +# To run verification at the end of the flow maintain source in ./SRC directory \ No newline at end of file diff --git a/openfpga_flow/scripts/run_fpga_flow.py b/openfpga_flow/scripts/run_fpga_flow.py index d0bbd6dd0..4c90f6f7c 100644 --- a/openfpga_flow/scripts/run_fpga_flow.py +++ b/openfpga_flow/scripts/run_fpga_flow.py @@ -398,7 +398,7 @@ def prepare_run_directory(run_dir): # Clean run_dir is created change working directory os.chdir(run_dir) - # Create arch dir in run_dir and copy flattern architecrture file + # Create arch dir in run_dir and copy flattened architecture file os.mkdir("arch") tmpl = Template( open(args.arch_file, encoding='utf-8').read()) @@ -407,7 +407,15 @@ def prepare_run_directory(run_dir): with open(args.arch_file, 'w', encoding='utf-8') as archfile: archfile.write(tmpl.substitute(script_env_vars["PATH"])) - # Create benchmark dir in run_dir and copy flattern architecrture file + if (args.openfpga_arch_file): + tmpl = Template( + open(args.openfpga_arch_file, encoding='utf-8').read()) + arch_filename = os.path.basename(args.openfpga_arch_file) + args.openfpga_arch_file = os.path.join(run_dir, "arch", arch_filename) + with open(args.openfpga_arch_file, 'w', encoding='utf-8') as archfile: + archfile.write(tmpl.substitute(script_env_vars["PATH"])) + + # Create benchmark dir in run_dir and copy flattern architecture file os.mkdir("benchmark") try: for index, eachfile in enumerate(args.benchmark_files): diff --git a/openfpga_flow/tasks/OpenFPGAShell_Example/config/task.conf b/openfpga_flow/tasks/OpenFPGAShell_Example/config/task.conf index 54d8e8a4a..9247493e1 100644 --- a/openfpga_flow/tasks/OpenFPGAShell_Example/config/task.conf +++ b/openfpga_flow/tasks/OpenFPGAShell_Example/config/task.conf @@ -30,4 +30,4 @@ bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/ bench0_chan_width = 300 [SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] -min_route_chan_width=1.3 \ No newline at end of file +end_flow_with_test= \ No newline at end of file From 130b78ca74f305db727b6c43ffd9533e355b93bc Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 11 Apr 2020 18:00:37 -0600 Subject: [PATCH 414/645] update arch in openfpga_flow --- .../openfpga_arch/k6_N10_40nm_openfpga.xml | 8 +-- .../k6_frac_N10_40nm_openfpga.xml | 54 ++++++++----------- .../k6_frac_N10_adder_chain_40nm_openfpga.xml | 9 +--- ...c_N10_adder_chain_mem16K_40nm_openfpga.xml | 10 +--- ...0_adder_chain_mem16K_aib_40nm_openfpga.xml | 13 +---- .../k6_frac_N10_spyio_40nm_openfpga.xml | 14 ++--- .../OpenFPGAShell_Example/config/task.conf | 2 +- 7 files changed, 33 insertions(+), 77 deletions(-) diff --git a/openfpga_flow/openfpga_arch/k6_N10_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_N10_40nm_openfpga.xml index d93495f6b..de0602e1e 100644 --- a/openfpga_flow/openfpga_arch/k6_N10_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_N10_40nm_openfpga.xml @@ -96,7 +96,6 @@ - @@ -106,7 +105,6 @@ - @@ -116,7 +114,6 @@ - @@ -127,7 +124,6 @@ - @@ -150,7 +146,6 @@ - @@ -161,8 +156,7 @@ - - + diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml index afb45dc1f..51e250a8a 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml @@ -1,5 +1,5 @@ + @@ -105,14 +104,12 @@ - - + - @@ -122,7 +119,6 @@ - @@ -132,7 +128,6 @@ - @@ -140,15 +135,14 @@ - - - - - - - - - + + + + + + + + @@ -166,22 +160,20 @@ - - - - - - - - - + + + + + + + + - - + @@ -202,9 +194,9 @@ - - - + + + diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml index 98823698f..ae08c8250 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml @@ -110,7 +110,6 @@ - @@ -120,7 +119,6 @@ - @@ -130,7 +128,6 @@ - @@ -141,7 +138,6 @@ - @@ -168,7 +164,6 @@ - @@ -179,8 +174,7 @@ - - + @@ -189,7 +183,6 @@ - diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml index 9f37c1293..cb145e06d 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml @@ -110,7 +110,6 @@ - @@ -120,7 +119,6 @@ - @@ -130,7 +128,6 @@ - @@ -141,7 +138,6 @@ - @@ -168,7 +164,6 @@ - @@ -179,8 +174,7 @@ - - + @@ -189,7 +183,6 @@ - @@ -200,7 +193,6 @@ - diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml index ea56fe036..e65851291 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml @@ -110,7 +110,6 @@ - @@ -120,7 +119,6 @@ - @@ -130,7 +128,6 @@ - @@ -141,7 +138,6 @@ - @@ -168,7 +164,6 @@ - @@ -179,8 +174,7 @@ - - + @@ -189,7 +183,6 @@ - @@ -200,7 +193,6 @@ - @@ -213,12 +205,11 @@ - - + diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml index 2b432ce86..159214507 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml @@ -110,7 +110,6 @@ - @@ -120,7 +119,6 @@ - @@ -130,7 +128,6 @@ - @@ -141,7 +138,6 @@ - @@ -167,7 +163,6 @@ - @@ -178,12 +173,11 @@ - - + - - - + + + diff --git a/openfpga_flow/tasks/OpenFPGAShell_Example/config/task.conf b/openfpga_flow/tasks/OpenFPGAShell_Example/config/task.conf index 9247493e1..f06ae2589 100644 --- a/openfpga_flow/tasks/OpenFPGAShell_Example/config/task.conf +++ b/openfpga_flow/tasks/OpenFPGAShell_Example/config/task.conf @@ -30,4 +30,4 @@ bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/ bench0_chan_width = 300 [SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] -end_flow_with_test= \ No newline at end of file +end_flow_with_test= From 49ddbf98c355d4327ac4b2a61c24ddc963ee213e Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 11 Apr 2020 18:01:09 -0600 Subject: [PATCH 415/645] add more testing architecture to openfpga_flow --- ...c_N10_adder_column_chain_40nm_openfpga.xml | 285 +++++++++++++++++ ...N10_adder_register_chain_40nm_openfpga.xml | 288 +++++++++++++++++ ...dder_register_scan_chain_40nm_openfpga.xml | 294 ++++++++++++++++++ .../k6_frac_N10_stdcell_mux_40nm_openfpga.xml | 252 +++++++++++++++ .../k6_frac_N10_tree_mux_40nm_openfpga.xml | 251 +++++++++++++++ 5 files changed, 1370 insertions(+) create mode 100644 openfpga_flow/openfpga_arch/k6_frac_N10_adder_column_chain_40nm_openfpga.xml create mode 100644 openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_chain_40nm_openfpga.xml create mode 100644 openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_40nm_openfpga.xml create mode 100644 openfpga_flow/openfpga_arch/k6_frac_N10_stdcell_mux_40nm_openfpga.xml create mode 100644 openfpga_flow/openfpga_arch/k6_frac_N10_tree_mux_40nm_openfpga.xml diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_column_chain_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_column_chain_40nm_openfpga.xml new file mode 100644 index 000000000..65117d199 --- /dev/null +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_column_chain_40nm_openfpga.xml @@ -0,0 +1,285 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + + + + 10e-12 5e-12 + + + 10e-12 5e-12 + + + + + + + + + + + + 10e-12 5e-12 5e-12 + + + 10e-12 5e-12 5e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_chain_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_chain_40nm_openfpga.xml new file mode 100644 index 000000000..779880dea --- /dev/null +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_chain_40nm_openfpga.xml @@ -0,0 +1,288 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + + + + 10e-12 5e-12 + + + 10e-12 5e-12 + + + + + + + + + + + + 10e-12 5e-12 5e-12 + + + 10e-12 5e-12 5e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_40nm_openfpga.xml new file mode 100644 index 000000000..621847439 --- /dev/null +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_40nm_openfpga.xml @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + + + + 10e-12 5e-12 + + + 10e-12 5e-12 + + + + + + + + + + + + 10e-12 5e-12 5e-12 + + + 10e-12 5e-12 5e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_stdcell_mux_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_stdcell_mux_40nm_openfpga.xml new file mode 100644 index 000000000..59f493a13 --- /dev/null +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_stdcell_mux_40nm_openfpga.xml @@ -0,0 +1,252 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + + + + 10e-12 5e-12 + + + 10e-12 5e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_tree_mux_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_tree_mux_40nm_openfpga.xml new file mode 100644 index 000000000..d04318510 --- /dev/null +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_tree_mux_40nm_openfpga.xml @@ -0,0 +1,251 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + + + + 10e-12 5e-12 + + + 10e-12 5e-12 + + + + + + + + + + + + 10e-12 5e-12 5e-12 + + + 10e-12 5e-12 5e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 80bdb41df61a58a7165371999b7239b153a561cf Mon Sep 17 00:00:00 2001 From: ganeshgore Date: Sat, 11 Apr 2020 18:30:21 -0600 Subject: [PATCH 416/645] Updated task file to run formal verification --- openfpga_flow/tasks/OpenFPGAShell_Example/config/task.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/openfpga_flow/tasks/OpenFPGAShell_Example/config/task.conf b/openfpga_flow/tasks/OpenFPGAShell_Example/config/task.conf index f06ae2589..4ea65822f 100644 --- a/openfpga_flow/tasks/OpenFPGAShell_Example/config/task.conf +++ b/openfpga_flow/tasks/OpenFPGAShell_Example/config/task.conf @@ -31,3 +31,4 @@ bench0_chan_width = 300 [SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] end_flow_with_test= +vpr_fpga_verilog_formal_verification_top_netlist= From 68fd296e14ebb9b40f0c37762884f400816da35f Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 12 Apr 2020 12:49:16 -0600 Subject: [PATCH 417/645] add more test vpr architecture to regression tests --- ...N10_tileable_adder_register_chain_40nm.xml | 696 +++++++++++++++++ ...ileable_adder_register_scan_chain_40nm.xml | 734 ++++++++++++++++++ 2 files changed, 1430 insertions(+) create mode 100644 openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_chain_40nm.xml create mode 100644 openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_40nm.xml diff --git a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_chain_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_chain_40nm.xml new file mode 100644 index 000000000..77dedbcb0 --- /dev/null +++ b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_chain_40nm.xml @@ -0,0 +1,696 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + + + + + + + + + + + + + + + + + + + + + + + clb.clk + clb.cin clb.regin + clb.O[9:0] clb.I[19:0] + clb.cout clb.regout clb.O[19:10] clb.I[39:20] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 235e-12 + 235e-12 + 235e-12 + 235e-12 + 235e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 195e-12 + 195e-12 + 195e-12 + 195e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_40nm.xml new file mode 100644 index 000000000..f83919c7c --- /dev/null +++ b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_40nm.xml @@ -0,0 +1,734 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + + + + + + + + + + + + + + + + + + + + + + + + + + + clb.clk + clb.cin clb.regin clb.scin + clb.O[9:0] clb.I[19:0] + clb.cout clb.regout clb.scout clb.O[19:10] clb.I[39:20] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 235e-12 + 235e-12 + 235e-12 + 235e-12 + 235e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 195e-12 + 195e-12 + 195e-12 + 195e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + 261e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From d806ad314819139edbfbff4a9268b7ee5173a14c Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 12 Apr 2020 12:54:21 -0600 Subject: [PATCH 418/645] add testcases using openfpga_shell in openfpga_flow --- .../openfpga_shell/frac_lut/config/task.conf | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 openfpga_flow/tasks/openfpga_shell/frac_lut/config/task.conf diff --git a/openfpga_flow/tasks/openfpga_shell/frac_lut/config/task.conf b/openfpga_flow/tasks/openfpga_shell/frac_lut/config/task.conf new file mode 100644 index 000000000..4ea65822f --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/frac_lut/config/task.conf @@ -0,0 +1,34 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif + +[SYNTHESIS_PARAM] +bench0_top = top +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_chan_width = 300 + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +end_flow_with_test= +vpr_fpga_verilog_formal_verification_top_netlist= From cc7adae91e121e0203789d68b90a7f8555a38d79 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 12 Apr 2020 12:57:13 -0600 Subject: [PATCH 419/645] deploy openfpga shell in Travis CI --- .travis/script.sh | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.travis/script.sh b/.travis/script.sh index f49bb68ea..754289e9f 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -18,6 +18,11 @@ end_section "OpenFPGA.build" start_section "OpenFPGA.TaskTun" "${GREEN}..Running_Regression..${NC}" cd - + +############################################### +# OpenFPGA with VPR7 +# TO BE DEPRECATED +############################################## echo -e "Testing single-mode architectures"; python3 openfpga_flow/scripts/run_fpga_task.py single_mode --debug --show_thread_logs #python3 openfpga_flow/scripts/run_fpga_task.py s298 @@ -37,4 +42,13 @@ python3 openfpga_flow/scripts/run_fpga_task.py explicit_verilog --debug --show_t echo -e "Testing Verilog generation with grid pin duplication "; python3 openfpga_flow/scripts/run_fpga_task.py duplicate_grid_pin --debug --show_thread_logs +############################################### +# OpenFPGA Shell with VPR8 +# (Will replace all the old tests) +############################################## +echo -e "Testing OpenFPGA Shell"; + +echo -e "Testing Verilog generation with simple fracturable LUT6 "; +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/frac_lut --debug --show_thread_logs + end_section "OpenFPGA.TaskTun" From 2444752de89c13a27c8e68a22b099fdf79823c06 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 12 Apr 2020 14:08:24 -0600 Subject: [PATCH 420/645] add untileable test case to Travis CI --- .travis/script.sh | 3 ++ .../openfpga_shell/frac_lut/config/task.conf | 2 +- .../untileable/config/task.conf | 34 +++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 openfpga_flow/tasks/openfpga_shell/untileable/config/task.conf diff --git a/.travis/script.sh b/.travis/script.sh index 754289e9f..e36520d1e 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -51,4 +51,7 @@ echo -e "Testing OpenFPGA Shell"; echo -e "Testing Verilog generation with simple fracturable LUT6 "; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/frac_lut --debug --show_thread_logs +echo -e "Testing Verilog generation with VPR's untileable routing architecture "; +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/untileable --debug --show_thread_logs + end_section "OpenFPGA.TaskTun" diff --git a/openfpga_flow/tasks/openfpga_shell/frac_lut/config/task.conf b/openfpga_flow/tasks/openfpga_shell/frac_lut/config/task.conf index 4ea65822f..a52d2f515 100644 --- a/openfpga_flow/tasks/openfpga_shell/frac_lut/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/frac_lut/config/task.conf @@ -18,7 +18,7 @@ fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml [ARCHITECTURES] -arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_40nm.xml +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml [BENCHMARKS] bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif diff --git a/openfpga_flow/tasks/openfpga_shell/untileable/config/task.conf b/openfpga_flow/tasks/openfpga_shell/untileable/config/task.conf new file mode 100644 index 000000000..4ea65822f --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/untileable/config/task.conf @@ -0,0 +1,34 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif + +[SYNTHESIS_PARAM] +bench0_top = top +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_chan_width = 300 + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +end_flow_with_test= +vpr_fpga_verilog_formal_verification_top_netlist= From 600a48edc7716ec8833b7591352c15913a750e06 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 12 Apr 2020 14:27:05 -0600 Subject: [PATCH 421/645] add test case of BRAM to Travis CI --- .travis/script.sh | 6 ++ openfpga_flow/VerilogNetlists/dpram16k.v | 56 +++++++++++++++++++ .../k6_frac_N10_tileable_adder_chain_40nm.xml | 2 +- ...c_N10_tileable_adder_chain_mem16K_40nm.xml | 2 +- ...c_N10_adder_chain_mem16K_40nm_openfpga.xml | 2 +- .../openfpga_shell/bram/config/task.conf | 34 +++++++++++ .../hard_adder/config/task.conf | 34 +++++++++++ 7 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 openfpga_flow/VerilogNetlists/dpram16k.v create mode 100644 openfpga_flow/tasks/openfpga_shell/bram/config/task.conf create mode 100644 openfpga_flow/tasks/openfpga_shell/hard_adder/config/task.conf diff --git a/.travis/script.sh b/.travis/script.sh index e36520d1e..81358c90f 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -54,4 +54,10 @@ python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/frac_lut --debug - echo -e "Testing Verilog generation with VPR's untileable routing architecture "; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/untileable --debug --show_thread_logs +echo -e "Testing Verilog generation with hard adder chain in CLBs "; +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/hard_adder --debug --show_thread_logs + +echo -e "Testing Verilog generation with 16k block RAMs "; +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/bram --debug --show_thread_logs + end_section "OpenFPGA.TaskTun" diff --git a/openfpga_flow/VerilogNetlists/dpram16k.v b/openfpga_flow/VerilogNetlists/dpram16k.v new file mode 100644 index 000000000..665884cb5 --- /dev/null +++ b/openfpga_flow/VerilogNetlists/dpram16k.v @@ -0,0 +1,56 @@ +//----------------------------------------------------- +// Design Name : dual_port_ram +// File Name : dpram.v +// Function : Dual port RAM 32x1024 +// Coder : Aurelien +//----------------------------------------------------- + +module dpram_512x32 ( + input clk, + input wen, + input ren, + input[0:9] waddr, + input[0:9] raddr, + input[0:31] d_in, + output[0:31] d_out ); + + dual_port_sram memory_0 ( + .wclk (clk), + .wen (wen), + .waddr (waddr), + .data_in (d_in), + .rclk (clk), + .ren (ren), + .raddr (raddr), + .d_out (d_out) ); + +endmodule + +module dual_port_sram ( + input wclk, + input wen, + input[0:9] waddr, + input[0:31] data_in, + input rclk, + input ren, + input[0:9] raddr, + output[0:31] d_out ); + + reg[0:31] ram[0:1023]; + reg[0:31] internal; + + assign d_out = internal; + + always @(posedge wclk) begin + if(wen) begin + ram[waddr] <= data_in; + end + end + + always @(posedge rclk) begin + if(ren) begin + internal <= ram[raddr]; + end + end + +endmodule diff --git a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_40nm.xml index eea76837f..c3598bd2b 100644 --- a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_40nm.xml +++ b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_40nm.xml @@ -161,7 +161,7 @@ - + diff --git a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_40nm.xml index 9422c3a09..49656c706 100644 --- a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_40nm.xml +++ b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_40nm.xml @@ -195,7 +195,7 @@ - + diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml index cb145e06d..eaacadd17 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml @@ -189,7 +189,7 @@ - + diff --git a/openfpga_flow/tasks/openfpga_shell/bram/config/task.conf b/openfpga_flow/tasks/openfpga_shell/bram/config/task.conf new file mode 100644 index 000000000..aeffa9a86 --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/bram/config/task.conf @@ -0,0 +1,34 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif + +[SYNTHESIS_PARAM] +bench0_top = top +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_chan_width = 300 + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +end_flow_with_test= +vpr_fpga_verilog_formal_verification_top_netlist= diff --git a/openfpga_flow/tasks/openfpga_shell/hard_adder/config/task.conf b/openfpga_flow/tasks/openfpga_shell/hard_adder/config/task.conf new file mode 100644 index 000000000..f9fbc97f2 --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/hard_adder/config/task.conf @@ -0,0 +1,34 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif + +[SYNTHESIS_PARAM] +bench0_top = top +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_chan_width = 300 + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +end_flow_with_test= +vpr_fpga_verilog_formal_verification_top_netlist= From 5d665aa04bd36dd12ca38d8d6220ff8321aa66a9 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 12 Apr 2020 14:32:09 -0600 Subject: [PATCH 422/645] reshape bram test case --- .travis/script.sh | 2 +- .../tasks/openfpga_shell/bram/{ => dpram16k}/config/task.conf | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename openfpga_flow/tasks/openfpga_shell/bram/{ => dpram16k}/config/task.conf (100%) diff --git a/.travis/script.sh b/.travis/script.sh index 81358c90f..f647a445a 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -58,6 +58,6 @@ echo -e "Testing Verilog generation with hard adder chain in CLBs "; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/hard_adder --debug --show_thread_logs echo -e "Testing Verilog generation with 16k block RAMs "; -python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/bram --debug --show_thread_logs +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/bram/dpram16k --debug --show_thread_logs end_section "OpenFPGA.TaskTun" diff --git a/openfpga_flow/tasks/openfpga_shell/bram/config/task.conf b/openfpga_flow/tasks/openfpga_shell/bram/dpram16k/config/task.conf similarity index 100% rename from openfpga_flow/tasks/openfpga_shell/bram/config/task.conf rename to openfpga_flow/tasks/openfpga_shell/bram/dpram16k/config/task.conf From 28cb41235906626a8d61db6110d79b47b817abb0 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 12 Apr 2020 14:37:08 -0600 Subject: [PATCH 423/645] add test case of wide BRAM 16k to Travis CI --- .travis/script.sh | 3 ++ .../bram/wide_dpram16k/config/task.conf | 35 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 openfpga_flow/tasks/openfpga_shell/bram/wide_dpram16k/config/task.conf diff --git a/.travis/script.sh b/.travis/script.sh index f647a445a..4c82f8128 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -60,4 +60,7 @@ python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/hard_adder --debug echo -e "Testing Verilog generation with 16k block RAMs "; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/bram/dpram16k --debug --show_thread_logs +echo -e "Testing Verilog generation with 16k block RAMs spanning two columns "; +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/bram/wide_dpram16k --debug --show_thread_logs + end_section "OpenFPGA.TaskTun" diff --git a/openfpga_flow/tasks/openfpga_shell/bram/wide_dpram16k/config/task.conf b/openfpga_flow/tasks/openfpga_shell/bram/wide_dpram16k/config/task.conf new file mode 100644 index 000000000..53cc5407c --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/bram/wide_dpram16k/config/task.conf @@ -0,0 +1,35 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml + + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_wide_mem16K_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif + +[SYNTHESIS_PARAM] +bench0_top = top +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_chan_width = 300 + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +end_flow_with_test= +vpr_fpga_verilog_formal_verification_top_netlist= From da5af8f0e0951079a8a301c921b3ab5905470c87 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 12 Apr 2020 14:54:45 -0600 Subject: [PATCH 424/645] try to add aib test case. bug found --- openfpga_flow/VerilogNetlists/aib.v | 19 +++++++++++ ...0_tileable_adder_chain_mem16K_aib_40nm.xml | 4 +-- ...0_adder_chain_mem16K_aib_40nm_openfpga.xml | 2 +- .../openfpga_shell/io/aib/config/task.conf | 34 +++++++++++++++++++ 4 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 openfpga_flow/VerilogNetlists/aib.v create mode 100644 openfpga_flow/tasks/openfpga_shell/io/aib/config/task.conf diff --git a/openfpga_flow/VerilogNetlists/aib.v b/openfpga_flow/VerilogNetlists/aib.v new file mode 100644 index 000000000..2ebfd5cea --- /dev/null +++ b/openfpga_flow/VerilogNetlists/aib.v @@ -0,0 +1,19 @@ +//----------------------------------------------------- +// Design Name : AIB interface +// File Name : aib.v +// Function : A wrapper for AIB interface +// Coder : Xifan Tang +//----------------------------------------------------- + +module aib ( + input tx_clk, + input rx_clk, + inout[0:79] pad, + input[0:79] tx_data, + output[0:79] rx_data); + +// May add the logic function of a real AIB +// Refer to the offical AIB github +// https://github.com/intel/aib-phy-hardware + +endmodule diff --git a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_aib_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_aib_40nm.xml index e196cb83c..2147d392d 100644 --- a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_aib_40nm.xml +++ b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_aib_40nm.xml @@ -225,7 +225,7 @@ - + @@ -235,7 +235,7 @@ - + diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml index e65851291..441f10ee4 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml @@ -189,7 +189,7 @@ - + diff --git a/openfpga_flow/tasks/openfpga_shell/io/aib/config/task.conf b/openfpga_flow/tasks/openfpga_shell/io/aib/config/task.conf new file mode 100644 index 000000000..abb9c625f --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/io/aib/config/task.conf @@ -0,0 +1,34 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_aib_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif + +[SYNTHESIS_PARAM] +bench0_top = top +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_chan_width = 300 + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +end_flow_with_test= +vpr_fpga_verilog_formal_verification_top_netlist= From 148cc74d6a79335ae0a6a609e3162e3a989ab0cc Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 12 Apr 2020 15:01:47 -0600 Subject: [PATCH 425/645] add io test cases to Travis CI --- .travis/script.sh | 6 ++++ ...er_chain_mem16K_multi_io_capacity_40nm.xml | 2 +- ...ble_adder_chain_mem16K_reduced_io_40nm.xml | 2 +- .../io/multi_io_capacity/config/task.conf | 34 +++++++++++++++++++ .../io/reduced_io/config/task.conf | 34 +++++++++++++++++++ 5 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 openfpga_flow/tasks/openfpga_shell/io/multi_io_capacity/config/task.conf create mode 100644 openfpga_flow/tasks/openfpga_shell/io/reduced_io/config/task.conf diff --git a/.travis/script.sh b/.travis/script.sh index 4c82f8128..59390a124 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -63,4 +63,10 @@ python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/bram/dpram16k --de echo -e "Testing Verilog generation with 16k block RAMs spanning two columns "; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/bram/wide_dpram16k --debug --show_thread_logs +echo -e "Testing Verilog generation with different I/O capacities on each side of an FPGA "; +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/io/multi_io_capacity --debug --show_thread_logs + +echo -e "Testing Verilog generation with I/Os only on left and right sides of an FPGA "; +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/io/reduced_io --debug --show_thread_logs + end_section "OpenFPGA.TaskTun" diff --git a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_multi_io_capacity_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_multi_io_capacity_40nm.xml index 8eb6f32bf..a3233edb3 100644 --- a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_multi_io_capacity_40nm.xml +++ b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_multi_io_capacity_40nm.xml @@ -226,7 +226,7 @@ - + diff --git a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_reduced_io_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_reduced_io_40nm.xml index 6f8c4cfae..2ed58d2f7 100644 --- a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_reduced_io_40nm.xml +++ b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_reduced_io_40nm.xml @@ -195,7 +195,7 @@ - + diff --git a/openfpga_flow/tasks/openfpga_shell/io/multi_io_capacity/config/task.conf b/openfpga_flow/tasks/openfpga_shell/io/multi_io_capacity/config/task.conf new file mode 100644 index 000000000..a3f9081d9 --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/io/multi_io_capacity/config/task.conf @@ -0,0 +1,34 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_multi_io_capacity_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif + +[SYNTHESIS_PARAM] +bench0_top = top +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_chan_width = 300 + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +end_flow_with_test= +vpr_fpga_verilog_formal_verification_top_netlist= diff --git a/openfpga_flow/tasks/openfpga_shell/io/reduced_io/config/task.conf b/openfpga_flow/tasks/openfpga_shell/io/reduced_io/config/task.conf new file mode 100644 index 000000000..8ed4998db --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/io/reduced_io/config/task.conf @@ -0,0 +1,34 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_reduced_io_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif + +[SYNTHESIS_PARAM] +bench0_top = top +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_chan_width = 300 + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +end_flow_with_test= +vpr_fpga_verilog_formal_verification_top_netlist= From 214d98fbcd8d05bd342229132d84a41b00b087e4 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 12 Apr 2020 15:28:22 -0600 Subject: [PATCH 426/645] add register chain and scan chain to Travis CI --- .travis/script.sh | 6 ++++ openfpga_flow/VerilogNetlists/ff.v | 32 +++++++++++++++++ ...N10_tileable_adder_register_chain_40nm.xml | 2 +- ...ileable_adder_register_scan_chain_40nm.xml | 2 +- ...dder_register_scan_chain_40nm_openfpga.xml | 4 +-- .../register_chain/config/task.conf | 34 +++++++++++++++++++ .../fabric_chain/scan_chain/config/task.conf | 34 +++++++++++++++++++ 7 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 openfpga_flow/tasks/openfpga_shell/fabric_chain/register_chain/config/task.conf create mode 100644 openfpga_flow/tasks/openfpga_shell/fabric_chain/scan_chain/config/task.conf diff --git a/.travis/script.sh b/.travis/script.sh index 59390a124..d6060b13f 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -69,4 +69,10 @@ python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/io/multi_io_capaci echo -e "Testing Verilog generation with I/Os only on left and right sides of an FPGA "; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/io/reduced_io --debug --show_thread_logs +echo -e "Testing Verilog generation with shift register chain across an FPGA"; +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/fabric_chain/register_chain --debug --show_thread_logs + +echo -e "Testing Verilog generation with scan chain across an FPGA"; +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/fabric_chain/scan_chain --debug --show_thread_logs + end_section "OpenFPGA.TaskTun" diff --git a/openfpga_flow/VerilogNetlists/ff.v b/openfpga_flow/VerilogNetlists/ff.v index f0097019e..2f2477f24 100644 --- a/openfpga_flow/VerilogNetlists/ff.v +++ b/openfpga_flow/VerilogNetlists/ff.v @@ -33,6 +33,38 @@ assign Q = q_reg; endmodule //End Of Module static_dff +module scan_chain_ff ( +/* Global ports go first */ +input set, // set input +input reset, // Reset input +input clk, // Clock Input +input TESTEN, // Clock Input +/* Local ports follow */ +input D, // Data Input +input DI, // Scan Chain Data Input +output Q // Q output +); +//------------Internal Variables-------- +reg q_reg; + +//-------------Code Starts Here--------- +always @ ( posedge clk or posedge reset or posedge set) +if (reset) begin + q_reg <= 1'b0; +end else if (set) begin + q_reg <= 1'b1; +end else if (TESTEN) begin + q_reg <= DI; +end else begin + q_reg <= D; +end + +// Wire q_reg to Q +assign Q = q_reg; + +endmodule //End Of Module static_dff + + //----------------------------------------------------- // Design Name : scan_chain_dff // File Name : ff.v diff --git a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_chain_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_chain_40nm.xml index 77dedbcb0..9d2f8aee9 100644 --- a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_chain_40nm.xml +++ b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_chain_40nm.xml @@ -165,7 +165,7 @@ - + diff --git a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_40nm.xml index f83919c7c..ea1144264 100644 --- a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_40nm.xml +++ b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_40nm.xml @@ -180,7 +180,7 @@ - + diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_40nm_openfpga.xml index 621847439..e6f6bdb6c 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_40nm_openfpga.xml @@ -137,7 +137,7 @@ This is flip-flop with scan-chain feature. When the TESTEN is enabled, the data will be propagated form DI instead of D --> - + @@ -228,7 +228,7 @@ - + diff --git a/openfpga_flow/tasks/openfpga_shell/fabric_chain/register_chain/config/task.conf b/openfpga_flow/tasks/openfpga_shell/fabric_chain/register_chain/config/task.conf new file mode 100644 index 000000000..29f1ac4b0 --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/fabric_chain/register_chain/config/task.conf @@ -0,0 +1,34 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_chain_40nm_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_chain_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif + +[SYNTHESIS_PARAM] +bench0_top = top +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_chan_width = 300 + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +end_flow_with_test= +vpr_fpga_verilog_formal_verification_top_netlist= diff --git a/openfpga_flow/tasks/openfpga_shell/fabric_chain/scan_chain/config/task.conf b/openfpga_flow/tasks/openfpga_shell/fabric_chain/scan_chain/config/task.conf new file mode 100644 index 000000000..e1e85366b --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/fabric_chain/scan_chain/config/task.conf @@ -0,0 +1,34 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_40nm_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif + +[SYNTHESIS_PARAM] +bench0_top = top +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_chan_width = 300 + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +end_flow_with_test= +vpr_fpga_verilog_formal_verification_top_netlist= From f71a85a1d4027823386d5c13c5bbc05d30165131 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 12 Apr 2020 15:39:45 -0600 Subject: [PATCH 427/645] add test cases on different routing multiplexer circuit designs to Travis CI --- .travis/script.sh | 6 ++++ .../k6_frac_N10_stdcell_mux_40nm_openfpga.xml | 10 +++--- .../mux_design/stdcell_mux2/config/task.conf | 34 +++++++++++++++++++ .../tree_structure/config/task.conf | 34 +++++++++++++++++++ 4 files changed, 79 insertions(+), 5 deletions(-) create mode 100644 openfpga_flow/tasks/openfpga_shell/mux_design/stdcell_mux2/config/task.conf create mode 100644 openfpga_flow/tasks/openfpga_shell/mux_design/tree_structure/config/task.conf diff --git a/.travis/script.sh b/.travis/script.sh index d6060b13f..6a852a8b4 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -75,4 +75,10 @@ python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/fabric_chain/regis echo -e "Testing Verilog generation with scan chain across an FPGA"; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/fabric_chain/scan_chain --debug --show_thread_logs +echo -e "Testing Verilog generation with routing mutliplexers implemented by tree structure"; +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/mux_design/tree_structure --debug --show_thread_logs + +echo -e "Testing Verilog generation with routing mutliplexers implemented by standard cell MUX2"; +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/mux_design/stdcell_mux2 --debug --show_thread_logs + end_section "OpenFPGA.TaskTun" diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_stdcell_mux_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_stdcell_mux_40nm_openfpga.xml index 59f493a13..660e9f003 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_stdcell_mux_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_stdcell_mux_40nm_openfpga.xml @@ -82,13 +82,13 @@ If your standard cell provider does not offer the exact truth table, you can simply swap the inputs as shown in the example below --> - + - + @@ -111,7 +111,7 @@ - + @@ -120,7 +120,7 @@ - + @@ -143,7 +143,7 @@ - + diff --git a/openfpga_flow/tasks/openfpga_shell/mux_design/stdcell_mux2/config/task.conf b/openfpga_flow/tasks/openfpga_shell/mux_design/stdcell_mux2/config/task.conf new file mode 100644 index 000000000..6ad9d0db0 --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/mux_design/stdcell_mux2/config/task.conf @@ -0,0 +1,34 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_stdcell_mux_40nm_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif + +[SYNTHESIS_PARAM] +bench0_top = top +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_chan_width = 300 + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +end_flow_with_test= +vpr_fpga_verilog_formal_verification_top_netlist= diff --git a/openfpga_flow/tasks/openfpga_shell/mux_design/tree_structure/config/task.conf b/openfpga_flow/tasks/openfpga_shell/mux_design/tree_structure/config/task.conf new file mode 100644 index 000000000..658e328c0 --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/mux_design/tree_structure/config/task.conf @@ -0,0 +1,34 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_tree_mux_40nm_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif + +[SYNTHESIS_PARAM] +bench0_top = top +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_chan_width = 300 + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +end_flow_with_test= +vpr_fpga_verilog_formal_verification_top_netlist= From a614e5aad9240a20bc99e40802d985fa5d6384aa Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 12 Apr 2020 15:43:19 -0600 Subject: [PATCH 428/645] add long adder chain to Travis CI --- .travis/script.sh | 3 ++ .../fabric_chain/adder_chain/config/task.conf | 34 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 openfpga_flow/tasks/openfpga_shell/fabric_chain/adder_chain/config/task.conf diff --git a/.travis/script.sh b/.travis/script.sh index 6a852a8b4..dc19e3582 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -69,6 +69,9 @@ python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/io/multi_io_capaci echo -e "Testing Verilog generation with I/Os only on left and right sides of an FPGA "; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/io/reduced_io --debug --show_thread_logs +echo -e "Testing Verilog generation with adder chain across an FPGA"; +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/fabric_chain/adder_chain --debug --show_thread_logs + echo -e "Testing Verilog generation with shift register chain across an FPGA"; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/fabric_chain/register_chain --debug --show_thread_logs diff --git a/openfpga_flow/tasks/openfpga_shell/fabric_chain/adder_chain/config/task.conf b/openfpga_flow/tasks/openfpga_shell/fabric_chain/adder_chain/config/task.conf new file mode 100644 index 000000000..348d11cde --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/fabric_chain/adder_chain/config/task.conf @@ -0,0 +1,34 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_adder_column_chain_40nm_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif + +[SYNTHESIS_PARAM] +bench0_top = top +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_chan_width = 300 + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +end_flow_with_test= +vpr_fpga_verilog_formal_verification_top_netlist= From 11e90145427f72ae4cd98bc19a2bcd1c13669824 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 12 Apr 2020 19:07:53 -0600 Subject: [PATCH 429/645] add notes about debugging the aib FPGA --- .../tasks/openfpga_shell/io/aib/config/task.conf | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/openfpga_flow/tasks/openfpga_shell/io/aib/config/task.conf b/openfpga_flow/tasks/openfpga_shell/io/aib/config/task.conf index abb9c625f..2d08de621 100644 --- a/openfpga_flow/tasks/openfpga_shell/io/aib/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/io/aib/config/task.conf @@ -17,6 +17,17 @@ timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml +##################################### +# Debugging status +# Fail in the following cases +# - tileable routing is used +# - vpr routing is used +# - compressed routing is enabled/disabled +# - duplicated pin is enabled/disabled +# +# Therefore, this could be a bug in the VPR +#################################### + [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_aib_40nm.xml From 23aef96d3ae5c47a697ad757bcaa5cad2364761f Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 12 Apr 2020 19:55:47 -0600 Subject: [PATCH 430/645] add behavioral verilog test case to Travis CI --- .travis/script.sh | 3 + .../k6_frac_N10_behavioral_40nm_openfpga.xml | 260 ++++++++++++++++++ .../behavioral_verilog/config/task.conf | 34 +++ 3 files changed, 297 insertions(+) create mode 100644 openfpga_flow/openfpga_arch/k6_frac_N10_behavioral_40nm_openfpga.xml create mode 100644 openfpga_flow/tasks/openfpga_shell/behavioral_verilog/config/task.conf diff --git a/.travis/script.sh b/.travis/script.sh index dc19e3582..3cc2a64b7 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -84,4 +84,7 @@ python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/mux_design/tree_st echo -e "Testing Verilog generation with routing mutliplexers implemented by standard cell MUX2"; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/mux_design/stdcell_mux2 --debug --show_thread_logs +echo -e "Testing Verilog generation with behavioral description"; +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/behavioral_verilog --debug --show_thread_logs + end_section "OpenFPGA.TaskTun" diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_behavioral_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_behavioral_40nm_openfpga.xml new file mode 100644 index 000000000..e305bbc08 --- /dev/null +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_behavioral_40nm_openfpga.xml @@ -0,0 +1,260 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + + + + 10e-12 5e-12 + + + 10e-12 5e-12 + + + + + + + + + + + + 10e-12 5e-12 5e-12 + + + 10e-12 5e-12 5e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/tasks/openfpga_shell/behavioral_verilog/config/task.conf b/openfpga_flow/tasks/openfpga_shell/behavioral_verilog/config/task.conf new file mode 100644 index 000000000..4d5f90d67 --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/behavioral_verilog/config/task.conf @@ -0,0 +1,34 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_behavioral_40nm_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif + +[SYNTHESIS_PARAM] +bench0_top = top +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_chan_width = 300 + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +end_flow_with_test= +vpr_fpga_verilog_formal_verification_top_netlist= From 59ea0a6ad58167867ebbf7728ba479b445145e8d Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 12 Apr 2020 20:00:20 -0600 Subject: [PATCH 431/645] add implicit verilog test case to Travis CI --- .travis/script.sh | 3 + .../implicit_verilog_example_script.openfpga | 61 +++++++++++++++++++ .../implicit_verilog/config/task.conf | 34 +++++++++++ 3 files changed, 98 insertions(+) create mode 100644 openfpga_flow/OpenFPGAShellScripts/implicit_verilog_example_script.openfpga create mode 100644 openfpga_flow/tasks/openfpga_shell/implicit_verilog/config/task.conf diff --git a/.travis/script.sh b/.travis/script.sh index 3cc2a64b7..402720e8e 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -87,4 +87,7 @@ python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/mux_design/stdcell echo -e "Testing Verilog generation with behavioral description"; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/behavioral_verilog --debug --show_thread_logs +echo -e "Testing implicit Verilog generation"; +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/implicit_verilog --debug --show_thread_logs + end_section "OpenFPGA.TaskTun" diff --git a/openfpga_flow/OpenFPGAShellScripts/implicit_verilog_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/implicit_verilog_example_script.openfpga new file mode 100644 index 000000000..d23534cbe --- /dev/null +++ b/openfpga_flow/OpenFPGAShellScripts/implicit_verilog_example_script.openfpga @@ -0,0 +1,61 @@ +# Run VPR for the 'and' design +#--write_rr_graph example_rr_graph.xml +vpr ${VPR_ARCH_FILE} ${VPR_TESTBENCH_BLIF} --clock_modeling route + +# Read OpenFPGA architecture definition +read_openfpga_arch -f ${OPENFPGA_ARCH_FILE} + +# Annotate the OpenFPGA architecture to VPR data base +# to debug use --verbose options +link_openfpga_arch --activity_file ${ACTIVITY_FILE} --sort_gsb_chan_node_in_edges + +# Check and correct any naming conflicts in the BLIF netlist +check_netlist_naming_conflict --fix --report ./netlist_renaming.xml + +# Apply fix-up to clustering nets based on routing results +pb_pin_fixup --verbose + +# Apply fix-up to Look-Up Table truth tables based on packing results +lut_truth_table_fixup + +# Build the module graph +# - Enabled compression on routing architecture modules +# - Enable pin duplication on grid modules +build_fabric --compress_routing --duplicate_grid_pin #--verbose + +# Repack the netlist to physical pbs +# This must be done before bitstream generator and testbench generation +# Strongly recommend it is done after all the fix-up have been applied +repack #--verbose + +# Build the bitstream +# - Output the fabric-independent bitstream to a file +build_architecture_bitstream --verbose --file fabric_indepenent_bitstream.xml + +# Build fabric-dependent bitstream +build_fabric_bitstream --verbose + +# Write the Verilog netlist for FPGA fabric +# - Enable the use of explicit port mapping in Verilog netlist +write_fabric_verilog --file ./SRC --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose + +# Write the Verilog testbench for FPGA fabric +# - We suggest the use of same output directory as fabric Verilog netlists +# - Must specify the reference benchmark file if you want to output any testbenches +# - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA +# - Enable pre-configured top-level testbench which is a fast verification skipping programming phase +# - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts +write_verilog_testbench --file ./SRC --reference_benchmark_file_path ${REFERENCE_VERILOG_TESTBENCH} --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini ./SimulationDeck/simulation_deck.ini + +# Write the SDC files for PnR backend +# - Turn on every options here +write_pnr_sdc --file ./SDC + +# Write the SDC to run timing analysis for a mapped FPGA fabric +write_analysis_sdc --file ./SDC_analysis + +# Finish and exit OpenFPGA +exit + +# Note : +# To run verification at the end of the flow maintain source in ./SRC directory diff --git a/openfpga_flow/tasks/openfpga_shell/implicit_verilog/config/task.conf b/openfpga_flow/tasks/openfpga_shell/implicit_verilog/config/task.conf new file mode 100644 index 000000000..9c10e2783 --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/implicit_verilog/config/task.conf @@ -0,0 +1,34 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/implicit_verilog_example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif + +[SYNTHESIS_PARAM] +bench0_top = top +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_chan_width = 300 + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +end_flow_with_test= +vpr_fpga_verilog_formal_verification_top_netlist= From e78643f1083a2992f5a7475fb43d39eb01f4bbb1 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 12 Apr 2020 20:06:40 -0600 Subject: [PATCH 432/645] add flatten routing test case to Travis CI --- .travis/script.sh | 3 + .../flatten_routing_example_script.openfpga | 61 +++++++++++++++++++ .../flatten_routing/config/task.conf | 34 +++++++++++ 3 files changed, 98 insertions(+) create mode 100644 openfpga_flow/OpenFPGAShellScripts/flatten_routing_example_script.openfpga create mode 100644 openfpga_flow/tasks/openfpga_shell/flatten_routing/config/task.conf diff --git a/.travis/script.sh b/.travis/script.sh index 402720e8e..9a4f8189d 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -90,4 +90,7 @@ python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/behavioral_verilog echo -e "Testing implicit Verilog generation"; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/implicit_verilog --debug --show_thread_logs +echo -e "Testing Verilog generation with flatten routing modules"; +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/flatten_routing --debug --show_thread_logs + end_section "OpenFPGA.TaskTun" diff --git a/openfpga_flow/OpenFPGAShellScripts/flatten_routing_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/flatten_routing_example_script.openfpga new file mode 100644 index 000000000..06a89a4f5 --- /dev/null +++ b/openfpga_flow/OpenFPGAShellScripts/flatten_routing_example_script.openfpga @@ -0,0 +1,61 @@ +# Run VPR for the 'and' design +#--write_rr_graph example_rr_graph.xml +vpr ${VPR_ARCH_FILE} ${VPR_TESTBENCH_BLIF} --clock_modeling route + +# Read OpenFPGA architecture definition +read_openfpga_arch -f ${OPENFPGA_ARCH_FILE} + +# Annotate the OpenFPGA architecture to VPR data base +# to debug use --verbose options +link_openfpga_arch --activity_file ${ACTIVITY_FILE} + +# Check and correct any naming conflicts in the BLIF netlist +check_netlist_naming_conflict --fix --report ./netlist_renaming.xml + +# Apply fix-up to clustering nets based on routing results +pb_pin_fixup --verbose + +# Apply fix-up to Look-Up Table truth tables based on packing results +lut_truth_table_fixup + +# Build the module graph +# - Enabled compression on routing architecture modules +# - Enable pin duplication on grid modules +build_fabric #--verbose + +# Repack the netlist to physical pbs +# This must be done before bitstream generator and testbench generation +# Strongly recommend it is done after all the fix-up have been applied +repack #--verbose + +# Build the bitstream +# - Output the fabric-independent bitstream to a file +build_architecture_bitstream --verbose --file fabric_indepenent_bitstream.xml + +# Build fabric-dependent bitstream +build_fabric_bitstream --verbose + +# Write the Verilog netlist for FPGA fabric +# - Enable the use of explicit port mapping in Verilog netlist +write_fabric_verilog --file ./SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose + +# Write the Verilog testbench for FPGA fabric +# - We suggest the use of same output directory as fabric Verilog netlists +# - Must specify the reference benchmark file if you want to output any testbenches +# - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA +# - Enable pre-configured top-level testbench which is a fast verification skipping programming phase +# - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts +write_verilog_testbench --file ./SRC --reference_benchmark_file_path ${REFERENCE_VERILOG_TESTBENCH} --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini ./SimulationDeck/simulation_deck.ini + +# Write the SDC files for PnR backend +# - Turn on every options here +write_pnr_sdc --file ./SDC + +# Write the SDC to run timing analysis for a mapped FPGA fabric +write_analysis_sdc --file ./SDC_analysis + +# Finish and exit OpenFPGA +exit + +# Note : +# To run verification at the end of the flow maintain source in ./SRC directory diff --git a/openfpga_flow/tasks/openfpga_shell/flatten_routing/config/task.conf b/openfpga_flow/tasks/openfpga_shell/flatten_routing/config/task.conf new file mode 100644 index 000000000..bb15c4c43 --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/flatten_routing/config/task.conf @@ -0,0 +1,34 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/flatten_routing_example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif + +[SYNTHESIS_PARAM] +bench0_top = top +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_chan_width = 300 + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +end_flow_with_test= +vpr_fpga_verilog_formal_verification_top_netlist= From 7ba3e2737139ca006819c7e94d8d6deea8157961 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 12 Apr 2020 20:10:51 -0600 Subject: [PATCH 433/645] add duplicated_grid_pin test case to Travis CI --- .travis/script.sh | 3 + ...uplicated_grid_pin_example_script.openfpga | 61 +++++++++++++++++++ .../example_script.openfpga | 4 +- .../duplicated_grid_pin/config/task.conf | 34 +++++++++++ 4 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 openfpga_flow/OpenFPGAShellScripts/duplicated_grid_pin_example_script.openfpga create mode 100644 openfpga_flow/tasks/openfpga_shell/duplicated_grid_pin/config/task.conf diff --git a/.travis/script.sh b/.travis/script.sh index 9a4f8189d..126f633d1 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -93,4 +93,7 @@ python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/implicit_verilog - echo -e "Testing Verilog generation with flatten routing modules"; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/flatten_routing --debug --show_thread_logs +echo -e "Testing Verilog generation with duplicated grid output pins"; +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/duplicated_grid_pin --debug --show_thread_logs + end_section "OpenFPGA.TaskTun" diff --git a/openfpga_flow/OpenFPGAShellScripts/duplicated_grid_pin_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/duplicated_grid_pin_example_script.openfpga new file mode 100644 index 000000000..ac808a9a6 --- /dev/null +++ b/openfpga_flow/OpenFPGAShellScripts/duplicated_grid_pin_example_script.openfpga @@ -0,0 +1,61 @@ +# Run VPR for the 'and' design +#--write_rr_graph example_rr_graph.xml +vpr ${VPR_ARCH_FILE} ${VPR_TESTBENCH_BLIF} --clock_modeling route + +# Read OpenFPGA architecture definition +read_openfpga_arch -f ${OPENFPGA_ARCH_FILE} + +# Annotate the OpenFPGA architecture to VPR data base +# to debug use --verbose options +link_openfpga_arch --activity_file ${ACTIVITY_FILE} --sort_gsb_chan_node_in_edges + +# Check and correct any naming conflicts in the BLIF netlist +check_netlist_naming_conflict --fix --report ./netlist_renaming.xml + +# Apply fix-up to clustering nets based on routing results +pb_pin_fixup --verbose + +# Apply fix-up to Look-Up Table truth tables based on packing results +lut_truth_table_fixup + +# Build the module graph +# - Enabled compression on routing architecture modules +# - Enable pin duplication on grid modules +build_fabric --compress_routing #--verbose + +# Repack the netlist to physical pbs +# This must be done before bitstream generator and testbench generation +# Strongly recommend it is done after all the fix-up have been applied +repack #--verbose + +# Build the bitstream +# - Output the fabric-independent bitstream to a file +build_architecture_bitstream --verbose --file fabric_indepenent_bitstream.xml + +# Build fabric-dependent bitstream +build_fabric_bitstream --verbose + +# Write the Verilog netlist for FPGA fabric +# - Enable the use of explicit port mapping in Verilog netlist +write_fabric_verilog --file ./SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose + +# Write the Verilog testbench for FPGA fabric +# - We suggest the use of same output directory as fabric Verilog netlists +# - Must specify the reference benchmark file if you want to output any testbenches +# - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA +# - Enable pre-configured top-level testbench which is a fast verification skipping programming phase +# - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts +write_verilog_testbench --file ./SRC --reference_benchmark_file_path ${REFERENCE_VERILOG_TESTBENCH} --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini ./SimulationDeck/simulation_deck.ini + +# Write the SDC files for PnR backend +# - Turn on every options here +write_pnr_sdc --file ./SDC + +# Write the SDC to run timing analysis for a mapped FPGA fabric +write_analysis_sdc --file ./SDC_analysis + +# Finish and exit OpenFPGA +exit + +# Note : +# To run verification at the end of the flow maintain source in ./SRC directory diff --git a/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga index f584f39d8..ac808a9a6 100644 --- a/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga @@ -21,7 +21,7 @@ lut_truth_table_fixup # Build the module graph # - Enabled compression on routing architecture modules # - Enable pin duplication on grid modules -build_fabric --compress_routing --duplicate_grid_pin #--verbose +build_fabric --compress_routing #--verbose # Repack the netlist to physical pbs # This must be done before bitstream generator and testbench generation @@ -58,4 +58,4 @@ write_analysis_sdc --file ./SDC_analysis exit # Note : -# To run verification at the end of the flow maintain source in ./SRC directory \ No newline at end of file +# To run verification at the end of the flow maintain source in ./SRC directory diff --git a/openfpga_flow/tasks/openfpga_shell/duplicated_grid_pin/config/task.conf b/openfpga_flow/tasks/openfpga_shell/duplicated_grid_pin/config/task.conf new file mode 100644 index 000000000..7f1f370c6 --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/duplicated_grid_pin/config/task.conf @@ -0,0 +1,34 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/duplicated_grid_pin_example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif + +[SYNTHESIS_PARAM] +bench0_top = top +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_chan_width = 300 + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +end_flow_with_test= +vpr_fpga_verilog_formal_verification_top_netlist= From 07a384e440d17f74dfe86486fa0519474d271620 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 13 Apr 2020 11:08:31 -0600 Subject: [PATCH 434/645] now use openfpga tokenizer to trim command line string in openfpga shell --- libopenfpga/libopenfpgashell/src/shell.tpp | 12 ++++++------ .../and_k6_frac_tileable_adder_chain.openfpga | 4 +++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/libopenfpga/libopenfpgashell/src/shell.tpp b/libopenfpga/libopenfpgashell/src/shell.tpp index 69ec89ab9..05f760e59 100644 --- a/libopenfpga/libopenfpgashell/src/shell.tpp +++ b/libopenfpga/libopenfpgashell/src/shell.tpp @@ -311,9 +311,9 @@ void Shell::run_script_mode(const char* script_file_name, T& context) { /* Remove the space at the end of the line * So that we can check easily if there is a continued line in the end */ - cmd_part.erase(std::find_if(cmd_part.rbegin(), cmd_part.rend(), [](int ch) { - return !std::isspace(ch); - }).base(), cmd_part.end()); + StringToken cmd_part_tokenizer(cmd_part); + cmd_part_tokenizer.rtrim(std::string(" ")); + cmd_part = cmd_part_tokenizer.data(); /* If the line ends with '\', this is a continued line, parse the next until it ends */ if ('\\' == cmd_part.back()) { @@ -334,9 +334,9 @@ void Shell::run_script_mode(const char* script_file_name, T& context) { } /* Remove the space at the beginning of the line */ - cmd_line.erase(cmd_line.begin(), std::find_if(cmd_line.begin(), cmd_line.end(), [](int ch) { - return !std::isspace(ch); - })); + StringToken cmd_line_tokenizer(cmd_line); + cmd_line_tokenizer.ltrim(std::string(" ")); + cmd_line = cmd_line_tokenizer.data(); /* Process the command only when the full command line in ended */ if (!cmd_line.empty()) { diff --git a/openfpga/test_script/and_k6_frac_tileable_adder_chain.openfpga b/openfpga/test_script/and_k6_frac_tileable_adder_chain.openfpga index 77fa29aa1..05ea64bce 100644 --- a/openfpga/test_script/and_k6_frac_tileable_adder_chain.openfpga +++ b/openfpga/test_script/and_k6_frac_tileable_adder_chain.openfpga @@ -41,7 +41,9 @@ build_fabric_bitstream --verbose # Write the Verilog netlist for FPGA fabric # - Enable the use of explicit port mapping in Verilog netlist -write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src/SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose + write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src/SRC \ + --explicit_port_mapping --include_timing --include_signal_init \ + --support_icarus_simulator --print_user_defined_template --verbose # Write the Verilog testbench for FPGA fabric # - We suggest the use of same output directory as fabric Verilog netlists From 56e0d2a918dd9ccf58f61570143b3f820e32d792 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 13 Apr 2020 12:58:44 -0600 Subject: [PATCH 435/645] critical patch on the ccff head and tail connection in grid modules for VPR7+OpenFPGA --- openfpga/src/fabric/build_grid_modules.cpp | 1 + vpr7_x2p/vpr/SRC/fpga_x2p/module_builder/build_grid_modules.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/openfpga/src/fabric/build_grid_modules.cpp b/openfpga/src/fabric/build_grid_modules.cpp index 0b693fcec..0d88bdc81 100644 --- a/openfpga/src/fabric/build_grid_modules.cpp +++ b/openfpga/src/fabric/build_grid_modules.cpp @@ -289,6 +289,7 @@ void build_primitive_block_module(ModuleManager& module_manager, /* Record memory-related information */ module_manager.add_configurable_child(primitive_module, memory_module, memory_instance_id); } + /* Add all the nets to connect configuration ports from memory module to primitive modules * This is a one-shot addition that covers all the memory modules in this primitive module! */ diff --git a/vpr7_x2p/vpr/SRC/fpga_x2p/module_builder/build_grid_modules.cpp b/vpr7_x2p/vpr/SRC/fpga_x2p/module_builder/build_grid_modules.cpp index c7578292a..6a98d89c5 100644 --- a/vpr7_x2p/vpr/SRC/fpga_x2p/module_builder/build_grid_modules.cpp +++ b/vpr7_x2p/vpr/SRC/fpga_x2p/module_builder/build_grid_modules.cpp @@ -292,7 +292,7 @@ void build_primitive_block_module(ModuleManager& module_manager, /* Add all the nets to connect configuration ports from memory module to primitive modules * This is a one-shot addition that covers all the memory modules in this primitive module! */ - if (false == memory_modules.empty()) { + if (0 < module_manager.configurable_children(primitive_module).size()) { add_module_nets_memory_config_bus(module_manager, primitive_module, sram_orgz_type, circuit_lib.design_tech_type(sram_model)); } From 7f37bf14416b20ce9b5d3beeb89716a8b3b16c78 Mon Sep 17 00:00:00 2001 From: ganeshgore Date: Wed, 15 Apr 2020 12:24:51 -0600 Subject: [PATCH 436/645] Added formal verification support to fpga_flow script --- openfpga_flow/scripts/run_fpga_flow.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/openfpga_flow/scripts/run_fpga_flow.py b/openfpga_flow/scripts/run_fpga_flow.py index 4c90f6f7c..0d64f864d 100644 --- a/openfpga_flow/scripts/run_fpga_flow.py +++ b/openfpga_flow/scripts/run_fpga_flow.py @@ -911,13 +911,23 @@ def run_netlists_verification(exit_if_fail=True): command = [cad_tools["iverilog_path"]] command += ["-o", compiled_file] - command += ["./SRC/%s_include_netlists.v" % - args.top_module] + fpga_define_file = "./SRC/fpga_defines.v" + fpga_define_file_bk = "./SRC/fpga_defines.v.bak" + shutil.copy(fpga_define_file, fpga_define_file_bk) + with open(fpga_define_file, "r") as fp: + fpga_defines = fp.readlines() + + command += ["./SRC/%s_include_netlists.v" % args.top_module] command += ["-s"] if args.vpr_fpga_verilog_formal_verification_top_netlist: command += [tb_top_formal] else: command += [tb_top_autochecked] + with open(fpga_define_file, "w") as fp: + for eachLine in fpga_defines: + if not (("ENABLE_FORMAL_VERIFICATION" in eachLine) or + "FORMAL_SIMULATION" in eachLine): + fp.write(eachLine) run_command("iverilog_verification", "iverilog_output.txt", command) vvp_command = ["vvp", compiled_file] From 689c4a3e19beb7f0ac4c3dd9f1dedc7d11e691bd Mon Sep 17 00:00:00 2001 From: ganeshgore Date: Wed, 15 Apr 2020 12:44:22 -0600 Subject: [PATCH 437/645] BugFix: The filename in the previous commit --- openfpga_flow/scripts/run_fpga_flow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openfpga_flow/scripts/run_fpga_flow.py b/openfpga_flow/scripts/run_fpga_flow.py index 0d64f864d..cee4ec2ce 100644 --- a/openfpga_flow/scripts/run_fpga_flow.py +++ b/openfpga_flow/scripts/run_fpga_flow.py @@ -911,8 +911,8 @@ def run_netlists_verification(exit_if_fail=True): command = [cad_tools["iverilog_path"]] command += ["-o", compiled_file] - fpga_define_file = "./SRC/fpga_defines.v" - fpga_define_file_bk = "./SRC/fpga_defines.v.bak" + fpga_define_file = "./SRC/define_simulation.v" + fpga_define_file_bk = "./SRC/define_simulation.v.bak" shutil.copy(fpga_define_file, fpga_define_file_bk) with open(fpga_define_file, "r") as fp: fpga_defines = fp.readlines() From 1e742a367689ef34db454de250e2f15196bd208e Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 15 Apr 2020 12:52:52 -0600 Subject: [PATCH 438/645] add test case on auto-check test benches --- .../configuration_chain/config/task.conf | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 openfpga_flow/tasks/openfpga_shell/configuration_chain/config/task.conf diff --git a/openfpga_flow/tasks/openfpga_shell/configuration_chain/config/task.conf b/openfpga_flow/tasks/openfpga_shell/configuration_chain/config/task.conf new file mode 100644 index 000000000..481faab3a --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/configuration_chain/config/task.conf @@ -0,0 +1,34 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif + +[SYNTHESIS_PARAM] +bench0_top = top +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_chan_width = 300 + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +end_flow_with_test= +#vpr_fpga_verilog_formal_verification_top_netlist= From 2ffd174e6a63c224be707bee70eb7225cc8674c8 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 15 Apr 2020 15:48:33 -0600 Subject: [PATCH 439/645] fixed a bug in single mode FPGA; add arch to regression test; deploy full testbench verification on Travis CI --- .travis/script.sh | 3 + .../fpga_sdc/analysis_sdc_writer_utils.cpp | 1 + .../fpga_verilog/verilog_top_testbench.cpp | 4 +- .../src/repack/build_physical_truth_table.cpp | 19 +- .../k4_N4_tileable_40nm.xml | 288 ++++++++++++++++++ .../arch/vpr_only_templates/k6_N10_40nm.xml | 10 +- .../k6_N10_tileable_40nm.xml | 10 +- .../openfpga_arch/k4_N4_40nm_openfpga.xml | 228 ++++++++++++++ .../configuration_chain/config/task.conf | 4 +- 9 files changed, 550 insertions(+), 17 deletions(-) create mode 100644 openfpga_flow/arch/vpr_only_templates/k4_N4_tileable_40nm.xml create mode 100644 openfpga_flow/openfpga_arch/k4_N4_40nm_openfpga.xml diff --git a/.travis/script.sh b/.travis/script.sh index 126f633d1..b577ee48c 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -48,6 +48,9 @@ python3 openfpga_flow/scripts/run_fpga_task.py duplicate_grid_pin --debug --show ############################################## echo -e "Testing OpenFPGA Shell"; +echo -e "Testing configuration chain of a K4N4 FPGA"; +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/configuration_chain --debug --show_thread_logs + echo -e "Testing Verilog generation with simple fracturable LUT6 "; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/frac_lut --debug --show_thread_logs diff --git a/openfpga/src/fpga_sdc/analysis_sdc_writer_utils.cpp b/openfpga/src/fpga_sdc/analysis_sdc_writer_utils.cpp index f761cafea..1491d161d 100644 --- a/openfpga/src/fpga_sdc/analysis_sdc_writer_utils.cpp +++ b/openfpga/src/fpga_sdc/analysis_sdc_writer_utils.cpp @@ -60,6 +60,7 @@ void disable_analysis_module_input_pin_net_sinks(std::fstream& fp, /* Find the module net which sources from this port! */ ModuleNetId module_net = module_manager.module_instance_port_net(parent_module, parent_module, 0, module_input_port, module_input_pin); + if (true != module_manager.valid_module_net_id(parent_module, module_net)) VTR_ASSERT(true == module_manager.valid_module_net_id(parent_module, module_net)); /* Touch each sink of the net! */ diff --git a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp index 98d69c337..25017d741 100644 --- a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp +++ b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp @@ -273,8 +273,8 @@ void print_verilog_top_testbench_global_ports_stimuli(std::fstream& fp, continue; } - /* Bypass gp output signals, they do not need any drivers */ - if (CIRCUIT_MODEL_PORT_OUTPUT == circuit_lib.port_type(model_global_port)) { + /* Bypass io signals, they do not need any drivers */ + if (true == circuit_lib.port_is_io(model_global_port)) { continue; } diff --git a/openfpga/src/repack/build_physical_truth_table.cpp b/openfpga/src/repack/build_physical_truth_table.cpp index 5e291d88b..51fc93f93 100644 --- a/openfpga/src/repack/build_physical_truth_table.cpp +++ b/openfpga/src/repack/build_physical_truth_table.cpp @@ -17,6 +17,9 @@ /* begin namespace openfpga */ namespace openfpga { +/* Mode 1 is the lut mode while mode 0 is the wire mode */ +constexpr int VPR_PB_TYPE_LUT_MODE = 1; + /*************************************************************************************** * Identify if LUT is used as wiring * In this case, LUT functions as a buffer @@ -56,9 +59,23 @@ std::vector generate_lut_rotated_input_pin_map(const std::vector std::vector rotated_pin_map(input_nets.size(), -1); VTR_ASSERT(1 == pb_graph_node->num_input_ports); + for (int ipin = 0; ipin < pb_graph_node->num_input_pins[0]; ++ipin) { + /* The lut pb_graph_node may not be the primitive node + * because VPR adds two default modes to its LUT pb_type + * If so, we will use the LUT mode of the pb_graph node + */ + t_port* lut_pb_type_in_port = pb_graph_node->input_pins[0][ipin].port; + if (0 != pb_graph_node->pb_type->num_modes) { + VTR_ASSERT(2 == pb_graph_node->pb_type->num_modes); + VTR_ASSERT(1 == pb_graph_node->pb_type->modes[VPR_PB_TYPE_LUT_MODE].num_pb_type_children); + lut_pb_type_in_port = &(pb_graph_node->pb_type->modes[VPR_PB_TYPE_LUT_MODE].pb_type_children[0].ports[0]); + VTR_ASSERT(std::string(lut_pb_type_in_port->name) == std::string(pb_graph_node->input_pins[0][ipin].port->name)); + VTR_ASSERT(lut_pb_type_in_port->num_pins == pb_graph_node->input_pins[0][ipin].port->num_pins); + } + /* Port exists (some LUTs may have no input and hence no port in the atom netlist) */ - AtomPortId atom_port = atom_ctx.nlist.find_atom_port(atom_blk, pb_graph_node->input_pins[0][ipin].port->model_port); + AtomPortId atom_port = atom_ctx.nlist.find_atom_port(atom_blk, lut_pb_type_in_port->model_port); if (!atom_port) { continue; } diff --git a/openfpga_flow/arch/vpr_only_templates/k4_N4_tileable_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k4_N4_tileable_40nm.xml new file mode 100644 index 000000000..533704db8 --- /dev/null +++ b/openfpga_flow/arch/vpr_only_templates/k4_N4_tileable_40nm.xml @@ -0,0 +1,288 @@ + + + + + + + + + + + + + + + + + + + + + + + + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 261e-12 + 261e-12 + 261e-12 + 261e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/arch/vpr_only_templates/k6_N10_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_N10_40nm.xml index 630011e84..e94f339c1 100644 --- a/openfpga_flow/arch/vpr_only_templates/k6_N10_40nm.xml +++ b/openfpga_flow/arch/vpr_only_templates/k6_N10_40nm.xml @@ -41,13 +41,12 @@ - - io.outpad io.inpad io.clock - io.outpad io.inpad io.clock - io.outpad io.inpad io.clock - io.outpad io.inpad io.clock + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad @@ -135,7 +134,6 @@ - diff --git a/openfpga_flow/arch/vpr_only_templates/k6_N10_tileable_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_N10_tileable_40nm.xml index d86d7aa62..f9065d437 100644 --- a/openfpga_flow/arch/vpr_only_templates/k6_N10_tileable_40nm.xml +++ b/openfpga_flow/arch/vpr_only_templates/k6_N10_tileable_40nm.xml @@ -41,13 +41,12 @@ - - io.outpad io.inpad io.clock - io.outpad io.inpad io.clock - io.outpad io.inpad io.clock - io.outpad io.inpad io.clock + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad @@ -135,7 +134,6 @@ - diff --git a/openfpga_flow/openfpga_arch/k4_N4_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k4_N4_40nm_openfpga.xml new file mode 100644 index 000000000..1d48387ed --- /dev/null +++ b/openfpga_flow/openfpga_arch/k4_N4_40nm_openfpga.xml @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + + + + + 10e-12 5e-12 5e-12 + + + 10e-12 5e-12 5e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/tasks/openfpga_shell/configuration_chain/config/task.conf b/openfpga_flow/tasks/openfpga_shell/configuration_chain/config/task.conf index 481faab3a..7415b96ab 100644 --- a/openfpga_flow/tasks/openfpga_shell/configuration_chain/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/configuration_chain/config/task.conf @@ -15,10 +15,10 @@ spice_output=false verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif -openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k4_N4_40nm_openfpga.xml [ARCHITECTURES] -arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_40nm.xml +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k4_N4_tileable_40nm.xml [BENCHMARKS] bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif From 72e8824a874a80d02ab860df01423372f1b594fa Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 15 Apr 2020 20:41:15 -0600 Subject: [PATCH 440/645] bug fixed on removing undriven pins (direct connection between clbs) from cb --- openfpga/src/annotation/annotate_rr_graph.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openfpga/src/annotation/annotate_rr_graph.cpp b/openfpga/src/annotation/annotate_rr_graph.cpp index eba72933f..644f4541c 100644 --- a/openfpga/src/annotation/annotate_rr_graph.cpp +++ b/openfpga/src/annotation/annotate_rr_graph.cpp @@ -373,6 +373,14 @@ RRGSB build_rr_gsb(const DeviceContext& vpr_device_ctx, ix, iy, IPIN, ipin_rr_node_grid_side); /* Fill the ipin nodes of RRGSB */ for (const RRNodeId& inode : temp_ipin_rr_nodes) { + /* Skip those has no configurable outgoing, they should NOT appear in the GSB connection + * This is for those grid output pins used by direct connections + */ + if (0 == std::distance(vpr_device_ctx.rr_graph.node_configurable_in_edges(inode).begin(), + vpr_device_ctx.rr_graph.node_configurable_in_edges(inode).end())) { + continue; + } + /* Do not consider IPINs that are directly connected by an OPIN * they are supposed to be handled by direct connection */ From a7d900088b6bd36ab1d720eea55664a0cc21df3a Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 15 Apr 2020 20:53:37 -0600 Subject: [PATCH 441/645] now generating simulation ini file will try to create directory first --- openfpga/src/fpga_verilog/simulation_info_writer.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openfpga/src/fpga_verilog/simulation_info_writer.cpp b/openfpga/src/fpga_verilog/simulation_info_writer.cpp index fbbb7e83c..7c0bf0e3e 100644 --- a/openfpga/src/fpga_verilog/simulation_info_writer.cpp +++ b/openfpga/src/fpga_verilog/simulation_info_writer.cpp @@ -12,6 +12,9 @@ #include "vtr_assert.h" #include "vtr_time.h" +/* Headers from openfpgautil library */ +#include "openfpga_digest.h" + #include "simulation_utils.h" #include "verilog_constants.h" @@ -34,6 +37,11 @@ void print_verilog_simulation_info(const std::string& ini_fname, std::string timer_message = std::string("Write exchangeable file containing simulation information '") + ini_fname + std::string("'"); + std::string ini_dir_path = format_dir_path(find_path_dir_name(ini_fname)); + + /* Create directories */ + create_directory(ini_dir_path); + /* Start time count */ vtr::ScopedStartFinishTimer timer(timer_message); From 2ea4b8a2a24f999d91c8f46778ec95c99cf89136 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 17 Apr 2020 19:12:27 -0600 Subject: [PATCH 442/645] add more flagship architectures --- ...adder_register_scan_chain_depop50_40nm.xml | 680 +++++++++++++++ ...egister_scan_chain_mem16K_depop50_40nm.xml | 781 ++++++++++++++++++ 2 files changed, 1461 insertions(+) create mode 100755 openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_depop50_40nm.xml create mode 100755 openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_mem16K_depop50_40nm.xml diff --git a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_depop50_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_depop50_40nm.xml new file mode 100755 index 000000000..ce1cc258c --- /dev/null +++ b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_depop50_40nm.xml @@ -0,0 +1,680 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + clb.sc_in clb.cin clb.cin_trick clb.regin clb.clk + clb.I0[9:0] clb.I1[9:0] clb.O[9:0] + clb.cout clb.cout_copy clb.sc_out clb.regout clb.I2[9:0] clb.I3[9:0] clb.O[19:10] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 202e-12 + 202e-12 + 202e-12 + 202e-12 + 202e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 180e-12 + 180e-12 + 180e-12 + 180e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 229e-12 + 229e-12 + 229e-12 + 229e-12 + 229e-12 + 229e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_mem16K_depop50_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_mem16K_depop50_40nm.xml new file mode 100755 index 000000000..37b5e17d9 --- /dev/null +++ b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_mem16K_depop50_40nm.xml @@ -0,0 +1,781 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + clb.sc_in clb.cin clb.cin_trick clb.regin clb.clk + clb.I0[9:0] clb.I1[9:0] clb.O[9:0] + clb.cout clb.cout_copy clb.sc_out clb.regout clb.I2[9:0] clb.I3[9:0] clb.O[19:10] + + + + + + + + + + + + + + + + + memory.clk + memory.waddr memory.d_in[15:0] memory.wen memory.d_out[15:0] + memory.raddr memory.d_in[31:16] memory.ren memory.d_out[31:16] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 202e-12 + 202e-12 + 202e-12 + 202e-12 + 202e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 180e-12 + 180e-12 + 180e-12 + 180e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 229e-12 + 229e-12 + 229e-12 + 229e-12 + 229e-12 + 229e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 7ce34be175fabde71de28b176b0212de34e189c3 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 17 Apr 2020 22:06:06 -0600 Subject: [PATCH 443/645] update sample architecture timing --- ...egister_scan_chain_mem16K_depop50_40nm.xml | 120 +++++++++--------- 1 file changed, 63 insertions(+), 57 deletions(-) diff --git a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_mem16K_depop50_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_mem16K_depop50_40nm.xml index 37b5e17d9..1e2808f66 100755 --- a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_mem16K_depop50_40nm.xml +++ b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_mem16K_depop50_40nm.xml @@ -181,7 +181,7 @@ This is strongly recommended if you want to PnR large FPGA fabric --> - + + @@ -216,13 +216,13 @@ - - + + - + 1 1 1 1 1 1 1 1 1 @@ -256,10 +256,10 @@ - + - + @@ -275,7 +275,7 @@ - + @@ -285,7 +285,7 @@ - + @@ -433,11 +433,11 @@ - 202e-12 - 202e-12 - 202e-12 - 202e-12 - 202e-12 + 193e-12 + 193e-12 + 193e-12 + 193e-12 + 193e-12 @@ -445,8 +445,8 @@ - - + + @@ -455,8 +455,8 @@ - - + + @@ -479,10 +479,10 @@ - 180e-12 - 180e-12 - 180e-12 - 180e-12 + 161e-12 + 161e-12 + 161e-12 + 161e-12 @@ -491,19 +491,19 @@ - - - - - - + + + + + + - - + + @@ -523,8 +523,8 @@ - - + + @@ -581,20 +581,20 @@ - 229e-12 - 229e-12 - 229e-12 - 229e-12 - 229e-12 - 229e-12 + 230e-12 + 230e-12 + 230e-12 + 230e-12 + 230e-12 + 230e-12 - - + + @@ -604,8 +604,8 @@ - - + + @@ -627,8 +627,8 @@ - - + + @@ -652,26 +652,28 @@ - - + + - - + + - - + + - - + + - - + + + + @@ -685,14 +687,18 @@ - - + + + + + + - + From 2f3a36ee81d80a090f3127e7de4b914590895a6c Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 18 Apr 2020 18:39:47 -0600 Subject: [PATCH 444/645] update timing and rename the arch file --- ...gister_scan_chain_mem16K_depop50_12nm.xml} | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) rename openfpga_flow/arch/vpr_only_templates/{k6_frac_N10_tileable_adder_register_scan_chain_mem16K_depop50_40nm.xml => k6_frac_N10_tileable_adder_register_scan_chain_mem16K_depop50_12nm.xml} (96%) diff --git a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_mem16K_depop50_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_mem16K_depop50_12nm.xml similarity index 96% rename from openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_mem16K_depop50_40nm.xml rename to openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_mem16K_depop50_12nm.xml index 1e2808f66..15281812f 100755 --- a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_mem16K_depop50_40nm.xml +++ b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_mem16K_depop50_12nm.xml @@ -1,8 +1,11 @@ - @@ -216,8 +219,8 @@ - - + + @@ -337,6 +340,9 @@ + @@ -485,6 +491,9 @@ 161e-12 + @@ -498,6 +507,9 @@ + @@ -522,6 +534,12 @@ + @@ -532,10 +550,8 @@ - - @@ -547,13 +563,10 @@ - - - @@ -563,10 +576,8 @@ - - @@ -589,6 +600,9 @@ 230e-12 + @@ -746,6 +760,7 @@ + From 95863e996a12e5ad20e931871716ded8a170fe7c Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 18 Apr 2020 18:43:56 -0600 Subject: [PATCH 445/645] minor update on arch to use auto layout sizing --- ...10_tileable_adder_register_scan_chain_depop50_40nm.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_depop50_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_depop50_40nm.xml index ce1cc258c..1b51c45af 100755 --- a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_depop50_40nm.xml +++ b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_depop50_40nm.xml @@ -142,19 +142,19 @@ This is strongly recommended if you want to PnR large FPGA fabric --> - + - + - - + + From f76a3090c4bb9a814dd1d118a6c7a99e26261c6c Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 18 Apr 2020 19:25:16 -0600 Subject: [PATCH 446/645] add mcnc big20 test cases and start debugging --- ...ister_scan_chain_depop50_40nm_openfpga.xml | 289 ++++++++++++++++++ .../mcnc_big20/config/task.conf | 134 ++++++++ 2 files changed, 423 insertions(+) create mode 100644 openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_depop50_40nm_openfpga.xml create mode 100644 openfpga_flow/tasks/openfpga_shell/mcnc_big20/config/task.conf diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_depop50_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_depop50_40nm_openfpga.xml new file mode 100644 index 000000000..8d2aadaeb --- /dev/null +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_depop50_40nm_openfpga.xml @@ -0,0 +1,289 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + + + + 10e-12 5e-12 + + + 10e-12 5e-12 + + + + + + + + + + + + 10e-12 5e-12 5e-12 + + + 10e-12 5e-12 5e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/tasks/openfpga_shell/mcnc_big20/config/task.conf b/openfpga_flow/tasks/openfpga_shell/mcnc_big20/config/task.conf new file mode 100644 index 000000000..9ecb77896 --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/mcnc_big20/config/task.conf @@ -0,0 +1,134 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_depop50_40nm_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_depop50_40nm.xml + +[BENCHMARKS] +# Pass +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/alu4/alu4.blif +# Pass, but port does not match, i_15_ is dangling +bench1=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex2/apex2.blif +# Pass +bench2=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex4/apex4.blif +bench3=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/bigkey/bigkey.blif +# To be tested +bench4=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/clma/clma.blif +bench5=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/des/des.blif +bench6=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/diffeq/diffeq.blif +bench7=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/dsip/dsip.blif +bench8=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/elliptic/elliptic.blif +bench9=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/ex1010/ex1010.blif +bench10=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/ex5p/ex5p.blif +bench11=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/frisc/frisc.blif +bench12=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/misex3/misex3.blif +bench13=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/pdc/pdc.blif +# Pass +bench14=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s298/s298.blif +bench15=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s38417/s38417.blif +bench16=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s38584/s38584.blif +bench17=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/seq/seq.blif +bench18=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/spla/spla.blif +bench19=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/tseng/tseng.blif + +[SYNTHESIS_PARAM] +# Benchmark alu4 +bench0_top = alu4 +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/alu4/alu4.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/alu4/alu4.v +# Benchmark apex2 +bench1_top = apex2 +bench1_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex2/apex2.act +bench1_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex2/apex2.v +# Benchmark apex4 +bench2_top = apex4 +bench2_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex4/apex4.act +bench2_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex4/apex4.v +# Benchmark bigkey +bench3_top = bigkey +bench3_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/bigkey/bigkey.act +bench3_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/bigkey/bigkey.v +# Benchmark clma +bench4_top = clma +bench4_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/clma/clma.act +bench4_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/clma/clma.v +# Benchmark des +bench5_top = des +bench5_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/des/des.act +bench5_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/des/des.v +# Benchmark diffeq +bench6_top = diffeq +bench6_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/diffeq/diffeq.act +bench6_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/diffeq/diffeq.v +# Benchmark dsip +bench7_top = dsip +bench7_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/dsip/dsip.act +bench7_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/dsip/dsip.v +# Benchmark elliptic +bench8_top = elliptic +bench8_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/elliptic/elliptic.act +bench8_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/elliptic/elliptic.v +# Benchmark ex1010 +bench9_top = ex1010 +bench9_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/ex1010/ex1010.act +bench9_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/ex1010/ex1010.v +# Benchmark ex5p +bench10_top = ex5p +bench10_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/ex5p/ex5p.act +bench10_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/ex5p/ex5p.v +# Benchmark frisc +bench11_top = frisc +bench11_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/frisc/frisc.act +bench11_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/frisc/frisc.v +# Benchmark misex3 +bench12_top = misex3 +bench12_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/misex3/misex3.act +bench12_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/misex3/misex3.v +# Benchmark pdc +bench13_top = pdc +bench13_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/pdc/pdc.act +bench13_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/pdc/pdc.v +# Benchmark s298 +bench14_top = s298 +bench14_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s298/s298.act +bench14_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s298/s298.v +# Benchmark s38417 +bench15_top = s38417 +bench15_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s38417/s38417.act +bench15_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s38417/s38417.v +# Benchmark s38584 +bench16_top = s38584 +bench16_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s38584/s38584.act +bench16_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s38584/s38584.v +# Benchmark seq +bench17_top = seq +bench17_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/seq/seq.act +bench17_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/seq/seq.v +# Benchmark spla +bench18_top = spla +bench18_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/spla/spla.act +bench18_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/spla/spla.v +# Benchmark tseng +bench19_top = tseng +bench19_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/tseng/tseng.act +bench19_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/tseng/tseng.v + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +#end_flow_with_test= +vpr_fpga_verilog_formal_verification_top_netlist= From 2e3a811f4fd39692ca5007636f2490bf99b7827c Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 18 Apr 2020 21:04:46 -0600 Subject: [PATCH 447/645] critical bug fixed in repacking. This is due to depop50% local routing where the same net may be mapped to two different pins in the same pb_graph_pin. Now we restrict the pin searching. But in long term, we should sync the pb_route results to post routing results --- .../src/annotation/vpr_routing_annotation.cpp | 7 +-- openfpga/src/repack/repack.cpp | 14 ++++-- .../mcnc_big20/config/task.conf | 43 ++++++++----------- 3 files changed, 33 insertions(+), 31 deletions(-) diff --git a/openfpga/src/annotation/vpr_routing_annotation.cpp b/openfpga/src/annotation/vpr_routing_annotation.cpp index 1c17450ed..b51f4be94 100644 --- a/openfpga/src/annotation/vpr_routing_annotation.cpp +++ b/openfpga/src/annotation/vpr_routing_annotation.cpp @@ -36,9 +36,10 @@ void VprRoutingAnnotation::set_rr_node_net(const RRNodeId& rr_node, /* Ensure that the node_id is in the list */ VTR_ASSERT(size_t(rr_node) < rr_node_nets_.size()); /* Warn any override attempt */ - if (ClusterNetId::INVALID() != rr_node_nets_[rr_node]) { - VTR_LOG_WARN("Override the net '%ld' for node '%ld' with in routing context annotation!\n", - size_t(net_id), size_t(rr_node)); + if ( (ClusterNetId::INVALID() != rr_node_nets_[rr_node]) + && (net_id != rr_node_nets_[rr_node])) { + VTR_LOG_WARN("Override the net '%ld' by net '%ld' for node '%ld' with in routing context annotation!\n", + size_t(rr_node_nets_[rr_node]), size_t(net_id), size_t(rr_node)); } rr_node_nets_[rr_node] = net_id; diff --git a/openfpga/src/repack/repack.cpp b/openfpga/src/repack/repack.cpp index 2d0b2c3e8..a32366be0 100644 --- a/openfpga/src/repack/repack.cpp +++ b/openfpga/src/repack/repack.cpp @@ -117,15 +117,21 @@ int find_pb_route_remapped_source_pb_pin(const t_pb* pb, for (int pin = 0; pin < pb->pb_graph_node->total_pb_pins; ++pin) { /* Bypass unused pins */ - if ((0 == pb->pb_route.count(pin)) || (AtomNetId::INVALID() == pb->pb_route[pin].atom_net_id)) { + if ((0 == pb->pb_route.count(pin)) || (AtomNetId::INVALID() == pb->pb_route.at(pin).atom_net_id)) { continue; } /* Get the driver pb pin id, it must be valid */ - if (atom_net_id != pb->pb_route[pin].atom_net_id) { + if (atom_net_id != pb->pb_route.at(pin).atom_net_id) { continue; } - /* Only care the pin that shares the same parent_node as source_pb_pin */ - if (source_pb_pin->parent_node == pb->pb_route[pin].pb_graph_pin->parent_node) { + /* Only care the pin has the same parent port as source_pb_pin + * Due to that the source_pb_pin may be swapped during routing + * the pb_route is out-of-date + * TODO: should update pb_route by post routing results + * On the other side, the swapping can only happen between equivalent pins + * in a port. So the port must match here! + */ + if (source_pb_pin->port == pb->pb_route.at(pin).pb_graph_pin->port) { pb_route_indices.push_back(pin); } } diff --git a/openfpga_flow/tasks/openfpga_shell/mcnc_big20/config/task.conf b/openfpga_flow/tasks/openfpga_shell/mcnc_big20/config/task.conf index 9ecb77896..0734df61d 100644 --- a/openfpga_flow/tasks/openfpga_shell/mcnc_big20/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/mcnc_big20/config/task.conf @@ -21,31 +21,26 @@ openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10 arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_depop50_40nm.xml [BENCHMARKS] -# Pass -bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/alu4/alu4.blif -# Pass, but port does not match, i_15_ is dangling -bench1=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex2/apex2.blif -# Pass -bench2=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex4/apex4.blif -bench3=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/bigkey/bigkey.blif -# To be tested -bench4=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/clma/clma.blif -bench5=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/des/des.blif -bench6=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/diffeq/diffeq.blif -bench7=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/dsip/dsip.blif -bench8=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/elliptic/elliptic.blif -bench9=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/ex1010/ex1010.blif -bench10=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/ex5p/ex5p.blif -bench11=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/frisc/frisc.blif -bench12=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/misex3/misex3.blif -bench13=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/pdc/pdc.blif -# Pass +#bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/alu4/alu4.blif +#bench1=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex2/apex2.blif +#bench2=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex4/apex4.blif +#bench3=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/bigkey/bigkey.blif +#bench4=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/clma/clma.blif +#bench5=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/des/des.blif +#bench6=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/diffeq/diffeq.blif +#bench7=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/dsip/dsip.blif +#bench8=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/elliptic/elliptic.blif +#bench9=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/ex1010/ex1010.blif +#bench10=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/ex5p/ex5p.blif +#bench11=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/frisc/frisc.blif +#bench12=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/misex3/misex3.blif +#bench13=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/pdc/pdc.blif bench14=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s298/s298.blif -bench15=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s38417/s38417.blif -bench16=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s38584/s38584.blif -bench17=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/seq/seq.blif -bench18=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/spla/spla.blif -bench19=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/tseng/tseng.blif +#bench15=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s38417/s38417.blif +#bench16=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s38584/s38584.blif +#bench17=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/seq/seq.blif +#bench18=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/spla/spla.blif +#bench19=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/tseng/tseng.blif [SYNTHESIS_PARAM] # Benchmark alu4 From cc163081f5f34881a5d1eb40d36206d6b38dcdfb Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 18 Apr 2020 21:06:43 -0600 Subject: [PATCH 448/645] recover mcnc big20 test configuration --- .../mcnc_big20/config/task.conf | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/openfpga_flow/tasks/openfpga_shell/mcnc_big20/config/task.conf b/openfpga_flow/tasks/openfpga_shell/mcnc_big20/config/task.conf index 0734df61d..7a64bfcde 100644 --- a/openfpga_flow/tasks/openfpga_shell/mcnc_big20/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/mcnc_big20/config/task.conf @@ -21,26 +21,26 @@ openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10 arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_depop50_40nm.xml [BENCHMARKS] -#bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/alu4/alu4.blif -#bench1=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex2/apex2.blif -#bench2=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex4/apex4.blif -#bench3=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/bigkey/bigkey.blif -#bench4=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/clma/clma.blif -#bench5=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/des/des.blif -#bench6=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/diffeq/diffeq.blif -#bench7=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/dsip/dsip.blif -#bench8=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/elliptic/elliptic.blif -#bench9=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/ex1010/ex1010.blif -#bench10=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/ex5p/ex5p.blif -#bench11=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/frisc/frisc.blif -#bench12=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/misex3/misex3.blif -#bench13=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/pdc/pdc.blif +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/alu4/alu4.blif +bench1=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex2/apex2.blif +bench2=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex4/apex4.blif +bench3=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/bigkey/bigkey.blif +bench4=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/clma/clma.blif +bench5=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/des/des.blif +bench6=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/diffeq/diffeq.blif +bench7=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/dsip/dsip.blif +bench8=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/elliptic/elliptic.blif +bench9=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/ex1010/ex1010.blif +bench10=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/ex5p/ex5p.blif +bench11=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/frisc/frisc.blif +bench12=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/misex3/misex3.blif +bench13=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/pdc/pdc.blif bench14=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s298/s298.blif -#bench15=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s38417/s38417.blif -#bench16=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s38584/s38584.blif -#bench17=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/seq/seq.blif -#bench18=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/spla/spla.blif -#bench19=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/tseng/tseng.blif +bench15=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s38417/s38417.blif +bench16=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s38584/s38584.blif +bench17=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/seq/seq.blif +bench18=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/spla/spla.blif +bench19=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/tseng/tseng.blif [SYNTHESIS_PARAM] # Benchmark alu4 From 98878f474b04270527d88356bc285bf8f91c616d Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 19 Apr 2020 12:03:31 -0600 Subject: [PATCH 449/645] light change on arch file to accelerate mcnc big20 run --- ...frac_N10_tileable_adder_register_scan_chain_depop50_40nm.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_depop50_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_depop50_40nm.xml index 1b51c45af..174649d93 100755 --- a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_depop50_40nm.xml +++ b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_depop50_40nm.xml @@ -73,7 +73,7 @@ make it physically equivalent on all sides so that only one definition of I/Os is needed. If I do not make a physically equivalent definition, then I need to define 4 different I/Os, one for each side of the FPGA --> - + From 32ed6092388066b421a1374016c907c0e52ef0d3 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 19 Apr 2020 12:49:07 -0600 Subject: [PATCH 450/645] update micro benchmark set and regression tests using them --- .../benchmarks/micro_benchmark/{ => and}/and.act | 0 .../benchmarks/micro_benchmark/{ => and}/and.blif | 0 .../benchmarks/micro_benchmark/{ => and}/and.v | 0 .../micro_benchmark/{ => and_latch}/and_latch.act | 0 .../micro_benchmark/{ => and_latch}/and_latch.blif | 0 .../micro_benchmark/{ => and_latch}/and_latch.v | 0 .../behavioral_verilog/config/task.conf | 6 +++--- .../openfpga_shell/bram/dpram16k/config/task.conf | 6 +++--- .../bram/wide_dpram16k/config/task.conf | 6 +++--- .../configuration_chain/config/task.conf | 6 +++--- .../duplicated_grid_pin/config/task.conf | 6 +++--- .../fabric_chain/adder_chain/config/task.conf | 6 +++--- .../fabric_chain/register_chain/config/task.conf | 6 +++--- .../fabric_chain/scan_chain/config/task.conf | 6 +++--- .../openfpga_shell/flatten_routing/config/task.conf | 6 +++--- .../tasks/openfpga_shell/frac_lut/config/task.conf | 12 ++++++++---- .../tasks/openfpga_shell/hard_adder/config/task.conf | 6 +++--- .../openfpga_shell/implicit_verilog/config/task.conf | 6 +++--- .../tasks/openfpga_shell/io/aib/config/task.conf | 6 +++--- .../io/multi_io_capacity/config/task.conf | 6 +++--- .../openfpga_shell/io/reduced_io/config/task.conf | 6 +++--- .../tasks/openfpga_shell/mcnc_big20/config/task.conf | 3 ++- .../mux_design/stdcell_mux2/config/task.conf | 6 +++--- .../mux_design/tree_structure/config/task.conf | 6 +++--- .../tasks/openfpga_shell/untileable/config/task.conf | 6 +++--- 25 files changed, 61 insertions(+), 56 deletions(-) rename openfpga_flow/benchmarks/micro_benchmark/{ => and}/and.act (100%) rename openfpga_flow/benchmarks/micro_benchmark/{ => and}/and.blif (100%) rename openfpga_flow/benchmarks/micro_benchmark/{ => and}/and.v (100%) rename openfpga_flow/benchmarks/micro_benchmark/{ => and_latch}/and_latch.act (100%) rename openfpga_flow/benchmarks/micro_benchmark/{ => and_latch}/and_latch.blif (100%) rename openfpga_flow/benchmarks/micro_benchmark/{ => and_latch}/and_latch.v (100%) diff --git a/openfpga_flow/benchmarks/micro_benchmark/and.act b/openfpga_flow/benchmarks/micro_benchmark/and/and.act similarity index 100% rename from openfpga_flow/benchmarks/micro_benchmark/and.act rename to openfpga_flow/benchmarks/micro_benchmark/and/and.act diff --git a/openfpga_flow/benchmarks/micro_benchmark/and.blif b/openfpga_flow/benchmarks/micro_benchmark/and/and.blif similarity index 100% rename from openfpga_flow/benchmarks/micro_benchmark/and.blif rename to openfpga_flow/benchmarks/micro_benchmark/and/and.blif diff --git a/openfpga_flow/benchmarks/micro_benchmark/and.v b/openfpga_flow/benchmarks/micro_benchmark/and/and.v similarity index 100% rename from openfpga_flow/benchmarks/micro_benchmark/and.v rename to openfpga_flow/benchmarks/micro_benchmark/and/and.v diff --git a/openfpga_flow/benchmarks/micro_benchmark/and_latch.act b/openfpga_flow/benchmarks/micro_benchmark/and_latch/and_latch.act similarity index 100% rename from openfpga_flow/benchmarks/micro_benchmark/and_latch.act rename to openfpga_flow/benchmarks/micro_benchmark/and_latch/and_latch.act diff --git a/openfpga_flow/benchmarks/micro_benchmark/and_latch.blif b/openfpga_flow/benchmarks/micro_benchmark/and_latch/and_latch.blif similarity index 100% rename from openfpga_flow/benchmarks/micro_benchmark/and_latch.blif rename to openfpga_flow/benchmarks/micro_benchmark/and_latch/and_latch.blif diff --git a/openfpga_flow/benchmarks/micro_benchmark/and_latch.v b/openfpga_flow/benchmarks/micro_benchmark/and_latch/and_latch.v similarity index 100% rename from openfpga_flow/benchmarks/micro_benchmark/and_latch.v rename to openfpga_flow/benchmarks/micro_benchmark/and_latch/and_latch.v diff --git a/openfpga_flow/tasks/openfpga_shell/behavioral_verilog/config/task.conf b/openfpga_flow/tasks/openfpga_shell/behavioral_verilog/config/task.conf index 4d5f90d67..c7c111313 100644 --- a/openfpga_flow/tasks/openfpga_shell/behavioral_verilog/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/behavioral_verilog/config/task.conf @@ -21,12 +21,12 @@ openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10 arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml [BENCHMARKS] -bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.blif [SYNTHESIS_PARAM] bench0_top = top -bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act -bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.v bench0_chan_width = 300 [SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] diff --git a/openfpga_flow/tasks/openfpga_shell/bram/dpram16k/config/task.conf b/openfpga_flow/tasks/openfpga_shell/bram/dpram16k/config/task.conf index aeffa9a86..cb3632359 100644 --- a/openfpga_flow/tasks/openfpga_shell/bram/dpram16k/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/bram/dpram16k/config/task.conf @@ -21,12 +21,12 @@ openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10 arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_40nm.xml [BENCHMARKS] -bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.blif [SYNTHESIS_PARAM] bench0_top = top -bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act -bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.v bench0_chan_width = 300 [SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] diff --git a/openfpga_flow/tasks/openfpga_shell/bram/wide_dpram16k/config/task.conf b/openfpga_flow/tasks/openfpga_shell/bram/wide_dpram16k/config/task.conf index 53cc5407c..756b2980e 100644 --- a/openfpga_flow/tasks/openfpga_shell/bram/wide_dpram16k/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/bram/wide_dpram16k/config/task.conf @@ -22,12 +22,12 @@ openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10 arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_wide_mem16K_40nm.xml [BENCHMARKS] -bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.blif [SYNTHESIS_PARAM] bench0_top = top -bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act -bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.v bench0_chan_width = 300 [SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] diff --git a/openfpga_flow/tasks/openfpga_shell/configuration_chain/config/task.conf b/openfpga_flow/tasks/openfpga_shell/configuration_chain/config/task.conf index 7415b96ab..5dca0428c 100644 --- a/openfpga_flow/tasks/openfpga_shell/configuration_chain/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/configuration_chain/config/task.conf @@ -21,12 +21,12 @@ openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k4_N4_40nm_ arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k4_N4_tileable_40nm.xml [BENCHMARKS] -bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.blif [SYNTHESIS_PARAM] bench0_top = top -bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act -bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.v bench0_chan_width = 300 [SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] diff --git a/openfpga_flow/tasks/openfpga_shell/duplicated_grid_pin/config/task.conf b/openfpga_flow/tasks/openfpga_shell/duplicated_grid_pin/config/task.conf index 7f1f370c6..03c54d43f 100644 --- a/openfpga_flow/tasks/openfpga_shell/duplicated_grid_pin/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/duplicated_grid_pin/config/task.conf @@ -21,12 +21,12 @@ openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10 arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml [BENCHMARKS] -bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.blif [SYNTHESIS_PARAM] bench0_top = top -bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act -bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.v bench0_chan_width = 300 [SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] diff --git a/openfpga_flow/tasks/openfpga_shell/fabric_chain/adder_chain/config/task.conf b/openfpga_flow/tasks/openfpga_shell/fabric_chain/adder_chain/config/task.conf index 348d11cde..82afc9a11 100644 --- a/openfpga_flow/tasks/openfpga_shell/fabric_chain/adder_chain/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/fabric_chain/adder_chain/config/task.conf @@ -21,12 +21,12 @@ openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10 arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_40nm.xml [BENCHMARKS] -bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.blif [SYNTHESIS_PARAM] bench0_top = top -bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act -bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.v bench0_chan_width = 300 [SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] diff --git a/openfpga_flow/tasks/openfpga_shell/fabric_chain/register_chain/config/task.conf b/openfpga_flow/tasks/openfpga_shell/fabric_chain/register_chain/config/task.conf index 29f1ac4b0..5c1ec8fb9 100644 --- a/openfpga_flow/tasks/openfpga_shell/fabric_chain/register_chain/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/fabric_chain/register_chain/config/task.conf @@ -21,12 +21,12 @@ openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10 arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_chain_40nm.xml [BENCHMARKS] -bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.blif [SYNTHESIS_PARAM] bench0_top = top -bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act -bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.v bench0_chan_width = 300 [SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] diff --git a/openfpga_flow/tasks/openfpga_shell/fabric_chain/scan_chain/config/task.conf b/openfpga_flow/tasks/openfpga_shell/fabric_chain/scan_chain/config/task.conf index e1e85366b..7a98b0731 100644 --- a/openfpga_flow/tasks/openfpga_shell/fabric_chain/scan_chain/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/fabric_chain/scan_chain/config/task.conf @@ -21,12 +21,12 @@ openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10 arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_40nm.xml [BENCHMARKS] -bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.blif [SYNTHESIS_PARAM] bench0_top = top -bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act -bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.v bench0_chan_width = 300 [SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] diff --git a/openfpga_flow/tasks/openfpga_shell/flatten_routing/config/task.conf b/openfpga_flow/tasks/openfpga_shell/flatten_routing/config/task.conf index bb15c4c43..7823bee48 100644 --- a/openfpga_flow/tasks/openfpga_shell/flatten_routing/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/flatten_routing/config/task.conf @@ -21,12 +21,12 @@ openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10 arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml [BENCHMARKS] -bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.blif [SYNTHESIS_PARAM] bench0_top = top -bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act -bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.v bench0_chan_width = 300 [SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] diff --git a/openfpga_flow/tasks/openfpga_shell/frac_lut/config/task.conf b/openfpga_flow/tasks/openfpga_shell/frac_lut/config/task.conf index a52d2f515..f9276b42b 100644 --- a/openfpga_flow/tasks/openfpga_shell/frac_lut/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/frac_lut/config/task.conf @@ -21,13 +21,17 @@ openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10 arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml [BENCHMARKS] -bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.blif +#bench1=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and_latch/and_latch.blif [SYNTHESIS_PARAM] bench0_top = top -bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act -bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v -bench0_chan_width = 300 +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.v + +bench1_top = top +bench1_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and_latch.act +bench1_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and_latch.v [SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] end_flow_with_test= diff --git a/openfpga_flow/tasks/openfpga_shell/hard_adder/config/task.conf b/openfpga_flow/tasks/openfpga_shell/hard_adder/config/task.conf index f9fbc97f2..cd63c7064 100644 --- a/openfpga_flow/tasks/openfpga_shell/hard_adder/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/hard_adder/config/task.conf @@ -21,12 +21,12 @@ openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10 arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_40nm.xml [BENCHMARKS] -bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.blif [SYNTHESIS_PARAM] bench0_top = top -bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act -bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.v bench0_chan_width = 300 [SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] diff --git a/openfpga_flow/tasks/openfpga_shell/implicit_verilog/config/task.conf b/openfpga_flow/tasks/openfpga_shell/implicit_verilog/config/task.conf index 9c10e2783..24e443381 100644 --- a/openfpga_flow/tasks/openfpga_shell/implicit_verilog/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/implicit_verilog/config/task.conf @@ -21,12 +21,12 @@ openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10 arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml [BENCHMARKS] -bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.blif [SYNTHESIS_PARAM] bench0_top = top -bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act -bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.v bench0_chan_width = 300 [SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] diff --git a/openfpga_flow/tasks/openfpga_shell/io/aib/config/task.conf b/openfpga_flow/tasks/openfpga_shell/io/aib/config/task.conf index 2d08de621..d71cc949f 100644 --- a/openfpga_flow/tasks/openfpga_shell/io/aib/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/io/aib/config/task.conf @@ -32,12 +32,12 @@ openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10 arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_aib_40nm.xml [BENCHMARKS] -bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.blif [SYNTHESIS_PARAM] bench0_top = top -bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act -bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.v bench0_chan_width = 300 [SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] diff --git a/openfpga_flow/tasks/openfpga_shell/io/multi_io_capacity/config/task.conf b/openfpga_flow/tasks/openfpga_shell/io/multi_io_capacity/config/task.conf index a3f9081d9..ee003aa84 100644 --- a/openfpga_flow/tasks/openfpga_shell/io/multi_io_capacity/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/io/multi_io_capacity/config/task.conf @@ -21,12 +21,12 @@ openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10 arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_multi_io_capacity_40nm.xml [BENCHMARKS] -bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.blif [SYNTHESIS_PARAM] bench0_top = top -bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act -bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.v bench0_chan_width = 300 [SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] diff --git a/openfpga_flow/tasks/openfpga_shell/io/reduced_io/config/task.conf b/openfpga_flow/tasks/openfpga_shell/io/reduced_io/config/task.conf index 8ed4998db..7d2be5aa5 100644 --- a/openfpga_flow/tasks/openfpga_shell/io/reduced_io/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/io/reduced_io/config/task.conf @@ -21,12 +21,12 @@ openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10 arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_reduced_io_40nm.xml [BENCHMARKS] -bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.blif [SYNTHESIS_PARAM] bench0_top = top -bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act -bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.v bench0_chan_width = 300 [SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] diff --git a/openfpga_flow/tasks/openfpga_shell/mcnc_big20/config/task.conf b/openfpga_flow/tasks/openfpga_shell/mcnc_big20/config/task.conf index 7a64bfcde..f28150126 100644 --- a/openfpga_flow/tasks/openfpga_shell/mcnc_big20/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/mcnc_big20/config/task.conf @@ -24,7 +24,8 @@ arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_ti bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/alu4/alu4.blif bench1=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex2/apex2.blif bench2=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex4/apex4.blif -bench3=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/bigkey/bigkey.blif +# VPR remove dangling PIs and POs which are in act file. Then VPR errors out by saying these PI/PO should not exist in act file +#bench3=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/bigkey/bigkey.blif bench4=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/clma/clma.blif bench5=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/des/des.blif bench6=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/diffeq/diffeq.blif diff --git a/openfpga_flow/tasks/openfpga_shell/mux_design/stdcell_mux2/config/task.conf b/openfpga_flow/tasks/openfpga_shell/mux_design/stdcell_mux2/config/task.conf index 6ad9d0db0..3aa07c229 100644 --- a/openfpga_flow/tasks/openfpga_shell/mux_design/stdcell_mux2/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/mux_design/stdcell_mux2/config/task.conf @@ -21,12 +21,12 @@ openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10 arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml [BENCHMARKS] -bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.blif [SYNTHESIS_PARAM] bench0_top = top -bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act -bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.v bench0_chan_width = 300 [SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] diff --git a/openfpga_flow/tasks/openfpga_shell/mux_design/tree_structure/config/task.conf b/openfpga_flow/tasks/openfpga_shell/mux_design/tree_structure/config/task.conf index 658e328c0..2b164ecf5 100644 --- a/openfpga_flow/tasks/openfpga_shell/mux_design/tree_structure/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/mux_design/tree_structure/config/task.conf @@ -21,12 +21,12 @@ openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10 arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml [BENCHMARKS] -bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.blif [SYNTHESIS_PARAM] bench0_top = top -bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act -bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.v bench0_chan_width = 300 [SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] diff --git a/openfpga_flow/tasks/openfpga_shell/untileable/config/task.conf b/openfpga_flow/tasks/openfpga_shell/untileable/config/task.conf index 4ea65822f..c67f71c55 100644 --- a/openfpga_flow/tasks/openfpga_shell/untileable/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/untileable/config/task.conf @@ -21,12 +21,12 @@ openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10 arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_40nm.xml [BENCHMARKS] -bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.blif +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.blif [SYNTHESIS_PARAM] bench0_top = top -bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.act -bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and.v +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.v bench0_chan_width = 300 [SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] From e10cafe0a575365b3c3b92a2a1ba719f733f9c3f Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 19 Apr 2020 16:42:31 -0600 Subject: [PATCH 451/645] Critical patch on repacking about wire LUT support. Previously, the wire LUT identification is too naive and does not consider all the cases --- openfpga/src/base/openfpga_repack.cpp | 3 +- .../src/repack/build_physical_truth_table.cpp | 60 ++++++++---- .../src/repack/build_physical_truth_table.h | 3 +- openfpga/src/repack/lb_router_utils.cpp | 10 ++ openfpga/src/repack/physical_pb.cpp | 24 +++++ openfpga/src/repack/physical_pb.h | 6 ++ openfpga/src/repack/repack.cpp | 49 ++++++---- openfpga/src/utils/lut_utils.cpp | 23 +++++ openfpga/src/utils/pb_type_utils.h | 9 ++ openfpga/src/utils/physical_pb_utils.cpp | 98 ++++++++++++++++++- openfpga/src/utils/physical_pb_utils.h | 3 +- .../openfpga_shell/frac_lut/config/task.conf | 8 +- 12 files changed, 248 insertions(+), 48 deletions(-) diff --git a/openfpga/src/base/openfpga_repack.cpp b/openfpga/src/base/openfpga_repack.cpp index fe0529d04..a0fd143f4 100644 --- a/openfpga/src/base/openfpga_repack.cpp +++ b/openfpga/src/base/openfpga_repack.cpp @@ -37,7 +37,8 @@ int repack(OpenfpgaContext& openfpga_ctx, g_vpr_ctx.atom(), g_vpr_ctx.clustering(), openfpga_ctx.vpr_device_annotation(), - openfpga_ctx.arch().circuit_lib); + openfpga_ctx.arch().circuit_lib, + cmd_context.option_enable(cmd, opt_verbose)); /* TODO: should identify the error code from internal function execution */ return CMD_EXEC_SUCCESS; diff --git a/openfpga/src/repack/build_physical_truth_table.cpp b/openfpga/src/repack/build_physical_truth_table.cpp index 51fc93f93..404911a2d 100644 --- a/openfpga/src/repack/build_physical_truth_table.cpp +++ b/openfpga/src/repack/build_physical_truth_table.cpp @@ -11,15 +11,13 @@ #include "openfpga_naming.h" #include "lut_utils.h" +#include "pb_type_utils.h" #include "physical_pb.h" #include "build_physical_truth_table.h" /* begin namespace openfpga */ namespace openfpga { -/* Mode 1 is the lut mode while mode 0 is the wire mode */ -constexpr int VPR_PB_TYPE_LUT_MODE = 1; - /*************************************************************************************** * Identify if LUT is used as wiring * In this case, LUT functions as a buffer @@ -102,7 +100,8 @@ void build_physical_pb_lut_truth_tables(PhysicalPb& physical_pb, const PhysicalPbId& lut_pb_id, const AtomContext& atom_ctx, const VprDeviceAnnotation& device_annotation, - const CircuitLibrary& circuit_lib) { + const CircuitLibrary& circuit_lib, + const bool& verbose) { const t_pb_graph_node* pb_graph_node = physical_pb.pb_graph_node(lut_pb_id); CircuitModelId lut_model = device_annotation.pb_type_circuit_model(physical_pb.pb_graph_node(lut_pb_id)->pb_type); @@ -125,20 +124,21 @@ void build_physical_pb_lut_truth_tables(PhysicalPb& physical_pb, continue; } /* Check if this is a LUT used as wiring */ - if (true == is_wired_lut(input_nets, output_net)) { - AtomNetlist::TruthTable wire_tt = build_wired_lut_truth_table(input_nets.size(), std::find(input_nets.begin(), input_nets.end(), output_net) - input_nets.begin()); - physical_pb.set_truth_table(lut_pb_id, output_pin, wire_tt); - continue; + AtomNetlist::TruthTable adapt_tt; + if (true == physical_pb.is_wire_lut_output(lut_pb_id, output_pin)) { + /* Double check: ensure that the output nets appear in the input net !!! */ + VTR_ASSERT(true == is_wired_lut(input_nets, output_net)); + adapt_tt = build_wired_lut_truth_table(input_nets.size(), std::find(input_nets.begin(), input_nets.end(), output_net) - input_nets.begin()); + } else { + /* Find the truth table from atom block which drives the atom net */ + const AtomBlockId& atom_blk = atom_ctx.nlist.net_driver_block(output_net); + VTR_ASSERT(true == atom_ctx.nlist.valid_block_id(atom_blk)); + const AtomNetlist::TruthTable& orig_tt = atom_ctx.nlist.block_truth_table(atom_blk); + + std::vector rotated_pin_map = generate_lut_rotated_input_pin_map(input_nets, atom_ctx, atom_blk, pb_graph_node); + adapt_tt = lut_truth_table_adaption(orig_tt, rotated_pin_map); } - /* Find the truth table from atom block which drives the atom net */ - const AtomBlockId& atom_blk = atom_ctx.nlist.net_driver_block(output_net); - VTR_ASSERT(true == atom_ctx.nlist.valid_block_id(atom_blk)); - const AtomNetlist::TruthTable& orig_tt = atom_ctx.nlist.block_truth_table(atom_blk); - - std::vector rotated_pin_map = generate_lut_rotated_input_pin_map(input_nets, atom_ctx, atom_blk, pb_graph_node); - const AtomNetlist::TruthTable& adapt_tt = lut_truth_table_adaption(orig_tt, rotated_pin_map); - /* Adapt the truth table for fracturable lut implementation and add to physical pb */ CircuitPortId lut_model_output_port = device_annotation.pb_circuit_port(output_pin->port); size_t lut_frac_level = circuit_lib.port_lut_frac_level(lut_model_output_port); @@ -148,6 +148,29 @@ void build_physical_pb_lut_truth_tables(PhysicalPb& physical_pb, size_t lut_output_mask = circuit_lib.port_lut_output_mask(lut_model_output_port)[output_pin->pin_number]; const AtomNetlist::TruthTable& frac_lut_tt = adapt_truth_table_for_frac_lut(lut_frac_level, lut_output_mask, adapt_tt); physical_pb.set_truth_table(lut_pb_id, output_pin, frac_lut_tt); + + /* Print debug information */ + VTR_LOGV(verbose, "Input nets: "); + for (const AtomNetId& net : input_nets) { + if (AtomNetId::INVALID() == net) { + VTR_LOGV(verbose, "unconn "); + } else { + VTR_ASSERT(AtomNetId::INVALID() != net); + VTR_LOGV(verbose, "%s ", atom_ctx.nlist.net_name(net).c_str()); + } + } + VTR_LOGV(verbose, "\n"); + + VTR_ASSERT(AtomNetId::INVALID() != output_net); + VTR_LOGV(verbose, "Output net: %s\n", atom_ctx.nlist.net_name(output_net).c_str()); + + VTR_LOGV(verbose, + "Add following truth table to pb_graph_pin '%s[%d]'\n", + output_pin->port->name, output_pin->pin_number); + for (const std::string& tt_line : truth_table_to_string(frac_lut_tt)) { + VTR_LOGV(verbose, "\t%s\n", tt_line.c_str()); + } + VTR_LOGV(verbose, "\n"); } } } @@ -164,7 +187,8 @@ void build_physical_lut_truth_tables(VprClusteringAnnotation& cluster_annotation const AtomContext& atom_ctx, const ClusteringContext& cluster_ctx, const VprDeviceAnnotation& device_annotation, - const CircuitLibrary& circuit_lib) { + const CircuitLibrary& circuit_lib, + const bool& verbose) { vtr::ScopedStartFinishTimer timer("Build truth tables for physical LUTs"); for (auto blk_id : cluster_ctx.clb_nlist.blocks()) { @@ -178,7 +202,7 @@ void build_physical_lut_truth_tables(VprClusteringAnnotation& cluster_annotation } /* Reach here, we have a LUT to deal with. Find the truth tables that mapped to the LUT */ - build_physical_pb_lut_truth_tables(physical_pb, primitive_pb, atom_ctx, device_annotation, circuit_lib); + build_physical_pb_lut_truth_tables(physical_pb, primitive_pb, atom_ctx, device_annotation, circuit_lib, verbose); } } } diff --git a/openfpga/src/repack/build_physical_truth_table.h b/openfpga/src/repack/build_physical_truth_table.h index c5d14059f..3c0c8ebf3 100644 --- a/openfpga/src/repack/build_physical_truth_table.h +++ b/openfpga/src/repack/build_physical_truth_table.h @@ -20,7 +20,8 @@ void build_physical_lut_truth_tables(VprClusteringAnnotation& cluster_annotation const AtomContext& atom_ctx, const ClusteringContext& cluster_ctx, const VprDeviceAnnotation& device_annotation, - const CircuitLibrary& circuit_lib); + const CircuitLibrary& circuit_lib, + const bool& verbose); } /* end namespace openfpga */ diff --git a/openfpga/src/repack/lb_router_utils.cpp b/openfpga/src/repack/lb_router_utils.cpp index 362ce4a76..1dea1750d 100644 --- a/openfpga/src/repack/lb_router_utils.cpp +++ b/openfpga/src/repack/lb_router_utils.cpp @@ -86,6 +86,16 @@ void save_lb_router_results_to_physical_pb(PhysicalPb& phy_pb, VTR_ASSERT(true == phy_pb.valid_pb_id(pb_id)); const AtomNetId& atom_net = lb_router.net_atom_net_id(net); + + /* Print info to help debug + bool verbose = true; + VTR_LOGV(verbose, + "\nSave net '%lu' to physical pb_graph_pin '%s.%s[%d]'\n", + size_t(atom_net), + pb_graph_pin->parent_node->pb_type->name, + pb_graph_pin->port->name, + pb_graph_pin->pin_number); + */ if (AtomNetId::INVALID() == phy_pb.pb_graph_pin_atom_net(pb_id, pb_graph_pin)) { phy_pb.set_pb_graph_pin_atom_net(pb_id, pb_graph_pin, atom_net); diff --git a/openfpga/src/repack/physical_pb.cpp b/openfpga/src/repack/physical_pb.cpp index 74894293a..4650d7d0d 100644 --- a/openfpga/src/repack/physical_pb.cpp +++ b/openfpga/src/repack/physical_pb.cpp @@ -81,6 +81,17 @@ AtomNetId PhysicalPb::pb_graph_pin_atom_net(const PhysicalPbId& pb, return AtomNetId::INVALID(); } +bool PhysicalPb::is_wire_lut_output(const PhysicalPbId& pb, + const t_pb_graph_pin* pb_graph_pin) const { + VTR_ASSERT(true == valid_pb_id(pb)); + if (wire_lut_outputs_[pb].find(pb_graph_pin) != wire_lut_outputs_[pb].end()) { + /* Find it, return the status */ + return wire_lut_outputs_[pb].at(pb_graph_pin); + } + /* Not found, return false */ + return false; +} + std::map PhysicalPb::truth_tables(const PhysicalPbId& pb) const { VTR_ASSERT(true == valid_pb_id(pb)); return truth_tables_[pb]; @@ -110,6 +121,7 @@ PhysicalPbId PhysicalPb::create_pb(const t_pb_graph_node* pb_graph_node) { pb_graph_nodes_.push_back(pb_graph_node); atom_blocks_.emplace_back(); pin_atom_nets_.emplace_back(); + wire_lut_outputs_.emplace_back(); child_pbs_.emplace_back(); parent_pbs_.emplace_back(); @@ -182,6 +194,18 @@ void PhysicalPb::set_pb_graph_pin_atom_net(const PhysicalPbId& pb, pin_atom_nets_[pb][pb_graph_pin] = atom_net; } +void PhysicalPb::set_wire_lut_output(const PhysicalPbId& pb, + const t_pb_graph_pin* pb_graph_pin, + const bool& wire_lut_output) { + VTR_ASSERT(true == valid_pb_id(pb)); + if (wire_lut_outputs_[pb].end() != wire_lut_outputs_[pb].find(pb_graph_pin)) { + VTR_LOG_WARN("Overwrite pb_graph_pin '%s[%d]' status on wire LUT output\n", + pb_graph_pin->port->name, pb_graph_pin->pin_number); + } + + wire_lut_outputs_[pb][pb_graph_pin] = wire_lut_output; +} + /****************************************************************************** * Private validators/invalidators ******************************************************************************/ diff --git a/openfpga/src/repack/physical_pb.h b/openfpga/src/repack/physical_pb.h index 11559da6b..6d8d2ba3b 100644 --- a/openfpga/src/repack/physical_pb.h +++ b/openfpga/src/repack/physical_pb.h @@ -50,6 +50,8 @@ class PhysicalPb { std::vector atom_blocks(const PhysicalPbId& pb) const; AtomNetId pb_graph_pin_atom_net(const PhysicalPbId& pb, const t_pb_graph_pin* pb_graph_pin) const; + bool is_wire_lut_output(const PhysicalPbId& pb, + const t_pb_graph_pin* pb_graph_pin) const; std::map truth_tables(const PhysicalPbId& pb) const; std::vector mode_bits(const PhysicalPbId& pb) const; public: /* Public mutators */ @@ -67,6 +69,9 @@ class PhysicalPb { void set_pb_graph_pin_atom_net(const PhysicalPbId& pb, const t_pb_graph_pin* pb_graph_pin, const AtomNetId& atom_net); + void set_wire_lut_output(const PhysicalPbId& pb, + const t_pb_graph_pin* pb_graph_pin, + const bool& wire_lut_output); public: /* Public validators/invalidators */ bool valid_pb_id(const PhysicalPbId& pb_id) const; bool empty() const; @@ -76,6 +81,7 @@ class PhysicalPb { vtr::vector names_; vtr::vector> atom_blocks_; vtr::vector> pin_atom_nets_; + vtr::vector> wire_lut_outputs_; /* Child pbs are organized as [0..num_child_pb_types-1][0..child_pb_type->num_pb-1] */ vtr::vector>> child_pbs_; diff --git a/openfpga/src/repack/repack.cpp b/openfpga/src/repack/repack.cpp index a32366be0..39697f100 100644 --- a/openfpga/src/repack/repack.cpp +++ b/openfpga/src/repack/repack.cpp @@ -278,17 +278,20 @@ void add_lb_router_nets(LbRouter& lb_router, VTR_ASSERT(sink_lb_rr_nodes.size() == sink_pb_graph_pins.size()); /* Printf for debugging only, may be enabled if verbose is enabled - VTR_LOG("Pb route for Net %s:\n", - atom_ctx.nlist.net_name(atom_net_id).c_str()); - VTR_LOG("Source node:\n\t%s -> %s\n", - source_pb_pin->to_string().c_str(), - source_pb_pin->to_string().c_str()); - VTR_LOG("Sink nodes:\n"); - for (t_pb_graph_pin* sink_pb_pin : sink_pb_graph_pins) { - VTR_LOG("\t%s\n", - sink_pb_pin->to_string().c_str()); - } */ + VTR_LOGV(verbose, + "Pb route for Net %s:\n", + atom_ctx.nlist.net_name(atom_net_id).c_str()); + VTR_LOGV(verbose, + "Source node:\n\t%s -> %s\n", + source_pb_pin->to_string().c_str(), + source_pb_pin->to_string().c_str()); + VTR_LOGV(verbose, "Sink nodes:\n"); + for (t_pb_graph_pin* sink_pb_pin : sink_pb_graph_pins) { + VTR_LOGV(verbose, + "\t%s\n", + sink_pb_pin->to_string().c_str()); + } /* Add the net */ add_lb_router_net_to_route(lb_router, lb_rr_graph, @@ -341,17 +344,20 @@ void add_lb_router_nets(LbRouter& lb_router, VTR_ASSERT(sink_lb_rr_nodes.size() == sink_pb_graph_pins.size()); /* Printf for debugging only, may be enabled if verbose is enabled - VTR_LOG("Pb route for Net %s:\n", - atom_ctx.nlist.net_name(atom_net_id).c_str()); - VTR_LOG("Source node:\n\t%s -> %s\n", - source_pb_pin->to_string().c_str(), - physical_source_pb_pin->to_string().c_str()); - VTR_LOG("Sink nodes:\n"); - for (t_pb_graph_pin* sink_pb_pin : sink_pb_graph_pins) { - VTR_LOG("\t%s\n", - sink_pb_pin->to_string().c_str()); - } */ + VTR_LOGV(verbose, + "Pb route for Net %s:\n", + atom_ctx.nlist.net_name(atom_net_id).c_str()); + VTR_LOGV(verbose, + "Source node:\n\t%s -> %s\n", + source_pb_pin->to_string().c_str(), + physical_source_pb_pin->to_string().c_str()); + VTR_LOGV(verbose, "Sink nodes:\n"); + for (t_pb_graph_pin* sink_pb_pin : sink_pb_graph_pins) { + VTR_LOGV(verbose, + "\t%s\n", + sink_pb_pin->to_string().c_str()); + } /* Add the net */ add_lb_router_net_to_route(lb_router, lb_rr_graph, @@ -429,7 +435,8 @@ void repack_cluster(const AtomContext& atom_ctx, clustering_ctx.clb_nlist.block_pb(block_id), clustering_ctx.clb_nlist.block_pb(block_id)->pb_route, atom_ctx, - device_annotation); + device_annotation, + verbose); /* Save routing results */ save_lb_router_results_to_physical_pb(phy_pb, lb_router, lb_rr_graph); VTR_LOGV(verbose, "Saved results in physical pb\n"); diff --git a/openfpga/src/utils/lut_utils.cpp b/openfpga/src/utils/lut_utils.cpp index ca3ac343e..35f613c73 100644 --- a/openfpga/src/utils/lut_utils.cpp +++ b/openfpga/src/utils/lut_utils.cpp @@ -473,6 +473,29 @@ std::vector build_frac_lut_bitstream(const CircuitLibrary& circuit_lib, VTR_ASSERT(bitstream_offset < lut_bitstream.size()); VTR_ASSERT(bitstream_offset + length_of_temp_bitstream_to_copy <= lut_bitstream.size()); + /* Print debug information + bool verbose = true; + VTR_LOGV(verbose, "Full truth table\n"); + for (const std::string& tt_line : truth_table_to_string(element.second)) { + VTR_LOGV(verbose, "\t%s\n", tt_line.c_str()); + } + VTR_LOGV(verbose, "\n"); + + VTR_LOGV(verbose, "Bitstream (size = %ld)\n", temp_bitstream.size()); + for (const bool& bit : temp_bitstream) { + if (true == bit) { + VTR_LOGV(verbose, "1"); + } else { + VTR_ASSERT(false == bit); + VTR_LOGV(verbose, "0"); + } + } + VTR_LOGV(verbose, "\n"); + + VTR_LOGV(verbose, "Bitstream offset = %d\n", bitstream_offset); + VTR_LOGV(verbose, "Bitstream length to be used = %d\n", length_of_temp_bitstream_to_copy); + */ + /* Copy to the segment of bitstream */ for (size_t bit = bitstream_offset; bit < bitstream_offset + length_of_temp_bitstream_to_copy; ++bit) { lut_bitstream[bit] = temp_bitstream[bit]; diff --git a/openfpga/src/utils/pb_type_utils.h b/openfpga/src/utils/pb_type_utils.h index 6a4dd91be..5d23f23bc 100644 --- a/openfpga/src/utils/pb_type_utils.h +++ b/openfpga/src/utils/pb_type_utils.h @@ -17,6 +17,15 @@ /* begin namespace openfpga */ namespace openfpga { +/* Constants */ + +/* Mode index of a LUT pb_type + * Mode 0 is the wire mode + * Mode 1 is the lut mode + */ +constexpr int VPR_PB_TYPE_WIRE_MODE = 0; +constexpr int VPR_PB_TYPE_LUT_MODE = 1; + bool is_primitive_pb_type(t_pb_type* pb_type); bool is_root_pb_type(t_pb_type* pb_type); diff --git a/openfpga/src/utils/physical_pb_utils.cpp b/openfpga/src/utils/physical_pb_utils.cpp index 93fbba992..cd1b13a4b 100644 --- a/openfpga/src/utils/physical_pb_utils.cpp +++ b/openfpga/src/utils/physical_pb_utils.cpp @@ -132,6 +132,16 @@ void update_primitive_physical_pb_pin_atom_net(PhysicalPb& phy_pb, t_pb_graph_pin* physical_pb_graph_pin = device_annotation.physical_pb_graph_pin(pb_graph_pin); VTR_ASSERT(nullptr != physical_pb_graph_pin); + /* Print info to help debug + bool verbose = true; + VTR_LOGV(verbose, + "\nSynchronize net '%lu' to physical pb_graph_pin '%s.%s[%d]'\n", + size_t(atom_net), + pb_graph_pin->parent_node->pb_type->name, + pb_graph_pin->port->name, + pb_graph_pin->pin_number); + */ + /* Check if the pin has been mapped to a net. * If yes, the atom net must be the same */ @@ -155,6 +165,7 @@ void synchronize_primitive_physical_pb_atom_nets(PhysicalPb& phy_pb, const AtomBlockId& atom_blk, const VprDeviceAnnotation& device_annotation) { /* Iterate over all the ports: input, output and clock */ + for (int iport = 0; iport < pb_graph_node->num_input_ports; ++iport) { for (int ipin = 0; ipin < pb_graph_node->num_input_pins[iport]; ++ipin) { /* Port exists (some LUTs may have no input and hence no port in the atom netlist) */ @@ -219,6 +230,46 @@ void synchronize_primitive_physical_pb_atom_nets(PhysicalPb& phy_pb, } } +/************************************************************************ + * Reach this function, the primitive pb should be + * - linked to a LUT pb_type + * - operating in the wire mode of a LUT + * + * Note: this function will not check the prequistics here + * Users must be responsible for this!!! + * + * This function will find the physical pb_graph_pin for each output + * of the pb_graph node and mark in the physical_pb database + * as driven by an wired LUT + ***********************************************************************/ +static +void mark_physical_pb_wired_lut_outputs(PhysicalPb& phy_pb, + const PhysicalPbId& primitive_pb, + const t_pb_graph_node* pb_graph_node, + const VprDeviceAnnotation& device_annotation, + const bool& verbose) { + + for (int iport = 0; iport < pb_graph_node->num_output_ports; ++iport) { + for (int ipin = 0; ipin < pb_graph_node->num_output_pins[iport]; ++ipin) { + t_pb_graph_pin* pb_graph_pin = &(pb_graph_node->output_pins[iport][ipin]); + + /* Find the physical pb_graph_pin */ + t_pb_graph_pin* physical_pb_graph_pin = device_annotation.physical_pb_graph_pin(pb_graph_pin); + VTR_ASSERT(nullptr != physical_pb_graph_pin); + + /* Print debug info */ + VTR_LOGV(verbose, + "Mark physical pb_graph pin '%s.%s[%d]' as wire LUT output\n", + physical_pb_graph_pin->parent_node->pb_type->name, + physical_pb_graph_pin->port->name, + physical_pb_graph_pin->pin_number); + + /* Label the pins in physical_pb as driven by wired LUT*/ + phy_pb.set_wire_lut_output(primitive_pb, physical_pb_graph_pin, true); + } + } +} + /************************************************************************ * Synchronize mapping results from an operating pb to a physical pb ***********************************************************************/ @@ -226,7 +277,8 @@ void rec_update_physical_pb_from_operating_pb(PhysicalPb& phy_pb, const t_pb* op_pb, const t_pb_routes& pb_route, const AtomContext& atom_ctx, - const VprDeviceAnnotation& device_annotation) { + const VprDeviceAnnotation& device_annotation, + const bool& verbose) { t_pb_graph_node* pb_graph_node = op_pb->pb_graph_node; t_pb_type* pb_type = pb_graph_node->pb_type; @@ -265,7 +317,49 @@ void rec_update_physical_pb_from_operating_pb(PhysicalPb& phy_pb, &(op_pb->child_pbs[ipb][jpb]), pb_route, atom_ctx, - device_annotation); + device_annotation, + verbose); + } else { + /* Some pb may be used just in routing purpose, find out the output nets */ + /* The following code is inspired by output_cluster.cpp */ + bool is_used = false; + t_pb_type* child_pb_type = &(mapped_mode->pb_type_children[ipb]); + int port_index = 0; + t_pb_graph_node* child_pb_graph_node = &(pb_graph_node->child_pb_graph_nodes[op_pb->mode][ipb][jpb]); + + for (int k = 0; k < child_pb_type->num_ports && !is_used; k++) { + if (OUT_PORT == child_pb_type->ports[k].type) { + for (int m = 0; m < child_pb_type->ports[k].num_pins; m++) { + int node_index = child_pb_graph_node->output_pins[port_index][m].pin_count_in_cluster; + if (pb_route.count(node_index) && pb_route[node_index].atom_net_id) { + is_used = true; + break; + } + } + port_index++; + } + } + /* Identify output pb_graph_pin that is driven by a wired LUT + * Without this function, physical Look-Up Table build-up will cause errors + * and bitstream will be incorrect!!! + */ + if (true == is_used) { + VTR_ASSERT(LUT_CLASS == child_pb_type->class_type); + + t_pb_graph_node* physical_pb_graph_node = device_annotation.physical_pb_graph_node(child_pb_graph_node); + VTR_ASSERT(nullptr != physical_pb_graph_node); + /* Find the physical pb */ + const PhysicalPbId& physical_pb = phy_pb.find_pb(physical_pb_graph_node); + VTR_ASSERT(true == phy_pb.valid_pb_id(physical_pb)); + + /* Set the mode bits */ + phy_pb.set_mode_bits(physical_pb, device_annotation.pb_type_mode_bits(child_pb_type)); + + mark_physical_pb_wired_lut_outputs(phy_pb, physical_pb, + child_pb_graph_node, + device_annotation, + verbose); + } } } } diff --git a/openfpga/src/utils/physical_pb_utils.h b/openfpga/src/utils/physical_pb_utils.h index 0e8bb71df..227feec6c 100644 --- a/openfpga/src/utils/physical_pb_utils.h +++ b/openfpga/src/utils/physical_pb_utils.h @@ -28,7 +28,8 @@ void rec_update_physical_pb_from_operating_pb(PhysicalPb& phy_pb, const t_pb* op_pb, const t_pb_routes& pb_route, const AtomContext& atom_ctx, - const VprDeviceAnnotation& device_annotation); + const VprDeviceAnnotation& device_annotation, + const bool& verbose); } /* end namespace openfpga */ diff --git a/openfpga_flow/tasks/openfpga_shell/frac_lut/config/task.conf b/openfpga_flow/tasks/openfpga_shell/frac_lut/config/task.conf index f9276b42b..cd002c9d4 100644 --- a/openfpga_flow/tasks/openfpga_shell/frac_lut/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/frac_lut/config/task.conf @@ -21,8 +21,8 @@ openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10 arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml [BENCHMARKS] -bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.blif -#bench1=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and_latch/and_latch.blif +#bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.blif +bench1=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and_latch/and_latch.blif [SYNTHESIS_PARAM] bench0_top = top @@ -30,8 +30,8 @@ bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/ bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.v bench1_top = top -bench1_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and_latch.act -bench1_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and_latch.v +bench1_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and_latch/and_latch.act +bench1_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and_latch/and_latch.v [SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] end_flow_with_test= From 8b03ec900ff23dfc7461dfc414ebdd461174e51c Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 19 Apr 2020 17:05:12 -0600 Subject: [PATCH 452/645] fine-tune micro benchmark to fit port mapping in testbenches --- .../benchmarks/micro_benchmark/and_latch/and_latch.v | 2 +- openfpga_flow/tasks/openfpga_shell/frac_lut/config/task.conf | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/openfpga_flow/benchmarks/micro_benchmark/and_latch/and_latch.v b/openfpga_flow/benchmarks/micro_benchmark/and_latch/and_latch.v index 893cdf7a4..a8f147f5b 100644 --- a/openfpga_flow/benchmarks/micro_benchmark/and_latch/and_latch.v +++ b/openfpga_flow/benchmarks/micro_benchmark/and_latch/and_latch.v @@ -1,9 +1,9 @@ `timescale 1ns / 1ps module top( - clk, a, b, + clk, c, d); diff --git a/openfpga_flow/tasks/openfpga_shell/frac_lut/config/task.conf b/openfpga_flow/tasks/openfpga_shell/frac_lut/config/task.conf index cd002c9d4..76c70bb6c 100644 --- a/openfpga_flow/tasks/openfpga_shell/frac_lut/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/frac_lut/config/task.conf @@ -21,8 +21,9 @@ openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10 arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml [BENCHMARKS] -#bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.blif -bench1=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and_latch/and_latch.blif +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and/and.blif +# Modelsim is ok with this but icarus fails due to poor support on timing and looping +#bench1=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and_latch/and_latch.blif [SYNTHESIS_PARAM] bench0_top = top From f6b7583a2aef52031b206957876b6538d7316f1c Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 20 Apr 2020 12:55:40 -0600 Subject: [PATCH 453/645] add tasks for single mode --- .../k6_N10_tileable_40nm.xml | 6 +-- .../openfpga_arch/k6_N10_40nm_openfpga.xml | 4 +- .../mcnc_big20/config/task.conf | 40 +++++++++---------- .../single_mode/config/task.conf | 38 ++++++++++++++++++ 4 files changed, 63 insertions(+), 25 deletions(-) create mode 100644 openfpga_flow/tasks/openfpga_shell/single_mode/config/task.conf diff --git a/openfpga_flow/arch/vpr_only_templates/k6_N10_tileable_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_N10_tileable_40nm.xml index f9065d437..fcbc20437 100644 --- a/openfpga_flow/arch/vpr_only_templates/k6_N10_tileable_40nm.xml +++ b/openfpga_flow/arch/vpr_only_templates/k6_N10_tileable_40nm.xml @@ -54,7 +54,7 @@ - + @@ -137,7 +137,7 @@ - + @@ -197,7 +197,7 @@ --> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + clb.sc_in clb.cin clb.cin_trick clb.regin clb.clk + clb.I0[9:0] clb.I1[9:0] clb.O[9:0] + clb.cout clb.cout_copy clb.sc_out clb.regout clb.I2[9:0] clb.I3[9:0] clb.O[19:10] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 202e-12 + 202e-12 + 202e-12 + 202e-12 + 202e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 180e-12 + 180e-12 + 180e-12 + 180e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 229e-12 + 229e-12 + 229e-12 + 229e-12 + 229e-12 + 229e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 9fb8971281064c24c810d9c533beea5fd8bf3634 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 22 Apr 2020 11:12:28 -0600 Subject: [PATCH 465/645] add FPGA arch with spypads to portofilo --- ...egister_scan_chain_depop50_spypad_40nm.xml | 680 ++++++++++++++++++ 1 file changed, 680 insertions(+) create mode 100755 openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_depop50_spypad_40nm.xml diff --git a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_depop50_spypad_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_depop50_spypad_40nm.xml new file mode 100755 index 000000000..70bc907f8 --- /dev/null +++ b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_depop50_spypad_40nm.xml @@ -0,0 +1,680 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + clb.sc_in clb.cin clb.cin_trick clb.regin clb.clk + clb.I0[9:0] clb.I1[9:0] clb.O[9:0] + clb.cout clb.cout_copy clb.sc_out clb.regout clb.I2[9:0] clb.I3[9:0] clb.O[19:10] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 1 1 1 1 + 1 1 1 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 202e-12 + 202e-12 + 202e-12 + 202e-12 + 202e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 180e-12 + 180e-12 + 180e-12 + 180e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 229e-12 + 229e-12 + 229e-12 + 229e-12 + 229e-12 + 229e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 73e90063728e037f45c4d8a6ac5e8a379bf3e5d3 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 22 Apr 2020 12:56:09 -0600 Subject: [PATCH 466/645] add arch file with spy pads --- ...egister_scan_chain_depop50_spypad_40nm.xml | 680 -------------- ...egister_scan_chain_depop50_spypad_40nm.xml | 862 ++++++++++++++++++ ...can_chain_depop50_spypad_40nm_openfpga.xml | 373 ++++++++ 3 files changed, 1235 insertions(+), 680 deletions(-) delete mode 100755 openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_register_scan_chain_depop50_spypad_40nm.xml create mode 100644 openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_depop50_spypad_40nm_openfpga.xml diff --git a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_register_scan_chain_depop50_spypad_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_register_scan_chain_depop50_spypad_40nm.xml deleted file mode 100755 index 70bc907f8..000000000 --- a/openfpga/test_vpr_arch/k6_frac_N10_tileable_adder_register_scan_chain_depop50_spypad_40nm.xml +++ /dev/null @@ -1,680 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - io.outpad io.inpad - io.outpad io.inpad - io.outpad io.inpad - io.outpad io.inpad - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - clb.sc_in clb.cin clb.cin_trick clb.regin clb.clk - clb.I0[9:0] clb.I1[9:0] clb.O[9:0] - clb.cout clb.cout_copy clb.sc_out clb.regout clb.I2[9:0] clb.I3[9:0] clb.O[19:10] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 1 1 1 1 - 1 1 1 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 202e-12 - 202e-12 - 202e-12 - 202e-12 - 202e-12 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 180e-12 - 180e-12 - 180e-12 - 180e-12 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 229e-12 - 229e-12 - 229e-12 - 229e-12 - 229e-12 - 229e-12 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_depop50_spypad_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_depop50_spypad_40nm.xml index 70bc907f8..d4a4104af 100755 --- a/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_depop50_spypad_40nm.xml +++ b/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_depop50_spypad_40nm.xml @@ -135,6 +135,51 @@ clb.cout clb.cout_copy clb.sc_out clb.regout clb.I2[9:0] clb.I3[9:0] clb.O[19:10] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + clb_spypad.sc_in clb_spypad.cin clb_spypad.cin_trick clb_spypad.regin clb_spypad.clk + clb_spypad.I0[9:0] clb_spypad.I1[9:0] clb_spypad.O[9:0] + clb_spypad.cout clb_spypad.cout_copy clb_spypad.sc_out clb_spypad.regout clb_spypad.I2[9:0] clb_spypad.I3[9:0] clb_spypad.O[19:10] + + @@ -153,6 +198,8 @@ + + @@ -676,5 +723,820 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 202e-12 + 202e-12 + 202e-12 + 202e-12 + 202e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 180e-12 + 180e-12 + 180e-12 + 180e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 229e-12 + 229e-12 + 229e-12 + 229e-12 + 229e-12 + 229e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 202e-12 + 202e-12 + 202e-12 + 202e-12 + 202e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 180e-12 + 180e-12 + 180e-12 + 180e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 229e-12 + 229e-12 + 229e-12 + 229e-12 + 229e-12 + 229e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_depop50_spypad_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_depop50_spypad_40nm_openfpga.xml new file mode 100644 index 000000000..4939b2dc0 --- /dev/null +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_depop50_spypad_40nm_openfpga.xml @@ -0,0 +1,373 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + + + + 10e-12 5e-12 + + + 10e-12 5e-12 + + + + + + + + + + + + 10e-12 5e-12 5e-12 + + + 10e-12 5e-12 5e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 726185cd5ee37eb28a1860091b8e5f50399b7411 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 22 Apr 2020 12:56:57 -0600 Subject: [PATCH 467/645] add test cases using spypad architecture --- .../openfpga_shell/spypad/config/task.conf | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 openfpga_flow/tasks/openfpga_shell/spypad/config/task.conf diff --git a/openfpga_flow/tasks/openfpga_shell/spypad/config/task.conf b/openfpga_flow/tasks/openfpga_shell/spypad/config/task.conf new file mode 100644 index 000000000..e093b2b53 --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/spypad/config/task.conf @@ -0,0 +1,34 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_depop50_spypad_40nm_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_depop50_spypad_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.blif + +[SYNTHESIS_PARAM] +bench0_top = and2 +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.v +bench0_chan_width = 300 + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +end_flow_with_test= +vpr_fpga_verilog_formal_verification_top_netlist= From 8ac6e107271f0d153a7c2d5a4fe14d0fe867a385 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 22 Apr 2020 14:41:16 -0600 Subject: [PATCH 468/645] bug fix in lut and mux module generation on supporting spypads --- .../libarchopenfpga/src/circuit_library.cpp | 2 +- openfpga/src/fabric/build_grid_modules.cpp | 28 ++++++++++++++++--- openfpga/src/fabric/build_lut_modules.cpp | 2 +- openfpga/src/fabric/build_mux_modules.cpp | 4 +-- openfpga/src/mux_lib/mux_graph.cpp | 2 +- 5 files changed, 29 insertions(+), 9 deletions(-) diff --git a/libopenfpga/libarchopenfpga/src/circuit_library.cpp b/libopenfpga/libarchopenfpga/src/circuit_library.cpp index cf8b8714f..a235e53af 100644 --- a/libopenfpga/libarchopenfpga/src/circuit_library.cpp +++ b/libopenfpga/libarchopenfpga/src/circuit_library.cpp @@ -777,7 +777,7 @@ std::vector CircuitLibrary::model_ports_by_type(const CircuitMode if ( type != port_type(port_id) ) { continue; } - /* We skip global ports if specified */ + /* We skip global ports if specified. Note: I/O port should be kept */ if ( (true == ignore_global_port) && (true == port_is_global(port_id)) ) { continue; diff --git a/openfpga/src/fabric/build_grid_modules.cpp b/openfpga/src/fabric/build_grid_modules.cpp index 0d88bdc81..12090d43d 100644 --- a/openfpga/src/fabric/build_grid_modules.cpp +++ b/openfpga/src/fabric/build_grid_modules.cpp @@ -163,16 +163,36 @@ void add_primitive_module_fpga_global_io_port(ModuleManager& module_manager, BasicPort logic_io_port = module_manager.module_port(logic_module, logic_io_port_id); VTR_ASSERT(logic_io_port.get_width() == module_port.get_width()); - /* Wire the GPIO port form primitive_module to the logic module!*/ + /* Wire the GPIO port from primitive_module to the logic module!*/ for (size_t pin_id = 0; pin_id < module_port.pins().size(); ++pin_id) { - ModuleNetId net = module_manager.create_module_net(primitive_module); if ( (ModuleManager::MODULE_GPIO_PORT == module_io_port_type) || (ModuleManager::MODULE_GPIN_PORT == module_io_port_type) ) { - module_manager.add_module_net_source(primitive_module, net, primitive_module, 0, primitive_io_port_id, module_port.pins()[pin_id]); + bool net_exist = true; + /* If the source port has already a net to drive, we just update the net sinks */ + ModuleNetId net = module_manager.module_instance_port_net(primitive_module, primitive_module, 0, primitive_io_port_id, module_port.pins()[pin_id]); + if (net == ModuleNetId::INVALID()) { + net_exist = false; + net = module_manager.create_module_net(primitive_module); + } + + if (false == net_exist) { + module_manager.add_module_net_source(primitive_module, net, primitive_module, 0, primitive_io_port_id, module_port.pins()[pin_id]); + } module_manager.add_module_net_sink(primitive_module, net, logic_module, logic_instance_id, logic_io_port_id, logic_io_port.pins()[pin_id]); } else { + bool net_exist = true; + /* If the source port has already a net to drive, we just update the net sinks */ + ModuleNetId net = module_manager.module_instance_port_net(primitive_module, logic_module, logic_instance_id, logic_io_port_id, logic_io_port.pins()[pin_id]); + if (net == ModuleNetId::INVALID()) { + net_exist = false; + net = module_manager.create_module_net(primitive_module); + } + VTR_ASSERT(ModuleManager::MODULE_GPOUT_PORT == module_io_port_type); - module_manager.add_module_net_source(primitive_module, net, logic_module, logic_instance_id, logic_io_port_id, logic_io_port.pins()[pin_id]); + + if (false == net_exist) { + module_manager.add_module_net_source(primitive_module, net, logic_module, logic_instance_id, logic_io_port_id, logic_io_port.pins()[pin_id]); + } module_manager.add_module_net_sink(primitive_module, net, primitive_module, 0, primitive_io_port_id, module_port.pins()[pin_id]); } } diff --git a/openfpga/src/fabric/build_lut_modules.cpp b/openfpga/src/fabric/build_lut_modules.cpp index 184de6698..2ba688e4d 100644 --- a/openfpga/src/fabric/build_lut_modules.cpp +++ b/openfpga/src/fabric/build_lut_modules.cpp @@ -45,7 +45,7 @@ void build_lut_module(ModuleManager& module_manager, /* Get the input ports from the mux */ std::vector lut_input_ports = circuit_lib.model_ports_by_type(lut_model, CIRCUIT_MODEL_PORT_INPUT, true); /* Get the output ports from the mux */ - std::vector lut_output_ports = circuit_lib.model_ports_by_type(lut_model, CIRCUIT_MODEL_PORT_OUTPUT, true); + std::vector lut_output_ports = circuit_lib.model_ports_by_type(lut_model, CIRCUIT_MODEL_PORT_OUTPUT, false); /* Classify SRAM ports into two categories: regular (not for mode select) and mode-select */ std::vector lut_regular_sram_ports = find_circuit_regular_sram_ports(circuit_lib, lut_model); diff --git a/openfpga/src/fabric/build_mux_modules.cpp b/openfpga/src/fabric/build_mux_modules.cpp index 41cb68118..a28eac264 100644 --- a/openfpga/src/fabric/build_mux_modules.cpp +++ b/openfpga/src/fabric/build_mux_modules.cpp @@ -849,7 +849,7 @@ vtr::vector build_mux_module_output_buffers(ModuleMana vtr::vector mux_output_nets(mux_graph.num_outputs(), ModuleNetId::INVALID()); /* Get the output ports from the mux */ - std::vector mux_output_ports = circuit_lib.model_ports_by_type(mux_model, CIRCUIT_MODEL_PORT_OUTPUT, true); + std::vector mux_output_ports = circuit_lib.model_ports_by_type(mux_model, CIRCUIT_MODEL_PORT_OUTPUT, false); /* Iterate over all the outputs in the MUX module */ for (const auto& output_port : mux_output_ports) { @@ -1098,7 +1098,7 @@ void build_cmos_mux_module(ModuleManager& module_manager, /* Get the input ports from the mux */ std::vector mux_input_ports = circuit_lib.model_ports_by_type(mux_model, CIRCUIT_MODEL_PORT_INPUT, true); /* Get the output ports from the mux */ - std::vector mux_output_ports = circuit_lib.model_ports_by_type(mux_model, CIRCUIT_MODEL_PORT_OUTPUT, true); + std::vector mux_output_ports = circuit_lib.model_ports_by_type(mux_model, CIRCUIT_MODEL_PORT_OUTPUT, false); /* Get the sram ports from the mux * Multiplexing structure does not mode_sram_ports, they are handled in LUT modules * Here we just bypass it. diff --git a/openfpga/src/mux_lib/mux_graph.cpp b/openfpga/src/mux_lib/mux_graph.cpp index 5b0f81828..d8066c055 100644 --- a/openfpga/src/mux_lib/mux_graph.cpp +++ b/openfpga/src/mux_lib/mux_graph.cpp @@ -1001,7 +1001,7 @@ void MuxGraph::build_onelevel_mux_graph(const size_t& mux_size, void MuxGraph::add_fracturable_outputs(const CircuitLibrary& circuit_lib, const CircuitModelId& circuit_model) { /* Iterate over output ports */ - for (const auto& port : circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_OUTPUT, true)) { + for (const auto& port : circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_OUTPUT, false)) { /* Get the fracturable_level */ size_t frac_level = circuit_lib.port_lut_frac_level(port); /* Bypass invalid frac_level */ From 341f38025ee12c86fec5f1557444f72db25ece8d Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 22 Apr 2020 14:42:30 -0600 Subject: [PATCH 469/645] add spypad to regression test --- .travis/script.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis/script.sh b/.travis/script.sh index fd9b8616e..231437e28 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -102,4 +102,7 @@ python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/flatten_routing -- echo -e "Testing Verilog generation with duplicated grid output pins"; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/duplicated_grid_pin --debug --show_thread_logs +echo -e "Testing Verilog generation with spy output pads"; +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/spypad --debug --show_thread_logs + end_section "OpenFPGA.TaskTun" From bf841b9a8e2b4711685709d251943a1b235db87d Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 22 Apr 2020 17:28:16 -0600 Subject: [PATCH 470/645] bug fixed in identifying wired LUT --- openfpga/src/utils/physical_pb_utils.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openfpga/src/utils/physical_pb_utils.cpp b/openfpga/src/utils/physical_pb_utils.cpp index cd1b13a4b..129784575 100644 --- a/openfpga/src/utils/physical_pb_utils.cpp +++ b/openfpga/src/utils/physical_pb_utils.cpp @@ -324,6 +324,12 @@ void rec_update_physical_pb_from_operating_pb(PhysicalPb& phy_pb, /* The following code is inspired by output_cluster.cpp */ bool is_used = false; t_pb_type* child_pb_type = &(mapped_mode->pb_type_children[ipb]); + + /* Bypass non-primitive pb_type, we care only the LUT pb_type */ + if (false == is_primitive_pb_type(child_pb_type)) { + continue; + } + int port_index = 0; t_pb_graph_node* child_pb_graph_node = &(pb_graph_node->child_pb_graph_nodes[op_pb->mode][ipb][jpb]); From 0c4904065f13b93d0d0959f5af510df7e494ea3c Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 22 Apr 2020 17:36:02 -0600 Subject: [PATCH 471/645] reduce activity error to warning. --- vpr/src/base/read_activity.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vpr/src/base/read_activity.cpp b/vpr/src/base/read_activity.cpp index 41cc86a21..821fc4672 100644 --- a/vpr/src/base/read_activity.cpp +++ b/vpr/src/base/read_activity.cpp @@ -19,8 +19,8 @@ static bool add_activity_to_net(const AtomNetlist& netlist, std::unordered_map Date: Wed, 22 Apr 2020 18:24:09 -0600 Subject: [PATCH 472/645] tweak mcnc scripts by stop VRP to remove buffers. Now passed mcnc big20 in Verilog/Bitstream generation --- .../mcnc_example_script.openfpga | 61 +++++++++++++++++++ .../mcnc_big20/config/task.conf | 47 +++++++------- 2 files changed, 86 insertions(+), 22 deletions(-) create mode 100644 openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga diff --git a/openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga new file mode 100644 index 000000000..cfe061298 --- /dev/null +++ b/openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga @@ -0,0 +1,61 @@ +# Run VPR for the 'and' design +#--write_rr_graph example_rr_graph.xml +vpr ${VPR_ARCH_FILE} ${VPR_TESTBENCH_BLIF} --clock_modeling route --absorb_buffer_luts off + +# Read OpenFPGA architecture definition +read_openfpga_arch -f ${OPENFPGA_ARCH_FILE} + +# Annotate the OpenFPGA architecture to VPR data base +# to debug use --verbose options +link_openfpga_arch --activity_file ${ACTIVITY_FILE} --sort_gsb_chan_node_in_edges + +# Check and correct any naming conflicts in the BLIF netlist +check_netlist_naming_conflict --fix --report ./netlist_renaming.xml + +# Apply fix-up to clustering nets based on routing results +pb_pin_fixup --verbose + +# Apply fix-up to Look-Up Table truth tables based on packing results +lut_truth_table_fixup + +# Build the module graph +# - Enabled compression on routing architecture modules +# - Enable pin duplication on grid modules +build_fabric --compress_routing #--verbose + +# Repack the netlist to physical pbs +# This must be done before bitstream generator and testbench generation +# Strongly recommend it is done after all the fix-up have been applied +repack #--verbose + +# Build the bitstream +# - Output the fabric-independent bitstream to a file +build_architecture_bitstream --verbose --file fabric_indepenent_bitstream.xml + +# Build fabric-dependent bitstream +build_fabric_bitstream --verbose + +# Write the Verilog netlist for FPGA fabric +# - Enable the use of explicit port mapping in Verilog netlist +write_fabric_verilog --file ./SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose + +# Write the Verilog testbench for FPGA fabric +# - We suggest the use of same output directory as fabric Verilog netlists +# - Must specify the reference benchmark file if you want to output any testbenches +# - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA +# - Enable pre-configured top-level testbench which is a fast verification skipping programming phase +# - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts +write_verilog_testbench --file ./SRC --reference_benchmark_file_path ${REFERENCE_VERILOG_TESTBENCH} --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini ./SimulationDeck/simulation_deck.ini + +# Write the SDC files for PnR backend +# - Turn on every options here +write_pnr_sdc --file ./SDC + +# Write the SDC to run timing analysis for a mapped FPGA fabric +write_analysis_sdc --file ./SDC_analysis + +# Finish and exit OpenFPGA +exit + +# Note : +# To run verification at the end of the flow maintain source in ./SRC directory diff --git a/openfpga_flow/tasks/openfpga_shell/mcnc_big20/config/task.conf b/openfpga_flow/tasks/openfpga_shell/mcnc_big20/config/task.conf index bab48c1fd..f2a92e6b9 100644 --- a/openfpga_flow/tasks/openfpga_shell/mcnc_big20/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/mcnc_big20/config/task.conf @@ -8,7 +8,7 @@ [GENERAL] run_engine=openfpga_shell -openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml power_analysis = true spice_output=false @@ -21,27 +21,30 @@ openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10 arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_depop50_40nm.xml [BENCHMARKS] -#bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/alu4/alu4.blif -#bench1=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex2/apex2.blif -#bench2=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex4/apex4.blif -## VPR remove dangling PIs and POs which are in act file. Then VPR errors out by saying these PI/PO should not exist in act file -##bench3=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/bigkey/bigkey.blif -#bench4=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/clma/clma.blif -#bench5=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/des/des.blif -#bench6=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/diffeq/diffeq.blif -#bench7=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/dsip/dsip.blif -#bench8=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/elliptic/elliptic.blif -#bench9=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/ex1010/ex1010.blif -#bench10=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/ex5p/ex5p.blif -#bench11=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/frisc/frisc.blif -#bench12=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/misex3/misex3.blif -#bench13=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/pdc/pdc.blif -bench14=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s298/s298.blif -#bench15=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s38417/s38417.blif -#bench16=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s38584/s38584.blif -#bench17=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/seq/seq.blif -#bench18=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/spla/spla.blif -#bench19=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/tseng/tseng.blif +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/alu4/alu4.blif +bench1=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex2/apex2.blif +bench2=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex4/apex4.blif +# VPR remove buffers which are in act file and create a new net. Then VPR errors out by saying the new net does not exist in act file +bench3=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/bigkey/bigkey.blif +# VPR remove buffers which are in act file and create a new net. Then VPR errors out by saying the new net does not exist in act file +bench4=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/clma/clma.blif +bench5=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/des/des.blif +bench6=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/diffeq/diffeq.blif +# VPR remove buffers which are in act file and create a new net. Then VPR errors out by saying the new net does not exist in act file +bench7=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/dsip/dsip.blif +bench8=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/elliptic/elliptic.blif +bench9=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/ex1010/ex1010.blif +bench10=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/ex5p/ex5p.blif +bench11=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/frisc/frisc.blif +bench12=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/misex3/misex3.blif +bench13=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/pdc/pdc.blif +# Passed +#bench14=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s298/s298.blif +bench15=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s38417/s38417.blif +bench16=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s38584/s38584.blif +bench17=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/seq/seq.blif +bench18=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/spla/spla.blif +bench19=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/tseng/tseng.blif [SYNTHESIS_PARAM] # Benchmark alu4 From df85175765159dc0f09def946bb7243e0de2e08a Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 22 Apr 2020 21:44:52 -0600 Subject: [PATCH 473/645] fine tuning on mcnc example script so that we can run run_modelsim.py --runsim --- openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga index cfe061298..66b4c12fa 100644 --- a/openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga @@ -45,7 +45,7 @@ write_fabric_verilog --file ./SRC --explicit_port_mapping --include_timing --inc # - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA # - Enable pre-configured top-level testbench which is a fast verification skipping programming phase # - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts -write_verilog_testbench --file ./SRC --reference_benchmark_file_path ${REFERENCE_VERILOG_TESTBENCH} --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini ./SimulationDeck/simulation_deck.ini +write_verilog_testbench --file ./SRC --reference_benchmark_file_path ${REFERENCE_VERILOG_TESTBENCH} --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini ./simulation_deck_info.ini # Write the SDC files for PnR backend # - Turn on every options here From 417d534121197ca93b05d21a421062bf88855aac Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 23 Apr 2020 16:15:45 -0600 Subject: [PATCH 474/645] fine tune mcnc example script to run Modelsim simulations easily --- .../OpenFPGAShellScripts/mcnc_example_script.openfpga | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga index 66b4c12fa..9a4f577f8 100644 --- a/openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga @@ -37,7 +37,11 @@ build_fabric_bitstream --verbose # Write the Verilog netlist for FPGA fabric # - Enable the use of explicit port mapping in Verilog netlist -write_fabric_verilog --file ./SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose +write_fabric_verilog --file ./SRC \ + --explicit_port_mapping \ + --include_timing \ + --include_signal_init + #--support_icarus_simulator # Write the Verilog testbench for FPGA fabric # - We suggest the use of same output directory as fabric Verilog netlists From 90f608baeaac8420bc091aba9d1e763ea255e357 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 23 Apr 2020 18:58:39 -0600 Subject: [PATCH 475/645] changing task mcnc file for debugging (temporarily now) Will be corrected later --- .../mcnc_big20/config/task.conf | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/openfpga_flow/tasks/openfpga_shell/mcnc_big20/config/task.conf b/openfpga_flow/tasks/openfpga_shell/mcnc_big20/config/task.conf index f2a92e6b9..5028283fb 100644 --- a/openfpga_flow/tasks/openfpga_shell/mcnc_big20/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/mcnc_big20/config/task.conf @@ -21,30 +21,30 @@ openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10 arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_depop50_40nm.xml [BENCHMARKS] -bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/alu4/alu4.blif -bench1=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex2/apex2.blif -bench2=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex4/apex4.blif -# VPR remove buffers which are in act file and create a new net. Then VPR errors out by saying the new net does not exist in act file -bench3=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/bigkey/bigkey.blif -# VPR remove buffers which are in act file and create a new net. Then VPR errors out by saying the new net does not exist in act file -bench4=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/clma/clma.blif -bench5=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/des/des.blif -bench6=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/diffeq/diffeq.blif -# VPR remove buffers which are in act file and create a new net. Then VPR errors out by saying the new net does not exist in act file -bench7=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/dsip/dsip.blif -bench8=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/elliptic/elliptic.blif -bench9=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/ex1010/ex1010.blif -bench10=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/ex5p/ex5p.blif -bench11=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/frisc/frisc.blif -bench12=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/misex3/misex3.blif -bench13=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/pdc/pdc.blif -# Passed -#bench14=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s298/s298.blif -bench15=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s38417/s38417.blif -bench16=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s38584/s38584.blif -bench17=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/seq/seq.blif -bench18=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/spla/spla.blif -bench19=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/tseng/tseng.blif +#bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/alu4/alu4.blif +#bench1=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex2/apex2.blif +#bench2=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex4/apex4.blif +## VPR remove buffers which are in act file and create a new net. Then VPR errors out by saying the new net does not exist in act file +#bench3=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/bigkey/bigkey.blif +## VPR remove buffers which are in act file and create a new net. Then VPR errors out by saying the new net does not exist in act file +#bench4=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/clma/clma.blif +#bench5=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/des/des.blif +#bench6=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/diffeq/diffeq.blif +## VPR remove buffers which are in act file and create a new net. Then VPR errors out by saying the new net does not exist in act file +#bench7=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/dsip/dsip.blif +#bench8=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/elliptic/elliptic.blif +#bench9=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/ex1010/ex1010.blif +#bench10=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/ex5p/ex5p.blif +#bench11=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/frisc/frisc.blif +#bench12=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/misex3/misex3.blif +#bench13=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/pdc/pdc.blif +## Passed +bench14=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s298/s298.blif +#bench15=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s38417/s38417.blif +#bench16=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s38584/s38584.blif +#bench17=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/seq/seq.blif +#bench18=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/spla/spla.blif +#bench19=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/tseng/tseng.blif [SYNTHESIS_PARAM] # Benchmark alu4 From 87b17fc25f2944012d85e1d0da18cfd54488e056 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 23 Apr 2020 18:59:09 -0600 Subject: [PATCH 476/645] add netlist manager data structure --- openfpga/src/base/netlist_manager.cpp | 201 ++++++++++++++++++++++++ openfpga/src/base/netlist_manager.h | 107 +++++++++++++ openfpga/src/base/netlist_manager_fwd.h | 25 +++ 3 files changed, 333 insertions(+) create mode 100644 openfpga/src/base/netlist_manager.cpp create mode 100644 openfpga/src/base/netlist_manager.h create mode 100644 openfpga/src/base/netlist_manager_fwd.h diff --git a/openfpga/src/base/netlist_manager.cpp b/openfpga/src/base/netlist_manager.cpp new file mode 100644 index 000000000..b7860d14f --- /dev/null +++ b/openfpga/src/base/netlist_manager.cpp @@ -0,0 +1,201 @@ +/****************************************************************************** + * This files includes memeber functions for data structure NetlistManager + ******************************************************************************/ +#include + +#include "vtr_assert.h" +#include "netlist_manager.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/****************************************************************************** + * Public aggregators + ******************************************************************************/ +/* Find all the netlists */ +NetlistManager::netlist_range NetlistManager::netlists() const { + return vtr::make_range(netlist_ids_.begin(), netlist_ids_.end()); +} + +/* Find all the modules that are included in a netlist */ +std::vector NetlistManager::netlist_modules(const NetlistId& netlist) const { + VTR_ASSERT(true == valid_netlist_id(netlist)); + return included_module_ids_[netlist]; +} + + +/****************************************************************************** + * Public accessors + ******************************************************************************/ +/* Find the name of a netlist */ +std::string NetlistManager::netlist_name(const NetlistId& netlist) const { + VTR_ASSERT(true == valid_netlist_id(netlist)); + return netlist_names_[netlist]; +} + +/* Find a netlist by its name */ +NetlistId NetlistManager::find_netlist(const std::string& netlist_name) const { + if (name_id_map_.find(netlist_name) != name_id_map_.end()) { + /* Found, return the id */ + return name_id_map_.at(netlist_name); + } + /* Not found, return an invalid id */ + return NetlistId::INVALID(); +} + +NetlistManager::e_netlist_type NetlistManager::netlist_type(const NetlistId& netlist) const { + VTR_ASSERT(true == valid_netlist_id(netlist)); + return netlist_types_[netlist]; +} + +/* Find if a module belongs to a netlist */ +bool NetlistManager::is_module_in_netlist(const NetlistId& netlist, const ModuleId& module) const { + VTR_ASSERT(true == valid_netlist_id(netlist)); + + for (const ModuleId& included_module : included_module_ids_[netlist]) { + /* Already in the netlist, return true */ + if (module == included_module) { + return true; + } + } + + /* Not in the netlist, return false */ + return false; +} + +/* Find the netlist that a module belongs to */ +NetlistId NetlistManager::find_module_netlist(const ModuleId& module) const { + /* Find if the module has been added to a netlist. If used, return false! */ + /* Not found, return an invalid value */ + if ( module_netlist_map_.end() + != module_netlist_map_.find(module)) { + return NetlistId::INVALID(); + } + return module_netlist_map_.at(module); +} + + +/* Find all the preprocessing flags that are included in a netlist */ +std::vector NetlistManager::netlist_preprocessing_flags(const NetlistId& netlist) const { + VTR_ASSERT(true == valid_netlist_id(netlist)); + + std::vector flags; + + for (const PreprocessingFlagId& flag_id : included_preprocessing_flag_ids_[netlist]) { + VTR_ASSERT(true == valid_preprocessing_flag_id(flag_id)); + flags.push_back(preprocessing_flag_names_[flag_id]); + } + + return flags; +} + +/****************************************************************************** + * Public mutators + ******************************************************************************/ +/* Add a netlist to the library */ +NetlistId NetlistManager::add_netlist(const std::string& name) { + /* Find if the name has been used. If used, return an invalid Id! */ + std::map::iterator it = name_id_map_.find(name); + if (it != name_id_map_.end()) { + return NetlistId::INVALID(); + } + + /* Create a new id */ + NetlistId netlist = NetlistId(netlist_ids_.size()); + netlist_ids_.push_back(netlist); + + /* Allocate related attributes */ + netlist_names_.push_back(name); + netlist_types_.push_back(NUM_NETLIST_TYPES); + included_module_ids_.emplace_back(); + included_preprocessing_flag_ids_.emplace_back(); + + /* Register in the name-to-id map */ + name_id_map_[name] = netlist; + + return netlist; +} + +void NetlistManager::set_netlist_type(const NetlistId& netlist, + const e_netlist_type& type) { + VTR_ASSERT(true == valid_netlist_id(netlist)); + netlist_types_[netlist] = type; +} + +/* Add a module to a netlist in the library */ +bool NetlistManager::add_netlist_module(const NetlistId& netlist, const ModuleId& module) { + VTR_ASSERT(true == valid_netlist_id(netlist)); + + /* Find if the module already in the netlist */ + std::vector::iterator module_it = std::find(included_module_ids_[netlist].begin(), included_module_ids_[netlist].end(), module); + if (module_it != included_module_ids_[netlist].end()) { + /* Already in the netlist, nothing to do */ + return true; + } + /* Try to register it in module-to-netlist map */ + /* Find if the module has been added to a netlist. If used, return false! */ + std::map::iterator map_it = module_netlist_map_.find(module); + if (map_it != module_netlist_map_.end()) { + return false; + } + + /* Does not exist! Should add it to the list */ + included_module_ids_[netlist].push_back(module); + /* Register it in module-to-netlist map */ + module_netlist_map_[module] = netlist; + return true; +} + +/* Add a pre-processing flag to a netlist */ +void NetlistManager::add_netlist_preprocessing_flag(const NetlistId& netlist, const std::string& preprocessing_flag) { + VTR_ASSERT(true == valid_netlist_id(netlist)); + + PreprocessingFlagId flag = PreprocessingFlagId(preprocessing_flag_ids_.size()); + + /* Find if the module already in the netlist */ + for (const PreprocessingFlagId& id : preprocessing_flag_ids_) { + if (0 != preprocessing_flag.compare(preprocessing_flag_names_[id])) { + continue; + } + /* Already in the list of pre-processing flags, push it ot the */ + flag = id; + break; + } + + /* Update the list if we need */ + if (flag == PreprocessingFlagId(preprocessing_flag_ids_.size())) { + preprocessing_flag_ids_.push_back(flag); + preprocessing_flag_names_.push_back(preprocessing_flag); + } + + /* Check if the flag is already in the netlist */ + std::vector::iterator it = std::find(included_preprocessing_flag_ids_[netlist].begin(), included_preprocessing_flag_ids_[netlist].end(), flag); + if (it == included_preprocessing_flag_ids_[netlist].end()) { + /* Not in the list, we add it */ + included_preprocessing_flag_ids_[netlist].push_back(flag); + } +} + +/****************************************************************************** + * Public validators/invalidators + ******************************************************************************/ +bool NetlistManager::valid_netlist_id(const NetlistId& netlist) const { + return (size_t(netlist) < netlist_ids_.size()) && (netlist == netlist_ids_[netlist]); +} + +/****************************************************************************** + * Private validators/invalidators + ******************************************************************************/ +bool NetlistManager::valid_preprocessing_flag_id(const PreprocessingFlagId& flag) const { + return (size_t(flag) < preprocessing_flag_ids_.size()) && (flag == preprocessing_flag_ids_[flag]); +} + +void NetlistManager::invalidate_name2id_map() { + name_id_map_.clear(); +} + +void NetlistManager::invalidate_module2netlist_map() { + module_netlist_map_.clear(); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/base/netlist_manager.h b/openfpga/src/base/netlist_manager.h new file mode 100644 index 000000000..fad76d25a --- /dev/null +++ b/openfpga/src/base/netlist_manager.h @@ -0,0 +1,107 @@ +/****************************************************************************** + * This files includes data structures for netlist management. + * It keeps a list of netlists that have been created + * Each netlist includes a list of ids of modules that are stored in ModuleManager + * + * When we want to dump out a netlist in Verilog/SPICE format, + * the netlist manager can generate the dependency on other netlists + * This can help us tracking the dependency and generate `include` files easily + * + * Cross-reference: + * + * +---------+ +---------+ + * | | ModuleId | | + * | Netlist |-------------->| Module | + * | Manager | | Manager | + * | | | | + * +---------+ +---------+ + * + ******************************************************************************/ +#ifndef NETLIST_MANAGER_H +#define NETLIST_MANAGER_H + +#include +#include +#include +#include "vtr_vector.h" +#include "netlist_manager_fwd.h" +#include "module_manager.h" + +/* begin namespace openfpga */ +namespace openfpga { + +class NetlistManager { + public: /* Internal Types */ + /* Type of netlists */ + enum e_netlist_type { + SUBMODULE_NETLIST, + LOGIC_BLOCK_NETLIST, + ROUTING_MODULE_NETLIST, + TOP_MODULE_NETLIST, + TESTBENCH_NETLIST, + NUM_NETLIST_TYPES + }; + public: /* Types and ranges */ + typedef vtr::vector::const_iterator netlist_iterator; + typedef vtr::Range netlist_range; + + public: /* Public aggregators */ + /* Find all the netlists */ + netlist_range netlists() const; + /* Find all the modules that are included in a netlist */ + std::vector netlist_modules(const NetlistId& netlist) const; + /* Find all the preprocessing flags that are included in a netlist */ + std::vector netlist_preprocessing_flags(const NetlistId& netlist) const; + + public: /* Public accessors */ + /* Find the name of a netlist */ + std::string netlist_name(const NetlistId& netlist) const; + /* Find a netlist by its name */ + NetlistId find_netlist(const std::string& netlist_name) const; + /* Get the type of a netlist */ + e_netlist_type netlist_type(const NetlistId& netlist) const; + /* Find if a module belongs to a netlist */ + bool is_module_in_netlist(const NetlistId& netlist, const ModuleId& module) const; + /* Find the netlist that a module belongs to */ + NetlistId find_module_netlist(const ModuleId& module) const; + + public: /* Public mutators */ + /* Add a netlist to the library */ + NetlistId add_netlist(const std::string& name); + /* Set a netlist type */ + void set_netlist_type(const NetlistId& netlist, + const e_netlist_type& type); + /* Add a module to a netlist in the library */ + bool add_netlist_module(const NetlistId& netlist, const ModuleId& module); + /* Add a pre-processing flag to a netlist */ + void add_netlist_preprocessing_flag(const NetlistId& netlist, const std::string& preprocessing_flag); + + public: /* Public validators/invalidators */ + bool valid_netlist_id(const NetlistId& netlist) const; + + private: /* Private validators/invalidators */ + bool valid_preprocessing_flag_id(const PreprocessingFlagId& flag) const; + void invalidate_name2id_map(); + void invalidate_module2netlist_map(); + + private: /* Internal data */ + vtr::vector netlist_ids_; + vtr::vector netlist_names_; + vtr::vector netlist_types_; + + vtr::vector> included_module_ids_; + vtr::vector> included_preprocessing_flag_ids_; + + vtr::vector preprocessing_flag_ids_; + vtr::vector preprocessing_flag_names_; + + /* fast look-up for netlist */ + std::map name_id_map_; + /* fast look-up for modules in netlists */ + std::map module_netlist_map_; +}; + +} /* end namespace openfpga */ + +#endif + diff --git a/openfpga/src/base/netlist_manager_fwd.h b/openfpga/src/base/netlist_manager_fwd.h new file mode 100644 index 000000000..186e1792c --- /dev/null +++ b/openfpga/src/base/netlist_manager_fwd.h @@ -0,0 +1,25 @@ +/************************************************** + * This file includes only declarations for + * the data structures for netlist managers + * Please refer to netlist_manager.h for more details + *************************************************/ +#ifndef NETLIST_MANAGER_FWD_H +#define NETLIST_MANAGER_FWD_H + +#include "vtr_strong_id.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/* Strong Ids for ModuleManager */ +struct netlist_id_tag; +struct preprocessing_flag_id_tag; + +typedef vtr::StrongId NetlistId; +typedef vtr::StrongId PreprocessingFlagId; + +class NetlistManager; + +} /* end namespace openfpga */ + +#endif From e811f8bb211c9d7c7420b411df1120942738a6db Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 23 Apr 2020 20:42:11 -0600 Subject: [PATCH 477/645] plug in netlist manager and now the include_netlist appears in one unique file --- openfpga/src/base/netlist_manager.cpp | 11 +++++ openfpga/src/base/netlist_manager.h | 2 + openfpga/src/base/openfpga_context.h | 8 +++ openfpga/src/base/openfpga_verilog.cpp | 4 +- openfpga/src/fpga_verilog/verilog_api.cpp | 22 ++++++--- openfpga/src/fpga_verilog/verilog_api.h | 5 +- .../verilog_auxiliary_netlists.cpp | 24 +++++++-- .../fpga_verilog/verilog_auxiliary_netlists.h | 4 +- .../src/fpga_verilog/verilog_decoders.cpp | 6 ++- openfpga/src/fpga_verilog/verilog_decoders.h | 3 +- .../fpga_verilog/verilog_essential_gates.cpp | 6 ++- .../fpga_verilog/verilog_essential_gates.h | 4 +- openfpga/src/fpga_verilog/verilog_grid.cpp | 30 ++++++++---- openfpga/src/fpga_verilog/verilog_grid.h | 4 +- openfpga/src/fpga_verilog/verilog_lut.cpp | 6 ++- openfpga/src/fpga_verilog/verilog_lut.h | 3 +- openfpga/src/fpga_verilog/verilog_memory.cpp | 6 ++- openfpga/src/fpga_verilog/verilog_memory.h | 3 +- openfpga/src/fpga_verilog/verilog_mux.cpp | 6 ++- openfpga/src/fpga_verilog/verilog_mux.h | 3 +- openfpga/src/fpga_verilog/verilog_routing.cpp | 49 ++++++++++++------- openfpga/src/fpga_verilog/verilog_routing.h | 7 ++- .../src/fpga_verilog/verilog_submodule.cpp | 21 ++++---- openfpga/src/fpga_verilog/verilog_submodule.h | 2 + .../src/fpga_verilog/verilog_top_module.cpp | 8 ++- .../src/fpga_verilog/verilog_top_module.h | 4 +- openfpga/src/fpga_verilog/verilog_wire.cpp | 6 ++- openfpga/src/fpga_verilog/verilog_wire.h | 3 +- 28 files changed, 184 insertions(+), 76 deletions(-) diff --git a/openfpga/src/base/netlist_manager.cpp b/openfpga/src/base/netlist_manager.cpp index b7860d14f..0b2e6ec45 100644 --- a/openfpga/src/base/netlist_manager.cpp +++ b/openfpga/src/base/netlist_manager.cpp @@ -74,6 +74,17 @@ NetlistId NetlistManager::find_module_netlist(const ModuleId& module) const { return module_netlist_map_.at(module); } +std::vector NetlistManager::netlists_by_type(const NetlistManager::e_netlist_type& netlist_type) const { + std::vector nlists; + + for (const NetlistId& nlist_id : netlist_ids_) { + if (netlist_type == netlist_types_[nlist_id]) { + nlists.push_back(nlist_id); + } + } + + return nlists; +} /* Find all the preprocessing flags that are included in a netlist */ std::vector NetlistManager::netlist_preprocessing_flags(const NetlistId& netlist) const { diff --git a/openfpga/src/base/netlist_manager.h b/openfpga/src/base/netlist_manager.h index fad76d25a..df2283e77 100644 --- a/openfpga/src/base/netlist_manager.h +++ b/openfpga/src/base/netlist_manager.h @@ -58,6 +58,8 @@ class NetlistManager { std::string netlist_name(const NetlistId& netlist) const; /* Find a netlist by its name */ NetlistId find_netlist(const std::string& netlist_name) const; + /* Find all the netlist in a given type */ + std::vector netlists_by_type(const e_netlist_type& netlist_type) const; /* Get the type of a netlist */ e_netlist_type netlist_type(const NetlistId& netlist) const; /* Find if a module belongs to a netlist */ diff --git a/openfpga/src/base/openfpga_context.h b/openfpga/src/base/openfpga_context.h index d4320915f..2ea114410 100644 --- a/openfpga/src/base/openfpga_context.h +++ b/openfpga/src/base/openfpga_context.h @@ -12,6 +12,7 @@ #include "mux_library.h" #include "tile_direct.h" #include "module_manager.h" +#include "netlist_manager.h" #include "openfpga_flow_manager.h" #include "bitstream_manager.h" #include "device_rr_gsb.h" @@ -61,6 +62,7 @@ class OpenfpgaContext : public Context { const std::vector& fabric_bitstream() const { return fabric_bitstream_; } const openfpga::IoLocationMap& io_location_map() const { return io_location_map_; } const std::unordered_map& net_activity() const { return net_activity_; } + const openfpga::NetlistManager& verilog_netlists() const { return verilog_netlists_; } public: /* Public mutators */ openfpga::Arch& mutable_arch() { return arch_; } openfpga::VprDeviceAnnotation& mutable_vpr_device_annotation() { return vpr_device_annotation_; } @@ -77,6 +79,7 @@ class OpenfpgaContext : public Context { std::vector& mutable_fabric_bitstream() { return fabric_bitstream_; } openfpga::IoLocationMap& mutable_io_location_map() { return io_location_map_; } std::unordered_map& mutable_net_activity() { return net_activity_; } + openfpga::NetlistManager& mutable_verilog_netlists() { return verilog_netlists_; } private: /* Internal data */ /* Data structure to store information from read_openfpga_arch library */ openfpga::Arch arch_; @@ -113,6 +116,11 @@ class OpenfpgaContext : public Context { openfpga::BitstreamManager bitstream_manager_; std::vector fabric_bitstream_; + /* Netlist database + * TODO: Each format should have an independent entry + */ + openfpga::NetlistManager verilog_netlists_; + /* Net activities of users' implementation */ std::unordered_map net_activity_; diff --git a/openfpga/src/base/openfpga_verilog.cpp b/openfpga/src/base/openfpga_verilog.cpp index c79b68fc2..9a877c9f2 100644 --- a/openfpga/src/base/openfpga_verilog.cpp +++ b/openfpga/src/base/openfpga_verilog.cpp @@ -45,6 +45,7 @@ int write_fabric_verilog(OpenfpgaContext& openfpga_ctx, options.set_compress_routing(openfpga_ctx.flow_manager().compress_routing()); fpga_fabric_verilog(openfpga_ctx.mutable_module_graph(), + openfpga_ctx.mutable_verilog_netlists(), openfpga_ctx.arch().circuit_lib, openfpga_ctx.mux_lib(), g_vpr_ctx.device(), @@ -82,7 +83,8 @@ int write_verilog_testbench(OpenfpgaContext& openfpga_ctx, options.set_print_simulation_ini(cmd_context.option_value(cmd, opt_print_simulation_ini)); options.set_verbose_output(cmd_context.option_enable(cmd, opt_verbose)); - fpga_verilog_testbench(openfpga_ctx.module_graph(), + fpga_verilog_testbench(openfpga_ctx.verilog_netlists(), + openfpga_ctx.module_graph(), openfpga_ctx.bitstream_manager(), openfpga_ctx.fabric_bitstream(), g_vpr_ctx.atom(), diff --git a/openfpga/src/fpga_verilog/verilog_api.cpp b/openfpga/src/fpga_verilog/verilog_api.cpp index c37b68888..d2823df48 100644 --- a/openfpga/src/fpga_verilog/verilog_api.cpp +++ b/openfpga/src/fpga_verilog/verilog_api.cpp @@ -51,6 +51,7 @@ namespace openfpga { * We should think clearly about how to handle them for both Verilog and SPICE generators! ********************************************************************/ void fpga_fabric_verilog(ModuleManager& module_manager, + NetlistManager& netlist_manager, const CircuitLibrary& circuit_lib, const MuxLibrary& mux_lib, const DeviceContext& device_ctx, @@ -88,33 +89,38 @@ void fpga_fabric_verilog(ModuleManager& module_manager, * the module manager. * Without the modules in the module manager, core logic generation is not possible!!! */ - print_verilog_submodule(module_manager, mux_lib, circuit_lib, + print_verilog_submodule(module_manager, netlist_manager, + mux_lib, circuit_lib, src_dir_path, submodule_dir_path, options); /* Generate routing blocks */ if (true == options.compress_routing()) { - print_verilog_unique_routing_modules(const_cast(module_manager), + print_verilog_unique_routing_modules(netlist_manager, + const_cast(module_manager), device_rr_gsb, src_dir_path, rr_dir_path, options.explicit_port_mapping()); } else { VTR_ASSERT(false == options.compress_routing()); - print_verilog_flatten_routing_modules(const_cast(module_manager), + print_verilog_flatten_routing_modules(netlist_manager, + const_cast(module_manager), device_rr_gsb, src_dir_path, rr_dir_path, options.explicit_port_mapping()); } /* Generate grids */ - print_verilog_grids(const_cast(module_manager), + print_verilog_grids(netlist_manager, + const_cast(module_manager), device_ctx, device_annotation, src_dir_path, lb_dir_path, options.explicit_port_mapping(), options.verbose_output()); /* Generate FPGA fabric */ - print_verilog_top_module(const_cast(module_manager), + print_verilog_top_module(netlist_manager, + const_cast(module_manager), src_dir_path, options.explicit_port_mapping()); @@ -134,7 +140,8 @@ void fpga_fabric_verilog(ModuleManager& module_manager, * This testbench is created for quick verification and formal verification purpose. * - Verilog netlist including preprocessing flags and all the Verilog netlists that have been generated ********************************************************************/ -void fpga_verilog_testbench(const ModuleManager& module_manager, +void fpga_verilog_testbench(const NetlistManager& netlist_manager, + const ModuleManager& module_manager, const BitstreamManager& bitstream_manager, const std::vector& fabric_bitstream, const AtomContext& atom_ctx, @@ -219,7 +226,8 @@ void fpga_verilog_testbench(const ModuleManager& module_manager, } /* Generate a Verilog file including all the netlists that have been generated */ - print_include_netlists(src_dir_path, + print_include_netlists(netlist_manager, + src_dir_path, netlist_name, options.reference_benchmark_file_path(), circuit_lib); diff --git a/openfpga/src/fpga_verilog/verilog_api.h b/openfpga/src/fpga_verilog/verilog_api.h index 66eb069ba..c9d194dff 100644 --- a/openfpga/src/fpga_verilog/verilog_api.h +++ b/openfpga/src/fpga_verilog/verilog_api.h @@ -12,6 +12,7 @@ #include "vpr_context.h" #include "vpr_device_annotation.h" #include "device_rr_gsb.h" +#include "netlist_manager.h" #include "module_manager.h" #include "bitstream_manager.h" #include "simulation_setting.h" @@ -28,6 +29,7 @@ namespace openfpga { void fpga_fabric_verilog(ModuleManager& module_manager, + NetlistManager& netlist_manager, const CircuitLibrary& circuit_lib, const MuxLibrary& mux_lib, const DeviceContext& device_ctx, @@ -35,7 +37,8 @@ void fpga_fabric_verilog(ModuleManager& module_manager, const DeviceRRGSB& device_rr_gsb, const FabricVerilogOption& options); -void fpga_verilog_testbench(const ModuleManager& module_manager, +void fpga_verilog_testbench(const NetlistManager& netlist_manager, + const ModuleManager& module_manager, const BitstreamManager& bitstream_manager, const std::vector& fabric_bitstream, const AtomContext& atom_ctx, diff --git a/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.cpp b/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.cpp index fd01a462f..ba2483ca7 100644 --- a/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.cpp +++ b/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.cpp @@ -29,7 +29,8 @@ namespace openfpga { * and user-defined. * Some netlists are open to compile under specific preprocessing flags *******************************************************************/ -void print_include_netlists(const std::string& src_dir, +void print_include_netlists(const NetlistManager& netlist_manager, + const std::string& src_dir, const std::string& circuit_name, const std::string& reference_benchmark_file, const CircuitLibrary& circuit_lib) { @@ -55,24 +56,37 @@ void print_include_netlists(const std::string& src_dir, fp << std::endl; /* Include all the user-defined netlists */ + print_verilog_comment(fp, std::string("------ Include user-defined netlists -----")); for (const std::string& user_defined_netlist : find_circuit_library_unique_verilog_netlists(circuit_lib)) { print_verilog_include_netlist(fp, user_defined_netlist); } /* Include all the primitive modules */ - print_verilog_include_netlist(fp, src_dir + std::string(DEFAULT_SUBMODULE_DIR_NAME) + std::string(SUBMODULE_VERILOG_FILE_NAME)); + print_verilog_comment(fp, std::string("------ Include primitive module netlists -----")); + for (const NetlistId& nlist_id : netlist_manager.netlists_by_type(NetlistManager::SUBMODULE_NETLIST)) { + print_verilog_include_netlist(fp, netlist_manager.netlist_name(nlist_id)); + } fp << std::endl; /* Include all the CLB, heterogeneous block modules */ - print_verilog_include_netlist(fp, src_dir + std::string(DEFAULT_LB_DIR_NAME) + std::string(LOGIC_BLOCK_VERILOG_FILE_NAME)); + print_verilog_comment(fp, std::string("------ Include logic block netlists -----")); + for (const NetlistId& nlist_id : netlist_manager.netlists_by_type(NetlistManager::LOGIC_BLOCK_NETLIST)) { + print_verilog_include_netlist(fp, netlist_manager.netlist_name(nlist_id)); + } fp << std::endl; /* Include all the routing architecture modules */ - print_verilog_include_netlist(fp, src_dir + std::string(DEFAULT_RR_DIR_NAME) + std::string(ROUTING_VERILOG_FILE_NAME)); + print_verilog_comment(fp, std::string("------ Include routing module netlists -----")); + for (const NetlistId& nlist_id : netlist_manager.netlists_by_type(NetlistManager::ROUTING_MODULE_NETLIST)) { + print_verilog_include_netlist(fp, netlist_manager.netlist_name(nlist_id)); + } fp << std::endl; /* Include FPGA top module */ - print_verilog_include_netlist(fp, src_dir + generate_fpga_top_netlist_name(std::string(VERILOG_NETLIST_FILE_POSTFIX))); + print_verilog_comment(fp, std::string("------ Include fabric top-level netlists -----")); + for (const NetlistId& nlist_id : netlist_manager.netlists_by_type(NetlistManager::TOP_MODULE_NETLIST)) { + print_verilog_include_netlist(fp, netlist_manager.netlist_name(nlist_id)); + } fp << std::endl; /* Include reference benchmark netlist only when auto-check flag is enabled */ diff --git a/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.h b/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.h index 291ca8915..ed1317862 100644 --- a/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.h +++ b/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.h @@ -7,6 +7,7 @@ #include #include "circuit_library.h" #include "fabric_verilog_options.h" +#include "netlist_manager.h" #include "verilog_testbench_options.h" /******************************************************************** @@ -16,7 +17,8 @@ /* begin namespace openfpga */ namespace openfpga { -void print_include_netlists(const std::string& src_dir, +void print_include_netlists(const NetlistManager& netlist_manager, + const std::string& src_dir, const std::string& circuit_name, const std::string& reference_benchmark_file, const CircuitLibrary& circuit_lib); diff --git a/openfpga/src/fpga_verilog/verilog_decoders.cpp b/openfpga/src/fpga_verilog/verilog_decoders.cpp index e9b9f6d02..0e993a3d3 100644 --- a/openfpga/src/fpga_verilog/verilog_decoders.cpp +++ b/openfpga/src/fpga_verilog/verilog_decoders.cpp @@ -162,7 +162,7 @@ void print_verilog_mux_local_decoder_module(std::fstream& fp, * See more details in the function print_verilog_mux_local_decoder() for more details ***************************************************************************************/ void print_verilog_submodule_mux_local_decoders(const ModuleManager& module_manager, - std::vector& netlist_names, + NetlistManager& netlist_manager, const MuxLibrary& mux_lib, const CircuitLibrary& circuit_lib, const std::string& verilog_dir, @@ -223,7 +223,9 @@ void print_verilog_submodule_mux_local_decoders(const ModuleManager& module_mana fp.close(); /* Add fname to the netlist name list */ - netlist_names.push_back(verilog_fname); + NetlistId nlist_id = netlist_manager.add_netlist(verilog_fname); + VTR_ASSERT(NetlistId::INVALID() != nlist_id); + netlist_manager.set_netlist_type(nlist_id, NetlistManager::SUBMODULE_NETLIST); VTR_LOG("Done\n"); } diff --git a/openfpga/src/fpga_verilog/verilog_decoders.h b/openfpga/src/fpga_verilog/verilog_decoders.h index 271e7aa5c..f8d559f4d 100644 --- a/openfpga/src/fpga_verilog/verilog_decoders.h +++ b/openfpga/src/fpga_verilog/verilog_decoders.h @@ -12,6 +12,7 @@ #include "mux_graph.h" #include "mux_library.h" #include "module_manager.h" +#include "netlist_manager.h" /******************************************************************** * Function declaration @@ -21,7 +22,7 @@ namespace openfpga { void print_verilog_submodule_mux_local_decoders(const ModuleManager& module_manager, - std::vector& netlist_names, + NetlistManager& netlist_manager, const MuxLibrary& mux_lib, const CircuitLibrary& circuit_lib, const std::string& verilog_dir, diff --git a/openfpga/src/fpga_verilog/verilog_essential_gates.cpp b/openfpga/src/fpga_verilog/verilog_essential_gates.cpp index a84d02bdf..20be3e7e4 100644 --- a/openfpga/src/fpga_verilog/verilog_essential_gates.cpp +++ b/openfpga/src/fpga_verilog/verilog_essential_gates.cpp @@ -526,7 +526,7 @@ void print_verilog_constant_generator_module(const ModuleManager& module_manager * etc. ***********************************************/ void print_verilog_submodule_essentials(const ModuleManager& module_manager, - std::vector& netlist_names, + NetlistManager& netlist_manager, const std::string& verilog_dir, const std::string& submodule_dir, const CircuitLibrary& circuit_lib) { @@ -577,7 +577,9 @@ void print_verilog_submodule_essentials(const ModuleManager& module_manager, fp.close(); /* Add fname to the netlist name list */ - netlist_names.push_back(verilog_fname); + NetlistId nlist_id = netlist_manager.add_netlist(verilog_fname); + VTR_ASSERT(NetlistId::INVALID() != nlist_id); + netlist_manager.set_netlist_type(nlist_id, NetlistManager::SUBMODULE_NETLIST); VTR_LOG("Done\n"); } diff --git a/openfpga/src/fpga_verilog/verilog_essential_gates.h b/openfpga/src/fpga_verilog/verilog_essential_gates.h index b7f9b519a..b267d0e6e 100644 --- a/openfpga/src/fpga_verilog/verilog_essential_gates.h +++ b/openfpga/src/fpga_verilog/verilog_essential_gates.h @@ -6,6 +6,8 @@ *******************************************************************/ #include #include "circuit_library.h" +#include "module_manager.h" +#include "netlist_manager.h" /******************************************************************** * Function declaration @@ -15,7 +17,7 @@ namespace openfpga { void print_verilog_submodule_essentials(const ModuleManager& module_manager, - std::vector& netlist_names, + NetlistManager& netlist_manager, const std::string& verilog_dir, const std::string& submodule_dir, const CircuitLibrary& circuit_lib); diff --git a/openfpga/src/fpga_verilog/verilog_grid.cpp b/openfpga/src/fpga_verilog/verilog_grid.cpp index 20cdd02f4..0cf509bde 100644 --- a/openfpga/src/fpga_verilog/verilog_grid.cpp +++ b/openfpga/src/fpga_verilog/verilog_grid.cpp @@ -194,8 +194,8 @@ void rec_print_verilog_logical_tile(std::fstream& fp, * for the logical tile (pb_graph/pb_type) *****************************************************************************/ static -void print_verilog_logical_tile_netlist(const ModuleManager& module_manager, - std::vector& netlist_names, +void print_verilog_logical_tile_netlist(NetlistManager& netlist_manager, + const ModuleManager& module_manager, const VprDeviceAnnotation& device_annotation, const std::string& verilog_dir, const std::string& subckt_dir, @@ -243,7 +243,9 @@ void print_verilog_logical_tile_netlist(const ModuleManager& module_manager, fp.close(); /* Add fname to the netlist name list */ - netlist_names.push_back(verilog_fname); + NetlistId nlist_id = netlist_manager.add_netlist(verilog_fname); + VTR_ASSERT(NetlistId::INVALID() != nlist_id); + netlist_manager.set_netlist_type(nlist_id, NetlistManager::LOGIC_BLOCK_NETLIST); VTR_LOG("Done\n"); VTR_LOG("\n"); @@ -258,8 +260,8 @@ void print_verilog_logical_tile_netlist(const ModuleManager& module_manager, * the I/O block locates at. *****************************************************************************/ static -void print_verilog_physical_tile_netlist(const ModuleManager& module_manager, - std::vector& netlist_names, +void print_verilog_physical_tile_netlist(NetlistManager& netlist_manager, + const ModuleManager& module_manager, const std::string& verilog_dir, const std::string& subckt_dir, t_physical_tile_type_ptr phy_block_type, @@ -319,7 +321,9 @@ void print_verilog_physical_tile_netlist(const ModuleManager& module_manager, fp.close(); /* Add fname to the netlist name list */ - netlist_names.push_back(verilog_fname); + NetlistId nlist_id = netlist_manager.add_netlist(verilog_fname); + VTR_ASSERT(NetlistId::INVALID() != nlist_id); + netlist_manager.set_netlist_type(nlist_id, NetlistManager::LOGIC_BLOCK_NETLIST); VTR_LOG("Done\n"); } @@ -330,7 +334,8 @@ void print_verilog_physical_tile_netlist(const ModuleManager& module_manager, * 2. Only one module for each CLB (FILL_TYPE) * 3. Only one module for each heterogeneous block ****************************************************************************/ -void print_verilog_grids(const ModuleManager& module_manager, +void print_verilog_grids(NetlistManager& netlist_manager, + const ModuleManager& module_manager, const DeviceContext& device_ctx, const VprDeviceAnnotation& device_annotation, const std::string& verilog_dir, @@ -354,7 +359,8 @@ void print_verilog_grids(const ModuleManager& module_manager, if (nullptr == logical_tile.pb_graph_head) { continue; } - print_verilog_logical_tile_netlist(module_manager, netlist_names, + print_verilog_logical_tile_netlist(netlist_manager, + module_manager, device_annotation, verilog_dir, subckt_dir, logical_tile.pb_graph_head, @@ -387,7 +393,8 @@ void print_verilog_grids(const ModuleManager& module_manager, std::set io_type_sides = find_physical_io_tile_located_sides(device_ctx.grid, &physical_tile); for (const e_side& io_type_side : io_type_sides) { - print_verilog_physical_tile_netlist(module_manager, netlist_names, + print_verilog_physical_tile_netlist(netlist_manager, + module_manager, verilog_dir, subckt_dir, &physical_tile, io_type_side, @@ -396,7 +403,8 @@ void print_verilog_grids(const ModuleManager& module_manager, continue; } else { /* For CLB and heterogenenous blocks */ - print_verilog_physical_tile_netlist(module_manager, netlist_names, + print_verilog_physical_tile_netlist(netlist_manager, + module_manager, verilog_dir, subckt_dir, &physical_tile, NUM_SIDES, @@ -408,6 +416,7 @@ void print_verilog_grids(const ModuleManager& module_manager, VTR_LOG("\n"); /* Output a header file for all the logic blocks */ + /* std::string grid_verilog_fname(LOGIC_BLOCK_VERILOG_FILE_NAME); VTR_LOG("Writing header file for grid Verilog modules '%s' ...", grid_verilog_fname.c_str()); @@ -415,6 +424,7 @@ void print_verilog_grids(const ModuleManager& module_manager, subckt_dir.c_str(), grid_verilog_fname.c_str()); VTR_LOG("Done\n"); + */ } } /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_grid.h b/openfpga/src/fpga_verilog/verilog_grid.h index da8ea09fa..6d2843628 100644 --- a/openfpga/src/fpga_verilog/verilog_grid.h +++ b/openfpga/src/fpga_verilog/verilog_grid.h @@ -7,6 +7,7 @@ #include #include "vpr_context.h" #include "module_manager.h" +#include "netlist_manager.h" #include "vpr_device_annotation.h" /******************************************************************** @@ -16,7 +17,8 @@ /* begin namespace openfpga */ namespace openfpga { -void print_verilog_grids(const ModuleManager& module_manager, +void print_verilog_grids(NetlistManager& netlist_manager, + const ModuleManager& module_manager, const DeviceContext& device_ctx, const VprDeviceAnnotation& device_annotation, const std::string& verilog_dir, diff --git a/openfpga/src/fpga_verilog/verilog_lut.cpp b/openfpga/src/fpga_verilog/verilog_lut.cpp index a849c5827..fabe9340e 100644 --- a/openfpga/src/fpga_verilog/verilog_lut.cpp +++ b/openfpga/src/fpga_verilog/verilog_lut.cpp @@ -30,7 +30,7 @@ namespace openfpga { * in the circuit library ********************************************************************/ void print_verilog_submodule_luts(const ModuleManager& module_manager, - std::vector& netlist_names, + NetlistManager& netlist_manager, const CircuitLibrary& circuit_lib, const std::string& verilog_dir, const std::string& submodule_dir, @@ -70,7 +70,9 @@ void print_verilog_submodule_luts(const ModuleManager& module_manager, fp.close(); /* Add fname to the netlist name list */ - netlist_names.push_back(verilog_fname); + NetlistId nlist_id = netlist_manager.add_netlist(verilog_fname); + VTR_ASSERT(NetlistId::INVALID() != nlist_id); + netlist_manager.set_netlist_type(nlist_id, NetlistManager::SUBMODULE_NETLIST); VTR_LOG("Done\n"); } diff --git a/openfpga/src/fpga_verilog/verilog_lut.h b/openfpga/src/fpga_verilog/verilog_lut.h index 7c8b85b79..e9ba3465a 100644 --- a/openfpga/src/fpga_verilog/verilog_lut.h +++ b/openfpga/src/fpga_verilog/verilog_lut.h @@ -9,6 +9,7 @@ #include "circuit_library.h" #include "module_manager.h" +#include "netlist_manager.h" /******************************************************************** * Function declaration @@ -18,7 +19,7 @@ namespace openfpga { void print_verilog_submodule_luts(const ModuleManager& module_manager, - std::vector& netlist_names, + NetlistManager& netlist_manager, const CircuitLibrary& circuit_lib, const std::string& verilog_dir, const std::string& submodule_dir, diff --git a/openfpga/src/fpga_verilog/verilog_memory.cpp b/openfpga/src/fpga_verilog/verilog_memory.cpp index de0418481..53420a794 100644 --- a/openfpga/src/fpga_verilog/verilog_memory.cpp +++ b/openfpga/src/fpga_verilog/verilog_memory.cpp @@ -97,7 +97,7 @@ void print_verilog_mux_memory_module(const ModuleManager& module_manager, * memory-bank organization for the memories. ********************************************************************/ void print_verilog_submodule_memories(const ModuleManager& module_manager, - std::vector& netlist_names, + NetlistManager& netlist_manager, const MuxLibrary& mux_lib, const CircuitLibrary& circuit_lib, const std::string& verilog_dir, @@ -187,7 +187,9 @@ void print_verilog_submodule_memories(const ModuleManager& module_manager, fp.close(); /* Add fname to the netlist name list */ - netlist_names.push_back(verilog_fname); + NetlistId nlist_id = netlist_manager.add_netlist(verilog_fname); + VTR_ASSERT(NetlistId::INVALID() != nlist_id); + netlist_manager.set_netlist_type(nlist_id, NetlistManager::SUBMODULE_NETLIST); VTR_LOG("Done\n"); } diff --git a/openfpga/src/fpga_verilog/verilog_memory.h b/openfpga/src/fpga_verilog/verilog_memory.h index 9d29eb15f..a331e5687 100644 --- a/openfpga/src/fpga_verilog/verilog_memory.h +++ b/openfpga/src/fpga_verilog/verilog_memory.h @@ -10,6 +10,7 @@ #include "mux_graph.h" #include "mux_library.h" #include "module_manager.h" +#include "netlist_manager.h" /******************************************************************** * Function declaration @@ -19,7 +20,7 @@ namespace openfpga { void print_verilog_submodule_memories(const ModuleManager& module_manager, - std::vector& netlist_names, + NetlistManager& netlist_manager, const MuxLibrary& mux_lib, const CircuitLibrary& circuit_lib, const std::string& verilog_dir, diff --git a/openfpga/src/fpga_verilog/verilog_mux.cpp b/openfpga/src/fpga_verilog/verilog_mux.cpp index 1e58e3fc8..c85978722 100644 --- a/openfpga/src/fpga_verilog/verilog_mux.cpp +++ b/openfpga/src/fpga_verilog/verilog_mux.cpp @@ -1224,7 +1224,7 @@ void generate_verilog_mux_module(ModuleManager& module_manager, * multiplexers in the FPGA device **********************************************/ void print_verilog_submodule_muxes(ModuleManager& module_manager, - std::vector& netlist_names, + NetlistManager& netlist_manager, const MuxLibrary& mux_lib, const CircuitLibrary& circuit_lib, const std::string& verilog_dir, @@ -1273,7 +1273,9 @@ void print_verilog_submodule_muxes(ModuleManager& module_manager, fp.close(); /* Add fname to the netlist name list */ - netlist_names.push_back(verilog_fname); + NetlistId nlist_id = netlist_manager.add_netlist(verilog_fname); + VTR_ASSERT(NetlistId::INVALID() != nlist_id); + netlist_manager.set_netlist_type(nlist_id, NetlistManager::SUBMODULE_NETLIST); VTR_LOG("Done\n"); } diff --git a/openfpga/src/fpga_verilog/verilog_mux.h b/openfpga/src/fpga_verilog/verilog_mux.h index 16e6c2f2a..c98fa77d0 100644 --- a/openfpga/src/fpga_verilog/verilog_mux.h +++ b/openfpga/src/fpga_verilog/verilog_mux.h @@ -11,6 +11,7 @@ #include "mux_graph.h" #include "mux_library.h" #include "module_manager.h" +#include "netlist_manager.h" /******************************************************************** * Function declaration @@ -20,7 +21,7 @@ namespace openfpga { void print_verilog_submodule_muxes(ModuleManager& module_manager, - std::vector& netlist_names, + NetlistManager& netlist_manager, const MuxLibrary& mux_lib, const CircuitLibrary& circuit_lib, const std::string& verilog_dir, diff --git a/openfpga/src/fpga_verilog/verilog_routing.cpp b/openfpga/src/fpga_verilog/verilog_routing.cpp index a3361fd06..8f74c0074 100644 --- a/openfpga/src/fpga_verilog/verilog_routing.cpp +++ b/openfpga/src/fpga_verilog/verilog_routing.cpp @@ -75,8 +75,8 @@ namespace openfpga { * ********************************************************************/ static -void print_verilog_routing_connection_box_unique_module(const ModuleManager& module_manager, - std::vector& netlist_names, +void print_verilog_routing_connection_box_unique_module(NetlistManager& netlist_manager, + const ModuleManager& module_manager, const std::string& verilog_dir, const std::string& subckt_dir, const RRGSB& rr_gsb, @@ -111,7 +111,9 @@ void print_verilog_routing_connection_box_unique_module(const ModuleManager& mod fp.close(); /* Add fname to the netlist name list */ - netlist_names.push_back(verilog_fname); + NetlistId nlist_id = netlist_manager.add_netlist(verilog_fname); + VTR_ASSERT(NetlistId::INVALID() != nlist_id); + netlist_manager.set_netlist_type(nlist_id, NetlistManager::ROUTING_MODULE_NETLIST); } /********************************************************************* @@ -178,8 +180,8 @@ void print_verilog_routing_connection_box_unique_module(const ModuleManager& mod * ********************************************************************/ static -void print_verilog_routing_switch_box_unique_module(const ModuleManager& module_manager, - std::vector& netlist_names, +void print_verilog_routing_switch_box_unique_module(NetlistManager& netlist_manager, + const ModuleManager& module_manager, const std::string& verilog_dir, const std::string& subckt_dir, const RRGSB& rr_gsb, @@ -210,7 +212,9 @@ void print_verilog_routing_switch_box_unique_module(const ModuleManager& module_ fp.close(); /* Add fname to the netlist name list */ - netlist_names.push_back(verilog_fname); + NetlistId nlist_id = netlist_manager.add_netlist(verilog_fname); + VTR_ASSERT(NetlistId::INVALID() != nlist_id); + netlist_manager.set_netlist_type(nlist_id, NetlistManager::ROUTING_MODULE_NETLIST); } /******************************************************************** @@ -218,8 +222,8 @@ void print_verilog_routing_switch_box_unique_module(const ModuleManager& module_ * and build a module for each of them *******************************************************************/ static -void print_verilog_flatten_connection_block_modules(const ModuleManager& module_manager, - std::vector& netlist_names, +void print_verilog_flatten_connection_block_modules(NetlistManager& netlist_manager, + const ModuleManager& module_manager, const DeviceRRGSB& device_rr_gsb, const std::string& verilog_dir, const std::string& subckt_dir, @@ -238,7 +242,8 @@ void print_verilog_flatten_connection_block_modules(const ModuleManager& module_ if (true != rr_gsb.is_cb_exist(cb_type)) { continue; } - print_verilog_routing_connection_box_unique_module(module_manager, netlist_names, + print_verilog_routing_connection_box_unique_module(netlist_manager, + module_manager, verilog_dir, subckt_dir, rr_gsb, cb_type, @@ -256,7 +261,8 @@ void print_verilog_flatten_connection_block_modules(const ModuleManager& module_ * 1. Connection blocks * 2. Switch blocks *******************************************************************/ -void print_verilog_flatten_routing_modules(const ModuleManager& module_manager, +void print_verilog_flatten_routing_modules(NetlistManager& netlist_manager, + const ModuleManager& module_manager, const DeviceRRGSB& device_rr_gsb, const std::string& verilog_dir, const std::string& subckt_dir, @@ -273,7 +279,8 @@ void print_verilog_flatten_routing_modules(const ModuleManager& module_manager, if (true != rr_gsb.is_sb_exist()) { continue; } - print_verilog_routing_switch_box_unique_module(module_manager, netlist_names, + print_verilog_routing_switch_box_unique_module(netlist_manager, + module_manager, verilog_dir, subckt_dir, rr_gsb, @@ -281,10 +288,11 @@ void print_verilog_flatten_routing_modules(const ModuleManager& module_manager, } } - print_verilog_flatten_connection_block_modules(module_manager, netlist_names, device_rr_gsb, verilog_dir, subckt_dir, CHANX, use_explicit_port_map); + print_verilog_flatten_connection_block_modules(netlist_manager, module_manager, device_rr_gsb, verilog_dir, subckt_dir, CHANX, use_explicit_port_map); - print_verilog_flatten_connection_block_modules(module_manager, netlist_names, device_rr_gsb, verilog_dir, subckt_dir, CHANY, use_explicit_port_map); + print_verilog_flatten_connection_block_modules(netlist_manager, module_manager, device_rr_gsb, verilog_dir, subckt_dir, CHANY, use_explicit_port_map); + /* VTR_LOG("Writing header file for routing submodules '%s'...", ROUTING_VERILOG_FILE_NAME); print_verilog_netlist_include_header_file(netlist_names, @@ -292,6 +300,7 @@ void print_verilog_flatten_routing_modules(const ModuleManager& module_manager, ROUTING_VERILOG_FILE_NAME); VTR_LOG("Done\n"); VTR_LOG("\n"); + */ } @@ -305,7 +314,8 @@ void print_verilog_flatten_routing_modules(const ModuleManager& module_manager, * Note: this function SHOULD be called only when * the option compact_routing_hierarchy is turned on!!! *******************************************************************/ -void print_verilog_unique_routing_modules(const ModuleManager& module_manager, +void print_verilog_unique_routing_modules(NetlistManager& netlist_manager, + const ModuleManager& module_manager, const DeviceRRGSB& device_rr_gsb, const std::string& verilog_dir, const std::string& subckt_dir, @@ -316,7 +326,8 @@ void print_verilog_unique_routing_modules(const ModuleManager& module_manager, /* Build unique switch block modules */ for (size_t isb = 0; isb < device_rr_gsb.get_num_sb_unique_module(); ++isb) { const RRGSB& unique_mirror = device_rr_gsb.get_sb_unique_module(isb); - print_verilog_routing_switch_box_unique_module(module_manager, netlist_names, + print_verilog_routing_switch_box_unique_module(netlist_manager, + module_manager, verilog_dir, subckt_dir, unique_mirror, @@ -327,7 +338,8 @@ void print_verilog_unique_routing_modules(const ModuleManager& module_manager, for (size_t icb = 0; icb < device_rr_gsb.get_num_cb_unique_module(CHANX); ++icb) { const RRGSB& unique_mirror = device_rr_gsb.get_cb_unique_module(CHANX, icb); - print_verilog_routing_connection_box_unique_module(module_manager, netlist_names, + print_verilog_routing_connection_box_unique_module(netlist_manager, + module_manager, verilog_dir, subckt_dir, unique_mirror, CHANX, @@ -338,19 +350,22 @@ void print_verilog_unique_routing_modules(const ModuleManager& module_manager, for (size_t icb = 0; icb < device_rr_gsb.get_num_cb_unique_module(CHANY); ++icb) { const RRGSB& unique_mirror = device_rr_gsb.get_cb_unique_module(CHANY, icb); - print_verilog_routing_connection_box_unique_module(module_manager, netlist_names, + print_verilog_routing_connection_box_unique_module(netlist_manager, + module_manager, verilog_dir, subckt_dir, unique_mirror, CHANY, use_explicit_port_map); } + /* VTR_LOG("Writing header file for routing submodules '%s'...", ROUTING_VERILOG_FILE_NAME); print_verilog_netlist_include_header_file(netlist_names, subckt_dir.c_str(), ROUTING_VERILOG_FILE_NAME); VTR_LOG("Done\n"); + */ VTR_LOG("\n"); } diff --git a/openfpga/src/fpga_verilog/verilog_routing.h b/openfpga/src/fpga_verilog/verilog_routing.h index 2a1a58a66..a9f8fd6a1 100644 --- a/openfpga/src/fpga_verilog/verilog_routing.h +++ b/openfpga/src/fpga_verilog/verilog_routing.h @@ -7,6 +7,7 @@ #include "mux_library.h" #include "module_manager.h" +#include "netlist_manager.h" #include "device_rr_gsb.h" /******************************************************************** @@ -16,13 +17,15 @@ /* begin namespace openfpga */ namespace openfpga { -void print_verilog_flatten_routing_modules(const ModuleManager& module_manager, +void print_verilog_flatten_routing_modules(NetlistManager& netlist_manager, + const ModuleManager& module_manager, const DeviceRRGSB& device_rr_gsb, const std::string& verilog_dir, const std::string& subckt_dir, const bool& use_explicit_port_map); -void print_verilog_unique_routing_modules(const ModuleManager& module_manager, +void print_verilog_unique_routing_modules(NetlistManager& netlist_manager, + const ModuleManager& module_manager, const DeviceRRGSB& device_rr_gsb, const std::string& verilog_dir, const std::string& subckt_dir, diff --git a/openfpga/src/fpga_verilog/verilog_submodule.cpp b/openfpga/src/fpga_verilog/verilog_submodule.cpp index 099118e5f..b38750ab9 100644 --- a/openfpga/src/fpga_verilog/verilog_submodule.cpp +++ b/openfpga/src/fpga_verilog/verilog_submodule.cpp @@ -32,6 +32,7 @@ namespace openfpga { * 6. Verilog template ********************************************************************/ void print_verilog_submodule(ModuleManager& module_manager, + NetlistManager& netlist_manager, const MuxLibrary& mux_lib, const CircuitLibrary& circuit_lib, const std::string& verilog_dir, @@ -44,12 +45,8 @@ void print_verilog_submodule(ModuleManager& module_manager, */ //add_user_defined_verilog_modules(module_manager, circuit_lib); - /* Create a vector to contain all the Verilog netlist names that have been generated in this function */ - std::vector netlist_names; - - print_verilog_submodule_essentials(const_cast(module_manager), - netlist_names, + netlist_manager, verilog_dir, submodule_dir, circuit_lib); @@ -59,28 +56,28 @@ void print_verilog_submodule(ModuleManager& module_manager, * because local decoders modules will be instanciated in the MUX modules */ print_verilog_submodule_mux_local_decoders(const_cast(module_manager), - netlist_names, + netlist_manager, mux_lib, circuit_lib, verilog_dir, submodule_dir); - print_verilog_submodule_muxes(module_manager, netlist_names, mux_lib, circuit_lib, + print_verilog_submodule_muxes(module_manager, netlist_manager, mux_lib, circuit_lib, verilog_dir, submodule_dir, fpga_verilog_opts.explicit_port_mapping()); /* LUTes */ print_verilog_submodule_luts(const_cast(module_manager), - netlist_names, circuit_lib, + netlist_manager, circuit_lib, verilog_dir, submodule_dir, fpga_verilog_opts.explicit_port_mapping()); /* Hard wires */ print_verilog_submodule_wires(const_cast(module_manager), - netlist_names, circuit_lib, + netlist_manager, circuit_lib, verilog_dir, submodule_dir); /* 4. Memories */ print_verilog_submodule_memories(const_cast(module_manager), - netlist_names, + netlist_manager, mux_lib, circuit_lib, verilog_dir, submodule_dir, fpga_verilog_opts.explicit_port_mapping()); @@ -93,9 +90,11 @@ void print_verilog_submodule(ModuleManager& module_manager, } /* Create a header file to include all the subckts */ - print_verilog_netlist_include_header_file(netlist_names, + /* + print_verilog_netlist_include_header_file(netlist_manager, submodule_dir.c_str(), SUBMODULE_VERILOG_FILE_NAME); + */ } } /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_submodule.h b/openfpga/src/fpga_verilog/verilog_submodule.h index 88e1ce965..d3de8b9d7 100644 --- a/openfpga/src/fpga_verilog/verilog_submodule.h +++ b/openfpga/src/fpga_verilog/verilog_submodule.h @@ -5,6 +5,7 @@ * Include header files that are required by function declaration *******************************************************************/ #include "module_manager.h" +#include "netlist_manager.h" #include "mux_library.h" #include "fabric_verilog_options.h" @@ -16,6 +17,7 @@ namespace openfpga { void print_verilog_submodule(ModuleManager& module_manager, + NetlistManager& netlist_manager, const MuxLibrary& mux_lib, const CircuitLibrary& circuit_lib, const std::string& verilog_dir, diff --git a/openfpga/src/fpga_verilog/verilog_top_module.cpp b/openfpga/src/fpga_verilog/verilog_top_module.cpp index 9bcfadc68..9596f20a3 100644 --- a/openfpga/src/fpga_verilog/verilog_top_module.cpp +++ b/openfpga/src/fpga_verilog/verilog_top_module.cpp @@ -34,7 +34,8 @@ namespace openfpga { * 4. Add module nets to connect datapath ports * 5. Add module nets/submodules to connect configuration ports *******************************************************************/ -void print_verilog_top_module(const ModuleManager& module_manager, +void print_verilog_top_module(NetlistManager& netlist_manager, + const ModuleManager& module_manager, const std::string& verilog_dir, const bool& use_explicit_mapping) { /* Create a module as the top-level fabric, and add it to the module manager */ @@ -69,6 +70,11 @@ void print_verilog_top_module(const ModuleManager& module_manager, /* Close file handler */ fp.close(); + /* Add fname to the netlist name list */ + NetlistId nlist_id = netlist_manager.add_netlist(verilog_fname); + VTR_ASSERT(NetlistId::INVALID() != nlist_id); + netlist_manager.set_netlist_type(nlist_id, NetlistManager::TOP_MODULE_NETLIST); + VTR_LOG("Done\n"); } diff --git a/openfpga/src/fpga_verilog/verilog_top_module.h b/openfpga/src/fpga_verilog/verilog_top_module.h index 2fb451e16..95f73e815 100644 --- a/openfpga/src/fpga_verilog/verilog_top_module.h +++ b/openfpga/src/fpga_verilog/verilog_top_module.h @@ -6,6 +6,7 @@ *******************************************************************/ #include #include "module_manager.h" +#include "netlist_manager.h" /******************************************************************** * Function declaration @@ -14,7 +15,8 @@ /* begin namespace openfpga */ namespace openfpga { -void print_verilog_top_module(const ModuleManager& module_manager, +void print_verilog_top_module(NetlistManager& netlist_manager, + const ModuleManager& module_manager, const std::string& verilog_dir, const bool& use_explicit_mapping); diff --git a/openfpga/src/fpga_verilog/verilog_wire.cpp b/openfpga/src/fpga_verilog/verilog_wire.cpp index 4f084fad9..d28b26f81 100644 --- a/openfpga/src/fpga_verilog/verilog_wire.cpp +++ b/openfpga/src/fpga_verilog/verilog_wire.cpp @@ -93,7 +93,7 @@ void print_verilog_wire_module(const ModuleManager& module_manager, * Top-level function to print wire modules *******************************************************************/ void print_verilog_submodule_wires(const ModuleManager& module_manager, - std::vector& netlist_names, + NetlistManager& netlist_manager, const CircuitLibrary& circuit_lib, const std::string& verilog_dir, const std::string& submodule_dir) { @@ -128,7 +128,9 @@ void print_verilog_submodule_wires(const ModuleManager& module_manager, fp.close(); /* Add fname to the netlist name list */ - netlist_names.push_back(verilog_fname); + NetlistId nlist_id = netlist_manager.add_netlist(verilog_fname); + VTR_ASSERT(NetlistId::INVALID() != nlist_id); + netlist_manager.set_netlist_type(nlist_id, NetlistManager::SUBMODULE_NETLIST); VTR_LOG("Done\n"); } diff --git a/openfpga/src/fpga_verilog/verilog_wire.h b/openfpga/src/fpga_verilog/verilog_wire.h index 55c39fb30..62331d8b9 100644 --- a/openfpga/src/fpga_verilog/verilog_wire.h +++ b/openfpga/src/fpga_verilog/verilog_wire.h @@ -9,6 +9,7 @@ #include "circuit_library.h" #include "module_manager.h" +#include "netlist_manager.h" /******************************************************************** * Function declaration @@ -18,7 +19,7 @@ namespace openfpga { void print_verilog_submodule_wires(const ModuleManager& module_manager, - std::vector& netlist_names, + NetlistManager& netlist_manager, const CircuitLibrary& circuit_lib, const std::string& verilog_dir, const std::string& submodule_dir); From 8f5a684b102afdb3d81518d75fef4487f4eca89f Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 24 Apr 2020 20:21:32 -0600 Subject: [PATCH 478/645] removed redundant include files in all the verilog netlists except the top one --- openfpga/src/fpga_verilog/verilog_api.cpp | 13 +++++------- .../src/fpga_verilog/verilog_decoders.cpp | 3 --- openfpga/src/fpga_verilog/verilog_decoders.h | 1 - .../fpga_verilog/verilog_essential_gates.cpp | 3 --- .../fpga_verilog/verilog_essential_gates.h | 1 - .../verilog_formal_random_top_testbench.cpp | 6 ------ .../verilog_formal_random_top_testbench.h | 1 - openfpga/src/fpga_verilog/verilog_grid.cpp | 15 +++----------- openfpga/src/fpga_verilog/verilog_grid.h | 1 - openfpga/src/fpga_verilog/verilog_lut.cpp | 3 --- openfpga/src/fpga_verilog/verilog_lut.h | 1 - openfpga/src/fpga_verilog/verilog_memory.cpp | 3 --- openfpga/src/fpga_verilog/verilog_memory.h | 1 - openfpga/src/fpga_verilog/verilog_mux.cpp | 3 --- openfpga/src/fpga_verilog/verilog_mux.h | 1 - .../verilog_preconfig_top_module.cpp | 8 +------- .../verilog_preconfig_top_module.h | 3 +-- openfpga/src/fpga_verilog/verilog_routing.cpp | 20 ++----------------- openfpga/src/fpga_verilog/verilog_routing.h | 2 -- .../src/fpga_verilog/verilog_submodule.cpp | 14 ++++++------- openfpga/src/fpga_verilog/verilog_submodule.h | 1 - .../fpga_verilog/verilog_submodule_utils.cpp | 3 --- .../fpga_verilog/verilog_submodule_utils.h | 1 - .../src/fpga_verilog/verilog_top_module.cpp | 3 --- .../fpga_verilog/verilog_top_testbench.cpp | 4 ---- .../src/fpga_verilog/verilog_top_testbench.h | 1 - openfpga/src/fpga_verilog/verilog_wire.cpp | 3 --- openfpga/src/fpga_verilog/verilog_wire.h | 1 - 28 files changed, 18 insertions(+), 102 deletions(-) diff --git a/openfpga/src/fpga_verilog/verilog_api.cpp b/openfpga/src/fpga_verilog/verilog_api.cpp index d2823df48..c8c7343ad 100644 --- a/openfpga/src/fpga_verilog/verilog_api.cpp +++ b/openfpga/src/fpga_verilog/verilog_api.cpp @@ -91,7 +91,7 @@ void fpga_fabric_verilog(ModuleManager& module_manager, */ print_verilog_submodule(module_manager, netlist_manager, mux_lib, circuit_lib, - src_dir_path, submodule_dir_path, + submodule_dir_path, options); /* Generate routing blocks */ @@ -99,14 +99,14 @@ void fpga_fabric_verilog(ModuleManager& module_manager, print_verilog_unique_routing_modules(netlist_manager, const_cast(module_manager), device_rr_gsb, - src_dir_path, rr_dir_path, + rr_dir_path, options.explicit_port_mapping()); } else { VTR_ASSERT(false == options.compress_routing()); print_verilog_flatten_routing_modules(netlist_manager, const_cast(module_manager), device_rr_gsb, - src_dir_path, rr_dir_path, + rr_dir_path, options.explicit_port_mapping()); } @@ -114,7 +114,7 @@ void fpga_fabric_verilog(ModuleManager& module_manager, print_verilog_grids(netlist_manager, const_cast(module_manager), device_ctx, device_annotation, - src_dir_path, lb_dir_path, + lb_dir_path, options.explicit_port_mapping(), options.verbose_output()); @@ -180,8 +180,7 @@ void fpga_verilog_testbench(const NetlistManager& netlist_manager, atom_ctx, place_ctx, io_location_map, netlist_annotation, netlist_name, - formal_verification_top_netlist_file_path, - src_dir_path); + formal_verification_top_netlist_file_path); } if (true == options.print_preconfig_top_testbench()) { @@ -190,7 +189,6 @@ void fpga_verilog_testbench(const NetlistManager& netlist_manager, + std::string(RANDOM_TOP_TESTBENCH_VERILOG_FILE_POSTFIX); print_verilog_random_top_testbench(netlist_name, random_top_testbench_file_path, - src_dir_path, atom_ctx, netlist_annotation, simulation_setting); @@ -208,7 +206,6 @@ void fpga_verilog_testbench(const NetlistManager& netlist_manager, netlist_annotation, netlist_name, top_testbench_file_path, - src_dir_path, simulation_setting); } diff --git a/openfpga/src/fpga_verilog/verilog_decoders.cpp b/openfpga/src/fpga_verilog/verilog_decoders.cpp index 0e993a3d3..30789e36f 100644 --- a/openfpga/src/fpga_verilog/verilog_decoders.cpp +++ b/openfpga/src/fpga_verilog/verilog_decoders.cpp @@ -165,7 +165,6 @@ void print_verilog_submodule_mux_local_decoders(const ModuleManager& module_mana NetlistManager& netlist_manager, const MuxLibrary& mux_lib, const CircuitLibrary& circuit_lib, - const std::string& verilog_dir, const std::string& submodule_dir) { std::string verilog_fname(submodule_dir + std::string(LOCAL_ENCODER_VERILOG_FILE_NAME)); @@ -181,8 +180,6 @@ void print_verilog_submodule_mux_local_decoders(const ModuleManager& module_mana print_verilog_file_header(fp, "Local Decoders for Multiplexers"); - print_verilog_include_defines_preproc_file(fp, verilog_dir); - /* Create a library for local encoders with different sizes */ DecoderLibrary decoder_lib; diff --git a/openfpga/src/fpga_verilog/verilog_decoders.h b/openfpga/src/fpga_verilog/verilog_decoders.h index f8d559f4d..9b2bbb6b1 100644 --- a/openfpga/src/fpga_verilog/verilog_decoders.h +++ b/openfpga/src/fpga_verilog/verilog_decoders.h @@ -25,7 +25,6 @@ void print_verilog_submodule_mux_local_decoders(const ModuleManager& module_mana NetlistManager& netlist_manager, const MuxLibrary& mux_lib, const CircuitLibrary& circuit_lib, - const std::string& verilog_dir, const std::string& submodule_dir); } /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_essential_gates.cpp b/openfpga/src/fpga_verilog/verilog_essential_gates.cpp index 20be3e7e4..cb8eb9504 100644 --- a/openfpga/src/fpga_verilog/verilog_essential_gates.cpp +++ b/openfpga/src/fpga_verilog/verilog_essential_gates.cpp @@ -527,7 +527,6 @@ void print_verilog_constant_generator_module(const ModuleManager& module_manager ***********************************************/ void print_verilog_submodule_essentials(const ModuleManager& module_manager, NetlistManager& netlist_manager, - const std::string& verilog_dir, const std::string& submodule_dir, const CircuitLibrary& circuit_lib) { /* TODO: remove .bak when this part is completed and tested */ @@ -546,8 +545,6 @@ void print_verilog_submodule_essentials(const ModuleManager& module_manager, print_verilog_file_header(fp, "Essential gates"); - print_verilog_include_defines_preproc_file(fp, verilog_dir); - /* Print constant generators */ /* VDD */ print_verilog_constant_generator_module(module_manager, fp, 0); diff --git a/openfpga/src/fpga_verilog/verilog_essential_gates.h b/openfpga/src/fpga_verilog/verilog_essential_gates.h index b267d0e6e..2510e6d3d 100644 --- a/openfpga/src/fpga_verilog/verilog_essential_gates.h +++ b/openfpga/src/fpga_verilog/verilog_essential_gates.h @@ -18,7 +18,6 @@ namespace openfpga { void print_verilog_submodule_essentials(const ModuleManager& module_manager, NetlistManager& netlist_manager, - const std::string& verilog_dir, const std::string& submodule_dir, const CircuitLibrary& circuit_lib); diff --git a/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.cpp b/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.cpp index fa4f68938..8d4dab5c9 100644 --- a/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.cpp +++ b/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.cpp @@ -183,7 +183,6 @@ void print_verilog_random_testbench_fpga_instance(std::fstream& fp, ********************************************************************/ void print_verilog_random_top_testbench(const std::string& circuit_name, const std::string& verilog_fname, - const std::string& verilog_dir, const AtomContext& atom_ctx, const VprNetlistAnnotation& netlist_annotation, const SimulationSetting& simulation_parameters) { @@ -203,11 +202,6 @@ void print_verilog_random_top_testbench(const std::string& circuit_name, std::string title = std::string("FPGA Verilog Testbench for Formal Top-level netlist of Design: ") + circuit_name; print_verilog_file_header(fp, title); - /* Print preprocessing flags and external netlists */ - print_verilog_include_defines_preproc_file(fp, verilog_dir); - - print_verilog_include_netlist(fp, std::string(verilog_dir + std::string(DEFINES_VERILOG_SIMULATION_FILE_NAME))); - /* Preparation: find all the clock ports */ std::vector clock_port_names = find_atom_netlist_clock_port_names(atom_ctx.nlist, netlist_annotation); diff --git a/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.h b/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.h index 88692ae4f..b12072b70 100644 --- a/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.h +++ b/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.h @@ -17,7 +17,6 @@ namespace openfpga { void print_verilog_random_top_testbench(const std::string& circuit_name, const std::string& verilog_fname, - const std::string& verilog_dir, const AtomContext& atom_ctx, const VprNetlistAnnotation& netlist_annotation, const SimulationSetting& simulation_parameters); diff --git a/openfpga/src/fpga_verilog/verilog_grid.cpp b/openfpga/src/fpga_verilog/verilog_grid.cpp index 0cf509bde..d2917836b 100644 --- a/openfpga/src/fpga_verilog/verilog_grid.cpp +++ b/openfpga/src/fpga_verilog/verilog_grid.cpp @@ -197,7 +197,6 @@ static void print_verilog_logical_tile_netlist(NetlistManager& netlist_manager, const ModuleManager& module_manager, const VprDeviceAnnotation& device_annotation, - const std::string& verilog_dir, const std::string& subckt_dir, t_pb_graph_node* pb_graph_head, const bool& use_explicit_mapping, @@ -220,9 +219,6 @@ void print_verilog_logical_tile_netlist(NetlistManager& netlist_manager, print_verilog_file_header(fp, std::string("Verilog modules for logical tile: " + std::string(pb_graph_head->pb_type->name) + "]")); - /* Print preprocessing flags */ - print_verilog_include_defines_preproc_file(fp, verilog_dir); - /* Print Verilog modules for all the pb_types/pb_graph_nodes * use a Depth-First Search Algorithm to print the sub-modules * Note: DFS is the right way. Do NOT use BFS. @@ -262,7 +258,6 @@ void print_verilog_logical_tile_netlist(NetlistManager& netlist_manager, static void print_verilog_physical_tile_netlist(NetlistManager& netlist_manager, const ModuleManager& module_manager, - const std::string& verilog_dir, const std::string& subckt_dir, t_physical_tile_type_ptr phy_block_type, const e_side& border_side, @@ -300,9 +295,6 @@ void print_verilog_physical_tile_netlist(NetlistManager& netlist_manager, print_verilog_file_header(fp, std::string("Verilog modules for physical tile: " + std::string(phy_block_type->name) + "]")); - /* Print preprocessing flags */ - print_verilog_include_defines_preproc_file(fp, verilog_dir); - /* Create a Verilog Module for the top-level physical block, and add to module manager */ std::string grid_module_name = generate_grid_block_module_name(std::string(GRID_VERILOG_FILE_NAME_PREFIX), std::string(phy_block_type->name), is_io_type(phy_block_type), border_side); ModuleId grid_module = module_manager.find_module(grid_module_name); @@ -338,7 +330,6 @@ void print_verilog_grids(NetlistManager& netlist_manager, const ModuleManager& module_manager, const DeviceContext& device_ctx, const VprDeviceAnnotation& device_annotation, - const std::string& verilog_dir, const std::string& subckt_dir, const bool& use_explicit_mapping, const bool& verbose) { @@ -362,7 +353,7 @@ void print_verilog_grids(NetlistManager& netlist_manager, print_verilog_logical_tile_netlist(netlist_manager, module_manager, device_annotation, - verilog_dir, subckt_dir, + subckt_dir, logical_tile.pb_graph_head, use_explicit_mapping, verbose); @@ -395,7 +386,7 @@ void print_verilog_grids(NetlistManager& netlist_manager, for (const e_side& io_type_side : io_type_sides) { print_verilog_physical_tile_netlist(netlist_manager, module_manager, - verilog_dir, subckt_dir, + subckt_dir, &physical_tile, io_type_side, use_explicit_mapping); @@ -405,7 +396,7 @@ void print_verilog_grids(NetlistManager& netlist_manager, /* For CLB and heterogenenous blocks */ print_verilog_physical_tile_netlist(netlist_manager, module_manager, - verilog_dir, subckt_dir, + subckt_dir, &physical_tile, NUM_SIDES, use_explicit_mapping); diff --git a/openfpga/src/fpga_verilog/verilog_grid.h b/openfpga/src/fpga_verilog/verilog_grid.h index 6d2843628..7603bd298 100644 --- a/openfpga/src/fpga_verilog/verilog_grid.h +++ b/openfpga/src/fpga_verilog/verilog_grid.h @@ -21,7 +21,6 @@ void print_verilog_grids(NetlistManager& netlist_manager, const ModuleManager& module_manager, const DeviceContext& device_ctx, const VprDeviceAnnotation& device_annotation, - const std::string& verilog_dir, const std::string& subckt_dir, const bool& use_explicit_mapping, const bool& verbose); diff --git a/openfpga/src/fpga_verilog/verilog_lut.cpp b/openfpga/src/fpga_verilog/verilog_lut.cpp index fabe9340e..34767aff7 100644 --- a/openfpga/src/fpga_verilog/verilog_lut.cpp +++ b/openfpga/src/fpga_verilog/verilog_lut.cpp @@ -32,7 +32,6 @@ namespace openfpga { void print_verilog_submodule_luts(const ModuleManager& module_manager, NetlistManager& netlist_manager, const CircuitLibrary& circuit_lib, - const std::string& verilog_dir, const std::string& submodule_dir, const bool& use_explicit_port_map) { std::string verilog_fname = submodule_dir + std::string(LUTS_VERILOG_FILE_NAME); @@ -50,8 +49,6 @@ void print_verilog_submodule_luts(const ModuleManager& module_manager, print_verilog_file_header(fp, "Look-Up Tables"); - print_verilog_include_defines_preproc_file(fp, verilog_dir); - /* Search for each LUT circuit model */ for (const auto& lut_model : circuit_lib.models()) { /* Bypass user-defined and non-LUT modules */ diff --git a/openfpga/src/fpga_verilog/verilog_lut.h b/openfpga/src/fpga_verilog/verilog_lut.h index e9ba3465a..a102e6df0 100644 --- a/openfpga/src/fpga_verilog/verilog_lut.h +++ b/openfpga/src/fpga_verilog/verilog_lut.h @@ -21,7 +21,6 @@ namespace openfpga { void print_verilog_submodule_luts(const ModuleManager& module_manager, NetlistManager& netlist_manager, const CircuitLibrary& circuit_lib, - const std::string& verilog_dir, const std::string& submodule_dir, const bool& use_explicit_port_map); diff --git a/openfpga/src/fpga_verilog/verilog_memory.cpp b/openfpga/src/fpga_verilog/verilog_memory.cpp index 53420a794..ad30eb337 100644 --- a/openfpga/src/fpga_verilog/verilog_memory.cpp +++ b/openfpga/src/fpga_verilog/verilog_memory.cpp @@ -100,7 +100,6 @@ void print_verilog_submodule_memories(const ModuleManager& module_manager, NetlistManager& netlist_manager, const MuxLibrary& mux_lib, const CircuitLibrary& circuit_lib, - const std::string& verilog_dir, const std::string& submodule_dir, const bool& use_explicit_port_map) { /* Plug in with the mux subckt */ @@ -118,8 +117,6 @@ void print_verilog_submodule_memories(const ModuleManager& module_manager, print_verilog_file_header(fp, "Memories used in FPGA"); - print_verilog_include_defines_preproc_file(fp, verilog_dir); - /* Create the memory circuits for the multiplexer */ for (auto mux : mux_lib.muxes()) { const MuxGraph& mux_graph = mux_lib.mux_graph(mux); diff --git a/openfpga/src/fpga_verilog/verilog_memory.h b/openfpga/src/fpga_verilog/verilog_memory.h index a331e5687..f489df2ad 100644 --- a/openfpga/src/fpga_verilog/verilog_memory.h +++ b/openfpga/src/fpga_verilog/verilog_memory.h @@ -23,7 +23,6 @@ void print_verilog_submodule_memories(const ModuleManager& module_manager, NetlistManager& netlist_manager, const MuxLibrary& mux_lib, const CircuitLibrary& circuit_lib, - const std::string& verilog_dir, const std::string& submodule_dir, const bool& use_explicit_port_map); diff --git a/openfpga/src/fpga_verilog/verilog_mux.cpp b/openfpga/src/fpga_verilog/verilog_mux.cpp index c85978722..3b7d3300b 100644 --- a/openfpga/src/fpga_verilog/verilog_mux.cpp +++ b/openfpga/src/fpga_verilog/verilog_mux.cpp @@ -1227,7 +1227,6 @@ void print_verilog_submodule_muxes(ModuleManager& module_manager, NetlistManager& netlist_manager, const MuxLibrary& mux_lib, const CircuitLibrary& circuit_lib, - const std::string& verilog_dir, const std::string& submodule_dir, const bool& use_explicit_port_map) { @@ -1245,8 +1244,6 @@ void print_verilog_submodule_muxes(ModuleManager& module_manager, print_verilog_file_header(fp, "Multiplexers"); - print_verilog_include_defines_preproc_file(fp, verilog_dir); - /* Generate basis sub-circuit for unique branches shared by the multiplexers */ for (auto mux : mux_lib.muxes()) { const MuxGraph& mux_graph = mux_lib.mux_graph(mux); diff --git a/openfpga/src/fpga_verilog/verilog_mux.h b/openfpga/src/fpga_verilog/verilog_mux.h index c98fa77d0..361c394dc 100644 --- a/openfpga/src/fpga_verilog/verilog_mux.h +++ b/openfpga/src/fpga_verilog/verilog_mux.h @@ -24,7 +24,6 @@ void print_verilog_submodule_muxes(ModuleManager& module_manager, NetlistManager& netlist_manager, const MuxLibrary& mux_lib, const CircuitLibrary& circuit_lib, - const std::string& verilog_dir, const std::string& submodule_dir, const bool& use_explicit_port_map); diff --git a/openfpga/src/fpga_verilog/verilog_preconfig_top_module.cpp b/openfpga/src/fpga_verilog/verilog_preconfig_top_module.cpp index 695d9275f..8e1de5cfc 100644 --- a/openfpga/src/fpga_verilog/verilog_preconfig_top_module.cpp +++ b/openfpga/src/fpga_verilog/verilog_preconfig_top_module.cpp @@ -385,8 +385,7 @@ void print_verilog_preconfig_top_module(const ModuleManager& module_manager, const IoLocationMap& io_location_map, const VprNetlistAnnotation& netlist_annotation, const std::string& circuit_name, - const std::string& verilog_fname, - const std::string& verilog_dir) { + const std::string& verilog_fname) { std::string timer_message = std::string("Write pre-configured FPGA top-level Verilog netlist for design '") + circuit_name + std::string("'"); /* Start time count */ @@ -403,11 +402,6 @@ void print_verilog_preconfig_top_module(const ModuleManager& module_manager, std::string title = std::string("Verilog netlist for pre-configured FPGA fabric by design: ") + circuit_name; print_verilog_file_header(fp, title); - /* Print preprocessing flags and external netlists */ - print_verilog_include_defines_preproc_file(fp, verilog_dir); - - print_verilog_include_netlist(fp, std::string(verilog_dir + std::string(DEFINES_VERILOG_SIMULATION_FILE_NAME))); - /* Print module declaration and ports */ print_verilog_preconfig_top_module_ports(fp, circuit_name, atom_ctx, netlist_annotation); diff --git a/openfpga/src/fpga_verilog/verilog_preconfig_top_module.h b/openfpga/src/fpga_verilog/verilog_preconfig_top_module.h index 499cafea9..e8efe5f29 100644 --- a/openfpga/src/fpga_verilog/verilog_preconfig_top_module.h +++ b/openfpga/src/fpga_verilog/verilog_preconfig_top_module.h @@ -29,8 +29,7 @@ void print_verilog_preconfig_top_module(const ModuleManager& module_manager, const IoLocationMap& io_location_map, const VprNetlistAnnotation& netlist_annotation, const std::string& circuit_name, - const std::string& verilog_fname, - const std::string& verilog_dir); + const std::string& verilog_fname); } /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_routing.cpp b/openfpga/src/fpga_verilog/verilog_routing.cpp index 8f74c0074..e6df8e858 100644 --- a/openfpga/src/fpga_verilog/verilog_routing.cpp +++ b/openfpga/src/fpga_verilog/verilog_routing.cpp @@ -77,7 +77,6 @@ namespace openfpga { static void print_verilog_routing_connection_box_unique_module(NetlistManager& netlist_manager, const ModuleManager& module_manager, - const std::string& verilog_dir, const std::string& subckt_dir, const RRGSB& rr_gsb, const t_rr_type& cb_type, @@ -94,9 +93,6 @@ void print_verilog_routing_connection_box_unique_module(NetlistManager& netlist_ print_verilog_file_header(fp, std::string("Verilog modules for Unique Connection Blocks[" + std::to_string(rr_gsb.get_cb_x(cb_type)) + "]["+ std::to_string(rr_gsb.get_cb_y(cb_type)) + "]")); - /* Print preprocessing flags */ - print_verilog_include_defines_preproc_file(fp, verilog_dir); - /* Create a Verilog Module based on the circuit model, and add to module manager */ ModuleId cb_module = module_manager.find_module(generate_connection_block_module_name(cb_type, gsb_coordinate)); VTR_ASSERT(true == module_manager.valid_module_id(cb_module)); @@ -182,7 +178,6 @@ void print_verilog_routing_connection_box_unique_module(NetlistManager& netlist_ static void print_verilog_routing_switch_box_unique_module(NetlistManager& netlist_manager, const ModuleManager& module_manager, - const std::string& verilog_dir, const std::string& subckt_dir, const RRGSB& rr_gsb, const bool& use_explicit_port_map) { @@ -198,9 +193,6 @@ void print_verilog_routing_switch_box_unique_module(NetlistManager& netlist_mana print_verilog_file_header(fp, std::string("Verilog modules for Unique Switch Blocks[" + std::to_string(rr_gsb.get_sb_x()) + "]["+ std::to_string(rr_gsb.get_sb_y()) + "]")); - /* Print preprocessing flags */ - print_verilog_include_defines_preproc_file(fp, verilog_dir); - /* Create a Verilog Module based on the circuit model, and add to module manager */ ModuleId sb_module = module_manager.find_module(generate_switch_block_module_name(gsb_coordinate)); VTR_ASSERT(true == module_manager.valid_module_id(sb_module)); @@ -225,7 +217,6 @@ static void print_verilog_flatten_connection_block_modules(NetlistManager& netlist_manager, const ModuleManager& module_manager, const DeviceRRGSB& device_rr_gsb, - const std::string& verilog_dir, const std::string& subckt_dir, const t_rr_type& cb_type, const bool& use_explicit_port_map) { @@ -244,7 +235,6 @@ void print_verilog_flatten_connection_block_modules(NetlistManager& netlist_mana } print_verilog_routing_connection_box_unique_module(netlist_manager, module_manager, - verilog_dir, subckt_dir, rr_gsb, cb_type, use_explicit_port_map); @@ -264,7 +254,6 @@ void print_verilog_flatten_connection_block_modules(NetlistManager& netlist_mana void print_verilog_flatten_routing_modules(NetlistManager& netlist_manager, const ModuleManager& module_manager, const DeviceRRGSB& device_rr_gsb, - const std::string& verilog_dir, const std::string& subckt_dir, const bool& use_explicit_port_map) { /* Create a vector to contain all the Verilog netlist names that have been generated in this function */ @@ -281,16 +270,15 @@ void print_verilog_flatten_routing_modules(NetlistManager& netlist_manager, } print_verilog_routing_switch_box_unique_module(netlist_manager, module_manager, - verilog_dir, subckt_dir, rr_gsb, use_explicit_port_map); } } - print_verilog_flatten_connection_block_modules(netlist_manager, module_manager, device_rr_gsb, verilog_dir, subckt_dir, CHANX, use_explicit_port_map); + print_verilog_flatten_connection_block_modules(netlist_manager, module_manager, device_rr_gsb, subckt_dir, CHANX, use_explicit_port_map); - print_verilog_flatten_connection_block_modules(netlist_manager, module_manager, device_rr_gsb, verilog_dir, subckt_dir, CHANY, use_explicit_port_map); + print_verilog_flatten_connection_block_modules(netlist_manager, module_manager, device_rr_gsb, subckt_dir, CHANY, use_explicit_port_map); /* VTR_LOG("Writing header file for routing submodules '%s'...", @@ -317,7 +305,6 @@ void print_verilog_flatten_routing_modules(NetlistManager& netlist_manager, void print_verilog_unique_routing_modules(NetlistManager& netlist_manager, const ModuleManager& module_manager, const DeviceRRGSB& device_rr_gsb, - const std::string& verilog_dir, const std::string& subckt_dir, const bool& use_explicit_port_map) { /* Create a vector to contain all the Verilog netlist names that have been generated in this function */ @@ -328,7 +315,6 @@ void print_verilog_unique_routing_modules(NetlistManager& netlist_manager, const RRGSB& unique_mirror = device_rr_gsb.get_sb_unique_module(isb); print_verilog_routing_switch_box_unique_module(netlist_manager, module_manager, - verilog_dir, subckt_dir, unique_mirror, use_explicit_port_map); @@ -340,7 +326,6 @@ void print_verilog_unique_routing_modules(NetlistManager& netlist_manager, print_verilog_routing_connection_box_unique_module(netlist_manager, module_manager, - verilog_dir, subckt_dir, unique_mirror, CHANX, use_explicit_port_map); @@ -352,7 +337,6 @@ void print_verilog_unique_routing_modules(NetlistManager& netlist_manager, print_verilog_routing_connection_box_unique_module(netlist_manager, module_manager, - verilog_dir, subckt_dir, unique_mirror, CHANY, use_explicit_port_map); diff --git a/openfpga/src/fpga_verilog/verilog_routing.h b/openfpga/src/fpga_verilog/verilog_routing.h index a9f8fd6a1..9d439053a 100644 --- a/openfpga/src/fpga_verilog/verilog_routing.h +++ b/openfpga/src/fpga_verilog/verilog_routing.h @@ -20,14 +20,12 @@ namespace openfpga { void print_verilog_flatten_routing_modules(NetlistManager& netlist_manager, const ModuleManager& module_manager, const DeviceRRGSB& device_rr_gsb, - const std::string& verilog_dir, const std::string& subckt_dir, const bool& use_explicit_port_map); void print_verilog_unique_routing_modules(NetlistManager& netlist_manager, const ModuleManager& module_manager, const DeviceRRGSB& device_rr_gsb, - const std::string& verilog_dir, const std::string& subckt_dir, const bool& use_explicit_port_map); diff --git a/openfpga/src/fpga_verilog/verilog_submodule.cpp b/openfpga/src/fpga_verilog/verilog_submodule.cpp index b38750ab9..37df77c60 100644 --- a/openfpga/src/fpga_verilog/verilog_submodule.cpp +++ b/openfpga/src/fpga_verilog/verilog_submodule.cpp @@ -35,7 +35,6 @@ void print_verilog_submodule(ModuleManager& module_manager, NetlistManager& netlist_manager, const MuxLibrary& mux_lib, const CircuitLibrary& circuit_lib, - const std::string& verilog_dir, const std::string& submodule_dir, const FabricVerilogOption& fpga_verilog_opts) { @@ -47,7 +46,6 @@ void print_verilog_submodule(ModuleManager& module_manager, print_verilog_submodule_essentials(const_cast(module_manager), netlist_manager, - verilog_dir, submodule_dir, circuit_lib); @@ -58,35 +56,35 @@ void print_verilog_submodule(ModuleManager& module_manager, print_verilog_submodule_mux_local_decoders(const_cast(module_manager), netlist_manager, mux_lib, circuit_lib, - verilog_dir, submodule_dir); + submodule_dir); print_verilog_submodule_muxes(module_manager, netlist_manager, mux_lib, circuit_lib, - verilog_dir, submodule_dir, + submodule_dir, fpga_verilog_opts.explicit_port_mapping()); /* LUTes */ print_verilog_submodule_luts(const_cast(module_manager), netlist_manager, circuit_lib, - verilog_dir, submodule_dir, + submodule_dir, fpga_verilog_opts.explicit_port_mapping()); /* Hard wires */ print_verilog_submodule_wires(const_cast(module_manager), netlist_manager, circuit_lib, - verilog_dir, submodule_dir); + submodule_dir); /* 4. Memories */ print_verilog_submodule_memories(const_cast(module_manager), netlist_manager, mux_lib, circuit_lib, - verilog_dir, submodule_dir, + submodule_dir, fpga_verilog_opts.explicit_port_mapping()); /* 5. Dump template for all the modules */ if (true == fpga_verilog_opts.print_user_defined_template()) { print_verilog_submodule_templates(const_cast(module_manager), circuit_lib, - verilog_dir, submodule_dir); + submodule_dir); } /* Create a header file to include all the subckts */ diff --git a/openfpga/src/fpga_verilog/verilog_submodule.h b/openfpga/src/fpga_verilog/verilog_submodule.h index d3de8b9d7..06bb7aeef 100644 --- a/openfpga/src/fpga_verilog/verilog_submodule.h +++ b/openfpga/src/fpga_verilog/verilog_submodule.h @@ -20,7 +20,6 @@ void print_verilog_submodule(ModuleManager& module_manager, NetlistManager& netlist_manager, const MuxLibrary& mux_lib, const CircuitLibrary& circuit_lib, - const std::string& verilog_dir, const std::string& submodule_dir, const FabricVerilogOption& fpga_verilog_opts); diff --git a/openfpga/src/fpga_verilog/verilog_submodule_utils.cpp b/openfpga/src/fpga_verilog/verilog_submodule_utils.cpp index b4255b3e9..faf965068 100644 --- a/openfpga/src/fpga_verilog/verilog_submodule_utils.cpp +++ b/openfpga/src/fpga_verilog/verilog_submodule_utils.cpp @@ -207,7 +207,6 @@ void print_one_verilog_template_module(const ModuleManager& module_manager, ********************************************************************/ void print_verilog_submodule_templates(const ModuleManager& module_manager, const CircuitLibrary& circuit_lib, - const std::string& verilog_dir, const std::string& submodule_dir) { std::string verilog_fname(submodule_dir + USER_DEFINED_TEMPLATE_VERILOG_FILE_NAME); @@ -223,8 +222,6 @@ void print_verilog_submodule_templates(const ModuleManager& module_manager, print_verilog_file_header(fp, "Template for user-defined Verilog modules"); - print_verilog_include_defines_preproc_file(fp, verilog_dir); - /* Output essential models*/ for (const auto& model : circuit_lib.models()) { /* Focus on user-defined modules, which must have a Verilog netlist defined */ diff --git a/openfpga/src/fpga_verilog/verilog_submodule_utils.h b/openfpga/src/fpga_verilog/verilog_submodule_utils.h index fedcb63a8..3f0e1dadd 100644 --- a/openfpga/src/fpga_verilog/verilog_submodule_utils.h +++ b/openfpga/src/fpga_verilog/verilog_submodule_utils.h @@ -29,7 +29,6 @@ void add_user_defined_verilog_modules(ModuleManager& module_manager, void print_verilog_submodule_templates(const ModuleManager& module_manager, const CircuitLibrary& circuit_lib, - const std::string& verilog_dir, const std::string& submodule_dir); } /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_top_module.cpp b/openfpga/src/fpga_verilog/verilog_top_module.cpp index 9596f20a3..79c6ebebe 100644 --- a/openfpga/src/fpga_verilog/verilog_top_module.cpp +++ b/openfpga/src/fpga_verilog/verilog_top_module.cpp @@ -58,9 +58,6 @@ void print_verilog_top_module(NetlistManager& netlist_manager, print_verilog_file_header(fp, std::string("Top-level Verilog module for FPGA")); - /* Print preprocessing flags */ - print_verilog_include_defines_preproc_file(fp, verilog_dir); - /* Write the module content in Verilog format */ write_verilog_module_to_file(fp, module_manager, top_module, use_explicit_mapping); diff --git a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp index 25017d741..a477fa99e 100644 --- a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp +++ b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp @@ -799,7 +799,6 @@ void print_verilog_top_testbench(const ModuleManager& module_manager, const VprNetlistAnnotation& netlist_annotation, const std::string& circuit_name, const std::string& verilog_fname, - const std::string& verilog_dir, const SimulationSetting& simulation_parameters) { std::string timer_message = std::string("Write autocheck testbench for FPGA top-level Verilog netlist for '") + circuit_name + std::string("'"); @@ -818,9 +817,6 @@ void print_verilog_top_testbench(const ModuleManager& module_manager, std::string title = std::string("FPGA Verilog Testbench for Top-level netlist of Design: ") + circuit_name; print_verilog_file_header(fp, title); - /* Print preprocessing flags and external netlists */ - print_verilog_include_defines_preproc_file(fp, verilog_dir); - /* Find the top_module */ ModuleId top_module = module_manager.find_module(generate_fpga_top_module_name()); VTR_ASSERT(true == module_manager.valid_module_id(top_module)); diff --git a/openfpga/src/fpga_verilog/verilog_top_testbench.h b/openfpga/src/fpga_verilog/verilog_top_testbench.h index 5684b37e2..c0ad77c51 100644 --- a/openfpga/src/fpga_verilog/verilog_top_testbench.h +++ b/openfpga/src/fpga_verilog/verilog_top_testbench.h @@ -33,7 +33,6 @@ void print_verilog_top_testbench(const ModuleManager& module_manager, const VprNetlistAnnotation& netlist_annotation, const std::string& circuit_name, const std::string& verilog_fname, - const std::string& verilog_dir, const SimulationSetting& simulation_parameters); } /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_wire.cpp b/openfpga/src/fpga_verilog/verilog_wire.cpp index d28b26f81..bb8419edd 100644 --- a/openfpga/src/fpga_verilog/verilog_wire.cpp +++ b/openfpga/src/fpga_verilog/verilog_wire.cpp @@ -95,7 +95,6 @@ void print_verilog_wire_module(const ModuleManager& module_manager, void print_verilog_submodule_wires(const ModuleManager& module_manager, NetlistManager& netlist_manager, const CircuitLibrary& circuit_lib, - const std::string& verilog_dir, const std::string& submodule_dir) { std::string verilog_fname(submodule_dir + std::string(WIRES_VERILOG_FILE_NAME)); @@ -111,8 +110,6 @@ void print_verilog_submodule_wires(const ModuleManager& module_manager, print_verilog_file_header(fp, "Wires"); - print_verilog_include_defines_preproc_file(fp, verilog_dir); - /* Print Verilog models for regular wires*/ print_verilog_comment(fp, std::string("----- BEGIN Verilog modules for regular wires -----")); for (const auto& model : circuit_lib.models_by_type(CIRCUIT_MODEL_WIRE)) { diff --git a/openfpga/src/fpga_verilog/verilog_wire.h b/openfpga/src/fpga_verilog/verilog_wire.h index 62331d8b9..d9996fbcf 100644 --- a/openfpga/src/fpga_verilog/verilog_wire.h +++ b/openfpga/src/fpga_verilog/verilog_wire.h @@ -21,7 +21,6 @@ namespace openfpga { void print_verilog_submodule_wires(const ModuleManager& module_manager, NetlistManager& netlist_manager, const CircuitLibrary& circuit_lib, - const std::string& verilog_dir, const std::string& submodule_dir); } /* end namespace openfpga */ From 890ead91b95ef1f6caea64157a5b38be3be07d8c Mon Sep 17 00:00:00 2001 From: ganeshgore Date: Fri, 24 Apr 2020 21:53:57 -0600 Subject: [PATCH 479/645] Fixed modelsim include references --- openfpga_flow/misc/modelsim_runsim.tcl | 12 +------ openfpga_flow/scripts/run_modelsim.py | 43 +++++++++++++++++++------- 2 files changed, 32 insertions(+), 23 deletions(-) diff --git a/openfpga_flow/misc/modelsim_runsim.tcl b/openfpga_flow/misc/modelsim_runsim.tcl index 64578d883..a7734bae0 100644 --- a/openfpga_flow/misc/modelsim_runsim.tcl +++ b/openfpga_flow/misc/modelsim_runsim.tcl @@ -11,27 +11,17 @@ set simtime ${SIMTIME} set unit ${UNIT} #Path were both tcl script are located -set verilog_path ${VERILOG_PATH} set project_path "${MODELSIM_PROJ_DIR}/msim_projects/" #Path were the verilog files are located -set verilog_path ${VERILOG_PATH} - -set verilog_files [list \ - ${VERILOG_PATH}${VERILOG_FILE1} \ - ${VERILOG_PATH}${VERILOG_FILE2} \ - ${VERILOG_PATH}fpga_defines.v - ] +set verilog_files ${VERILOG_PATH}/*_include_netlists_resolved.v #Source the tcl script source ${MODELSIM_PROJ_DIR}/${BENCHMARK}_autocheck_proc.tcl #Execute the top level procedure - try { top_create_new_project $$projectname $$verilog_files $$project_path $$simtime $$unit $$top_tb } finally { quit } - -#Relaunch simulation diff --git a/openfpga_flow/scripts/run_modelsim.py b/openfpga_flow/scripts/run_modelsim.py index 077b84e61..564ca504d 100644 --- a/openfpga_flow/scripts/run_modelsim.py +++ b/openfpga_flow/scripts/run_modelsim.py @@ -104,7 +104,7 @@ def main(): logfile_path = os.path.join(gc["task_dir"], taskname, task_run, "modelsim_run.log") resultfile_path = os.path.join(gc["task_dir"], - taskname, task_run, "modelsim_result.csv") + taskname, task_run, "modelsim_result.csv") logfilefh = logging.FileHandler(logfile_path, "w") logfilefh.setFormatter(logging.Formatter(FILE_LOG_FORMAT)) logger.addHandler(logfilefh) @@ -129,7 +129,8 @@ def main(): task_ini_files.append(INIfile) logger.info(f"Found {len(task_ini_files)} INI files") results = create_tcl_script(task_ini_files) - collect_result(resultfile_path, results) + if args.run_sim: + collect_result(resultfile_path, results) def clean_up_and_exit(msg): @@ -168,7 +169,21 @@ def create_tcl_script(files): config["MODELSIM_PROJ_NAME"] = args.modelsim_proj_name config["MODELSIM_INI"] = args.modelsim_ini - + config["VERILOG_PATH"] = os.path.join( + os.getcwd(), config["VERILOG_PATH"]) + IncludeFile = os.path.join( + os.getcwd(), + config["VERILOG_PATH"], + config["VERILOG_FILE2"]) + IncludeFileResolved = os.path.join( + os.getcwd(), + config["VERILOG_PATH"], + config["VERILOG_FILE2"].replace(".v", "_resolved.v")) + with open(IncludeFileResolved, "w") as fpw: + with open(IncludeFile, "r") as fp: + for eachline in fp.readlines(): + eachline = eachline.replace("./SRC", "../../../SRC/") + fpw.write(eachline) # Modify the variables in config file here config["TOP_TB"] = os.path.splitext(config["TOP_TB"])[0] @@ -193,13 +208,13 @@ def create_tcl_script(files): "ini_file": eachFile, "modelsim_run_dir": args.modelsim_run_dir, "runsim_filename": runsim_filename, - "run_complete" :False, + "run_complete": False, "status": False, - "finished" : True, - "starttime" : 0, - "endtime" : 0, + "finished": True, + "starttime": 0, + "endtime": 0, "Errors": 0, - "Warnings" : 0 + "Warnings": 0 }) # Execute modelsim if args.run_sim: @@ -229,7 +244,7 @@ def run_modelsim_thread(s, eachJob, job_list): thread_name = threading.currentThread().getName() eachJob["starttime"] = time.time() eachJob["Errors"] = 0 - eachJob["Warnings"]= 0 + eachJob["Warnings"] = 0 try: logfile = "%s_modelsim.log" % thread_name eachJob["logfile"] = "" + \ @@ -248,7 +263,8 @@ def run_modelsim_thread(s, eachJob, job_list): for line in process.stdout: if "Errors" in line: logger.info(line.strip()) - e,w = re.match("# .*: ([0-9].*), .*: ([0-9].*)", line).groups() + e, w = re.match( + "# .*: ([0-9].*), .*: ([0-9].*)", line).groups() eachJob["Errors"] += int(e) eachJob["Warnings"] += int(w) sys.stdout.buffer.flush() @@ -275,8 +291,10 @@ def run_modelsim_thread(s, eachJob, job_list): no_of_finished_job = sum([not eachJ["finished"] for eachJ in job_list]) logger.info("***** %d runs pending *****" % (no_of_finished_job)) + def collect_result(result_file, result_obj): - colnames = ["status", "Errors", "Warnings", "run_complete", "exectime", "finished", "logfile"] + colnames = ["status", "Errors", "Warnings", + "run_complete", "exectime", "finished", "logfile"] if len(result_obj): with open(result_file, 'w', newline='') as csvfile: writer = csv.DictWriter( @@ -285,11 +303,12 @@ def collect_result(result_file, result_obj): for eachResult in result_obj: writer.writerow(eachResult) logger.info("= = = ="*10) - passed_jobs = [ each["status"] for each in result_obj ] + passed_jobs = [each["status"] for each in result_obj] logger.info(f"Passed Jobs %d/%d", len(passed_jobs), len(result_obj)) logger.info(f"Result file stored at {result_file}") logger.info("= = = ="*10) + if __name__ == "__main__": if args.debug: logger.info("Setting loggger in debug mode") From 49edeb119c380e07c41bf19ea74089f92a579d58 Mon Sep 17 00:00:00 2001 From: ganeshgore Date: Sat, 25 Apr 2020 20:16:17 -0600 Subject: [PATCH 480/645] BugFix : Relative path for refrence benchmark fixed --- .../OpenFPGAShellScripts/mcnc_example_script.openfpga | 6 +++--- openfpga_flow/scripts/run_modelsim.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga index 9a4f577f8..ba086855e 100644 --- a/openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga @@ -40,8 +40,8 @@ build_fabric_bitstream --verbose write_fabric_verilog --file ./SRC \ --explicit_port_mapping \ --include_timing \ - --include_signal_init - #--support_icarus_simulator + --include_signal_init + #--support_icarus_simulator # Write the Verilog testbench for FPGA fabric # - We suggest the use of same output directory as fabric Verilog netlists @@ -49,7 +49,7 @@ write_fabric_verilog --file ./SRC \ # - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA # - Enable pre-configured top-level testbench which is a fast verification skipping programming phase # - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts -write_verilog_testbench --file ./SRC --reference_benchmark_file_path ${REFERENCE_VERILOG_TESTBENCH} --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini ./simulation_deck_info.ini +write_verilog_testbench --file ./SRC --reference_benchmark_file_path ./${REFERENCE_VERILOG_TESTBENCH} --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini ./simulation_deck_info.ini # Write the SDC files for PnR backend # - Turn on every options here diff --git a/openfpga_flow/scripts/run_modelsim.py b/openfpga_flow/scripts/run_modelsim.py index 564ca504d..4151ebd4b 100644 --- a/openfpga_flow/scripts/run_modelsim.py +++ b/openfpga_flow/scripts/run_modelsim.py @@ -182,7 +182,7 @@ def create_tcl_script(files): with open(IncludeFileResolved, "w") as fpw: with open(IncludeFile, "r") as fp: for eachline in fp.readlines(): - eachline = eachline.replace("./SRC", "../../../SRC/") + eachline = eachline.replace("\"./", "\"../../../") fpw.write(eachline) # Modify the variables in config file here config["TOP_TB"] = os.path.splitext(config["TOP_TB"])[0] From c31b20dc91cee0a333f4b5a6a3eea01ee1ff55e6 Mon Sep 17 00:00:00 2001 From: ganeshgore Date: Wed, 10 Jun 2020 23:12:30 -0600 Subject: [PATCH 481/645] Added support for simulation setting file in the task flow --- openfpga_flow/scripts/run_fpga_flow.py | 3 ++ openfpga_flow/scripts/run_fpga_task.py | 2 + .../mcnc_big20/config/task.conf | 47 ++++++++++--------- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/openfpga_flow/scripts/run_fpga_flow.py b/openfpga_flow/scripts/run_fpga_flow.py index cee4ec2ce..fcf47ee44 100644 --- a/openfpga_flow/scripts/run_fpga_flow.py +++ b/openfpga_flow/scripts/run_fpga_flow.py @@ -83,6 +83,8 @@ parser.add_argument('--openfpga_shell_template', type=str, help="Sample openfpga shell script") parser.add_argument('--openfpga_arch_file', type=str, help="Openfpga architecture file for shell") +parser.add_argument('--openfpga_sim_setting_file', type=str, + help="Openfpga simulation file for shell") parser.add_argument('--yosys_tmpl', type=str, help="Alternate yosys template, generates top_module.blif") parser.add_argument('--disp', action="store_true", @@ -683,6 +685,7 @@ def run_openfpga_shell(): path_variables = script_env_vars["PATH"] path_variables["VPR_ARCH_FILE"] = args.arch_file path_variables["OPENFPGA_ARCH_FILE"] = args.openfpga_arch_file + path_variables["OPENFPGA_SIM_SETTING_FILE"] = args.openfpga_sim_setting_file path_variables["VPR_TESTBENCH_BLIF"] = args.top_module+".blif" path_variables["ACTIVITY_FILE"] = args.top_module+"_ace_out.act" path_variables["REFERENCE_VERILOG_TESTBENCH"] = args.top_module + \ diff --git a/openfpga_flow/scripts/run_fpga_task.py b/openfpga_flow/scripts/run_fpga_task.py index 9567c4fe5..3361a3082 100644 --- a/openfpga_flow/scripts/run_fpga_task.py +++ b/openfpga_flow/scripts/run_fpga_task.py @@ -355,6 +355,8 @@ def create_run_command(curr_job_dir, archfile, benchmark_obj, param, task_conf): task_gc.get("openfpga_shell_template")] command += ["--openfpga_arch_file", task_gc.get("openfpga_arch_file")] + command += ["--openfpga_sim_setting_file", + task_gc.get("openfpga_sim_setting_file")] if benchmark_obj.get("activity_file"): command += ["--activity_file", benchmark_obj.get("activity_file")] diff --git a/openfpga_flow/tasks/openfpga_shell/mcnc_big20/config/task.conf b/openfpga_flow/tasks/openfpga_shell/mcnc_big20/config/task.conf index 5028283fb..149c0695b 100644 --- a/openfpga_flow/tasks/openfpga_shell/mcnc_big20/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/mcnc_big20/config/task.conf @@ -16,12 +16,13 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_depop50_40nm_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_depop50_40nm.xml [BENCHMARKS] -#bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/alu4/alu4.blif +#bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/alu4/alu4.blif #bench1=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex2/apex2.blif #bench2=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex4/apex4.blif ## VPR remove buffers which are in act file and create a new net. Then VPR errors out by saying the new net does not exist in act file @@ -39,91 +40,91 @@ arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_ti #bench12=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/misex3/misex3.blif #bench13=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/pdc/pdc.blif ## Passed -bench14=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s298/s298.blif -#bench15=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s38417/s38417.blif +bench14=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s298/s298.blif +# bench15=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s38417/s38417.blif #bench16=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s38584/s38584.blif #bench17=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/seq/seq.blif #bench18=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/spla/spla.blif #bench19=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/tseng/tseng.blif [SYNTHESIS_PARAM] -# Benchmark alu4 +# Benchmark alu4 bench0_top = alu4 bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/alu4/alu4.act bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/alu4/alu4.v -# Benchmark apex2 +# Benchmark apex2 bench1_top = apex2 bench1_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex2/apex2.act bench1_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex2/apex2.v -# Benchmark apex4 +# Benchmark apex4 bench2_top = apex4 bench2_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex4/apex4.act bench2_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex4/apex4.v -# Benchmark bigkey +# Benchmark bigkey bench3_top = bigkey bench3_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/bigkey/bigkey.act bench3_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/bigkey/bigkey.v -# Benchmark clma +# Benchmark clma bench4_top = clma bench4_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/clma/clma.act bench4_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/clma/clma.v -# Benchmark des +# Benchmark des bench5_top = des bench5_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/des/des.act bench5_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/des/des.v -# Benchmark diffeq +# Benchmark diffeq bench6_top = diffeq bench6_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/diffeq/diffeq.act bench6_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/diffeq/diffeq.v -# Benchmark dsip +# Benchmark dsip bench7_top = dsip bench7_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/dsip/dsip.act bench7_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/dsip/dsip.v -# Benchmark elliptic +# Benchmark elliptic bench8_top = elliptic bench8_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/elliptic/elliptic.act bench8_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/elliptic/elliptic.v -# Benchmark ex1010 +# Benchmark ex1010 bench9_top = ex1010 bench9_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/ex1010/ex1010.act bench9_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/ex1010/ex1010.v -# Benchmark ex5p +# Benchmark ex5p bench10_top = ex5p bench10_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/ex5p/ex5p.act bench10_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/ex5p/ex5p.v -# Benchmark frisc +# Benchmark frisc bench11_top = frisc bench11_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/frisc/frisc.act bench11_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/frisc/frisc.v -# Benchmark misex3 +# Benchmark misex3 bench12_top = misex3 bench12_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/misex3/misex3.act bench12_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/misex3/misex3.v -# Benchmark pdc +# Benchmark pdc bench13_top = pdc bench13_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/pdc/pdc.act bench13_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/pdc/pdc.v -# Benchmark s298 +# Benchmark s298 bench14_top = s298 bench14_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s298/s298.act bench14_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s298/s298.v -# Benchmark s38417 +# Benchmark s38417 bench15_top = s38417 bench15_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s38417/s38417.act bench15_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s38417/s38417.v -# Benchmark s38584 +# Benchmark s38584 bench16_top = s38584 bench16_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s38584/s38584.act bench16_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s38584/s38584.v -# Benchmark seq +# Benchmark seq bench17_top = seq bench17_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/seq/seq.act bench17_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/seq/seq.v -# Benchmark spla +# Benchmark spla bench18_top = spla bench18_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/spla/spla.act bench18_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/spla/spla.v -# Benchmark tseng +# Benchmark tseng bench19_top = tseng bench19_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/tseng/tseng.act bench19_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/tseng/tseng.v From 9bf91bd92a78b10823f0038653bf58c0a5c5f141 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 25 Apr 2020 20:18:10 -0600 Subject: [PATCH 482/645] start testing mcnc_big20 using OpenFPGA tasks --- .../mcnc_big20/config/task.conf | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/openfpga_flow/tasks/openfpga_shell/mcnc_big20/config/task.conf b/openfpga_flow/tasks/openfpga_shell/mcnc_big20/config/task.conf index 149c0695b..5532a0888 100644 --- a/openfpga_flow/tasks/openfpga_shell/mcnc_big20/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/mcnc_big20/config/task.conf @@ -22,30 +22,30 @@ openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulatio arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_depop50_40nm.xml [BENCHMARKS] -#bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/alu4/alu4.blif -#bench1=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex2/apex2.blif -#bench2=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex4/apex4.blif -## VPR remove buffers which are in act file and create a new net. Then VPR errors out by saying the new net does not exist in act file -#bench3=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/bigkey/bigkey.blif -## VPR remove buffers which are in act file and create a new net. Then VPR errors out by saying the new net does not exist in act file -#bench4=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/clma/clma.blif -#bench5=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/des/des.blif -#bench6=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/diffeq/diffeq.blif -## VPR remove buffers which are in act file and create a new net. Then VPR errors out by saying the new net does not exist in act file -#bench7=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/dsip/dsip.blif -#bench8=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/elliptic/elliptic.blif -#bench9=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/ex1010/ex1010.blif -#bench10=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/ex5p/ex5p.blif -#bench11=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/frisc/frisc.blif -#bench12=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/misex3/misex3.blif -#bench13=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/pdc/pdc.blif -## Passed -bench14=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s298/s298.blif -# bench15=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s38417/s38417.blif -#bench16=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s38584/s38584.blif -#bench17=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/seq/seq.blif -#bench18=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/spla/spla.blif -#bench19=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/tseng/tseng.blif +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/alu4/alu4.blif +bench1=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex2/apex2.blif +bench2=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/apex4/apex4.blif +# VPR remove buffers which are in act file and create a new net. Then VPR errors out by saying the new net does not exist in act file +bench3=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/bigkey/bigkey.blif +# VPR remove buffers which are in act file and create a new net. Then VPR errors out by saying the new net does not exist in act file +bench4=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/clma/clma.blif +bench5=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/des/des.blif +bench6=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/diffeq/diffeq.blif +# VPR remove buffers which are in act file and create a new net. Then VPR errors out by saying the new net does not exist in act file +bench7=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/dsip/dsip.blif +bench8=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/elliptic/elliptic.blif +bench9=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/ex1010/ex1010.blif +bench10=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/ex5p/ex5p.blif +bench11=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/frisc/frisc.blif +bench12=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/misex3/misex3.blif +bench13=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/pdc/pdc.blif +# Passed +bench14=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s298/s298.blif +bench15=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s38417/s38417.blif +bench16=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/s38584/s38584.blif +bench17=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/seq/seq.blif +bench18=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/spla/spla.blif +bench19=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/mcnc_big20/tseng/tseng.blif [SYNTHESIS_PARAM] # Benchmark alu4 From a3fe6a9fcbfc5560ad8ffebde27c17d14bbd5db8 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 28 Apr 2020 18:16:52 -0600 Subject: [PATCH 483/645] update travis script with example run on MCNC big20 --- .travis/script.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.travis/script.sh b/.travis/script.sh index 231437e28..60c3d0715 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -105,4 +105,10 @@ python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/duplicated_grid_pi echo -e "Testing Verilog generation with spy output pads"; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/spypad --debug --show_thread_logs +# Verify MCNC big20 benchmark suite with ModelSim +# Please make sure you have ModelSim installed in the environment +# Otherwise, it will fail +#python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/mcnc_big20 --debug --show_thread_logs --maxthreads 20 +#python3 openfpga_flow/scripts/run_modelsim.py openfpga_shell/mcnc_big20 --run_sim + end_section "OpenFPGA.TaskTun" From 42cede37fa4d3c432092d36ce4073ce99b9a8477 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 28 Apr 2020 18:22:41 -0600 Subject: [PATCH 484/645] add testcases on generate fabric/testbench only --- .../generate_fabric_example_script.openfpga | 32 +++++++++++ ...generate_testbench_example_script.openfpga | 53 +++++++++++++++++++ .../generate_fabric/config/task.conf | 31 +++++++++++ .../generate_testbench/config/task.conf | 31 +++++++++++ 4 files changed, 147 insertions(+) create mode 100644 openfpga_flow/OpenFPGAShellScripts/generate_fabric_example_script.openfpga create mode 100644 openfpga_flow/OpenFPGAShellScripts/generate_testbench_example_script.openfpga create mode 100644 openfpga_flow/tasks/openfpga_shell/generate_fabric/config/task.conf create mode 100644 openfpga_flow/tasks/openfpga_shell/generate_testbench/config/task.conf diff --git a/openfpga_flow/OpenFPGAShellScripts/generate_fabric_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/generate_fabric_example_script.openfpga new file mode 100644 index 000000000..23ed17f59 --- /dev/null +++ b/openfpga_flow/OpenFPGAShellScripts/generate_fabric_example_script.openfpga @@ -0,0 +1,32 @@ +# Run VPR for the 'and' design +#--write_rr_graph example_rr_graph.xml +vpr ${VPR_ARCH_FILE} ${VPR_TESTBENCH_BLIF} --clock_modeling route + +# Read OpenFPGA architecture definition +read_openfpga_arch -f ${OPENFPGA_ARCH_FILE} + +# Annotate the OpenFPGA architecture to VPR data base +# to debug use --verbose options +link_openfpga_arch --activity_file ${ACTIVITY_FILE} --sort_gsb_chan_node_in_edges + +# Check and correct any naming conflicts in the BLIF netlist +check_netlist_naming_conflict --fix --report ./netlist_renaming.xml + +# Build the module graph +# - Enabled compression on routing architecture modules +# - Enable pin duplication on grid modules +build_fabric --compress_routing #--verbose + +# Write the Verilog netlist for FPGA fabric +# - Enable the use of explicit port mapping in Verilog netlist +write_fabric_verilog --file ./SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose + +# Write the SDC files for PnR backend +# - Turn on every options here +write_pnr_sdc --file ./SDC + +# Finish and exit OpenFPGA +exit + +# Note : +# To run verification at the end of the flow maintain source in ./SRC directory diff --git a/openfpga_flow/OpenFPGAShellScripts/generate_testbench_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/generate_testbench_example_script.openfpga new file mode 100644 index 000000000..0926935e9 --- /dev/null +++ b/openfpga_flow/OpenFPGAShellScripts/generate_testbench_example_script.openfpga @@ -0,0 +1,53 @@ +# Run VPR for the 'and' design +#--write_rr_graph example_rr_graph.xml +vpr ${VPR_ARCH_FILE} ${VPR_TESTBENCH_BLIF} --clock_modeling route + +# Read OpenFPGA architecture definition +read_openfpga_arch -f ${OPENFPGA_ARCH_FILE} + +# Annotate the OpenFPGA architecture to VPR data base +# to debug use --verbose options +link_openfpga_arch --activity_file ${ACTIVITY_FILE} --sort_gsb_chan_node_in_edges + +# Check and correct any naming conflicts in the BLIF netlist +check_netlist_naming_conflict --fix --report ./netlist_renaming.xml + +# Apply fix-up to clustering nets based on routing results +pb_pin_fixup --verbose + +# Apply fix-up to Look-Up Table truth tables based on packing results +lut_truth_table_fixup + +# Build the module graph +# - Enabled compression on routing architecture modules +# - Enable pin duplication on grid modules +build_fabric --compress_routing #--verbose + +# Repack the netlist to physical pbs +# This must be done before bitstream generator and testbench generation +# Strongly recommend it is done after all the fix-up have been applied +repack #--verbose + +# Build the bitstream +# - Output the fabric-independent bitstream to a file +build_architecture_bitstream --verbose --file fabric_indepenent_bitstream.xml + +# Build fabric-dependent bitstream +build_fabric_bitstream --verbose + +# Write the Verilog testbench for FPGA fabric +# - We suggest the use of same output directory as fabric Verilog netlists +# - Must specify the reference benchmark file if you want to output any testbenches +# - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA +# - Enable pre-configured top-level testbench which is a fast verification skipping programming phase +# - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts +write_verilog_testbench --file ./TESTBENCH --reference_benchmark_file_path ${REFERENCE_VERILOG_TESTBENCH} --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini ./SimulationDeck/simulation_deck.ini + +# Write the SDC to run timing analysis for a mapped FPGA fabric +write_analysis_sdc --file ./SDC_analysis + +# Finish and exit OpenFPGA +exit + +# Note : +# To run verification at the end of the flow maintain source in ./SRC directory diff --git a/openfpga_flow/tasks/openfpga_shell/generate_fabric/config/task.conf b/openfpga_flow/tasks/openfpga_shell/generate_fabric/config/task.conf new file mode 100644 index 000000000..75bc6c703 --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/generate_fabric/config/task.conf @@ -0,0 +1,31 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/generate_fabric_example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.blif + +[SYNTHESIS_PARAM] +bench0_top = and2 +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.v + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] diff --git a/openfpga_flow/tasks/openfpga_shell/generate_testbench/config/task.conf b/openfpga_flow/tasks/openfpga_shell/generate_testbench/config/task.conf new file mode 100644 index 000000000..9540b767f --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/generate_testbench/config/task.conf @@ -0,0 +1,31 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/generate_testbench_example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.blif + +[SYNTHESIS_PARAM] +bench0_top = and2 +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.v + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] From 5c851a84675b55549b2ee6db04ec4164a75127d1 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 28 Apr 2020 18:24:20 -0600 Subject: [PATCH 485/645] deploy the fabric/testbench generation only cases to travis --- .travis/script.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.travis/script.sh b/.travis/script.sh index 60c3d0715..ac801729c 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -105,6 +105,12 @@ python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/duplicated_grid_pi echo -e "Testing Verilog generation with spy output pads"; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/spypad --debug --show_thread_logs +echo -e "Testing fabric Verilog generation only"; +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/generate_fabric --debug --show_thread_logs + +echo -e "Testing Verilog testbench generation only"; +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/generate_testbench --debug --show_thread_logs + # Verify MCNC big20 benchmark suite with ModelSim # Please make sure you have ModelSim installed in the environment # Otherwise, it will fail From 69306faf22d3ea79bcf35439caa089193d00449d Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 28 Apr 2020 23:21:14 -0600 Subject: [PATCH 486/645] add a new include netlist for all the fabric-related netlists --- openfpga/src/fpga_verilog/verilog_api.cpp | 6 +- .../verilog_auxiliary_netlists.cpp | 69 ++++++++++++++++++- .../fpga_verilog/verilog_auxiliary_netlists.h | 4 ++ openfpga/src/fpga_verilog/verilog_constants.h | 1 + 4 files changed, 77 insertions(+), 3 deletions(-) diff --git a/openfpga/src/fpga_verilog/verilog_api.cpp b/openfpga/src/fpga_verilog/verilog_api.cpp index c8c7343ad..2c2ea4e93 100644 --- a/openfpga/src/fpga_verilog/verilog_api.cpp +++ b/openfpga/src/fpga_verilog/verilog_api.cpp @@ -124,13 +124,17 @@ void fpga_fabric_verilog(ModuleManager& module_manager, src_dir_path, options.explicit_port_mapping()); + /* Generate an netlist including all the fabric-related netlists */ + print_fabric_include_netlist(const_cast(netlist_manager), + src_dir_path, + circuit_lib); + /* Given a brief stats on how many Verilog modules have been written to files */ VTR_LOGV(options.verbose_output(), "Written %lu Verilog modules in total\n", module_manager.num_modules()); } - /******************************************************************** * A top-level function of FPGA-Verilog which focuses on fabric Verilog generation * This function will generate diff --git a/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.cpp b/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.cpp index ba2483ca7..b69868d17 100644 --- a/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.cpp +++ b/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.cpp @@ -25,8 +25,73 @@ namespace openfpga { *******************************************************************/ /******************************************************************** - * Print a file that includes all the netlists that have been generated - * and user-defined. + * Print a file that includes all the fabric netlists + * that have been generated and user-defined. + * This does NOT include any testbenches! + * Some netlists are open to compile under specific preprocessing flags + *******************************************************************/ +void print_fabric_include_netlist(const NetlistManager& netlist_manager, + const std::string& src_dir, + const CircuitLibrary& circuit_lib) { + std::string verilog_fname = src_dir + std::string(FABRIC_INCLUDE_NETLIST_FILE_NAME); + + /* Create the file stream */ + std::fstream fp; + fp.open(verilog_fname, std::fstream::out | std::fstream::trunc); + + /* Validate the file stream */ + check_file_stream(verilog_fname.c_str(), fp); + + /* Print the title */ + print_verilog_file_header(fp, std::string("Fabric Netlist Summary")); + + /* Print preprocessing flags */ + print_verilog_comment(fp, std::string("------ Include defines: preproc flags -----")); + print_verilog_include_netlist(fp, std::string(src_dir + std::string(DEFINES_VERILOG_FILE_NAME))); + fp << std::endl; + + /* Include all the user-defined netlists */ + print_verilog_comment(fp, std::string("------ Include user-defined netlists -----")); + for (const std::string& user_defined_netlist : find_circuit_library_unique_verilog_netlists(circuit_lib)) { + print_verilog_include_netlist(fp, user_defined_netlist); + } + + /* Include all the primitive modules */ + print_verilog_comment(fp, std::string("------ Include primitive module netlists -----")); + for (const NetlistId& nlist_id : netlist_manager.netlists_by_type(NetlistManager::SUBMODULE_NETLIST)) { + print_verilog_include_netlist(fp, netlist_manager.netlist_name(nlist_id)); + } + fp << std::endl; + + /* Include all the CLB, heterogeneous block modules */ + print_verilog_comment(fp, std::string("------ Include logic block netlists -----")); + for (const NetlistId& nlist_id : netlist_manager.netlists_by_type(NetlistManager::LOGIC_BLOCK_NETLIST)) { + print_verilog_include_netlist(fp, netlist_manager.netlist_name(nlist_id)); + } + fp << std::endl; + + /* Include all the routing architecture modules */ + print_verilog_comment(fp, std::string("------ Include routing module netlists -----")); + for (const NetlistId& nlist_id : netlist_manager.netlists_by_type(NetlistManager::ROUTING_MODULE_NETLIST)) { + print_verilog_include_netlist(fp, netlist_manager.netlist_name(nlist_id)); + } + fp << std::endl; + + /* Include FPGA top module */ + print_verilog_comment(fp, std::string("------ Include fabric top-level netlists -----")); + for (const NetlistId& nlist_id : netlist_manager.netlists_by_type(NetlistManager::TOP_MODULE_NETLIST)) { + print_verilog_include_netlist(fp, netlist_manager.netlist_name(nlist_id)); + } + fp << std::endl; + + /* Close the file stream */ + fp.close(); +} + +/******************************************************************** + * Print a file that includes all the netlists + * including the fabric netlists and testbenches + * that have been generated and user-defined. * Some netlists are open to compile under specific preprocessing flags *******************************************************************/ void print_include_netlists(const NetlistManager& netlist_manager, diff --git a/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.h b/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.h index ed1317862..52292881a 100644 --- a/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.h +++ b/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.h @@ -17,6 +17,10 @@ /* begin namespace openfpga */ namespace openfpga { +void print_fabric_include_netlist(const NetlistManager& netlist_manager, + const std::string& src_dir, + const CircuitLibrary& circuit_lib); + void print_include_netlists(const NetlistManager& netlist_manager, const std::string& src_dir, const std::string& circuit_name, diff --git a/openfpga/src/fpga_verilog/verilog_constants.h b/openfpga/src/fpga_verilog/verilog_constants.h index 797a00574..4456a1048 100644 --- a/openfpga/src/fpga_verilog/verilog_constants.h +++ b/openfpga/src/fpga_verilog/verilog_constants.h @@ -22,6 +22,7 @@ constexpr char* MODELSIM_SIMULATION_TIME_UNIT = "ms"; constexpr char* ICARUS_SIMULATOR_FLAG = "ICARUS_SIMULATOR"; // the flag to enable specific Verilog code in testbenches // End of Icarus variables and flag +constexpr char* FABRIC_INCLUDE_NETLIST_FILE_NAME = "fabric_netlists.v"; constexpr char* TOP_INCLUDE_NETLIST_FILE_NAME_POSTFIX = "_include_netlists.v"; constexpr char* VERILOG_TOP_POSTFIX = "_top.v"; constexpr char* FORMAL_VERIFICATION_VERILOG_FILE_POSTFIX = "_top_formal_verification.v"; From 3c781b18d3b49e449be3e470fbb1b9f7930ff094 Mon Sep 17 00:00:00 2001 From: CHARAS SAMY Date: Fri, 1 May 2020 09:55:38 -0600 Subject: [PATCH 487/645] Added routing benchmark --- .../routing_test/routing_test.act | 10 ++++++++++ .../routing_test/routing_test.blif | 16 ++++++++++++++++ .../routing_test/routing_test.v | 19 +++++++++++++++++++ 3 files changed, 45 insertions(+) create mode 100755 openfpga_flow/benchmarks/micro_benchmark/routing_test/routing_test.act create mode 100755 openfpga_flow/benchmarks/micro_benchmark/routing_test/routing_test.blif create mode 100644 openfpga_flow/benchmarks/micro_benchmark/routing_test/routing_test.v diff --git a/openfpga_flow/benchmarks/micro_benchmark/routing_test/routing_test.act b/openfpga_flow/benchmarks/micro_benchmark/routing_test/routing_test.act new file mode 100755 index 000000000..19f52fdd9 --- /dev/null +++ b/openfpga_flow/benchmarks/micro_benchmark/routing_test/routing_test.act @@ -0,0 +1,10 @@ +IN0 0.505000 0.204400 +IN1 0.491000 0.206000 +IN2 0.472000 0.204400 +clk 0.500000 2.000000 +OUT1 0.491000 0.206000 +OUT0 0.505000 0.204400 +OUT2 0.472000 0.204400 +n15 0.491000 0.101146 +n18 0.505000 0.103222 +n21 0.472000 0.096477 diff --git a/openfpga_flow/benchmarks/micro_benchmark/routing_test/routing_test.blif b/openfpga_flow/benchmarks/micro_benchmark/routing_test/routing_test.blif new file mode 100755 index 000000000..bf85b04b3 --- /dev/null +++ b/openfpga_flow/benchmarks/micro_benchmark/routing_test/routing_test.blif @@ -0,0 +1,16 @@ +# Benchmark "routing_test" written by ABC on Tue Apr 21 18:25:21 2020 +.model routing_test +.inputs IN0 IN1 IN2 clk +.outputs OUT0 OUT1 OUT2 + +.latch n15 OUT1 re clk 2 +.latch n18 OUT0 re clk 2 +.latch n21 OUT2 re clk 2 + +.names IN1 n15 +1 1 +.names IN0 n18 +1 1 +.names IN2 n21 +1 1 +.end diff --git a/openfpga_flow/benchmarks/micro_benchmark/routing_test/routing_test.v b/openfpga_flow/benchmarks/micro_benchmark/routing_test/routing_test.v new file mode 100644 index 000000000..1cacf4857 --- /dev/null +++ b/openfpga_flow/benchmarks/micro_benchmark/routing_test/routing_test.v @@ -0,0 +1,19 @@ + +module routing_test(IN0,IN1,IN2,OUT0,OUT1,OUT2,clk); + +input wire IN0,IN1,IN2,clk; + +output reg OUT0, OUT1, OUT2; + +always @(posedge clk) + begin + + OUT0 <= IN0; + OUT1 <= IN1; + OUT2 <= IN2; + + end + + + +endmodule From f6cea1e17c7b364e5c61908c993b947e5ad4713f Mon Sep 17 00:00:00 2001 From: CHARAS SAMY Date: Fri, 1 May 2020 10:55:23 -0600 Subject: [PATCH 488/645] Added test_mode_low benchmark --- .../test_mode_low/test_mode_low.act | 23 ++++++++ .../test_mode_low/test_mode_low.blif | 35 +++++++++++++ .../test_mode_low/test_mode_low.v | 52 +++++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 openfpga_flow/benchmarks/micro_benchmark/test_mode_low/test_mode_low.act create mode 100644 openfpga_flow/benchmarks/micro_benchmark/test_mode_low/test_mode_low.blif create mode 100644 openfpga_flow/benchmarks/micro_benchmark/test_mode_low/test_mode_low.v diff --git a/openfpga_flow/benchmarks/micro_benchmark/test_mode_low/test_mode_low.act b/openfpga_flow/benchmarks/micro_benchmark/test_mode_low/test_mode_low.act new file mode 100644 index 000000000..44920c540 --- /dev/null +++ b/openfpga_flow/benchmarks/micro_benchmark/test_mode_low/test_mode_low.act @@ -0,0 +1,23 @@ +a 0.5 0.2 +b 0.5 0.2 +clk 0.5 0.2 +out_0 0.5 0.2 +out_1 0.5 0.2 +out_2 0.5 0.2 +out_3 0.5 0.2 +sum_0 0.5 0.2 +sum_1 0.5 0.2 +sum_2 0.5 0.2 +sum_3 0.5 0.2 +sum_4 0.5 0.2 +sum_5 0.5 0.2 +sum_6 0.5 0.2 +sum_7 0.5 0.2 +pipe_a_0 0.5 0.2 +pipe_a_1 0.5 0.2 +pipe_b_0 0.5 0.2 +pipe_b_1 0.5 0.2 +pipe_sum_0 0.5 0.2 +pipe_sum_1 0.5 0.2 +pipe_sum_2 0.5 0.2 +pipe_sum_3 0.5 0.2 diff --git a/openfpga_flow/benchmarks/micro_benchmark/test_mode_low/test_mode_low.blif b/openfpga_flow/benchmarks/micro_benchmark/test_mode_low/test_mode_low.blif new file mode 100644 index 000000000..fd6a62c17 --- /dev/null +++ b/openfpga_flow/benchmarks/micro_benchmark/test_mode_low/test_mode_low.blif @@ -0,0 +1,35 @@ +.model test_mode_low +.inputs a b clk +.outputs out_0 out_1 out_2 out_3 + +#.subckt shift D=a clk=clk Q=pipe_a_0 +#.subckt shift D=pipe_a_0 clk=clk Q=pipe_a_1 +#.subckt shift D=b clk=clk Q=pipe_b_0 +#.subckt shift D=pipe_b_0 clk=clk Q=pipe_b_1 + + +.latch a pipe_a_0 re clk 0 +.latch pipe_a_0 pipe_a_1 re clk 0 +.latch b pipe_b_0 re clk 0 +.latch pipe_b_0 pipe_b_1 re clk 0 + +.latch sum_0 pipe_sum_0 re clk 0 +.latch sum_2 pipe_sum_1 re clk 0 +.latch sum_4 pipe_sum_2 re clk 0 +.latch sum_6 pipe_sum_3 re clk 0 + +.subckt adder a=pipe_a_1 b=pipe_b_1 cin=pipe_sum_3 cout=sum_1 sumout=sum_0 +.subckt adder a=pipe_sum_0 b=pipe_sum_2 cin=sum_1 cout=sum_3 sumout=sum_2 +.subckt adder a=pipe_sum_1 b=pipe_sum_3 cin=sum_3 cout=sum_5 sumout=sum_4 +.subckt adder a=pipe_sum_2 b=pipe_sum_0 cin=sum_5 cout=sum_7 sumout=sum_6 + +.names pipe_sum_0 out_0 +1 1 +.names pipe_sum_1 out_1 +1 1 +.names pipe_sum_2 out_2 +1 1 +.names pipe_sum_3 out_3 +1 1 + +.end diff --git a/openfpga_flow/benchmarks/micro_benchmark/test_mode_low/test_mode_low.v b/openfpga_flow/benchmarks/micro_benchmark/test_mode_low/test_mode_low.v new file mode 100644 index 000000000..ac34e62f3 --- /dev/null +++ b/openfpga_flow/benchmarks/micro_benchmark/test_mode_low/test_mode_low.v @@ -0,0 +1,52 @@ +////////////////////////////////////// +// // +// 2x2 Test-modes Low density // +// // +////////////////////////////////////// + + +module test_mode_low ( + a, + b, + clk, + reset, + out ); + + input wire a; + input wire b; + input wire clk; + input wire reset; + output wire[3:0] out; + + reg[1:0] pipe_a; + reg[1:0] pipe_b; + reg[3:0] pipe_sum; + wire[7:0] sum; + + assign sum[1:0] = pipe_a[1] + pipe_b[1] + pipe_sum[3]; + assign sum[3:2] = pipe_sum[0] + sum[1] + pipe_sum[2]; + assign sum[5:4] = pipe_sum[1] + sum[3] + pipe_sum[3]; + assign sum[7:6] = pipe_sum[2] + sum[5] + pipe_sum[0]; + assign out = pipe_sum; + + initial begin + pipe_a <= 2'b00; + pipe_b <= 2'b00; + pipe_sum <= 4'b0000; + end + + always @(posedge clk or posedge reset) begin + if(reset) begin + pipe_a <= 2'b00; + pipe_b <= 2'b00; + pipe_sum <= 4'b0000; + end else begin + pipe_a[0] <= a; + pipe_a[1] <= pipe_a[0]; + pipe_b[0] <= b; + pipe_b[1] <= pipe_b[0]; + pipe_sum <= {sum[6], sum[4], sum[2], sum[0]}; + end + end + +endmodule From 6dd8d347e140d9aad6c8c72c02786240fc291166 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 1 May 2020 14:39:04 -0600 Subject: [PATCH 489/645] try to deploy microbenchmark test_mode_low but fail due to .v port mismatch with .blif --- openfpga_flow/tasks/openfpga_shell/spypad/config/task.conf | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openfpga_flow/tasks/openfpga_shell/spypad/config/task.conf b/openfpga_flow/tasks/openfpga_shell/spypad/config/task.conf index e093b2b53..b42e17287 100644 --- a/openfpga_flow/tasks/openfpga_shell/spypad/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/spypad/config/task.conf @@ -22,11 +22,18 @@ arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_ti [BENCHMARKS] bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.blif +# Cannot pass automatically. Need change in .v file to match ports +# When passed, we can replace the and2 benchmark +#bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/test_mode_low/test_mode_low.blif [SYNTHESIS_PARAM] bench0_top = and2 bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.act bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.v + +#bench0_top = test_mode_low +#bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/test_mode_low/test_mode_low.act +#bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/test_mode_low/test_mode_low.v bench0_chan_width = 300 [SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] From 98a658a01349f246f7f4118f348ed441719830b6 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 1 May 2020 14:47:08 -0600 Subject: [PATCH 490/645] bug fixed in routing_test.v. Deployed to regression tests --- .../micro_benchmark/routing_test/routing_test.v | 2 +- .../tasks/openfpga_shell/frac_lut/config/task.conf | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/openfpga_flow/benchmarks/micro_benchmark/routing_test/routing_test.v b/openfpga_flow/benchmarks/micro_benchmark/routing_test/routing_test.v index 1cacf4857..d9729c1c8 100644 --- a/openfpga_flow/benchmarks/micro_benchmark/routing_test/routing_test.v +++ b/openfpga_flow/benchmarks/micro_benchmark/routing_test/routing_test.v @@ -1,5 +1,5 @@ -module routing_test(IN0,IN1,IN2,OUT0,OUT1,OUT2,clk); +module routing_test(IN0,IN1,IN2, clk, OUT0,OUT1,OUT2); input wire IN0,IN1,IN2,clk; diff --git a/openfpga_flow/tasks/openfpga_shell/frac_lut/config/task.conf b/openfpga_flow/tasks/openfpga_shell/frac_lut/config/task.conf index a69c1107e..030c0d41e 100644 --- a/openfpga_flow/tasks/openfpga_shell/frac_lut/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/frac_lut/config/task.conf @@ -21,18 +21,24 @@ openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10 arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml [BENCHMARKS] +# bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.blif +bench1=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/routing_test/routing_test.blif # Modelsim is ok with this but icarus fails due to poor support on timing and looping -#bench1=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2_latch/and2_latch.blif +#bench2=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2_latch/and2_latch.blif [SYNTHESIS_PARAM] bench0_top = and2 bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.act bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.v -bench1_top = and2_latch -bench1_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2_latch/and2_latch.act -bench1_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2_latch/and2_latch.v +bench1_top = routing_test +bench1_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/routing_test/routing_test.act +bench1_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/routing_test/routing_test.v + +bench2_top = and2_latch +bench2_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2_latch/and2_latch.act +bench2_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2_latch/and2_latch.v [SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] end_flow_with_test= From 889f179ce779c7c5de4e8b9337f2c54b62eccf96 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 1 May 2020 14:54:57 -0600 Subject: [PATCH 491/645] add local encoder test case --- ...6_frac_N10_local_encoder_40nm_openfpga.xml | 260 ++++++++++++++++++ .../mux_design/local_encoder/config/task.conf | 34 +++ 2 files changed, 294 insertions(+) create mode 100644 openfpga_flow/openfpga_arch/k6_frac_N10_local_encoder_40nm_openfpga.xml create mode 100644 openfpga_flow/tasks/openfpga_shell/mux_design/local_encoder/config/task.conf diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_local_encoder_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_local_encoder_40nm_openfpga.xml new file mode 100644 index 000000000..201a4be58 --- /dev/null +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_local_encoder_40nm_openfpga.xml @@ -0,0 +1,260 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + + + + 10e-12 5e-12 + + + 10e-12 5e-12 + + + + + + + + + + + + 10e-12 5e-12 5e-12 + + + 10e-12 5e-12 5e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/tasks/openfpga_shell/mux_design/local_encoder/config/task.conf b/openfpga_flow/tasks/openfpga_shell/mux_design/local_encoder/config/task.conf new file mode 100644 index 000000000..76cf4b589 --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/mux_design/local_encoder/config/task.conf @@ -0,0 +1,34 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_local_encoder_40nm_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.blif + +[SYNTHESIS_PARAM] +bench0_top = and2 +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.v +bench0_chan_width = 300 + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +end_flow_with_test= +vpr_fpga_verilog_formal_verification_top_netlist= From 1d35ac8086f541acdeefe01ffe8d0fd071a69d85 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 1 May 2020 14:56:07 -0600 Subject: [PATCH 492/645] deploy local encoder to CI --- .travis/script.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis/script.sh b/.travis/script.sh index ac801729c..cb9601683 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -90,6 +90,9 @@ python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/mux_design/tree_st echo -e "Testing Verilog generation with routing mutliplexers implemented by standard cell MUX2"; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/mux_design/stdcell_mux2 --debug --show_thread_logs +echo -e "Testing Verilog generation with routing mutliplexers implemented by local encoders"; +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/mux_design/local_encoder --debug --show_thread_logs + echo -e "Testing Verilog generation with behavioral description"; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/behavioral_verilog --debug --show_thread_logs From 889bc8dbe82999372ce41dda188b7d55c1ceaa94 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 1 May 2020 15:05:59 -0600 Subject: [PATCH 493/645] add more test cases about LUT design and deploy to CI --- .travis/script.sh | 11 +- ..._N10_intermediate_buffer_40nm_openfpga.xml | 229 ++++++++++++++++++ .../frac_lut/config/task.conf | 0 .../intermediate_buffer/config/task.conf | 45 ++++ .../single_mode/config/task.conf | 0 5 files changed, 281 insertions(+), 4 deletions(-) create mode 100644 openfpga_flow/openfpga_arch/k6_N10_intermediate_buffer_40nm_openfpga.xml rename openfpga_flow/tasks/openfpga_shell/{ => lut_design}/frac_lut/config/task.conf (100%) create mode 100644 openfpga_flow/tasks/openfpga_shell/lut_design/intermediate_buffer/config/task.conf rename openfpga_flow/tasks/openfpga_shell/{ => lut_design}/single_mode/config/task.conf (100%) diff --git a/.travis/script.sh b/.travis/script.sh index cb9601683..19da16b8b 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -51,11 +51,14 @@ echo -e "Testing OpenFPGA Shell"; echo -e "Testing configuration chain of a K4N4 FPGA"; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/configuration_chain --debug --show_thread_logs -echo -e "Testing Verilog generation for a single mode LUT6 FPGA using micro benchmarks"; -python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/single_mode --debug --show_thread_logs +echo -e "Testing Verilog generation for LUTs: a single mode LUT6 FPGA using micro benchmarks"; +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/lut_design/single_mode --debug --show_thread_logs -echo -e "Testing Verilog generation with simple fracturable LUT6 "; -python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/frac_lut --debug --show_thread_logs +echo -e "Testing Verilog generation for LUTs: simple fracturable LUT6 "; +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/lut_design/frac_lut --debug --show_thread_logs + +echo -e "Testing Verilog generation for LUTs: LUT6 with intermediate buffers"; +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/lut_design/intermediate_buffer --debug --show_thread_logs echo -e "Testing Verilog generation with VPR's untileable routing architecture "; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/untileable --debug --show_thread_logs diff --git a/openfpga_flow/openfpga_arch/k6_N10_intermediate_buffer_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_N10_intermediate_buffer_40nm_openfpga.xml new file mode 100644 index 000000000..ef1c60a80 --- /dev/null +++ b/openfpga_flow/openfpga_arch/k6_N10_intermediate_buffer_40nm_openfpga.xml @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + + + + + 10e-12 5e-12 5e-12 + + + 10e-12 5e-12 5e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/tasks/openfpga_shell/frac_lut/config/task.conf b/openfpga_flow/tasks/openfpga_shell/lut_design/frac_lut/config/task.conf similarity index 100% rename from openfpga_flow/tasks/openfpga_shell/frac_lut/config/task.conf rename to openfpga_flow/tasks/openfpga_shell/lut_design/frac_lut/config/task.conf diff --git a/openfpga_flow/tasks/openfpga_shell/lut_design/intermediate_buffer/config/task.conf b/openfpga_flow/tasks/openfpga_shell/lut_design/intermediate_buffer/config/task.conf new file mode 100644 index 000000000..b2535bd7c --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/lut_design/intermediate_buffer/config/task.conf @@ -0,0 +1,45 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_N10_intermediate_buffer_40nm_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_N10_tileable_40nm.xml + +[BENCHMARKS] +# +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.blif +bench1=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/routing_test/routing_test.blif +# Modelsim is ok with this but icarus fails due to poor support on timing and looping +#bench2=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2_latch/and2_latch.blif + +[SYNTHESIS_PARAM] +bench0_top = and2 +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.v + +bench1_top = routing_test +bench1_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/routing_test/routing_test.act +bench1_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/routing_test/routing_test.v + +bench2_top = and2_latch +bench2_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2_latch/and2_latch.act +bench2_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2_latch/and2_latch.v + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +end_flow_with_test= +vpr_fpga_verilog_formal_verification_top_netlist= diff --git a/openfpga_flow/tasks/openfpga_shell/single_mode/config/task.conf b/openfpga_flow/tasks/openfpga_shell/lut_design/single_mode/config/task.conf similarity index 100% rename from openfpga_flow/tasks/openfpga_shell/single_mode/config/task.conf rename to openfpga_flow/tasks/openfpga_shell/lut_design/single_mode/config/task.conf From 1e2226e1c3038e0c9a757800c69b6c793da87a10 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 1 May 2020 21:18:14 -0600 Subject: [PATCH 494/645] now use explicit port mapping in the verilog testbenches for reference benchmarks --- .../verilog_formal_random_top_testbench.cpp | 7 ++++++- .../src/fpga_verilog/verilog_testbench_utils.cpp | 16 +++++++++++++++- .../src/fpga_verilog/verilog_testbench_utils.h | 4 ++++ .../src/fpga_verilog/verilog_top_testbench.cpp | 6 +++++- 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.cpp b/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.cpp index 8d4dab5c9..a06f58188 100644 --- a/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.cpp +++ b/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.cpp @@ -108,13 +108,17 @@ void print_verilog_top_random_testbench_benchmark_instance(std::fstream& fp, /* Do NOT use explicit port mapping here: * VPR added a prefix of "out_" to the output ports of input benchmark */ + std::vector prefix_to_remove; + prefix_to_remove.push_back(std::string(VPR_BENCHMARK_OUT_PORT_PREFIX)); + prefix_to_remove.push_back(std::string(OPENFPGA_BENCHMARK_OUT_PORT_PREFIX)); print_verilog_testbench_benchmark_instance(fp, reference_verilog_top_name, std::string(BENCHMARK_INSTANCE_NAME), std::string(), std::string(), + prefix_to_remove, std::string(BENCHMARK_PORT_POSTFIX), atom_ctx, netlist_annotation, - false); + true); print_verilog_comment(fp, std::string("----- End reference Benchmark Instanication -------")); @@ -146,6 +150,7 @@ void print_verilog_random_testbench_fpga_instance(std::fstream& fp, std::string(FPGA_INSTANCE_NAME), std::string(FORMAL_VERIFICATION_TOP_MODULE_PORT_POSTFIX), std::string(FORMAL_VERIFICATION_TOP_MODULE_PORT_POSTFIX), + std::vector(), std::string(FPGA_PORT_POSTFIX), atom_ctx, netlist_annotation, true); diff --git a/openfpga/src/fpga_verilog/verilog_testbench_utils.cpp b/openfpga/src/fpga_verilog/verilog_testbench_utils.cpp index dae42f042..8b5c741f1 100644 --- a/openfpga/src/fpga_verilog/verilog_testbench_utils.cpp +++ b/openfpga/src/fpga_verilog/verilog_testbench_utils.cpp @@ -57,6 +57,7 @@ void print_verilog_testbench_benchmark_instance(std::fstream& fp, const std::string& instance_name, const std::string& module_input_port_postfix, const std::string& module_output_port_postfix, + const std::vector& output_port_prefix_to_remove, const std::string& output_port_postfix, const AtomContext& atom_ctx, const VprNetlistAnnotation& netlist_annotation, @@ -97,8 +98,21 @@ void print_verilog_testbench_benchmark_instance(std::fstream& fp, } else { VTR_ASSERT_SAFE(AtomBlockType::OUTPAD == atom_ctx.nlist.block_type(atom_blk)); fp << "\t\t"; + /* Note that VPR added a prefix "out_" or "out:" to the name of output blocks + * We can remove this when specified through input argument + */ + std::string output_block_name = block_name; + for (const std::string& prefix_to_remove : output_port_prefix_to_remove) { + if (!prefix_to_remove.empty()) { + if (0 == output_block_name.find(prefix_to_remove)) { + output_block_name.erase(0, prefix_to_remove.length()); + break; + } + } + } + if (true == use_explicit_port_map) { - fp << "." << block_name << module_output_port_postfix << "("; + fp << "." << output_block_name << module_output_port_postfix << "("; } fp << block_name << output_port_postfix; if (true == use_explicit_port_map) { diff --git a/openfpga/src/fpga_verilog/verilog_testbench_utils.h b/openfpga/src/fpga_verilog/verilog_testbench_utils.h index 5e0bd69f9..d51f3b7e8 100644 --- a/openfpga/src/fpga_verilog/verilog_testbench_utils.h +++ b/openfpga/src/fpga_verilog/verilog_testbench_utils.h @@ -20,6 +20,9 @@ /* begin namespace openfpga */ namespace openfpga { +constexpr char* VPR_BENCHMARK_OUT_PORT_PREFIX = "out:"; +constexpr char* OPENFPGA_BENCHMARK_OUT_PORT_PREFIX = "out_"; + void print_verilog_testbench_fpga_instance(std::fstream& fp, const ModuleManager& module_manager, const ModuleId& top_module, @@ -30,6 +33,7 @@ void print_verilog_testbench_benchmark_instance(std::fstream& fp, const std::string& instance_name, const std::string& module_input_port_postfix, const std::string& module_output_port_postfix, + const std::vector& output_port_prefix_to_remove, const std::string& output_port_postfix, const AtomContext& atom_ctx, const VprNetlistAnnotation& netlist_annotation, diff --git a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp index a477fa99e..6a647de35 100644 --- a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp +++ b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp @@ -458,13 +458,17 @@ void print_verilog_top_testbench_benchmark_instance(std::fstream& fp, /* Do NOT use explicit port mapping here: * VPR added a prefix of "out_" to the output ports of input benchmark */ + std::vector prefix_to_remove; + prefix_to_remove.push_back(std::string(VPR_BENCHMARK_OUT_PORT_PREFIX)); + prefix_to_remove.push_back(std::string(OPENFPGA_BENCHMARK_OUT_PORT_PREFIX)); print_verilog_testbench_benchmark_instance(fp, reference_verilog_top_name, std::string(TOP_TESTBENCH_REFERENCE_INSTANCE_NAME), std::string(), std::string(), + prefix_to_remove, std::string(TOP_TESTBENCH_REFERENCE_OUTPUT_POSTFIX), atom_ctx, netlist_annotation, - false); + true); print_verilog_comment(fp, std::string("----- End reference Benchmark Instanication -------")); From facd87dafe5eec9bd0734836ddb1f80c5333bdce Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 1 May 2020 21:39:43 -0600 Subject: [PATCH 495/645] use wildcard in SDC generation for multiple-instanced-blocks --- openfpga/src/fpga_sdc/sdc_memory_utils.cpp | 18 +++++++++++++++++- openfpga/src/fpga_sdc/sdc_writer_utils.cpp | 20 +++++++++++++++++--- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/openfpga/src/fpga_sdc/sdc_memory_utils.cpp b/openfpga/src/fpga_sdc/sdc_memory_utils.cpp index 1910b4197..61457b467 100644 --- a/openfpga/src/fpga_sdc/sdc_memory_utils.cpp +++ b/openfpga/src/fpga_sdc/sdc_memory_utils.cpp @@ -3,6 +3,9 @@ * fabric using SDC commands *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_assert.h" + /* Headers from openfpgautil library */ #include "openfpga_digest.h" @@ -26,17 +29,30 @@ void rec_print_pnr_sdc_disable_configurable_memory_module_output(std::fstream& f const ModuleId& parent_module, const std::string& parent_module_path) { + /* Keep tracking multiple-instanced-blocks (MIB) + * if they should be skipped as the unique module has been visited already + */ + std::map skip_mib; /* For each configurable child, we will go one level down in priority */ for (size_t child_index = 0; child_index < module_manager.configurable_children(parent_module).size(); ++child_index) { std::string child_module_path = parent_module_path; ModuleId child_module_id = module_manager.configurable_children(parent_module)[child_index]; size_t child_instance_id = module_manager.configurable_child_instances(parent_module)[child_index]; if (true == module_manager.instance_name(parent_module, child_module_id, child_instance_id).empty()) { + if (0 < skip_mib.count(child_module_id)) { + VTR_ASSERT(skip_mib.at(child_module_id) = true); + continue; + } /* Give a default name __ */ child_module_path += module_manager.module_name(child_module_id); child_module_path += "_"; - child_module_path += std::to_string(child_instance_id); + child_module_path += "*"; + //child_module_path += std::to_string(child_instance_id); child_module_path += "_"; + /* If we use wild card to disable all the other instance + * So we can skip later if there is no specific instance name + */ + skip_mib[child_module_id] = true; } else { child_module_path += module_manager.instance_name(parent_module, child_module_id, child_instance_id); } diff --git a/openfpga/src/fpga_sdc/sdc_writer_utils.cpp b/openfpga/src/fpga_sdc/sdc_writer_utils.cpp index 363d97e1c..4f021034c 100644 --- a/openfpga/src/fpga_sdc/sdc_writer_utils.cpp +++ b/openfpga/src/fpga_sdc/sdc_writer_utils.cpp @@ -5,6 +5,9 @@ #include #include +/* Headers from vtrutil library */ +#include "vtr_assert.h" + /* Headers from openfpgautil library */ #include "openfpga_digest.h" @@ -46,10 +49,21 @@ std::string generate_sdc_port(const BasicPort& port) { /* Only connection require a format of [:] * others require a format of [:] */ - /* When LSB == MSB, we can use a simplified format []*/ - if ( 1 == port.get_width()) { - size_str = "[" + std::to_string(port.get_lsb()) + "]"; + /* When LSB == MSB, we can use a simplified format + * If LSB != 0, we need to give explicit pin number + * [] + * Otherwise, we can keep a compact format + * + */ + if (1 == port.get_width()) { + if (0 != port.get_lsb()) { + size_str = "[" + std::to_string(port.get_lsb()) + "]"; + } else { + VTR_ASSERT(0 == port.get_lsb()); + size_str.clear(); + } } + sdc_line = port.get_name() + size_str; return sdc_line; From 8695c5ee78cb1b55e2e41aeac3021b6f6cf4b99a Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 2 May 2020 14:17:07 -0600 Subject: [PATCH 496/645] add options to use general-purpose wildcards in SDC generator --- .../src/openfpga_wildcard_string.cpp | 105 ++++++++++++++++++ .../src/openfpga_wildcard_string.h | 57 ++++++++++ openfpga/src/base/openfpga_naming.cpp | 14 +++ openfpga/src/base/openfpga_naming.h | 3 + openfpga/src/base/openfpga_sdc.cpp | 4 + openfpga/src/base/openfpga_sdc_command.cpp | 6 + openfpga/src/fpga_sdc/analysis_sdc_option.cpp | 9 ++ openfpga/src/fpga_sdc/analysis_sdc_option.h | 3 + openfpga/src/fpga_sdc/analysis_sdc_writer.cpp | 2 +- openfpga/src/fpga_sdc/pnr_sdc_option.cpp | 9 ++ openfpga/src/fpga_sdc/pnr_sdc_option.h | 3 + openfpga/src/fpga_sdc/pnr_sdc_writer.cpp | 6 +- openfpga/src/fpga_sdc/sdc_memory_utils.cpp | 61 +++++++--- openfpga/src/fpga_sdc/sdc_memory_utils.h | 1 + 14 files changed, 263 insertions(+), 20 deletions(-) create mode 100644 libopenfpga/libopenfpgautil/src/openfpga_wildcard_string.cpp create mode 100644 libopenfpga/libopenfpgautil/src/openfpga_wildcard_string.h diff --git a/libopenfpga/libopenfpgautil/src/openfpga_wildcard_string.cpp b/libopenfpga/libopenfpgautil/src/openfpga_wildcard_string.cpp new file mode 100644 index 000000000..67031a2a5 --- /dev/null +++ b/libopenfpga/libopenfpgautil/src/openfpga_wildcard_string.cpp @@ -0,0 +1,105 @@ +/************************************************************************ + * Member functions for WildCardString class + ***********************************************************************/ +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" + +#include "openfpga_wildcard_string.h" + +/* namespace openfpga begins */ +namespace openfpga { + +/************************************************************************ + * Constructors + ***********************************************************************/ +WildCardString::WildCardString(const std::string& data) { + set_data(data); +} + +/************************************************************************ + * Public Accessors + ***********************************************************************/ +std::string WildCardString::data() const { + return data_; +} + +/************************************************************************ + * Public Mutators + ***********************************************************************/ +void WildCardString::set_data(const std::string& data) { + data_ = data; + + set_default_wildcard_char(); + set_default_sensitive_chars(); + apply_wildcard_char(); + compress(); +} + +/************************************************************************ + * Internal Mutators + ***********************************************************************/ +void WildCardString::set_default_wildcard_char() { + wildcard_char_ = '*'; +} + +void WildCardString::set_default_sensitive_chars() { + sensitive_chars_.clear(); + sensitive_chars_.reserve(10); + sensitive_chars_.push_back('0'); + sensitive_chars_.push_back('1'); + sensitive_chars_.push_back('2'); + sensitive_chars_.push_back('3'); + sensitive_chars_.push_back('4'); + sensitive_chars_.push_back('5'); + sensitive_chars_.push_back('6'); + sensitive_chars_.push_back('7'); + sensitive_chars_.push_back('8'); + sensitive_chars_.push_back('9'); +} + +void WildCardString::apply_wildcard_char() { + /* Step by step: + * For each sensitive character, + * replace all of its occurance in the string data_ with wildcard character + */ + for (const char& char_to_replace : sensitive_chars_) { + size_t cur_pos = 0; + std::string::size_type found; + while (std::string::npos != (found = data_.find_first_of(char_to_replace, cur_pos))) { + data_.replace(found, 1, 1, wildcard_char_); + cur_pos = found + 1; + } + } +} + +void WildCardString::compress() { + for (std::string::size_type i = 0; i < data_.size(); ++i) { + /* Care only wildcard character */ + if (wildcard_char_ != data_[i]) { + continue; + } + + /* Finish if this is the end of string */ + if (data_.size() - 1 == i) { + break; + } + + /* Try to find the next element and see if the same as wild card + * Keep erase the next element until we have a non-wildcard character + */ + while (data_[i] == data_[i + 1]) { + /* Erase the next element */ + data_.erase(i + 1, 1); + + /* Finish if this is the end of string */ + if (data_.size() - 1 == i) { + break; + } + } + } +} + +} /* namespace openfpga ends */ diff --git a/libopenfpga/libopenfpgautil/src/openfpga_wildcard_string.h b/libopenfpga/libopenfpgautil/src/openfpga_wildcard_string.h new file mode 100644 index 000000000..874641cf4 --- /dev/null +++ b/libopenfpga/libopenfpgautil/src/openfpga_wildcard_string.h @@ -0,0 +1,57 @@ +#ifndef OPENFPGA_WILDCARD_STRING_H +#define OPENFPGA_WILDCARD_STRING_H + +/******************************************************************** + * Include header files that are required by data structure declaration + *******************************************************************/ +#include +#include + +/* namespace openfpga begins */ +namespace openfpga { + +/************************************************************************ + * This file includes a object that can apply wildcard characters + * By default it will replace any digital numbers with a '*' character + * Users can set the wildcard character on their needs + * + * Example: + * std::string orig_str; + * WildCardString wc_str(orig_str); + * std::string output = wc_str.data(); + * + ***********************************************************************/ + +class WildCardString { + public : /* Constructors*/ + WildCardString (const std::string& data); + + public : /* Public Accessors */ + std::string data() const; + + public : /* Public Mutators */ + /* Give a string to apply wildcards */ + void set_data(const std::string& data); + + private : /* Private Mutators */ + /* Use default wildcard character '*' */ + void set_default_wildcard_char(); + + /* Use default sensitive words which are numbers */ + void set_default_sensitive_chars(); + + /* Replace sensitive words with wildcard characters */ + void apply_wildcard_char(); + + /* Remove redundant wildcard chars (which are next to each other) */ + void compress(); + + private : /* Internal data */ + std::string data_; /* Lines to be splited */ + std::vector sensitive_chars_; + char wildcard_char_; +}; + +} /* namespace openfpga ends */ + +#endif diff --git a/openfpga/src/base/openfpga_naming.cpp b/openfpga/src/base/openfpga_naming.cpp index 0796d4df1..74750fca5 100644 --- a/openfpga/src/base/openfpga_naming.cpp +++ b/openfpga/src/base/openfpga_naming.cpp @@ -30,6 +30,20 @@ std::string generate_instance_name(const std::string& instance_name, return instance_name + std::string("_") + std::to_string(instance_id) + std::string("_"); } +/************************************************ + * A generic function to generate the instance name + * in the following format: + * __ + * This is mainly used by module manager to give a default + * name for each instance when outputting the module + * in Verilog/SPICE format + ***********************************************/ +std::string generate_instance_wildcard_name(const std::string& instance_name, + const std::string& wildcard_str) { + return instance_name + std::string("_") + wildcard_str + std::string("_"); +} + + /************************************************ * Generate the node name for a multiplexing structure * Case 1 : If there is an intermediate buffer followed by, diff --git a/openfpga/src/base/openfpga_naming.h b/openfpga/src/base/openfpga_naming.h index d3307fe3c..9896dddd4 100644 --- a/openfpga/src/base/openfpga_naming.h +++ b/openfpga/src/base/openfpga_naming.h @@ -26,6 +26,9 @@ namespace openfpga { std::string generate_instance_name(const std::string& instance_name, const size_t& instance_id); +std::string generate_instance_wildcard_name(const std::string& instance_name, + const std::string& wildcard_str); + std::string generate_mux_node_name(const size_t& node_level, const bool& add_buffer_postfix); diff --git a/openfpga/src/base/openfpga_sdc.cpp b/openfpga/src/base/openfpga_sdc.cpp index 21a3ab8fb..c0c5b3f22 100644 --- a/openfpga/src/base/openfpga_sdc.cpp +++ b/openfpga/src/base/openfpga_sdc.cpp @@ -29,6 +29,7 @@ int write_pnr_sdc(OpenfpgaContext& openfpga_ctx, const Command& cmd, const CommandContext& cmd_context) { CommandOptionId opt_output_dir = cmd.option("file"); + CommandOptionId opt_flatten_names = cmd.option("flatten_names"); CommandOptionId opt_constrain_global_port = cmd.option("constrain_global_port"); CommandOptionId opt_constrain_non_clock_global_port = cmd.option("constrain_non_clock_global_port"); CommandOptionId opt_constrain_grid = cmd.option("constrain_grid"); @@ -49,6 +50,7 @@ int write_pnr_sdc(OpenfpgaContext& openfpga_ctx, PnrSdcOption options(sdc_dir_path); + options.set_flatten_names(cmd_context.option_enable(cmd, opt_flatten_names)); options.set_constrain_global_port(cmd_context.option_enable(cmd, opt_constrain_global_port)); options.set_constrain_non_clock_global_port(cmd_context.option_enable(cmd, opt_constrain_non_clock_global_port)); options.set_constrain_grid(cmd_context.option_enable(cmd, opt_constrain_grid)); @@ -95,6 +97,7 @@ int write_analysis_sdc(OpenfpgaContext& openfpga_ctx, const Command& cmd, const CommandContext& cmd_context) { CommandOptionId opt_output_dir = cmd.option("file"); + CommandOptionId opt_flatten_names = cmd.option("flatten_names"); /* This is an intermediate data structure which is designed to modularize the FPGA-SDC * Keep it independent from any other outside data structures @@ -106,6 +109,7 @@ int write_analysis_sdc(OpenfpgaContext& openfpga_ctx, AnalysisSdcOption options(sdc_dir_path); options.set_generate_sdc_analysis(true); + options.set_flatten_names(cmd_context.option_enable(cmd, opt_flatten_names)); /* Collect global ports from the circuit library: * TODO: should we place this in the OpenFPGA context? diff --git a/openfpga/src/base/openfpga_sdc_command.cpp b/openfpga/src/base/openfpga_sdc_command.cpp index 551b37d2b..02595a10c 100644 --- a/openfpga/src/base/openfpga_sdc_command.cpp +++ b/openfpga/src/base/openfpga_sdc_command.cpp @@ -26,6 +26,9 @@ ShellCommandId add_openfpga_write_pnr_sdc_command(openfpga::Shell skip_mib; + std::map> wildcard_names; + /* For each configurable child, we will go one level down in priority */ for (size_t child_index = 0; child_index < module_manager.configurable_children(parent_module).size(); ++child_index) { std::string child_module_path = parent_module_path; ModuleId child_module_id = module_manager.configurable_children(parent_module)[child_index]; size_t child_instance_id = module_manager.configurable_child_instances(parent_module)[child_index]; + std::string child_instance_name; if (true == module_manager.instance_name(parent_module, child_module_id, child_instance_id).empty()) { - if (0 < skip_mib.count(child_module_id)) { - VTR_ASSERT(skip_mib.at(child_module_id) = true); + child_instance_name = generate_instance_name(module_manager.module_name(child_module_id), child_instance_id); + } else { + child_instance_name = module_manager.instance_name(parent_module, child_module_id, child_instance_id); + } + + if (false == flatten_names) { + /* Try to adapt to a wildcard name: replace all the numbers with a wildcard character '*' */ + WildCardString wildcard_str(child_instance_name); + /* If the wildcard name is already in the list, we can skip this + * Otherwise, we have to + * - output this instance + * - record the wildcard name in the map + */ + if ( (0 < wildcard_names.count(child_module_id)) + && (wildcard_names.at(child_module_id).end() != std::find(wildcard_names.at(child_module_id).begin(), + wildcard_names.at(child_module_id).end(), + wildcard_str.data())) ) { continue; } - /* Give a default name __ */ - child_module_path += module_manager.module_name(child_module_id); - child_module_path += "_"; - child_module_path += "*"; - //child_module_path += std::to_string(child_instance_id); - child_module_path += "_"; - /* If we use wild card to disable all the other instance - * So we can skip later if there is no specific instance name - */ - skip_mib[child_module_id] = true; + + child_module_path += wildcard_str.data(); + + wildcard_names[child_module_id].push_back(wildcard_str.data()); } else { - child_module_path += module_manager.instance_name(parent_module, child_module_id, child_instance_id); + child_module_path += child_instance_name; } + child_module_path = format_dir_path(child_module_path); - rec_print_pnr_sdc_disable_configurable_memory_module_output(fp, module_manager, + rec_print_pnr_sdc_disable_configurable_memory_module_output(fp, flatten_names, + module_manager, child_module_id, child_module_path); } diff --git a/openfpga/src/fpga_sdc/sdc_memory_utils.h b/openfpga/src/fpga_sdc/sdc_memory_utils.h index 7fbf92294..576b3a36e 100644 --- a/openfpga/src/fpga_sdc/sdc_memory_utils.h +++ b/openfpga/src/fpga_sdc/sdc_memory_utils.h @@ -16,6 +16,7 @@ namespace openfpga { void rec_print_pnr_sdc_disable_configurable_memory_module_output(std::fstream& fp, + const bool& flatten_names, const ModuleManager& module_manager, const ModuleId& parent_module, const std::string& parent_module_path); From ecdbdcb59254ba3a79032ababe2f4bc29de74f4e Mon Sep 17 00:00:00 2001 From: Xifan Tang Date: Sat, 2 May 2020 14:23:20 -0600 Subject: [PATCH 497/645] update documentation on new SDC options --- docs/source/openfpga_shell/openfpga_commands.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/source/openfpga_shell/openfpga_commands.rst b/docs/source/openfpga_shell/openfpga_commands.rst index 543b87397..12a1d89c7 100644 --- a/docs/source/openfpga_shell/openfpga_commands.rst +++ b/docs/source/openfpga_shell/openfpga_commands.rst @@ -176,6 +176,8 @@ FPGA-SDC - ``--file`` or ``-f`` Specify the output directory for SDC files + - ``--flatten_names`` Use flatten names (no wildcards) in SDC files + - ``--constrain_global_port`` Constrain all the global ports of FPGA fabric. - ``--constrain_non_clock_global_port`` Constrain all the non-clock global ports as clocks ports of FPGA fabric @@ -205,3 +207,5 @@ FPGA-SDC Write the SDC to run timing analysis for a mapped FPGA fabric - ``--file`` or ``-f`` Specify the output directory for SDC files + + - ``--flatten_names`` Use flatten names (no wildcards) in SDC files From d0793d90298e8edb70a5b8a4ef7bda9116d3f77d Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 2 May 2020 15:52:17 -0600 Subject: [PATCH 498/645] now disable_sb_output support wildcard --- openfpga/src/base/openfpga_sdc_command.cpp | 4 +- openfpga/src/fpga_sdc/pnr_sdc_writer.cpp | 241 ++++++++++++++++----- 2 files changed, 186 insertions(+), 59 deletions(-) diff --git a/openfpga/src/base/openfpga_sdc_command.cpp b/openfpga/src/base/openfpga_sdc_command.cpp index 02595a10c..77c581da2 100644 --- a/openfpga/src/base/openfpga_sdc_command.cpp +++ b/openfpga/src/base/openfpga_sdc_command.cpp @@ -27,7 +27,7 @@ ShellCommandId add_openfpga_write_pnr_sdc_command(openfpga::Shell sb_range = device_rr_gsb.get_gsb_range(); - /* Go for each SB */ - for (size_t ix = 0; ix < sb_range.x(); ++ix) { - for (size_t iy = 0; iy < sb_range.y(); ++iy) { - const RRGSB& rr_gsb = device_rr_gsb.get_gsb(ix, iy); - - if (false == rr_gsb.is_sb_exist()) { - continue; - } - - vtr::Point gsb_coordinate(rr_gsb.get_sb_x(), rr_gsb.get_sb_y()); - std::string sb_instance_name = generate_switch_block_module_name(gsb_coordinate); - - ModuleId sb_module = module_manager.find_module(sb_instance_name); - VTR_ASSERT(true == module_manager.valid_module_id(sb_module)); - - /* Disable the outputs of the module */ - for (const BasicPort& output_port : module_manager.module_ports_by_type(sb_module, ModuleManager::MODULE_OUTPUT_PORT)) { - fp << "set_disable_timing " << sb_instance_name << "/" << output_port.get_name() << std::endl; - fp << std::endl; - } - } - } - - /* Close file handler */ - fp.close(); -} - -/******************************************************************** - * Break combinational loops in FPGA fabric, which mainly come from - * loops of multiplexers. - * To handle this, we disable the timing at outputs of Switch blocks - * This function is designed for compact routing hierarchy - *******************************************************************/ -static -void print_pnr_sdc_compact_routing_disable_switch_block_outputs(const std::string& sdc_dir, + const bool& flatten_names, const ModuleManager& module_manager, const ModuleId& top_module, const DeviceRRGSB& device_rr_gsb) { @@ -209,6 +156,131 @@ void print_pnr_sdc_compact_routing_disable_switch_block_outputs(const std::strin /* Generate the descriptions*/ print_sdc_file_header(fp, std::string("Disable Switch Block outputs for PnR")); + std::string root_path = format_dir_path(module_manager.module_name(top_module)); + + /* Build wildcard names for the instance names of multiple-instanced-blocks (MIB) + * We will find all the instance names and see there are common prefix + * If so, we can use wildcards + */ + std::map> wildcard_names; + + /* Get the range of SB array */ + vtr::Point sb_range = device_rr_gsb.get_gsb_range(); + /* Go for each SB */ + for (size_t ix = 0; ix < sb_range.x(); ++ix) { + for (size_t iy = 0; iy < sb_range.y(); ++iy) { + const RRGSB& rr_gsb = device_rr_gsb.get_gsb(ix, iy); + + if (false == rr_gsb.is_sb_exist()) { + continue; + } + + std::string module_path = root_path; + + vtr::Point gsb_coordinate(rr_gsb.get_sb_x(), rr_gsb.get_sb_y()); + std::string sb_instance_name = generate_switch_block_module_name(gsb_coordinate); + + ModuleId sb_module = module_manager.find_module(sb_instance_name); + VTR_ASSERT(true == module_manager.valid_module_id(sb_module)); + + if (false == flatten_names) { + /* Try to adapt to a wildcard name: replace all the numbers with a wildcard character '*' */ + WildCardString wildcard_str(sb_instance_name); + /* If the wildcard name is already in the list, we can skip this + * Otherwise, we have to + * - output this instance + * - record the wildcard name in the map + */ + if ( (0 < wildcard_names.count(sb_module)) + && (wildcard_names.at(sb_module).end() != std::find(wildcard_names.at(sb_module).begin(), + wildcard_names.at(sb_module).end(), + wildcard_str.data())) ) { + continue; + } + + module_path += wildcard_str.data(); + + wildcard_names[sb_module].push_back(wildcard_str.data()); + } else { + module_path += sb_instance_name; + } + + module_path = format_dir_path(module_path); + + std::vector port_wildcard_names; + + /* Disable the outputs of the module */ + for (const BasicPort& output_port : module_manager.module_ports_by_type(sb_module, ModuleManager::MODULE_OUTPUT_PORT)) { + std::string port_name = output_port.get_name(); + + if (false == flatten_names) { + /* Try to adapt to a wildcard name: replace all the numbers with a wildcard character '*' */ + WildCardString port_wildcard_str(output_port.get_name()); + /* If the wildcard name is already in the list, we can skip this + * Otherwise, we have to + * - output this port + * - record the wildcard name in the vector + */ + if (port_wildcard_names.end() != std::find(port_wildcard_names.begin(), + port_wildcard_names.end(), + port_wildcard_str.data())) { + continue; + } + + port_name = port_wildcard_str.data(); + + port_wildcard_names.push_back(port_wildcard_str.data()); + } + + fp << "set_disable_timing "; + fp << module_path; + fp << port_name << std::endl; + + fp << std::endl; + } + } + } + + /* Close file handler */ + fp.close(); +} + +/******************************************************************** + * Break combinational loops in FPGA fabric, which mainly come from + * loops of multiplexers. + * To handle this, we disable the timing at outputs of Switch blocks + * This function is designed for compact routing hierarchy + *******************************************************************/ +static +void print_pnr_sdc_compact_routing_disable_switch_block_outputs(const std::string& sdc_dir, + const bool& flatten_names, + const ModuleManager& module_manager, + const ModuleId& top_module, + const DeviceRRGSB& device_rr_gsb) { + /* Create the file name for Verilog netlist */ + std::string sdc_fname(sdc_dir + std::string(SDC_DISABLE_SB_OUTPUTS_FILE_NAME)); + + /* Start time count */ + std::string timer_message = std::string("Write SDC to disable switch block outputs for P&R flow '") + sdc_fname + std::string("'"); + vtr::ScopedStartFinishTimer timer(timer_message); + + /* Create the file stream */ + std::fstream fp; + fp.open(sdc_fname, std::fstream::out | std::fstream::trunc); + + check_file_stream(sdc_fname.c_str(), fp); + + /* Generate the descriptions*/ + print_sdc_file_header(fp, std::string("Disable Switch Block outputs for PnR")); + + std::string root_path = format_dir_path(module_manager.module_name(top_module)); + + /* Build wildcard names for the instance names of multiple-instanced-blocks (MIB) + * We will find all the instance names and see there are common prefix + * If so, we can use wildcards + */ + std::map> wildcard_names; + /* Build unique switch block modules */ for (size_t isb = 0; isb < device_rr_gsb.get_num_sb_unique_module(); ++isb) { const RRGSB& rr_gsb = device_rr_gsb.get_sb_unique_module(isb); @@ -217,13 +289,66 @@ void print_pnr_sdc_compact_routing_disable_switch_block_outputs(const std::strin ModuleId sb_module = module_manager.find_module(sb_module_name); VTR_ASSERT(true == module_manager.valid_module_id(sb_module)); + + std::string module_path = root_path; /* Find all the instances in the top-level module */ for (const size_t& instance_id : module_manager.child_module_instances(top_module, sb_module)) { std::string sb_instance_name = module_manager.instance_name(top_module, sb_module, instance_id); + + if (false == flatten_names) { + /* Try to adapt to a wildcard name: replace all the numbers with a wildcard character '*' */ + WildCardString wildcard_str(sb_instance_name); + /* If the wildcard name is already in the list, we can skip this + * Otherwise, we have to + * - output this instance + * - record the wildcard name in the map + */ + if ( (0 < wildcard_names.count(sb_module)) + && (wildcard_names.at(sb_module).end() != std::find(wildcard_names.at(sb_module).begin(), + wildcard_names.at(sb_module).end(), + wildcard_str.data())) ) { + continue; + } + + module_path += wildcard_str.data(); + + wildcard_names[sb_module].push_back(wildcard_str.data()); + } else { + module_path += sb_instance_name; + } + + module_path = format_dir_path(module_path); + + std::vector port_wildcard_names; + /* Disable the outputs of the module */ for (const BasicPort& output_port : module_manager.module_ports_by_type(sb_module, ModuleManager::MODULE_OUTPUT_PORT)) { - fp << "set_disable_timing " << sb_instance_name << "/" << output_port.get_name() << std::endl; + std::string port_name = output_port.get_name(); + + if (false == flatten_names) { + /* Try to adapt to a wildcard name: replace all the numbers with a wildcard character '*' */ + WildCardString port_wildcard_str(output_port.get_name()); + /* If the wildcard name is already in the list, we can skip this + * Otherwise, we have to + * - output this port + * - record the wildcard name in the vector + */ + if (port_wildcard_names.end() != std::find(port_wildcard_names.begin(), + port_wildcard_names.end(), + port_wildcard_str.data())) { + continue; + } + + port_name = port_wildcard_str.data(); + + port_wildcard_names.push_back(port_wildcard_str.data()); + } + + fp << "set_disable_timing "; + fp << module_path; + fp << port_name << std::endl; + fp << std::endl; } } @@ -282,12 +407,14 @@ void print_pnr_sdc(const PnrSdcOption& sdc_options, if (true == sdc_options.constrain_switch_block_outputs()) { if (true == compact_routing_hierarchy) { print_pnr_sdc_compact_routing_disable_switch_block_outputs(sdc_options.sdc_dir(), + sdc_options.flatten_names(), module_manager, top_module, device_rr_gsb); } else { VTR_ASSERT_SAFE (false == compact_routing_hierarchy); print_pnr_sdc_flatten_routing_disable_switch_block_outputs(sdc_options.sdc_dir(), - module_manager, + sdc_options.flatten_names(), + module_manager, top_module, device_rr_gsb); } } From 7503c58fb277c0a798bcd8f9f675d67f6d4f9e39 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 2 May 2020 16:33:43 -0600 Subject: [PATCH 499/645] small fix on SDC generator for SB which do not exist in FPGA --- openfpga/src/fpga_sdc/pnr_sdc_writer.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp b/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp index e3c5625ef..a26b67a71 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp +++ b/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp @@ -284,6 +284,11 @@ void print_pnr_sdc_compact_routing_disable_switch_block_outputs(const std::strin /* Build unique switch block modules */ for (size_t isb = 0; isb < device_rr_gsb.get_num_sb_unique_module(); ++isb) { const RRGSB& rr_gsb = device_rr_gsb.get_sb_unique_module(isb); + + if (false == rr_gsb.is_sb_exist()) { + continue; + } + vtr::Point gsb_coordinate(rr_gsb.get_sb_x(), rr_gsb.get_sb_y()); std::string sb_module_name = generate_switch_block_module_name(gsb_coordinate); From 7e82c23f52af2d351e8387ffbb451fc93a5c6d85 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 2 May 2020 18:31:37 -0600 Subject: [PATCH 500/645] now add SDC generator supports both hierarchical and flatten in writing timing constraints --- openfpga/src/base/openfpga_sdc.cpp | 2 + openfpga/src/base/openfpga_sdc_command.cpp | 5 +- openfpga/src/fpga_sdc/pnr_sdc_option.cpp | 9 +++ openfpga/src/fpga_sdc/pnr_sdc_option.h | 3 + .../src/fpga_sdc/pnr_sdc_routing_writer.cpp | 81 +++++++++++++++++-- .../src/fpga_sdc/pnr_sdc_routing_writer.h | 4 + openfpga/src/fpga_sdc/pnr_sdc_writer.cpp | 4 + openfpga/src/fpga_sdc/sdc_writer_utils.cpp | 40 +++++++++ openfpga/src/fpga_sdc/sdc_writer_utils.h | 7 ++ 9 files changed, 149 insertions(+), 6 deletions(-) diff --git a/openfpga/src/base/openfpga_sdc.cpp b/openfpga/src/base/openfpga_sdc.cpp index c0c5b3f22..9fa360cff 100644 --- a/openfpga/src/base/openfpga_sdc.cpp +++ b/openfpga/src/base/openfpga_sdc.cpp @@ -30,6 +30,7 @@ int write_pnr_sdc(OpenfpgaContext& openfpga_ctx, CommandOptionId opt_output_dir = cmd.option("file"); CommandOptionId opt_flatten_names = cmd.option("flatten_names"); + CommandOptionId opt_hierarchical = cmd.option("hierarchical"); CommandOptionId opt_constrain_global_port = cmd.option("constrain_global_port"); CommandOptionId opt_constrain_non_clock_global_port = cmd.option("constrain_non_clock_global_port"); CommandOptionId opt_constrain_grid = cmd.option("constrain_grid"); @@ -51,6 +52,7 @@ int write_pnr_sdc(OpenfpgaContext& openfpga_ctx, PnrSdcOption options(sdc_dir_path); options.set_flatten_names(cmd_context.option_enable(cmd, opt_flatten_names)); + options.set_hierarchical(cmd_context.option_enable(cmd, opt_hierarchical)); options.set_constrain_global_port(cmd_context.option_enable(cmd, opt_constrain_global_port)); options.set_constrain_non_clock_global_port(cmd_context.option_enable(cmd, opt_constrain_non_clock_global_port)); options.set_constrain_grid(cmd_context.option_enable(cmd, opt_constrain_grid)); diff --git a/openfpga/src/base/openfpga_sdc_command.cpp b/openfpga/src/base/openfpga_sdc_command.cpp index 77c581da2..479cb8de9 100644 --- a/openfpga/src/base/openfpga_sdc_command.cpp +++ b/openfpga/src/base/openfpga_sdc_command.cpp @@ -29,6 +29,9 @@ ShellCommandId add_openfpga_write_pnr_sdc_command(openfpga::Shell sb_range = device_rr_gsb.get_gsb_range(); /* Go for each SB */ @@ -186,7 +207,24 @@ void print_pnr_sdc_flatten_routing_constrain_sb_timing(const std::string& sdc_di if (false == rr_gsb.is_sb_exist()) { continue; } + + vtr::Point gsb_coordinate(rr_gsb.get_sb_x(), rr_gsb.get_sb_y()); + std::string sb_instance_name = generate_switch_block_module_name(gsb_coordinate); + + ModuleId sb_module = module_manager.find_module(sb_instance_name); + VTR_ASSERT(true == module_manager.valid_module_id(sb_module)); + + std::string module_path = root_path + + std::string("\\/") + + std::string("("); + + /* Find all the instances in the top-level module */ + module_path += sb_instance_name; + module_path += std::string(")") + std::string("\\"); + print_pnr_sdc_constrain_sb_timing(sdc_dir, + hierarchical, + module_path, module_manager, rr_graph, rr_gsb, @@ -200,7 +238,9 @@ void print_pnr_sdc_flatten_routing_constrain_sb_timing(const std::string& sdc_di * This function is designed for compact routing hierarchy *******************************************************************/ void print_pnr_sdc_compact_routing_constrain_sb_timing(const std::string& sdc_dir, + const bool& hierarchical, const ModuleManager& module_manager, + const ModuleId& top_module, const RRGraph& rr_graph, const DeviceRRGSB& device_rr_gsb, const bool& constrain_zero_delay_paths) { @@ -208,12 +248,43 @@ void print_pnr_sdc_compact_routing_constrain_sb_timing(const std::string& sdc_di /* Start time count */ vtr::ScopedStartFinishTimer timer("Write SDC for constrain Switch Block timing for P&R flow"); + std::string root_path = module_manager.module_name(top_module); + for (size_t isb = 0; isb < device_rr_gsb.get_num_sb_unique_module(); ++isb) { const RRGSB& rr_gsb = device_rr_gsb.get_sb_unique_module(isb); if (false == rr_gsb.is_sb_exist()) { continue; } + + /* Find all the sb instance under this module + * Create a regular expression to include these instance names + */ + vtr::Point gsb_coordinate(rr_gsb.get_sb_x(), rr_gsb.get_sb_y()); + std::string sb_module_name = generate_switch_block_module_name(gsb_coordinate); + + ModuleId sb_module = module_manager.find_module(sb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(sb_module)); + + std::string module_path = root_path + + std::string("\\/") + + std::string("("); + + /* Find all the instances in the top-level module */ + bool first_element = true; + for (const size_t& instance_id : module_manager.child_module_instances(top_module, sb_module)) { + std::string sb_instance_name = module_manager.instance_name(top_module, sb_module, instance_id); + if (false == first_element) { + module_path += std::string("|"); + } + module_path += sb_instance_name; + first_element = false; + } + + module_path += std::string(")") + std::string("\\"); + print_pnr_sdc_constrain_sb_timing(sdc_dir, + hierarchical, + module_path, module_manager, rr_graph, rr_gsb, diff --git a/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.h b/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.h index 0ced07938..aa0d04cbc 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.h +++ b/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.h @@ -18,13 +18,17 @@ namespace openfpga { void print_pnr_sdc_flatten_routing_constrain_sb_timing(const std::string& sdc_dir, + const bool& hierarchical, const ModuleManager& module_manager, + const ModuleId& top_module, const RRGraph& rr_graph, const DeviceRRGSB& device_rr_gsb, const bool& constrain_zero_delay_paths); void print_pnr_sdc_compact_routing_constrain_sb_timing(const std::string& sdc_dir, + const bool& hierarchical, const ModuleManager& module_manager, + const ModuleId& top_module, const RRGraph& rr_graph, const DeviceRRGSB& device_rr_gsb, const bool& constrain_zero_delay_paths); diff --git a/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp b/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp index a26b67a71..4b363d7d7 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp +++ b/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp @@ -428,14 +428,18 @@ void print_pnr_sdc(const PnrSdcOption& sdc_options, if (true == sdc_options.constrain_sb()) { if (true == compact_routing_hierarchy) { print_pnr_sdc_compact_routing_constrain_sb_timing(sdc_options.sdc_dir(), + sdc_options.hierarchical(), module_manager, + top_module, device_ctx.rr_graph, device_rr_gsb, sdc_options.constrain_zero_delay_paths()); } else { VTR_ASSERT_SAFE (false == compact_routing_hierarchy); print_pnr_sdc_flatten_routing_constrain_sb_timing(sdc_options.sdc_dir(), + sdc_options.hierarchical(), module_manager, + top_module, device_ctx.rr_graph, device_rr_gsb, sdc_options.constrain_zero_delay_paths()); diff --git a/openfpga/src/fpga_sdc/sdc_writer_utils.cpp b/openfpga/src/fpga_sdc/sdc_writer_utils.cpp index 4f021034c..13b70fec4 100644 --- a/openfpga/src/fpga_sdc/sdc_writer_utils.cpp +++ b/openfpga/src/fpga_sdc/sdc_writer_utils.cpp @@ -101,6 +101,46 @@ void print_pnr_sdc_constrain_max_delay(std::fstream& fp, fp << std::endl; } +/******************************************************************** + * Constrain a path between two ports of a module with a given maximum timing value + * This function use regular expression and get_pins which are + * from open-source SDC 2.1 format + *******************************************************************/ +void print_pnr_sdc_regexp_constrain_max_delay(std::fstream& fp, + const std::string& src_instance_name, + const std::string& src_port_name, + const std::string& des_instance_name, + const std::string& des_port_name, + const float& delay) { + /* Validate file stream */ + valid_file_stream(fp); + + fp << "set_max_delay"; + + fp << " -from "; + fp << "[get_pins -regexp \""; + if (!src_instance_name.empty()) { + fp << src_instance_name << "/"; + } + fp << src_port_name; + + fp << "\"]"; + + fp << " -to "; + fp << "[get_pins -regexp \""; + + if (!des_instance_name.empty()) { + fp << des_instance_name << "/"; + } + fp << des_port_name; + + fp << "\"]"; + + fp << " " << std::setprecision(10) << delay; + + fp << std::endl; +} + /******************************************************************** * Constrain a path between two ports of a module with a given minimum timing value *******************************************************************/ diff --git a/openfpga/src/fpga_sdc/sdc_writer_utils.h b/openfpga/src/fpga_sdc/sdc_writer_utils.h index 80f6e97e9..c7a7cd3a1 100644 --- a/openfpga/src/fpga_sdc/sdc_writer_utils.h +++ b/openfpga/src/fpga_sdc/sdc_writer_utils.h @@ -28,6 +28,13 @@ void print_pnr_sdc_constrain_max_delay(std::fstream& fp, const std::string& des_port_name, const float& delay); +void print_pnr_sdc_regexp_constrain_max_delay(std::fstream& fp, + const std::string& src_instance_name, + const std::string& src_port_name, + const std::string& des_instance_name, + const std::string& des_port_name, + const float& delay); + void print_pnr_sdc_constrain_min_delay(std::fstream& fp, const std::string& src_instance_name, const std::string& src_port_name, From d18e924a899e16eab1c279d1c9fd2e07b80d234c Mon Sep 17 00:00:00 2001 From: Xifan Tang Date: Sat, 2 May 2020 18:38:00 -0600 Subject: [PATCH 501/645] Update documentation on new fpga_sdc option --- docs/source/openfpga_shell/openfpga_commands.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source/openfpga_shell/openfpga_commands.rst b/docs/source/openfpga_shell/openfpga_commands.rst index 12a1d89c7..d3020e9c1 100644 --- a/docs/source/openfpga_shell/openfpga_commands.rst +++ b/docs/source/openfpga_shell/openfpga_commands.rst @@ -176,6 +176,8 @@ FPGA-SDC - ``--file`` or ``-f`` Specify the output directory for SDC files + - ``--hierarchical`` Output SDC files without full path in hierarchy + - ``--flatten_names`` Use flatten names (no wildcards) in SDC files - ``--constrain_global_port`` Constrain all the global ports of FPGA fabric. From 609115e51f635c86eab258edad976250fa3baede Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 2 May 2020 19:02:35 -0600 Subject: [PATCH 502/645] now hierarchical SDC generation is applicable to CB timing constraints --- .../src/fpga_sdc/pnr_sdc_routing_writer.cpp | 137 ++++++++++++++++-- .../src/fpga_sdc/pnr_sdc_routing_writer.h | 4 + openfpga/src/fpga_sdc/pnr_sdc_writer.cpp | 4 + 3 files changed, 133 insertions(+), 12 deletions(-) diff --git a/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp b/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp index 6f7d77c69..60e756839 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp +++ b/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp @@ -298,6 +298,8 @@ void print_pnr_sdc_compact_routing_constrain_sb_timing(const std::string& sdc_di *******************************************************************/ static void print_pnr_sdc_constrain_cb_mux_timing(std::fstream& fp, + const bool& hierarchical, + const std::string& module_path, const ModuleManager& module_manager, const ModuleId& cb_module, const RRGraph& rr_graph, @@ -366,11 +368,22 @@ void print_pnr_sdc_constrain_cb_mux_timing(std::fstream& fp, } /* Constrain a path */ - print_pnr_sdc_constrain_port2port_timing(fp, - module_manager, - cb_module, module_input_port, - cb_module, module_output_port, - switch_delays[module_input_port]); + if (true == hierarchical) { + print_pnr_sdc_constrain_port2port_timing(fp, + module_manager, + cb_module, module_input_port, + cb_module, module_output_port, + switch_delays[module_input_port]); + } else { + VTR_ASSERT_SAFE(false == hierarchical); + print_pnr_sdc_regexp_constrain_max_delay(fp, + std::string(module_path), + generate_sdc_port(module_manager.module_port(cb_module, module_input_port)), + std::string(module_path), + generate_sdc_port(module_manager.module_port(cb_module, module_output_port)), + switch_delays[module_input_port]); + + } } } @@ -380,6 +393,8 @@ void print_pnr_sdc_constrain_cb_mux_timing(std::fstream& fp, *******************************************************************/ static void print_pnr_sdc_constrain_cb_timing(const std::string& sdc_dir, + const bool& hierarchical, + const std::string& module_path, const ModuleManager& module_manager, const RRGraph& rr_graph, const RRGSB& rr_gsb, @@ -443,11 +458,21 @@ void print_pnr_sdc_constrain_cb_timing(const std::string& sdc_dir, } /* Constrain a path with routing segment delay */ - print_pnr_sdc_constrain_port2port_timing(fp, - module_manager, - cb_module, input_port_id, - cb_module, output_port_id, - routing_segment_delay); + if (true == hierarchical) { + print_pnr_sdc_constrain_port2port_timing(fp, + module_manager, + cb_module, input_port_id, + cb_module, output_port_id, + routing_segment_delay); + } else { + VTR_ASSERT_SAFE(false == hierarchical); + print_pnr_sdc_regexp_constrain_max_delay(fp, + std::string(module_path), + generate_sdc_port(module_manager.module_port(cb_module, input_port_id)), + std::string(module_path), + generate_sdc_port(module_manager.module_port(cb_module, output_port_id)), + routing_segment_delay); + } } /* Contrain each multiplexers inside the connection block */ @@ -459,6 +484,7 @@ void print_pnr_sdc_constrain_cb_timing(const std::string& sdc_dir, for (size_t inode = 0; inode < rr_gsb.get_num_ipin_nodes(cb_ipin_side); ++inode) { const RRNodeId& ipin_rr_node = rr_gsb.get_ipin_node(cb_ipin_side, inode); print_pnr_sdc_constrain_cb_mux_timing(fp, + hierarchical, module_path, module_manager, cb_module, rr_graph, rr_gsb, cb_type, ipin_rr_node, @@ -476,7 +502,9 @@ void print_pnr_sdc_constrain_cb_timing(const std::string& sdc_dir, *******************************************************************/ static void print_pnr_sdc_flatten_routing_constrain_cb_timing(const std::string& sdc_dir, + const bool& hierarchical, const ModuleManager& module_manager, + const ModuleId& top_module, const RRGraph& rr_graph, const DeviceRRGSB& device_rr_gsb, const t_rr_type& cb_type, @@ -484,6 +512,8 @@ void print_pnr_sdc_flatten_routing_constrain_cb_timing(const std::string& sdc_di /* Build unique X-direction connection block modules */ vtr::Point cb_range = device_rr_gsb.get_gsb_range(); + std::string root_path = module_manager.module_name(top_module); + for (size_t ix = 0; ix < cb_range.x(); ++ix) { for (size_t iy = 0; iy < cb_range.y(); ++iy) { /* Check if the connection block exists in the device! @@ -494,7 +524,26 @@ void print_pnr_sdc_flatten_routing_constrain_cb_timing(const std::string& sdc_di if (false == rr_gsb.is_cb_exist(cb_type)) { continue; } + + /* Find all the cb instance under this module + * Create a regular expression to include these instance names + */ + vtr::Point gsb_coordinate(rr_gsb.get_cb_x(cb_type), rr_gsb.get_cb_y(cb_type)); + std::string cb_instance_name = generate_connection_block_module_name(cb_type, gsb_coordinate); + ModuleId cb_module = module_manager.find_module(cb_instance_name); + VTR_ASSERT(true == module_manager.valid_module_id(cb_module)); + + std::string module_path = root_path + + std::string("\\/") + + std::string("("); + + /* Find all the instances in the top-level module */ + module_path += cb_instance_name; + module_path += std::string(")") + std::string("\\"); + print_pnr_sdc_constrain_cb_timing(sdc_dir, + hierarchical, + module_path, module_manager, rr_graph, rr_gsb, @@ -510,7 +559,9 @@ void print_pnr_sdc_flatten_routing_constrain_cb_timing(const std::string& sdc_di * and print SDC file for each of them *******************************************************************/ void print_pnr_sdc_flatten_routing_constrain_cb_timing(const std::string& sdc_dir, + const bool& hierarchical, const ModuleManager& module_manager, + const ModuleId& top_module, const RRGraph& rr_graph, const DeviceRRGSB& device_rr_gsb, const bool& constrain_zero_delay_paths) { @@ -518,13 +569,15 @@ void print_pnr_sdc_flatten_routing_constrain_cb_timing(const std::string& sdc_di /* Start time count */ vtr::ScopedStartFinishTimer timer("Write SDC for constrain Connection Block timing for P&R flow"); - print_pnr_sdc_flatten_routing_constrain_cb_timing(sdc_dir, module_manager, + print_pnr_sdc_flatten_routing_constrain_cb_timing(sdc_dir, hierarchical, + module_manager, top_module, rr_graph, device_rr_gsb, CHANX, constrain_zero_delay_paths); - print_pnr_sdc_flatten_routing_constrain_cb_timing(sdc_dir, module_manager, + print_pnr_sdc_flatten_routing_constrain_cb_timing(sdc_dir, hierarchical, + module_manager, top_module, rr_graph, device_rr_gsb, CHANY, @@ -536,7 +589,9 @@ void print_pnr_sdc_flatten_routing_constrain_cb_timing(const std::string& sdc_di * This function is designed for compact routing hierarchy *******************************************************************/ void print_pnr_sdc_compact_routing_constrain_cb_timing(const std::string& sdc_dir, + const bool& hierarchical, const ModuleManager& module_manager, + const ModuleId& top_module, const RRGraph& rr_graph, const DeviceRRGSB& device_rr_gsb, const bool& constrain_zero_delay_paths) { @@ -544,10 +599,40 @@ void print_pnr_sdc_compact_routing_constrain_cb_timing(const std::string& sdc_di /* Start time count */ vtr::ScopedStartFinishTimer timer("Write SDC for constrain Connection Block timing for P&R flow"); + std::string root_path = module_manager.module_name(top_module); + /* Print SDC for unique X-direction connection block modules */ for (size_t icb = 0; icb < device_rr_gsb.get_num_cb_unique_module(CHANX); ++icb) { const RRGSB& unique_mirror = device_rr_gsb.get_cb_unique_module(CHANX, icb); + + /* Find all the cb instance under this module + * Create a regular expression to include these instance names + */ + vtr::Point gsb_coordinate(unique_mirror.get_cb_x(CHANX), unique_mirror.get_cb_y(CHANX)); + std::string cb_module_name = generate_connection_block_module_name(CHANX, gsb_coordinate); + ModuleId cb_module = module_manager.find_module(cb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(cb_module)); + + std::string module_path = root_path + + std::string("\\/") + + std::string("("); + + /* Find all the instances in the top-level module */ + bool first_element = true; + for (const size_t& instance_id : module_manager.child_module_instances(top_module, cb_module)) { + std::string cb_instance_name = module_manager.instance_name(top_module, cb_module, instance_id); + if (false == first_element) { + module_path += std::string("|"); + } + module_path += cb_instance_name; + first_element = false; + } + + module_path += std::string(")") + std::string("\\"); + print_pnr_sdc_constrain_cb_timing(sdc_dir, + hierarchical, + module_path, module_manager, rr_graph, unique_mirror, @@ -558,7 +643,35 @@ void print_pnr_sdc_compact_routing_constrain_cb_timing(const std::string& sdc_di /* Print SDC for unique Y-direction connection block modules */ for (size_t icb = 0; icb < device_rr_gsb.get_num_cb_unique_module(CHANY); ++icb) { const RRGSB& unique_mirror = device_rr_gsb.get_cb_unique_module(CHANY, icb); + + /* Find all the cb instance under this module + * Create a regular expression to include these instance names + */ + vtr::Point gsb_coordinate(unique_mirror.get_cb_x(CHANY), unique_mirror.get_cb_y(CHANY)); + std::string cb_module_name = generate_connection_block_module_name(CHANY, gsb_coordinate); + ModuleId cb_module = module_manager.find_module(cb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(cb_module)); + + std::string module_path = root_path + + std::string("\\/") + + std::string("("); + + /* Find all the instances in the top-level module */ + bool first_element = true; + for (const size_t& instance_id : module_manager.child_module_instances(top_module, cb_module)) { + std::string cb_instance_name = module_manager.instance_name(top_module, cb_module, instance_id); + if (false == first_element) { + module_path += std::string("|"); + } + module_path += cb_instance_name; + first_element = false; + } + + module_path += std::string(")") + std::string("\\"); + print_pnr_sdc_constrain_cb_timing(sdc_dir, + hierarchical, + module_path, module_manager, rr_graph, unique_mirror, diff --git a/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.h b/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.h index aa0d04cbc..a29a7ac56 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.h +++ b/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.h @@ -34,13 +34,17 @@ void print_pnr_sdc_compact_routing_constrain_sb_timing(const std::string& sdc_di const bool& constrain_zero_delay_paths); void print_pnr_sdc_flatten_routing_constrain_cb_timing(const std::string& sdc_dir, + const bool& hierarchical, const ModuleManager& module_manager, + const ModuleId& top_module, const RRGraph& rr_graph, const DeviceRRGSB& device_rr_gsb, const bool& constrain_zero_delay_paths); void print_pnr_sdc_compact_routing_constrain_cb_timing(const std::string& sdc_dir, + const bool& hierarchical, const ModuleManager& module_manager, + const ModuleId& top_module, const RRGraph& rr_graph, const DeviceRRGSB& device_rr_gsb, const bool& constrain_zero_delay_paths); diff --git a/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp b/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp index 4b363d7d7..167ec497b 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp +++ b/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp @@ -450,14 +450,18 @@ void print_pnr_sdc(const PnrSdcOption& sdc_options, if (true == sdc_options.constrain_cb()) { if (true == compact_routing_hierarchy) { print_pnr_sdc_compact_routing_constrain_cb_timing(sdc_options.sdc_dir(), + sdc_options.hierarchical(), module_manager, + top_module, device_ctx.rr_graph, device_rr_gsb, sdc_options.constrain_zero_delay_paths()); } else { VTR_ASSERT_SAFE (false == compact_routing_hierarchy); print_pnr_sdc_flatten_routing_constrain_cb_timing(sdc_options.sdc_dir(), + sdc_options.hierarchical(), module_manager, + top_module, device_ctx.rr_graph, device_rr_gsb, sdc_options.constrain_zero_delay_paths()); From 0e44cf3ea353b78c6d7108c1c867dbb0f26395bf Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 2 May 2020 21:09:00 -0600 Subject: [PATCH 503/645] now SDC to disable routing multiplexer outputs can use wildcards --- openfpga/src/fpga_sdc/pnr_sdc_writer.cpp | 34 +++++-- openfpga/src/fpga_sdc/sdc_mux_utils.cpp | 113 +++++++++++++++++++++++ openfpga/src/fpga_sdc/sdc_mux_utils.h | 28 ++++++ 3 files changed, 165 insertions(+), 10 deletions(-) create mode 100644 openfpga/src/fpga_sdc/sdc_mux_utils.cpp create mode 100644 openfpga/src/fpga_sdc/sdc_mux_utils.h diff --git a/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp b/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp index 167ec497b..c88e3e2ef 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp +++ b/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp @@ -29,6 +29,7 @@ #include "sdc_writer_naming.h" #include "sdc_writer_utils.h" #include "sdc_memory_utils.h" +#include "sdc_mux_utils.h" #include "pnr_sdc_global_port.h" #include "pnr_sdc_routing_writer.h" #include "pnr_sdc_grid_writer.h" @@ -80,9 +81,11 @@ void print_pnr_sdc_constrain_configurable_memory_outputs(const std::string& sdc_ *******************************************************************/ static void print_sdc_disable_routing_multiplexer_outputs(const std::string& sdc_dir, + const bool& flatten_names, const MuxLibrary& mux_lib, const CircuitLibrary& circuit_lib, - const ModuleManager& module_manager) { + const ModuleManager& module_manager, + const ModuleId& top_module) { /* Create the file name for Verilog netlist */ std::string sdc_fname(sdc_dir + std::string(SDC_DISABLE_MUX_OUTPUTS_FILE_NAME)); @@ -112,16 +115,22 @@ void print_sdc_disable_routing_multiplexer_outputs(const std::string& sdc_dir, std::string mux_module_name = generate_mux_subckt_name(circuit_lib, mux_model, find_mux_num_datapath_inputs(circuit_lib, mux_model, mux_graph.num_inputs()), std::string("")); + /* Find the module name in module manager */ ModuleId mux_module = module_manager.find_module(mux_module_name); VTR_ASSERT(true == module_manager.valid_module_id(mux_module)); - - /* Disable the timing for the output ports */ - for (const BasicPort& output_port : module_manager.module_ports_by_type(mux_module, ModuleManager::MODULE_OUTPUT_PORT)) { - fp << "set_disable_timing [get_pins -filter \"name =~ " << output_port.get_name() << "*\" "; - fp << "-of [get_cells -hier -filter \"ref_lib_cell_name == " << mux_module_name << "\"]]" << std::endl; - fp << std::endl; - } + + /* Go recursively in the module manager, + * starting from the top-level module: instance id of the top-level module is 0 by default + * Disable all the outputs of child modules that matches the mux_module id + */ + rec_print_pnr_sdc_disable_routing_multiplexer_outputs(fp, + flatten_names, + module_manager, + top_module, + mux_module, + format_dir_path(module_manager.module_name(top_module))); + } /* Close file handler */ @@ -398,14 +407,19 @@ void print_pnr_sdc(const PnrSdcOption& sdc_options, /* Output Design Constraints to disable outputs of memory cells */ if (true == sdc_options.constrain_configurable_memory_outputs()) { - print_pnr_sdc_constrain_configurable_memory_outputs(sdc_options.sdc_dir(), sdc_options.flatten_names(), module_manager, top_module); + print_pnr_sdc_constrain_configurable_memory_outputs(sdc_options.sdc_dir(), + sdc_options.flatten_names(), + module_manager, + top_module); } /* Break loops from Multiplexer Output */ if (true == sdc_options.constrain_routing_multiplexer_outputs()) { print_sdc_disable_routing_multiplexer_outputs(sdc_options.sdc_dir(), + sdc_options.flatten_names(), mux_lib, circuit_lib, - module_manager); + module_manager, + top_module); } /* Break loops from any SB output */ diff --git a/openfpga/src/fpga_sdc/sdc_mux_utils.cpp b/openfpga/src/fpga_sdc/sdc_mux_utils.cpp new file mode 100644 index 000000000..ee30eae9b --- /dev/null +++ b/openfpga/src/fpga_sdc/sdc_mux_utils.cpp @@ -0,0 +1,113 @@ +/******************************************************************** + * Most utilized function used to constrain routing multiplexers in FPGA + * fabric using SDC commands + *******************************************************************/ + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" + +/* Headers from openfpgautil library */ +#include "openfpga_wildcard_string.h" +#include "openfpga_digest.h" + +#include "openfpga_naming.h" + +#include "sdc_writer_utils.h" + +#include "sdc_mux_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Print SDC commands to disable outputs of routing multiplexer modules + * in a given module id + * This function will be executed in a recursive way, + * using a Depth-First Search (DFS) strategy + * It will iterate over all the configurable children under each module + * and print a SDC command to disable its outputs + * + * Note: + * - When flatten_names is true + * this function will not apply any wildcard to names + * - When flatten_names is false + * It will straightforwardly output the instance name and port name + * This function will try to apply wildcard to names + * so that SDC file size can be minimal + *******************************************************************/ +void rec_print_pnr_sdc_disable_routing_multiplexer_outputs(std::fstream& fp, + const bool& flatten_names, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const ModuleId& mux_module, + const std::string& parent_module_path) { + + /* Build wildcard names for the instance names of multiple-instanced-blocks (MIB) + * We will find all the instance names and see there are common prefix + * If so, we can use wildcards + */ + std::map> wildcard_names; + + /* For each child, we will go one level down in priority */ + for (const ModuleId& child_module : module_manager.child_modules(parent_module)) { + + std::string child_module_path = parent_module_path; + + /* Iterate over the child instances*/ + for (const size_t& child_instance : module_manager.child_module_instances(parent_module, child_module)) { + std::string child_instance_name; + if (true == module_manager.instance_name(parent_module, child_module, child_instance).empty()) { + child_instance_name = generate_instance_name(module_manager.module_name(child_module), child_instance); + } else { + child_instance_name = module_manager.instance_name(parent_module, child_module, child_instance); + } + + if (false == flatten_names) { + /* Try to adapt to a wildcard name: replace all the numbers with a wildcard character '*' */ + WildCardString wildcard_str(child_instance_name); + /* If the wildcard name is already in the list, we can skip this + * Otherwise, we have to + * - output this instance + * - record the wildcard name in the map + */ + if ( (0 < wildcard_names.count(child_module)) + && (wildcard_names.at(child_module).end() != std::find(wildcard_names.at(child_module).begin(), + wildcard_names.at(child_module).end(), + wildcard_str.data())) ) { + continue; + } + + child_module_path += wildcard_str.data(); + + wildcard_names[child_module].push_back(wildcard_str.data()); + } else { + child_module_path += child_instance_name; + } + + child_module_path = format_dir_path(child_module_path); + + /* If this is NOT the MUX module we want, we go recursively */ + if (mux_module != child_module) { + rec_print_pnr_sdc_disable_routing_multiplexer_outputs(fp, flatten_names, + module_manager, + child_module, + mux_module, + child_module_path); + continue; + } + + /* Validate file stream */ + valid_file_stream(fp); + + /* Reach here, this is the MUX module we want, disable the outputs */ + for (const BasicPort& output_port : module_manager.module_ports_by_type(mux_module, ModuleManager::MODULE_OUTPUT_PORT)) { + fp << "set_disable_timing "; + fp << child_module_path << output_port.get_name(); + fp << std::endl; + } + } + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/sdc_mux_utils.h b/openfpga/src/fpga_sdc/sdc_mux_utils.h new file mode 100644 index 000000000..70089aa93 --- /dev/null +++ b/openfpga/src/fpga_sdc/sdc_mux_utils.h @@ -0,0 +1,28 @@ +#ifndef SDC_MUX_UTILS_H +#define SDC_MUX_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "module_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void rec_print_pnr_sdc_disable_routing_multiplexer_outputs(std::fstream& fp, + const bool& flatten_names, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const ModuleId& mux_module, + const std::string& parent_module_path); + + +} /* end namespace openfpga */ + +#endif From 2fbf9c2cfc0c053f8c484470c1a4f265036981a4 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 4 May 2020 12:28:47 -0600 Subject: [PATCH 504/645] change to a higher simulation clock speed to accelerate CI verification. Later, we should place simulation information in another XML so that we can reuse that easily --- openfpga_flow/openfpga_arch/k4_N4_40nm_openfpga.xml | 4 ++-- openfpga_flow/openfpga_arch/k6_N10_40nm_openfpga.xml | 4 ++-- .../k6_N10_intermediate_buffer_40nm_openfpga.xml | 4 ++-- openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml | 4 ++-- .../openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml | 6 +++--- .../k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml | 6 +++--- .../k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml | 6 +++--- .../k6_frac_N10_adder_column_chain_40nm_openfpga.xml | 6 +++--- .../k6_frac_N10_adder_register_chain_40nm_openfpga.xml | 6 +++--- .../k6_frac_N10_adder_register_scan_chain_40nm_openfpga.xml | 6 +++--- ..._N10_adder_register_scan_chain_depop50_40nm_openfpga.xml | 6 +++--- ...der_register_scan_chain_depop50_spypad_40nm_openfpga.xml | 6 +++--- .../openfpga_arch/k6_frac_N10_behavioral_40nm_openfpga.xml | 6 +++--- .../k6_frac_N10_local_encoder_40nm_openfpga.xml | 6 +++--- .../openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml | 6 +++--- .../openfpga_arch/k6_frac_N10_stdcell_mux_40nm_openfpga.xml | 6 +++--- .../openfpga_arch/k6_frac_N10_tree_mux_40nm_openfpga.xml | 6 +++--- 17 files changed, 47 insertions(+), 47 deletions(-) diff --git a/openfpga_flow/openfpga_arch/k4_N4_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k4_N4_40nm_openfpga.xml index 1d48387ed..298e0c0c0 100644 --- a/openfpga_flow/openfpga_arch/k4_N4_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k4_N4_40nm_openfpga.xml @@ -195,8 +195,8 @@ - - + + diff --git a/openfpga_flow/openfpga_arch/k6_N10_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_N10_40nm_openfpga.xml index c3385e96c..e6e53d9f6 100644 --- a/openfpga_flow/openfpga_arch/k6_N10_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_N10_40nm_openfpga.xml @@ -195,8 +195,8 @@ - - + + diff --git a/openfpga_flow/openfpga_arch/k6_N10_intermediate_buffer_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_N10_intermediate_buffer_40nm_openfpga.xml index ef1c60a80..b211bd040 100644 --- a/openfpga_flow/openfpga_arch/k6_N10_intermediate_buffer_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_N10_intermediate_buffer_40nm_openfpga.xml @@ -196,8 +196,8 @@ - - + + diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml index 51e250a8a..9b20ab2a2 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml @@ -227,8 +227,8 @@ - - + + diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml index ae08c8250..10051a9ca 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml @@ -251,9 +251,9 @@ - - - + + + diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml index eaacadd17..40a451b55 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml @@ -268,9 +268,9 @@ - - - + + + diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml index 441f10ee4..9e608ead9 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml @@ -280,9 +280,9 @@ - - - + + + diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_column_chain_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_column_chain_40nm_openfpga.xml index 65117d199..79b82375f 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_column_chain_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_column_chain_40nm_openfpga.xml @@ -251,9 +251,9 @@ - - - + + + diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_chain_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_chain_40nm_openfpga.xml index 779880dea..33cfa9e61 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_chain_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_chain_40nm_openfpga.xml @@ -254,9 +254,9 @@ - - - + + + diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_40nm_openfpga.xml index e6f6bdb6c..460c8743e 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_40nm_openfpga.xml @@ -260,9 +260,9 @@ - - - + + + diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_depop50_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_depop50_40nm_openfpga.xml index 8d2aadaeb..4bb4a24a2 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_depop50_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_depop50_40nm_openfpga.xml @@ -255,9 +255,9 @@ - - - + + + diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_depop50_spypad_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_depop50_spypad_40nm_openfpga.xml index 4939b2dc0..e1dde16b6 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_depop50_spypad_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_depop50_spypad_40nm_openfpga.xml @@ -339,9 +339,9 @@ - - - + + + diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_behavioral_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_behavioral_40nm_openfpga.xml index e305bbc08..ebad2d203 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_behavioral_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_behavioral_40nm_openfpga.xml @@ -226,9 +226,9 @@ - - - + + + diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_local_encoder_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_local_encoder_40nm_openfpga.xml index 201a4be58..b32deb8a9 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_local_encoder_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_local_encoder_40nm_openfpga.xml @@ -226,9 +226,9 @@ - - - + + + diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml index 159214507..c980065bb 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml @@ -230,9 +230,9 @@ - - - + + + diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_stdcell_mux_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_stdcell_mux_40nm_openfpga.xml index 660e9f003..2b42cdced 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_stdcell_mux_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_stdcell_mux_40nm_openfpga.xml @@ -218,9 +218,9 @@ - - - + + + diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_tree_mux_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_tree_mux_40nm_openfpga.xml index d04318510..c26c30f31 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_tree_mux_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_tree_mux_40nm_openfpga.xml @@ -217,9 +217,9 @@ - - - + + + From 4083fae41ac24d03d11c76dab940e95b90c8bb05 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 4 May 2020 12:34:54 -0600 Subject: [PATCH 505/645] add new test cases about user-defined simulation settings --- .../k4_N4_40nm_fixed_sim_openfpga.xml | 228 ++++++++++++++++++ .../config/task.conf | 34 +++ 2 files changed, 262 insertions(+) create mode 100644 openfpga_flow/openfpga_arch/k4_N4_40nm_fixed_sim_openfpga.xml create mode 100644 openfpga_flow/tasks/openfpga_shell/fixed_simulation_settings/config/task.conf diff --git a/openfpga_flow/openfpga_arch/k4_N4_40nm_fixed_sim_openfpga.xml b/openfpga_flow/openfpga_arch/k4_N4_40nm_fixed_sim_openfpga.xml new file mode 100644 index 000000000..677061785 --- /dev/null +++ b/openfpga_flow/openfpga_arch/k4_N4_40nm_fixed_sim_openfpga.xml @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + + + + + 10e-12 5e-12 5e-12 + + + 10e-12 5e-12 5e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/tasks/openfpga_shell/fixed_simulation_settings/config/task.conf b/openfpga_flow/tasks/openfpga_shell/fixed_simulation_settings/config/task.conf new file mode 100644 index 000000000..4875931e3 --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/fixed_simulation_settings/config/task.conf @@ -0,0 +1,34 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k4_N4_40nm_fixed_sim_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k4_N4_tileable_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.blif + +[SYNTHESIS_PARAM] +bench0_top = and2 +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.v +bench0_chan_width = 300 + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +end_flow_with_test= +#vpr_fpga_verilog_formal_verification_top_netlist= From 47f040822fee04dcc4e2d93639cac53215660014 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 4 May 2020 12:36:06 -0600 Subject: [PATCH 506/645] deploy the tests to CI --- .travis/script.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis/script.sh b/.travis/script.sh index 19da16b8b..675ab4996 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -51,6 +51,9 @@ echo -e "Testing OpenFPGA Shell"; echo -e "Testing configuration chain of a K4N4 FPGA"; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/configuration_chain --debug --show_thread_logs +echo -e "Testing user-defined simulation settings: clock frequency and number of cycles"; +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/fixed_simulation_settings --debug --show_thread_logs + echo -e "Testing Verilog generation for LUTs: a single mode LUT6 FPGA using micro benchmarks"; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/lut_design/single_mode --debug --show_thread_logs From 8726c618eb73cefb41bad3743ce1119ccf4a2115 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 5 May 2020 12:31:11 -0600 Subject: [PATCH 507/645] add time unit support on SDC generator. Now users can define time_unit thru cmd-line options --- .../libopenfpgautil/src/openfpga_scale.cpp | 183 ++++++++++++++++++ .../libopenfpgautil/src/openfpga_scale.h | 29 +++ openfpga/src/base/openfpga_sdc.cpp | 12 ++ openfpga/src/base/openfpga_sdc_command.cpp | 8 + openfpga/src/fpga_sdc/analysis_sdc_option.cpp | 9 + openfpga/src/fpga_sdc/analysis_sdc_option.h | 3 + openfpga/src/fpga_sdc/analysis_sdc_writer.cpp | 11 +- openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp | 37 +++- openfpga/src/fpga_sdc/pnr_sdc_grid_writer.h | 1 + openfpga/src/fpga_sdc/pnr_sdc_option.cpp | 9 + openfpga/src/fpga_sdc/pnr_sdc_option.h | 3 + .../src/fpga_sdc/pnr_sdc_routing_writer.cpp | 41 +++- .../src/fpga_sdc/pnr_sdc_routing_writer.h | 4 + openfpga/src/fpga_sdc/pnr_sdc_writer.cpp | 5 + openfpga/src/fpga_sdc/sdc_writer_utils.cpp | 15 ++ openfpga/src/fpga_sdc/sdc_writer_utils.h | 3 + 16 files changed, 353 insertions(+), 20 deletions(-) create mode 100644 libopenfpga/libopenfpgautil/src/openfpga_scale.cpp create mode 100644 libopenfpga/libopenfpgautil/src/openfpga_scale.h diff --git a/libopenfpga/libopenfpgautil/src/openfpga_scale.cpp b/libopenfpga/libopenfpgautil/src/openfpga_scale.cpp new file mode 100644 index 000000000..9baa753d1 --- /dev/null +++ b/libopenfpga/libopenfpgautil/src/openfpga_scale.cpp @@ -0,0 +1,183 @@ +/******************************************************************** + * This file includes functions that convert time/resistance/capacitance + * units to string or vice versa + *******************************************************************/ +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" + +/* Headers from openfpgautil library */ +#include "openfpga_scale.h" + +namespace openfpga { + +/* A small ratio for float number comparison + * If the float number B is in the range of the referance A +/- epsilon + * we regard A == B + * A - A * EPSILON <= B <= A + A * EPSILON + */ +#define EPSILON_RATIO 1e-3 + +bool same_float_number(const float& a, + const float& b, + const float& epsilon) { + /* Always use a positive epsilon */ + if ( (a - a * std::abs(epsilon) <= b) + && (b <= a + a * std::abs(epsilon)) ) { + return true; + } + + return false; +} + +/******************************************************************** + * Convert numeric unit to string: + * - 1e12 -> T + * - 1e9 -> B + * - 1e6 -> M + * - 1e3 -> k + * - 1. -> + * - 1e-3 -> m + * - 1e-6 -> u + * - 1e-9 -> n + * - 1e-12 -> p + * - 1e-15 -> f + * - 1e-18 -> a + *******************************************************************/ +std::string unit_to_string(const float& unit) { + if (true == same_float_number(unit, 1., EPSILON_RATIO)) { + return std::string(); + /* Larger than 1 unit */ + } else if (true == same_float_number(unit, 1e3, EPSILON_RATIO)) { + return std::string("k"); + } else if (true == same_float_number(unit, 1e6, EPSILON_RATIO)) { + return std::string("M"); + } else if (true == same_float_number(unit, 1e9, EPSILON_RATIO)) { + return std::string("B"); + } else if (true == same_float_number(unit, 1e12, EPSILON_RATIO)) { + return std::string("T"); + /* Less than 1 unit */ + } else if (true == same_float_number(unit, 1e-3, EPSILON_RATIO)) { + return std::string("m"); + } else if (true == same_float_number(unit, 1e-6, EPSILON_RATIO)) { + return std::string("u"); + } else if (true == same_float_number(unit, 1e-9, EPSILON_RATIO)) { + return std::string("n"); + } else if (true == same_float_number(unit, 1e-12, EPSILON_RATIO)) { + return std::string("p"); + } else if (true == same_float_number(unit, 1e-15, EPSILON_RATIO)) { + return std::string("f"); + } else if (true == same_float_number(unit, 1e-18, EPSILON_RATIO)) { + return std::string("a"); + } + + /* Invalid unit report error */ + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid unit %g!\nAcceptable units are [1e12|1e9|1e6|1e3|1|1e-3|1e-6|1e-9|1e-12|1e-15|1e-18]\n", + unit); + exit(1); +} + +/******************************************************************** + * Convert numeric time unit to string + * e.g. 1e-12 -> ps + *******************************************************************/ +std::string time_unit_to_string(const float& unit) { + /* For larger than 1 unit, we do not accept */ + if (1e6 < unit) { + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid time unit %g!\nAcceptable units are [1e6|1e3|1|1e-3|1e-6|1e-9|1e-12|1e-15|1e-18]\n", + unit); + exit(1); + } + + return unit_to_string(unit) + std::string("s"); +} + +/******************************************************************** + * Convert string unit to numeric: + * - T -> 1e12 + * - B -> 1e9 + * - M -> 1e6 + * - k -> 1e3 + * - "" -> 1. + * - m -> 1e-3 + * - u -> 1e-6 + * - n -> 1e-9 + * - p -> 1e-12 + * - f -> 1e-15 + * - a -> 1e-18 + *******************************************************************/ +float string_to_unit(const std::string& scale) { + if (true == scale.empty()) { + return 1.; + /* Larger than 1 unit */ + } else if (std::string("T") == scale) { + return 1e12; + } else if (std::string("B") == scale) { + return 1e9; + } else if (std::string("M") == scale) { + return 1e6; + } else if (std::string("k") == scale) { + return 1e3; + /* Less than 1 unit */ + } else if (std::string("m") == scale) { + return 1e-3; + } else if (std::string("u") == scale) { + return 1e-6; + } else if (std::string("n") == scale) { + return 1e-9; + } else if (std::string("p") == scale) { + return 1e-12; + } else if (std::string("f") == scale) { + return 1e-15; + } else if (std::string("a") == scale) { + return 1e-18; + } + + /* Invalid unit report error */ + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid unit %s!\nAcceptable units are [a|f|p|n|u|k|M|B|T] or empty\n", + scale); + exit(1); +} + +/******************************************************************** + * Convert string time unit to numeric + * e.g. ps -> 1e-12 + *******************************************************************/ +float string_to_time_unit(const std::string& scale) { + if ( (1 != scale.length()) + && (2 != scale.length()) ) { + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Time unit (='%s') must contain only one or two characters!\n", + scale); + } + /* The last character must be 's' */ + if ('s' != scale.back()) { + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Time unit (='%s') must end with 's'!\n", + scale); + } + + float unit = 1.; + VTR_ASSERT ( (1 == scale.length()) + || (2 == scale.length()) ); + if (2 == scale.length()) { + unit = string_to_unit(scale.substr(0, 1)); + } + + /* For larger than 1 unit, we do not accept */ + if (1e6 < unit) { + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid time unit %g!\nAcceptable units are [1e6|1e3|1|1e-3|1e-6|1e-9|1e-12|1e-15|1e-18]\n", + unit); + exit(1); + } + + return unit; +} + +} /* namespace openfpga ends */ diff --git a/libopenfpga/libopenfpgautil/src/openfpga_scale.h b/libopenfpga/libopenfpgautil/src/openfpga_scale.h new file mode 100644 index 000000000..197923548 --- /dev/null +++ b/libopenfpga/libopenfpgautil/src/openfpga_scale.h @@ -0,0 +1,29 @@ +#ifndef OPENFPGA_SCALE_H +#define OPENFPGA_SCALE_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include + +/******************************************************************** + * Function declaration + *******************************************************************/ +/* namespace openfpga begins */ +namespace openfpga { + +bool same_float_number(const float& a, + const float& b, + const float& epsilon); + +std::string unit_to_string(const float& unit); + +std::string time_unit_to_string(const float& unit); + +float string_to_unit(const std::string& scale); + +float string_to_time_unit(const std::string& scale); + +} /* namespace openfpga ends */ + +#endif diff --git a/openfpga/src/base/openfpga_sdc.cpp b/openfpga/src/base/openfpga_sdc.cpp index 9fa360cff..d90376839 100644 --- a/openfpga/src/base/openfpga_sdc.cpp +++ b/openfpga/src/base/openfpga_sdc.cpp @@ -9,6 +9,7 @@ #include "command_exit_codes.h" /* Headers from openfpgautil library */ +#include "openfpga_scale.h" #include "openfpga_digest.h" #include "circuit_library_utils.h" @@ -31,6 +32,7 @@ int write_pnr_sdc(OpenfpgaContext& openfpga_ctx, CommandOptionId opt_output_dir = cmd.option("file"); CommandOptionId opt_flatten_names = cmd.option("flatten_names"); CommandOptionId opt_hierarchical = cmd.option("hierarchical"); + CommandOptionId opt_time_unit = cmd.option("time_unit"); CommandOptionId opt_constrain_global_port = cmd.option("constrain_global_port"); CommandOptionId opt_constrain_non_clock_global_port = cmd.option("constrain_non_clock_global_port"); CommandOptionId opt_constrain_grid = cmd.option("constrain_grid"); @@ -53,6 +55,11 @@ int write_pnr_sdc(OpenfpgaContext& openfpga_ctx, options.set_flatten_names(cmd_context.option_enable(cmd, opt_flatten_names)); options.set_hierarchical(cmd_context.option_enable(cmd, opt_hierarchical)); + + if (true == cmd_context.option_enable(cmd, opt_time_unit)) { + options.set_time_unit(string_to_time_unit(cmd_context.option_value(cmd, opt_time_unit))); + } + options.set_constrain_global_port(cmd_context.option_enable(cmd, opt_constrain_global_port)); options.set_constrain_non_clock_global_port(cmd_context.option_enable(cmd, opt_constrain_non_clock_global_port)); options.set_constrain_grid(cmd_context.option_enable(cmd, opt_constrain_grid)); @@ -100,6 +107,7 @@ int write_analysis_sdc(OpenfpgaContext& openfpga_ctx, CommandOptionId opt_output_dir = cmd.option("file"); CommandOptionId opt_flatten_names = cmd.option("flatten_names"); + CommandOptionId opt_time_unit = cmd.option("time_unit"); /* This is an intermediate data structure which is designed to modularize the FPGA-SDC * Keep it independent from any other outside data structures @@ -113,6 +121,10 @@ int write_analysis_sdc(OpenfpgaContext& openfpga_ctx, options.set_generate_sdc_analysis(true); options.set_flatten_names(cmd_context.option_enable(cmd, opt_flatten_names)); + if (true == cmd_context.option_enable(cmd, opt_time_unit)) { + options.set_time_unit(string_to_time_unit(cmd_context.option_value(cmd, opt_time_unit))); + } + /* Collect global ports from the circuit library: * TODO: should we place this in the OpenFPGA context? */ diff --git a/openfpga/src/base/openfpga_sdc_command.cpp b/openfpga/src/base/openfpga_sdc_command.cpp index 479cb8de9..bdf123e8b 100644 --- a/openfpga/src/base/openfpga_sdc_command.cpp +++ b/openfpga/src/base/openfpga_sdc_command.cpp @@ -32,6 +32,10 @@ ShellCommandId add_openfpga_write_pnr_sdc_command(openfpga::Shellinput_edges[iedge]->delay_max); + des_pb_graph_pin->input_edges[iedge]->delay_max / time_unit); } } @@ -161,6 +163,7 @@ void print_pnr_sdc_constrain_pb_pin_interc_timing(std::fstream& fp, *******************************************************************/ static void print_pnr_sdc_constrain_pb_interc_timing(std::fstream& fp, + const float& time_unit, const ModuleManager& module_manager, const ModuleId& parent_module, t_pb_graph_node* des_pb_graph_node, @@ -176,7 +179,8 @@ void print_pnr_sdc_constrain_pb_interc_timing(std::fstream& fp, for (int ipin = 0; ipin < des_pb_graph_node->num_input_pins[iport]; ++ipin) { /* If this is a idle block, we set 0 to the selected edge*/ /* Get the selected edge of current pin*/ - print_pnr_sdc_constrain_pb_pin_interc_timing(fp, + print_pnr_sdc_constrain_pb_pin_interc_timing(fp, + time_unit, module_manager, parent_module, &(des_pb_graph_node->input_pins[iport][ipin]), physical_mode, @@ -189,6 +193,7 @@ void print_pnr_sdc_constrain_pb_interc_timing(std::fstream& fp, for (int iport = 0; iport < des_pb_graph_node->num_output_ports; ++iport) { for (int ipin = 0; ipin < des_pb_graph_node->num_output_pins[iport]; ++ipin) { print_pnr_sdc_constrain_pb_pin_interc_timing(fp, + time_unit, module_manager, parent_module, &(des_pb_graph_node->output_pins[iport][ipin]), physical_mode, @@ -217,6 +222,7 @@ void print_pnr_sdc_constrain_pb_interc_timing(std::fstream& fp, *******************************************************************/ static void print_pnr_sdc_constrain_pb_graph_node_timing(const std::string& sdc_dir, + const float& time_unit, const ModuleManager& module_manager, t_pb_graph_node* parent_pb_graph_node, t_mode* physical_mode, @@ -242,6 +248,9 @@ void print_pnr_sdc_constrain_pb_graph_node_timing(const std::string& sdc_dir, /* Generate the descriptions*/ print_sdc_file_header(fp, std::string("Timing constraints for Grid " + pb_module_name + " in PnR")); + /* Print time unit for the SDC file */ + print_sdc_timescale(fp, time_unit_to_string(time_unit)); + /* We check output_pins of cur_pb_graph_node and its the input_edges * Built the interconnections between outputs of cur_pb_graph_node and outputs of child_pb_graph_node * child_pb_graph_node.output_pins -----------------> cur_pb_graph_node.outpins @@ -250,6 +259,7 @@ void print_pnr_sdc_constrain_pb_graph_node_timing(const std::string& sdc_dir, * input_pins, edges, output_pins */ print_pnr_sdc_constrain_pb_interc_timing(fp, + time_unit, module_manager, pb_module, parent_pb_graph_node, CIRCUIT_PB_PORT_OUTPUT, @@ -268,6 +278,7 @@ void print_pnr_sdc_constrain_pb_graph_node_timing(const std::string& sdc_dir, t_pb_graph_node* child_pb_graph_node = &(parent_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ipb][jpb]); /* For each child_pb_graph_node input pins*/ print_pnr_sdc_constrain_pb_interc_timing(fp, + time_unit, module_manager, pb_module, child_pb_graph_node, CIRCUIT_PB_PORT_INPUT, @@ -291,6 +302,7 @@ void print_pnr_sdc_constrain_pb_graph_node_timing(const std::string& sdc_dir, *******************************************************************/ static void print_pnr_sdc_constrain_primitive_pb_graph_node(const std::string& sdc_dir, + const float& time_unit, const ModuleManager& module_manager, t_pb_graph_node* primitive_pb_graph_node, const bool& constrain_zero_delay_paths) { @@ -350,6 +362,9 @@ void print_pnr_sdc_constrain_primitive_pb_graph_node(const std::string& sdc_dir, /* Generate the descriptions*/ print_sdc_file_header(fp, std::string("Timing constraints for Grid " + pb_module_name + " in PnR")); + /* Print time unit for the SDC file */ + print_sdc_timescale(fp, time_unit_to_string(time_unit)); + /* We traverse the pb_graph pins where we can find pin-to-pin timing annotation * We walk through output pins here, build timing constraints by pair each output to input * Clock pins are not walked through because they will be handled by clock tree synthesis @@ -387,7 +402,7 @@ void print_pnr_sdc_constrain_primitive_pb_graph_node(const std::string& sdc_dir, generate_sdc_port(src_port), pb_module_name, generate_sdc_port(sink_port), - tmax); + tmax / time_unit); } /* Find min delay between src and sink pin */ @@ -400,7 +415,7 @@ void print_pnr_sdc_constrain_primitive_pb_graph_node(const std::string& sdc_dir, generate_sdc_port(src_port), pb_module_name, generate_sdc_port(sink_port), - tmin); + tmin / time_unit); } } } @@ -417,6 +432,7 @@ void print_pnr_sdc_constrain_primitive_pb_graph_node(const std::string& sdc_dir, *******************************************************************/ static void rec_print_pnr_sdc_constrain_pb_graph_timing(const std::string& sdc_dir, + const float& time_unit, const ModuleManager& module_manager, const VprDeviceAnnotation& device_annotation, t_pb_graph_node* parent_pb_graph_node, @@ -433,7 +449,9 @@ void rec_print_pnr_sdc_constrain_pb_graph_timing(const std::string& sdc_dir, /* Constrain the primitive node if a timing matrix is defined */ if (true == is_primitive_pb_type(parent_pb_type)) { - print_pnr_sdc_constrain_primitive_pb_graph_node(sdc_dir, module_manager, + print_pnr_sdc_constrain_primitive_pb_graph_node(sdc_dir, + time_unit, + module_manager, parent_pb_graph_node, constrain_zero_delay_paths); return; @@ -446,6 +464,7 @@ void rec_print_pnr_sdc_constrain_pb_graph_timing(const std::string& sdc_dir, /* Write a SDC file for this pb_type */ print_pnr_sdc_constrain_pb_graph_node_timing(sdc_dir, + time_unit, module_manager, parent_pb_graph_node, physical_mode, @@ -455,7 +474,9 @@ void rec_print_pnr_sdc_constrain_pb_graph_timing(const std::string& sdc_dir, * Note that we assume a full hierarchical P&R, we will only visit pb_graph_node of unique pb_type */ for (int ipb = 0; ipb < physical_mode->num_pb_type_children; ++ipb) { - rec_print_pnr_sdc_constrain_pb_graph_timing(sdc_dir, module_manager, + rec_print_pnr_sdc_constrain_pb_graph_timing(sdc_dir, + time_unit, + module_manager, device_annotation, &(parent_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ipb][0]), constrain_zero_delay_paths); @@ -466,6 +487,7 @@ void rec_print_pnr_sdc_constrain_pb_graph_timing(const std::string& sdc_dir, * Top-level function to print timing constraints for pb_types *******************************************************************/ void print_pnr_sdc_constrain_grid_timing(const std::string& sdc_dir, + const float& time_unit, const DeviceContext& device_ctx, const VprDeviceAnnotation& device_annotation, const ModuleManager& module_manager, @@ -485,7 +507,8 @@ void print_pnr_sdc_constrain_grid_timing(const std::string& sdc_dir, continue; } /* Special for I/O block, generate one module for each border side */ - rec_print_pnr_sdc_constrain_pb_graph_timing(sdc_dir, module_manager, + rec_print_pnr_sdc_constrain_pb_graph_timing(sdc_dir, time_unit, + module_manager, device_annotation, pb_graph_head, constrain_zero_delay_paths); diff --git a/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.h b/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.h index 400ca1616..909caff2a 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.h +++ b/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.h @@ -18,6 +18,7 @@ namespace openfpga { void print_pnr_sdc_constrain_grid_timing(const std::string& sdc_dir, + const float& time_unit, const DeviceContext& device_ctx, const VprDeviceAnnotation& device_annotation, const ModuleManager& module_manager, diff --git a/openfpga/src/fpga_sdc/pnr_sdc_option.cpp b/openfpga/src/fpga_sdc/pnr_sdc_option.cpp index 3b7c417e0..f8ee429f3 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_option.cpp +++ b/openfpga/src/fpga_sdc/pnr_sdc_option.cpp @@ -13,6 +13,7 @@ PnrSdcOption::PnrSdcOption(const std::string& sdc_dir) { sdc_dir_ = sdc_dir; hierarchical_ = false; flatten_names_ = false; + time_unit_ = 1.; constrain_global_port_ = false; constrain_non_clock_global_port_ = false; constrain_grid_ = false; @@ -39,6 +40,10 @@ bool PnrSdcOption::hierarchical() const { return hierarchical_; } +float PnrSdcOption::time_unit() const { + return time_unit_; +} + bool PnrSdcOption::generate_sdc_pnr() const { return constrain_global_port_ || constrain_grid_ @@ -100,6 +105,10 @@ void PnrSdcOption::set_hierarchical(const bool& hierarchical) { hierarchical_ = hierarchical; } +void PnrSdcOption::set_time_unit(const float& time_unit) { + time_unit_ = time_unit; +} + void PnrSdcOption::set_generate_sdc_pnr(const bool& generate_sdc_pnr) { constrain_global_port_ = generate_sdc_pnr; constrain_grid_ = generate_sdc_pnr; diff --git a/openfpga/src/fpga_sdc/pnr_sdc_option.h b/openfpga/src/fpga_sdc/pnr_sdc_option.h index ff52a75fd..4fe997321 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_option.h +++ b/openfpga/src/fpga_sdc/pnr_sdc_option.h @@ -18,6 +18,7 @@ class PnrSdcOption { std::string sdc_dir() const; bool flatten_names() const; bool hierarchical() const; + float time_unit() const; bool generate_sdc_pnr() const; bool constrain_global_port() const; bool constrain_non_clock_global_port() const; @@ -32,6 +33,7 @@ class PnrSdcOption { void set_sdc_dir(const std::string& sdc_dir); void set_flatten_names(const bool& flatten_names); void set_hierarchical(const bool& hierarchical); + void set_time_unit(const float& time_unit); void set_generate_sdc_pnr(const bool& generate_sdc_pnr); void set_constrain_global_port(const bool& constrain_global_port); void set_constrain_non_clock_global_port(const bool& constrain_non_clock_global_port); @@ -46,6 +48,7 @@ class PnrSdcOption { std::string sdc_dir_; bool flatten_names_; bool hierarchical_; + float time_unit_; bool constrain_global_port_; bool constrain_non_clock_global_port_; bool constrain_grid_; diff --git a/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp b/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp index 60e756839..e530e7903 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp +++ b/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp @@ -16,6 +16,7 @@ #include "vtr_time.h" /* Headers from openfpgautil library */ +#include "openfpga_scale.h" #include "openfpga_port.h" #include "openfpga_side_manager.h" #include "openfpga_digest.h" @@ -49,6 +50,7 @@ float find_pnr_sdc_switch_tmax(const t_rr_switch_inf& switch_inf) { *******************************************************************/ static void print_pnr_sdc_constrain_sb_mux_timing(std::fstream& fp, + const float& time_unit, const bool& hierarchical, const std::string& module_path, const ModuleManager& module_manager, @@ -104,7 +106,7 @@ void print_pnr_sdc_constrain_sb_mux_timing(std::fstream& fp, generate_sdc_port(module_manager.module_port(sb_module, module_input_port)), module_path, generate_sdc_port(module_manager.module_port(sb_module, module_output_port)), - switch_delays[module_input_port]); + switch_delays[module_input_port] / time_unit); } else { VTR_ASSERT_SAFE(true == hierarchical); @@ -112,7 +114,7 @@ void print_pnr_sdc_constrain_sb_mux_timing(std::fstream& fp, module_manager, sb_module, module_input_port, sb_module, module_output_port, - switch_delays[module_input_port]); + switch_delays[module_input_port] / time_unit); } } } @@ -127,6 +129,7 @@ void print_pnr_sdc_constrain_sb_mux_timing(std::fstream& fp, *******************************************************************/ static void print_pnr_sdc_constrain_sb_timing(const std::string& sdc_dir, + const float& time_unit, const bool& hierarchical, const std::string& module_path, const ModuleManager& module_manager, @@ -152,6 +155,9 @@ void print_pnr_sdc_constrain_sb_timing(const std::string& sdc_dir, /* Generate the descriptions*/ print_sdc_file_header(fp, std::string("Constrain timing of Switch Block " + sb_module_name + " for PnR")); + /* Print time unit for the SDC file */ + print_sdc_timescale(fp, time_unit_to_string(time_unit)); + for (size_t side = 0; side < rr_gsb.get_num_sides(); ++side) { SideManager side_manager(side); for (size_t itrack = 0; itrack < rr_gsb.get_chan_width(side_manager.get_side()); ++itrack) { @@ -166,6 +172,7 @@ void print_pnr_sdc_constrain_sb_timing(const std::string& sdc_dir, } /* This is a MUX, constrain all the paths from an input to an output */ print_pnr_sdc_constrain_sb_mux_timing(fp, + time_unit, hierarchical, module_path, module_manager, sb_module, @@ -186,6 +193,7 @@ void print_pnr_sdc_constrain_sb_timing(const std::string& sdc_dir, * This function is designed for flatten routing hierarchy *******************************************************************/ void print_pnr_sdc_flatten_routing_constrain_sb_timing(const std::string& sdc_dir, + const float& time_unit, const bool& hierarchical, const ModuleManager& module_manager, const ModuleId& top_module, @@ -223,6 +231,7 @@ void print_pnr_sdc_flatten_routing_constrain_sb_timing(const std::string& sdc_di module_path += std::string(")") + std::string("\\"); print_pnr_sdc_constrain_sb_timing(sdc_dir, + time_unit, hierarchical, module_path, module_manager, @@ -238,6 +247,7 @@ void print_pnr_sdc_flatten_routing_constrain_sb_timing(const std::string& sdc_di * This function is designed for compact routing hierarchy *******************************************************************/ void print_pnr_sdc_compact_routing_constrain_sb_timing(const std::string& sdc_dir, + const float& time_unit, const bool& hierarchical, const ModuleManager& module_manager, const ModuleId& top_module, @@ -283,6 +293,7 @@ void print_pnr_sdc_compact_routing_constrain_sb_timing(const std::string& sdc_di module_path += std::string(")") + std::string("\\"); print_pnr_sdc_constrain_sb_timing(sdc_dir, + time_unit, hierarchical, module_path, module_manager, @@ -298,6 +309,7 @@ void print_pnr_sdc_compact_routing_constrain_sb_timing(const std::string& sdc_di *******************************************************************/ static void print_pnr_sdc_constrain_cb_mux_timing(std::fstream& fp, + const float& time_unit, const bool& hierarchical, const std::string& module_path, const ModuleManager& module_manager, @@ -373,7 +385,7 @@ void print_pnr_sdc_constrain_cb_mux_timing(std::fstream& fp, module_manager, cb_module, module_input_port, cb_module, module_output_port, - switch_delays[module_input_port]); + switch_delays[module_input_port] / time_unit); } else { VTR_ASSERT_SAFE(false == hierarchical); print_pnr_sdc_regexp_constrain_max_delay(fp, @@ -381,7 +393,7 @@ void print_pnr_sdc_constrain_cb_mux_timing(std::fstream& fp, generate_sdc_port(module_manager.module_port(cb_module, module_input_port)), std::string(module_path), generate_sdc_port(module_manager.module_port(cb_module, module_output_port)), - switch_delays[module_input_port]); + switch_delays[module_input_port] / time_unit); } } @@ -393,6 +405,7 @@ void print_pnr_sdc_constrain_cb_mux_timing(std::fstream& fp, *******************************************************************/ static void print_pnr_sdc_constrain_cb_timing(const std::string& sdc_dir, + const float& time_unit, const bool& hierarchical, const std::string& module_path, const ModuleManager& module_manager, @@ -420,6 +433,9 @@ void print_pnr_sdc_constrain_cb_timing(const std::string& sdc_dir, /* Generate the descriptions*/ print_sdc_file_header(fp, std::string("Constrain timing of Connection Block " + cb_module_name + " for PnR")); + /* Print time unit for the SDC file */ + print_sdc_timescale(fp, time_unit_to_string(time_unit)); + /* Contrain each routing track inside the connection block */ for (size_t itrack = 0; itrack < rr_gsb.get_cb_chan_width(cb_type); ++itrack) { /* Create a port description for the input */ @@ -463,7 +479,7 @@ void print_pnr_sdc_constrain_cb_timing(const std::string& sdc_dir, module_manager, cb_module, input_port_id, cb_module, output_port_id, - routing_segment_delay); + routing_segment_delay / time_unit); } else { VTR_ASSERT_SAFE(false == hierarchical); print_pnr_sdc_regexp_constrain_max_delay(fp, @@ -471,7 +487,7 @@ void print_pnr_sdc_constrain_cb_timing(const std::string& sdc_dir, generate_sdc_port(module_manager.module_port(cb_module, input_port_id)), std::string(module_path), generate_sdc_port(module_manager.module_port(cb_module, output_port_id)), - routing_segment_delay); + routing_segment_delay / time_unit); } } @@ -484,6 +500,7 @@ void print_pnr_sdc_constrain_cb_timing(const std::string& sdc_dir, for (size_t inode = 0; inode < rr_gsb.get_num_ipin_nodes(cb_ipin_side); ++inode) { const RRNodeId& ipin_rr_node = rr_gsb.get_ipin_node(cb_ipin_side, inode); print_pnr_sdc_constrain_cb_mux_timing(fp, + time_unit, hierarchical, module_path, module_manager, cb_module, rr_graph, rr_gsb, cb_type, @@ -502,6 +519,7 @@ void print_pnr_sdc_constrain_cb_timing(const std::string& sdc_dir, *******************************************************************/ static void print_pnr_sdc_flatten_routing_constrain_cb_timing(const std::string& sdc_dir, + const float& time_unit, const bool& hierarchical, const ModuleManager& module_manager, const ModuleId& top_module, @@ -542,6 +560,7 @@ void print_pnr_sdc_flatten_routing_constrain_cb_timing(const std::string& sdc_di module_path += std::string(")") + std::string("\\"); print_pnr_sdc_constrain_cb_timing(sdc_dir, + time_unit, hierarchical, module_path, module_manager, @@ -559,6 +578,7 @@ void print_pnr_sdc_flatten_routing_constrain_cb_timing(const std::string& sdc_di * and print SDC file for each of them *******************************************************************/ void print_pnr_sdc_flatten_routing_constrain_cb_timing(const std::string& sdc_dir, + const float& time_unit, const bool& hierarchical, const ModuleManager& module_manager, const ModuleId& top_module, @@ -569,14 +589,16 @@ void print_pnr_sdc_flatten_routing_constrain_cb_timing(const std::string& sdc_di /* Start time count */ vtr::ScopedStartFinishTimer timer("Write SDC for constrain Connection Block timing for P&R flow"); - print_pnr_sdc_flatten_routing_constrain_cb_timing(sdc_dir, hierarchical, + print_pnr_sdc_flatten_routing_constrain_cb_timing(sdc_dir, time_unit, + hierarchical, module_manager, top_module, rr_graph, device_rr_gsb, CHANX, constrain_zero_delay_paths); - print_pnr_sdc_flatten_routing_constrain_cb_timing(sdc_dir, hierarchical, + print_pnr_sdc_flatten_routing_constrain_cb_timing(sdc_dir, time_unit, + hierarchical, module_manager, top_module, rr_graph, device_rr_gsb, @@ -589,6 +611,7 @@ void print_pnr_sdc_flatten_routing_constrain_cb_timing(const std::string& sdc_di * This function is designed for compact routing hierarchy *******************************************************************/ void print_pnr_sdc_compact_routing_constrain_cb_timing(const std::string& sdc_dir, + const float& time_unit, const bool& hierarchical, const ModuleManager& module_manager, const ModuleId& top_module, @@ -631,6 +654,7 @@ void print_pnr_sdc_compact_routing_constrain_cb_timing(const std::string& sdc_di module_path += std::string(")") + std::string("\\"); print_pnr_sdc_constrain_cb_timing(sdc_dir, + time_unit, hierarchical, module_path, module_manager, @@ -670,6 +694,7 @@ void print_pnr_sdc_compact_routing_constrain_cb_timing(const std::string& sdc_di module_path += std::string(")") + std::string("\\"); print_pnr_sdc_constrain_cb_timing(sdc_dir, + time_unit, hierarchical, module_path, module_manager, diff --git a/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.h b/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.h index a29a7ac56..d95c03a00 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.h +++ b/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.h @@ -18,6 +18,7 @@ namespace openfpga { void print_pnr_sdc_flatten_routing_constrain_sb_timing(const std::string& sdc_dir, + const float& time_unit, const bool& hierarchical, const ModuleManager& module_manager, const ModuleId& top_module, @@ -26,6 +27,7 @@ void print_pnr_sdc_flatten_routing_constrain_sb_timing(const std::string& sdc_di const bool& constrain_zero_delay_paths); void print_pnr_sdc_compact_routing_constrain_sb_timing(const std::string& sdc_dir, + const float& time_unit, const bool& hierarchical, const ModuleManager& module_manager, const ModuleId& top_module, @@ -34,6 +36,7 @@ void print_pnr_sdc_compact_routing_constrain_sb_timing(const std::string& sdc_di const bool& constrain_zero_delay_paths); void print_pnr_sdc_flatten_routing_constrain_cb_timing(const std::string& sdc_dir, + const float& time_unit, const bool& hierarchical, const ModuleManager& module_manager, const ModuleId& top_module, @@ -42,6 +45,7 @@ void print_pnr_sdc_flatten_routing_constrain_cb_timing(const std::string& sdc_di const bool& constrain_zero_delay_paths); void print_pnr_sdc_compact_routing_constrain_cb_timing(const std::string& sdc_dir, + const float& time_unit, const bool& hierarchical, const ModuleManager& module_manager, const ModuleId& top_module, diff --git a/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp b/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp index c88e3e2ef..ae6d3a4b8 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp +++ b/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp @@ -442,6 +442,7 @@ void print_pnr_sdc(const PnrSdcOption& sdc_options, if (true == sdc_options.constrain_sb()) { if (true == compact_routing_hierarchy) { print_pnr_sdc_compact_routing_constrain_sb_timing(sdc_options.sdc_dir(), + sdc_options.time_unit(), sdc_options.hierarchical(), module_manager, top_module, @@ -451,6 +452,7 @@ void print_pnr_sdc(const PnrSdcOption& sdc_options, } else { VTR_ASSERT_SAFE (false == compact_routing_hierarchy); print_pnr_sdc_flatten_routing_constrain_sb_timing(sdc_options.sdc_dir(), + sdc_options.time_unit(), sdc_options.hierarchical(), module_manager, top_module, @@ -464,6 +466,7 @@ void print_pnr_sdc(const PnrSdcOption& sdc_options, if (true == sdc_options.constrain_cb()) { if (true == compact_routing_hierarchy) { print_pnr_sdc_compact_routing_constrain_cb_timing(sdc_options.sdc_dir(), + sdc_options.time_unit(), sdc_options.hierarchical(), module_manager, top_module, @@ -473,6 +476,7 @@ void print_pnr_sdc(const PnrSdcOption& sdc_options, } else { VTR_ASSERT_SAFE (false == compact_routing_hierarchy); print_pnr_sdc_flatten_routing_constrain_cb_timing(sdc_options.sdc_dir(), + sdc_options.time_unit(), sdc_options.hierarchical(), module_manager, top_module, @@ -485,6 +489,7 @@ void print_pnr_sdc(const PnrSdcOption& sdc_options, /* Output Timing constraints for Programmable blocks */ if (true == sdc_options.constrain_grid()) { print_pnr_sdc_constrain_grid_timing(sdc_options.sdc_dir(), + sdc_options.time_unit(), device_ctx, device_annotation, module_manager, diff --git a/openfpga/src/fpga_sdc/sdc_writer_utils.cpp b/openfpga/src/fpga_sdc/sdc_writer_utils.cpp index 13b70fec4..0fcf450ad 100644 --- a/openfpga/src/fpga_sdc/sdc_writer_utils.cpp +++ b/openfpga/src/fpga_sdc/sdc_writer_utils.cpp @@ -38,6 +38,21 @@ void print_sdc_file_header(std::fstream& fp, fp << std::endl; } +/******************************************************************** + * Write a timescale definition in SDC file + *******************************************************************/ +void print_sdc_timescale(std::fstream& fp, + const std::string& timescale) { + + valid_file_stream(fp); + + fp << "#############################################" << std::endl; + fp << "#\tDefine time unit " << std::endl; + fp << "#############################################" << std::endl; + fp << "set_units -time " << timescale << std::endl; + fp << std::endl; +} + /******************************************************************** * Write a port in SDC format *******************************************************************/ diff --git a/openfpga/src/fpga_sdc/sdc_writer_utils.h b/openfpga/src/fpga_sdc/sdc_writer_utils.h index c7a7cd3a1..4b353045b 100644 --- a/openfpga/src/fpga_sdc/sdc_writer_utils.h +++ b/openfpga/src/fpga_sdc/sdc_writer_utils.h @@ -19,6 +19,9 @@ namespace openfpga { void print_sdc_file_header(std::fstream& fp, const std::string& usage); +void print_sdc_timescale(std::fstream& fp, + const std::string& timescale); + std::string generate_sdc_port(const BasicPort& port); void print_pnr_sdc_constrain_max_delay(std::fstream& fp, From ac378febef3c582782db8e307caf091b54c44b25 Mon Sep 17 00:00:00 2001 From: Xifan Tang Date: Tue, 5 May 2020 12:36:01 -0600 Subject: [PATCH 508/645] update doc about time units in SDC generator --- docs/source/openfpga_shell/openfpga_commands.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/source/openfpga_shell/openfpga_commands.rst b/docs/source/openfpga_shell/openfpga_commands.rst index d3020e9c1..987e8b981 100644 --- a/docs/source/openfpga_shell/openfpga_commands.rst +++ b/docs/source/openfpga_shell/openfpga_commands.rst @@ -180,6 +180,8 @@ FPGA-SDC - ``--flatten_names`` Use flatten names (no wildcards) in SDC files + - ``--time_unit`` Specify a time unit to be used in SDC files. Acceptable values are string: ``as`` | ``fs`` | ``ps`` | ``ns`` | ``us`` | ``ms`` | ``ks`` | ``Ms``. By default, we will consider second (``s``). + - ``--constrain_global_port`` Constrain all the global ports of FPGA fabric. - ``--constrain_non_clock_global_port`` Constrain all the non-clock global ports as clocks ports of FPGA fabric @@ -211,3 +213,5 @@ FPGA-SDC - ``--file`` or ``-f`` Specify the output directory for SDC files - ``--flatten_names`` Use flatten names (no wildcards) in SDC files + + - ``--time_unit`` Specify a time unit to be used in SDC files. Acceptable values are string: ``as`` | ``fs`` | ``ps`` | ``ns`` | ``us`` | ``ms`` | ``ks`` | ``Ms``. By default, we will consider second (``s``). From 98fbcb5410c8c8ccc9d7f60e714c770254d364c5 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 5 May 2020 12:43:49 -0600 Subject: [PATCH 509/645] add time unit test for SDC generation to CI --- .travis/script.sh | 3 + .../sdc_time_unit_example_script.openfpga | 61 +++++++++++++++++++ .../sdc_time_unit/config/task.conf | 34 +++++++++++ 3 files changed, 98 insertions(+) create mode 100644 openfpga_flow/OpenFPGAShellScripts/sdc_time_unit_example_script.openfpga create mode 100644 openfpga_flow/tasks/openfpga_shell/sdc_time_unit/config/task.conf diff --git a/.travis/script.sh b/.travis/script.sh index 675ab4996..ba4c209a1 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -120,6 +120,9 @@ python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/generate_fabric -- echo -e "Testing Verilog testbench generation only"; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/generate_testbench --debug --show_thread_logs +echo -e "Testing SDC generation with time units"; +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/sdc_time_unit --debug --show_thread_logs + # Verify MCNC big20 benchmark suite with ModelSim # Please make sure you have ModelSim installed in the environment # Otherwise, it will fail diff --git a/openfpga_flow/OpenFPGAShellScripts/sdc_time_unit_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/sdc_time_unit_example_script.openfpga new file mode 100644 index 000000000..1d153a4e9 --- /dev/null +++ b/openfpga_flow/OpenFPGAShellScripts/sdc_time_unit_example_script.openfpga @@ -0,0 +1,61 @@ +# Run VPR for the 'and' design +#--write_rr_graph example_rr_graph.xml +vpr ${VPR_ARCH_FILE} ${VPR_TESTBENCH_BLIF} --clock_modeling route + +# Read OpenFPGA architecture definition +read_openfpga_arch -f ${OPENFPGA_ARCH_FILE} + +# Annotate the OpenFPGA architecture to VPR data base +# to debug use --verbose options +link_openfpga_arch --activity_file ${ACTIVITY_FILE} --sort_gsb_chan_node_in_edges + +# Check and correct any naming conflicts in the BLIF netlist +check_netlist_naming_conflict --fix --report ./netlist_renaming.xml + +# Apply fix-up to clustering nets based on routing results +pb_pin_fixup --verbose + +# Apply fix-up to Look-Up Table truth tables based on packing results +lut_truth_table_fixup + +# Build the module graph +# - Enabled compression on routing architecture modules +# - Enable pin duplication on grid modules +build_fabric --compress_routing #--verbose + +# Repack the netlist to physical pbs +# This must be done before bitstream generator and testbench generation +# Strongly recommend it is done after all the fix-up have been applied +repack #--verbose + +# Build the bitstream +# - Output the fabric-independent bitstream to a file +build_architecture_bitstream --verbose --file fabric_indepenent_bitstream.xml + +# Build fabric-dependent bitstream +build_fabric_bitstream --verbose + +# Write the Verilog netlist for FPGA fabric +# - Enable the use of explicit port mapping in Verilog netlist +write_fabric_verilog --file ./SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose + +# Write the Verilog testbench for FPGA fabric +# - We suggest the use of same output directory as fabric Verilog netlists +# - Must specify the reference benchmark file if you want to output any testbenches +# - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA +# - Enable pre-configured top-level testbench which is a fast verification skipping programming phase +# - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts +write_verilog_testbench --file ./SRC --reference_benchmark_file_path ${REFERENCE_VERILOG_TESTBENCH} --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini ./SimulationDeck/simulation_deck.ini + +# Write the SDC files for PnR backend +# - Turn on every options here +write_pnr_sdc --time_unit ps --file ./SDC + +# Write the SDC to run timing analysis for a mapped FPGA fabric +write_analysis_sdc --time_unit ps --file ./SDC_analysis + +# Finish and exit OpenFPGA +exit + +# Note : +# To run verification at the end of the flow maintain source in ./SRC directory diff --git a/openfpga_flow/tasks/openfpga_shell/sdc_time_unit/config/task.conf b/openfpga_flow/tasks/openfpga_shell/sdc_time_unit/config/task.conf new file mode 100644 index 000000000..1aeaa036f --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/sdc_time_unit/config/task.conf @@ -0,0 +1,34 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/sdc_time_unit_example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k4_N4_40nm_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k4_N4_tileable_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.blif + +[SYNTHESIS_PARAM] +bench0_top = and2 +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.v +bench0_chan_width = 300 + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +#end_flow_with_test= +#vpr_fpga_verilog_formal_verification_top_netlist= From 0985c720e92381c9456060a6eabc8d2d4aba8b7b Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 5 May 2020 13:40:18 -0600 Subject: [PATCH 510/645] remove regexp in SDC generation. --- .../src/fpga_sdc/pnr_sdc_routing_writer.cpp | 103 ++++-------------- 1 file changed, 23 insertions(+), 80 deletions(-) diff --git a/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp b/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp index e530e7903..de43b7803 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp +++ b/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp @@ -101,12 +101,12 @@ void print_pnr_sdc_constrain_sb_mux_timing(std::fstream& fp, } /* Constrain a path */ if (false == hierarchical) { - print_pnr_sdc_regexp_constrain_max_delay(fp, - module_path, - generate_sdc_port(module_manager.module_port(sb_module, module_input_port)), - module_path, - generate_sdc_port(module_manager.module_port(sb_module, module_output_port)), - switch_delays[module_input_port] / time_unit); + print_pnr_sdc_constrain_max_delay(fp, + module_path, + generate_sdc_port(module_manager.module_port(sb_module, module_input_port)), + module_path, + generate_sdc_port(module_manager.module_port(sb_module, module_output_port)), + switch_delays[module_input_port] / time_unit); } else { VTR_ASSERT_SAFE(true == hierarchical); @@ -222,13 +222,7 @@ void print_pnr_sdc_flatten_routing_constrain_sb_timing(const std::string& sdc_di ModuleId sb_module = module_manager.find_module(sb_instance_name); VTR_ASSERT(true == module_manager.valid_module_id(sb_module)); - std::string module_path = root_path - + std::string("\\/") - + std::string("("); - - /* Find all the instances in the top-level module */ - module_path += sb_instance_name; - module_path += std::string(")") + std::string("\\"); + std::string module_path = format_dir_path(root_path) + sb_instance_name; print_pnr_sdc_constrain_sb_timing(sdc_dir, time_unit, @@ -275,22 +269,7 @@ void print_pnr_sdc_compact_routing_constrain_sb_timing(const std::string& sdc_di ModuleId sb_module = module_manager.find_module(sb_module_name); VTR_ASSERT(true == module_manager.valid_module_id(sb_module)); - std::string module_path = root_path - + std::string("\\/") - + std::string("("); - - /* Find all the instances in the top-level module */ - bool first_element = true; - for (const size_t& instance_id : module_manager.child_module_instances(top_module, sb_module)) { - std::string sb_instance_name = module_manager.instance_name(top_module, sb_module, instance_id); - if (false == first_element) { - module_path += std::string("|"); - } - module_path += sb_instance_name; - first_element = false; - } - - module_path += std::string(")") + std::string("\\"); + std::string module_path = format_dir_path(root_path) + sb_module_name; print_pnr_sdc_constrain_sb_timing(sdc_dir, time_unit, @@ -388,12 +367,12 @@ void print_pnr_sdc_constrain_cb_mux_timing(std::fstream& fp, switch_delays[module_input_port] / time_unit); } else { VTR_ASSERT_SAFE(false == hierarchical); - print_pnr_sdc_regexp_constrain_max_delay(fp, - std::string(module_path), - generate_sdc_port(module_manager.module_port(cb_module, module_input_port)), - std::string(module_path), - generate_sdc_port(module_manager.module_port(cb_module, module_output_port)), - switch_delays[module_input_port] / time_unit); + print_pnr_sdc_constrain_max_delay(fp, + std::string(module_path), + generate_sdc_port(module_manager.module_port(cb_module, module_input_port)), + std::string(module_path), + generate_sdc_port(module_manager.module_port(cb_module, module_output_port)), + switch_delays[module_input_port] / time_unit); } } @@ -482,12 +461,12 @@ void print_pnr_sdc_constrain_cb_timing(const std::string& sdc_dir, routing_segment_delay / time_unit); } else { VTR_ASSERT_SAFE(false == hierarchical); - print_pnr_sdc_regexp_constrain_max_delay(fp, - std::string(module_path), - generate_sdc_port(module_manager.module_port(cb_module, input_port_id)), - std::string(module_path), - generate_sdc_port(module_manager.module_port(cb_module, output_port_id)), - routing_segment_delay / time_unit); + print_pnr_sdc_constrain_max_delay(fp, + std::string(module_path), + generate_sdc_port(module_manager.module_port(cb_module, input_port_id)), + std::string(module_path), + generate_sdc_port(module_manager.module_port(cb_module, output_port_id)), + routing_segment_delay / time_unit); } } @@ -551,13 +530,7 @@ void print_pnr_sdc_flatten_routing_constrain_cb_timing(const std::string& sdc_di ModuleId cb_module = module_manager.find_module(cb_instance_name); VTR_ASSERT(true == module_manager.valid_module_id(cb_module)); - std::string module_path = root_path - + std::string("\\/") - + std::string("("); - - /* Find all the instances in the top-level module */ - module_path += cb_instance_name; - module_path += std::string(")") + std::string("\\"); + std::string module_path = format_dir_path(root_path) + cb_instance_name; print_pnr_sdc_constrain_cb_timing(sdc_dir, time_unit, @@ -636,22 +609,7 @@ void print_pnr_sdc_compact_routing_constrain_cb_timing(const std::string& sdc_di ModuleId cb_module = module_manager.find_module(cb_module_name); VTR_ASSERT(true == module_manager.valid_module_id(cb_module)); - std::string module_path = root_path - + std::string("\\/") - + std::string("("); - - /* Find all the instances in the top-level module */ - bool first_element = true; - for (const size_t& instance_id : module_manager.child_module_instances(top_module, cb_module)) { - std::string cb_instance_name = module_manager.instance_name(top_module, cb_module, instance_id); - if (false == first_element) { - module_path += std::string("|"); - } - module_path += cb_instance_name; - first_element = false; - } - - module_path += std::string(")") + std::string("\\"); + std::string module_path = format_dir_path(root_path) + cb_module_name; print_pnr_sdc_constrain_cb_timing(sdc_dir, time_unit, @@ -676,22 +634,7 @@ void print_pnr_sdc_compact_routing_constrain_cb_timing(const std::string& sdc_di ModuleId cb_module = module_manager.find_module(cb_module_name); VTR_ASSERT(true == module_manager.valid_module_id(cb_module)); - std::string module_path = root_path - + std::string("\\/") - + std::string("("); - - /* Find all the instances in the top-level module */ - bool first_element = true; - for (const size_t& instance_id : module_manager.child_module_instances(top_module, cb_module)) { - std::string cb_instance_name = module_manager.instance_name(top_module, cb_module, instance_id); - if (false == first_element) { - module_path += std::string("|"); - } - module_path += cb_instance_name; - first_element = false; - } - - module_path += std::string(")") + std::string("\\"); + std::string module_path = format_dir_path(root_path) + cb_module_name; print_pnr_sdc_constrain_cb_timing(sdc_dir, time_unit, From 6aff33dd35ecc23d258ddfc043c1cac2f8df1a05 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 5 May 2020 14:36:27 -0600 Subject: [PATCH 511/645] add fabric hierarchy writer --- .../libopenfpgautil/src/openfpga_digest.cpp | 16 +++ .../libopenfpgautil/src/openfpga_digest.h | 3 + openfpga/src/base/openfpga_build_fabric.cpp | 25 ++++ openfpga/src/base/openfpga_build_fabric.h | 3 + openfpga/src/base/openfpga_setup_command.cpp | 47 ++++++- .../src/fabric/fabric_hierarchy_writer.cpp | 129 ++++++++++++++++++ openfpga/src/fabric/fabric_hierarchy_writer.h | 23 ++++ openfpga/test_script/and_k6_frac.openfpga | 2 + 8 files changed, 245 insertions(+), 3 deletions(-) create mode 100644 openfpga/src/fabric/fabric_hierarchy_writer.cpp create mode 100644 openfpga/src/fabric/fabric_hierarchy_writer.h diff --git a/libopenfpga/libopenfpgautil/src/openfpga_digest.cpp b/libopenfpga/libopenfpgautil/src/openfpga_digest.cpp index 3ae2083df..00cbbe1e3 100644 --- a/libopenfpga/libopenfpgautil/src/openfpga_digest.cpp +++ b/libopenfpga/libopenfpgautil/src/openfpga_digest.cpp @@ -235,4 +235,20 @@ void create_directory(const std::string& dir_path, const bool& recursive) { } } +/******************************************************************** + * Write a number of space to a file + ********************************************************************/ +bool write_space_to_file(std::fstream& fp, + const size_t& num_space) { + if (false == valid_file_stream(fp)) { + return false; + } + + for (size_t i = 0; i < num_space; ++i) { + fp << " "; + } + + return true; +} + } /* namespace openfpga ends */ diff --git a/libopenfpga/libopenfpgautil/src/openfpga_digest.h b/libopenfpga/libopenfpgautil/src/openfpga_digest.h index 0e1cd5d76..a28b2c4d0 100644 --- a/libopenfpga/libopenfpgautil/src/openfpga_digest.h +++ b/libopenfpga/libopenfpgautil/src/openfpga_digest.h @@ -25,6 +25,9 @@ std::string find_path_dir_name(const std::string& file_name); void create_directory(const std::string& dir_path, const bool& recursive = true); +bool write_space_to_file(std::fstream& fp, + const size_t& num_space); + } /* namespace openfpga ends */ #endif diff --git a/openfpga/src/base/openfpga_build_fabric.cpp b/openfpga/src/base/openfpga_build_fabric.cpp index 67203216a..e013ab267 100644 --- a/openfpga/src/base/openfpga_build_fabric.cpp +++ b/openfpga/src/base/openfpga_build_fabric.cpp @@ -11,6 +11,7 @@ #include "device_rr_gsb.h" #include "device_rr_gsb_utils.h" #include "build_device_module.h" +#include "fabric_hierarchy_writer.h" #include "openfpga_build_fabric.h" /* Include global variables of VPR */ @@ -85,4 +86,28 @@ int build_fabric(OpenfpgaContext& openfpga_ctx, return CMD_EXEC_SUCCESS; } +/******************************************************************** + * Build the module graph for FPGA device + *******************************************************************/ +int write_fabric_hierarchy(const OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context) { + + CommandOptionId opt_verbose = cmd.option("verbose"); + + /* Check the option '--file' is enabled or not + * Actually, it must be enabled as the shell interface will check + * before reaching this fuction + */ + CommandOptionId opt_file = cmd.option("file"); + VTR_ASSERT(true == cmd_context.option_enable(cmd, opt_file)); + VTR_ASSERT(false == cmd_context.option_value(cmd, opt_file).empty()); + + std::string hie_file_name = cmd_context.option_value(cmd, opt_file); + + /* Write hierarchy to a file */ + return write_fabric_hierarchy_to_text_file(openfpga_ctx.module_graph(), + hie_file_name, + cmd_context.option_enable(cmd, opt_verbose)); +} + } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_build_fabric.h b/openfpga/src/base/openfpga_build_fabric.h index 3b63c7ab2..6a090397d 100644 --- a/openfpga/src/base/openfpga_build_fabric.h +++ b/openfpga/src/base/openfpga_build_fabric.h @@ -18,6 +18,9 @@ namespace openfpga { int build_fabric(OpenfpgaContext& openfpga_ctx, const Command& cmd, const CommandContext& cmd_context); +int write_fabric_hierarchy(const OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context); + } /* end namespace openfpga */ #endif diff --git a/openfpga/src/base/openfpga_setup_command.cpp b/openfpga/src/base/openfpga_setup_command.cpp index 8f4b0cec3..2a3b11227 100644 --- a/openfpga/src/base/openfpga_setup_command.cpp +++ b/openfpga/src/base/openfpga_setup_command.cpp @@ -238,6 +238,37 @@ ShellCommandId add_openfpga_build_fabric_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id, + const std::vector& dependent_cmds) { + + Command shell_cmd("write_fabric_hierarchy"); + + /* Add an option '--file' */ + CommandOptionId opt_file = shell_cmd.add_option("file", true, "Specify the file name to write the hierarchy to"); + shell_cmd.set_option_short_name(opt_file, "f"); + shell_cmd.set_option_require_value(opt_file, openfpga::OPT_STRING); + + /* Add an option '--verbose' */ + shell_cmd.add_option("verbose", false, "Show verbose outputs"); + + /* Add command 'write_fabric_hierarchy' to the Shell */ + ShellCommandId shell_cmd_id = shell.add_command(shell_cmd, "Write the hierarchy of FPGA fabric graph to a plain-text file"); + shell.set_command_class(shell_cmd_id, cmd_class_id); + shell.set_command_const_execute_function(shell_cmd_id, write_fabric_hierarchy); + + /* Add command dependency to the Shell */ + shell.set_command_dependency(shell_cmd_id, dependent_cmds); + + return shell_cmd_id; +} + void add_openfpga_setup_commands(openfpga::Shell& shell) { /* Get the unique id of 'vpr' command which is to be used in creating the dependency graph */ const ShellCommandId& vpr_cmd_id = shell.command(std::string("vpr")); @@ -317,9 +348,19 @@ void add_openfpga_setup_commands(openfpga::Shell& shell) { /* The 'build_fabric' command should NOT be executed before 'link_openfpga_arch' */ std::vector build_fabric_dependent_cmds; build_fabric_dependent_cmds.push_back(link_arch_cmd_id); - add_openfpga_build_fabric_command(shell, - openfpga_setup_cmd_class, - build_fabric_dependent_cmds); + ShellCommandId build_fabric_cmd_id = add_openfpga_build_fabric_command(shell, + openfpga_setup_cmd_class, + build_fabric_dependent_cmds); + + /******************************** + * Command 'write_fabric_hierarchy' + */ + /* The 'write_fabric_hierarchy' command should NOT be executed before 'build_fabric' */ + std::vector write_fabric_hie_dependent_cmds; + write_fabric_hie_dependent_cmds.push_back(build_fabric_cmd_id); + add_openfpga_write_fabric_hierarchy_command(shell, + openfpga_setup_cmd_class, + write_fabric_hie_dependent_cmds); } } /* end namespace openfpga */ diff --git a/openfpga/src/fabric/fabric_hierarchy_writer.cpp b/openfpga/src/fabric/fabric_hierarchy_writer.cpp new file mode 100644 index 000000000..7b752f015 --- /dev/null +++ b/openfpga/src/fabric/fabric_hierarchy_writer.cpp @@ -0,0 +1,129 @@ +/*************************************************************************************** + * Output internal structure of Module Graph hierarchy to file formats + ***************************************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_log.h" +#include "vtr_assert.h" +#include "vtr_time.h" + +/* Headers from openfpgautil library */ +#include "openfpga_digest.h" + +#include "openfpga_naming.h" + +#include "fabric_hierarchy_writer.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/*************************************************************************************** + * Recursively output child module of the parent_module to a text file + * We use Depth-First Search (DFS) here so that we can output a tree down to leaf first + * Add space (indent) based on the depth in hierarchy + * e.g. depth = 1 means a space as indent + ***************************************************************************************/ +static +int rec_output_module_hierarchy_to_text_file(std::fstream& fp, + const size_t& hie_depth, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const bool& verbose) { + if (false == valid_file_stream(fp)) { + return 2; + } + + /* Iterate over all the child module */ + for (const ModuleId& child_module : module_manager.child_modules(parent_module)) { + if (false == write_space_to_file(fp, hie_depth)) { + return 2; + } + + if (true != module_manager.valid_module_id(child_module)) { + VTR_LOGV_ERROR(verbose, + "Unable to find the child module '%u'!\n", + size_t(child_module)); + return 1; + } + + fp << module_manager.module_name(child_module); + fp << "\n"; + + /* Go to next level */ + int status = rec_output_module_hierarchy_to_text_file(fp, + hie_depth + 1, /* Increment the depth for the next level */ + module_manager, + child_module, + verbose); + if (0 != status) { + return status; + } + } + + return 0; +} + +/*************************************************************************************** + * Write the hierarchy of modules to a plain text file + * e.g., + * + * + * ... + * This file is mainly used by hierarchical P&R flow + * + * Return 0 if successful + * Return 1 if there are more serious bugs in the architecture + * Return 2 if fail when creating files + ***************************************************************************************/ +int write_fabric_hierarchy_to_text_file(const ModuleManager& module_manager, + const std::string& fname, + const bool& verbose) { + std::string timer_message = std::string("Write fabric hierarchy to plain-text file '") + fname + std::string("'"); + + std::string dir_path = format_dir_path(find_path_dir_name(fname)); + + /* Create directories */ + create_directory(dir_path); + + /* Start time count */ + vtr::ScopedStartFinishTimer timer(timer_message); + + /* Use default name if user does not provide one */ + VTR_ASSERT(true != fname.empty()); + + /* Create a file handler*/ + std::fstream fp; + /* Open a file */ + fp.open(fname, std::fstream::out | std::fstream::trunc); + + /* Validate the file stream */ + check_file_stream(fname.c_str(), fp); + + /* Find top-level module */ + std::string top_module_name = generate_fpga_top_module_name(); + ModuleId top_module = module_manager.find_module(top_module_name); + if (true != module_manager.valid_module_id(top_module)) { + VTR_LOGV_ERROR(verbose, + "Unable to find the top-level module '%s'!\n", + top_module_name.c_str()); + return 1; + } + + /* Record current depth of module: top module is the root with 0 depth */ + size_t hie_depth = 0; + + fp << top_module_name << "\n"; + + /* Visit child module recursively and output the hierarchy */ + int err_code = rec_output_module_hierarchy_to_text_file(fp, + hie_depth + 1, /* Start with level 1 */ + module_manager, + top_module, + verbose); + + /* close a file */ + fp.close(); + + return err_code; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fabric/fabric_hierarchy_writer.h b/openfpga/src/fabric/fabric_hierarchy_writer.h new file mode 100644 index 000000000..48e112c76 --- /dev/null +++ b/openfpga/src/fabric/fabric_hierarchy_writer.h @@ -0,0 +1,23 @@ +#ifndef FABRIC_HIERARCHY_WRITER_H +#define FABRIC_HIERARCHY_WRITER_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "vpr_context.h" +#include "openfpga_context.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +int write_fabric_hierarchy_to_text_file(const ModuleManager& module_manager, + const std::string& fname, + const bool& verbose); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/test_script/and_k6_frac.openfpga b/openfpga/test_script/and_k6_frac.openfpga index 20a75a661..014142fb1 100644 --- a/openfpga/test_script/and_k6_frac.openfpga +++ b/openfpga/test_script/and_k6_frac.openfpga @@ -24,6 +24,8 @@ lut_truth_table_fixup #--verbose # - Enable pin duplication on grid modules build_fabric --compress_routing --duplicate_grid_pin #--verbose +write_fabric_hierarchy --file ./fabric_hierarchy.txt + # Repack the netlist to physical pbs # This must be done before bitstream generator and testbench generation # Strongly recommend it is done after all the fix-up have been applied From 1943929353ad5a70355f62a131f705e5c11ff29b Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 5 May 2020 14:38:46 -0600 Subject: [PATCH 512/645] add write_fabric_hierarchy to regression tests --- .../duplicated_grid_pin_example_script.openfpga | 4 ++++ openfpga_flow/OpenFPGAShellScripts/example_script.openfpga | 4 ++++ .../flatten_routing_example_script.openfpga | 4 ++++ .../generate_fabric_example_script.openfpga | 4 ++++ .../generate_testbench_example_script.openfpga | 4 ++++ .../implicit_verilog_example_script.openfpga | 4 ++++ .../OpenFPGAShellScripts/mcnc_example_script.openfpga | 4 ++++ .../sdc_time_unit_example_script.openfpga | 4 ++++ 8 files changed, 32 insertions(+) diff --git a/openfpga_flow/OpenFPGAShellScripts/duplicated_grid_pin_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/duplicated_grid_pin_example_script.openfpga index ac808a9a6..1ccd8418d 100644 --- a/openfpga_flow/OpenFPGAShellScripts/duplicated_grid_pin_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/duplicated_grid_pin_example_script.openfpga @@ -23,6 +23,10 @@ lut_truth_table_fixup # - Enable pin duplication on grid modules build_fabric --compress_routing #--verbose +# Write the fabric hierarchy of module graph to a file +# This is used by hierarchical PnR flows +write_fabric_hierarchy --file ./fabric_hierarchy.txt + # Repack the netlist to physical pbs # This must be done before bitstream generator and testbench generation # Strongly recommend it is done after all the fix-up have been applied diff --git a/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga index ac808a9a6..1ccd8418d 100644 --- a/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga @@ -23,6 +23,10 @@ lut_truth_table_fixup # - Enable pin duplication on grid modules build_fabric --compress_routing #--verbose +# Write the fabric hierarchy of module graph to a file +# This is used by hierarchical PnR flows +write_fabric_hierarchy --file ./fabric_hierarchy.txt + # Repack the netlist to physical pbs # This must be done before bitstream generator and testbench generation # Strongly recommend it is done after all the fix-up have been applied diff --git a/openfpga_flow/OpenFPGAShellScripts/flatten_routing_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/flatten_routing_example_script.openfpga index 06a89a4f5..529fc19ae 100644 --- a/openfpga_flow/OpenFPGAShellScripts/flatten_routing_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/flatten_routing_example_script.openfpga @@ -23,6 +23,10 @@ lut_truth_table_fixup # - Enable pin duplication on grid modules build_fabric #--verbose +# Write the fabric hierarchy of module graph to a file +# This is used by hierarchical PnR flows +write_fabric_hierarchy --file ./fabric_hierarchy.txt + # Repack the netlist to physical pbs # This must be done before bitstream generator and testbench generation # Strongly recommend it is done after all the fix-up have been applied diff --git a/openfpga_flow/OpenFPGAShellScripts/generate_fabric_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/generate_fabric_example_script.openfpga index 23ed17f59..6514f36b4 100644 --- a/openfpga_flow/OpenFPGAShellScripts/generate_fabric_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/generate_fabric_example_script.openfpga @@ -17,6 +17,10 @@ check_netlist_naming_conflict --fix --report ./netlist_renaming.xml # - Enable pin duplication on grid modules build_fabric --compress_routing #--verbose +# Write the fabric hierarchy of module graph to a file +# This is used by hierarchical PnR flows +write_fabric_hierarchy --file ./fabric_hierarchy.txt + # Write the Verilog netlist for FPGA fabric # - Enable the use of explicit port mapping in Verilog netlist write_fabric_verilog --file ./SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose diff --git a/openfpga_flow/OpenFPGAShellScripts/generate_testbench_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/generate_testbench_example_script.openfpga index 0926935e9..5f7be9ffa 100644 --- a/openfpga_flow/OpenFPGAShellScripts/generate_testbench_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/generate_testbench_example_script.openfpga @@ -23,6 +23,10 @@ lut_truth_table_fixup # - Enable pin duplication on grid modules build_fabric --compress_routing #--verbose +# Write the fabric hierarchy of module graph to a file +# This is used by hierarchical PnR flows +write_fabric_hierarchy --file ./fabric_hierarchy.txt + # Repack the netlist to physical pbs # This must be done before bitstream generator and testbench generation # Strongly recommend it is done after all the fix-up have been applied diff --git a/openfpga_flow/OpenFPGAShellScripts/implicit_verilog_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/implicit_verilog_example_script.openfpga index d23534cbe..aa4ea30ad 100644 --- a/openfpga_flow/OpenFPGAShellScripts/implicit_verilog_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/implicit_verilog_example_script.openfpga @@ -23,6 +23,10 @@ lut_truth_table_fixup # - Enable pin duplication on grid modules build_fabric --compress_routing --duplicate_grid_pin #--verbose +# Write the fabric hierarchy of module graph to a file +# This is used by hierarchical PnR flows +write_fabric_hierarchy --file ./fabric_hierarchy.txt + # Repack the netlist to physical pbs # This must be done before bitstream generator and testbench generation # Strongly recommend it is done after all the fix-up have been applied diff --git a/openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga index ba086855e..71d3ee294 100644 --- a/openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga @@ -23,6 +23,10 @@ lut_truth_table_fixup # - Enable pin duplication on grid modules build_fabric --compress_routing #--verbose +# Write the fabric hierarchy of module graph to a file +# This is used by hierarchical PnR flows +write_fabric_hierarchy --file ./fabric_hierarchy.txt + # Repack the netlist to physical pbs # This must be done before bitstream generator and testbench generation # Strongly recommend it is done after all the fix-up have been applied diff --git a/openfpga_flow/OpenFPGAShellScripts/sdc_time_unit_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/sdc_time_unit_example_script.openfpga index 1d153a4e9..39c4d1c70 100644 --- a/openfpga_flow/OpenFPGAShellScripts/sdc_time_unit_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/sdc_time_unit_example_script.openfpga @@ -23,6 +23,10 @@ lut_truth_table_fixup # - Enable pin duplication on grid modules build_fabric --compress_routing #--verbose +# Write the fabric hierarchy of module graph to a file +# This is used by hierarchical PnR flows +write_fabric_hierarchy --file ./fabric_hierarchy.txt + # Repack the netlist to physical pbs # This must be done before bitstream generator and testbench generation # Strongly recommend it is done after all the fix-up have been applied From c651df64218961058d03213dcbca9451bd926672 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 5 May 2020 15:35:41 -0600 Subject: [PATCH 513/645] add hierarchy writer to SDC generator --- openfpga/src/base/openfpga_sdc.cpp | 3 + openfpga/src/base/openfpga_sdc_command.cpp | 3 + openfpga/src/fpga_sdc/pnr_sdc_option.cpp | 8 + openfpga/src/fpga_sdc/pnr_sdc_option.h | 3 + openfpga/src/fpga_sdc/pnr_sdc_writer.cpp | 26 +++ .../src/fpga_sdc/sdc_hierarchy_writer.cpp | 173 ++++++++++++++++++ 6 files changed, 216 insertions(+) create mode 100644 openfpga/src/fpga_sdc/sdc_hierarchy_writer.cpp diff --git a/openfpga/src/base/openfpga_sdc.cpp b/openfpga/src/base/openfpga_sdc.cpp index d90376839..d492e653a 100644 --- a/openfpga/src/base/openfpga_sdc.cpp +++ b/openfpga/src/base/openfpga_sdc.cpp @@ -33,6 +33,7 @@ int write_pnr_sdc(OpenfpgaContext& openfpga_ctx, CommandOptionId opt_flatten_names = cmd.option("flatten_names"); CommandOptionId opt_hierarchical = cmd.option("hierarchical"); CommandOptionId opt_time_unit = cmd.option("time_unit"); + CommandOptionId opt_output_hierarchy = cmd.option("output_hierarchy"); CommandOptionId opt_constrain_global_port = cmd.option("constrain_global_port"); CommandOptionId opt_constrain_non_clock_global_port = cmd.option("constrain_non_clock_global_port"); CommandOptionId opt_constrain_grid = cmd.option("constrain_grid"); @@ -60,6 +61,8 @@ int write_pnr_sdc(OpenfpgaContext& openfpga_ctx, options.set_time_unit(string_to_time_unit(cmd_context.option_value(cmd, opt_time_unit))); } + options.set_output_hierarchy(cmd_context.option_enable(cmd, opt_output_hierarchy)); + options.set_constrain_global_port(cmd_context.option_enable(cmd, opt_constrain_global_port)); options.set_constrain_non_clock_global_port(cmd_context.option_enable(cmd, opt_constrain_non_clock_global_port)); options.set_constrain_grid(cmd_context.option_enable(cmd, opt_constrain_grid)); diff --git a/openfpga/src/base/openfpga_sdc_command.cpp b/openfpga/src/base/openfpga_sdc_command.cpp index bdf123e8b..6d58d4ac1 100644 --- a/openfpga/src/base/openfpga_sdc_command.cpp +++ b/openfpga/src/base/openfpga_sdc_command.cpp @@ -32,6 +32,9 @@ ShellCommandId add_openfpga_write_pnr_sdc_command(openfpga::Shell + * / + * ... + * This file is mainly used by hierarchical P&R flow + * + * Return 0 if successful + * Return 1 if there are more serious bugs in the architecture + * Return 2 if fail when creating files + ***************************************************************************************/ +void print_pnr_sdc_routing_sb_hierarchy(const std::string& sdc_dir, + const ModuleManager& module_manager, + const ModuleId& top_module, + const DeviceRRGSB& device_rr_gsb) { + + std::string fname(sdc_dir + std::string("sb_hierarchy.txt")); + + std::string timer_message = std::string("Write Switch Block hierarchy to plain-text file '") + fname + std::string("'"); + + std::string dir_path = format_dir_path(find_path_dir_name(fname)); + + /* Create directories */ + create_directory(dir_path); + + /* Start time count */ + vtr::ScopedStartFinishTimer timer(timer_message); + + /* Use default name if user does not provide one */ + VTR_ASSERT(true != fname.empty()); + + /* Create a file handler*/ + std::fstream fp; + /* Open a file */ + fp.open(fname, std::fstream::out | std::fstream::trunc); + + /* Validate the file stream */ + check_file_stream(fname.c_str(), fp); + + for (size_t isb = 0; isb < device_rr_gsb.get_num_sb_unique_module(); ++isb) { + const RRGSB& rr_gsb = device_rr_gsb.get_sb_unique_module(isb); + if (false == rr_gsb.is_sb_exist()) { + continue; + } + + /* Find all the sb instance under this module + * Create a regular expression to include these instance names + */ + vtr::Point gsb_coordinate(rr_gsb.get_sb_x(), rr_gsb.get_sb_y()); + std::string sb_module_name = generate_switch_block_module_name(gsb_coordinate); + + ModuleId sb_module = module_manager.find_module(sb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(sb_module)); + + /* Create the file name for SDC */ + std::string sdc_fname(sdc_dir + generate_switch_block_module_name(gsb_coordinate) + std::string(SDC_FILE_NAME_POSTFIX)); + + fp << sdc_fname << "\n"; + + /* Go through all the instance */ + for (const size_t& instance_id : module_manager.child_module_instances(top_module, sb_module)) { + std::string sb_instance_name = module_manager.instance_name(top_module, sb_module, instance_id); + fp << " - "; + fp << sb_module_name << "/" << sb_instance_name << "\n"; + } + + fp << "\n"; + } + + /* close a file */ + fp.close(); +} + +/*************************************************************************************** + * Write the hierarchy of Switch Block module and its instances to a plain text file + * e.g., + * + * / + * ... + * This file is mainly used by hierarchical P&R flow + * + * Return 0 if successful + * Return 1 if there are more serious bugs in the architecture + * Return 2 if fail when creating files + ***************************************************************************************/ +void print_pnr_sdc_routing_cb_hierarchy(const std::string& sdc_dir, + const ModuleManager& module_manager, + const ModuleId& top_module, + const t_rr_type& cb_type, + const DeviceRRGSB& device_rr_gsb) { + + /* TODO: This is dirty, should use constant to handle this */ + std::string fname(sdc_dir); + if (CHANX == cb_type) { + fname += std::string("cbx_hierarchy.txt"); + } else { + VTR_ASSERT(CHANY == cb_type); + fname += std::string("cby_hierarchy.txt"); + } + + std::string timer_message = std::string("Write Connection Block hierarchy to plain-text file '") + fname + std::string("'"); + + std::string dir_path = format_dir_path(find_path_dir_name(fname)); + + /* Create directories */ + create_directory(dir_path); + + /* Start time count */ + vtr::ScopedStartFinishTimer timer(timer_message); + + /* Use default name if user does not provide one */ + VTR_ASSERT(true != fname.empty()); + + /* Create a file handler*/ + std::fstream fp; + /* Open a file */ + fp.open(fname, std::fstream::out | std::fstream::trunc); + + /* Validate the file stream */ + check_file_stream(fname.c_str(), fp); + + /* Print SDC for unique X-direction connection block modules */ + for (size_t icb = 0; icb < device_rr_gsb.get_num_cb_unique_module(cb_type); ++icb) { + const RRGSB& unique_mirror = device_rr_gsb.get_cb_unique_module(cb_type, icb); + + /* Find all the cb instance under this module + * Create a regular expression to include these instance names + */ + vtr::Point gsb_coordinate(unique_mirror.get_cb_x(cb_type), unique_mirror.get_cb_y(cb_type)); + std::string cb_module_name = generate_connection_block_module_name(cb_type, gsb_coordinate); + ModuleId cb_module = module_manager.find_module(cb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(cb_module)); + + /* Create the file name for SDC */ + std::string sdc_fname(sdc_dir + generate_connection_block_module_name(cb_type, gsb_coordinate) + std::string(SDC_FILE_NAME_POSTFIX)); + + fp << sdc_fname << "\n"; + + /* Go through all the instance */ + for (const size_t& instance_id : module_manager.child_module_instances(top_module, cb_module)) { + std::string cb_instance_name = module_manager.instance_name(top_module, cb_module, instance_id); + fp << " - "; + fp << cb_module_name << "/" << cb_instance_name << "\n"; + } + + fp << "\n"; + } + + /* close a file */ + fp.close(); +} + +} /* end namespace openfpga */ From 17c254a3706323626008da0433f42679052edb38 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 5 May 2020 15:36:07 -0600 Subject: [PATCH 514/645] add missing file to follow up the previous commit --- openfpga/src/fpga_sdc/sdc_hierarchy_writer.h | 30 ++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 openfpga/src/fpga_sdc/sdc_hierarchy_writer.h diff --git a/openfpga/src/fpga_sdc/sdc_hierarchy_writer.h b/openfpga/src/fpga_sdc/sdc_hierarchy_writer.h new file mode 100644 index 000000000..3e7cae5a7 --- /dev/null +++ b/openfpga/src/fpga_sdc/sdc_hierarchy_writer.h @@ -0,0 +1,30 @@ +#ifndef SDC_HIERARCHY_WRITER_H +#define SDC_HIERARCHY_WRITER_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "vpr_context.h" +#include "openfpga_context.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_pnr_sdc_routing_sb_hierarchy(const std::string& sdc_dir, + const ModuleManager& module_manager, + const ModuleId& top_module, + const DeviceRRGSB& device_rr_gsb); + +void print_pnr_sdc_routing_cb_hierarchy(const std::string& sdc_dir, + const ModuleManager& module_manager, + const ModuleId& top_module, + const t_rr_type& cb_type, + const DeviceRRGSB& device_rr_gsb); + +} /* end namespace openfpga */ + +#endif From 752470c2da734d2c69753f883bf6682761575e49 Mon Sep 17 00:00:00 2001 From: Xifan Tang Date: Tue, 5 May 2020 15:50:55 -0600 Subject: [PATCH 515/645] update documentation on write hierarchy command and options --- docs/source/openfpga_shell/openfpga_commands.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/source/openfpga_shell/openfpga_commands.rst b/docs/source/openfpga_shell/openfpga_commands.rst index 987e8b981..296a24a25 100644 --- a/docs/source/openfpga_shell/openfpga_commands.rst +++ b/docs/source/openfpga_shell/openfpga_commands.rst @@ -102,6 +102,15 @@ Setup OpenFPGA .. note:: This is a must-run command before launching FPGA-Verilog, FPGA-Bitstream, FPGA-SDC and FPGA-SPICE +.. option:: write_fabric_hierarchy + + Write the hierarchy of FPGA fabric graph to a plain-text file + + - ``--file`` or ``-f`` Specify the file name to write the hierarchy. + + - ``--verbose`` Show verbose log + + .. note:: This file is designed for hierarchical PnR flow, which requires the tree of Multiple-Instanced-Blocks (MIBs). FPGA-Bitstream ~~~~~~~~~~~~~~ @@ -182,6 +191,10 @@ FPGA-SDC - ``--time_unit`` Specify a time unit to be used in SDC files. Acceptable values are string: ``as`` | ``fs`` | ``ps`` | ``ns`` | ``us`` | ``ms`` | ``ks`` | ``Ms``. By default, we will consider second (``s``). + - ``--output_hierarchy`` Output hierarchy of Multiple-Instance-Blocks(MIBs) to plain text file. This is applied to constrain timing for grids, Switch Blocks and Connection Blocks. + + .. note:: Valid only when ``compress_routing`` is enabled in ``build_fabric`` + - ``--constrain_global_port`` Constrain all the global ports of FPGA fabric. - ``--constrain_non_clock_global_port`` Constrain all the non-clock global ports as clocks ports of FPGA fabric From d9dc7160a7f98efcb3be1a593f01860cf299e853 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 5 May 2020 16:14:08 -0600 Subject: [PATCH 516/645] minor fix on the hierarchy writer in SDC generator --- openfpga/src/base/openfpga_sdc_command.cpp | 2 +- openfpga/src/fpga_sdc/sdc_hierarchy_writer.cpp | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openfpga/src/base/openfpga_sdc_command.cpp b/openfpga/src/base/openfpga_sdc_command.cpp index 6d58d4ac1..a0f578786 100644 --- a/openfpga/src/base/openfpga_sdc_command.cpp +++ b/openfpga/src/base/openfpga_sdc_command.cpp @@ -36,7 +36,7 @@ ShellCommandId add_openfpga_write_pnr_sdc_command(openfpga::Shell Date: Tue, 5 May 2020 16:40:41 -0600 Subject: [PATCH 517/645] add --depth option to fabric hierarchy writer --- openfpga/src/base/openfpga_build_fabric.cpp | 14 ++++++++++++++ openfpga/src/base/openfpga_setup_command.cpp | 4 ++++ .../src/fabric/fabric_hierarchy_writer.cpp | 19 ++++++++++++++++--- openfpga/src/fabric/fabric_hierarchy_writer.h | 1 + 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/openfpga/src/base/openfpga_build_fabric.cpp b/openfpga/src/base/openfpga_build_fabric.cpp index e013ab267..708bef124 100644 --- a/openfpga/src/base/openfpga_build_fabric.cpp +++ b/openfpga/src/base/openfpga_build_fabric.cpp @@ -102,11 +102,25 @@ int write_fabric_hierarchy(const OpenfpgaContext& openfpga_ctx, VTR_ASSERT(true == cmd_context.option_enable(cmd, opt_file)); VTR_ASSERT(false == cmd_context.option_value(cmd, opt_file).empty()); + /* Default depth requirement, will not stop until the leaf */ + int depth = -1; + CommandOptionId opt_depth = cmd.option("depth"); + if (true == cmd_context.option_enable(cmd, opt_depth)) { + depth = std::atoi(cmd_context.option_value(cmd, opt_depth).c_str()); + /* Error out if we have negative depth */ + if (0 > depth) { + VTR_LOG_ERROR("Invalid depth '%d' which should be 0 or a positive number!\n", + depth); + return CMD_EXEC_FATAL_ERROR; + } + } + std::string hie_file_name = cmd_context.option_value(cmd, opt_file); /* Write hierarchy to a file */ return write_fabric_hierarchy_to_text_file(openfpga_ctx.module_graph(), hie_file_name, + size_t(depth), cmd_context.option_enable(cmd, opt_verbose)); } diff --git a/openfpga/src/base/openfpga_setup_command.cpp b/openfpga/src/base/openfpga_setup_command.cpp index 2a3b11227..8af22e7da 100644 --- a/openfpga/src/base/openfpga_setup_command.cpp +++ b/openfpga/src/base/openfpga_setup_command.cpp @@ -255,6 +255,10 @@ ShellCommandId add_openfpga_write_fabric_hierarchy_command(openfpga::Shell Date: Tue, 5 May 2020 16:44:07 -0600 Subject: [PATCH 518/645] update documentation on the depth option for fabric hierarchy writer --- docs/source/openfpga_shell/openfpga_commands.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source/openfpga_shell/openfpga_commands.rst b/docs/source/openfpga_shell/openfpga_commands.rst index 296a24a25..5cd5a8c33 100644 --- a/docs/source/openfpga_shell/openfpga_commands.rst +++ b/docs/source/openfpga_shell/openfpga_commands.rst @@ -108,6 +108,8 @@ Setup OpenFPGA - ``--file`` or ``-f`` Specify the file name to write the hierarchy. + - ``--depth`` Specify at which depth of the fabric module graph should the writer stop outputting. The root module start from depth 0. For example, if you want a two-level hierarchy, you should specify depth as 1. + - ``--verbose`` Show verbose log .. note:: This file is designed for hierarchical PnR flow, which requires the tree of Multiple-Instanced-Blocks (MIBs). From b57a90a6ca98bd5080ff321e840ec70d2b57d4d3 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 5 May 2020 20:12:13 -0600 Subject: [PATCH 519/645] add SDC hierarchy writer for grids and now support flatten hierarchy in grid timing constraints --- openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp | 124 +++++++++-- openfpga/src/fpga_sdc/pnr_sdc_grid_writer.h | 2 + openfpga/src/fpga_sdc/pnr_sdc_writer.cpp | 18 +- .../src/fpga_sdc/sdc_hierarchy_writer.cpp | 206 +++++++++++++++++- openfpga/src/fpga_sdc/sdc_hierarchy_writer.h | 7 + openfpga/src/fpga_sdc/sdc_writer_naming.h | 5 + 6 files changed, 342 insertions(+), 20 deletions(-) diff --git a/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp b/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp index f0708cc61..740ba097b 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp +++ b/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp @@ -21,7 +21,6 @@ #include "openfpga_digest.h" #include "openfpga_side_manager.h" - #include "openfpga_interconnect_types.h" #include "vpr_utils.h" #include "mux_utils.h" @@ -30,6 +29,7 @@ #include "openfpga_naming.h" #include "pb_type_utils.h" #include "pb_graph_utils.h" +#include "openfpga_physical_tile_utils.h" #include "sdc_writer_naming.h" #include "sdc_writer_utils.h" @@ -45,6 +45,8 @@ namespace openfpga { static void print_pnr_sdc_constrain_pb_pin_interc_timing(std::fstream& fp, const float& time_unit, + const bool& hierarchical, + const std::string& module_path, const ModuleManager& module_manager, const ModuleId& parent_module, t_pb_graph_pin* des_pb_graph_pin, @@ -147,11 +149,22 @@ void print_pnr_sdc_constrain_pb_pin_interc_timing(std::fstream& fp, continue; } + /* Give full path if hierarchical is not enabled */ + std::string src_module_path = src_instance_name; + if (false == hierarchical) { + src_module_path = module_path + src_instance_name; + } + + std::string des_module_path = des_instance_name; + if (false == hierarchical) { + src_module_path = module_path + des_instance_name; + } + /* Print a SDC timing constraint */ print_pnr_sdc_constrain_max_delay(fp, - src_instance_name, + src_module_path, generate_sdc_port(src_port), - des_instance_name, + des_module_path, generate_sdc_port(des_port), des_pb_graph_pin->input_edges[iedge]->delay_max / time_unit); } @@ -164,6 +177,8 @@ void print_pnr_sdc_constrain_pb_pin_interc_timing(std::fstream& fp, static void print_pnr_sdc_constrain_pb_interc_timing(std::fstream& fp, const float& time_unit, + const bool& hierarchical, + const std::string& module_path, const ModuleManager& module_manager, const ModuleId& parent_module, t_pb_graph_node* des_pb_graph_node, @@ -181,6 +196,8 @@ void print_pnr_sdc_constrain_pb_interc_timing(std::fstream& fp, /* Get the selected edge of current pin*/ print_pnr_sdc_constrain_pb_pin_interc_timing(fp, time_unit, + hierarchical, + module_path, module_manager, parent_module, &(des_pb_graph_node->input_pins[iport][ipin]), physical_mode, @@ -194,6 +211,8 @@ void print_pnr_sdc_constrain_pb_interc_timing(std::fstream& fp, for (int ipin = 0; ipin < des_pb_graph_node->num_output_pins[iport]; ++ipin) { print_pnr_sdc_constrain_pb_pin_interc_timing(fp, time_unit, + hierarchical, + module_path, module_manager, parent_module, &(des_pb_graph_node->output_pins[iport][ipin]), physical_mode, @@ -223,6 +242,8 @@ void print_pnr_sdc_constrain_pb_interc_timing(std::fstream& fp, static void print_pnr_sdc_constrain_pb_graph_node_timing(const std::string& sdc_dir, const float& time_unit, + const bool& hierarchical, + const std::string& module_path, const ModuleManager& module_manager, t_pb_graph_node* parent_pb_graph_node, t_mode* physical_mode, @@ -260,6 +281,8 @@ void print_pnr_sdc_constrain_pb_graph_node_timing(const std::string& sdc_dir, */ print_pnr_sdc_constrain_pb_interc_timing(fp, time_unit, + hierarchical, + module_path, module_manager, pb_module, parent_pb_graph_node, CIRCUIT_PB_PORT_OUTPUT, @@ -279,6 +302,8 @@ void print_pnr_sdc_constrain_pb_graph_node_timing(const std::string& sdc_dir, /* For each child_pb_graph_node input pins*/ print_pnr_sdc_constrain_pb_interc_timing(fp, time_unit, + hierarchical, + module_path, module_manager, pb_module, child_pb_graph_node, CIRCUIT_PB_PORT_INPUT, @@ -303,6 +328,8 @@ void print_pnr_sdc_constrain_pb_graph_node_timing(const std::string& sdc_dir, static void print_pnr_sdc_constrain_primitive_pb_graph_node(const std::string& sdc_dir, const float& time_unit, + const bool& hierarchical, + const std::string& module_path, const ModuleManager& module_manager, t_pb_graph_node* primitive_pb_graph_node, const bool& constrain_zero_delay_paths) { @@ -394,13 +421,20 @@ void print_pnr_sdc_constrain_primitive_pb_graph_node(const std::string& sdc_dir, /* Find max delay between src and sink pin */ float tmax = sink_pin->input_edges[iedge]->delay_max; + + /* Generate module path in hierarchy depending if the hierarchical is enabled */ + std::string module_hie_path = pb_module_name; + if (false == hierarchical) { + module_hie_path = module_path + pb_module_name; + } + /* If the delay is zero, constrain only when user wants it */ if ( (true == constrain_zero_delay_paths) || (0. == tmax) ) { print_pnr_sdc_constrain_max_delay(fp, - pb_module_name, + module_hie_path, generate_sdc_port(src_port), - pb_module_name, + module_hie_path, generate_sdc_port(sink_port), tmax / time_unit); } @@ -411,9 +445,9 @@ void print_pnr_sdc_constrain_primitive_pb_graph_node(const std::string& sdc_dir, if ( (true == constrain_zero_delay_paths) || (0. == tmin) ) { print_pnr_sdc_constrain_min_delay(fp, - pb_module_name, + module_hie_path, generate_sdc_port(src_port), - pb_module_name, + module_hie_path, generate_sdc_port(sink_port), tmin / time_unit); } @@ -433,6 +467,8 @@ void print_pnr_sdc_constrain_primitive_pb_graph_node(const std::string& sdc_dir, static void rec_print_pnr_sdc_constrain_pb_graph_timing(const std::string& sdc_dir, const float& time_unit, + const bool& hierarchical, + const std::string& module_path, const ModuleManager& module_manager, const VprDeviceAnnotation& device_annotation, t_pb_graph_node* parent_pb_graph_node, @@ -451,6 +487,8 @@ void rec_print_pnr_sdc_constrain_pb_graph_timing(const std::string& sdc_dir, if (true == is_primitive_pb_type(parent_pb_type)) { print_pnr_sdc_constrain_primitive_pb_graph_node(sdc_dir, time_unit, + hierarchical, + module_path, module_manager, parent_pb_graph_node, constrain_zero_delay_paths); @@ -465,6 +503,8 @@ void rec_print_pnr_sdc_constrain_pb_graph_timing(const std::string& sdc_dir, /* Write a SDC file for this pb_type */ print_pnr_sdc_constrain_pb_graph_node_timing(sdc_dir, time_unit, + hierarchical, + module_path, module_manager, parent_pb_graph_node, physical_mode, @@ -476,6 +516,8 @@ void rec_print_pnr_sdc_constrain_pb_graph_timing(const std::string& sdc_dir, for (int ipb = 0; ipb < physical_mode->num_pb_type_children; ++ipb) { rec_print_pnr_sdc_constrain_pb_graph_timing(sdc_dir, time_unit, + hierarchical, + format_dir_path(module_path + std::string(physical_mode->pb_type_children[ipb].name)), module_manager, device_annotation, &(parent_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ipb][0]), @@ -488,26 +530,80 @@ void rec_print_pnr_sdc_constrain_pb_graph_timing(const std::string& sdc_dir, *******************************************************************/ void print_pnr_sdc_constrain_grid_timing(const std::string& sdc_dir, const float& time_unit, + const bool& hierarchical, const DeviceContext& device_ctx, const VprDeviceAnnotation& device_annotation, const ModuleManager& module_manager, + const ModuleId& top_module, const bool& constrain_zero_delay_paths) { /* Start time count */ vtr::ScopedStartFinishTimer timer("Write SDC for constraining grid timing for P&R flow"); + std::string root_path = format_dir_path(module_manager.module_name(top_module)); + for (const t_physical_tile_type& physical_tile : device_ctx.physical_tile_types) { /* Bypass empty type or nullptr */ if (true == is_empty_type(&physical_tile)) { continue; - } else { - VTR_ASSERT(1 == physical_tile.equivalent_sites.size()); - t_pb_graph_node* pb_graph_head = physical_tile.equivalent_sites[0]->pb_graph_head; - if (nullptr == pb_graph_head) { - continue; + } + + VTR_ASSERT(1 == physical_tile.equivalent_sites.size()); + t_pb_graph_node* pb_graph_head = physical_tile.equivalent_sites[0]->pb_graph_head; + if (nullptr == pb_graph_head) { + continue; + } + + if (true == is_io_type(&physical_tile)) { + /* Special for I/O block: + * We will search the grids and see where the I/O blocks are located: + * - If a I/O block locates on border sides of FPGA fabric: + * i.e., one or more from {TOP, RIGHT, BOTTOM, LEFT}, + * we will generate one module for each border side + * - If a I/O block locates in the center of FPGA fabric: + * we will generate one module with NUM_SIDES (same treatment as regular grids) + */ + std::set io_type_sides = find_physical_io_tile_located_sides(device_ctx.grid, + &physical_tile); + + + /* Generate the grid module name */ + for (const e_side& io_type_side : io_type_sides) { + std::string grid_module_name = generate_grid_block_module_name(std::string(GRID_MODULE_NAME_PREFIX), + std::string(physical_tile.name), + is_io_type(&physical_tile), + io_type_side); + /* Find the module Id */ + ModuleId grid_module = module_manager.find_module(grid_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(grid_module)); + + std::string module_path = format_dir_path(root_path + grid_module_name); + + rec_print_pnr_sdc_constrain_pb_graph_timing(sdc_dir, + time_unit, + hierarchical, + module_path, + module_manager, + device_annotation, + pb_graph_head, + constrain_zero_delay_paths); } - /* Special for I/O block, generate one module for each border side */ - rec_print_pnr_sdc_constrain_pb_graph_timing(sdc_dir, time_unit, + } else { + /* For CLB and heterogenenous blocks */ + std::string grid_module_name = generate_grid_block_module_name(std::string(GRID_MODULE_NAME_PREFIX), + std::string(physical_tile.name), + is_io_type(&physical_tile), + NUM_SIDES); + /* Find the module Id */ + ModuleId grid_module = module_manager.find_module(grid_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(grid_module)); + + std::string module_path = format_dir_path(root_path + grid_module_name); + + rec_print_pnr_sdc_constrain_pb_graph_timing(sdc_dir, + time_unit, + hierarchical, + module_path, module_manager, device_annotation, pb_graph_head, diff --git a/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.h b/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.h index 909caff2a..761bcf94c 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.h +++ b/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.h @@ -19,9 +19,11 @@ namespace openfpga { void print_pnr_sdc_constrain_grid_timing(const std::string& sdc_dir, const float& time_unit, + const bool& hierarchical, const DeviceContext& device_ctx, const VprDeviceAnnotation& device_annotation, const ModuleManager& module_manager, + const ModuleId& top_module, const bool& constrain_zero_delay_paths); } /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp b/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp index 0d1b71e5f..3ef29330d 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp +++ b/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp @@ -464,7 +464,8 @@ void print_pnr_sdc(const PnrSdcOption& sdc_options, } /* Output hierachy to plain text file */ - if ( (true == sdc_options.output_hierarchy()) + if ( (true == sdc_options.constrain_sb()) + && (true == sdc_options.output_hierarchy()) && (true == compact_routing_hierarchy) ) { print_pnr_sdc_routing_sb_hierarchy(sdc_options.sdc_dir(), module_manager, @@ -497,7 +498,8 @@ void print_pnr_sdc(const PnrSdcOption& sdc_options, } /* Output hierachy to plain text file */ - if ( (true == sdc_options.output_hierarchy()) + if ( (true == sdc_options.constrain_cb()) + && (true == sdc_options.output_hierarchy()) && (true == compact_routing_hierarchy) ) { print_pnr_sdc_routing_cb_hierarchy(sdc_options.sdc_dir(), module_manager, @@ -516,11 +518,23 @@ void print_pnr_sdc(const PnrSdcOption& sdc_options, if (true == sdc_options.constrain_grid()) { print_pnr_sdc_constrain_grid_timing(sdc_options.sdc_dir(), sdc_options.time_unit(), + sdc_options.hierarchical(), device_ctx, device_annotation, module_manager, + top_module, sdc_options.constrain_zero_delay_paths()); } + + if ( (true == sdc_options.constrain_grid()) + && (true == sdc_options.output_hierarchy()) ) { + print_pnr_sdc_grid_hierarchy(sdc_options.sdc_dir(), + device_ctx, + device_annotation, + module_manager, + top_module); + + } } } /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/sdc_hierarchy_writer.cpp b/openfpga/src/fpga_sdc/sdc_hierarchy_writer.cpp index aff95ec5b..68cbed4b6 100644 --- a/openfpga/src/fpga_sdc/sdc_hierarchy_writer.cpp +++ b/openfpga/src/fpga_sdc/sdc_hierarchy_writer.cpp @@ -9,8 +9,12 @@ /* Headers from openfpgautil library */ #include "openfpga_digest.h" +#include "openfpga_reserved_words.h" #include "openfpga_naming.h" +#include "pb_type_utils.h" +#include "openfpga_physical_tile_utils.h" + #include "sdc_writer_naming.h" #include "sdc_hierarchy_writer.h" @@ -34,7 +38,7 @@ void print_pnr_sdc_routing_sb_hierarchy(const std::string& sdc_dir, const ModuleId& top_module, const DeviceRRGSB& device_rr_gsb) { - std::string fname(sdc_dir + std::string("sb_hierarchy.txt")); + std::string fname(sdc_dir + std::string(SDC_SB_HIERARCHY_FILE_NAME)); std::string timer_message = std::string("Write Switch Block hierarchy to plain-text file '") + fname + std::string("'"); @@ -109,13 +113,12 @@ void print_pnr_sdc_routing_cb_hierarchy(const std::string& sdc_dir, const t_rr_type& cb_type, const DeviceRRGSB& device_rr_gsb) { - /* TODO: This is dirty, should use constant to handle this */ std::string fname(sdc_dir); if (CHANX == cb_type) { - fname += std::string("cbx_hierarchy.txt"); + fname += std::string(SDC_CBX_HIERARCHY_FILE_NAME); } else { VTR_ASSERT(CHANY == cb_type); - fname += std::string("cby_hierarchy.txt"); + fname += std::string(SDC_CBY_HIERARCHY_FILE_NAME); } std::string timer_message = std::string("Write Connection Block hierarchy to plain-text file '") + fname + std::string("'"); @@ -170,4 +173,199 @@ void print_pnr_sdc_routing_cb_hierarchy(const std::string& sdc_dir, fp.close(); } +/******************************************************************** + * Recursively write the hierarchy of pb_type and its instances to a plain text file + * e.g., + * + * / + * ... + * This file is mainly used by hierarchical P&R flow + *******************************************************************/ +static +void rec_print_pnr_sdc_grid_pb_graph_hierarchy(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& parent_pb_module, + const VprDeviceAnnotation& device_annotation, + t_pb_graph_node* parent_pb_graph_node) { + /* Validate the file stream */ + VTR_ASSERT(true == valid_file_stream(fp)); + + /* Validate pb_graph node */ + if (nullptr == parent_pb_graph_node) { + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid parent_pb_graph_node.\n"); + exit(1); + } + + /* Get the pb_type */ + t_pb_type* parent_pb_type = parent_pb_graph_node->pb_type; + + if (true == is_primitive_pb_type(parent_pb_type)) { + return; + } + + std::string pb_module_name = generate_physical_block_module_name(parent_pb_type); + + /* Find the pb module in module manager */ + ModuleId pb_module = module_manager.find_module(pb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(pb_module)); + + fp << "- " << pb_module_name << "\n"; + + /* Go through all the instance */ + for (const size_t& instance_id : module_manager.child_module_instances(parent_pb_module, pb_module)) { + std::string child_instance_name = module_manager.instance_name(parent_pb_module, pb_module, instance_id); + fp << " "; + fp << "- " << child_instance_name << "\n"; + } + + fp << "\n"; + + /* Note we only go through the graph through the physical modes. + * which we build the modules + */ + t_mode* physical_mode = device_annotation.physical_mode(parent_pb_type); + + /* Go recursively to the lower level in the pb_graph + * Note that we assume a full hierarchical P&R, we will only visit pb_graph_node of unique pb_type + */ + for (int ipb = 0; ipb < physical_mode->num_pb_type_children; ++ipb) { + rec_print_pnr_sdc_grid_pb_graph_hierarchy(fp, + module_manager, + pb_module, + device_annotation, + &(parent_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ipb][0])); + } +} + + +/*************************************************************************************** + * Write the hierarchy of grid module and its instances to a plain text file + * e.g., + * + * / + * ... + * This file is mainly used by hierarchical P&R flow + * + * Return 0 if successful + * Return 1 if there are more serious bugs in the architecture + * Return 2 if fail when creating files + ***************************************************************************************/ +void print_pnr_sdc_grid_hierarchy(const std::string& sdc_dir, + const DeviceContext& device_ctx, + const VprDeviceAnnotation& device_annotation, + const ModuleManager& module_manager, + const ModuleId& top_module) { + + std::string fname(sdc_dir + std::string(SDC_GRID_HIERARCHY_FILE_NAME)); + + std::string timer_message = std::string("Write Grid hierarchy to plain-text file '") + fname + std::string("'"); + + std::string dir_path = format_dir_path(find_path_dir_name(fname)); + + /* Create directories */ + create_directory(dir_path); + + /* Start time count */ + vtr::ScopedStartFinishTimer timer(timer_message); + + /* Use default name if user does not provide one */ + VTR_ASSERT(true != fname.empty()); + + /* Create a file handler*/ + std::fstream fp; + /* Open a file */ + fp.open(fname, std::fstream::out | std::fstream::trunc); + + /* Validate the file stream */ + check_file_stream(fname.c_str(), fp); + + std::string root_path = format_dir_path(module_manager.module_name(top_module)); + + for (const t_physical_tile_type& physical_tile : device_ctx.physical_tile_types) { + /* Bypass empty type or nullptr */ + if (true == is_empty_type(&physical_tile)) { + continue; + } + + VTR_ASSERT(1 == physical_tile.equivalent_sites.size()); + t_pb_graph_node* pb_graph_head = physical_tile.equivalent_sites[0]->pb_graph_head; + if (nullptr == pb_graph_head) { + continue; + } + + if (true == is_io_type(&physical_tile)) { + /* Special for I/O block: + * We will search the grids and see where the I/O blocks are located: + * - If a I/O block locates on border sides of FPGA fabric: + * i.e., one or more from {TOP, RIGHT, BOTTOM, LEFT}, + * we will generate one module for each border side + * - If a I/O block locates in the center of FPGA fabric: + * we will generate one module with NUM_SIDES (same treatment as regular grids) + */ + std::set io_type_sides = find_physical_io_tile_located_sides(device_ctx.grid, + &physical_tile); + + + /* Generate the grid module name */ + for (const e_side& io_type_side : io_type_sides) { + std::string grid_module_name = generate_grid_block_module_name(std::string(GRID_MODULE_NAME_PREFIX), + std::string(physical_tile.name), + is_io_type(&physical_tile), + io_type_side); + /* Find the module Id */ + ModuleId grid_module = module_manager.find_module(grid_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(grid_module)); + + fp << "- " << grid_module_name << "\n"; + + /* Go through all the instance */ + for (const size_t& instance_id : module_manager.child_module_instances(top_module, grid_module)) { + std::string grid_instance_name = module_manager.instance_name(top_module, grid_module, instance_id); + fp << " "; + fp << "- " << grid_instance_name << "\n"; + } + + fp << "\n"; + + rec_print_pnr_sdc_grid_pb_graph_hierarchy(fp, + module_manager, + grid_module, + device_annotation, + pb_graph_head); + } + } else { + /* For CLB and heterogenenous blocks */ + std::string grid_module_name = generate_grid_block_module_name(std::string(GRID_MODULE_NAME_PREFIX), + std::string(physical_tile.name), + is_io_type(&physical_tile), + NUM_SIDES); + /* Find the module Id */ + ModuleId grid_module = module_manager.find_module(grid_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(grid_module)); + + fp << "- " << grid_module_name << "\n"; + + /* Go through all the instance */ + for (const size_t& instance_id : module_manager.child_module_instances(top_module, grid_module)) { + std::string grid_instance_name = module_manager.instance_name(top_module, grid_module, instance_id); + fp << " "; + fp << "- " << grid_instance_name << "\n"; + } + + fp << "\n"; + + rec_print_pnr_sdc_grid_pb_graph_hierarchy(fp, + module_manager, + grid_module, + device_annotation, + pb_graph_head); + } + } + + /* close a file */ + fp.close(); +} + + } /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/sdc_hierarchy_writer.h b/openfpga/src/fpga_sdc/sdc_hierarchy_writer.h index 3e7cae5a7..eb4aaf9c0 100644 --- a/openfpga/src/fpga_sdc/sdc_hierarchy_writer.h +++ b/openfpga/src/fpga_sdc/sdc_hierarchy_writer.h @@ -25,6 +25,13 @@ void print_pnr_sdc_routing_cb_hierarchy(const std::string& sdc_dir, const t_rr_type& cb_type, const DeviceRRGSB& device_rr_gsb); +void print_pnr_sdc_grid_hierarchy(const std::string& sdc_dir, + const DeviceContext& device_ctx, + const VprDeviceAnnotation& device_annotation, + const ModuleManager& module_manager, + const ModuleId& top_module); + + } /* end namespace openfpga */ #endif diff --git a/openfpga/src/fpga_sdc/sdc_writer_naming.h b/openfpga/src/fpga_sdc/sdc_writer_naming.h index 9783e29f2..17126f98b 100644 --- a/openfpga/src/fpga_sdc/sdc_writer_naming.h +++ b/openfpga/src/fpga_sdc/sdc_writer_naming.h @@ -13,6 +13,11 @@ constexpr char* SDC_DISABLE_MUX_OUTPUTS_FILE_NAME = "disable_routing_multiplexer constexpr char* SDC_DISABLE_SB_OUTPUTS_FILE_NAME = "disable_sb_outputs.sdc"; constexpr char* SDC_CB_FILE_NAME = "cb.sdc"; +constexpr char* SDC_GRID_HIERARCHY_FILE_NAME = "grid_hierarchy.txt"; +constexpr char* SDC_SB_HIERARCHY_FILE_NAME = "sb_hierarchy.txt"; +constexpr char* SDC_CBX_HIERARCHY_FILE_NAME = "cbx_hierarchy.txt"; +constexpr char* SDC_CBY_HIERARCHY_FILE_NAME = "cby_hierarchy.txt"; + constexpr char* SDC_ANALYSIS_FILE_NAME = "fpga_top_analysis.sdc"; } /* end namespace openfpga */ From 55518f4cec765cfab0ed688cfd337f0a2e2efdd1 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 5 May 2020 22:31:11 -0600 Subject: [PATCH 520/645] minor fix in the sdc hierarchy writer for grids --- .../src/fpga_sdc/sdc_hierarchy_writer.cpp | 49 +++++++++++-------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/openfpga/src/fpga_sdc/sdc_hierarchy_writer.cpp b/openfpga/src/fpga_sdc/sdc_hierarchy_writer.cpp index 68cbed4b6..3e6b7e8a3 100644 --- a/openfpga/src/fpga_sdc/sdc_hierarchy_writer.cpp +++ b/openfpga/src/fpga_sdc/sdc_hierarchy_writer.cpp @@ -183,6 +183,7 @@ void print_pnr_sdc_routing_cb_hierarchy(const std::string& sdc_dir, *******************************************************************/ static void rec_print_pnr_sdc_grid_pb_graph_hierarchy(std::fstream& fp, + const size_t& depth, const ModuleManager& module_manager, const ModuleId& parent_pb_module, const VprDeviceAnnotation& device_annotation, @@ -200,26 +201,26 @@ void rec_print_pnr_sdc_grid_pb_graph_hierarchy(std::fstream& fp, /* Get the pb_type */ t_pb_type* parent_pb_type = parent_pb_graph_node->pb_type; - if (true == is_primitive_pb_type(parent_pb_type)) { - return; - } - std::string pb_module_name = generate_physical_block_module_name(parent_pb_type); /* Find the pb module in module manager */ ModuleId pb_module = module_manager.find_module(pb_module_name); VTR_ASSERT(true == module_manager.valid_module_id(pb_module)); + write_space_to_file(fp, depth * 2); fp << "- " << pb_module_name << "\n"; /* Go through all the instance */ for (const size_t& instance_id : module_manager.child_module_instances(parent_pb_module, pb_module)) { std::string child_instance_name = module_manager.instance_name(parent_pb_module, pb_module, instance_id); + write_space_to_file(fp, depth * 2); fp << " "; fp << "- " << child_instance_name << "\n"; } - fp << "\n"; + if (true == is_primitive_pb_type(parent_pb_type)) { + return; + } /* Note we only go through the graph through the physical modes. * which we build the modules @@ -230,11 +231,14 @@ void rec_print_pnr_sdc_grid_pb_graph_hierarchy(std::fstream& fp, * Note that we assume a full hierarchical P&R, we will only visit pb_graph_node of unique pb_type */ for (int ipb = 0; ipb < physical_mode->num_pb_type_children; ++ipb) { - rec_print_pnr_sdc_grid_pb_graph_hierarchy(fp, - module_manager, - pb_module, - device_annotation, - &(parent_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ipb][0])); + for (int jpb = 0; jpb < physical_mode->pb_type_children[ipb].num_pb; ++jpb) { + rec_print_pnr_sdc_grid_pb_graph_hierarchy(fp, + depth + 2, + module_manager, + pb_module, + device_annotation, + &(parent_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ipb][jpb])); + } } } @@ -324,15 +328,16 @@ void print_pnr_sdc_grid_hierarchy(const std::string& sdc_dir, std::string grid_instance_name = module_manager.instance_name(top_module, grid_module, instance_id); fp << " "; fp << "- " << grid_instance_name << "\n"; - } + rec_print_pnr_sdc_grid_pb_graph_hierarchy(fp, + 2, + module_manager, + grid_module, + device_annotation, + pb_graph_head); + } fp << "\n"; - rec_print_pnr_sdc_grid_pb_graph_hierarchy(fp, - module_manager, - grid_module, - device_annotation, - pb_graph_head); } } else { /* For CLB and heterogenenous blocks */ @@ -351,15 +356,17 @@ void print_pnr_sdc_grid_hierarchy(const std::string& sdc_dir, std::string grid_instance_name = module_manager.instance_name(top_module, grid_module, instance_id); fp << " "; fp << "- " << grid_instance_name << "\n"; + + rec_print_pnr_sdc_grid_pb_graph_hierarchy(fp, + 2, + module_manager, + grid_module, + device_annotation, + pb_graph_head); } fp << "\n"; - rec_print_pnr_sdc_grid_pb_graph_hierarchy(fp, - module_manager, - grid_module, - device_annotation, - pb_graph_head); } } From b167c859801c468634f9a1399026de120a2e3919 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 6 May 2020 11:01:49 -0600 Subject: [PATCH 521/645] fully expand grid hierarchy in SDC writer --- .../src/fpga_sdc/sdc_hierarchy_writer.cpp | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/openfpga/src/fpga_sdc/sdc_hierarchy_writer.cpp b/openfpga/src/fpga_sdc/sdc_hierarchy_writer.cpp index 3e6b7e8a3..f4730b350 100644 --- a/openfpga/src/fpga_sdc/sdc_hierarchy_writer.cpp +++ b/openfpga/src/fpga_sdc/sdc_hierarchy_writer.cpp @@ -216,30 +216,30 @@ void rec_print_pnr_sdc_grid_pb_graph_hierarchy(std::fstream& fp, write_space_to_file(fp, depth * 2); fp << " "; fp << "- " << child_instance_name << "\n"; - } - if (true == is_primitive_pb_type(parent_pb_type)) { - return; - } - - /* Note we only go through the graph through the physical modes. - * which we build the modules - */ - t_mode* physical_mode = device_annotation.physical_mode(parent_pb_type); - - /* Go recursively to the lower level in the pb_graph - * Note that we assume a full hierarchical P&R, we will only visit pb_graph_node of unique pb_type - */ - for (int ipb = 0; ipb < physical_mode->num_pb_type_children; ++ipb) { - for (int jpb = 0; jpb < physical_mode->pb_type_children[ipb].num_pb; ++jpb) { - rec_print_pnr_sdc_grid_pb_graph_hierarchy(fp, - depth + 2, - module_manager, - pb_module, - device_annotation, - &(parent_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ipb][jpb])); + if (true == is_primitive_pb_type(parent_pb_type)) { + return; } - } + + /* Note we only go through the graph through the physical modes. + * which we build the modules + */ + t_mode* physical_mode = device_annotation.physical_mode(parent_pb_type); + + /* Go recursively to the lower level in the pb_graph + * Note that we assume a full hierarchical P&R, we will only visit pb_graph_node of unique pb_type + */ + for (int ipb = 0; ipb < physical_mode->num_pb_type_children; ++ipb) { + for (int jpb = 0; jpb < physical_mode->pb_type_children[ipb].num_pb; ++jpb) { + rec_print_pnr_sdc_grid_pb_graph_hierarchy(fp, + depth + 2, + module_manager, + pb_module, + device_annotation, + &(parent_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ipb][jpb])); + } + } + } } From cc6d98887281636fdd3cacfb95bb1590632eccc1 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 6 May 2020 14:31:56 -0600 Subject: [PATCH 522/645] bug fix in grid SDC generator --- openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp b/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp index 740ba097b..009b834fe 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp +++ b/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp @@ -94,7 +94,7 @@ void print_pnr_sdc_constrain_pb_pin_interc_timing(std::fstream& fp, * If des module is not the parent module, it is a child module. * We should find the instance id */ - std::string src_instance_name = src_module_name; + std::string src_instance_name; if (parent_module != src_module) { src_instance_name = module_manager.module_name(parent_module) + std::string("/"); /* Instance id is actually the placement index */ @@ -124,7 +124,7 @@ void print_pnr_sdc_constrain_pb_pin_interc_timing(std::fstream& fp, * If des module is not the parent module, it is a child module. * We should find the instance id */ - std::string des_instance_name = des_module_name; + std::string des_instance_name; if (parent_module != des_module) { des_instance_name = module_manager.module_name(parent_module) + std::string("/"); /* Instance id is actually the placement index */ @@ -152,12 +152,18 @@ void print_pnr_sdc_constrain_pb_pin_interc_timing(std::fstream& fp, /* Give full path if hierarchical is not enabled */ std::string src_module_path = src_instance_name; if (false == hierarchical) { + if (true == src_instance_name.empty()) { + src_instance_name = generate_instance_name(src_module_name, 0); + } src_module_path = module_path + src_instance_name; } std::string des_module_path = des_instance_name; if (false == hierarchical) { - src_module_path = module_path + des_instance_name; + if (true == des_instance_name.empty()) { + des_instance_name = generate_instance_name(des_module_name, 0); + } + des_module_path = module_path + des_instance_name; } /* Print a SDC timing constraint */ From 10e1a4b2fea6a6829c1863fb6843b93721b8f510 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 6 May 2020 14:51:51 -0600 Subject: [PATCH 523/645] format fix in the fabric hierarchy and grid SDC hierarchy to be complaint to YAML format --- .../src/fabric/fabric_hierarchy_writer.cpp | 13 +++++++++++-- .../src/fpga_sdc/sdc_hierarchy_writer.cpp | 19 +++++++++++-------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/openfpga/src/fabric/fabric_hierarchy_writer.cpp b/openfpga/src/fabric/fabric_hierarchy_writer.cpp index 959c1fa5f..a40bfee39 100644 --- a/openfpga/src/fabric/fabric_hierarchy_writer.cpp +++ b/openfpga/src/fabric/fabric_hierarchy_writer.cpp @@ -40,7 +40,7 @@ int rec_output_module_hierarchy_to_text_file(std::fstream& fp, /* Iterate over all the child module */ for (const ModuleId& child_module : module_manager.child_modules(parent_module)) { - if (false == write_space_to_file(fp, current_hie_depth)) { + if (false == write_space_to_file(fp, current_hie_depth * 2)) { return 2; } @@ -51,7 +51,16 @@ int rec_output_module_hierarchy_to_text_file(std::fstream& fp, return 1; } + fp << "- "; fp << module_manager.module_name(child_module); + + /* If this is the leaf node, we leave a new line + * Otherwise, we will leave a ':' to be compatible to YAML file format + */ + if ( (0 != module_manager.child_modules(child_module).size()) + && (hie_depth_to_stop >= current_hie_depth + 1) ) { + fp << ":"; + } fp << "\n"; /* Go to next level */ @@ -123,7 +132,7 @@ int write_fabric_hierarchy_to_text_file(const ModuleManager& module_manager, return 0; } - fp << top_module_name << "\n"; + fp << top_module_name << ":" << "\n"; /* Visit child module recursively and output the hierarchy */ int err_code = rec_output_module_hierarchy_to_text_file(fp, diff --git a/openfpga/src/fpga_sdc/sdc_hierarchy_writer.cpp b/openfpga/src/fpga_sdc/sdc_hierarchy_writer.cpp index f4730b350..2c47aec2f 100644 --- a/openfpga/src/fpga_sdc/sdc_hierarchy_writer.cpp +++ b/openfpga/src/fpga_sdc/sdc_hierarchy_writer.cpp @@ -79,7 +79,7 @@ void print_pnr_sdc_routing_sb_hierarchy(const std::string& sdc_dir, /* Create the file name for SDC */ std::string sdc_fname(sdc_dir + generate_switch_block_module_name(gsb_coordinate) + std::string(SDC_FILE_NAME_POSTFIX)); - fp << "- " << sb_module_name << "\n"; + fp << "- " << sb_module_name << ":" << "\n"; /* Go through all the instance */ for (const size_t& instance_id : module_manager.child_module_instances(top_module, sb_module)) { @@ -163,7 +163,7 @@ void print_pnr_sdc_routing_cb_hierarchy(const std::string& sdc_dir, for (const size_t& instance_id : module_manager.child_module_instances(top_module, cb_module)) { std::string cb_instance_name = module_manager.instance_name(top_module, cb_module, instance_id); fp << " "; - fp << "- " << cb_instance_name << "\n"; + fp << "- " << cb_instance_name << ":" << "\n"; } fp << "\n"; @@ -208,18 +208,21 @@ void rec_print_pnr_sdc_grid_pb_graph_hierarchy(std::fstream& fp, VTR_ASSERT(true == module_manager.valid_module_id(pb_module)); write_space_to_file(fp, depth * 2); - fp << "- " << pb_module_name << "\n"; + fp << "- " << pb_module_name << ":" << "\n"; /* Go through all the instance */ for (const size_t& instance_id : module_manager.child_module_instances(parent_pb_module, pb_module)) { std::string child_instance_name = module_manager.instance_name(parent_pb_module, pb_module, instance_id); write_space_to_file(fp, depth * 2); fp << " "; - fp << "- " << child_instance_name << "\n"; + fp << "- " << child_instance_name; if (true == is_primitive_pb_type(parent_pb_type)) { + fp << "\n"; return; } + + fp << ":" << "\n"; /* Note we only go through the graph through the physical modes. * which we build the modules @@ -321,13 +324,13 @@ void print_pnr_sdc_grid_hierarchy(const std::string& sdc_dir, ModuleId grid_module = module_manager.find_module(grid_module_name); VTR_ASSERT(true == module_manager.valid_module_id(grid_module)); - fp << "- " << grid_module_name << "\n"; + fp << "- " << grid_module_name << ":" << "\n"; /* Go through all the instance */ for (const size_t& instance_id : module_manager.child_module_instances(top_module, grid_module)) { std::string grid_instance_name = module_manager.instance_name(top_module, grid_module, instance_id); fp << " "; - fp << "- " << grid_instance_name << "\n"; + fp << "- " << grid_instance_name << ":" << "\n"; rec_print_pnr_sdc_grid_pb_graph_hierarchy(fp, 2, @@ -349,13 +352,13 @@ void print_pnr_sdc_grid_hierarchy(const std::string& sdc_dir, ModuleId grid_module = module_manager.find_module(grid_module_name); VTR_ASSERT(true == module_manager.valid_module_id(grid_module)); - fp << "- " << grid_module_name << "\n"; + fp << "- " << grid_module_name << ":" << "\n"; /* Go through all the instance */ for (const size_t& instance_id : module_manager.child_module_instances(top_module, grid_module)) { std::string grid_instance_name = module_manager.instance_name(top_module, grid_module, instance_id); fp << " "; - fp << "- " << grid_instance_name << "\n"; + fp << "- " << grid_instance_name << ":" << "\n"; rec_print_pnr_sdc_grid_pb_graph_hierarchy(fp, 2, From 99fa51cb49d9a7089fd62b3f7018c3a1c187afc5 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 6 May 2020 14:56:04 -0600 Subject: [PATCH 524/645] bug fixed in the SDC CB hierarchy writer --- openfpga/src/fpga_sdc/sdc_hierarchy_writer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openfpga/src/fpga_sdc/sdc_hierarchy_writer.cpp b/openfpga/src/fpga_sdc/sdc_hierarchy_writer.cpp index 2c47aec2f..49214aaa3 100644 --- a/openfpga/src/fpga_sdc/sdc_hierarchy_writer.cpp +++ b/openfpga/src/fpga_sdc/sdc_hierarchy_writer.cpp @@ -157,13 +157,13 @@ void print_pnr_sdc_routing_cb_hierarchy(const std::string& sdc_dir, /* Create the file name for SDC */ std::string sdc_fname(sdc_dir + generate_connection_block_module_name(cb_type, gsb_coordinate) + std::string(SDC_FILE_NAME_POSTFIX)); - fp << "- " << cb_module_name << "\n"; + fp << "- " << cb_module_name << ":" << "\n"; /* Go through all the instance */ for (const size_t& instance_id : module_manager.child_module_instances(top_module, cb_module)) { std::string cb_instance_name = module_manager.instance_name(top_module, cb_module, instance_id); fp << " "; - fp << "- " << cb_instance_name << ":" << "\n"; + fp << "- " << cb_instance_name << "\n"; } fp << "\n"; From 84d24ad075c9dd0e29e14f025250d4061aead299 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 6 May 2020 15:05:41 -0600 Subject: [PATCH 525/645] bug fix in pnr sdc grid writer for module paths in hierarchical view --- openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp | 2 -- openfpga/test_script/and_k6_frac.openfpga | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp b/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp index 009b834fe..26949ec29 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp +++ b/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp @@ -96,7 +96,6 @@ void print_pnr_sdc_constrain_pb_pin_interc_timing(std::fstream& fp, */ std::string src_instance_name; if (parent_module != src_module) { - src_instance_name = module_manager.module_name(parent_module) + std::string("/"); /* Instance id is actually the placement index */ size_t instance_id = src_pb_graph_node->placement_index; if (true == module_manager.instance_name(parent_module, src_module, instance_id).empty()) { @@ -126,7 +125,6 @@ void print_pnr_sdc_constrain_pb_pin_interc_timing(std::fstream& fp, */ std::string des_instance_name; if (parent_module != des_module) { - des_instance_name = module_manager.module_name(parent_module) + std::string("/"); /* Instance id is actually the placement index */ size_t instance_id = des_pb_graph_node->placement_index; if (true == module_manager.instance_name(parent_module, des_module, instance_id).empty()) { diff --git a/openfpga/test_script/and_k6_frac.openfpga b/openfpga/test_script/and_k6_frac.openfpga index 014142fb1..90f20b2b7 100644 --- a/openfpga/test_script/and_k6_frac.openfpga +++ b/openfpga/test_script/and_k6_frac.openfpga @@ -52,6 +52,7 @@ write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src/SRC --reference_ # Write the SDC files for PnR backend # - Turn on every options here +write_pnr_sdc --hierarchical --file /var/tmp/xtang/openfpga_test_src/SDC_hie write_pnr_sdc --file /var/tmp/xtang/openfpga_test_src/SDC # Write the SDC to run timing analysis for a mapped FPGA fabric From b8a79c563d70927b622018cfc6c3788b91db386d Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 6 May 2020 16:08:40 -0600 Subject: [PATCH 526/645] bug fix in the SDC port generation --- openfpga/src/fpga_sdc/sdc_memory_utils.cpp | 9 +++------ openfpga/src/fpga_sdc/sdc_writer_utils.cpp | 7 +------ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/openfpga/src/fpga_sdc/sdc_memory_utils.cpp b/openfpga/src/fpga_sdc/sdc_memory_utils.cpp index 07b7d0804..c9459ebaf 100644 --- a/openfpga/src/fpga_sdc/sdc_memory_utils.cpp +++ b/openfpga/src/fpga_sdc/sdc_memory_utils.cpp @@ -100,12 +100,9 @@ void rec_print_pnr_sdc_disable_configurable_memory_module_output(std::fstream& f /* Disable timing for each output port of this module */ for (const BasicPort& output_port : module_manager.module_ports_by_type(parent_module, ModuleManager::MODULE_OUTPUT_PORT)) { - for (const size_t& pin : output_port.pins()) { - BasicPort output_pin(output_port.get_name(), pin, pin); - fp << "set_disable_timing "; - fp << parent_module_path << generate_sdc_port(output_pin); - fp << std::endl; - } + fp << "set_disable_timing "; + fp << parent_module_path << output_port.get_name(); + fp << std::endl; } } diff --git a/openfpga/src/fpga_sdc/sdc_writer_utils.cpp b/openfpga/src/fpga_sdc/sdc_writer_utils.cpp index 0fcf450ad..85ff6edcb 100644 --- a/openfpga/src/fpga_sdc/sdc_writer_utils.cpp +++ b/openfpga/src/fpga_sdc/sdc_writer_utils.cpp @@ -71,12 +71,7 @@ std::string generate_sdc_port(const BasicPort& port) { * */ if (1 == port.get_width()) { - if (0 != port.get_lsb()) { - size_str = "[" + std::to_string(port.get_lsb()) + "]"; - } else { - VTR_ASSERT(0 == port.get_lsb()); - size_str.clear(); - } + size_str = "[" + std::to_string(port.get_lsb()) + "]"; } sdc_line = port.get_name() + size_str; From 05d276097e71d04483fab47c75740ce874067e14 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 6 May 2020 16:20:04 -0600 Subject: [PATCH 527/645] critical bug fixed in openfpga shell so that our command parse results should be reset each time before parsing a line --- libopenfpga/libopenfpgashell/src/command_context.cpp | 5 +++++ libopenfpga/libopenfpgashell/src/command_context.h | 2 ++ libopenfpga/libopenfpgashell/src/shell.tpp | 4 ++++ 3 files changed, 11 insertions(+) diff --git a/libopenfpga/libopenfpgashell/src/command_context.cpp b/libopenfpga/libopenfpgashell/src/command_context.cpp index ee7c684d6..24593a77b 100644 --- a/libopenfpga/libopenfpgashell/src/command_context.cpp +++ b/libopenfpga/libopenfpgashell/src/command_context.cpp @@ -66,4 +66,9 @@ void CommandContext::set_option_value(const Command& command, option_values_[option_id] = value; } +void CommandContext::reset() { + std::fill(option_enabled_.begin(), option_enabled_.end(), false); + std::fill(option_values_.begin(), option_values_.end(), std::string()); +} + } /* End namespace openfpga */ diff --git a/libopenfpga/libopenfpgashell/src/command_context.h b/libopenfpga/libopenfpgashell/src/command_context.h index 2300888cf..5f3fb8856 100644 --- a/libopenfpga/libopenfpgashell/src/command_context.h +++ b/libopenfpga/libopenfpgashell/src/command_context.h @@ -43,6 +43,8 @@ class CommandContext { const CommandOptionId& option_id, const bool& status); void set_option_value(const Command& command, const CommandOptionId& option_id, const std::string& value); + /* Reset the command context to initial state */ + void reset(); private: /* Internal data */ /* Identify if the option is enabled or not */ vtr::vector option_enabled_; diff --git a/libopenfpga/libopenfpgashell/src/shell.tpp b/libopenfpga/libopenfpgashell/src/shell.tpp index 05f760e59..b5c506d42 100644 --- a/libopenfpga/libopenfpgashell/src/shell.tpp +++ b/libopenfpga/libopenfpgashell/src/shell.tpp @@ -473,6 +473,10 @@ int Shell::execute_command(const char* cmd_line, return command_status_[cmd_id]; } + /* Reset the command parse results to initial status + * Avoid conflict when calling the same command in the second time + */ + command_contexts_[cmd_id].reset(); if (false == parse_command(tokens, commands_[cmd_id], command_contexts_[cmd_id])) { /* Echo the command */ print_command_options(commands_[cmd_id]); From 8d2360a7101588fc1a923468a7a31746cfa1f21f Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 9 May 2020 19:01:41 -0600 Subject: [PATCH 528/645] simplify include_netlist.v --- openfpga/src/base/openfpga_verilog.cpp | 3 +- openfpga/src/fpga_verilog/verilog_api.cpp | 9 ++-- openfpga/src/fpga_verilog/verilog_api.h | 3 +- .../verilog_auxiliary_netlists.cpp | 41 ++----------------- .../fpga_verilog/verilog_auxiliary_netlists.h | 6 +-- 5 files changed, 10 insertions(+), 52 deletions(-) diff --git a/openfpga/src/base/openfpga_verilog.cpp b/openfpga/src/base/openfpga_verilog.cpp index 9a877c9f2..1d47055df 100644 --- a/openfpga/src/base/openfpga_verilog.cpp +++ b/openfpga/src/base/openfpga_verilog.cpp @@ -83,8 +83,7 @@ int write_verilog_testbench(OpenfpgaContext& openfpga_ctx, options.set_print_simulation_ini(cmd_context.option_value(cmd, opt_print_simulation_ini)); options.set_verbose_output(cmd_context.option_enable(cmd, opt_verbose)); - fpga_verilog_testbench(openfpga_ctx.verilog_netlists(), - openfpga_ctx.module_graph(), + fpga_verilog_testbench(openfpga_ctx.module_graph(), openfpga_ctx.bitstream_manager(), openfpga_ctx.fabric_bitstream(), g_vpr_ctx.atom(), diff --git a/openfpga/src/fpga_verilog/verilog_api.cpp b/openfpga/src/fpga_verilog/verilog_api.cpp index 2c2ea4e93..7f82507f5 100644 --- a/openfpga/src/fpga_verilog/verilog_api.cpp +++ b/openfpga/src/fpga_verilog/verilog_api.cpp @@ -144,8 +144,7 @@ void fpga_fabric_verilog(ModuleManager& module_manager, * This testbench is created for quick verification and formal verification purpose. * - Verilog netlist including preprocessing flags and all the Verilog netlists that have been generated ********************************************************************/ -void fpga_verilog_testbench(const NetlistManager& netlist_manager, - const ModuleManager& module_manager, +void fpga_verilog_testbench(const ModuleManager& module_manager, const BitstreamManager& bitstream_manager, const std::vector& fabric_bitstream, const AtomContext& atom_ctx, @@ -227,11 +226,9 @@ void fpga_verilog_testbench(const NetlistManager& netlist_manager, } /* Generate a Verilog file including all the netlists that have been generated */ - print_include_netlists(netlist_manager, - src_dir_path, + print_include_netlists(src_dir_path, netlist_name, - options.reference_benchmark_file_path(), - circuit_lib); + options.reference_benchmark_file_path()); } diff --git a/openfpga/src/fpga_verilog/verilog_api.h b/openfpga/src/fpga_verilog/verilog_api.h index c9d194dff..301e68b3e 100644 --- a/openfpga/src/fpga_verilog/verilog_api.h +++ b/openfpga/src/fpga_verilog/verilog_api.h @@ -37,8 +37,7 @@ void fpga_fabric_verilog(ModuleManager& module_manager, const DeviceRRGSB& device_rr_gsb, const FabricVerilogOption& options); -void fpga_verilog_testbench(const NetlistManager& netlist_manager, - const ModuleManager& module_manager, +void fpga_verilog_testbench(const ModuleManager& module_manager, const BitstreamManager& bitstream_manager, const std::vector& fabric_bitstream, const AtomContext& atom_ctx, diff --git a/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.cpp b/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.cpp index b69868d17..65e0d1ca2 100644 --- a/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.cpp +++ b/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.cpp @@ -94,11 +94,9 @@ void print_fabric_include_netlist(const NetlistManager& netlist_manager, * that have been generated and user-defined. * Some netlists are open to compile under specific preprocessing flags *******************************************************************/ -void print_include_netlists(const NetlistManager& netlist_manager, - const std::string& src_dir, +void print_include_netlists(const std::string& src_dir, const std::string& circuit_name, - const std::string& reference_benchmark_file, - const CircuitLibrary& circuit_lib) { + const std::string& reference_benchmark_file) { std::string verilog_fname = src_dir + circuit_name + std::string(TOP_INCLUDE_NETLIST_FILE_NAME_POSTFIX); /* Create the file stream */ @@ -112,46 +110,13 @@ void print_include_netlists(const NetlistManager& netlist_manager, print_verilog_file_header(fp, std::string("Netlist Summary")); /* Print preprocessing flags */ - print_verilog_comment(fp, std::string("------ Include defines: preproc flags -----")); - print_verilog_include_netlist(fp, std::string(src_dir + std::string(DEFINES_VERILOG_FILE_NAME))); - fp << std::endl; - print_verilog_comment(fp, std::string("------ Include simulation defines -----")); print_verilog_include_netlist(fp, src_dir + std::string(DEFINES_VERILOG_SIMULATION_FILE_NAME)); fp << std::endl; - /* Include all the user-defined netlists */ - print_verilog_comment(fp, std::string("------ Include user-defined netlists -----")); - for (const std::string& user_defined_netlist : find_circuit_library_unique_verilog_netlists(circuit_lib)) { - print_verilog_include_netlist(fp, user_defined_netlist); - } - - /* Include all the primitive modules */ - print_verilog_comment(fp, std::string("------ Include primitive module netlists -----")); - for (const NetlistId& nlist_id : netlist_manager.netlists_by_type(NetlistManager::SUBMODULE_NETLIST)) { - print_verilog_include_netlist(fp, netlist_manager.netlist_name(nlist_id)); - } - fp << std::endl; - - /* Include all the CLB, heterogeneous block modules */ - print_verilog_comment(fp, std::string("------ Include logic block netlists -----")); - for (const NetlistId& nlist_id : netlist_manager.netlists_by_type(NetlistManager::LOGIC_BLOCK_NETLIST)) { - print_verilog_include_netlist(fp, netlist_manager.netlist_name(nlist_id)); - } - fp << std::endl; - - /* Include all the routing architecture modules */ - print_verilog_comment(fp, std::string("------ Include routing module netlists -----")); - for (const NetlistId& nlist_id : netlist_manager.netlists_by_type(NetlistManager::ROUTING_MODULE_NETLIST)) { - print_verilog_include_netlist(fp, netlist_manager.netlist_name(nlist_id)); - } - fp << std::endl; - /* Include FPGA top module */ print_verilog_comment(fp, std::string("------ Include fabric top-level netlists -----")); - for (const NetlistId& nlist_id : netlist_manager.netlists_by_type(NetlistManager::TOP_MODULE_NETLIST)) { - print_verilog_include_netlist(fp, netlist_manager.netlist_name(nlist_id)); - } + print_verilog_include_netlist(fp, src_dir + std::string(FABRIC_INCLUDE_NETLIST_FILE_NAME)); fp << std::endl; /* Include reference benchmark netlist only when auto-check flag is enabled */ diff --git a/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.h b/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.h index 52292881a..57f04032f 100644 --- a/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.h +++ b/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.h @@ -21,11 +21,9 @@ void print_fabric_include_netlist(const NetlistManager& netlist_manager, const std::string& src_dir, const CircuitLibrary& circuit_lib); -void print_include_netlists(const NetlistManager& netlist_manager, - const std::string& src_dir, +void print_include_netlists(const std::string& src_dir, const std::string& circuit_name, - const std::string& reference_benchmark_file, - const CircuitLibrary& circuit_lib); + const std::string& reference_benchmark_file); void print_verilog_preprocessing_flags_netlist(const std::string& src_dir, const FabricVerilogOption& fabric_verilog_opts); From dad99d13a20a64eb660cf2609d0bda85339d96d1 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 11 May 2020 14:52:55 -0600 Subject: [PATCH 529/645] bug fixed in SDC timing writer for primitive pb_type --- openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp b/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp index 26949ec29..b56f8acf0 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp +++ b/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp @@ -397,34 +397,34 @@ void print_pnr_sdc_constrain_primitive_pb_graph_node(const std::string& sdc_dir, print_sdc_timescale(fp, time_unit_to_string(time_unit)); /* We traverse the pb_graph pins where we can find pin-to-pin timing annotation - * We walk through output pins here, build timing constraints by pair each output to input + * We walk through input pins here, build timing constraints by pair each input to output + * Because VPR keeps all the timing values in pin_timing data structure instead of pb_graph_pin edges * Clock pins are not walked through because they will be handled by clock tree synthesis */ - for (int iport = 0; iport < logical_primitive_pb_graph_node->num_output_ports; ++iport) { - for (int ipin = 0; ipin < logical_primitive_pb_graph_node->num_output_pins[iport]; ++ipin) { - t_pb_graph_pin* sink_pin = &(logical_primitive_pb_graph_node->output_pins[iport][ipin]); + for (int iport = 0; iport < logical_primitive_pb_graph_node->num_input_ports; ++iport) { + for (int ipin = 0; ipin < logical_primitive_pb_graph_node->num_input_pins[iport]; ++ipin) { + t_pb_graph_pin* src_pin = &(logical_primitive_pb_graph_node->input_pins[iport][ipin]); /* Port must exist in the module graph */ - ModulePortId sink_module_port_id = module_manager.find_module_port(pb_module, generate_pb_type_port_name(physical_pb_type, sink_pin->port)); - VTR_ASSERT(true == module_manager.valid_module_port_id(pb_module, sink_module_port_id)); - BasicPort sink_port = module_manager.module_port(pb_module, sink_module_port_id); + ModulePortId src_module_port_id = module_manager.find_module_port(pb_module, generate_pb_type_port_name(physical_pb_type, src_pin->port)); + VTR_ASSERT(true == module_manager.valid_module_port_id(pb_module, src_module_port_id)); + BasicPort src_port = module_manager.module_port(pb_module, src_module_port_id); /* Set the correct pin number of the port */ - sink_port.set_width(sink_pin->pin_number, sink_pin->pin_number); + src_port.set_width(src_pin->pin_number, src_pin->pin_number); /* Find all the sink pin from this source pb_graph_pin */ - for (int iedge = 0; iedge < sink_pin->num_input_edges; ++iedge) { - VTR_ASSERT(1 == sink_pin->input_edges[iedge]->num_input_pins); - t_pb_graph_pin* src_pin = sink_pin->input_edges[iedge]->input_pins[0]; + for (int itiming = 0; itiming < src_pin->num_pin_timing; ++itiming) { + t_pb_graph_pin* sink_pin = src_pin->pin_timing[itiming]; /* Port must exist in the module graph */ - ModulePortId src_module_port_id = module_manager.find_module_port(pb_module, generate_pb_type_port_name(physical_pb_type, src_pin->port)); - VTR_ASSERT(true == module_manager.valid_module_port_id(pb_module, src_module_port_id)); - BasicPort src_port = module_manager.module_port(pb_module, src_module_port_id); + ModulePortId sink_module_port_id = module_manager.find_module_port(pb_module, generate_pb_type_port_name(physical_pb_type, sink_pin->port)); + VTR_ASSERT(true == module_manager.valid_module_port_id(pb_module, sink_module_port_id)); + BasicPort sink_port = module_manager.module_port(pb_module, sink_module_port_id); /* Set the correct pin number of the port */ - src_port.set_width(src_pin->pin_number, src_pin->pin_number); + sink_port.set_width(sink_pin->pin_number, sink_pin->pin_number); /* Find max delay between src and sink pin */ - float tmax = sink_pin->input_edges[iedge]->delay_max; + float tmax = src_pin->pin_timing_del_max[itiming]; /* Generate module path in hierarchy depending if the hierarchical is enabled */ std::string module_hie_path = pb_module_name; @@ -434,7 +434,7 @@ void print_pnr_sdc_constrain_primitive_pb_graph_node(const std::string& sdc_dir, /* If the delay is zero, constrain only when user wants it */ if ( (true == constrain_zero_delay_paths) - || (0. == tmax) ) { + || (0. != tmax) ) { print_pnr_sdc_constrain_max_delay(fp, module_hie_path, generate_sdc_port(src_port), @@ -444,10 +444,10 @@ void print_pnr_sdc_constrain_primitive_pb_graph_node(const std::string& sdc_dir, } /* Find min delay between src and sink pin */ - float tmin = sink_pin->input_edges[iedge]->delay_min; + float tmin = src_pin->pin_timing_del_min[itiming]; /* If the delay is zero, constrain only when user wants it */ if ( (true == constrain_zero_delay_paths) - || (0. == tmin) ) { + || (0. != tmin) ) { print_pnr_sdc_constrain_min_delay(fp, module_hie_path, generate_sdc_port(src_port), From 4c0953415b87984e969a9a1d4161fc89b3721dc7 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 12 May 2020 20:44:53 -0600 Subject: [PATCH 530/645] add configuration chain sdc writer --- openfpga/src/base/openfpga_sdc.cpp | 36 ++++ openfpga/src/base/openfpga_sdc.h | 3 + openfpga/src/base/openfpga_sdc_command.cpp | 49 +++++ .../configuration_chain_sdc_writer.cpp | 175 ++++++++++++++++++ .../fpga_sdc/configuration_chain_sdc_writer.h | 26 +++ openfpga/src/fpga_sdc/sdc_writer_utils.cpp | 12 +- 6 files changed, 295 insertions(+), 6 deletions(-) create mode 100644 openfpga/src/fpga_sdc/configuration_chain_sdc_writer.cpp create mode 100644 openfpga/src/fpga_sdc/configuration_chain_sdc_writer.h diff --git a/openfpga/src/base/openfpga_sdc.cpp b/openfpga/src/base/openfpga_sdc.cpp index d492e653a..93bd46959 100644 --- a/openfpga/src/base/openfpga_sdc.cpp +++ b/openfpga/src/base/openfpga_sdc.cpp @@ -15,6 +15,7 @@ #include "circuit_library_utils.h" #include "pnr_sdc_writer.h" #include "analysis_sdc_writer.h" +#include "configuration_chain_sdc_writer.h" #include "openfpga_sdc.h" /* Include global variables of VPR */ @@ -102,6 +103,41 @@ int write_pnr_sdc(OpenfpgaContext& openfpga_ctx, return CMD_EXEC_SUCCESS; } +/******************************************************************** + * A wrapper function to call the PnR SDC generator on configuration chain + * of FPGA-SDC + *******************************************************************/ +int write_configuration_chain_sdc(const OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context) { + /* If the configuration protocol is not a configuration chain, we will not write anything */ + if (CONFIG_MEM_SCAN_CHAIN != openfpga_ctx.arch().config_protocol.type()) { + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Configuration protocol is %s. Expected %s to write SDC!\n", + CONFIG_PROTOCOL_TYPE_STRING[openfpga_ctx.arch().config_protocol.type()], + CONFIG_PROTOCOL_TYPE_STRING[CONFIG_MEM_SCAN_CHAIN]); + return CMD_EXEC_FATAL_ERROR; + } + + /* Get command options */ + CommandOptionId opt_output_dir = cmd.option("file"); + CommandOptionId opt_time_unit = cmd.option("time_unit"); + CommandOptionId opt_min_delay = cmd.option("min_delay"); + CommandOptionId opt_max_delay = cmd.option("max_delay"); + + std::string sdc_dir_path = format_dir_path(cmd_context.option_value(cmd, opt_output_dir)); + + float time_unit = string_to_time_unit(cmd_context.option_value(cmd, opt_time_unit)); + + /* Write the SDC for configuration chain */ + print_pnr_sdc_constrain_configurable_chain(cmd_context.option_value(cmd, opt_output_dir), + time_unit, + std::stof(cmd_context.option_value(cmd, opt_max_delay)), + std::stof(cmd_context.option_value(cmd, opt_min_delay)), + openfpga_ctx.module_graph()); + + return CMD_EXEC_SUCCESS; +} + /******************************************************************** * A wrapper function to call the analysis SDC generator of FPGA-SDC *******************************************************************/ diff --git a/openfpga/src/base/openfpga_sdc.h b/openfpga/src/base/openfpga_sdc.h index 99a87f899..060c304bc 100644 --- a/openfpga/src/base/openfpga_sdc.h +++ b/openfpga/src/base/openfpga_sdc.h @@ -18,6 +18,9 @@ namespace openfpga { int write_pnr_sdc(OpenfpgaContext& openfpga_ctx, const Command& cmd, const CommandContext& cmd_context); +int write_configuration_chain_sdc(const OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context); + int write_analysis_sdc(OpenfpgaContext& openfpga_ctx, const Command& cmd, const CommandContext& cmd_context); diff --git a/openfpga/src/base/openfpga_sdc_command.cpp b/openfpga/src/base/openfpga_sdc_command.cpp index a0f578786..faa488931 100644 --- a/openfpga/src/base/openfpga_sdc_command.cpp +++ b/openfpga/src/base/openfpga_sdc_command.cpp @@ -80,6 +80,45 @@ ShellCommandId add_openfpga_write_pnr_sdc_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id, + const std::vector& dependent_cmds) { + Command shell_cmd("write_configuration_chain_sdc"); + + /* Add an option '--file' in short '-f'*/ + CommandOptionId output_opt = shell_cmd.add_option("file", true, "Specify the SDC file to constrain configuration chain"); + shell_cmd.set_option_short_name(output_opt, "f"); + shell_cmd.set_option_require_value(output_opt, openfpga::OPT_STRING); + + /* Add an option '--time_unit' */ + CommandOptionId time_unit_opt = shell_cmd.add_option("time_unit", false, "Specify the time unit in SDC files. Acceptable is [a|f|p|n|u|m|k|M]s"); + shell_cmd.set_option_require_value(time_unit_opt, openfpga::OPT_STRING); + + /* Add an option '--min_delay' */ + CommandOptionId min_dly_opt = shell_cmd.add_option("min_delay", false, "Specify the minimum delay to be used."); + shell_cmd.set_option_require_value(min_dly_opt, openfpga::OPT_STRING); + + /* Add an option '--max_delay' */ + CommandOptionId max_dly_opt = shell_cmd.add_option("max_delay", false, "Specify the maximum delay to be used."); + shell_cmd.set_option_require_value(max_dly_opt, openfpga::OPT_STRING); + + /* Add command 'write_configuration_chain_sdc' to the Shell */ + ShellCommandId shell_cmd_id = shell.add_command(shell_cmd, "generate SDC files to constrain the configuration chain for FPGA fabric"); + shell.set_command_class(shell_cmd_id, cmd_class_id); + shell.set_command_const_execute_function(shell_cmd_id, write_configuration_chain_sdc); + + /* Add command dependency to the Shell */ + shell.set_command_dependency(shell_cmd_id, dependent_cmds); + + return shell_cmd_id; +} + /******************************************************************** * - Add a command to Shell environment: generate PnR SDC * - Add associated options @@ -134,6 +173,16 @@ void add_openfpga_sdc_commands(openfpga::Shell& shell) { openfpga_sdc_cmd_class, pnr_sdc_cmd_dependency); + /******************************** + * Command 'write_configuration_chain_sdc' + */ + /* The 'write_configuration_chain_sdc' command should NOT be executed before 'build_fabric' */ + std::vector cc_sdc_cmd_dependency; + cc_sdc_cmd_dependency.push_back(build_fabric_id); + add_openfpga_write_configuration_chain_sdc_command(shell, + openfpga_sdc_cmd_class, + cc_sdc_cmd_dependency); + /******************************** * Command 'write_analysis_sdc' */ diff --git a/openfpga/src/fpga_sdc/configuration_chain_sdc_writer.cpp b/openfpga/src/fpga_sdc/configuration_chain_sdc_writer.cpp new file mode 100644 index 000000000..ce41e6851 --- /dev/null +++ b/openfpga/src/fpga_sdc/configuration_chain_sdc_writer.cpp @@ -0,0 +1,175 @@ +/******************************************************************** + * This file includes functions that print SDC (Synopsys Design Constraint) + * files in physical design tools, i.e., Place & Route (PnR) tools + * The SDC files are used to constrain the timing of configuration chain + * + * Note that this is different from the SDC to constrain VPR Place&Route + * engine! These SDCs are designed for PnR to generate FPGA layouts!!! + *******************************************************************/ +#include +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_time.h" +#include "vtr_log.h" + +/* Headers from openfpgautil library */ +#include "openfpga_scale.h" +#include "openfpga_port.h" +#include "openfpga_digest.h" + +#include "openfpga_naming.h" + +#include "sdc_writer_utils.h" +#include "configuration_chain_sdc_writer.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Print SDC commands to constrain the timing between outputs and inputs + * of all the configurable memory modules + * + * |<------Max/Min delay-->| + * | | + * +------+ out in +------+ + * | CCFF |---------------------->| CCFF | + * +------+ +------+ + * + * This function will be executed in a recursive way, + * using a Depth-First Search (DFS) strategy + * It will iterate over all the configurable children under each module + * and print a SDC command + * + *******************************************************************/ +static +void rec_print_pnr_sdc_constrain_configurable_chain(std::fstream& fp, + const float& tmax, + const float& tmin, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const std::string& parent_module_path, + std::string& previous_module_path, + ModuleId& previous_module) { + + /* For each configurable child, we will go one level down in priority */ + for (size_t child_index = 0; child_index < module_manager.configurable_children(parent_module).size(); ++child_index) { + std::string child_module_path = parent_module_path; + ModuleId child_module_id = module_manager.configurable_children(parent_module)[child_index]; + size_t child_instance_id = module_manager.configurable_child_instances(parent_module)[child_index]; + std::string child_instance_name; + if (true == module_manager.instance_name(parent_module, child_module_id, child_instance_id).empty()) { + child_instance_name = generate_instance_name(module_manager.module_name(child_module_id), child_instance_id); + } else { + child_instance_name = module_manager.instance_name(parent_module, child_module_id, child_instance_id); + } + + child_module_path += child_instance_name; + + child_module_path = format_dir_path(child_module_path); + + rec_print_pnr_sdc_constrain_configurable_chain(fp, + tmax, tmin, + module_manager, + child_module_id, + child_module_path, + previous_module_path, + previous_module); + } + + /* If there is no configurable children any more, this is a leaf module, print a SDC command for disable timing */ + if (0 < module_manager.configurable_children(parent_module).size()) { + return; + } + + /* Validate file stream */ + valid_file_stream(fp); + + /* Disable timing for each output port of this module */ + if (!previous_module_path.empty()) { + bool first_port = true; + for (const BasicPort& output_port : module_manager.module_ports_by_type(previous_module, ModuleManager::MODULE_OUTPUT_PORT)) { + /* Only the first output port will be considered, + * being consistent with build_memory_module.cpp:395 + */ + if (false == first_port) { + continue; + } + + for (const BasicPort& input_port : module_manager.module_ports_by_type(parent_module, ModuleManager::MODULE_INPUT_PORT)) { + print_pnr_sdc_constrain_max_delay(fp, + previous_module_path, + generate_sdc_port(output_port), + parent_module_path, + generate_sdc_port(input_port), + tmax); + + print_pnr_sdc_constrain_min_delay(fp, + previous_module_path, + generate_sdc_port(output_port), + parent_module_path, + generate_sdc_port(input_port), + tmin); + } + + first_port = false; + } + } + + /* Update previous module */ + previous_module_path = parent_module_path; + previous_module = parent_module; +} + + +/******************************************************************** + * Break combinational loops in FPGA fabric, which mainly come from + * configurable memory cells. + * To handle this, we disable the outputs of memory cells + *******************************************************************/ +void print_pnr_sdc_constrain_configurable_chain(const std::string& sdc_fname, + const float& time_unit, + const float& max_delay, + const float& min_delay, + const ModuleManager& module_manager) { + + /* Create the directory */ + create_directory(find_path_dir_name(sdc_fname)); + + /* Start time count */ + std::string timer_message = std::string("Write SDC to constrain configurable chain for P&R flow '") + sdc_fname + std::string("'"); + vtr::ScopedStartFinishTimer timer(timer_message); + + /* Create the file stream */ + std::fstream fp; + fp.open(sdc_fname, std::fstream::out | std::fstream::trunc); + + check_file_stream(sdc_fname.c_str(), fp); + + /* Generate the descriptions*/ + print_sdc_file_header(fp, std::string("Timing constraints for configurable chains used in PnR")); + + /* Print time unit for the SDC file */ + print_sdc_timescale(fp, time_unit_to_string(time_unit)); + + std::string top_module_name = generate_fpga_top_module_name(); + ModuleId top_module = module_manager.find_module(top_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(top_module)); + + /* Go recursively in the module manager, starting from the top-level module: instance id of the top-level module is 0 by default */ + std::string previous_module_path; + ModuleId previous_module = ModuleId::INVALID(); + rec_print_pnr_sdc_constrain_configurable_chain(fp, + max_delay/time_unit, min_delay/time_unit, + module_manager, top_module, + format_dir_path(module_manager.module_name(top_module)), + previous_module_path, + previous_module); + + /* Close file handler */ + fp.close(); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/configuration_chain_sdc_writer.h b/openfpga/src/fpga_sdc/configuration_chain_sdc_writer.h new file mode 100644 index 000000000..1e636c8a9 --- /dev/null +++ b/openfpga/src/fpga_sdc/configuration_chain_sdc_writer.h @@ -0,0 +1,26 @@ +#ifndef CONFIGURATION_CHAIN_SDC_WRITER_H +#define CONFIGURATION_CHAIN_SDC_WRITER_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "module_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_pnr_sdc_constrain_configurable_chain(const std::string& sdc_fname, + const float& time_unit, + const float& max_delay, + const float& min_delay, + const ModuleManager& module_manager); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_sdc/sdc_writer_utils.cpp b/openfpga/src/fpga_sdc/sdc_writer_utils.cpp index 85ff6edcb..93a3a94aa 100644 --- a/openfpga/src/fpga_sdc/sdc_writer_utils.cpp +++ b/openfpga/src/fpga_sdc/sdc_writer_utils.cpp @@ -95,14 +95,14 @@ void print_pnr_sdc_constrain_max_delay(std::fstream& fp, fp << " -from "; if (!src_instance_name.empty()) { - fp << src_instance_name << "/"; + fp << format_dir_path(src_instance_name); } fp << src_port_name; fp << " -to "; if (!des_instance_name.empty()) { - fp << des_instance_name << "/"; + fp << format_dir_path(des_instance_name); } fp << des_port_name; @@ -130,7 +130,7 @@ void print_pnr_sdc_regexp_constrain_max_delay(std::fstream& fp, fp << " -from "; fp << "[get_pins -regexp \""; if (!src_instance_name.empty()) { - fp << src_instance_name << "/"; + fp << format_dir_path(src_instance_name); } fp << src_port_name; @@ -140,7 +140,7 @@ void print_pnr_sdc_regexp_constrain_max_delay(std::fstream& fp, fp << "[get_pins -regexp \""; if (!des_instance_name.empty()) { - fp << des_instance_name << "/"; + fp << format_dir_path(des_instance_name); } fp << des_port_name; @@ -167,14 +167,14 @@ void print_pnr_sdc_constrain_min_delay(std::fstream& fp, fp << " -from "; if (!src_instance_name.empty()) { - fp << src_instance_name << "/"; + fp << format_dir_path(src_instance_name); } fp << src_port_name; fp << " -to "; if (!des_instance_name.empty()) { - fp << des_instance_name << "/"; + fp << format_dir_path(des_instance_name); } fp << des_port_name; From fc2b09514eb8a6546419b6e0905892de90111abd Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 12 May 2020 20:51:16 -0600 Subject: [PATCH 531/645] add configuration chain write to regression tests --- ...onfiguration_chain_example_script.openfpga | 68 +++++++++++++++++++ .../configuration_chain/config/task.conf | 2 +- 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 openfpga_flow/OpenFPGAShellScripts/configuration_chain_example_script.openfpga diff --git a/openfpga_flow/OpenFPGAShellScripts/configuration_chain_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/configuration_chain_example_script.openfpga new file mode 100644 index 000000000..326bdba6a --- /dev/null +++ b/openfpga_flow/OpenFPGAShellScripts/configuration_chain_example_script.openfpga @@ -0,0 +1,68 @@ +# Run VPR for the 'and' design +#--write_rr_graph example_rr_graph.xml +vpr ${VPR_ARCH_FILE} ${VPR_TESTBENCH_BLIF} --clock_modeling route + +# Read OpenFPGA architecture definition +read_openfpga_arch -f ${OPENFPGA_ARCH_FILE} + +# Annotate the OpenFPGA architecture to VPR data base +# to debug use --verbose options +link_openfpga_arch --activity_file ${ACTIVITY_FILE} --sort_gsb_chan_node_in_edges + +# Check and correct any naming conflicts in the BLIF netlist +check_netlist_naming_conflict --fix --report ./netlist_renaming.xml + +# Apply fix-up to clustering nets based on routing results +pb_pin_fixup --verbose + +# Apply fix-up to Look-Up Table truth tables based on packing results +lut_truth_table_fixup + +# Build the module graph +# - Enabled compression on routing architecture modules +# - Enable pin duplication on grid modules +build_fabric --compress_routing #--verbose + +# Write the fabric hierarchy of module graph to a file +# This is used by hierarchical PnR flows +write_fabric_hierarchy --file ./fabric_hierarchy.txt + +# Repack the netlist to physical pbs +# This must be done before bitstream generator and testbench generation +# Strongly recommend it is done after all the fix-up have been applied +repack #--verbose + +# Build the bitstream +# - Output the fabric-independent bitstream to a file +build_architecture_bitstream --verbose --file fabric_indepenent_bitstream.xml + +# Build fabric-dependent bitstream +build_fabric_bitstream --verbose + +# Write the Verilog netlist for FPGA fabric +# - Enable the use of explicit port mapping in Verilog netlist +write_fabric_verilog --file ./SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose + +# Write the Verilog testbench for FPGA fabric +# - We suggest the use of same output directory as fabric Verilog netlists +# - Must specify the reference benchmark file if you want to output any testbenches +# - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA +# - Enable pre-configured top-level testbench which is a fast verification skipping programming phase +# - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts +write_verilog_testbench --file ./SRC --reference_benchmark_file_path ${REFERENCE_VERILOG_TESTBENCH} --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini ./SimulationDeck/simulation_deck.ini + +# Write the SDC files for PnR backend +# - Turn on every options here +write_pnr_sdc --file ./SDC + +# Write SDC to constrain timing of configuration chain +write_configuration_chain_sdc --file ./SDC/ccff_timing.sdc --time_unit ns --max_delay 5 --min_delay 2.5 + +# Write the SDC to run timing analysis for a mapped FPGA fabric +write_analysis_sdc --file ./SDC_analysis + +# Finish and exit OpenFPGA +exit + +# Note : +# To run verification at the end of the flow maintain source in ./SRC directory diff --git a/openfpga_flow/tasks/openfpga_shell/configuration_chain/config/task.conf b/openfpga_flow/tasks/openfpga_shell/configuration_chain/config/task.conf index 79781a758..9e18efc65 100644 --- a/openfpga_flow/tasks/openfpga_shell/configuration_chain/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/configuration_chain/config/task.conf @@ -8,7 +8,7 @@ [GENERAL] run_engine=openfpga_shell -openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/configuration_chain_example_script.openfpga power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml power_analysis = true spice_output=false From 02e86c565ac0051b47db452e78f9d09cfbf83247 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 12 May 2020 20:53:17 -0600 Subject: [PATCH 532/645] bug fix in configuration chain SDC writer --- openfpga/src/fpga_sdc/configuration_chain_sdc_writer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openfpga/src/fpga_sdc/configuration_chain_sdc_writer.cpp b/openfpga/src/fpga_sdc/configuration_chain_sdc_writer.cpp index ce41e6851..5372741bb 100644 --- a/openfpga/src/fpga_sdc/configuration_chain_sdc_writer.cpp +++ b/openfpga/src/fpga_sdc/configuration_chain_sdc_writer.cpp @@ -162,7 +162,7 @@ void print_pnr_sdc_constrain_configurable_chain(const std::string& sdc_fname, std::string previous_module_path; ModuleId previous_module = ModuleId::INVALID(); rec_print_pnr_sdc_constrain_configurable_chain(fp, - max_delay/time_unit, min_delay/time_unit, + max_delay, min_delay, module_manager, top_module, format_dir_path(module_manager.module_name(top_module)), previous_module_path, From a41c8dbcb3eff7497cb388a5f87c9098e8d6c11d Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 12 May 2020 20:54:11 -0600 Subject: [PATCH 533/645] change to use default sphinx build version --- docs/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Makefile b/docs/Makefile index 6f26ca799..1f299ec6b 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -3,7 +3,7 @@ # You can set these variables from the command line. SPHINXOPTS = -SPHINXBUILD = sphinx-build-3.6 +SPHINXBUILD = sphinx-build SOURCEDIR = source BUILDDIR = build From df9cf32b4969a1b3eb918b041e5144a95b583512 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 12 May 2020 20:59:55 -0600 Subject: [PATCH 534/645] update documenation for configuration chain writer --- .../source/openfpga_shell/openfpga_commands.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/source/openfpga_shell/openfpga_commands.rst b/docs/source/openfpga_shell/openfpga_commands.rst index 5cd5a8c33..c07b7a71c 100644 --- a/docs/source/openfpga_shell/openfpga_commands.rst +++ b/docs/source/openfpga_shell/openfpga_commands.rst @@ -221,6 +221,23 @@ FPGA-SDC - ``--verbose`` Enable verbose output +.. option:: write_configuration_chain_sdc + + Write the SDC file to constrain the timing for configuration chain. The timing constraints will always start from the first output (Q) of a Configuration Chain Flip-flop (CCFF) and ends at the inputs of the next CCFF in the chain. Note that Qb of CCFF will not be constrained! + + - ``--file`` or ``-f`` Specify the output SDC file + + - ``--time_unit`` Specify a time unit to be used in SDC files. Acceptable values are string: ``as`` | ``fs`` | ``ps`` | ``ns`` | ``us`` | ``ms`` | ``ks`` | ``Ms``. By default, we will consider second (``s``). + + + - ``--max_delay`` Specify the maximum delay to be used. The timing value should follow the time unit defined in this command. + + - ``--min_delay`` Specify the minimum delay to be used. The timing value should follow the time unit defined in this command. + + .. note:: + Only applicable when configuration chain is used as configuration protocol + + .. option:: write_analysis_sdc Write the SDC to run timing analysis for a mapped FPGA fabric From 99751b84f5814743acd93a13c9fef1bc7aa6de7b Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 12 May 2020 22:49:41 -0600 Subject: [PATCH 535/645] bug fix in configuration chain sdc writer --- openfpga/src/fpga_sdc/configuration_chain_sdc_writer.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openfpga/src/fpga_sdc/configuration_chain_sdc_writer.cpp b/openfpga/src/fpga_sdc/configuration_chain_sdc_writer.cpp index 5372741bb..ea74ea579 100644 --- a/openfpga/src/fpga_sdc/configuration_chain_sdc_writer.cpp +++ b/openfpga/src/fpga_sdc/configuration_chain_sdc_writer.cpp @@ -101,16 +101,16 @@ void rec_print_pnr_sdc_constrain_configurable_chain(std::fstream& fp, for (const BasicPort& input_port : module_manager.module_ports_by_type(parent_module, ModuleManager::MODULE_INPUT_PORT)) { print_pnr_sdc_constrain_max_delay(fp, previous_module_path, - generate_sdc_port(output_port), + output_port.get_name(), parent_module_path, - generate_sdc_port(input_port), + input_port.get_name(), tmax); print_pnr_sdc_constrain_min_delay(fp, previous_module_path, - generate_sdc_port(output_port), + output_port.get_name(), parent_module_path, - generate_sdc_port(input_port), + input_port.get_name(), tmin); } From ae9f1fbd908f3724ad54c630bcf088cd1d7cc2d9 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 13 May 2020 23:37:07 -0600 Subject: [PATCH 536/645] critical bug fixed in the disable MUX output --- openfpga/src/fpga_sdc/sdc_mux_utils.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openfpga/src/fpga_sdc/sdc_mux_utils.cpp b/openfpga/src/fpga_sdc/sdc_mux_utils.cpp index ee30eae9b..7f7acc41f 100644 --- a/openfpga/src/fpga_sdc/sdc_mux_utils.cpp +++ b/openfpga/src/fpga_sdc/sdc_mux_utils.cpp @@ -52,10 +52,10 @@ void rec_print_pnr_sdc_disable_routing_multiplexer_outputs(std::fstream& fp, /* For each child, we will go one level down in priority */ for (const ModuleId& child_module : module_manager.child_modules(parent_module)) { - std::string child_module_path = parent_module_path; - /* Iterate over the child instances*/ for (const size_t& child_instance : module_manager.child_module_instances(parent_module, child_module)) { + std::string child_module_path = parent_module_path; + std::string child_instance_name; if (true == module_manager.instance_name(parent_module, child_module, child_instance).empty()) { child_instance_name = generate_instance_name(module_manager.module_name(child_module), child_instance); From 13f591cacfa451e9d0d8f97d6cb92fbb934ce820 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 14 May 2020 16:53:15 -0600 Subject: [PATCH 537/645] add new command to disable timing for configure ports of programmable modules --- openfpga/src/base/openfpga_sdc.cpp | 27 +++ openfpga/src/base/openfpga_sdc.h | 3 + openfpga/src/base/openfpga_sdc_command.cpp | 40 ++++ .../fpga_sdc/configure_port_sdc_writer.cpp | 160 ++++++++++++++ .../src/fpga_sdc/configure_port_sdc_writer.h | 26 +++ openfpga/src/fpga_sdc/pnr_sdc_writer.cpp | 63 ------ openfpga/src/fpga_sdc/sdc_mux_utils.cpp | 204 +++++++++++------- openfpga/src/fpga_sdc/sdc_mux_utils.h | 21 +- openfpga/src/fpga_sdc/sdc_writer_utils.cpp | 111 ++++++++++ openfpga/src/fpga_sdc/sdc_writer_utils.h | 8 + ...onfiguration_chain_example_script.openfpga | 3 + .../example_script.openfpga | 3 + 12 files changed, 523 insertions(+), 146 deletions(-) create mode 100644 openfpga/src/fpga_sdc/configure_port_sdc_writer.cpp create mode 100644 openfpga/src/fpga_sdc/configure_port_sdc_writer.h diff --git a/openfpga/src/base/openfpga_sdc.cpp b/openfpga/src/base/openfpga_sdc.cpp index 93bd46959..bba115d3c 100644 --- a/openfpga/src/base/openfpga_sdc.cpp +++ b/openfpga/src/base/openfpga_sdc.cpp @@ -16,6 +16,7 @@ #include "pnr_sdc_writer.h" #include "analysis_sdc_writer.h" #include "configuration_chain_sdc_writer.h" +#include "configure_port_sdc_writer.h" #include "openfpga_sdc.h" /* Include global variables of VPR */ @@ -138,6 +139,32 @@ int write_configuration_chain_sdc(const OpenfpgaContext& openfpga_ctx, return CMD_EXEC_SUCCESS; } +/******************************************************************** + * A wrapper function to call the PnR SDC generator on routing multiplexers + * of FPGA-SDC + *******************************************************************/ +int write_sdc_disable_timing_configure_ports(const OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context) { + + /* Get command options */ + CommandOptionId opt_output_dir = cmd.option("file"); + CommandOptionId opt_flatten_names = cmd.option("flatten_names"); + + std::string sdc_dir_path = format_dir_path(cmd_context.option_value(cmd, opt_output_dir)); + + /* Write the SDC for configuration chain */ + if (CMD_EXEC_FATAL_ERROR == + print_sdc_disable_timing_configure_ports(cmd_context.option_value(cmd, opt_output_dir), + cmd_context.option_enable(cmd, opt_flatten_names), + openfpga_ctx.mux_lib(), + openfpga_ctx.arch().circuit_lib, + openfpga_ctx.module_graph())) { + return CMD_EXEC_FATAL_ERROR; + } + + return CMD_EXEC_SUCCESS; +} + /******************************************************************** * A wrapper function to call the analysis SDC generator of FPGA-SDC *******************************************************************/ diff --git a/openfpga/src/base/openfpga_sdc.h b/openfpga/src/base/openfpga_sdc.h index 060c304bc..03f4af03f 100644 --- a/openfpga/src/base/openfpga_sdc.h +++ b/openfpga/src/base/openfpga_sdc.h @@ -21,6 +21,9 @@ int write_pnr_sdc(OpenfpgaContext& openfpga_ctx, int write_configuration_chain_sdc(const OpenfpgaContext& openfpga_ctx, const Command& cmd, const CommandContext& cmd_context); +int write_sdc_disable_timing_configure_ports(const OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context); + int write_analysis_sdc(OpenfpgaContext& openfpga_ctx, const Command& cmd, const CommandContext& cmd_context); diff --git a/openfpga/src/base/openfpga_sdc_command.cpp b/openfpga/src/base/openfpga_sdc_command.cpp index faa488931..17910a711 100644 --- a/openfpga/src/base/openfpga_sdc_command.cpp +++ b/openfpga/src/base/openfpga_sdc_command.cpp @@ -119,6 +119,36 @@ ShellCommandId add_openfpga_write_configuration_chain_sdc_command(openfpga::Shel return shell_cmd_id; } +/******************************************************************** + * - Add a command to Shell environment: generate PnR SDC for configure ports + * - Add associated options + * - Add command dependency + *******************************************************************/ +static +ShellCommandId add_openfpga_write_sdc_disable_timing_configure_ports_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id, + const std::vector& dependent_cmds) { + Command shell_cmd("write_sdc_disable_timing_configure_ports"); + + /* Add an option '--file' in short '-f'*/ + CommandOptionId output_opt = shell_cmd.add_option("file", true, "Specify the output directory"); + shell_cmd.set_option_short_name(output_opt, "f"); + shell_cmd.set_option_require_value(output_opt, openfpga::OPT_STRING); + + /* Add an option '--flatten_name' */ + shell_cmd.add_option("flatten_names", false, "Use flatten names (no wildcards) in SDC files"); + + /* Add command 'write_configuration_chain_sdc' to the Shell */ + ShellCommandId shell_cmd_id = shell.add_command(shell_cmd, "generate SDC files to disable timing for configure ports across FPGA fabric"); + shell.set_command_class(shell_cmd_id, cmd_class_id); + shell.set_command_const_execute_function(shell_cmd_id, write_sdc_disable_timing_configure_ports); + + /* Add command dependency to the Shell */ + shell.set_command_dependency(shell_cmd_id, dependent_cmds); + + return shell_cmd_id; +} + /******************************************************************** * - Add a command to Shell environment: generate PnR SDC * - Add associated options @@ -183,6 +213,16 @@ void add_openfpga_sdc_commands(openfpga::Shell& shell) { openfpga_sdc_cmd_class, cc_sdc_cmd_dependency); + /******************************** + * Command 'write_sdc_disable_timing_configure_ports' + */ + /* The 'write_sdc_disable_timing_configure_ports' command should NOT be executed before 'build_fabric' */ + std::vector config_port_sdc_cmd_dependency; + config_port_sdc_cmd_dependency.push_back(build_fabric_id); + add_openfpga_write_sdc_disable_timing_configure_ports_command(shell, + openfpga_sdc_cmd_class, + config_port_sdc_cmd_dependency); + /******************************** * Command 'write_analysis_sdc' */ diff --git a/openfpga/src/fpga_sdc/configure_port_sdc_writer.cpp b/openfpga/src/fpga_sdc/configure_port_sdc_writer.cpp new file mode 100644 index 000000000..c38a2d725 --- /dev/null +++ b/openfpga/src/fpga_sdc/configure_port_sdc_writer.cpp @@ -0,0 +1,160 @@ +/******************************************************************** + * This file includes functions that print SDC (Synopsys Design Constraint) + * files in physical design tools, i.e., Place & Route (PnR) tools + * The SDC files are used to constrain the timing of configuration chain + * + * Note that this is different from the SDC to constrain VPR Place&Route + * engine! These SDCs are designed for PnR to generate FPGA layouts!!! + *******************************************************************/ +#include +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_time.h" +#include "vtr_log.h" + +/* Headers from openfpgashell library */ +#include "command_exit_codes.h" + +/* Headers from openfpgautil library */ +#include "openfpga_scale.h" +#include "openfpga_port.h" +#include "openfpga_digest.h" + +#include "openfpga_naming.h" + +#include "sdc_writer_naming.h" +#include "sdc_writer_utils.h" +#include "sdc_mux_utils.h" +#include "configure_port_sdc_writer.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Break combinational loops in FPGA fabric, which mainly come from + * non-MUX programmable modules + * To handle this, we disable the timing at configuration ports + * + * Return code: + * 0: success + * 1: fatal error occurred + *******************************************************************/ +static +int print_sdc_disable_non_mux_circuit_configure_ports(std::fstream& fp, + const bool& flatten_names, + const CircuitLibrary& circuit_lib, + const ModuleManager& module_manager, + const ModuleId& top_module) { + + if (false == valid_file_stream(fp)) { + return CMD_EXEC_FATAL_ERROR; + } + + /* Iterate over the MUX modules */ + for (const CircuitModelId& model : circuit_lib.models()) { + + /* Skip MUXes, they are handled in another function */ + if (CIRCUIT_MODEL_MUX == circuit_lib.model_type(model)) { + continue; + } + + /* We care programmable circuit models only */ + if (0 == circuit_lib.model_ports_by_type(model, CIRCUIT_MODEL_PORT_SRAM).size()) { + continue; + } + + std::string programmable_module_name = circuit_lib.model_name(model); + + /* Find the module name in module manager */ + ModuleId programmable_module = module_manager.find_module(programmable_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(programmable_module)); + + /* Go recursively in the module manager, + * starting from the top-level module: instance id of the top-level module is 0 by default + * Disable all the outputs of child modules that matches the mux_module id + */ + for (const CircuitPortId& sram_port : circuit_lib.model_ports_by_type(model, CIRCUIT_MODEL_PORT_SRAM)) { + const std::string& sram_port_name = circuit_lib.port_lib_name(sram_port); + if (CMD_EXEC_FATAL_ERROR == + rec_print_sdc_disable_timing_for_module_ports(fp, + flatten_names, + module_manager, + top_module, + programmable_module, + format_dir_path(module_manager.module_name(top_module)), + sram_port_name)) { + return CMD_EXEC_FATAL_ERROR; + } + } + } + + return CMD_EXEC_SUCCESS; +} + +/******************************************************************** + * Break combinational loops in FPGA fabric, which mainly come from + * the configure ports of each programmable module. + * To handle this, we disable the configure ports of + * - routing multiplexers + * - other circuit model that has SRAM ports + *******************************************************************/ +int print_sdc_disable_timing_configure_ports(const std::string& sdc_fname, + const bool& flatten_names, + const MuxLibrary& mux_lib, + const CircuitLibrary& circuit_lib, + const ModuleManager& module_manager) { + /* Create the directory */ + create_directory(find_path_dir_name(sdc_fname)); + + /* Start time count */ + std::string timer_message = std::string("Write SDC to disable timing on configuration outputs of programmable cells for P&R flow '") + sdc_fname + std::string("'"); + vtr::ScopedStartFinishTimer timer(timer_message); + + /* Create the file stream */ + std::fstream fp; + fp.open(sdc_fname, std::fstream::out | std::fstream::trunc); + + check_file_stream(sdc_fname.c_str(), fp); + + /* Generate the descriptions*/ + print_sdc_file_header(fp, std::string("Disable configuration outputs of all the programmable cells for PnR")); + + std::string top_module_name = generate_fpga_top_module_name(); + ModuleId top_module = module_manager.find_module(top_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(top_module)); + + /* Disable timing for the configure ports of all the routing multiplexer */ + VTR_LOG("Write disable timing for routing multiplexers..."); + if (CMD_EXEC_FATAL_ERROR == print_sdc_disable_routing_multiplexer_configure_ports(fp, + flatten_names, + mux_lib, + circuit_lib, + module_manager, + top_module)) { + VTR_LOG("Fatal errors occurred\n"); + return CMD_EXEC_FATAL_ERROR; + } + VTR_LOG("Done\n"); + + /* Disable timing for the other programmable circuit models */ + VTR_LOG("Write disable timing for other programmable modules..."); + if (CMD_EXEC_FATAL_ERROR == print_sdc_disable_non_mux_circuit_configure_ports(fp, + flatten_names, + circuit_lib, + module_manager, + top_module)) { + VTR_LOG("Fatal errors occurred\n"); + return CMD_EXEC_FATAL_ERROR; + } + VTR_LOG("Done\n"); + + /* Close file handler */ + fp.close(); + + return CMD_EXEC_SUCCESS; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/configure_port_sdc_writer.h b/openfpga/src/fpga_sdc/configure_port_sdc_writer.h new file mode 100644 index 000000000..ef66af749 --- /dev/null +++ b/openfpga/src/fpga_sdc/configure_port_sdc_writer.h @@ -0,0 +1,26 @@ +#ifndef CONFIGURE_PORT_SDC_WRITER_H +#define CONFIGURE_PORT_SDC_WRITER_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "module_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +int print_sdc_disable_timing_configure_ports(const std::string& sdc_fname, + const bool& flatten_names, + const MuxLibrary& mux_lib, + const CircuitLibrary& circuit_lib, + const ModuleManager& module_manager); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp b/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp index 3ef29330d..c02988462 100644 --- a/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp +++ b/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp @@ -75,69 +75,6 @@ void print_pnr_sdc_constrain_configurable_memory_outputs(const std::string& sdc_ fp.close(); } -/******************************************************************** - * Break combinational loops in FPGA fabric, which mainly come from - * loops of multiplexers. - * To handle this, we disable the timing at outputs of routing multiplexers - *******************************************************************/ -static -void print_sdc_disable_routing_multiplexer_outputs(const std::string& sdc_dir, - const bool& flatten_names, - const MuxLibrary& mux_lib, - const CircuitLibrary& circuit_lib, - const ModuleManager& module_manager, - const ModuleId& top_module) { - /* Create the file name for Verilog netlist */ - std::string sdc_fname(sdc_dir + std::string(SDC_DISABLE_MUX_OUTPUTS_FILE_NAME)); - - /* Start time count */ - std::string timer_message = std::string("Write SDC to disable routing multiplexer outputs for P&R flow '") + sdc_fname + std::string("'"); - vtr::ScopedStartFinishTimer timer(timer_message); - - /* Create the file stream */ - std::fstream fp; - fp.open(sdc_fname, std::fstream::out | std::fstream::trunc); - - check_file_stream(sdc_fname.c_str(), fp); - - /* Generate the descriptions*/ - print_sdc_file_header(fp, std::string("Disable routing multiplexer outputs for PnR")); - - /* Iterate over the MUX modules */ - for (const MuxId& mux_id : mux_lib.muxes()) { - const CircuitModelId& mux_model = mux_lib.mux_circuit_model(mux_id); - - /* Skip LUTs, we only care about multiplexers here */ - if (CIRCUIT_MODEL_MUX != circuit_lib.model_type(mux_model)) { - continue; - } - - const MuxGraph& mux_graph = mux_lib.mux_graph(mux_id); - std::string mux_module_name = generate_mux_subckt_name(circuit_lib, mux_model, - find_mux_num_datapath_inputs(circuit_lib, mux_model, mux_graph.num_inputs()), - std::string("")); - - /* Find the module name in module manager */ - ModuleId mux_module = module_manager.find_module(mux_module_name); - VTR_ASSERT(true == module_manager.valid_module_id(mux_module)); - - /* Go recursively in the module manager, - * starting from the top-level module: instance id of the top-level module is 0 by default - * Disable all the outputs of child modules that matches the mux_module id - */ - rec_print_pnr_sdc_disable_routing_multiplexer_outputs(fp, - flatten_names, - module_manager, - top_module, - mux_module, - format_dir_path(module_manager.module_name(top_module))); - - } - - /* Close file handler */ - fp.close(); -} - /******************************************************************** * Break combinational loops in FPGA fabric, which mainly come from * loops of multiplexers. diff --git a/openfpga/src/fpga_sdc/sdc_mux_utils.cpp b/openfpga/src/fpga_sdc/sdc_mux_utils.cpp index 7f7acc41f..0d2770566 100644 --- a/openfpga/src/fpga_sdc/sdc_mux_utils.cpp +++ b/openfpga/src/fpga_sdc/sdc_mux_utils.cpp @@ -6,13 +6,19 @@ /* Headers from vtrutil library */ #include "vtr_assert.h" #include "vtr_log.h" +#include "vtr_time.h" + +/* Headers from openfpgashell library */ +#include "command_exit_codes.h" /* Headers from openfpgautil library */ -#include "openfpga_wildcard_string.h" #include "openfpga_digest.h" #include "openfpga_naming.h" +#include "mux_utils.h" + +#include "sdc_writer_naming.h" #include "sdc_writer_utils.h" #include "sdc_mux_utils.h" @@ -21,93 +27,137 @@ namespace openfpga { /******************************************************************** - * Print SDC commands to disable outputs of routing multiplexer modules - * in a given module id - * This function will be executed in a recursive way, - * using a Depth-First Search (DFS) strategy - * It will iterate over all the configurable children under each module - * and print a SDC command to disable its outputs - * - * Note: - * - When flatten_names is true - * this function will not apply any wildcard to names - * - When flatten_names is false - * It will straightforwardly output the instance name and port name - * This function will try to apply wildcard to names - * so that SDC file size can be minimal + * Break combinational loops in FPGA fabric, which mainly come from + * loops of multiplexers. + * To handle this, we disable the timing at outputs of routing multiplexers *******************************************************************/ -void rec_print_pnr_sdc_disable_routing_multiplexer_outputs(std::fstream& fp, - const bool& flatten_names, - const ModuleManager& module_manager, - const ModuleId& parent_module, - const ModuleId& mux_module, - const std::string& parent_module_path) { +void print_sdc_disable_routing_multiplexer_outputs(const std::string& sdc_dir, + const bool& flatten_names, + const MuxLibrary& mux_lib, + const CircuitLibrary& circuit_lib, + const ModuleManager& module_manager, + const ModuleId& top_module) { + /* Create the file name for Verilog netlist */ + std::string sdc_fname(sdc_dir + std::string(SDC_DISABLE_MUX_OUTPUTS_FILE_NAME)); - /* Build wildcard names for the instance names of multiple-instanced-blocks (MIB) - * We will find all the instance names and see there are common prefix - * If so, we can use wildcards - */ - std::map> wildcard_names; + /* Start time count */ + std::string timer_message = std::string("Write SDC to disable routing multiplexer outputs for P&R flow '") + sdc_fname + std::string("'"); + vtr::ScopedStartFinishTimer timer(timer_message); - /* For each child, we will go one level down in priority */ - for (const ModuleId& child_module : module_manager.child_modules(parent_module)) { + /* Create the file stream */ + std::fstream fp; + fp.open(sdc_fname, std::fstream::out | std::fstream::trunc); - /* Iterate over the child instances*/ - for (const size_t& child_instance : module_manager.child_module_instances(parent_module, child_module)) { - std::string child_module_path = parent_module_path; + check_file_stream(sdc_fname.c_str(), fp); - std::string child_instance_name; - if (true == module_manager.instance_name(parent_module, child_module, child_instance).empty()) { - child_instance_name = generate_instance_name(module_manager.module_name(child_module), child_instance); - } else { - child_instance_name = module_manager.instance_name(parent_module, child_module, child_instance); - } + /* Generate the descriptions*/ + print_sdc_file_header(fp, std::string("Disable routing multiplexer outputs for PnR")); - if (false == flatten_names) { - /* Try to adapt to a wildcard name: replace all the numbers with a wildcard character '*' */ - WildCardString wildcard_str(child_instance_name); - /* If the wildcard name is already in the list, we can skip this - * Otherwise, we have to - * - output this instance - * - record the wildcard name in the map - */ - if ( (0 < wildcard_names.count(child_module)) - && (wildcard_names.at(child_module).end() != std::find(wildcard_names.at(child_module).begin(), - wildcard_names.at(child_module).end(), - wildcard_str.data())) ) { - continue; - } + /* Iterate over the MUX modules */ + for (const MuxId& mux_id : mux_lib.muxes()) { + const CircuitModelId& mux_model = mux_lib.mux_circuit_model(mux_id); + + /* Skip LUTs, we only care about multiplexers here */ + if (CIRCUIT_MODEL_MUX != circuit_lib.model_type(mux_model)) { + continue; + } - child_module_path += wildcard_str.data(); - - wildcard_names[child_module].push_back(wildcard_str.data()); - } else { - child_module_path += child_instance_name; - } - - child_module_path = format_dir_path(child_module_path); + const MuxGraph& mux_graph = mux_lib.mux_graph(mux_id); + std::string mux_module_name = generate_mux_subckt_name(circuit_lib, mux_model, + find_mux_num_datapath_inputs(circuit_lib, mux_model, mux_graph.num_inputs()), + std::string("")); - /* If this is NOT the MUX module we want, we go recursively */ - if (mux_module != child_module) { - rec_print_pnr_sdc_disable_routing_multiplexer_outputs(fp, flatten_names, - module_manager, - child_module, - mux_module, - child_module_path); - continue; - } + /* Find the module name in module manager */ + ModuleId mux_module = module_manager.find_module(mux_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(mux_module)); - /* Validate file stream */ - valid_file_stream(fp); - - /* Reach here, this is the MUX module we want, disable the outputs */ - for (const BasicPort& output_port : module_manager.module_ports_by_type(mux_module, ModuleManager::MODULE_OUTPUT_PORT)) { - fp << "set_disable_timing "; - fp << child_module_path << output_port.get_name(); - fp << std::endl; - } + /* Go recursively in the module manager, + * starting from the top-level module: instance id of the top-level module is 0 by default + * Disable all the outputs of child modules that matches the mux_module id + */ + for (const BasicPort& output_port : module_manager.module_ports_by_type(mux_module, ModuleManager::MODULE_OUTPUT_PORT)) { + rec_print_sdc_disable_timing_for_module_ports(fp, + flatten_names, + module_manager, + top_module, + mux_module, + format_dir_path(module_manager.module_name(top_module)), + output_port.get_name()); } } + + /* Close file handler */ + fp.close(); +} + +/******************************************************************** + * Break combinational loops in FPGA fabric, which mainly come from + * loops of multiplexers. + * To handle this, we disable the timing at configuration ports of routing multiplexers + * + * Return code: + * 0: success + * 1: fatal error occurred + *******************************************************************/ +int print_sdc_disable_routing_multiplexer_configure_ports(std::fstream& fp, + const bool& flatten_names, + const MuxLibrary& mux_lib, + const CircuitLibrary& circuit_lib, + const ModuleManager& module_manager, + const ModuleId& top_module) { + + if (false == valid_file_stream(fp)) { + return CMD_EXEC_FATAL_ERROR; + } + + /* Iterate over the MUX modules */ + for (const MuxId& mux_id : mux_lib.muxes()) { + const CircuitModelId& mux_model = mux_lib.mux_circuit_model(mux_id); + + const MuxGraph& mux_graph = mux_lib.mux_graph(mux_id); + std::string mux_module_name = generate_mux_subckt_name(circuit_lib, mux_model, + find_mux_num_datapath_inputs(circuit_lib, mux_model, mux_graph.num_inputs()), + std::string("")); + + /* Find the module name in module manager */ + ModuleId mux_module = module_manager.find_module(mux_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(mux_module)); + + /* Go recursively in the module manager, + * starting from the top-level module: instance id of the top-level module is 0 by default + * Disable all the outputs of child modules that matches the mux_module id + */ + for (const CircuitPortId& mux_sram_port : circuit_lib.model_ports_by_type(mux_model, CIRCUIT_MODEL_PORT_SRAM)) { + const std::string& mux_sram_port_name = circuit_lib.port_prefix(mux_sram_port); + VTR_ASSERT(true == module_manager.valid_module_port_id(mux_module, module_manager.find_module_port(mux_module, mux_sram_port_name))); + if (CMD_EXEC_FATAL_ERROR == + rec_print_sdc_disable_timing_for_module_ports(fp, + flatten_names, + module_manager, + top_module, + mux_module, + format_dir_path(module_manager.module_name(top_module)), + mux_sram_port_name)) { + return CMD_EXEC_FATAL_ERROR; + } + + const std::string& mux_sram_inv_port_name = circuit_lib.port_prefix(mux_sram_port) + "_inv"; + VTR_ASSERT(true == module_manager.valid_module_port_id(mux_module, module_manager.find_module_port(mux_module, mux_sram_inv_port_name))); + if (CMD_EXEC_FATAL_ERROR == + rec_print_sdc_disable_timing_for_module_ports(fp, + flatten_names, + module_manager, + top_module, + mux_module, + format_dir_path(module_manager.module_name(top_module)), + mux_sram_inv_port_name)) { + return CMD_EXEC_FATAL_ERROR; + } + } + + } + + return CMD_EXEC_SUCCESS; } } /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/sdc_mux_utils.h b/openfpga/src/fpga_sdc/sdc_mux_utils.h index 70089aa93..a63a42bb3 100644 --- a/openfpga/src/fpga_sdc/sdc_mux_utils.h +++ b/openfpga/src/fpga_sdc/sdc_mux_utils.h @@ -6,6 +6,8 @@ *******************************************************************/ #include #include +#include "mux_library.h" +#include "circuit_library.h" #include "module_manager.h" /******************************************************************** @@ -15,12 +17,19 @@ /* begin namespace openfpga */ namespace openfpga { -void rec_print_pnr_sdc_disable_routing_multiplexer_outputs(std::fstream& fp, - const bool& flatten_names, - const ModuleManager& module_manager, - const ModuleId& parent_module, - const ModuleId& mux_module, - const std::string& parent_module_path); +void print_sdc_disable_routing_multiplexer_outputs(const std::string& sdc_dir, + const bool& flatten_names, + const MuxLibrary& mux_lib, + const CircuitLibrary& circuit_lib, + const ModuleManager& module_manager, + const ModuleId& top_module); + +int print_sdc_disable_routing_multiplexer_configure_ports(std::fstream& fp, + const bool& flatten_names, + const MuxLibrary& mux_lib, + const CircuitLibrary& circuit_lib, + const ModuleManager& module_manager, + const ModuleId& top_module); } /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/sdc_writer_utils.cpp b/openfpga/src/fpga_sdc/sdc_writer_utils.cpp index 93a3a94aa..e448d806e 100644 --- a/openfpga/src/fpga_sdc/sdc_writer_utils.cpp +++ b/openfpga/src/fpga_sdc/sdc_writer_utils.cpp @@ -4,12 +4,16 @@ #include #include #include +#include /* Headers from vtrutil library */ #include "vtr_assert.h" /* Headers from openfpgautil library */ #include "openfpga_digest.h" +#include "openfpga_wildcard_string.h" + +#include "openfpga_naming.h" #include "sdc_writer_utils.h" @@ -295,4 +299,111 @@ void print_sdc_set_port_output_delay(std::fstream& fp, fp << std::endl; } +/******************************************************************** + * Print SDC commands to disable a given port of modules + * in a given module id + * This function will be executed in a recursive way, + * using a Depth-First Search (DFS) strategy + * It will iterate over all the configurable children under each module + * and print a SDC command to disable its outputs + * + * Return code: + * 0: success + * 1: fatal error occurred + * + * Note: + * - When flatten_names is true + * this function will not apply any wildcard to names + * - When flatten_names is false + * It will straightforwardly output the instance name and port name + * This function will try to apply wildcard to names + * so that SDC file size can be minimal + *******************************************************************/ +int rec_print_sdc_disable_timing_for_module_ports(std::fstream& fp, + const bool& flatten_names, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const ModuleId& module_to_disable, + const std::string& parent_module_path, + const std::string& disable_port_name) { + + if (false == valid_file_stream(fp)) { + return 1; + } + + /* Build wildcard names for the instance names of multiple-instanced-blocks (MIB) + * We will find all the instance names and see there are common prefix + * If so, we can use wildcards + */ + std::map> wildcard_names; + + /* For each child, we will go one level down in priority */ + for (const ModuleId& child_module : module_manager.child_modules(parent_module)) { + + /* Iterate over the child instances*/ + for (const size_t& child_instance : module_manager.child_module_instances(parent_module, child_module)) { + std::string child_module_path = parent_module_path; + + std::string child_instance_name; + if (true == module_manager.instance_name(parent_module, child_module, child_instance).empty()) { + child_instance_name = generate_instance_name(module_manager.module_name(child_module), child_instance); + } else { + child_instance_name = module_manager.instance_name(parent_module, child_module, child_instance); + } + + if (false == flatten_names) { + /* Try to adapt to a wildcard name: replace all the numbers with a wildcard character '*' */ + WildCardString wildcard_str(child_instance_name); + /* If the wildcard name is already in the list, we can skip this + * Otherwise, we have to + * - output this instance + * - record the wildcard name in the map + */ + if ( (0 < wildcard_names.count(child_module)) + && (wildcard_names.at(child_module).end() != std::find(wildcard_names.at(child_module).begin(), + wildcard_names.at(child_module).end(), + wildcard_str.data())) ) { + continue; + } + + child_module_path += wildcard_str.data(); + + wildcard_names[child_module].push_back(wildcard_str.data()); + } else { + child_module_path += child_instance_name; + } + + child_module_path = format_dir_path(child_module_path); + + /* If this is NOT the MUX module we want, we go recursively */ + if (module_to_disable != child_module) { + int status = rec_print_sdc_disable_timing_for_module_ports(fp, flatten_names, + module_manager, + child_module, + module_to_disable, + child_module_path, + disable_port_name); + if (1 == status) { + return 1; /* FATAL ERRORS */ + } + continue; + } + + /* Validate file stream */ + valid_file_stream(fp); + + /* Reach here, this is the MUX module we want, disable the outputs */ + ModulePortId port_to_disable = module_manager.find_module_port(module_to_disable, disable_port_name); + if (ModulePortId::INVALID() == port_to_disable) { + return 1; /* FATAL ERRORS */ + } + fp << "set_disable_timing "; + fp << child_module_path << module_manager.module_port(module_to_disable, port_to_disable).get_name(); + fp << std::endl; + } + } + + return 0; /* Success */ +} + } /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/sdc_writer_utils.h b/openfpga/src/fpga_sdc/sdc_writer_utils.h index 4b353045b..7a3861737 100644 --- a/openfpga/src/fpga_sdc/sdc_writer_utils.h +++ b/openfpga/src/fpga_sdc/sdc_writer_utils.h @@ -74,6 +74,14 @@ void print_sdc_set_port_output_delay(std::fstream& fp, const BasicPort& clock_port, const float& delay); +int rec_print_sdc_disable_timing_for_module_ports(std::fstream& fp, + const bool& flatten_names, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const ModuleId& module_to_disable, + const std::string& parent_module_path, + const std::string& disable_port_name); + } /* end namespace openfpga */ #endif diff --git a/openfpga_flow/OpenFPGAShellScripts/configuration_chain_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/configuration_chain_example_script.openfpga index 326bdba6a..8b523f4ae 100644 --- a/openfpga_flow/OpenFPGAShellScripts/configuration_chain_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/configuration_chain_example_script.openfpga @@ -58,6 +58,9 @@ write_pnr_sdc --file ./SDC # Write SDC to constrain timing of configuration chain write_configuration_chain_sdc --file ./SDC/ccff_timing.sdc --time_unit ns --max_delay 5 --min_delay 2.5 +# Write SDC to disable timing for configure ports +write_sdc_disable_timing_configure_ports --file ./SDC/disable_configure_ports.sdc + # Write the SDC to run timing analysis for a mapped FPGA fabric write_analysis_sdc --file ./SDC_analysis diff --git a/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga index 1ccd8418d..f1dc80820 100644 --- a/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga @@ -55,6 +55,9 @@ write_verilog_testbench --file ./SRC --reference_benchmark_file_path ${REFERENCE # - Turn on every options here write_pnr_sdc --file ./SDC +# Write SDC to disable timing for configure ports +write_sdc_disable_timing_configure_ports --file ./SDC/disable_configure_ports.sdc + # Write the SDC to run timing analysis for a mapped FPGA fabric write_analysis_sdc --file ./SDC_analysis From f4dd882f0f974b12f98348ddeedd2b65df84a585 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 14 May 2020 16:57:07 -0600 Subject: [PATCH 538/645] documentation updated for new command --- docs/source/openfpga_shell/openfpga_commands.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/source/openfpga_shell/openfpga_commands.rst b/docs/source/openfpga_shell/openfpga_commands.rst index c07b7a71c..4e5770ebd 100644 --- a/docs/source/openfpga_shell/openfpga_commands.rst +++ b/docs/source/openfpga_shell/openfpga_commands.rst @@ -237,6 +237,13 @@ FPGA-SDC .. note:: Only applicable when configuration chain is used as configuration protocol +.. option:: write_sdc_disable_timing_configure_ports + + Write the SDC file to disable timing for configure ports of programmable modules. The SDC aims to break the combinational loops across FPGAs and avoid false path timing to be visible to timing analyzers + + - ``--file`` or ``-f`` Specify the output SDC file + + - ``--flatten_names`` Use flatten names (no wildcards) in SDC files .. option:: write_analysis_sdc From 067d09f9542bccef2765a92a295ab3e9b37590f0 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 14 May 2020 17:13:05 -0600 Subject: [PATCH 539/645] bug fix for configure port disable_timing writer --- openfpga/src/fpga_sdc/configure_port_sdc_writer.cpp | 5 ++++- openfpga/src/fpga_sdc/sdc_mux_utils.cpp | 7 ++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/openfpga/src/fpga_sdc/configure_port_sdc_writer.cpp b/openfpga/src/fpga_sdc/configure_port_sdc_writer.cpp index c38a2d725..ee9c83667 100644 --- a/openfpga/src/fpga_sdc/configure_port_sdc_writer.cpp +++ b/openfpga/src/fpga_sdc/configure_port_sdc_writer.cpp @@ -25,6 +25,8 @@ #include "openfpga_naming.h" +#include "circuit_library_utils.h" + #include "sdc_writer_naming.h" #include "sdc_writer_utils.h" #include "sdc_mux_utils.h" @@ -76,8 +78,9 @@ int print_sdc_disable_non_mux_circuit_configure_ports(std::fstream& fp, * starting from the top-level module: instance id of the top-level module is 0 by default * Disable all the outputs of child modules that matches the mux_module id */ - for (const CircuitPortId& sram_port : circuit_lib.model_ports_by_type(model, CIRCUIT_MODEL_PORT_SRAM)) { + for (const CircuitPortId& sram_port : find_circuit_mode_select_sram_ports(circuit_lib, model)) { const std::string& sram_port_name = circuit_lib.port_lib_name(sram_port); + VTR_ASSERT(true == module_manager.valid_module_port_id(programmable_module, module_manager.find_module_port(programmable_module, sram_port_name))); if (CMD_EXEC_FATAL_ERROR == rec_print_sdc_disable_timing_for_module_ports(fp, flatten_names, diff --git a/openfpga/src/fpga_sdc/sdc_mux_utils.cpp b/openfpga/src/fpga_sdc/sdc_mux_utils.cpp index 0d2770566..b72d17fdf 100644 --- a/openfpga/src/fpga_sdc/sdc_mux_utils.cpp +++ b/openfpga/src/fpga_sdc/sdc_mux_utils.cpp @@ -17,6 +17,7 @@ #include "openfpga_naming.h" #include "mux_utils.h" +#include "circuit_library_utils.h" #include "sdc_writer_naming.h" #include "sdc_writer_utils.h" @@ -127,8 +128,8 @@ int print_sdc_disable_routing_multiplexer_configure_ports(std::fstream& fp, * starting from the top-level module: instance id of the top-level module is 0 by default * Disable all the outputs of child modules that matches the mux_module id */ - for (const CircuitPortId& mux_sram_port : circuit_lib.model_ports_by_type(mux_model, CIRCUIT_MODEL_PORT_SRAM)) { - const std::string& mux_sram_port_name = circuit_lib.port_prefix(mux_sram_port); + for (const CircuitPortId& mux_sram_port : find_circuit_regular_sram_ports(circuit_lib, mux_model)) { + const std::string& mux_sram_port_name = circuit_lib.port_lib_name(mux_sram_port); VTR_ASSERT(true == module_manager.valid_module_port_id(mux_module, module_manager.find_module_port(mux_module, mux_sram_port_name))); if (CMD_EXEC_FATAL_ERROR == rec_print_sdc_disable_timing_for_module_ports(fp, @@ -141,7 +142,7 @@ int print_sdc_disable_routing_multiplexer_configure_ports(std::fstream& fp, return CMD_EXEC_FATAL_ERROR; } - const std::string& mux_sram_inv_port_name = circuit_lib.port_prefix(mux_sram_port) + "_inv"; + const std::string& mux_sram_inv_port_name = circuit_lib.port_lib_name(mux_sram_port) + "_inv"; VTR_ASSERT(true == module_manager.valid_module_port_id(mux_module, module_manager.find_module_port(mux_module, mux_sram_inv_port_name))); if (CMD_EXEC_FATAL_ERROR == rec_print_sdc_disable_timing_for_module_ports(fp, From 910be3cadb0197d9254d1c1b0bfc6b0210ae6fb9 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 14 May 2020 17:23:58 -0600 Subject: [PATCH 540/645] massively deploy disable_timing for configure ports in CI --- .../duplicated_grid_pin_example_script.openfpga | 3 +++ .../flatten_routing_example_script.openfpga | 3 +++ .../generate_fabric_example_script.openfpga | 3 +++ .../implicit_verilog_example_script.openfpga | 3 +++ .../OpenFPGAShellScripts/mcnc_example_script.openfpga | 3 +++ .../OpenFPGAShellScripts/sdc_time_unit_example_script.openfpga | 3 +++ 6 files changed, 18 insertions(+) diff --git a/openfpga_flow/OpenFPGAShellScripts/duplicated_grid_pin_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/duplicated_grid_pin_example_script.openfpga index 1ccd8418d..f1dc80820 100644 --- a/openfpga_flow/OpenFPGAShellScripts/duplicated_grid_pin_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/duplicated_grid_pin_example_script.openfpga @@ -55,6 +55,9 @@ write_verilog_testbench --file ./SRC --reference_benchmark_file_path ${REFERENCE # - Turn on every options here write_pnr_sdc --file ./SDC +# Write SDC to disable timing for configure ports +write_sdc_disable_timing_configure_ports --file ./SDC/disable_configure_ports.sdc + # Write the SDC to run timing analysis for a mapped FPGA fabric write_analysis_sdc --file ./SDC_analysis diff --git a/openfpga_flow/OpenFPGAShellScripts/flatten_routing_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/flatten_routing_example_script.openfpga index 529fc19ae..88be8a878 100644 --- a/openfpga_flow/OpenFPGAShellScripts/flatten_routing_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/flatten_routing_example_script.openfpga @@ -55,6 +55,9 @@ write_verilog_testbench --file ./SRC --reference_benchmark_file_path ${REFERENCE # - Turn on every options here write_pnr_sdc --file ./SDC +# Write SDC to disable timing for configure ports +write_sdc_disable_timing_configure_ports --file ./SDC/disable_configure_ports.sdc + # Write the SDC to run timing analysis for a mapped FPGA fabric write_analysis_sdc --file ./SDC_analysis diff --git a/openfpga_flow/OpenFPGAShellScripts/generate_fabric_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/generate_fabric_example_script.openfpga index 6514f36b4..ff40f4c76 100644 --- a/openfpga_flow/OpenFPGAShellScripts/generate_fabric_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/generate_fabric_example_script.openfpga @@ -29,6 +29,9 @@ write_fabric_verilog --file ./SRC --explicit_port_mapping --include_timing --inc # - Turn on every options here write_pnr_sdc --file ./SDC +# Write SDC to disable timing for configure ports +write_sdc_disable_timing_configure_ports --file ./SDC/disable_configure_ports.sdc + # Finish and exit OpenFPGA exit diff --git a/openfpga_flow/OpenFPGAShellScripts/implicit_verilog_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/implicit_verilog_example_script.openfpga index aa4ea30ad..579e113a9 100644 --- a/openfpga_flow/OpenFPGAShellScripts/implicit_verilog_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/implicit_verilog_example_script.openfpga @@ -55,6 +55,9 @@ write_verilog_testbench --file ./SRC --reference_benchmark_file_path ${REFERENCE # - Turn on every options here write_pnr_sdc --file ./SDC +# Write SDC to disable timing for configure ports +write_sdc_disable_timing_configure_ports --file ./SDC/disable_configure_ports.sdc + # Write the SDC to run timing analysis for a mapped FPGA fabric write_analysis_sdc --file ./SDC_analysis diff --git a/openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga index 71d3ee294..3293979ea 100644 --- a/openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga @@ -59,6 +59,9 @@ write_verilog_testbench --file ./SRC --reference_benchmark_file_path ./${REFEREN # - Turn on every options here write_pnr_sdc --file ./SDC +# Write SDC to disable timing for configure ports +write_sdc_disable_timing_configure_ports --file ./SDC/disable_configure_ports.sdc + # Write the SDC to run timing analysis for a mapped FPGA fabric write_analysis_sdc --file ./SDC_analysis diff --git a/openfpga_flow/OpenFPGAShellScripts/sdc_time_unit_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/sdc_time_unit_example_script.openfpga index 39c4d1c70..b4b6ff5ef 100644 --- a/openfpga_flow/OpenFPGAShellScripts/sdc_time_unit_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/sdc_time_unit_example_script.openfpga @@ -55,6 +55,9 @@ write_verilog_testbench --file ./SRC --reference_benchmark_file_path ${REFERENCE # - Turn on every options here write_pnr_sdc --time_unit ps --file ./SDC +# Write SDC to disable timing for configure ports +write_sdc_disable_timing_configure_ports --file ./SDC/disable_configure_ports.sdc + # Write the SDC to run timing analysis for a mapped FPGA fabric write_analysis_sdc --time_unit ps --file ./SDC_analysis From e9ceedb01ba1bcaade45c24520196f45314620ba Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 14 May 2020 19:59:39 -0600 Subject: [PATCH 541/645] use constant openfpga context in SDC generator --- openfpga/src/base/openfpga_sdc.cpp | 4 ++-- openfpga/src/base/openfpga_sdc.h | 4 ++-- openfpga/src/base/openfpga_sdc_command.cpp | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openfpga/src/base/openfpga_sdc.cpp b/openfpga/src/base/openfpga_sdc.cpp index bba115d3c..148f5414b 100644 --- a/openfpga/src/base/openfpga_sdc.cpp +++ b/openfpga/src/base/openfpga_sdc.cpp @@ -28,7 +28,7 @@ namespace openfpga { /******************************************************************** * A wrapper function to call the PnR SDC generator of FPGA-SDC *******************************************************************/ -int write_pnr_sdc(OpenfpgaContext& openfpga_ctx, +int write_pnr_sdc(const OpenfpgaContext& openfpga_ctx, const Command& cmd, const CommandContext& cmd_context) { CommandOptionId opt_output_dir = cmd.option("file"); @@ -168,7 +168,7 @@ int write_sdc_disable_timing_configure_ports(const OpenfpgaContext& openfpga_ctx /******************************************************************** * A wrapper function to call the analysis SDC generator of FPGA-SDC *******************************************************************/ -int write_analysis_sdc(OpenfpgaContext& openfpga_ctx, +int write_analysis_sdc(const OpenfpgaContext& openfpga_ctx, const Command& cmd, const CommandContext& cmd_context) { CommandOptionId opt_output_dir = cmd.option("file"); diff --git a/openfpga/src/base/openfpga_sdc.h b/openfpga/src/base/openfpga_sdc.h index 03f4af03f..2dd97b72a 100644 --- a/openfpga/src/base/openfpga_sdc.h +++ b/openfpga/src/base/openfpga_sdc.h @@ -15,7 +15,7 @@ /* begin namespace openfpga */ namespace openfpga { -int write_pnr_sdc(OpenfpgaContext& openfpga_ctx, +int write_pnr_sdc(const OpenfpgaContext& openfpga_ctx, const Command& cmd, const CommandContext& cmd_context); int write_configuration_chain_sdc(const OpenfpgaContext& openfpga_ctx, @@ -24,7 +24,7 @@ int write_configuration_chain_sdc(const OpenfpgaContext& openfpga_ctx, int write_sdc_disable_timing_configure_ports(const OpenfpgaContext& openfpga_ctx, const Command& cmd, const CommandContext& cmd_context); -int write_analysis_sdc(OpenfpgaContext& openfpga_ctx, +int write_analysis_sdc(const OpenfpgaContext& openfpga_ctx, const Command& cmd, const CommandContext& cmd_context); } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_sdc_command.cpp b/openfpga/src/base/openfpga_sdc_command.cpp index 17910a711..bf207c33f 100644 --- a/openfpga/src/base/openfpga_sdc_command.cpp +++ b/openfpga/src/base/openfpga_sdc_command.cpp @@ -72,7 +72,7 @@ ShellCommandId add_openfpga_write_pnr_sdc_command(openfpga::Shell Date: Thu, 14 May 2020 20:02:42 -0600 Subject: [PATCH 542/645] use error code in read_arch command --- libopenfpga/libarchopenfpga/src/check_circuit_library.cpp | 6 +++--- libopenfpga/libarchopenfpga/src/check_circuit_library.h | 2 +- openfpga/src/base/openfpga_read_arch.cpp | 5 +++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp b/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp index 0abc16a0e..f5392a2e4 100644 --- a/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp +++ b/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp @@ -447,7 +447,7 @@ size_t check_circuit_library_ports(const CircuitLibrary& circuit_lib) { * 9. LUT must have at least an input, an output and a SRAM ports * 10. We must have default circuit models for these types: MUX, channel wires and wires ***********************************************************************/ -void check_circuit_library(const CircuitLibrary& circuit_lib) { +bool check_circuit_library(const CircuitLibrary& circuit_lib) { size_t num_err = 0; vtr::ScopedStartFinishTimer timer("Check circuit library"); @@ -545,10 +545,10 @@ void check_circuit_library(const CircuitLibrary& circuit_lib) { if (0 < num_err) { VTR_LOG("Finished checking circuit library with %d errors!\n", num_err); - exit(1); + return false; } VTR_LOG("Checking circuit library passed.\n"); - return; + return true; } diff --git a/libopenfpga/libarchopenfpga/src/check_circuit_library.h b/libopenfpga/libarchopenfpga/src/check_circuit_library.h index 7a056d796..a6690f60c 100644 --- a/libopenfpga/libarchopenfpga/src/check_circuit_library.h +++ b/libopenfpga/libarchopenfpga/src/check_circuit_library.h @@ -43,6 +43,6 @@ size_t check_sram_circuit_model_ports(const CircuitLibrary& circuit_lib, const CircuitModelId& circuit_model, const bool& check_blwl); -void check_circuit_library(const CircuitLibrary& circuit_lib); +bool check_circuit_library(const CircuitLibrary& circuit_lib); #endif diff --git a/openfpga/src/base/openfpga_read_arch.cpp b/openfpga/src/base/openfpga_read_arch.cpp index 214af1612..ee0670268 100644 --- a/openfpga/src/base/openfpga_read_arch.cpp +++ b/openfpga/src/base/openfpga_read_arch.cpp @@ -46,9 +46,10 @@ int read_arch(OpenfpgaContext& openfpga_context, * 2. Technology library (TODO) * 3. Simulation settings (TODO) */ - check_circuit_library(openfpga_context.arch().circuit_lib); + if (false == check_circuit_library(openfpga_context.arch().circuit_lib)) { + return CMD_EXEC_FATAL_ERROR; + } - /* TODO: should identify the error code from internal function execution */ return CMD_EXEC_SUCCESS; } From 6177921d4c1264c1033451f8b670e75c2fbbd924 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 17 May 2020 19:31:46 -0600 Subject: [PATCH 543/645] bug fixed in configure port disable timing. Now we disable the right ports of LUTs --- .../fpga_sdc/configure_port_sdc_writer.cpp | 87 +++++++++++++++++++ openfpga/src/fpga_sdc/sdc_mux_utils.cpp | 5 ++ 2 files changed, 92 insertions(+) diff --git a/openfpga/src/fpga_sdc/configure_port_sdc_writer.cpp b/openfpga/src/fpga_sdc/configure_port_sdc_writer.cpp index ee9c83667..a2a04f003 100644 --- a/openfpga/src/fpga_sdc/configure_port_sdc_writer.cpp +++ b/openfpga/src/fpga_sdc/configure_port_sdc_writer.cpp @@ -35,6 +35,76 @@ /* begin namespace openfpga */ namespace openfpga { +/******************************************************************** + * Break combinational loops in FPGA fabric, which mainly come from + * Look-Up Table programmable modules + * To handle this, we disable the timing at configuration ports + * + * Return code: + * 0: success + * 1: fatal error occurred + *******************************************************************/ +static +int print_sdc_disable_lut_configure_ports(std::fstream& fp, + const bool& flatten_names, + const CircuitLibrary& circuit_lib, + const ModuleManager& module_manager, + const ModuleId& top_module) { + + if (false == valid_file_stream(fp)) { + return CMD_EXEC_FATAL_ERROR; + } + + /* Iterate over the MUX modules */ + for (const CircuitModelId& model : circuit_lib.models()) { + + /* Only care LUTs */ + if (CIRCUIT_MODEL_LUT != circuit_lib.model_type(model)) { + continue; + } + + std::string programmable_module_name = circuit_lib.model_name(model); + + /* Find the module name in module manager */ + ModuleId programmable_module = module_manager.find_module(programmable_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(programmable_module)); + + /* Go recursively in the module manager, + * starting from the top-level module: instance id of the top-level module is 0 by default + * Disable all the outputs of child modules that matches the mux_module id + */ + for (const CircuitPortId& sram_port : circuit_lib.model_ports_by_type(model, CIRCUIT_MODEL_PORT_SRAM)) { + const std::string& sram_port_name = circuit_lib.port_lib_name(sram_port); + VTR_ASSERT(true == module_manager.valid_module_port_id(programmable_module, module_manager.find_module_port(programmable_module, sram_port_name))); + if (CMD_EXEC_FATAL_ERROR == + rec_print_sdc_disable_timing_for_module_ports(fp, + flatten_names, + module_manager, + top_module, + programmable_module, + format_dir_path(module_manager.module_name(top_module)), + sram_port_name)) { + return CMD_EXEC_FATAL_ERROR; + } + + const std::string& sram_inv_port_name = circuit_lib.port_lib_name(sram_port) + "_inv"; + VTR_ASSERT(true == module_manager.valid_module_port_id(programmable_module, module_manager.find_module_port(programmable_module, sram_inv_port_name))); + if (CMD_EXEC_FATAL_ERROR == + rec_print_sdc_disable_timing_for_module_ports(fp, + flatten_names, + module_manager, + top_module, + programmable_module, + format_dir_path(module_manager.module_name(top_module)), + sram_inv_port_name)) { + return CMD_EXEC_FATAL_ERROR; + } + } + } + + return CMD_EXEC_SUCCESS; +} + /******************************************************************** * Break combinational loops in FPGA fabric, which mainly come from * non-MUX programmable modules @@ -63,6 +133,11 @@ int print_sdc_disable_non_mux_circuit_configure_ports(std::fstream& fp, continue; } + /* Skip LUTs, they are handled in another function */ + if (CIRCUIT_MODEL_LUT == circuit_lib.model_type(model)) { + continue; + } + /* We care programmable circuit models only */ if (0 == circuit_lib.model_ports_by_type(model, CIRCUIT_MODEL_PORT_SRAM).size()) { continue; @@ -128,6 +203,18 @@ int print_sdc_disable_timing_configure_ports(const std::string& sdc_fname, std::string top_module_name = generate_fpga_top_module_name(); ModuleId top_module = module_manager.find_module(top_module_name); VTR_ASSERT(true == module_manager.valid_module_id(top_module)); + + /* Disable timing for the configure ports of all the Look-Up Tables */ + VTR_LOG("Write disable timing for Look-Up Tables..."); + if (CMD_EXEC_FATAL_ERROR == print_sdc_disable_lut_configure_ports(fp, + flatten_names, + circuit_lib, + module_manager, + top_module)) { + VTR_LOG("Fatal errors occurred\n"); + return CMD_EXEC_FATAL_ERROR; + } + VTR_LOG("Done\n"); /* Disable timing for the configure ports of all the routing multiplexer */ VTR_LOG("Write disable timing for routing multiplexers..."); diff --git a/openfpga/src/fpga_sdc/sdc_mux_utils.cpp b/openfpga/src/fpga_sdc/sdc_mux_utils.cpp index b72d17fdf..c0fd21fca 100644 --- a/openfpga/src/fpga_sdc/sdc_mux_utils.cpp +++ b/openfpga/src/fpga_sdc/sdc_mux_utils.cpp @@ -114,6 +114,11 @@ int print_sdc_disable_routing_multiplexer_configure_ports(std::fstream& fp, /* Iterate over the MUX modules */ for (const MuxId& mux_id : mux_lib.muxes()) { const CircuitModelId& mux_model = mux_lib.mux_circuit_model(mux_id); + + /* Skip LUTs, we only care about multiplexers here */ + if (CIRCUIT_MODEL_MUX != circuit_lib.model_type(mux_model)) { + continue; + } const MuxGraph& mux_graph = mux_lib.mux_graph(mux_id); std::string mux_module_name = generate_mux_subckt_name(circuit_lib, mux_model, From 8915d10d2790e296525f9c9c16c11b3a606978a6 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 17 May 2020 19:36:57 -0600 Subject: [PATCH 544/645] add verbose output option to configure port disable timing writer --- openfpga/src/base/openfpga_sdc.cpp | 4 +++- openfpga/src/base/openfpga_sdc_command.cpp | 3 +++ .../fpga_sdc/configure_port_sdc_writer.cpp | 24 +++++++++++-------- .../src/fpga_sdc/configure_port_sdc_writer.h | 3 ++- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/openfpga/src/base/openfpga_sdc.cpp b/openfpga/src/base/openfpga_sdc.cpp index 148f5414b..2d44520db 100644 --- a/openfpga/src/base/openfpga_sdc.cpp +++ b/openfpga/src/base/openfpga_sdc.cpp @@ -149,6 +149,7 @@ int write_sdc_disable_timing_configure_ports(const OpenfpgaContext& openfpga_ctx /* Get command options */ CommandOptionId opt_output_dir = cmd.option("file"); CommandOptionId opt_flatten_names = cmd.option("flatten_names"); + CommandOptionId opt_verbose = cmd.option("verbose"); std::string sdc_dir_path = format_dir_path(cmd_context.option_value(cmd, opt_output_dir)); @@ -158,7 +159,8 @@ int write_sdc_disable_timing_configure_ports(const OpenfpgaContext& openfpga_ctx cmd_context.option_enable(cmd, opt_flatten_names), openfpga_ctx.mux_lib(), openfpga_ctx.arch().circuit_lib, - openfpga_ctx.module_graph())) { + openfpga_ctx.module_graph(), + cmd_context.option_enable(cmd, opt_verbose))) { return CMD_EXEC_FATAL_ERROR; } diff --git a/openfpga/src/base/openfpga_sdc_command.cpp b/openfpga/src/base/openfpga_sdc_command.cpp index bf207c33f..5459b7fec 100644 --- a/openfpga/src/base/openfpga_sdc_command.cpp +++ b/openfpga/src/base/openfpga_sdc_command.cpp @@ -138,6 +138,9 @@ ShellCommandId add_openfpga_write_sdc_disable_timing_configure_ports_command(ope /* Add an option '--flatten_name' */ shell_cmd.add_option("flatten_names", false, "Use flatten names (no wildcards) in SDC files"); + /* Add an option '--verbose' */ + shell_cmd.add_option("verbose", false, "Enable verbose outputs"); + /* Add command 'write_configuration_chain_sdc' to the Shell */ ShellCommandId shell_cmd_id = shell.add_command(shell_cmd, "generate SDC files to disable timing for configure ports across FPGA fabric"); shell.set_command_class(shell_cmd_id, cmd_class_id); diff --git a/openfpga/src/fpga_sdc/configure_port_sdc_writer.cpp b/openfpga/src/fpga_sdc/configure_port_sdc_writer.cpp index a2a04f003..e0508d3f4 100644 --- a/openfpga/src/fpga_sdc/configure_port_sdc_writer.cpp +++ b/openfpga/src/fpga_sdc/configure_port_sdc_writer.cpp @@ -183,7 +183,8 @@ int print_sdc_disable_timing_configure_ports(const std::string& sdc_fname, const bool& flatten_names, const MuxLibrary& mux_lib, const CircuitLibrary& circuit_lib, - const ModuleManager& module_manager) { + const ModuleManager& module_manager, + const bool& verbose) { /* Create the directory */ create_directory(find_path_dir_name(sdc_fname)); @@ -205,41 +206,44 @@ int print_sdc_disable_timing_configure_ports(const std::string& sdc_fname, VTR_ASSERT(true == module_manager.valid_module_id(top_module)); /* Disable timing for the configure ports of all the Look-Up Tables */ - VTR_LOG("Write disable timing for Look-Up Tables..."); + VTR_LOGV(verbose, "Write disable timing for Look-Up Tables..."); if (CMD_EXEC_FATAL_ERROR == print_sdc_disable_lut_configure_ports(fp, flatten_names, circuit_lib, module_manager, top_module)) { - VTR_LOG("Fatal errors occurred\n"); + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Fatal errors occurred\n"); return CMD_EXEC_FATAL_ERROR; } - VTR_LOG("Done\n"); + VTR_LOGV(verbose, "Done\n"); /* Disable timing for the configure ports of all the routing multiplexer */ - VTR_LOG("Write disable timing for routing multiplexers..."); + VTR_LOGV(verbose, "Write disable timing for routing multiplexers..."); if (CMD_EXEC_FATAL_ERROR == print_sdc_disable_routing_multiplexer_configure_ports(fp, flatten_names, mux_lib, circuit_lib, module_manager, top_module)) { - VTR_LOG("Fatal errors occurred\n"); + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Fatal errors occurred\n"); return CMD_EXEC_FATAL_ERROR; } - VTR_LOG("Done\n"); + VTR_LOGV(verbose, "Done\n"); /* Disable timing for the other programmable circuit models */ - VTR_LOG("Write disable timing for other programmable modules..."); + VTR_LOGV(verbose, "Write disable timing for other programmable modules..."); if (CMD_EXEC_FATAL_ERROR == print_sdc_disable_non_mux_circuit_configure_ports(fp, flatten_names, circuit_lib, module_manager, top_module)) { - VTR_LOG("Fatal errors occurred\n"); + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Fatal errors occurred\n"); return CMD_EXEC_FATAL_ERROR; } - VTR_LOG("Done\n"); + VTR_LOGV(verbose, "Done\n"); /* Close file handler */ fp.close(); diff --git a/openfpga/src/fpga_sdc/configure_port_sdc_writer.h b/openfpga/src/fpga_sdc/configure_port_sdc_writer.h index ef66af749..b8b4791d6 100644 --- a/openfpga/src/fpga_sdc/configure_port_sdc_writer.h +++ b/openfpga/src/fpga_sdc/configure_port_sdc_writer.h @@ -19,7 +19,8 @@ int print_sdc_disable_timing_configure_ports(const std::string& sdc_fname, const bool& flatten_names, const MuxLibrary& mux_lib, const CircuitLibrary& circuit_lib, - const ModuleManager& module_manager); + const ModuleManager& module_manager, + const bool& verbose); } /* end namespace openfpga */ From c2a81c76e1a1ba50d06a21d21db2b2e16db6e68f Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 17 May 2020 19:38:13 -0600 Subject: [PATCH 545/645] update doc for new options --- docs/source/openfpga_shell/openfpga_commands.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source/openfpga_shell/openfpga_commands.rst b/docs/source/openfpga_shell/openfpga_commands.rst index 4e5770ebd..909a6416f 100644 --- a/docs/source/openfpga_shell/openfpga_commands.rst +++ b/docs/source/openfpga_shell/openfpga_commands.rst @@ -245,6 +245,8 @@ FPGA-SDC - ``--flatten_names`` Use flatten names (no wildcards) in SDC files + - ``--verbose`` Show verbose log + .. option:: write_analysis_sdc Write the SDC to run timing analysis for a mapped FPGA fabric From e089b0ef22412d13eed645519369eb471aacb1f3 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 17 May 2020 19:45:27 -0600 Subject: [PATCH 546/645] use constant string for inverted port naming --- openfpga/src/base/openfpga_reserved_words.h | 3 +++ openfpga/src/fabric/build_lut_modules.cpp | 7 ++++--- openfpga/src/fabric/build_mux_modules.cpp | 6 +++--- openfpga/src/fpga_sdc/configure_port_sdc_writer.cpp | 3 ++- openfpga/src/fpga_sdc/sdc_mux_utils.cpp | 3 ++- openfpga/src/utils/module_manager_utils.cpp | 5 +++-- 6 files changed, 17 insertions(+), 10 deletions(-) diff --git a/openfpga/src/base/openfpga_reserved_words.h b/openfpga/src/base/openfpga_reserved_words.h index f04490c0d..be47315ba 100644 --- a/openfpga/src/base/openfpga_reserved_words.h +++ b/openfpga/src/base/openfpga_reserved_words.h @@ -30,6 +30,9 @@ constexpr char* GRID_MUX_INSTANCE_PREFIX = "mux_"; constexpr char* SWITCH_BLOCK_MUX_INSTANCE_PREFIX = "mux_"; constexpr char* CONNECTION_BLOCK_MUX_INSTANCE_PREFIX = "mux_"; +/* Inverted port naming */ +constexpr char* INV_PORT_POSTFIX = "_inv"; + /* Bitstream file strings */ constexpr char* BITSTREAM_XML_FILE_NAME_POSTFIX = "_bitstream.xml"; diff --git a/openfpga/src/fabric/build_lut_modules.cpp b/openfpga/src/fabric/build_lut_modules.cpp index 2ba688e4d..477308e08 100644 --- a/openfpga/src/fabric/build_lut_modules.cpp +++ b/openfpga/src/fabric/build_lut_modules.cpp @@ -10,6 +10,7 @@ #include "vtr_log.h" #include "vtr_time.h" +#include "openfpga_reserved_words.h" #include "openfpga_naming.h" #include "circuit_library_utils.h" #include "module_manager.h" @@ -106,7 +107,7 @@ void build_lut_module(ModuleManager& module_manager, for (const auto& port : lut_regular_sram_ports) { BasicPort mem_port(circuit_lib.port_prefix(port), circuit_lib.port_size(port)); module_manager.add_port(lut_module, mem_port, ModuleManager::MODULE_INPUT_PORT); - BasicPort mem_inv_port(std::string(circuit_lib.port_prefix(port) + "_inv"), circuit_lib.port_size(port)); + BasicPort mem_inv_port(std::string(circuit_lib.port_prefix(port) + INV_PORT_POSTFIX), circuit_lib.port_size(port)); module_manager.add_port(lut_module, mem_inv_port, ModuleManager::MODULE_INPUT_PORT); } @@ -114,7 +115,7 @@ void build_lut_module(ModuleManager& module_manager, for (const auto& port : lut_mode_select_sram_ports) { BasicPort mem_port(circuit_lib.port_prefix(port), circuit_lib.port_size(port)); module_manager.add_port(lut_module, mem_port, ModuleManager::MODULE_INPUT_PORT); - BasicPort mem_inv_port(std::string(circuit_lib.port_prefix(port) + "_inv"), circuit_lib.port_size(port)); + BasicPort mem_inv_port(std::string(circuit_lib.port_prefix(port) + INV_PORT_POSTFIX), circuit_lib.port_size(port)); module_manager.add_port(lut_module, mem_inv_port, ModuleManager::MODULE_INPUT_PORT); } @@ -336,7 +337,7 @@ void build_lut_module(ModuleManager& module_manager, module_manager.add_module_net_sink(lut_module, lut_mux_sram_nets[pin], lut_mux_module, lut_mux_instance, lut_mux_sram_port_id, pin); } - ModulePortId lut_mux_sram_inv_port_id = module_manager.find_module_port(lut_mux_module, std::string(circuit_lib.port_prefix(lut_regular_sram_ports[0]) + "_inv")); + ModulePortId lut_mux_sram_inv_port_id = module_manager.find_module_port(lut_mux_module, std::string(circuit_lib.port_prefix(lut_regular_sram_ports[0]) + INV_PORT_POSTFIX)); BasicPort lut_mux_sram_inv_port = module_manager.module_port(lut_mux_module, lut_mux_sram_inv_port_id); VTR_ASSERT(lut_mux_sram_inv_port.get_width() == lut_mux_sram_inv_nets.size()); /* Wire the port to lut_mux_sram_net */ diff --git a/openfpga/src/fabric/build_mux_modules.cpp b/openfpga/src/fabric/build_mux_modules.cpp index a28eac264..6bc2a55d5 100644 --- a/openfpga/src/fabric/build_mux_modules.cpp +++ b/openfpga/src/fabric/build_mux_modules.cpp @@ -977,7 +977,7 @@ void build_mux_module_local_encoders_and_memory_nets(ModuleManager& module_manag /* Add mem and mem_inv nets here */ size_t mem_inv_net_cnt = 0; for (const auto& port : mux_sram_ports) { - ModulePortId mem_inv_port_id = module_manager.find_module_port(mux_module, std::string(circuit_lib.port_prefix(port) + "_inv")); + ModulePortId mem_inv_port_id = module_manager.find_module_port(mux_module, std::string(circuit_lib.port_prefix(port) + INV_PORT_POSTFIX)); BasicPort mem_inv_port = module_manager.module_port(mux_module, mem_inv_port_id); for (const size_t& pin : mem_inv_port.pins()) { MuxMemId mem_id = MuxMemId(mem_inv_net_cnt); @@ -998,7 +998,7 @@ void build_mux_module_local_encoders_and_memory_nets(ModuleManager& module_manag /* Local port to record the LSB and MSB of each level, here, we deposite (0, 0) */ ModulePortId mux_module_sram_port_id = module_manager.find_module_port(mux_module, circuit_lib.port_prefix(mux_sram_ports[0])); - ModulePortId mux_module_sram_inv_port_id = module_manager.find_module_port(mux_module, circuit_lib.port_prefix(mux_sram_ports[0]) + "_inv"); + ModulePortId mux_module_sram_inv_port_id = module_manager.find_module_port(mux_module, circuit_lib.port_prefix(mux_sram_ports[0]) + INV_PORT_POSTFIX); BasicPort lvl_addr_port(circuit_lib.port_prefix(mux_sram_ports[0]), 0); BasicPort lvl_data_port(decoder_data_port.get_name(), 0); BasicPort lvl_data_inv_port(decoder_data_inv_port.get_name(), 0); @@ -1181,7 +1181,7 @@ void build_cmos_mux_module(ModuleManager& module_manager, for (const auto& port : mux_sram_ports) { BasicPort mem_port(circuit_lib.port_prefix(port), num_mems); module_manager.add_port(mux_module, mem_port, ModuleManager::MODULE_INPUT_PORT); - BasicPort mem_inv_port(std::string(circuit_lib.port_prefix(port) + "_inv"), num_mems); + BasicPort mem_inv_port(std::string(circuit_lib.port_prefix(port) + INV_PORT_POSTFIX), num_mems); module_manager.add_port(mux_module, mem_inv_port, ModuleManager::MODULE_INPUT_PORT); /* Update counter */ sram_port_cnt++; diff --git a/openfpga/src/fpga_sdc/configure_port_sdc_writer.cpp b/openfpga/src/fpga_sdc/configure_port_sdc_writer.cpp index e0508d3f4..42d5c030c 100644 --- a/openfpga/src/fpga_sdc/configure_port_sdc_writer.cpp +++ b/openfpga/src/fpga_sdc/configure_port_sdc_writer.cpp @@ -23,6 +23,7 @@ #include "openfpga_port.h" #include "openfpga_digest.h" +#include "openfpga_reserved_words.h" #include "openfpga_naming.h" #include "circuit_library_utils.h" @@ -87,7 +88,7 @@ int print_sdc_disable_lut_configure_ports(std::fstream& fp, return CMD_EXEC_FATAL_ERROR; } - const std::string& sram_inv_port_name = circuit_lib.port_lib_name(sram_port) + "_inv"; + const std::string& sram_inv_port_name = circuit_lib.port_lib_name(sram_port) + INV_PORT_POSTFIX; VTR_ASSERT(true == module_manager.valid_module_port_id(programmable_module, module_manager.find_module_port(programmable_module, sram_inv_port_name))); if (CMD_EXEC_FATAL_ERROR == rec_print_sdc_disable_timing_for_module_ports(fp, diff --git a/openfpga/src/fpga_sdc/sdc_mux_utils.cpp b/openfpga/src/fpga_sdc/sdc_mux_utils.cpp index c0fd21fca..57fd8f8a1 100644 --- a/openfpga/src/fpga_sdc/sdc_mux_utils.cpp +++ b/openfpga/src/fpga_sdc/sdc_mux_utils.cpp @@ -14,6 +14,7 @@ /* Headers from openfpgautil library */ #include "openfpga_digest.h" +#include "openfpga_reserved_words.h" #include "openfpga_naming.h" #include "mux_utils.h" @@ -147,7 +148,7 @@ int print_sdc_disable_routing_multiplexer_configure_ports(std::fstream& fp, return CMD_EXEC_FATAL_ERROR; } - const std::string& mux_sram_inv_port_name = circuit_lib.port_lib_name(mux_sram_port) + "_inv"; + const std::string& mux_sram_inv_port_name = circuit_lib.port_lib_name(mux_sram_port) + INV_PORT_POSTFIX; VTR_ASSERT(true == module_manager.valid_module_port_id(mux_module, module_manager.find_module_port(mux_module, mux_sram_inv_port_name))); if (CMD_EXEC_FATAL_ERROR == rec_print_sdc_disable_timing_for_module_ports(fp, diff --git a/openfpga/src/utils/module_manager_utils.cpp b/openfpga/src/utils/module_manager_utils.cpp index a57e97261..b1408dcad 100644 --- a/openfpga/src/utils/module_manager_utils.cpp +++ b/openfpga/src/utils/module_manager_utils.cpp @@ -13,6 +13,7 @@ /* Headers from openfpgautil library */ #include "openfpga_port.h" +#include "openfpga_reserved_words.h" #include "openfpga_naming.h" #include "memory_utils.h" #include "pb_type_utils.h" @@ -612,11 +613,11 @@ void add_module_nets_between_logic_and_memory_sram_bus(ModuleManager& module_man std::vector logic_model_sramb_port_names; /* Regular sram port goes first */ for (CircuitPortId regular_sram_port : find_circuit_regular_sram_ports(circuit_lib, logic_model)) { - logic_model_sramb_port_names.push_back(circuit_lib.port_prefix(regular_sram_port) + std::string("_inv")); + logic_model_sramb_port_names.push_back(circuit_lib.port_prefix(regular_sram_port) + std::string(INV_PORT_POSTFIX)); } /* Mode-select sram port goes first */ for (CircuitPortId mode_select_sram_port : find_circuit_mode_select_sram_ports(circuit_lib, logic_model)) { - logic_model_sramb_port_names.push_back(circuit_lib.port_prefix(mode_select_sram_port) + std::string("_inv")); + logic_model_sramb_port_names.push_back(circuit_lib.port_prefix(mode_select_sram_port) + std::string(INV_PORT_POSTFIX)); } /* Find the port ids in the memory */ std::vector logic_module_sramb_port_ids; From 6f133bd009b2afa204e5f005ad4177b87ff79316 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 20 May 2020 18:49:21 -0600 Subject: [PATCH 547/645] bug fix in packable mode support --- vpr/src/pack/cluster_router.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/vpr/src/pack/cluster_router.cpp b/vpr/src/pack/cluster_router.cpp index 7f2cd37d9..49a60a805 100644 --- a/vpr/src/pack/cluster_router.cpp +++ b/vpr/src/pack/cluster_router.cpp @@ -208,6 +208,15 @@ static bool check_edge_for_route_conflicts(std::unordered_mappb_type->modes[mode_of_edge]; auto result = mode_map->insert(std::make_pair(pb_graph_node, mode)); + + /* Xifan Tang: Insert unpackable mode to the illegal mode list */ + if (false == mode->packable) { + if (std::find(pb_graph_node->illegal_modes.begin(), pb_graph_node->illegal_modes.end(), mode->index) == pb_graph_node->illegal_modes.end()) { + pb_graph_node->illegal_modes.push_back(mode->index); + } + return true; + } + if (!result.second) { if (result.first->second != mode) { std::cout << vtr::string_fmt("Differing modes for block. Got %s mode, while previously was %s for interconnect %s.", @@ -1173,10 +1182,6 @@ static void expand_node_all_modes(t_lb_router_data* router_data, t_expansion_nod if (cur_mode != -1 && mode != cur_mode) { continue; } - /* Xifan Tang: Do not expand in unpackable modes */ - if (false == pin->parent_node->pb_type->parent_mode->packable) { - continue; - } /* Check whether a mode is illegal. If it is then the node will not be expanded */ bool is_illegal = false; From bba476fef499e50801c65ecf190e7e6c95f2a089 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 22 May 2020 14:40:05 -0600 Subject: [PATCH 548/645] add explicit port mapping support to Verilog testbench generator --- openfpga/src/base/openfpga_verilog.cpp | 2 ++ .../src/base/openfpga_verilog_command.cpp | 3 +++ openfpga/src/fpga_verilog/verilog_api.cpp | 9 +++++--- .../verilog_formal_random_top_testbench.cpp | 21 ++++++++++++------- .../verilog_formal_random_top_testbench.h | 3 ++- .../verilog_preconfig_top_module.cpp | 6 ++++-- .../verilog_preconfig_top_module.h | 3 ++- .../verilog_testbench_options.cpp | 9 ++++++++ .../fpga_verilog/verilog_testbench_options.h | 3 +++ .../fpga_verilog/verilog_testbench_utils.cpp | 6 ++++-- .../fpga_verilog/verilog_testbench_utils.h | 3 ++- .../fpga_verilog/verilog_top_testbench.cpp | 14 ++++++++----- .../src/fpga_verilog/verilog_top_testbench.h | 3 ++- ...onfiguration_chain_example_script.openfpga | 2 +- ...uplicated_grid_pin_example_script.openfpga | 2 +- .../example_script.openfpga | 2 +- .../flatten_routing_example_script.openfpga | 2 +- ...generate_testbench_example_script.openfpga | 2 +- .../sdc_time_unit_example_script.openfpga | 2 +- 19 files changed, 68 insertions(+), 29 deletions(-) diff --git a/openfpga/src/base/openfpga_verilog.cpp b/openfpga/src/base/openfpga_verilog.cpp index 1d47055df..481bd2866 100644 --- a/openfpga/src/base/openfpga_verilog.cpp +++ b/openfpga/src/base/openfpga_verilog.cpp @@ -69,6 +69,7 @@ int write_verilog_testbench(OpenfpgaContext& openfpga_ctx, CommandOptionId opt_print_formal_verification_top_netlist = cmd.option("print_formal_verification_top_netlist"); CommandOptionId opt_print_preconfig_top_testbench = cmd.option("print_preconfig_top_testbench"); CommandOptionId opt_print_simulation_ini = cmd.option("print_simulation_ini"); + CommandOptionId opt_explicit_port_mapping = cmd.option("explicit_port_mapping"); CommandOptionId opt_verbose = cmd.option("verbose"); /* This is an intermediate data structure which is designed to modularize the FPGA-Verilog @@ -81,6 +82,7 @@ int write_verilog_testbench(OpenfpgaContext& openfpga_ctx, options.set_print_preconfig_top_testbench(cmd_context.option_enable(cmd, opt_print_preconfig_top_testbench)); options.set_print_top_testbench(cmd_context.option_enable(cmd, opt_print_top_testbench)); options.set_print_simulation_ini(cmd_context.option_value(cmd, opt_print_simulation_ini)); + options.set_explicit_port_mapping(cmd_context.option_enable(cmd, opt_explicit_port_mapping)); options.set_verbose_output(cmd_context.option_enable(cmd, opt_verbose)); fpga_verilog_testbench(openfpga_ctx.module_graph(), diff --git a/openfpga/src/base/openfpga_verilog_command.cpp b/openfpga/src/base/openfpga_verilog_command.cpp index 71fad80f0..99ee7a319 100644 --- a/openfpga/src/base/openfpga_verilog_command.cpp +++ b/openfpga/src/base/openfpga_verilog_command.cpp @@ -89,6 +89,9 @@ ShellCommandId add_openfpga_write_verilog_testbench_command(openfpga::Shell(), std::string(FPGA_PORT_POSTFIX), atom_ctx, netlist_annotation, - true); + explicit_port_mapping); print_verilog_comment(fp, std::string("----- End FPGA Fabric Instanication -------")); @@ -190,7 +192,8 @@ void print_verilog_random_top_testbench(const std::string& circuit_name, const std::string& verilog_fname, const AtomContext& atom_ctx, const VprNetlistAnnotation& netlist_annotation, - const SimulationSetting& simulation_parameters) { + const SimulationSetting& simulation_parameters, + const bool& explicit_port_mapping) { std::string timer_message = std::string("Write configuration-skip testbench for FPGA top-level Verilog netlist implemented by '") + circuit_name.c_str() + std::string("'"); /* Start time count */ @@ -214,10 +217,14 @@ void print_verilog_random_top_testbench(const std::string& circuit_name, print_verilog_top_random_testbench_ports(fp, circuit_name, clock_port_names, atom_ctx, netlist_annotation); /* Call defined top-level module */ - print_verilog_random_testbench_fpga_instance(fp, circuit_name, atom_ctx, netlist_annotation); + print_verilog_random_testbench_fpga_instance(fp, circuit_name, + atom_ctx, netlist_annotation, + explicit_port_mapping); /* Call defined benchmark */ - print_verilog_top_random_testbench_benchmark_instance(fp, circuit_name, atom_ctx, netlist_annotation); + print_verilog_top_random_testbench_benchmark_instance(fp, circuit_name, + atom_ctx, netlist_annotation, + explicit_port_mapping); /* Find clock port to be used */ BasicPort clock_port = generate_verilog_testbench_clock_port(clock_port_names, std::string(DEFAULT_CLOCK_NAME)); diff --git a/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.h b/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.h index b12072b70..775bc14d4 100644 --- a/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.h +++ b/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.h @@ -19,7 +19,8 @@ void print_verilog_random_top_testbench(const std::string& circuit_name, const std::string& verilog_fname, const AtomContext& atom_ctx, const VprNetlistAnnotation& netlist_annotation, - const SimulationSetting& simulation_parameters); + const SimulationSetting& simulation_parameters, + const bool& explicit_port_mapping); } /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_preconfig_top_module.cpp b/openfpga/src/fpga_verilog/verilog_preconfig_top_module.cpp index 8e1de5cfc..8e577dfb4 100644 --- a/openfpga/src/fpga_verilog/verilog_preconfig_top_module.cpp +++ b/openfpga/src/fpga_verilog/verilog_preconfig_top_module.cpp @@ -385,7 +385,8 @@ void print_verilog_preconfig_top_module(const ModuleManager& module_manager, const IoLocationMap& io_location_map, const VprNetlistAnnotation& netlist_annotation, const std::string& circuit_name, - const std::string& verilog_fname) { + const std::string& verilog_fname, + const bool& explicit_port_mapping) { std::string timer_message = std::string("Write pre-configured FPGA top-level Verilog netlist for design '") + circuit_name + std::string("'"); /* Start time count */ @@ -414,7 +415,8 @@ void print_verilog_preconfig_top_module(const ModuleManager& module_manager, /* Instanciate FPGA top-level module */ print_verilog_testbench_fpga_instance(fp, module_manager, top_module, - std::string(FORMAL_VERIFICATION_TOP_MODULE_UUT_NAME)); + std::string(FORMAL_VERIFICATION_TOP_MODULE_UUT_NAME), + explicit_port_mapping); /* Find clock ports in benchmark */ std::vector benchmark_clock_port_names = find_atom_netlist_clock_port_names(atom_ctx.nlist, netlist_annotation); diff --git a/openfpga/src/fpga_verilog/verilog_preconfig_top_module.h b/openfpga/src/fpga_verilog/verilog_preconfig_top_module.h index e8efe5f29..9177676fa 100644 --- a/openfpga/src/fpga_verilog/verilog_preconfig_top_module.h +++ b/openfpga/src/fpga_verilog/verilog_preconfig_top_module.h @@ -29,7 +29,8 @@ void print_verilog_preconfig_top_module(const ModuleManager& module_manager, const IoLocationMap& io_location_map, const VprNetlistAnnotation& netlist_annotation, const std::string& circuit_name, - const std::string& verilog_fname); + const std::string& verilog_fname, + const bool& explicit_port_mapping); } /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_testbench_options.cpp b/openfpga/src/fpga_verilog/verilog_testbench_options.cpp index 8cd2969f2..9fc6bfd33 100644 --- a/openfpga/src/fpga_verilog/verilog_testbench_options.cpp +++ b/openfpga/src/fpga_verilog/verilog_testbench_options.cpp @@ -19,6 +19,7 @@ VerilogTestbenchOption::VerilogTestbenchOption() { print_formal_verification_top_netlist_ = false; print_top_testbench_ = false; simulation_ini_path_.clear(); + explicit_port_mapping_ = false; verbose_output_ = false; } @@ -53,6 +54,10 @@ std::string VerilogTestbenchOption::simulation_ini_path() const { return simulation_ini_path_; } +bool VerilogTestbenchOption::explicit_port_mapping() const { + return explicit_port_mapping_; +} + bool VerilogTestbenchOption::verbose_output() const { return verbose_output_; } @@ -97,6 +102,10 @@ void VerilogTestbenchOption::set_print_simulation_ini(const std::string& simulat simulation_ini_path_ = simulation_ini_path; } +void VerilogTestbenchOption::set_explicit_port_mapping(const bool& enabled) { + explicit_port_mapping_ = enabled; +} + void VerilogTestbenchOption::set_verbose_output(const bool& enabled) { verbose_output_ = enabled; } diff --git a/openfpga/src/fpga_verilog/verilog_testbench_options.h b/openfpga/src/fpga_verilog/verilog_testbench_options.h index d18bfa224..2572e3dee 100644 --- a/openfpga/src/fpga_verilog/verilog_testbench_options.h +++ b/openfpga/src/fpga_verilog/verilog_testbench_options.h @@ -29,6 +29,7 @@ class VerilogTestbenchOption { bool print_top_testbench() const; bool print_simulation_ini() const; std::string simulation_ini_path() const; + bool explicit_port_mapping() const; bool verbose_output() const; public: /* Public validator */ bool validate() const; @@ -45,6 +46,7 @@ class VerilogTestbenchOption { void set_print_preconfig_top_testbench(const bool& enabled); void set_print_top_testbench(const bool& enabled); void set_print_simulation_ini(const std::string& simulation_ini_path); + void set_explicit_port_mapping(const bool& enabled); void set_verbose_output(const bool& enabled); private: /* Internal Data */ std::string output_directory_; @@ -54,6 +56,7 @@ class VerilogTestbenchOption { bool print_top_testbench_; /* Print simulation ini is enabled only when the path is not empty */ std::string simulation_ini_path_; + bool explicit_port_mapping_; bool verbose_output_; }; diff --git a/openfpga/src/fpga_verilog/verilog_testbench_utils.cpp b/openfpga/src/fpga_verilog/verilog_testbench_utils.cpp index 8b5c741f1..b733c8ddd 100644 --- a/openfpga/src/fpga_verilog/verilog_testbench_utils.cpp +++ b/openfpga/src/fpga_verilog/verilog_testbench_utils.cpp @@ -30,7 +30,8 @@ namespace openfpga { void print_verilog_testbench_fpga_instance(std::fstream& fp, const ModuleManager& module_manager, const ModuleId& top_module, - const std::string& top_instance_name) { + const std::string& top_instance_name, + const bool& explicit_port_mapping) { /* Validate the file stream */ valid_file_stream(fp); @@ -43,7 +44,8 @@ void print_verilog_testbench_fpga_instance(std::fstream& fp, /* Use explicit port mapping for a clean instanciation */ print_verilog_module_instance(fp, module_manager, top_module, top_instance_name, - port2port_name_map, true); + port2port_name_map, + explicit_port_mapping); /* Add an empty line as a splitter */ fp << std::endl; diff --git a/openfpga/src/fpga_verilog/verilog_testbench_utils.h b/openfpga/src/fpga_verilog/verilog_testbench_utils.h index d51f3b7e8..9ba67f298 100644 --- a/openfpga/src/fpga_verilog/verilog_testbench_utils.h +++ b/openfpga/src/fpga_verilog/verilog_testbench_utils.h @@ -26,7 +26,8 @@ constexpr char* OPENFPGA_BENCHMARK_OUT_PORT_PREFIX = "out_"; void print_verilog_testbench_fpga_instance(std::fstream& fp, const ModuleManager& module_manager, const ModuleId& top_module, - const std::string& top_instance_name); + const std::string& top_instance_name, + const bool& explicit_port_mapping); void print_verilog_testbench_benchmark_instance(std::fstream& fp, const std::string& module_name, diff --git a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp index 6a647de35..dc4856cb3 100644 --- a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp +++ b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp @@ -446,7 +446,8 @@ static void print_verilog_top_testbench_benchmark_instance(std::fstream& fp, const std::string& reference_verilog_top_name, const AtomContext& atom_ctx, - const VprNetlistAnnotation& netlist_annotation) { + const VprNetlistAnnotation& netlist_annotation, + const bool& explicit_port_mapping) { /* Validate the file stream */ valid_file_stream(fp); @@ -468,7 +469,7 @@ void print_verilog_top_testbench_benchmark_instance(std::fstream& fp, prefix_to_remove, std::string(TOP_TESTBENCH_REFERENCE_OUTPUT_POSTFIX), atom_ctx, netlist_annotation, - true); + explicit_port_mapping); print_verilog_comment(fp, std::string("----- End reference Benchmark Instanication -------")); @@ -803,7 +804,8 @@ void print_verilog_top_testbench(const ModuleManager& module_manager, const VprNetlistAnnotation& netlist_annotation, const std::string& circuit_name, const std::string& verilog_fname, - const SimulationSetting& simulation_parameters) { + const SimulationSetting& simulation_parameters, + const bool& explicit_port_mapping) { std::string timer_message = std::string("Write autocheck testbench for FPGA top-level Verilog netlist for '") + circuit_name + std::string("'"); @@ -856,7 +858,8 @@ void print_verilog_top_testbench(const ModuleManager& module_manager, /* Instanciate FPGA top-level module */ print_verilog_testbench_fpga_instance(fp, module_manager, top_module, - std::string(TOP_TESTBENCH_FPGA_INSTANCE_NAME)); + std::string(TOP_TESTBENCH_FPGA_INSTANCE_NAME), + explicit_port_mapping); /* Connect I/Os to benchmark I/Os or constant driver */ print_verilog_testbench_connect_fpga_ios(fp, module_manager, top_module, @@ -870,7 +873,8 @@ void print_verilog_top_testbench(const ModuleManager& module_manager, print_verilog_top_testbench_benchmark_instance(fp, circuit_name, atom_ctx, - netlist_annotation); + netlist_annotation, + explicit_port_mapping); /* Print tasks used for loading bitstreams */ print_verilog_top_testbench_load_bitstream_task(fp, sram_orgz_type); diff --git a/openfpga/src/fpga_verilog/verilog_top_testbench.h b/openfpga/src/fpga_verilog/verilog_top_testbench.h index c0ad77c51..39a2024d1 100644 --- a/openfpga/src/fpga_verilog/verilog_top_testbench.h +++ b/openfpga/src/fpga_verilog/verilog_top_testbench.h @@ -33,7 +33,8 @@ void print_verilog_top_testbench(const ModuleManager& module_manager, const VprNetlistAnnotation& netlist_annotation, const std::string& circuit_name, const std::string& verilog_fname, - const SimulationSetting& simulation_parameters); + const SimulationSetting& simulation_parameters, + const bool& explicit_port_mapping); } /* end namespace openfpga */ diff --git a/openfpga_flow/OpenFPGAShellScripts/configuration_chain_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/configuration_chain_example_script.openfpga index 8b523f4ae..11f3ed295 100644 --- a/openfpga_flow/OpenFPGAShellScripts/configuration_chain_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/configuration_chain_example_script.openfpga @@ -49,7 +49,7 @@ write_fabric_verilog --file ./SRC --explicit_port_mapping --include_timing --inc # - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA # - Enable pre-configured top-level testbench which is a fast verification skipping programming phase # - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts -write_verilog_testbench --file ./SRC --reference_benchmark_file_path ${REFERENCE_VERILOG_TESTBENCH} --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini ./SimulationDeck/simulation_deck.ini +write_verilog_testbench --file ./SRC --reference_benchmark_file_path ${REFERENCE_VERILOG_TESTBENCH} --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini ./SimulationDeck/simulation_deck.ini --explicit_port_mapping # Write the SDC files for PnR backend # - Turn on every options here diff --git a/openfpga_flow/OpenFPGAShellScripts/duplicated_grid_pin_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/duplicated_grid_pin_example_script.openfpga index f1dc80820..bcc8d36e3 100644 --- a/openfpga_flow/OpenFPGAShellScripts/duplicated_grid_pin_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/duplicated_grid_pin_example_script.openfpga @@ -49,7 +49,7 @@ write_fabric_verilog --file ./SRC --explicit_port_mapping --include_timing --inc # - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA # - Enable pre-configured top-level testbench which is a fast verification skipping programming phase # - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts -write_verilog_testbench --file ./SRC --reference_benchmark_file_path ${REFERENCE_VERILOG_TESTBENCH} --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini ./SimulationDeck/simulation_deck.ini +write_verilog_testbench --file ./SRC --reference_benchmark_file_path ${REFERENCE_VERILOG_TESTBENCH} --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini ./SimulationDeck/simulation_deck.ini --explicit_port_mapping # Write the SDC files for PnR backend # - Turn on every options here diff --git a/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga index f1dc80820..bcc8d36e3 100644 --- a/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga @@ -49,7 +49,7 @@ write_fabric_verilog --file ./SRC --explicit_port_mapping --include_timing --inc # - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA # - Enable pre-configured top-level testbench which is a fast verification skipping programming phase # - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts -write_verilog_testbench --file ./SRC --reference_benchmark_file_path ${REFERENCE_VERILOG_TESTBENCH} --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini ./SimulationDeck/simulation_deck.ini +write_verilog_testbench --file ./SRC --reference_benchmark_file_path ${REFERENCE_VERILOG_TESTBENCH} --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini ./SimulationDeck/simulation_deck.ini --explicit_port_mapping # Write the SDC files for PnR backend # - Turn on every options here diff --git a/openfpga_flow/OpenFPGAShellScripts/flatten_routing_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/flatten_routing_example_script.openfpga index 88be8a878..3210b411f 100644 --- a/openfpga_flow/OpenFPGAShellScripts/flatten_routing_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/flatten_routing_example_script.openfpga @@ -49,7 +49,7 @@ write_fabric_verilog --file ./SRC --explicit_port_mapping --include_timing --inc # - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA # - Enable pre-configured top-level testbench which is a fast verification skipping programming phase # - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts -write_verilog_testbench --file ./SRC --reference_benchmark_file_path ${REFERENCE_VERILOG_TESTBENCH} --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini ./SimulationDeck/simulation_deck.ini +write_verilog_testbench --file ./SRC --reference_benchmark_file_path ${REFERENCE_VERILOG_TESTBENCH} --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini ./SimulationDeck/simulation_deck.ini --explicit_port_mapping # Write the SDC files for PnR backend # - Turn on every options here diff --git a/openfpga_flow/OpenFPGAShellScripts/generate_testbench_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/generate_testbench_example_script.openfpga index 5f7be9ffa..a426a7833 100644 --- a/openfpga_flow/OpenFPGAShellScripts/generate_testbench_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/generate_testbench_example_script.openfpga @@ -45,7 +45,7 @@ build_fabric_bitstream --verbose # - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA # - Enable pre-configured top-level testbench which is a fast verification skipping programming phase # - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts -write_verilog_testbench --file ./TESTBENCH --reference_benchmark_file_path ${REFERENCE_VERILOG_TESTBENCH} --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini ./SimulationDeck/simulation_deck.ini +write_verilog_testbench --file ./TESTBENCH --reference_benchmark_file_path ${REFERENCE_VERILOG_TESTBENCH} --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini ./SimulationDeck/simulation_deck.ini --explicit_port_mapping # Write the SDC to run timing analysis for a mapped FPGA fabric write_analysis_sdc --file ./SDC_analysis diff --git a/openfpga_flow/OpenFPGAShellScripts/sdc_time_unit_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/sdc_time_unit_example_script.openfpga index b4b6ff5ef..6841eb935 100644 --- a/openfpga_flow/OpenFPGAShellScripts/sdc_time_unit_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/sdc_time_unit_example_script.openfpga @@ -49,7 +49,7 @@ write_fabric_verilog --file ./SRC --explicit_port_mapping --include_timing --inc # - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA # - Enable pre-configured top-level testbench which is a fast verification skipping programming phase # - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts -write_verilog_testbench --file ./SRC --reference_benchmark_file_path ${REFERENCE_VERILOG_TESTBENCH} --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini ./SimulationDeck/simulation_deck.ini +write_verilog_testbench --file ./SRC --reference_benchmark_file_path ${REFERENCE_VERILOG_TESTBENCH} --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini ./SimulationDeck/simulation_deck.ini --explicit_port_mapping # Write the SDC files for PnR backend # - Turn on every options here From f6895fcc148efcd3a708d7d82aaa8fb1017f3d59 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 22 May 2020 14:41:21 -0600 Subject: [PATCH 549/645] update documentation for new options of Verilog testbench writer --- docs/source/openfpga_shell/openfpga_commands.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/source/openfpga_shell/openfpga_commands.rst b/docs/source/openfpga_shell/openfpga_commands.rst index 909a6416f..7f72414d0 100644 --- a/docs/source/openfpga_shell/openfpga_commands.rst +++ b/docs/source/openfpga_shell/openfpga_commands.rst @@ -150,7 +150,7 @@ FPGA-Verilog - ``--file`` or ``-f`` Specify the output directory for the Verilog netlists - - ``--explict_port_mapping`` Use explict port mapping when writing the Verilog netlists + - ``--explicit_port_mapping`` Use explicit port mapping when writing the Verilog netlists - ``--include_timing`` Output timing information to Verilog netlists for primitive modules @@ -178,6 +178,8 @@ FPGA-Verilog - ``--print_simulation_ini`` Output an exchangeable simulation ini file, which is needed only when you need to interface different HDL simulators using openfpga flow-run scripts + - ``--explicit_port_mapping`` Use explicit port mapping when writing the Verilog netlists + FPGA-SDC ~~~~~~~~ From c27d77a418169c24a26042508c293f87ad2d32f0 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 23 May 2020 21:07:44 -0600 Subject: [PATCH 550/645] clean-up documentation for a shallow hierarchy --- docs/source/arch_lang/figures/.DS_Store | Bin 6148 -> 0 bytes docs/source/index.rst | 18 +++--------------- .../arch_lang/addon_vpr_syntax.rst | 0 .../arch_lang/annotate_vpr_arch.rst | 0 .../arch_lang/circuit_library.rst | 0 .../arch_lang/circuit_model_examples.rst | 0 .../arch_lang/direct_interconnect.rst | 0 .../{ => manual}/arch_lang/figures/Buffer.png | Bin .../{ => manual}/arch_lang/figures/FF.png | Bin .../arch_lang/figures/Inverter_1.png | Bin .../arch_lang/figures/Tapered_inverter.png | Bin .../arch_lang/figures/ccff_fpga.png | Bin .../arch_lang/figures/global_inout_ports.png | Bin .../arch_lang/figures/global_input_ports.png | Bin .../arch_lang/figures/gpin_ports.png | Bin .../arch_lang/figures/gpio_ports.png | Bin .../arch_lang/figures/gpout_ports.png | Bin .../{ => manual}/arch_lang/figures/iopad.png | Bin .../{ => manual}/arch_lang/figures/lut.png | Bin .../arch_lang/figures/meas_edge.png | Bin .../{ => manual}/arch_lang/figures/mux.png | Bin .../arch_lang/figures/mux1lvl.png | Bin .../arch_lang/figures/pass-gate.png | Bin .../arch_lang/figures/pass_transistor.png | Bin .../arch_lang/figures/point2point_example.png | Bin .../figures/point2point_truthtable.png | Bin .../{ => manual}/arch_lang/figures/scff.png | Bin .../{ => manual}/arch_lang/figures/sram.png | Bin .../arch_lang/figures/thru_channel.png | Bin .../{ => manual}/arch_lang/figures/wire.png | Bin .../{ => manual}/arch_lang/generality.rst | 0 docs/source/{ => manual}/arch_lang/index.rst | 5 ++++- .../arch_lang/simulation_setting.rst | 0 .../arch_lang/technology_library.rst | 0 .../fabric_dependent_bitstream.rst | 0 .../fpga_bitstream/generic_bitstream.rst | 0 .../{ => manual}/fpga_bitstream/index.rst | 0 .../fpga_spice/command_line_usage.rst | 0 .../fpga_spice/customize_subckt.rst | 0 .../fpga_spice/file_organization.rst | 0 docs/source/{ => manual}/fpga_spice/index.rst | 0 .../fpga_spice/spice_simulation.rst | 0 .../fpga_verilog/figures/Layout_Diagram.png | Bin .../figures/Verification_step.pdf | 0 .../figures/fpga_asap_10x10_final.png | Bin .../figures/fpga_asap_10x10_floorplan.png | Bin .../figures/verification_step.png | Bin .../fpga_verilog/file_organization.rst | 0 .../{ => manual}/fpga_verilog/func_verify.rst | 0 .../{ => manual}/fpga_verilog/index.rst | 0 .../{ => manual}/fpga_verilog/sc_flow.rst | 0 docs/source/manual/index.rst | 18 ++++++++++++++++++ docs/source/manual/openfpga_flow/index.rst | 12 ++++++++++++ .../openfpga_flow}/run_fpga_flow.rst | 0 .../openfpga_flow}/run_fpga_task.rst | 0 .../{ => manual}/openfpga_shell/index.rst | 0 .../openfpga_shell/launch_openfpga_shell.rst | 0 .../openfpga_shell/openfpga_commands.rst | 0 .../openfpga_shell/openfpga_script.rst | 0 docs/source/tutorials/index.rst | 11 +---------- 60 files changed, 38 insertions(+), 26 deletions(-) delete mode 100644 docs/source/arch_lang/figures/.DS_Store rename docs/source/{ => manual}/arch_lang/addon_vpr_syntax.rst (100%) rename docs/source/{ => manual}/arch_lang/annotate_vpr_arch.rst (100%) rename docs/source/{ => manual}/arch_lang/circuit_library.rst (100%) rename docs/source/{ => manual}/arch_lang/circuit_model_examples.rst (100%) rename docs/source/{ => manual}/arch_lang/direct_interconnect.rst (100%) rename docs/source/{ => manual}/arch_lang/figures/Buffer.png (100%) rename docs/source/{ => manual}/arch_lang/figures/FF.png (100%) rename docs/source/{ => manual}/arch_lang/figures/Inverter_1.png (100%) rename docs/source/{ => manual}/arch_lang/figures/Tapered_inverter.png (100%) rename docs/source/{ => manual}/arch_lang/figures/ccff_fpga.png (100%) rename docs/source/{ => manual}/arch_lang/figures/global_inout_ports.png (100%) rename docs/source/{ => manual}/arch_lang/figures/global_input_ports.png (100%) rename docs/source/{ => manual}/arch_lang/figures/gpin_ports.png (100%) rename docs/source/{ => manual}/arch_lang/figures/gpio_ports.png (100%) rename docs/source/{ => manual}/arch_lang/figures/gpout_ports.png (100%) rename docs/source/{ => manual}/arch_lang/figures/iopad.png (100%) rename docs/source/{ => manual}/arch_lang/figures/lut.png (100%) rename docs/source/{ => manual}/arch_lang/figures/meas_edge.png (100%) rename docs/source/{ => manual}/arch_lang/figures/mux.png (100%) rename docs/source/{ => manual}/arch_lang/figures/mux1lvl.png (100%) rename docs/source/{ => manual}/arch_lang/figures/pass-gate.png (100%) rename docs/source/{ => manual}/arch_lang/figures/pass_transistor.png (100%) rename docs/source/{ => manual}/arch_lang/figures/point2point_example.png (100%) rename docs/source/{ => manual}/arch_lang/figures/point2point_truthtable.png (100%) rename docs/source/{ => manual}/arch_lang/figures/scff.png (100%) rename docs/source/{ => manual}/arch_lang/figures/sram.png (100%) rename docs/source/{ => manual}/arch_lang/figures/thru_channel.png (100%) rename docs/source/{ => manual}/arch_lang/figures/wire.png (100%) rename docs/source/{ => manual}/arch_lang/generality.rst (100%) rename docs/source/{ => manual}/arch_lang/index.rst (69%) rename docs/source/{ => manual}/arch_lang/simulation_setting.rst (100%) rename docs/source/{ => manual}/arch_lang/technology_library.rst (100%) rename docs/source/{ => manual}/fpga_bitstream/fabric_dependent_bitstream.rst (100%) rename docs/source/{ => manual}/fpga_bitstream/generic_bitstream.rst (100%) rename docs/source/{ => manual}/fpga_bitstream/index.rst (100%) rename docs/source/{ => manual}/fpga_spice/command_line_usage.rst (100%) rename docs/source/{ => manual}/fpga_spice/customize_subckt.rst (100%) rename docs/source/{ => manual}/fpga_spice/file_organization.rst (100%) rename docs/source/{ => manual}/fpga_spice/index.rst (100%) rename docs/source/{ => manual}/fpga_spice/spice_simulation.rst (100%) rename docs/source/{ => manual}/fpga_verilog/figures/Layout_Diagram.png (100%) rename docs/source/{ => manual}/fpga_verilog/figures/Verification_step.pdf (100%) rename docs/source/{ => manual}/fpga_verilog/figures/fpga_asap_10x10_final.png (100%) rename docs/source/{ => manual}/fpga_verilog/figures/fpga_asap_10x10_floorplan.png (100%) rename docs/source/{ => manual}/fpga_verilog/figures/verification_step.png (100%) rename docs/source/{ => manual}/fpga_verilog/file_organization.rst (100%) rename docs/source/{ => manual}/fpga_verilog/func_verify.rst (100%) rename docs/source/{ => manual}/fpga_verilog/index.rst (100%) rename docs/source/{ => manual}/fpga_verilog/sc_flow.rst (100%) create mode 100644 docs/source/manual/index.rst create mode 100644 docs/source/manual/openfpga_flow/index.rst rename docs/source/{tutorials => manual/openfpga_flow}/run_fpga_flow.rst (100%) rename docs/source/{tutorials => manual/openfpga_flow}/run_fpga_task.rst (100%) rename docs/source/{ => manual}/openfpga_shell/index.rst (100%) rename docs/source/{ => manual}/openfpga_shell/launch_openfpga_shell.rst (100%) rename docs/source/{ => manual}/openfpga_shell/openfpga_commands.rst (100%) rename docs/source/{ => manual}/openfpga_shell/openfpga_script.rst (100%) diff --git a/docs/source/arch_lang/figures/.DS_Store b/docs/source/arch_lang/figures/.DS_Store deleted file mode 100644 index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 Date: Sat, 23 May 2020 21:14:32 -0600 Subject: [PATCH 551/645] minor fix on the manual subtree --- docs/source/manual/openfpga_shell/index.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/manual/openfpga_shell/index.rst b/docs/source/manual/openfpga_shell/index.rst index 0aa515e23..24512f67d 100644 --- a/docs/source/manual/openfpga_shell/index.rst +++ b/docs/source/manual/openfpga_shell/index.rst @@ -1,5 +1,5 @@ -OpenFPGA Interface ------------------- +OpenFPGA Shell +-------------- .. _openfpga_shell: OpenFPGA Shell From c5a3e44e61ef0e3cdcef9e295c23a8d2bd46a7f0 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 24 May 2020 16:29:57 -0600 Subject: [PATCH 552/645] Update Verilog fabric netlist documentation --- .../manual/fpga_verilog/fabric_netlist.rst | 132 ++++++++++++++++++ .../figures/fabric_netlist_hierarchy.png | Bin 0 -> 49128 bytes .../fpga_verilog/figures/generic_fabric.png | Bin 0 -> 121309 bytes .../manual/fpga_verilog/file_organization.rst | 20 --- docs/source/manual/fpga_verilog/index.rst | 2 +- .../openfpga_shell/openfpga_commands.rst | 2 + 6 files changed, 135 insertions(+), 21 deletions(-) create mode 100644 docs/source/manual/fpga_verilog/fabric_netlist.rst create mode 100644 docs/source/manual/fpga_verilog/figures/fabric_netlist_hierarchy.png create mode 100644 docs/source/manual/fpga_verilog/figures/generic_fabric.png delete mode 100644 docs/source/manual/fpga_verilog/file_organization.rst diff --git a/docs/source/manual/fpga_verilog/fabric_netlist.rst b/docs/source/manual/fpga_verilog/fabric_netlist.rst new file mode 100644 index 000000000..7ce403300 --- /dev/null +++ b/docs/source/manual/fpga_verilog/fabric_netlist.rst @@ -0,0 +1,132 @@ +.. _fabric_netlists: + +Fabric Netlists +--------------- + +In this paper, we will introduce the hierarchy, dependency and functionality of each Verilog netlist, which are generated to model the FPGA fabric. + +.. note:: These netlists are automatically generated by the OpenFPGA command ``write_fabric_verilog``. See :ref:`openfpga_verilog_commands` for its detailed usage. + +All the generated Verilog netlists are located in the directory as you specify in the OpenFPGA command ``write_fabric_verilog``. +Inside the directory, the Verilog netlists are organized as illustrated in :numref:`fig_fabric_netlist_hierarchy`. + +.. _fig_fabric_netlist_hierarchy: + +.. figure:: ./figures/fabric_netlist_hierarchy.png + :scale: 90% + + Hierarchy of Verilog netlists modeling a FPGA fabric + +.. _fig_generic_fabric: + +.. figure:: ./figures/generic_fabric.png + :scale: 80% + + An illustrative FPGA fabric modelled by the Verilog netlists + +Top-level Netlists +~~~~~~~~~~~~~~~~~~ + +.. option:: fabric_netlists.v + + This file includes all the related Verilog netlists that are used by the ``fpga_top.v``. + This file is created to simplify the netlist addition for HDL simulator and backend tools. + This is the only file you need to add to a simulator or backend project. + + .. note:: User-defined (external) Verilog netlists will be included in this file. + +.. option:: fpga_top.v + + This netlist contains the top-level module of the fpga fabric, corresponding to the fabric shown in :numref:`fig_generic_fabric`. + +.. option:: fpga_defines.v + + This file includes pre-processing flags required by the ``fpga_top.v``, to smooth HDL simulation. + It will include the folliwng pre-procesing flags: + + - ```define ENABLE_TIMING`` When enabled, all the delay values defined in primitive Verilog modules will be considered in compilation. This flag is added when ``--include_timing`` option is enabled when calling the ``write_fabric_verilog`` command. + + .. note:: We strongly recommend users to turn on this flag as it can help simulators to converge quickly. + + - ```define ENABLE_SIGNAL_INITIALIZATION`` When enabled, all the outputs of primitive Verilog modules will be initialized with a random value. This flag is added when ``--include_signal_init`` option is enabled when calling the ``write_fabric_verilog`` command. + + .. note:: We strongly recommend users to turn on this flag as it can help simulators to converge quickly. + + - ```define ICARUS_SIMULATOR`` When enabled, Verilog netlists are generated to be compatible with the syntax required by `icarus iVerilog simulator`__. This flag is added when ``--support_icarus_simulator`` option is enabled when calling the ``write_fabric_verilog`` command. + + .. warning:: Please disable this flag if you are not using icarus iVerilog simulator. + +__ iverilog_website_ + +.. _iverilog_website: http://iverilog.icarus.com/ + +Logic Blocks +~~~~~~~~~~~~ +This sub-directory contains all the Verilog modules modeling configurable logic blocks, heterogeneous blocks as well as I/O blocks. +Take the example in :numref:`fig_generic_fabric`, the modules are CLBs, DSP blocks, I/Os and Block RAMs. + +.. option:: .v + + For each ```` defined in the VPR architecture description, a Verilog netlist will be generated to model its internal structure. + + .. note:: For I/O blocks, separated ``.v`` will be generated for each side of a FPGA fabric. + +.. option:: .v + + For each root ``pb_type`` defined in the ```` of VPR architecture description, a Verilog netlist will be generated to model its internal structure. + +Routing Blocks +~~~~~~~~~~~~~~ +This sub-directory contains all the Verilog modules modeling Switch Blocks (SBs) and Connection Blocks (CBs). +Take the example in :numref:`fig_generic_fabric`, the modules are the Switch Blocks, X- and Y- Connection Blocks of a tile. + +.. option:: sb__.v + + For each unique Switch Block (SB) created by VPR routing resource graph generator, a Verilog netlist will be generated. The ```` and ```` denote the coordinate of the Switch Block in the FPGA fabric. + +.. option:: cbx__.v + + For each unique X-direction Connection Block (CBX) created by VPR routing resource graph generator, a Verilog netlist will be generated. The ```` and ```` denote the coordinate of the Connection Block in the FPGA fabric. + +.. option:: cby__.v + + For each unique Y-direction Connection Block (CBY) created by VPR routing resource graph generator, a Verilog netlist will be generated. The ```` and ```` denote the coordinate of the Connection Block in the FPGA fabric. + +Primitive Modules +~~~~~~~~~~~~~~~~~ +This sub-directory contains all the primitive Verilog modules, which are used to build the logic blocks and routing blocks. + +.. option:: luts.v + + Verilog modules for all the Look-Up Tables (LUTs), which are defined as ```` of OpenFPGA architecture description. See details in :ref:`circuit_library`. + +.. option:: wires.v + + Verilog modules for all the routing wires, which are defined as ```` of OpenFPGA architecture description. See details in :ref:`circuit_library`. + +.. option:: memories.v + + Verilog modules for all the configurable memories, which are defined as ```` of OpenFPGA architecture description. See details in :ref:`circuit_library`. + +.. option:: muxes.v + + Verilog modules for all the routing multiplexers, which are defined as ```` of OpenFPGA architecture description. See details in :ref:`circuit_library`. + + .. note:: multiplexers used in Look-Up Tables are also defined in this netlist. + +.. option:: inv_buf_passgate.v + + Verilog modules for all the inverters, buffers and pass-gate logics, which are defined as ```` of OpenFPGA architecture description. See details in :ref:`circuit_library`. + +.. option:: local_encoder.v + + Verilog modules for all the encoders and decoders, which are created when routing multiplexers are defined to include local encoders. See details in :ref:`circuit_model_examples`. + +.. option:: user_defined_templates.v + + This is a template netlist, which users can refer to when writing up their user-defined Verilog modules. + The user-defined Verilog modules are those ```` in the OpenFPGA architecture description with a specific ``verilog_netlist`` path. + It contains Verilog modules with ports declaration (compatible to other netlists that are auto-generated by OpenFPGA) but without any functionality. + This file is created only when the option ``--print_user_defined_template`` is enabled when calling the ``write_fabric_verilog`` command. + + .. warning:: Do not include this netlist in simulation without any modification to its content! diff --git a/docs/source/manual/fpga_verilog/figures/fabric_netlist_hierarchy.png b/docs/source/manual/fpga_verilog/figures/fabric_netlist_hierarchy.png new file mode 100644 index 0000000000000000000000000000000000000000..232eb940ce75d555f549acb109e7bfd9a284c237 GIT binary patch literal 49128 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⁣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 E0C1yyS^xk5 literal 0 HcmV?d00001 diff --git a/docs/source/manual/fpga_verilog/figures/generic_fabric.png b/docs/source/manual/fpga_verilog/figures/generic_fabric.png new file mode 100644 index 0000000000000000000000000000000000000000..64354d1b84560ba5c6075a455771d0e807d27e14 GIT binary patch literal 121309 zcmbrmWmsLwwl0h;V4=YwxCD2X0Kwhe-QC^Y3BfhEyIYXp5S-u^+%34?MepvNKIi-H zuL}>%T1D9yHLTtmlL&cPF+?~VI503ULn!OhM1LFLBThsQ2Gb$QuNK^(@>?|n3DI9ooUM3C)Mey}gzOwmh}h^^=^07*;E0HbcpQyQxfO*)|LzX@ z#7koC>}=1?z~JWQM(@T#Z|7*nz{JJH#lXnSz|2esYC-4ZVe4$*PG{>x`kz7m8AsT} z$;i>d-r2&=mgsF<14BC(XI>JLw~7Ay^`CV*TbTZt$=2y_vOokfy#0lNiJp<+zhi^C z^1Rh@%Q;$@fM$LhpO1;>ua^I#?$12`X)kYX=WGY!f}@3zgsroQqrD@DBY*W~{=4`8 zy~Y0?OVPy1&f4Xzvx=>SGv7Z0{IBZ&-Al;M1~jRY)7y&K{+{#Sb$_?#VR&QLzu5Pm zz4@yav^jimJPiNcXg;`!*vLpQFn%xzVF6`#@M9gAOmvm$>P4OJ^ajIRdjY zl2j*A1-T35*$L-5_uZKpmTV1lC(#WA|3Kkku6lS{S_?Gm*!8ZWVmB{SimM-2Kb$9& zPpX^R^}3W_=U0Cp{`9&I4+ek>@FODw{+}L<_z>(fKgFp(0D%yJ{zOD?5B_tYG7g^FB8jsv1Ts3<9-YMGPcwfaWK)1T7rd~5lyDnOk5PA4kM}ps4!DkK2VdA& zneO`Tgr2p1nH8R0azWDI)h%*uMCw5a%+#5pK((OOAj-EX;|2KFx`sCB3*r9Eo=Ai& zZg$8%G%kr!Y@!S9i}VEHj*{9N8wlSW7j)z==5OZzllAaIA{Y^T9=e@W)(h#5v?g72 z>{Tn+#trss)RA$`XAmL6(BQtGLjEUnAcVWWj-zWWX(v@=tR#BTf3NGw|YmbnxDA`-VKvYHg4^Do}jKCOslQX$<`)WB+v^uQh}B+73|Lm z8&NceuJMPyZaqK|QGjcAI-UA4_%!afzzn($5yta1Fd5xr&S3lmAnbox{P#Zl6X{a{ zyfuo{v30#%A$~ZhWJf#sMP#{^9Nf1o$02;to-R|u)3ESf+*!-;-KJ3hEx219aJPzy z`afh2hz!n~MWwb!kh$9cHo~GP@G8{zy z2u2mtkW;FJ*NW2-w}evDFz>g4~om8R9lCuF0nZ)H4t5-%DI?y~7m&!U9p#p%}IQ;x7fnapi?oiuyO5s+i7- z7=*77kC*7B$?fvI4tR^2^TTA)^dJrqMIs|Z(M94Y3IB`CE);hRss*2Kk||pS0iM5p zxx;?<@@N_CS)yG#MeQgL9H&19L@@H$mjDE4HtqHpx;UL ztFkna{9iij4~w!fp7@z)g8`!QSq6Fc?n!&J9^Z5lzdr==Hh5?VOS#nPv!}N_m$>0S ztRiAZNrkWV@JV=^e^z+I2e1l3fcOGQ`vE8n2;i4_MJcVwpRBQ{jz?Dbgq(=hqKc0_m)2ojF5hotYV)AR*6@;&4u8LY znTtpLuT3Z?)~}cF8@IF}zedD`=rH;2hgT>wz%=GS4)i{c-F2v2rB52P=JaOpJg$mzCcX) zxZ)wo|7?psI)tGB3!KC-g)}gDNs|fF@oDpOND}KckMt&#cJw#G=vmd{UKrX23DnSs zv=YhUM?(-x`DkRJ)5uQW$3IJZkK62^aQEHX=zqvUI*5`@A=(pDl5NbccP1 zUPGZBO~0{`@I~nC{ z%6euayPh9{-D3Nnp-7Dp$-icm2yyIL4kr;IGXomnGH$fXH!OOuv$eQHAbOC6`x;J4jjfDGWQUR#3MF0ZpZkD<5Ww03(PaVs$8hE28cZA$4>h zZxWM2W_$^}mW)PE9Y7>2&=hvI@XJ3j1;_`$1Fb_p^Wq0~hHl~1EJfVx4JlnbMV#TN zP1D>tP<(?)r4!WZ;klm0pmCcw{a(#}_@i&6tEuXS|9%fMfE>7EgT9b%3X2LghC22k z>5q*J01|&j6>hBnmNWnTC5W>z!@82@M@o4fzf*59x$``)Vj)u~iz=*v$~sF96RU&NN*$ zSU>xlURKcXSOUZ04}C<|Kw(7zBVybjvjD9ND9U#o*grCE=)umw>PewYZFC&oJ4iC+ zKzEVEsceKH3`x}?1HOm)Q<@tDX~O-99Rje0_{*<^4d+m`5EqNd4A z8S7nsf!%SOtLdv6s|OgFR3cO~@@+65v#$E17UO&lGbRQG*z z)PbFEr%O#5wx>3yqLf2t^(R920=hq__Yt6y)c#ux*--SQWOE9{1e~^Qw%80iFm13? zZ%?_09Y_mcu&4Byg3ORBSfhemDkW~~OP5>B4hpRHB$E$n3{xy3=A%*=3&AE;ctt@LlIanOoDTw_fQHeLCX{xda^h~- zye=jmRbjz`0Ke6R34D!-I6CVnYm&0^zt|D#&j*Dl*gtQ}KlZ3CusVLvF@9A4Wni3p z!P50i6hx^j8w(8VB}qMhhp~9^Z;IOOtDqv zn?O;=qG-!BvdvR8tsaA;=Hz z6YUsgrLl<; zP3$=TCGgm*dYca8fjxIT4R(9uW#mCL0Zf!8JIPt~$v}Yi7x;fP2_zl*tPq+ZqJCjG zOjh$&0+lc;!uX-GJ8Tv*^0iXk3EgzjRRt2iGfbyFB^m(%pGiUbKSZ1i@n1>>h(u8) zsm))mTlFuD$twFL)D%Zw=Ig)AVPmf$b|T`zOKQSxqoV>0QNdsNFqvTEfpmWqnJJo| zF*#EcXC=FrOD=wz*_9}Noy|d8)<&756>a5Au28k6sDED4ItJ(#KoOMDDx56!b_PLx z6Yeje=uH1w#Rp&kAijJq5L2DKrhtt7SP(yKT`s0Qrd~i|Yc+rI%fQk1bm-*L0?t*hBzvc{w7;-c*`@A(|=qRXCoJyCP7v{F1ZeY3spCn z-7jJDKMswN7eW(W^K;N9yQHlbP9v@(MN(2`PG-ZWjg$Ch@hHw(h#^U{8iGr0sI*g@ zoK|*UJ2IJFM@t-lVN+IRNzPcm5n4T>X}X#yHOYS%sV@RBbQR_fACQD4sSJR_ajl^L zQMmH#r||L%vCQ#?{_y6*9at(IwWeX=@Tn=`puEP^m-t(DMyp7y!1cJFSO4{KNG{=0u?T2qBF%3pCAv1jTWdE$3uiL4y+m@rLuslO&n>SEEOhN&16yvSD%V6QwElu@tn{zn4UADg7j1~ zp1_h-?L48l;`;rMfGn7QA%Df7HqzdmSq^E1> zr0v|Ws^h2|@>rOjCC5A6mhf*9m+72Eay=aW^XDO-{eZLIjprXHdN#k;hcD%tDS-dv zLy|+Vd(;A6H@~chWAcbQYhvZ?8I-x-X}w;?O)V_l3O?c*dUB#l>-Bb1xAetF@0phu zP)N(+v31$w`?;aBYAWSS8KhSTIhJ9~>u-h>Z0a(bxD8erJEvN`{OUj|qPr?WEauTx z#TN_cPY;YinlZ+YpBau-T%Ss`bxtL>;<`oyzxJ3RXW^F4Xztpx)?pb%z{sY{H{+kv zd*6$%XC(mN5~Qw>EH~IR=1OVyo)*$hbIfXz-M`UfgV}Hvmuv!T9ZuJPpRR~F?f`n* z=z;c&C7KT;)Nw~bc&uUGen>Uyn8>IuqyPTVNAO(&5lFk#lJGnN8HQA4Mty)X6x|Zv z2v6RYr04gmK8G=m+4=kxbbaU43=#yy=_o743xvuV<6YOdEd8ZHohAF;irB-OpXk=0 zl^=y?cAiqIl;^z1$3*EVn5-ev$J~hyVN=ox1lKF)fif1LVc-=QY#Ps-{iH}Ob;+zg z{D@@Dyz@h}spYO^B(jdL1lP~+6oLU7Muq8!w9|g{wOl@M;Z5?w~e88gt z-Bbr%WZ=O3mL4$<9Q@Ksj5itj7_fv8p2Y_^rkrAK>lH799L@%4+3Kz!%kORQ5Zmm) zgfp1~$bKN zU`j+`E-FeRC_wWh_$EHB&`gkvp4I$!oD}GPj{=kyD5uT0Zpx&)gV{NKuE#SptizRo z%8=z!O3(SSiDs!(Pk>f3QOHv2+RMJLx8%-!-_`Tn6L7_$^qIb_^dk@1tgO87%2%lV zqZTg*G^TfX@Q+)7=Z5T(lIW5Q(_h_t>KB8t!l`qoO0lYU>kR}7YC|SeV^q@J(3jtX zODz@D5UPJ0$*k$A222+Hr3>v&Rqe@7Ng^`kW6S%DHfw9+IwlH7C?|vFHY|SIip%Pc z-q`p>W6R5}*?W_Oi?emn$%8t+mKtlZp4&>ln=Yr;*%eKG8k#w$vfIh+fj3SB!=i=X z60t>cbzu{|Vz5%04YJHB96k+Fa`?P&{$+ap8n72|ztmtXC7x5XI-oT1`!y=2aXEk8 z<|tx>4+cqCBIC3==Qy;}&8oj`TTb&PX903V5wg-x#g7@tXsa_8e1-2in0d;A6DE=G zg6CjX8B_-?rMccDxl#!efq6nC8GJ1LX|oe!e^DwTR$pe7o(F$9n_SYBlfPDQwO2vx zD_lA(-_XkonB^zkB|-`eIXeT-P4gMAk8aQ4c-5x1J^5CvI@OgQz9bzHAdIUfFKexS zTZMWFS z#c(^s+Y;|Oo+UW=uF7!7mg5ewCS%x6V3s;q!sY^Ywqn_1cI#=r)HZ=$-e;foysI0s zn~#Y<(?4Q$(yr&jq#6Cx*Zv`b$h?~8_v$tJXCLF#CPN~c_*)g#?lPBP=U7EY1uCpx z_AK+pj+0B4szD}$swP@P2} z6gOSysZNo&QEh(dyOqtS%qoqy(RPIcg?f(RGhBl$|%{V?w{Q8)vYvGrOAIjcX(bUU34Rz=eSqr zVY>u7ra(97b$r?P%kVB_-SxF4p7MJoYI9)l4$)M1&C81dpV?M!@$I?b~`;mj9&5&{G>&KYVuPCj~mT-27N{Liyw|)UG&Mhy4 zP?F;2>$=&)5rSRvF(Bq3n8QQ**fRaee9SqliFPO^84mLXg;)KUi&$qEoG_3T# z?W5-zXPXqUK_I^|GQV4Io$*ea7UmUYX^Se##fm_%qiSknhU+tPQl!)Er5#Yt-HYHU zz{H`(pv~ykspO>QaliXg%`$155Fd%`gl)))!<|<>D&`|oG4UkB&VnAtm33|z3xXIBA3C5kj~cQCGPlS)YNL&$1>YJ34qG!OqD$vSxge`2X_kR089(~!+ zgCft9YARTZeOe=!lq>qQ_)FMYDTPbkuyUptYC@zr zHMJ{)ts)3TRPuOwt&!#whke%@R0Xdcsry=#LCZ)e&nL8n-9NpUR#9mX#RS01!F(6* zg%9k#^Qg@TpClTnWA+PalJ4qrGs*e{42~+b&-lvlcDe#VxWnZKAOsGeV%8%uHoxfC z_Fu=lfJx$wHL129S>`=cEm);jkXFXe-k|gJ&0^38kZlMkUD?SbC}P0J5<8JSLwOVXhIs zJYpSmbk#yC)f%k{<*LP>pR^|2^R0`bwWDdZ%QAnz`lK$UDrcBW6Ou*GkJPG3x(0u5 zz?);&bt=+iT7Tg6wt7j#Nqz02G~cx&EzSLdT&Sag>%rl~of|P@KO?}ZTR@FaH~Sl z0nGQqfXa0_=RwMTlwm!unp5kdO}su|v{wSo6iPx>_9S=S4J!OHCR2u_iWLVw>5A@E zK1qdzxf4=P&nDi5nJyRyNOq3qIb~nJ6{h)J@*0x}J-GjQ(F1-9ENFQ*hcOZvF3I$f zQddD$7zEc?+%nuMy-eAxc+SUy|cNHxh0JV8*Wtj3pgCS%mAOW03$>+>uk+IiALXEo#5mi%{sNe400h~=AI z&zz?yO>K^00+rb41%8rti=0?r87;=0T1f)mv$K&yh0JmF&R68BAKzZ*wII)4`T-fY zfs2+$K#fw8Ikg>ST#bO}5Rxp0?KMB>e(QBqB&Lvre*s8V-DCYLoWr<2?aGSBKW-QFbs%49uOPx39bYD4Aw z_=}Z&+7Pa4b*kUnI*La)U3g`;qS7cozfeF$bIfC2#`!W4rI9|GbDWD*s{S@F-qt zvyUEv>AE(2)f991UpOy6pa?28HlaT|5IU)=E-y9qOb>B%5l#7XYeG_e_BII^ps*1pt1sJtftBdmcJ9C#vRSo6ViWR;xgtb`J*TE1YE3Ef9oFj?~pJBes0W zS%>gvBlBndB8(gk_lB!PnzH61!<@^|&ojwm62@V)onU~^RrT32o-cb2n74pYlW_+} z{Gw*F5LNE#^duXUCnB=Jgi{vEC~nX&zd&Snv-qly5*>QQl$WyzLX{KiZmaTmyZHDA z-&Gj5E}HbwZ15+t*ghUS$${=AMF2m_ax7%NtS$GieDZ(TIj1jtfBHZ%dcGzMb-z%E z{3vmA9EC5Zsxr!GUXtgHzaW+vZnJYsvLI4;a3EaV)&yLr$T@j}{w>pl`|7g~e zVRa~Q^z~#QlT$%yL5J&}XNjoWI7+|(XB*oiJY>)^^c2%SL=#?SiSX6l$FrLw$!qkA zAn>c=?f_OLDsTQ)U2rxU5wZ-&>kaX5&}jM0UWI4DvQ>j1B0J5O%g?srA^R?mztYg| znJ)!~ND|)3d?IjAWe4HY`HfGxFxPMY6hIgv_{pOgyjCeRy0I#JI;JpP%c;mNVYe4! ze_AumnQ}EZ5)DM7rNQIn9#ib^0@PCp6nQ$fpQSOJzvYlfB@uK8UWTU|DVmb~w0pX8 zx_v57?>|XOh!Zj7N8>BUju{nSv~y`M!pMg3e)N3&@w{Z;YRqA z0i4mHZ63^-|CY{R6D3){@Sg5I6_f&bMRweCmidli9w&nMn!k-o-_q`ThHq{KV^<_5 z+3wS(yB6?nZ1Fb^VUiUg78niYpnQl==ibs#ukcSz0x_9^-ogSbKQxs>&&uGal4~XHm zoik3{dmnQX`dc&YbwczAELcdF*gHTHhiRP=()5jFov&>kgn>iB7w$~U3B^FFpy%lb z1eq~#!_hZzV;zdeGxN*oHP%&OiuwRCGMUZI?9+*|w(2PsecVe&fAw8a1Rj}O#Na4NR#7w(-LQ2fU`AvAm6tcS32XXw34vS5osb!2y0Ha#nfH z(!fghuJkBCGDm}5#`*u}{o z4$<<+{3z!iXToLi)W&^Y=ST9gA7xF}u;1+Iz&zij&A=tJOz9%j)$be9w#(9zk^+3O z-~01f51Wrevszy7Nwi`vNtaF8%8Y8k+)hj?fblkjs-WboOfILI+4gD1Y~JNs=tZm2 z5@li6<`YXn$Fhcr zEcbTg0NP1YET2oPUrBj@m%WgYbJ+{4pOsYE?dIh04H~SyXG-iAgkO=Fo}?5=DPR8UF0*l)a!(2`tyk#o zSOvMrBLz!QL~W>))Pcd*Cj{jqf> z#%jC3q;%z2a#8-XL6Q}c^WUY8Eu*Rl28K6J;q$Sw zP-5Q`V%#y^o2a^!x#^LPjF)xZ_!cFJ@ZM&BUvX1;-+>>L(47eqA0K*A+7`@9f6%Q3-bp*&1z(C!EZED%mYS|3Ue$#X)!7gZchkL>G^0^J@>xdQB$^5VsKf(?qjl z@Ky1;lHRS$G`J*eTKWyD<}DXdI&Ax$1N+o75N+qfi!#?jECU|gV}8Z1NBi8r zQK(-@+k(I)BLo{u`1Co&q!q$47v^>UKDoR}S)BsHRAsV(kg4+KNQd_KGZ-{^7#0=o ztZdHU;8g|6R%4-8iG(3tTU}Ru;0+HtM-U~WhN)rUk=Svxq|m%kTzy8(Sz6iEsZ2lP zWd2VS)9w(3xHaL4-_45xUpu7w=IUp}PdT^WkD{#P*_UyidE8D zdOuI3rGMuUbGvMrlkM`JvtUV9=+dqVH?Q*0-+EBOQ*{!%;|U}E%)-r9=sFysQfqcm zW@|L0)_#yOhGSEtM__c$#*O86QYc4m)$nf1rEw}p!dF&O<{J3fP5<0TETQ}9SK2S$ zvd(sl9WrMUBg7M}FCtuCSW4iD^_O3Gy!kbV%PD(5sSc=9ZK0?{yH8u5!!f#%MD`Tw z^BNa8rUgP2A5pO`Z8=*BG>rCh{DgH5Sg;tfQ_9zQSV)|xfJ=X^G#Dr3f89C%lBav( z&J8{hJ$h?0HHq{Sv*T(-a48t0sGa$@g`X^Oqg{bB`RmeGnGBR{WtugqD&o%mqrTK{ z3WMX=8C&a+SZ(KyDPV&;+g-i)jOZOUHQBXlvd>Dgt9ul=<8MNA;l*Y*uaNFZo=t}M z+#z(&=56Ic&C6RA_l)FK^JsqkGSsP*&aIj?Af7Sy&5Sl4L+@fIMe|D#bUrB|FLqaSGz3ifH`RC$cA_Wqp|P z?&1m(LX#TifyhPTb5zEUWE$@VNbGbIO$otf<_HD&hV6Hz%_0;C05u+yFW z3f*WUe8l2-K-gtdxRS>_Qpk*RIUva0EG9+$y;Ihy*@zl4li1pp4(Qg7imVYLWKzm=Q z&22(ZiKSV+QuVc|%g97bHh`p6i6^Y>b^TM0O#V-9j$YSq>Ipa9YEzMl{Eil8T<)(^ zK+4BSvdhw(_s`%z)4pO(PD)f$>}~jhA*SSe)q6J3Z*l6}Q28m9Q?=ScBsK%M-Sfiv zw7*p!LVt>R)}m0;fpIWwp%s`VTPO$}btKsWRC@R!#&~-doVFFxN#>)$$W2P9Wfgpk z2@4M$-O9}rhYph-I7LdiKdW96`=NQb+QDbj-)RQ#N?2d-X{VnddT7Bo_<0yzx)4Dc z7xjL3g)8h%j)CpKG?f`2)#GXeCD6U<(z~iTdc^q~6YpSs<8N<IDOWZ^yy^4xAK50?yuoIg(&u_{Y0wn`?hU?2&03`sCO##yLf&jX@)s9| znvXr2ho0=6Re~rY`rg&av!xCsg(gP2;i2y6zh}y!r#uMhi+epf07ALD?q5#{iBF0) ziN!#G(Rn0)0Y*bn$*Rl7+(v!(@l{YUQ{8p+M>LpjZLuT=OB}daOA8!uZc9B<+R27D#+G`9;&86_x~L76{fCcSZzqOr#%9-&9( zEqEMAmhavl=Y>D1`lZN2ZG681+X zRJl)rZYGCLS&?VL%~m;sizb#$n{#50R}vLBM3Q)maQL&073+~S+1c`e_&PgL>>k*v zG^5)~e6$lkpX5rQA*k2{uV4UqIqwh5^XbdrJ|eI+EN(iK(Dpvm&)9$0@i`SQ2=I^` zC)~i-&psS-QJv5EHOs(Ucj}8_-9L2fRCWUSVD&O^_OK->ZAGfD3N^M=5?XV!l9uUr zWY0;xAXffvTONiJ(7H1lJKIL=7gU?@w4i$h=-^puKuZ!*Cmhy94u=NNA1SD&X`=9P;2Pyjr{IXBZ!1 z26wejhMq3N1fHm>wZlT(9u>K&Tt+DyunrD=GcCcOgo_+<2)VI!*=B@O{pRG_{vT)- zN%hN2V|OP~(C(4czla5is#Ni61LtlpJKH1|y1YM;>HAcHU=RU7119S?eBmH`n}rh9 zO6b|Tvz^(B+g9aNpYT{hZ3zz;Ri(9Gh%Qp_u??umfufT9SCE~ zyQ8{0Tq^;`QPCBTtmy2kY5|UV$l;=Z?UYf$N=MH3tfhDz*$pmzpPKuLNz{Rb8ODdB_G zc>VoKl?A4=IXhrw%PrAgma`>IC`=SA1TZm-3l&|LAUG>E@zpZxn#8s? z3axc;VYhK~+Ncp@Nb^!vjrgRq1ddoJPcr6aDGNW zuklQl=RFNxeKPMar`g{=l4UQlA0|si0S01V+l8Ha}D z-$a&>iMvNMdTJp?mJ&o{+U{JBva*t84o0Uf?u2+asovge@kHKup4hBGa+@pAXt>go zve=-5EOkg3n9Z?)^m2cR_vWrU0NT8GvY1rt?yv~!3`Pvf@GeN=RRaq)Y;%(F(5TF9 zjT=z<$v!BYK;xGnwb&}D&o3e7VTz>4uf)~D1QEEvU4XAQm9VrBGjb7*%^Mp%^xUCC>dUC$9_ z8l5e0Y}utChM)&0f)^kr=2onrlYXDMS)$5{f;#{oLKq-y9X5GZ=G$Pf3(ehhg8h7h z%;0t*{Um$S+7Pz~bQD7d@3?6@v;MBU<%BoSibrm&1A^u1<(JuH%1qW%O8sz!*t45H zZ_kxjR!4bG)ukj)(PZi7CRFi8aL4YO6E0SVJs(6mMo}`5#<@OsL^VCvj8$bi?A1!B zt)ubSrOCo=;;(Lj?Xz*L~9a)Qs3Fwfg^Jbv%WkkW3>H3(e z>zJe!=)VqB7ZsS14Bcr`_br-t$Y(F~sblOgJ62Yu=G6YfeU%C*hnq~Nr$o{ERe554MFk)8gXhSOdb*BkOeX}haX z*}GH3gB;3XvIQagm(%=)>mgWY>2Oc>YB@6(8_}mV*ff}cwgpAw>1hepvtJ$!r=+wI z4lLFymi#nr`}t8NDzB=$r7g34BcRkV1rI4|bAchN0<1xzGJ|ggso_S&pK5Ve(C|DRrBDg&Uq6^5SfXx!;d01lrV;@k%E?x1lyW@a|B)=0k!Xc&Jz4|CSor zA3vL)DL$HpeG$yupZ2Xh=)$@!Hod-*^K%~Irw zJmvViFN}q7$ihAmSH%8leK^5GDA4Fia1CX$({Jg0?wV5CMir$wxjDeE%zkv<>YzNP z#;2Wl&6uM;C)hW`nVU?X_?!G4ivI2Twbp#G-sxkAl0fe*x z1>6Ex6l@uxI)*zm3>rDSJj~!;WldrU-mm>l1w)%W-;+en%TJ#Fk)3v!M}}I*IZV3~ zi<4SkSSuRTO>5=okd8%nFRc9h`@w8!&D3-Wqc*nY&&E?KKlxIwa!>bMnyVAI>MnpL zr#}k%$RlP8w9pROYO#LG45+CMu>w2l+~4`$QCk@A70qJW%z!E3+;bBqyn0O3CmnEI z%?6(2<)r2G$6E72sBM_Yvi-V^Eaoj!&Qz0z5`(8~HDU(cDe)&oM&Xf$f-;5^V5YlF z#RabPD^gyPOzReS*;pDz@kYUaI`nngNULg;#{PP<^qd8`qxNXWYi4<9T(7FPn01aF zt!mzFU*-(HvMNjp&Lf>V()Z1_94ng@Lm@7+7touzI7E&U;L%Lo%ULJxqtj!>3+a#b z;61F5_3q=n!>o%5i=CO|z!a$pRCL6E_)VDgu6cd=WF9sGw$JuY<{ zYwWbITs~X{Ma4`FQjCu+En=sWy^1Fn3IIZw2wN(a(i}XH;AoiJk{qTaGA&ZVM`Jio z5T#KJbU8WO(8y%Ngmy%GV>s=g#(}$IYUJpM*+Nxh6cVC2WQ~Zim#-B&O|j=KSO}FG z24*=|{yxiduynS8uYBaA;T7gdTwA8HL5u(yPC#KSMYqD6XtL^$KO32P{Xq(PbtZYH1f$AwnLsNdF; zvOJzoY$HnI<9bAEbL!1Xl&7Gr&TY_8iQ@cmgH<6F0eb;De4F~E z#O`7l>6lT~(!2maH>APuxBIQi*g!Yu9{IJdbuu-2KJIz>xhjskJ00fw5nA4salh8y z=PFNDEQu1|BbA*Hi&20ah*My1brgD+p_s<@s#qD+_lYlA%1}0gzYe_|F|&Zdg=W|v zoI$5uuo{bROINd8-wtZ^n!hWmTAHa@7su{PRAcrU5;_sRz zMaSqOn4y9^{J~{4eQ`F{RmDi={11B=1u}v$!q*3PYLQ1ecW>~@jM`Y)EIh}M^r#ee zCET8!yZwEFtKMA%1bg(w=1TiknUizARX0(!`3-yR$7NG(iu`pNxBCzw*JhLB%=UzV zXRpizM_2o6fu*bdqO!ZONA}`!zm6ro^Qni#xhuuxl%c7^}tlU4a zhd*HDzrAv3{{ga56IwSfLXn_5*dM|ENc4OqTm_{fv$X1t1D{brq>!3epc@V@z3Y=d zf^yC$ch8xS0;VhDhkXrR%34)C-p4^%?#P2!(95txuR)~kQ{Unb#LIq$FB%=5mAb;G zg92p6U0&umYRPiHuMF0FD6EE8{fmzCl!cxR=!TXu7N z{0jt@p;ZhVQ8^*D2IGobojeqym{<)2iMFZ-5>L$N-@KTlBrE8BfMxr0zwS?g>rnMs zV&yrTibI5e>{mhsGiN&T7$VIFN(Eh%@lPL?RCts3pA)gE6ihyqQ9iUTow35}IF z3@u&xDnptFqR~VWLt>cR@nt|*Oi`5+t6D5Cd)3`UBsIz?Q(ym)VFT;#0n?Jy=7Huh zt?&>Y5Zw@euPs`8$(Qw=)#PT)x-?ge|GctWtk4gIt%Y(Z(B+IudNHuxHvQ<>w5RSLS+9Tei-(*n?`0*E&#YPN$cF@|GdSug?CLz}uIG_-TXqIYkb4 zDWBhY2?xig<1>e#8z~-dVD!_V_|n~?=);96L5^Dy@hw7bQ_%ACMbMzd%wC51fs_;# z1L?%eT2fM?0>n)-=vtb@-(Qt0!W14yXtWG!bOF^HtpzhRNzN8N?VtYz(6G9}zx%$J zNaqJAw9j-_VkeI_J%hxdpM^e}3C=?Ur}3tBABu%Rc`|5hY_PZrK8VO4n_2b{55-}4 z*=Bh7F2c1XS${k5PW2@Js8d@8gU5lZVcRaFq;YD~F5?bfJ(BONJ$t;Q#FsFi|ZfPhBQ}S1fjln=Xco@U{>~zz2(1Vj|)xnl@TepXJQ6R z*B0b}ljA}Q9WHNL&2K4G9(B6~EIK7e<3R%KoR0x0za`YSS}z^UoB5D&SEJ0|7x9)I zFv1E`Mk3dI|GeoQwQ1x2P3?4|D5Nw^#opO6Q(S17BbSd3SHDskuTpX%_Y4F$CLJuM zUM>_+>jGsFQs@9KSBWM=hvONWs>y0gm1dT|Vz@c#&gN$e%BKmeRjhbR*cx%?VmGY@ z(W-T;wr$(CZQHh8)n(f} zc|Y$tAI``>uyf}cxguuFYlc6zD_88kCF#Z=*BNsijW~C>x=I)ym@c5IVUScoDqzaS zu(YRzn@zw#u87TLYQ*ugWAkWL8lQy^Uqx;X6*bn3k(Hy4Xrs}Z`kCL*;Km`gpXGJ1 z&_m9e!bBtEaau#8h5isaL!l^nBghiBz(xMx3KoZVj^2b)+Nu99hKe*NPCXPhJY}Un>2jqr;z^oNTm62R|nYebnBhj@B`$ zt1T0FgBvxY%iWB>)eg|9qUVBJ&?=M~&?XqbFHMYyLJ(ajE<5S1x%ct`C6FH1D5bIOQi^`IWh=9-(nUwab?Zckm(ct+1VAosN`f zo{AJIkr5hb#wz*w~K+?7X>5%$}|hqJo%R#_gstzz}SyP0Dap(*chV_!50r9N>B) z-5`z`QveQ%`k$XC-@1hzpy4p#zDSd4dIem~b;M!=@I^a>UEo1-B0TVm<|3jBIc6d| zPOlPerX({|74m0)hZY~cd4BoP?2NVlyL8C&rX22=R{FJA9Z9tzVx!5MEzD|F94V~+ zB3sI%Ysv10MptCEJ26iyyqMLBoqsBMu)e8REY`)yl2$Wx#;KKe8Xc3)!$+PSb=u2$ zZn!*xZi88_M^Vd^SJ=|yuYfdLs-=FjRwaIEeT(4xOR@`0a1}{QMSEErRDz3AnMP&J zzXjK^1e|eNBqQ23rXgagRxWbqAu;^{pm?;^-s##ZGgJWR`cj} zlI#dG;Ai6`5(0uo2rwHi44t=kqf`tp)x~pj!aeFwZlW-y zqKfB8$)X@KA-W*EWzo9ii(tvVG!m1FjXje{1a~b$$NXi#4RdvcX72P6C$n+tKnFPl z3!EIG&q3jCeXE2)QgpTNc}{qrqwnkIUa*R{x4uV_??z#cS2j(_rU>Ak?$SGb&XnXe z>ycr!_dsyLv972bmavF z2H|?mUTPYXF4QjqC_Y4!uJ*4cb`d| zhR3bTqYbz#D`0QDk8OS$mrtEk_HUM4{!Eub(*QatX2{Qm1IX?kCj@||5!bUhw3Z+O7tAa za%MtjNI46+9<{66C{#u`R4{yk0e_?8ox1*$c~8}4alYfQqLImGWL z!@|2YWCod#87+0b>nBs} z&e@B4!vdQF8n`~D4=XlHUWmg`+HITM7P;%*F-4u7T?Mk4i5aZLS<^ zOv)=IWZAPrtXLALs$7iMYtQS|^<>sm$f*8&pce$B4{{TEF%uf(EvI#S&Bi~%Bdv%} z0r$&DE`3lxL>^871zmR@WvVAa{``@jgZez4PuK<15Md)d;!a`K0gN(HCsL|=|G-26 z9uo_>R%}H4QmdQSE|Vjmi|x7!?W!%)$Tj&O{f6-DiMu84*XD-rD3EvkEA^A(iEcz$ z+a)Ic0;M6N9}xj2^+0jws_ zpKb&K7lPAUMkXZ?vISu0I?V9_w|L(vkCpd!_qq8!#&L ztnf&d7F*jHZPm{|Wmk0QQmU|ph0vFVE}Oo2!c0dy<%L?X1#-P7`r8_!^Y|)3YpV1h zJs-My8)8&O4x5vOfU`gf>kG4VoP-epEkjVC-hj~)aS=B?1_QZToYdn)^yuBUPKlQx#!MLD=zX+# z!oFZovc*ZY*!1rb_;g|HDFR{my<*tlhTljK?kn5;5MV9oZ z3qZ8(z6c9(G2b~!4ZsN!0(s7ow$^^f%QaSm-DF%Id!B4%yO>0(yVoz+AF_|*S(L~x zPB3yqZaG*$Uc~f3r#sVmCTu5scHf*n$(}Xoyqx6aG&_zs-fAv?K@Ln|G;j z7;%5@&G%(cm(mdEg9PMvq*s)+Pq*qsCYtX}N1AVs8kSLbBK*|#f*;3+>(RK`Ohzc_ zk**YxvRvuA;u}Po4&vC8gK2;7LGiUO4eO`t7e{`T*E>Qd^NvmgaEVe%Vo^Ivlk4?% z{zw_aEz`eH&xnjX%U=(_{{XDj*3d?sgc%}WBKnImyq|*>QVIQ*rU+E4*?Sgo%bnahr!g4g9LmMa75Azd7 zWw?J6Eap{LVN1mJ>}&X*g)_Ua>hLntEngPf@M%xc-Lf1jFmUh=8t~6%597QpC zU#3<+ZV_A!-~j#hL;K4uBH0K+)zqF(E_M97^k3(QEeHNC=~|>J_xO z)+;y0>ODmgCZ<7L*MHQn9b|^S9$%C>Zp33G_26B()6SH%0EWiAg8JoujNE^?6F&;oPj$>>jrO@P@yjDG5rq7l`SdMO6;%kPvtO`)Hk15o2 zuQmI$m{{VA7|v2nJf(5iu%tb?{j#s>ygypn+I8MgNx0KJ^ATK-Vf7uOwScv2D#;RF%XNb1K@wczKSO zwD~VeB&~)z&%(1V2Jga@8|nggCpnFi7+(mV*F1XNR0TLR4;$OwU85u zid`M>^PR#cuTQ=jUcGl&_L?@YCgEnm=G&gTnrFx@^$zecJPq~TW25Gm>Pf%x$!}>q zJSEz3LzF%9M!J<>-*R}z$CtX0hVVf-bH9N*R)`v@%rp=IEYt;I<ZK9p(iDqXNy9Xzn*%1Hafq+nxnKm$}G@j-hM=X z8gRJ6kC&SKl7deVc@m?rC(@0%3eKQ-8YuruZs7fFfC1D|R5HHHRR08A?P5i2O6<^R z2->m>0^*Z@LJOE=ZN=Ch<|uPEz0r$#05oE&+-LK=-e_2ENGj%8WGp$W62sPmV2nU> zhyyfQc)?XL#DD?(8QklsW~%Y>B5!9i7i7a#jq(>~6D3obYX{m@{eW zeC`$^1gAV}n7c(3hyluSo^^{WL+L2$!5S)6c~>V?*Z%*&7+^8LK>Ka^MzSnZ8l>h2 z4aBps%_)b$1;q`#4So%K2w63WVq!g`Rr%=BGpIq?h;%L6Aw*B|pYM0vIkqnfS(Vm@ z&65(R4#@D(3Z9PC6N_XvbR|NquL0@x;d9zSB$`x}T9lVR1+8K@LCjB=%Ec%p1Bp(F z^laCy^3+zAwA0;%q_%1uoh83W0?I$G9=2<4Hss^AkR_xi)%Ter7K8?rftkQE(q?(3;u>CMXqT}ClwifxpE zpQkl0UV4R0@WPh+y76>cMBQ6Fy{nBN#_p&t(Fr`2(q!2WruTqMZF)4El-v;qUz@2< zJ+(8M(rAnLZp$#cJv78|5cYC*9t4G``6r1x@y2DwbI9>@U!a*srzpr)^rd$CD@`Gc z`BEo0J0d8N!94Kz6YnF^S&_#0q+5$8`!2Nk_E$I94sw1Tm{im_f*kO={=}CqXl7;+ z0>pnF%Ew9GE;e4-H?{DveE%Hp@RiyM8Ls~U7JU#zNVfetLz`{zmEy9B`&|GHGTkuj zx&z3zFIRGH5woKWPPSg69AjmGrpFMJ#n_eI=JZF@rBG!f8i4abNy$YuOQ|uyITJB% zm?+BJ80fa1!R=7X*j!w>99|EbWs1Pt@kymT-=Q!pipmO111ry|QT!x($`t3ncKj{7 zDb8JNQBOQQO|fmeaRGYOyhYQCV$Y1VVujRE5l-(sF}(0wEhDtVpf=@3?3-8@t;?Ul zBI!eCROUFaRNXKBsBkFRIXH8liqFHC_o&KZ?EbWDb|?u#+XWm`i^o4FKtoNJ$8 zp4O7429T!Qqf;X(zvLFz&%kfKo)2k^Gybi*4nT*N9v&mq`Sz;;Q~>pE{YRElY~MPy zaVGF@%UvGs_?3M2M3rO#rFB=dQeJhUhu!uc}aIp)Up#;7!06`cwF~!QFn*EG%IEp%5(=vFuF1S`Pbhq469iU z-2+R2tP>cAhvTX~t0P;V7j%g(n$mk;m%@5)%Y$!!o$MmMbcvlTkCFEQ9PCG2)E7;& zEIq;Tm_USak<1(!PH82I%f0fslup&-mB!J@*+GCfomCM6`O0rhB^7S`oM@6wp*{FP zqo8+{0f&_K1B{K6OH zg{cG_F1c|0=i#{BTSKF;236>4AolNfz1M#NG`0sqNQu+J&ah`amO0}SW~@FV6X58j zV-X^Bz%#CxaFla2U7TnySrU5?@Rk8rUb-v3Zuef7)X(Zcav-A`UZFsR(JGh(m*n^( zL^URaoW=0W_-49o9QY^O+P8XgV`|$y!~uem(2R|pjApIWW^_tWAXMrbTA*0GV5TP0 zcjQyfj^ig@GwOyTtF@#*<^yh@j#I#$e|tcsu)1LXfs2uDN-T22Al-?LqJ-JrFz(b> z4%{DU@`^RUnhHu78M3^=|J-mNG&_psFQ** zJ=!J0Z)_|w?vZp4oyKM5k<1iGZPIhO#52%l!UgYMvuh+RTNS+H0n|~o3)~MetGwUv zGK5wxe6v-i^@EToN`NWGJivJTL9$ZE_!l=1H}x9%NLpY3`)9f$&wair;8%;IDt9?% zNE)a5_+h{#x2Z}P`46|6&_do4?QXb4_f22Ur>~m!#;-&V#&%VLU{QE)V;nqcrv_n( z&P&~n+??_s*x}^}5}aAFJN{%$@iGc!pIZ8K_wZ0PAF$9GYi?x{$&IN*H+^UX+fEVr z%=&$0e4p6neCyh=*4Yz%kLzkR={kVXzwyA^NC1K}`N%jayTdQWI=6U4W*DjjVqTx6 z&;F30z(()7VsHi7W52`r+AlqHzl_4w7A&cerR|mNmD~1j!|sD|YX&RD(ET0Ykn}S` zj3{z7FoEHU&r@HKg$1Jr>JVAL8u`m3o>F0h=1%UfvP6|1u#?pX zLepF2! zHlFwA3qtY4xEkLx2bp;^U-)UoVps|{bgygmTw8kD)Db^GDK6j=&6nE9I<4hdL$x(| zVg=5c1gWdKGg+YGfdRWog1^ll_2Cn6jj2(&PC|UMH33DI*5J5;kgog!*VU#^fh(QD zm{z32`HkZJ1T}w+H}Uk%{31)YPzZ-87!yXSuwaj(GH5YNN0GXc-m<^$26 zhA!-T7wlfv0IPeKp>x@;*3a|O z`My{;?0rDe<-kq^=mXQ@##=X!TBGRlVr1`jWCgd^nnc>4TNbK!pVrO5o^Up$W49Hl z`}}l6lr~TPcbYm{)0ohdB2Ky*(IopXE=Cgw?&m6=F`Gn{nSu9SOiQYsb+1;I{_d}4}=*MFM@nht|WleQSs-vLuz z%FkKTw-;*gu|w+=f8BOlHoT%p#<2Ll%DQejsN>X2_7pJ~MaC+uX`_ACgO{?Or?2d} zT^`vt9ar{35YpDcyo|t>X~Edv*pvfThL#x7c^=8;*w^5N6rk04$rvZ|{VX?gHM#^< zP4qQEHx^50RJY)@EC7ln1&Ipe?#`9LH4WW=vR@D%Q4iz&MR45%lXV2Y`WIo1E3Qin zw=0Q!Cq&pgzr=xge3yNbB+PC2#WRDJ-vIWgCfHD+=Ii*M;l{!aeJ4P*od8#ehFf*7 z+ZurGNN~t{g?2>42H_)T`ybncYTfaNCsczc`X4GEMFk)uumy%q>5LR4U7}4dwlO zIo5Put;~FE4l5GqR(;YDyCPrxd-N0E(=6#TQtl&)fG_8GCfgD-424XZfuZ{(1=xMKrsV zgu1`&41HsTqx%a>fmK!4l2`?-fReDTfPR>97R|8*hH_0F;bDy7csTM#uUD@kZ~Ixh zlhGp52BRk+ZfF!*2cq~VSw@VFfTiN0Y+~=K3>|N6{{N3x1=v&`!jHSKeemtPlNeV` z=LuaP%m?_==S;Um4!`NSkM$qY*(lJ(9H>oHN9mc?|> zxLd}LgnoLdYt5?E;=(Kui#Jqwfe|)$Sfk@h~wvtRs-czAuCb!8b_DC6MbAo`;9%%?2`2%AWF8oB$Iu z&y#xg*0i+t!L_R`8@p&NQLdBgCN3n#x`(n_+Q*Ryf7V5qVB`H^{><{l(AA#al(goh zDpw}XmM2WkhH@8`8^GKUaON%x!uD z7>V4&mVA09R(UYxhLh;yX^3^vbY3UBGfakDmy^wl-1*fVAOVM1rkWnN@z%fk2+~{KRau+$Aae zA7-W8tCbTc@H!pHIk$E_U7j-{Kc+dUc!e_Bn;gjp6QEH;VLCHkf`bE}w`fHs?@S3K*Z^6U+vHI3|B^@{~KcKzb(SDA;0 zSK%3))e`QsG!0ALf{Qm4!g3+i#v~6*jd%+x`PiQcuyPs@B^EC6idP~l3zg2rgU(g7 zqQXG|z|_Y<$a3Ut_x@5r#k(?j1#a@}-^Yxl&FL?W;KzmB&QrAL7k2p0yGoWOZwwF;ZfLB0z2mAGKyWil<2gAI}7*>AsMrINB$9|G0h)(oGH zt5WPU@GWk?jAt{8mp`T5?(c!6iyZX)X9#f$R+JW!)|+MeHKSuaVp=!{n^BI5u}Rr6UD+tz%J*!Zgma2>=mnO!m&hL^(AXZ6h42wbo?kVH#jiCl%Ys1|}b zJ>KPrv_wbwmL|Q7XLZ_ zT%|dZ(Wklx~Znsof|AA|{S{S>MUQ zM@mut>{)PjS)&_Qd4a5sx_2OzAS1={S|m?nuZ#1mA7Me9p%zrEFg5?tt>NQ${jSSd3m8d?`OC6FN?y^LEOHOw zc$ToIeW`k6o`HhW92qh0hXb*u?Qx^oB@0*ZUVX7xW?pz3xEVJQIcfgHRe6P@p6m1^ z;k2@7syrMSj)KyHb?|-J?p=}8Xt*Y9vcPs>wxXgV31qxC2K6ETVSCk&3kdGqXnHBQ z-U_l_`6$?Ny13UgpKR4EqbLpQh`JKQ9a*|=a&fxKvi#@J0nD_Z4;<$5NB={Z>kw$7 zRN117D(Z-Ilo8S%q~yY9R-~TliTahwZaNkTIFRV$H?F7kMER{6w(*TNPXHI>_ z1oWTs0>H`wb>;f&r4GG_)kGI;;=ijX^80`d3nBku`p!;$=WaMuVuD_{Ed4FFqyIvm zt_j%Y$SoJMZ`YQQX>TXD3&oXUDax z3q+S}WecHeuD4EBiuP~g)^)0XNxwjcug2dt%l{5`SS0Di5G7KkNn)j~*uA0iHqU*_ z+;#l1wVZ64SeI@65yt59_D3?nqKidJh9*W(b>~GOrk6O!1F8-7Mo8TQ(CNy=Bl@6KHiF!= z+o^xMf4coFi1LzL*_`BJkDrzmIYKb~gEpZ4n`rW+6r_(_Fve^_(gTaE(H!UhSeLA( zlyIVH5#SJ=BquEv*{;r8?^0SqEU#K9rJBgt9}#my9u^(Xah3K6O?$GrZj^0kpCy}B zRc)*1VEW8D8&(YG@%!eQ>D zcQl&R{$NUBTB%1m8PePyHX)TCM~S2BC|`wX1NsA$gD`@PV1O~CT*90M16ZZOABF_r z-b79VVUr6ZDp;Z`2q&{JsYZ#c%DC>YhKD}1%9eQB>Aby$2{-I0Qop}Zd;z0OWzTqa zzFIMu^ErxfeEdT`A!Dcpx_&YM+kadvpbWR*JH?kGORb|VV9N9M1K1to2iP6+%?boV z!$FS)3JRJ;Lkvj;O%>{qwa%0aH0zb)(SRun?!&>bNis=XLjdAhb%+c(l@It*rIBei!$Va=Kr(3ybkwI2q{JXjouRbg8PTrCSf= zP8qpp3Mdljf>5rV{!)0uPa-V!`g>TmJ{w{-q)6=?+tN}X?P!aP%gzY6r@ zdQG_cnf@<3)9`sh`!(2c#>rxE%%47&O?+3mmq<9TsScHo_nw4xb~{Sc8vD(v{8hZ( z5{^7FMCZ*)*7!DtKfdahhD0@c&};7r!T^W&W~Tx^S0h1<5hBuoSZr_XRd77+bD201 zVkM-K5ooQu7Qm-qCkMC8h6OJ6NZ1OipLAO&aj0&jJ~{eoFut7^7NP?-5vwA?o=n7k zG5#-0;yirLNyt^8DG*Q4wJW`3MmW?lnb6gm)1daAujx_8luC)<^GG#)XXmX zw^@Gzj?BnM@AXJXU_0h=F{zB<+U@TdO+{3E(22K#-j~$wf9BH71{}bp4<6|}PoPaw zX8rcuKcPs?xhqCkWoFR%pEaT=Z;~8bovGjXQNp(? z`#t|PFG|-k7_6?^Xh!$%UxgP9&Brq!5iD$;E8hqZBBX8GX9=)$`>^OB9%JQ3!w`~% zt*3^CsAm3#U9R{W>?hbK37X_@LYhHTL*02wk-;i{ZP*p2@ve#?cVhpAYu(k+$KHhVTTjp3cO`7|8HnqJu?Ugm!sB2 zZZZ5{kxZWk_;O+{VmsK>;;qI~$7sO*M0v1HeRh#%d8kNe?BLj`^e@P?*9VilBO9$Nf`VQ+XKN2J!|L=o!J^elx>)x0lg~gk z-Y)%{)N`eOp|9E7$$$Tft>P*h(}BGFG$-HTdf^cTW$U@I_#pzj)Ge&OscmX6-uj7CcYc-^|~X`PS8Tp z>-GZw^XP!3&GFi{EqOI5XfaiPIO5X3Z=p1$ARynWGwLjWto*MWeQ}QEkJ-1pk@fUG zwruC!m(VUX$Ta>o+Fu3Gi*b2y4d!e(NsM5AOhh?w4NU%)itwcO@V2udo0|4N-I}F) zUOq?kg{8-@@PF;UGbiVZ-Gyd~&-@*+%<1}t>Od+F<(`&A2^}>{CIM~aUxD*FrBy(K zc2HH-1{(yeT7SfRRa`_UY+FXBT-q$DV7qV^u}FkkyHlFXqGev6G=k5)H{gu87Ya z{`&Wjzh)Q|`ti0fQL>nA+*S|Wlw!l(p+olRbtGcRi38cS%eW=_hf@_4m6V6%C zW2!j{l&}x!d(E!2MNO)G`VeBsez&1%|2#!ot{q(AezR98KJaHM90dsH=~srV{apC0 zvY_7RL!ReHhe}rqVHROlvE|v= zR~q4d!WP_Z--IU5S!dWg6#mfriTg;S`6Z-ar0fkBVHc$LU9Gc?4eH9NV30KH@!|2z z9ZpoWWp>_MX^lPv@D4VKT(pJ#&vb#0VX4*O8M(*svf?ks@#njIZ`;m15=ly~77O)I zqFbB;JnC1}paCL;v}4eK_uMU}9fZb#;ZHk*rY&KE4tVl(kqPiO_&ejf_2JFi!}YJb z_X+K^@vFa~_dJrU9hNX#6Oggz)V*SEG_qD}4MZb6%rj5My4=PC*Ub0N@#5yW`#Dw5 zBvF0TldgG+jYgL|3PGlBf30V!`i_s9Yb#CQkg=GHs7tcfwv+h=3`UI}D6bUk*4a}3 zRc`hS9J^;$G9V(jl%jCyPv9udH5ooZR8_!Q2oWLT0{443L_O#!p^omo3nqids1l}$ z_PJe^bC{LrKad;Zi2XZ{HSsyhZZ;s_+1F|$hCj0}{q14-&CGz-YWKaa2hIb@QO;Wq zbT|Hz74>@t-s1WU=-Cv1g!?vhl zT@LBjS*?`<`EE$BMFnq_!dZFdcM5|!t|I*d z5UvG!CK+gCWUEK%&@6nP$n+CeDCItTNa5RX6YNU&DUNw3n-8Nb9BBDNMZZq_l?Hv} z?>}8QHn{6>SDDO^B8}J1oXxVrv2l-)@N;9DVg{$tLh;SCFzua=^9`<2onG^*-X*RH zp}Mjo>uvbeS~{l^-Wv``7hzqB<-8AbstHO8YWvV_Bmx3P@ENcq*MB--xWVA>6oQq< zWv&Y2b|9*O*&@yvvCZP2^Ehb5$D$}W5t68&ffgaz`}s4CG&0YlI;0j~RgB4|rE|_z zh`W|kBhPmn6kN3&yvTn)kdj6n+UBMWv!r?HD*?ty4XvhzyA|GJ+nE>G=W2{yz|)+e z{e|kSY8A8RBcskuyd)qx%w>fa9oTcc`$9*bS4{InX_jMF>L}tNbd&F~D+iOBJ%I3e z5*FB~DDCei`tL#DJio!|{?N6#z*s z!!V~L+fHN$92mrH=aH*H^s?PhzRf^F7>|9j!+>$IqpA9pA&q6k)8wBDH#BrYMl1oA z4Cz>#W+?;96z<{yF?iZ)uC=R0luUgIX_eiJM(}wCvbw)?9X)9`>PprdIEkSZBSXT~ zPN4Du?XQO@ZurlPaZV{NPHzTOdBq&^l%P}Crf_lO*@FUP`dM2^kss==`*cITM#R|? zU*0obbZ?vTrUSjeB~?h!uQsC@T7*O7_29$T$n04>*3Ldv?&{~JL7%9XCgc|yJ7?(Bw(6!c?cdb>wslr6zR~IF z46$FSvSf0?ou7ZYPm|*V@>IF(4~b>do4w~bW$!v37*kFziw+~43+gA8IonyNZ85cr z?msJizN?Iyz~u&pV8e9+G#^3F>?ge^d0%G9Fs?IP?KYc9^?=Qs8IQB1y@kjme2V>P|(l5IEhFEJw$uDL`bH6K7R*HGOEYaJUt;B2H3@I)MXTHQY3GK3h%E_a5Lr2KDp6HcSh2 z7{2sds*prquHqimI|Z%?9l1+=*q4u37ICH}X9xCu=|=!b2l)bv;Q#*26urf5$vSll zzTzZp+1I~BQ__z0)Ft{K7g59r4AewWgJwngnS;;%|3}lk8%8P z=T)Yrqdf@dz+ypfc~&3Gg@xem9(_|3NP7rjKyB$_hkoj<;~UxGTWPNTQRnew z@$r68EdpSVjXK;?vhXjpM<<4uXprrfu2UayuHJVz$n8oZ+DaNaK;L4rAv*{l!CE(n z=ZMJbp1F%SIDFe-A=Nl5h~EFaVd|f&n`@f>u_4sj)6j6qbYOtn6J)X56CY2bY;Iy_ zFpuB&!>&N8bf)O>aEyc+7mc=YQAQYE<;YQvA_d@#hmOl8BE%(*t;UJP?-KdSpx>v| z;6+FGj#$=SLv&5w-kRsFABW(QAn@y+gwjZFpT*Z|(|ez;H?a>oI47%JADyKb3NdePUk76>Q>HGsktJ<%hWrC z6RKQT4rzv*%BGgl76BsEd(0DgC!Lp&$1O#<(2G9!)iK#3a<~QfF)I}W-~M=^xl_Hh zPc*^2QV&)wjPESCi^p#x0lrf?PD6J`onxbsm-CVMqs4MsKN zDwg^40x@TC`{AV2?f2V_VIL{T+%r4vo^atxblev4I+~5{in!$x6-`d+DJZ+J2Fre( zl6uWRqJ7Jo9Da^=7hY}k78DkAIYk5HSXaX$J`da`x=kg~SlY+PUQneBe<5o$aXq>Q zocdCXU>^vuCXK2Zx4a3c;bU4AxHKlZFVT?I^KWH z;WkDv%~riT{9H5OXOgIg^h zthm!$6;X{;N|()y4_QDgpb73Ky0*MymgdwwYi@l9Ms$qlsDQKw=bKugStgHy5B3AD z2r@CrkBt`*j)wgo6}>TMp#l0T3&spY$d&&pP}+=l(iNxil7ZUzTCz!uLDMn8+Jxam zGME}0LSKRZ9VBI6>u5hfU$o7`5-fGp)i_H2R2%O`Sc^$dw;h9#TFitg-mI@TwKO+b zM{9`RjE4(6Jmm9$kvZ>6^-(G`-C=aaBzuv2rQSY@W=-cTZZQpPK~D^MI4{JSnbTm( zG5hcm=#FoUxz+>qU)j-|);tPJ#aIMs^<_craPw0EF$w>9?PeWj*H9!>BiQjH7lDz+3eJgAKc zHq(3&8Z>bO+yN$d1}eDWGq23Ox4kcwC;CwaN!r|HBqjED+XovgjGEOG7=*wg>msk0 z8?$vsleFR%6b}v;WOof$uRahXeBAhe9~Y-mj$k_{J=T;x%6=sRZ;Y~nCrTbT^fTYq zb5(Gqu(KJqnZ3e?0MKaZlLt*JMCQ;=nJB-Z0_O_??g4+IOmwySGWzE6zz{HL%`>r% z&?>5N8A!mh$#nk{O8FBK`!UB7WlxQM^T-`lulW$trf2^!%T zQUAWYtfEqW4h;fa`Etl$_1&W}qJAOzWH(($*XAS4rYNsCNLXPZNQ5X4`un^fSss@Z zQax(QQ59T6&(wxNXR=R2sTdWQRiLlbzr<#Y^^7t?UQHoHmhEylV*3wlJ6ZNdBTJ}` z^Psx-hC3X-LSxW|A|wT1O(>;S%Ra)_M(1%h&nQK-qwj9)5*gerSm52GWo6f_3zrnQ z4(nVqWEah^pFuufegy~Q|7zRHw5QCa7Y}K?TLd8LXeRUZ^Y_oik=iYDpQD;=bu6=H zMtMqTpkRJH$XDjMsvdt0ZC$($k?s7ECzQJtFrnLA2@$uq6TBLC)6Q7bf8TfA+C&41 z$iLlU3&e@$02d`}j|&5KvvtrQ4#Gj{sLw#!nc-YhBdE^3G{6V@=*3?gf(8^0&z`S} zgE1L5ijh)cc=_`cgrbny!EI*hnEwJw4|rcxW>IV|pUI;nfG&fIJ!7vEYnObw7EoE;q3dRWS+!pm!la}6mb7Q|OpH-piZ+C)guH;=~22@*pgPC$U z8_&}=d+f*s(;-}$X^>9iy7tSB~aYeh`DLSt4QYa z1GVKmI|EWOuO#lkf47|*xcamw-^qAYNw!B9t34ju6x(0PZZ+8Ey131i`6=f_cQS0I zo_k{WUYR+{LM_B^_-b(!cFigR{D&vpXdB>8V3gz-sgp>dUp{gqM7R@+n8#bFHCO(( zq1L5`fh0QmYJ+b8(y>ABdWd&}cuW-NgGu24MiG2p*Yw%>sQswXnSzpjRQGlHCqrL* zKI(xPH5eW!eByPO+4~HsIvahosV`}@bgyp%#I5J&p+;XF!`nyxG)RsGBC0Q({?jjq zw!N$^mXwln&Xeo{uEszwe%dt$+s?B>?-r67U?ovBkrhat*ValN#|~(%$Elyxvyng^DXWc^6`gIfErnInSSp zW`ms%V~>Q)m3%}If2|Gkqo5^>YmY~d2?A_Ad<|YaNkp8MWHISuZCRkIa?)!B}`} zHY8R~u{cp4A|$+j`0U@II-C&qsv>R_$wd9rqF~ibQOfi& z+q48Iwc(N{PBDNxU4e{^dm?}TzWvZHcC}r0Af(4&KST9tJP_;j_NX_%}gND?FHz6WEmX*qS)kmfh`KM^vScB(Z{kyOd9f{dY4%60Xtx4a^Q|pRoM^x=y_R4lE`qBk<2Z98q&03QUo5gw8XdoZBaAn1I3A{Y8 zRwz=ZH2-$a#pvb}DU{6{UDeJ`C~#q)M^+lXr)KxR6z9nDpoRE5~1FJ{pxxSXS&R@uMl zQEk#2^V>e7eFJ4p=`?cBsq8at1{vmJ$;B8a)rj{K|E zC31b4C`t(Ul0Z8$Mh;2SmC)`NA-j8lyGJ?HHu|ZBL02e3l!IJ~_S1%I>L_N#Z+-!p zqbMicOwFCcYW${f^qQWx0!ItS-1gzKd)V&se)!&27L`?6DBT5-fYtc(pxZUfmq&^| zu5~1taOHyMXYQ(uWA@IN3DB+Dt=}iIEFl!5w52>`|6Xs-G})6G8%;Nvle?@&iaoQ5 ze$X`JeXM)HZbN(tk4UTsJ{@94r@3iT&LDK(L3yD`n?(hAM03grZJ0k}Eg=H3j2I3;o>w5NTW4SC2>9#>#UV{wd zR4w(`On@N703XS^u{cH)(p!e#>k+G5?8X9K=BV0QaE{x}E0+iq*5Y03)gv(MY#s1> z-ocQ37NYoZI-AF7yW;g2I~8~kW&44{vI98Zf;77tHv`~64?c$ zYN0GRea(I8eOk@oQSJDy|;^(wXv;52AWh#yJc6{3vo9RH1 zjDr-(7w9_`NPHN>kjODHdOKI$RW+H6>72P`A0j6sA}S|3pI5WoaE9%OYR3Sf`-IIF z+wmU)uQ_S(p;cRHN{uM-lu#Xq_Ae9bd&6h`0;0eNB-zg5k%jcJ(0-fNP zodcN_E>iN#cRc+flLfQsRjJ&&eCSqH8v7S(>Hx3F8QoEw$$|V$x+j3W7p>?=UGt(N zgLfD0*OB67Xw-^ZF?+c};y+n<9q4a}@t$Dt8_ba6)iQ6{&=a{>mfH*=aoGBa%$>eH zhCYWITLHS#9$n$D(h)RJCyCy%l+16ZIGnhdmF8N$Ul{bjLB;{yYzuJyv2REhfwq2) zGmEhPs=Bu-1Bx9rLZG%~IYZDCv`rmO7v?f$sW~fAOyj%RUK60zTaB^l)U`){&I@cm z6nAXg|2qfGD#v30a-XMqnYybP>*==1x|%uuhRN~?>i?nYE!g7dqHSFq65QP(Sn$T( z-GY1Y#@)4XcXtgg!Civ8Lx3Q`-Cb|z+vn`(++WbuRjXFbImbK3^0zfQEDGO%f2vd6 zg#`hakIa+aIA)OUhGZ7Hdy;A?#?c7OsL^4-t2qf_#8;O_N}tb-W5$K0`UAO^{Cn5y ztWHsFd~*B7=Jay&H}2Z=H?Dv@9LtxEInW=<)#hhH5}1W1ERyVM8;QG&6rhe_T05#v zT)BW1(ui=9bkyCqRZ79~f(faP7#}+AFTK3S5=qx^vX&qmwLL5-MJck`AD{8;1tm|o zJRG5YA?((Ro2m(+_V=qp6yvrME?eNd4=Kb5LyAf7&*2Ezcz3(vS`+nVDVi7!{bSk> zJ*x$#Z*}@_PQbEnYV|U+({iE;*U25*63MEq;hTAvUBVgr$=xU0kJjb$@*avJ)x+~3 z+;)cQdqxp5GHAvEk~^Gtn|CZxmn3M(cUnU9ZRmH;d5}4F39P#cPQ*J$l21HQ zMjVWdR)VN7IT|lm58OxAy@R+7mA^c4m2kaPe>rqqT?Ou$^=js6gzfr2;cvZ0f%X}F z7V~4n2q9e=LkO?7)>M6Q_(Qv`lpjP45mt^o{t15QW~;)^D+btm(sr}>>q8|4joi2y zy4_Yf28lv3TjB6$0&L58-y{iGm5b3Xg9ENsZvdZvsp9JvKRFg5RCYp@O@X+GPN9`` zmXIO!KeNKr%S&}Ey0YYwc`1Og5WO@Tu1@~HX*udKQmOp*(nF2%p-Z>~&YAZN{ukY! z9^u3GlzO1GM(*Y(2rVS_Z_kdsa#~7Gl$Y%tbXHDu*pBhD4H6h_7y9Sj9OhPPrvv9{ zu1kVWC|@?=)#Kx5a!e#qGdGzdoakt{E+S17KX7&K1hY08zhEqaTTMOD*nK0ad9h{c z3iFVFF7&!e5DuQzy*C8h4=F&3*A4d8H5~^>`z=w;C0ix0?XVjhMt7-~|NRGKo*x81 z)pVY$*N&Ow#w!Y*VC0b!q|6|cLj}C_C1=;-4ir)7(kJl!aa$ZMo#)2SlkFy zlRYX%V+jg4B9C=qN1?>;VYymM_>>60WzD=aK^xke*9LJdp=ouLiv%(lhWcRhDF&-+ zp|d&MlI$?%4y`SA2+;!ThGM<`0#;VvOS^hq5iJ_w!_@-P9rb z&GYXf|34*{3x@kHPE)>hQ)J|rG>QYAo;QCSKY94i70W=TAv^aS+1N{>*}~c8%dD~G zml}ciI~1PRU?WRwuvmuGqGIR>k@!lE;dJ`|163!g>MHfLMMyB663v z=ic7FShPh)(F*HPNB#Y01mhl5@~Qq=ON_85L2mbRy;5@1lupa45qfA_?h}k4deA9X zN`q;C>iUcsl2rP@u%qH?VCr58tYww-#(^nNwhG_U>6;?eFhRx(HvMXens&Zx}FL2|RqdbI1~Y}3ACI@;I#|op%a7!04NM z0LEI8R^}&G<5_NYNVx^Gh&I1_%X|raT!s)VC~yxS0Dg`0!R1acDr6Dp9x+Q`P-;HL z(+AQsy~W2&%CguMKlU-WL&ZQvR%vxvVO$Q8)X49zbmBx9=0(2v%Xa=m{z&KYwd}(M zvPhHl<}c3q8k7aAtA(*+M2*#?zI4Bu9P~BTGeM9~X8umq=Zw z%`Wdw)cNm!(t;oRztKew!bI6k*#&sV+n9T(sZVrTu>XVe>2Ba?zfkWzN8Y^5WI2qH zb-)#8ddb77tg3at>V@0M{o1BJ*kM1|QQ|WwoE%(K?eaPxX?qnpL@&uI|L?8*_Y;5r z6)+9w)T}i`5$RmA4LJy)|M)>jP>X|Ifptg&~Rv zm=&H!PS3}Rh++^k(%^M_e}qA)@+LHsx*6d1j^Iz~%(J;Aa^UJzt99ffn%vyPz{ZaI zX?9iErHF(7Rsb;1{i_55j%0}o9oXtE9{q3>9>&o#09h#UPZMa@b;diR5^h3#?M%YN z>Tph>9pdbKDFV*u9Bsuybm;y26#QF5|J_?qJ9@n9`1bbpSCg>nAPp{VSJI{>;iy{S z_X0-gkN4Tgx1KJBp%rva=8cNd5ex~6!Xf-MsTN1nBAH_rl$z)?qqv8p(uAx8>_w2U znAgxgTDjD8sd^SnwwQCrMiUXk^H<86%da}k3MYTQqzKBqwZzgdl7~@j%?n5UC9D_g zP>ZPkzt;kJf?ns8V3ja+{9Lu%S~1jEQQgZ~#Xf^U?*MQYa(jE!8tl6x9ZJD5kI+2k zsrQ(2OQN*PR)GUpt>gNZc1D=%EB%N`9f9;}O?l<2NAgADgXN`C?$qvDWjl@t7W%}> zVLwtv-vVvngp=nnHsyoyXoHN*et$?)>0u*q`SwL2JOVJtydC)2KTh;Qdt6c&HJ6&r zU+A;bMrBJCiVN!TC(zVMX3TBC9W~CD@X<1vd=D3M0`Gb+Var!9 z7R#)^b^|rA&7^8ga=+~QCl03HN?3NYr3;XZ#0zJ<+CsZ{-`v>o@_$I7W{m?vt6|}K_Sw=Cc8XN5zccHPD|AreX`slVLIRs z>?gf=Ayq<(C_f_e+U4J>VQ(bQ$+~yMI}j6&4ccw_rj|zn=wfXnhadOSXMN_I?(>>< ztvi*yJ(TP%UruTNQTWoSwn_&RjR0exKbSVpJ>EMt_|dna-k|)=Lo1rSJ9Y=3&Cu-Z zFHaFs3RP?r_k#86r>-voaT;CIqCUn!`|_RXuPkkdE4bR6AM&K1%RZj19v`QA|L7im z+KumLa%UU+T7@#OYu_pmI94MQ;6M&>goF1%t`c z3_TO?+9`(l*ywlr=!md8Lb715>0P)_-5I~pX8_=}7f|5C ziSoxZQ8_|5nowmXn_F*?T&`NQg#7g5GaZagK!DJ4%HncN>9Y+)M3;nglw3ngfGqF7 z3DBGs%~J|SG6)A(6SPhFQg;(J?C|}q1F74wHQ@fYKW(3!DeIJt-1YWczBVqA^S(S4 z6=&hW#m*1#OMOJ+DB)fF0iV@R-ih=Ht9GulXxFEv>=; z%As*WkNABTV_K_gjO$uI<5Z2cM?l#LW0_^^QivS?U28xq2jj0wse3AbD!k$kUcCxuB$%4{qJ0o3@Zl$1mQ{yemrg4l<^zK{!kxHNO9|3!$YTa4)XW zv>5&LoQ`*_;9TAXbbUG# zNu9>&nSI2ElK=AljllR$7AYj$+#?3%V(E>L{AOmMpHI?=m*tXQA(m;4zF1TOZ)c{kk){Hc+y+g$k{T$h=KyHnl?O0UVxQQ z5>jmODcxvcToaJcf#HUGw%0|b-LY%7T9nk+qS0>=#gb?E>#WFEcLYY8%ng~TSY|nW zr!gZwIm65(k%|_pNT^3Yk_MuRQ9r$=_Cv>rL6ugc~6lS#;g7AdpzA+K>mLq9$UycX_I_~d`_|I+mB6!40E=e(fHdpqM zcN)FSNY=FnoNY*L+XAY5$bv|Ii67GdPT;>w@Lmq&>2*B%Aje%{4z-#zy;UzzYK+D) z^ZfjrFvll}=5W0H*2X#~B>Rsz6eizNgNcUAJbGY4fgQ-*V|K`Ccu>whAh^9Jfb>Z3 zOtPs;>M){ah1Q6dfMeATj!Qfuyi(kjk(0HPqdfC-!Q(`}BWNkGhxJ)>pDZHWDKO0p z1N09ZtyB?MiU2^{3cvEGAm8)=piF`kJ*<$ScO_nM%by&4!A{humE8ZDi~wAy&& z(Db>hvh$P{w`c==W`Q=2ZN9rUBz))!O>%clykU<|A~h9209@7EJQeg6k zUMsb3qwCCociJR*far>jf)3!>K z`ZqBmlcs~}nU0u}2NedYZ?6ASTU=7+pNIS%=I>#TOA z5Q!!^fZ*i8>PW%9!qexrC_p@zU$j=O4Cvury^npA$?9=8y4pPvfq9W3o{Y7cRfIgb zaDRBeHzTPX?(Jj9C=o4--%Up%R!{7Co#=h zr%+?uvPy}9{XNr2zI=me%RzpfyHc@w1A|$Y7A*fAv4qimu3jmTh)ym`6tdsxY~X4# z%2-wLnx6VjhU2y3f?8Y!Mr5X4pdX)A1NcdnaW`?G&)a`y_BCf(y!7Xta18F-@0D9w z@9jYzibw9l=c|T6k1Z!PWP7k_lQpQ;QmP1K)|RxlYust;AO7A+SG@@N=|vj7puJNI z8d}!SQxc5HmfR5G^nz+VaiJefSZAnneP16DYE(&1fS`>_B23uRwoT1g4~ha^%l+|rAAb%{lQu&Ew&X=+e**dWs(**C%V(qjmLdMhs=PVN~<=-{+{aII<+Ed zYdBZht!&FLE1Yc4V{f?gwVmJuIgyH! zE)P8)e72Uia9hJo14q+~V{WzNQR`>l-Imdn4LLYBsu^<73VWS48Y|!?@E#*r$G3=+U%j2@V;qe-yZ6ND?JjJxV4&=(6GzE z+?|Fkl(YV7;+pyMyuI1PRWRw8{Vm#gs5J@@4VXg-?!ZAHxyI#@3dJEe)m+V zPAim-5tp+Yd&MIUH-f7>!$4mK!{H_Sb4ii?FB#558TyZi84a<@XjIJ=9;akvkgtQ# zXQ;LA^!$qHLHi5v5vqHf{!FB3FAE~YzfzzpriomBX+%~$PrUo?7YlxS=o6kQ1>j_& zOZSbjv0CR3-Y_~JC`}t3Y=7i)(*!XiWAjCfHtkB7!o_G7qan~i4dJRwAv3!Lf_~g( zdqITWB59;B{LcsE-v<^A1IBnx7<0$w#XXMPWYpgQpk%O3kGF%m9FO0Qu6H(0Q}Q%X zz(xyGhy_W5=|$G$L3#F16#KPVepCHn@wZSE zP&)g}HU`Gey_1)^lhWZq?xJ_>9<+_95xG(GX!fI+@13dN48RcZPcTox=%a5DmWFB1 z%(`J9OC_LTApd$s`ZmF8Yyp14cJ^yO=Y)8hhYU;awZH0X4!t{F*V)sI+nqu;5#vEw zg0L&;v7O;o_nGbbY6g|^i-nC#HNS4>(d{nojxE-;{-+mSMg};Ca(k)wvBGCR2e?=K zdC$dDT%0d=dRY_7z2?()rx`ylXO=H5Q7_JzvJQ*3beu}$B1#{0MUrA@wxR^%13t;K zbYBFc56r)cNQ8_ty@f^^$_ti3=lfwp8Hs}#T8RUqM!Zh)u-(^k4;LrtV`PaENB8MZ z5>3rD^D@kF$Ac<-DcLRx5d%Lx^jDq>HGgGDe%~I5NawQ8J$mhCQv~y4nyEk@yW4!8 zV`|nJlrq4Spy^ZPh!j+n1@AblH4*&pM*1IU;a{Nydv-^VS7aj#2{Ulmq5R zz{YO5TImq8*QbL#DYZNnPCLMiPq%xB3R7iPV*FG4@V1$qp)AS*H|EORu2NOJ*7^CV zg8PHd`OC&wxyu3B7TBKhC;mSP`hQ0Z;VLkJce?|Tpw!756Z$slC+zx6@T6@*dGo%P zs|A`kQ}aM@OqrhaD?AfA6|&0?UGI)KPg^eetgZ7#ZEtbuI>us65nA{@Gf(U>0(I5@ zGcRZsCu5W@HwMea>84sc9|Q0Nk!AXh}o~qD|2PR zp?aFB#Qe~Ye6!5E$4--k$5JNmnh%Gc(YApuNOaJXtokJ|>i^FM2SOM~1eu#X6l-f| zte?>DY1ePEkOBI?SGA3IGnev~l)?OV!~;slt=8iA-u^};RYsP?vV9`j#v|8{!Lbh( zuPfFr38QAwF_38l6U6e3M@1)QuQ8IuMJlDh^9JAvv%BvGt6T1BGZv%;J}}!t%Rmuc z%aac5@>R*siLE+|0@HQV$_C%adRz1OQUlG(q6G_nQ-~T$giclfeEZXtlsYW=>-!tENzS+@rXp)OcBJ z0ytJ%H<=Qo5xCO^+@~eEUb`sh1NkA>Tc|%bq!`^(%QlKZB}_UD|C;vQ@iYb>hoQRU zuWv@kwl?Af9rKg>h`dr*TjuBZBX6cVL>QO<5!;wkYiP!)=0>2n+tsiG8S(Y#$q3uD z4f{$Nt~<5_6o{&ObkkXbO2jNQkEe|Y)!~>)9eK@4?_#57 z;F%Et!b*1SM}!jG6DHvx+QL1RzA%$de|eFc*Cx~20VlKD=JZ$)evkS+1vkdH5Ev?r zB`q-<6rcn>`QPkDLP=9R<4cDpDl@wys**IsH$Ka;T4Vkde!(Sq0_J*io< z3=}+Zp0^Vs9x3>HKZeRtUrr294xW+CyMDSsUO7x$khpxczJSm76yUeENK^Z`!*l(r z-0STHy^-eq0KHUIwq$mRF!{LJmDFN86u92!W335|8EWa7>zk7oGz86M0Qa@BvxA=> zv(;S3mAKFLsaDD1ETp}WaP!qq73j z-(38D2OwL+E($SuM;^wYS9r(8z3SlhZXxWk5aNby74uR}FhOUr&0o`zMby=T z>3H+o+V5ym;?*Zq=XyGN5XP`*n%bRtrXiV`i+U`VqFJ$9hD>+UYy$ltRqZJd6V{h2 z=;u`901IQ-^-GJD=Rr$G_-&bUzg+-$!0QC^oL7oATu*%MP}0cZuUKMqnTM&Agl4&_Fu_+&oF-*tu^itA1Tb28~J`WMV;i%@*r|R?*WZdES-yy z-@UTlTff$svsj4l%Gb2 z^^>wbhE&e-tjooupz_{EKvif<*PAHq6mgrhJ#k5zG1kcYh=BM$O z6Kk?~a&G_JzuYa_@83Er=4H$e`@I#*?Uwf(FO7OW^zjA-9}1Tl_Mqn*7Ddv(5v#xC za*ARPs!T2CnEDC0CV0#o3x+YUOCHiNik^jbt#qF~@w)KM^nLeO_ebH2w4u@-c^*;? zkecTDF?Z&vH0istR^Q{6DeH*xPmP?Q1pc?m3K-U zQ2UzZLtpz@X&5@_M=kf(=ifd*8dM3jnSLN95c2X%<_T#Nd=Xk{~ zFErW{@rj5h>HWopz*UY5+P-t3Z|Qb`V;_E)p@wT;ZS9=`d|m$NQ9gd)>$-rhqyj6N zmGMXt0+u2gRsgRFj~UYq4cbEPNRS(3M!znZO3MJ59F4z{fq3fZHvEWWP)rBpJa=km zWVSrYfG}dKk-9SZmJ7_pA6klnqSimI%{9JnkgXo*Z+)g2-!EcZ#I|-?K;Tm(?y7D8 zVq1&2ohg^eH78^}ieHLI3T8`mm7E_tZyU|CW!}$sRlflDa(yTCJQ>qE1Pf+INh1Qs z|0&RoTRINR#4FY^%ai)_rDrEz9xqKAc_R-QP0ztSoJwz|)mRB#sMkiNBZ&X3Lc4!v zM<={BQP?+iMu`^8=@w0F@3yxw(esp4Qb?9;yicxMkzniVQB53{7Ym-p1V#(RLJ`dBH$j)2}LMTKOv!O$pNEfz4Jo$CI0YWH+ zH~fPt>%Y$3yO*$QETG%!z9FMR9qW8XoziG7e4da6RM*O_!&eV4kZ=$P0MY$rqN9|A zgZ9^FI}c!rU5G-9hlt~6^6|GZPPq0PJn+cnD4D4~gv}Am^rX$X%^NAlcQ3{3@e?7) zje*@JUmC*r=LL$(slcWgz2--$Of^(qyf5ImbNFM0c{XC1@n|1RIlZI zWnsw;%}TEc;-Q?MWrR-GkpZSIBi^Kr)ar~7g=dR*%lf3FggiONi3@*Nm7ZJE8V!0z z^yS*^mQIS7iE<79>HL2Gqw^~X9aVcA*Nz-wwZG(dhBMTYfTcI{{eF+Kz~ELuR8*8y zQEJz$?WusY3~}^pkZwZP8=vXLBZ;S8-7|2*FF6g1Dm4wuwX%%%Y)CXM@n+;?^g87h z^O~T3LKl&`&z&x6(1a;LGyojXeh$?mtu{gkD-2+YTFhZ_8`(kCJG=hVM+P0;vm;AF=G` zxGm`+C!$q0)HZyfqC;soFkV-93l!OjtS>ZrMWZKat}*Qk!t5h{b4=Q1023{k|0g^Xj@)vpbDV zvt#1{wSs$rq?%Tq27aN4A$mvvdzB9PGc4=7a((|+)zj)Y=T(HHXdJFi4ZWEHg&H?&6rx9ZNNeY&ae8UG_Lf%w4&b#+EJHgkkiKm8vw@ z$_57#dJ{%nYZ+O&!~ERfK1aoXiJFJxdMnW<>3FN&Lm4-WU%X^bpF4MJ<4Qb# zgXd5E;YbWJWREV_`K>53yH@21uzH9-CMam^`em+n)~xxfGN4ZoqQrO|ZH5qCjEq5b z6Y<2}NQP%aw}J3Uiuy9qKGBD?b}g#vmvWrwCAnNPp+WBDIOjyr6mOL}P=4GL=v%;A zYT0xBaGn+5{|8Rq!e%ujJIH2=yOOE4d4dwn42+UE4jX7nQuEtNIp%qOTfVnE76iU9 zeJ%)i_nHyKrz*DN7}P3iH1nA(%XepN+hdg`_M(2yxQb!!-ZNzoNznHd>|CZB3TY4wC@J^@q*g+p?mKLB58W$M-hr$hb=On$XUyl-5vY z2>bV=U|uVL>quD`7Rx4$eK%C+P)B?MlK}i9`?A1%mmyy>H0q{mjtQ8W=!%7X=jh@+ zi%Elgvhvx++aljsH~#~Rg$?!Li-YIQd-+PZ=;>?Uc0x%#akAU-?r?Bf9M^&Leq?d+ zZ&wt`x&21sXX~WjCbysGcYneDaqB2WdHs@G!|u$>b0Ao@pC7W%f3qx%7=!h`q~qx} z?tUCOhk^U@RF+H|yN#TD7!R6>7XHjrGC_O+Jgc%k zKXPPW$m-q);<+|#6r=shjLmzuew3f}Z+n>>7n8Se|8maMQ;~OMv;UbPXxwt^O|eM3 zaE>G4@AE%Fe5i(+(k`~GCR%jd4e23+53Bi)=7Wf?TNJ5i$y36zs)hvtFc)yetWe1P znc+I17euL3h^M^yZUE*fj!k^to?LM$>n|69Mtdi-1n1sGw(xBlzbbooR6(E|{)4fx zhwPefXA#1;c>nOVY4)#b-xh19J7%_&7fIIcz&wTLTkZ3Nr>}X)0C+`>alS41`k7n(y3;-jhM2B5KY>+pe_bTZp&20)HFxHs3OQ3$tQ^$B3- z7KHEK8gn$xZ`!iXMe^mp+uSFNZO%&eoGfB41v90GVyhG}-;SxbyRW*e7w;#wZGj2d z!9vJYmicc{p;?79lq?tfp6Qo)pQNR$h;D5QN2hB)h%CAsQX{hI<;u{Nm(|PAn}57J z`H5~^&cfXa^e`WDm0vV~>EA&u4r^?$?4^DSgA@^RG|_+3$#s4PJa3G)$&fInR`*|7 zXLkqX!=#i35o&^+O3g8JiJzfh&=s(Q`-R3ItxhdY5@|4Ld?Z@V-P~CG;5Z{6pC?31 ziu5tH|LJ#%ez@XfqW;0vbVvOq;XL6ny9BgQM~s&=qvAPc!lr&+zAODGb02ffKzh@^p8(oF;%yKw z7l8y(IDZx$aX)=?D{%JEHw~E9om6W&aDf4HCp^T<@gtjsMgoAspB$d|Z2TW#AiV5V zBtzwS3E<;`osw`s#?@KPLyG?-?aH z0+lf~4Q6VBC*TK=a+h8~P(%pg_FIf&l`ZNX6OQ_+7+2ix?@4?(Nw!OKUqvTTi4_1{ zZiQxujO{7a`-na{FGY7jP3bdGoz*3W8N_9J@m>6KgteN$=x&3O`=fe~qZqhKmN2AC zalec6cQJ*pGIn%@yRIwJNeea{?EmoiM+A@vtH^wYup3PO&Y4>*lkZEAB<) zt1Q0Ghg~uRPr{rH=nSetUGS5 z6{}94p#+fuu42uanj75pd^JxVu8aj!XUmXXI$Xatd#dnj-eZ%z3(Y9+JWFuC(~FRQ z|92H|EH>Tv(=D+P(~9Cn%`vSw#TTfBn?k4eoF>k1r(ZYzwCzhF{ML>`7{Q5x%hb!h zj-?}so`Yb`L;ubrg^ej@g(QY}dnSB=hs?n`6+CeH+|S+{=MSIAV7S9}18Jm072s~I z?pw^zLVX!dVeyabP~|f9c0Jt-;xppZWO-#3XppVDr@Lc)Ti7`K4Zt7F-n^ZwCw|4i z3`2x>9q2D5)lE^d4aNbPd?5)E@h1s}Y3qN^&(`MwJA%KFwk`a100VQC4&=mod{;78 zz?mw@P(7{z7m0C0ExY4PfPTRkUKBzxY`IGnKG2w<@e3;TLHu&O^JwHBw0OD`{OGyg z%})9A@1+4Emc6n4I+EIT&ym`_9Y}44xG_htoxY4S1)i=oO7{C+R)!Gs2Wq9|jNq}_ zys&dD{ZDdB43k=>Rxd1r9hRY#j)vI`NRb+%Iash&-2sy2Z9%X#PlaUCM>`gI;8#&e zC%AK1x`=y|=V-M)Vxn@=s-W;D%dcVZz71lg8{lT(RtuQ0%>d4>vz!mh^;O+|HT^o; zTh<}zIa-j24F#Uhf;ZS~coV!}^9oxUa}46Jq)jq=av{-Nr5{QoS0Y1Yfvy8AkU)3c z^}DWNe8!Up2w17EgO^wa`jU;mZ>>bEtXui#pR+v|JkCbLpoinV#9ZVxA;YetSZ_*g zJpv4VT8Fvc(YMWVza%r@ei?F>R?cfSxT%Ehqa|MFUZn#%tZ zM#gqx`Fl2Fva<-AhuGgcI- zVf0QdKgnbVmwOkttk*lphe*pEqR01=@V@5H2}aj&Fp1kY@*id@%PG$7hJpdFucybF z#YH46QCSg%5ZI}U2b_m_jB&mQq}p-)VdyWSfuFlAh}?+RO&(SX|7ufWn1%HkBqF@Z z>Qto7n`(v?Lh_%DzDZru(WA6DR3@F@nk_ML5o&uw3(3xDlENC{;C!v^XFcz8bVcDp(TLSzurG1^@>^R!Z}LT~6G&vUPl3O$g)iU5 zMlI{VjX^+GGU+I7xAbYy^Chv~{VQ)x75BsE3SH6nfUlsf80OhO)rE}}j7{iw2-zbd z61XuCL-$LmL|S4uZ;$vYwauEaz63yxP2uXA=}L1Ik9=xF-i`L}EO2f>f{;}n!xFgY zV$a3xN|ulFC#^Sv<%*lj5lgNz;`hr{(iyw&dvlHk-0ltm7o=tmDb~3sVE-S>I{ri9Gj26 zlcb(c3{0JhEu_HnWU|mfgc8eu@e(- zXl<~ETlCou-_$aY>4)aQq;&;*Y6DHyAB4-<8d_nP_BrYIV0H}eW=K14&C1=iO-lgg z8sV_zOWrk~``09J_CcyhebV`pl$_(+$I@2yB7Rf%d-igVfI&7JS~Woi={sJsoh=bZ zHW#h>8{?nr^i;HFVsKo^Z|dgunm^QPz8WDTeRuFzLr3;tF-%elviiC4S9}kWNaaSg z-V1}FL4^Iq2ae~m=G|9x1Nk(}uqnim0qvR!DQZ{XF4Gm*PMdu#!^x2m$ZoBDrkht4$ z9hUKXWrkWI=N8hx$=qmhy{=;tIeyVT~?~DYvgK)`F*YvCVd{KMHZ+%_l z>g|}$fbZwL7{Pxf=Jk=b@`Rb=oVgtDVizM2<%aU=22nCt!eNzEpuu*kQffTZ%*d@3 z@Iyb7BG-C8BttE45wcaGh5?)@lh;N*=c#5zTD`@; zhHE!WSlR_T@$VvuxD&f>b+#f6*%j1U#JB3lgt|pmOIX2`5XiftZm7U0wVf%C2_6W2 z{mzE5N8LutI9lm%dax~GCgp5j$W(YjQ9mo4HT8yXIu6`c?F~QA{%Bp&OW77US&Wq& zTZsddSNbImD+mq;s`!`in>cQUdJwvM<=CzCFze zRbejQ8^uQqJaS{DrBGJZ!GOJfLxr|sSL^rvc$*DPgX_vg?J^wqJ{O0mxNk<3)K!W% zj1maWUt298IsMva7e$SoD5y@~AEkymkQzJRJRcwD;u79%dB=Dhy8M!!x$kCGS#O^U z$dk)77AvN$L<*^YAGSNC&1?!LU27O><{mqzvD>^1whfMGC4z*yKcsF2v)D%&WCY{C zwz`A5qXyt2(jQO7lP|klFS|puz&ReFdVIylts#WqC>t97YQ+X+0riv+1UzJbq2b}K zHXXZ@>5p6(%oT;2C)oH;=*7;zo9no?t+{h=T$H05Jy_@3bECnT>8BBLRcURu?}w4~ z#(m;G^f#l=4#NODUE_S@^TJ7YjM~_*lpVVwTZ;@JK|puAL*00BXm(9HuRtrzTbFT1Yd zDgs~inm-JUyLAMtruTh(isR79LQAR%$FdCKPY{mV`q{=7*e%_T)M&TU@wc;O$^>XM z6i&TIJkrlVX()1ke;gM$C>6>25ZYFgCapa$qhk86cQn0vD7`z|v~%~Ii5|*G&iiabm#4%I6BTvaaGM0YceH1sqZH#^`!d)RHfQ z&`%B7%y)fWPT%HoS-UnA*ByBbU2qO!ebII>*rkMk2WkwE)4;tQ7EW?!&&0@=0hx@( zB}m1eT{|31P#}??z@^}+OWF;+8IBs12Lh>_Uy)$@LOgnArMgw01l4LXA?6(~5Fyff{;rKoA>fK4}r7vmR?VNdEC!cMlbb!gyqh(Ot^ zxk6vzr>sW3xH@C_HG>O`>kJl6u;#o{uH0y@>Zt$b>*JuQ_G5>8;X!MxAC!xSq1pphvhPrqIV|Cqb!VJDz(ZHy8}DAOGw#D@Av zYB9Dq3ygB*%`PP;J4t>st1^F#g!JyuLQ-_lRz7jy8pDl>#nHq%2`AdP~3SemVJJD$|Hwc zb;KpuAh-=W)+9$WfB^%8dUJy`R7D1|9aN!$$;`E=iF=4J7SH04vw1282EWQ7vXqml zof`+wtG24BmnoJ~>C52r&`jh*d2W$Q#Vo_c+eOc6qH=f6|325Qdg0F%2@;jwEMW zB}!CGdqn2h$Yk~Wj#h8bFnS9+nXPjb!y;lrNGau=5oC*UWO=ISEAPTid@o;@Unnlt z&X4$jBUnbVp6O)x*4BHoVQ5ahN2{ zHlM$+w&OY6k0}c0?*$Ajk?8DSJgJnHrK_V$Z)X#|TIK^mhe7%{bJD-vq!F>1d8niZ z7Yc}=WcttG36SgaT~~>=zGM#arC?>;#-x+M@}{*)^aku@Qe6hLMyEt~EeaXJDrU^*VC7sXGp zxY)Z*`^-=g5rB!*C>+y}nHlQOZ0fYh+fD<{NTB<)z7hKUZBh zVi$BWMm$h=c(zd{@j{-YPm!;Ytp0sv;j_P}yB$ex9@;II1s%>tmhs%Rqp zLqaU&CNA0qGxDd2(|1j)SSQ9x^oK=^M%AWc#h)cgqnaW7{rbg>aiDZM{`O(TSHS{7 zoqPVIs2cadX8U=J8`Rg;o_`HHKP~j&#uw-K^#1Vaf$MAI-3xu)pZ1d{j@oP8B9k|6=^MuU8yJSEXiOv z;Yd%%c0;Zc`YwB)K0qd)iim`^pTeZKoKw?enGxsxZv)G6jgUKK;LO%}(H_PiJgHn_f1zSBk)-}U0TIbvsh z-k7a9j7HAn2te`$7)XJ8vUbUg_zUjxI8hH$B%Y}$yI!Ef<5i&+=&|@2IGe;oH1rqj z68k≀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

sIJNElw+3TXufDSrBbo)iTJ z3S@Xy=y(MBAaW=dNFV9z`Cs zh2|yS5>WRGTmQ-@3|Cco3oD0v+)!QNPa$h12qhQ4nysgT82hR36K}BPuL>W-T#N{X zNv-B@i|)SkT8-*JbLfc9ZzBXq%xe*i?5beOI)WSj(gLGlY+#X;h5!|8$(Eb8*U(31I846{11;ILHZmtv7$W?COfl$ zklTXdgqsA#z7B(Xb*S~9jmba=gQZ^*lT;@mSt%{V*zX%(>8)!^X|?YjzAbd$?`AI9 zm_3(4bG&VyufE&<$X?~G9sX)brw^A1-7H!Fl%T0-`eoK%EENlI@z0vY) z-yV7yxgBD6q5z(z7}g8F0a4yT%6({22)~Zv&l!{bgEq%*BG$dR;d2QjwZjQ#l^G=@ zAzUMnkFU_COK5+<_`UzD%F#2GV9l2;4*Q)SQ}ubB1PQOL1E;xdHJYe>#ykieXwETg zA1_IhA7$qec%D}8umpFF^7op#v!zk0S}kL~u}Lao+HVh$6}4v{Tw zlxv)!LEkmir~FA?*38cm39(zWR}jzPuQaCZo~V6;%gbS&7V#&^U!NVy~6eGk5C$p_`5wxB0_v}bXhhe=1 z@1>m;G#S+QD_qoMxPDP9oV}M1QSU26 z&jng<&GfAIf`4KQbz+u$%%q68VZCT&1-u80mTKIkz3lMg#Ma4s-Cs~tp55;^q%9w7 zKRbuV9C~)o@O3AF>HE8fqsL2hPSwZnYa!&lGyCUrX%?R{ zj2t_YZzO-}p@IS!ty^A6pY&d5%t_ox?*5_KK?$m5f@~>nGQQ2xLDG81fdWUt?=7t7hWJ>1r9YJia)M|h%C~FRU?FAOAKZ} zz*fO}cq1|87P8Xy6LOADL#)^2Nl4N8$xB|Zotojekzx(3KuG!2jr`Ae4~}dpNrI(m zqwj_dg`S}?w?^=G3~z`PFx^ng@>Aj64e$r=aMPns1b#j&ersRHk{oR`)~)Mpbwj|? zKP~#0=dfM3b7e<9ZmL?fIsl9~&SCf)bn_M6vFT3yyAk@Y8sj2S!F(#7?y4%Uq~B(- za4!Bt#&I9kx?YBVo!y}dY;5)zB7P?f&zn{c(=sqn`eEtkO5w$dc5o?ScHw*Wrw7B2 zcVh6}DTdKW(9em1kySvBywA>^?{WWYILH(hAQ*8PDJismT$@NifsjfJIfiD?<-oA{ zZISp#3wiM3U1Ox?u&;im&mhG-;6Rd@!Z^yql9R(C-pJa;M zIrw2*UmcU@)+d)BTC7cCxsdDA&^$I+kmN=8h03Jik&nu{ZG`w|zYbz|t?=haDVYGq zRM&aYF4kht$LNY|XNH$$RrJL-;IbWn!6ePeSgEOM?D~v?7F{5IkWi^R8vwm(U1D6= zE1$g@2OA3p267Fv@FD_->JH-d+$J~+p8!#26!YHXZ9yRtG`>56@+`u4>eZY==~Y_#h1E! z2zr-QP1*@etra?la0{r@AXfYNQ0BHJI-}xte|Fh{Y4*8Wlog)fsSS9YD_i8^+_wG= zVV%P|*ppwhQgzsup6TiN;kQUdeSWDpX|{gF3(kf)r){C*w}>pC?BAe}mEdxkRM6~o zLon!bCeD`VF`Aaia@x53JQZF8T)#L{rp{7}nSR>em)~Dc8Jt{s0FBy}*mgxlFdrmm z=wvDE3V4gGz?tW--NOSO(yKmmz6Y$m5LZA^7s6AGFzdsHluGnhp&6Ws#a}{m0DVjC ze+#uHMUgg75QkK^GK~L8u#;b<0{F6+1^IB+5Ne+&NQ1RR$fE>!6j zP%vQ4z1w4%w^fHWp1jz}ct5r1dnTnH4Ec4Qf~0(il?P__S3 z-{BOYKPZYO1g%A`5ROsnNaCGWMnwO!r4U;TIbrw-CWC8;)@UvN8y@F!K^q@#?)M(W zU;)ov!@hYuekk*n5rEo!wCC@51u$oI6Q`)cl)E5nwIvVk`IPMrGub8gLD@*{N;i>J zXSMc9IVRfZ^n!GC(R0`7V)J)RIFW35#QX_Zg7P#IoD+;oL;+>iG9!@=hm#loe<*lm}JfOxZ?xemukD3*4+c> zoDvz$0*gNX9rr%}ctJVL9Kc1rlWvG;Hcq4d!m2h8{dr1KfhlN=db(1V+R?ZbS9Ifr z8tDd!C$MCb*w#9UZ>;IlJktglr07`zMAVZnjPRi6@WR6 zBPB>a7y9q*{{FxN8}IrBkF7zUcGe9z;6O7mSb*suK<{xY&l>)OMM5D^%@? z2B8(5+>qLIiBm(+l7Fk#`bZC!Ydh8whVNOxx zuAgI}BENh|6fT}bY1`wL^@x`UF%Y9tOwux5gv|T+$6qvqfQH8ngtS^lwdY5q{Hg}S! z>Cw$_%$+xs7Oy#s`K={C{~NjU8>BGXMM$si!-9^->q|`E)!45-?=d@`Qrr{I`Uq4H zUemdCWm}O-ezxbqIj++QD`8GHr`%BzTk#{Np=VeH%HZ%{r`4fpe5Th9?GR{G(j>4{ z2_JwWN9&$Bp@I19etII7<0;xFh!Db513*xZ~GZqnC&FwOlHO4&simEuS=twRJeM%?G9ju zo+zZahgXf9NPz0!q}U`(3t}vB)v2ZaT{&U)r7HI_D`~EqO{8j~HCUWNzf`@*`NFz; zUx*uWSL;wP*x!1~m9lA;6CnDy(Oxz={N3*H178YJJDngpD;#e@GnrTYD0T34gIjof zzUwoUE5+q+U*)3qCB#;>yG#|ArE!^b#4g8t;p;Xzq}Xi9_$d*>#DeKrKNIY|YAHOB z(_zwB#HMs>Jjr5tEN4A3?zluvMA8NBa-?9xrVjZPU9Cr*(;e=nX=O0^=4M8e%=Tb8 zc{n454N&xBze0<)hIL7)bi@7w6ovr1&hJ+%^~06Fa<-zW4Sa%3gn|xUrw%M!%#w~3 z;!IdpEQd2JOUR5ma-0UP;ZH1LRPt2L!xXJezV`LK62o@E+6uSw@WoZxZI<(SU3W1s zGKzySp>*qAa;^ZV82lVqDsw)dTsNm&sdUPOwfbpFA2qp0enBWDx&e9n2y=P4uAJ=4 z_Br%b-0gC0o^+jNsl;;Trc%KSZ~Emy@wJ?H_Fk-NRE;;zio*6e$VxZ#+A`9kU(^<8 zKcCU7C^$_>R={H?8Br{OFJJzgbcU+XDg>U24{t zD^&kr0hZ_jgOz#Rz_PoF7~Y-H2K*4e=}g`|Qn5Lr6Mc!#I1PB6jOZ2g?ok1>j>!>R zWcfv?Ejt^{Y93>;lPjfT^xJz&Dk|=lxefWP%!g}o@_Jti5xB^6qS8`yiw)lFnfPt> zIx8>?@tl%?csP{&m@pyGsgukzuIMSgl^d#VK>J(?lVl2Zzc783?P`p#O`7}}di=J? zjDe6>n4msvU8=y~OkFrbDH4YrvObT<%PXzcd9@;dvs`*5MWO*hWat5vpYDj9?0GeA zQ^Y+_xAlODchFBb(c`IiSGEm-7WmbriGg+0?yJdy(ub07QjKU^e~cVF<>XPtk0kXVkf>C< zU1Dhht_}^x`GdZvFx(06D78~8no8!o2wm~xT;qQ>C^w+ar^j@FuY+IsDaj z2%_42@8|Sr1p-MJ?+;q*8m(@p*H3~SOor9_DZn&)Jp2IIkVb^RiDJ?%jLnLqVTcaX zK0sa_teaOhuHp*bRg4argC|G9DCjCQS`wLYVUt&}L-&;|4foi>&SFywi+<2!)gE?kXBE^;h3`pD~X{CL7XfD(wC(69w z@|!ge)9w%TAAgt9g^Wb)qRQiD_ywB+0=OIW7o-iME5C~QVNd>oI372C zj@aA2{la$fq_iZ5vQs{19{Wj)`e|fHSZF&k0=ZXgTm|w*Z0hO`hbVuQFF(G3%9`Ga z>dFg*kC{NP^MzR2TVz>iClvG+=~KCX12Q5@ai6|s5Ybdbe2>V>_RXJLW6V_?S6Rc( z=SC~XZlSJ@j1U%)@u$0*0##sNQd&PfS%vx$=vxD2o{XU&4+3ruQbf-b!S#^e8Mwt`jmx|Gb5_r zrN==)v$rr9$R!HX6>-HR;^q=muSg-T5)a;L2a6t{uh)ESa#Y}igV}2j$#@07P4tOw zP2{J^qk1&jSO8S$1yNYKYzK-UNIgouD0=^(f-Q*y4ve)%LskNqnicYxR@-q=yLmwI zxPpyl_i%f4Q*-yC4zm@AC*Zk_ z93pe18_J}B7@Zjv#BMg`WFjNSLK)}q=5#>k$4t`Lc91`+ph5-)GyJ(wbjvOX4fgOO zB{PqUjut-1?Wm&c=of644J!^|-UM#ymBdaHNEWk&>j|-GCdpA^7(3Dr(?t1|6R_k% zb>#p$i)^~Np}UF!DJk$?{4__&44j3oJ52E0anD@NvZy##+|dEqhhX3Be$SXCfTz3A zW~Q`8!9_cJb#oy)4ZyrI1Sw5nZOVt573sJxP08JJUw_iH1B1xPA^8yGpDWJr7D zekSqZo4g!+Aj_+s=Dh4P@x&2%aiC6j+Kho@J=y96-X+T4EQXmUh&7D)7P%D+HR%-r z?gtc6|Ly(U56Mb74_6=Czq%HE13T#Wp@$wsRda-SuCq1tMFxNDhb*Jk!ut1quOBC2 zf#Q>VMOT%sU@2Rz=@0pLxOZfu9xy#z5{gA|z&*6&c0Q;NA~*$c&y2X~tatJ$=`L{1 z;UkZA%z;WsPrvaT!M}=-BX2a1@V+Wt>>ioAT7cGPUf;Oj-X+m)f zlkbj@(F%ZTE`v+b-q;(2A|+m1-Xwt9 zz@X2Sg~lZ5>0M)bvET^F1VwL%H##{2B9ya)*q`99qq!1bNkjs^W0;3%<-RWv-?{f* zGDNjY%SxwCSH?xv<9EJ_%>^pA`9`DRnN8gCEcZHg^H!FrLa zLDA;8&cLv|Iog>v1AETKtoRylAckH!;0U?db>%P^=%HjHWcbOfrL~Je9Z@S)X7qcK z=H>Ukg>QevgPw**(B!qvfq{5fTyb>PvX;02L8AgpkNnN36~=L@3k7gl2I2!9psHRL zs37rs*oPRSrDae{!XFRti30dV?>!wP1AlHVAd;SWOuJ)lzSuwtz&@=y@>UINC{P{W zH@=W(E={!3u>@(|r7o1E1PBq9YkE&(i^far2LnqU)yyx1EwZwD=b+DBs8#)WzOfLa zSDbCj8%h@O*^qJ2xZ*>Zk#c)grL4~$;E_nhjb@ck>bpB!pq;-z9gzF`op+2F=bX~J z>Xfjc_*oNtkJp*f@0)W^G=(WOrd@`>)arA%W1HX~b54$<3&<%{IA>ZVvl;w0$k+G+ zu{dRv&?ee2_?_@{`gwT~GnGXj7Lpz6EH?K@7%G*^Ozv9dMWZ(E@Q4^G4Z4hkMC1-0 zkWtZ_Ke;W1?#7NGXrm+0*CGR~I#>g+Ki@pVirgH#cTsbEUJ>p5F@Ys_VCN<_LgO8jD=A z1AOF*QBSg@6zeh$Os+{Xg$&4t^9lBLU|-&q3M-jenFD1C{+7y9AatTZx=kBdHfN_X zo9|Eh8(j}V3HeIc%unqkXiKPwnZDKDJ>K4`s=G%PAijf`1p_@dbXUhL*UU~<_6ua= zu_A(7Yd~h?b`xvqj*>AY2Bv4XP36Ltw1c#d{_}^+&8{(gwn<-^pKzhp*R(c0)TAiU zRk=0jNE_$+(IQ8O57VFre=&A~H2dx=^c>G?YY%<~4KP?nVnds!uBPT*0ip z&+G~#itH~`LM=iPCfPHZ=*`35zv8uAR9{_?z)8Df3E}DY-m|Ed-HIc_=)aya^a|TQ zeV(zF_7O0b>P9NLGJdAY)aLS&;rXD`V^#IM!hR`FFaGGtfFcuF&GV)OptGABEz8Id z9;wM;xS~U?6MQoY?>~(BRJ4FCx@EA6?1anjC=n%Vdf(EbuE5;cO0W)PJaruXIpaZM zO3}A??1*hOBXEQJH6tQOzNe5%Tn%hLu-}FN^xg?S-vl41U5xXql-6T_rc022&8fh6 z7C$E6pew1M-Vmib&k^{L_K57lub^!zrp+IL#kuZYu->_e%(6cTxz8Xals>aHJ_TR* zSd2!>uPkoVs;e4Er<1cW zx$#>xx2;C$^gL39^fbRwZ(A$+9W>B+BzW^m3T5emmxvW=HuQjVE>CRiK(%)Z;Y5Bd zqPx#9yj@lK*Kq{TfO6=nso0nR+Z>$_Xg|(l>h0dBRd}N(!tr#c&if|vQ;O_1&79O~ znjp$3GmrC!pJ2YrOV1*89je>7k}T2XTjvMH{oWa*i)Fx&tWBpRWrqCk8n)H0Xlize zgGQeV?cX$q#N)}5SKq-t0zBpodW_38IflpOB+O1tRMumSc)(6hq!;GRD+{nS^WZ4t z{=xZqOa&cV(6z%B5FqcXs%6-f)OUo7(SkjoGCc>Xzyf=CRQ1fM)b4C~HWeB(oB1PX zNFNj;10Mc}-ySeLo?Sre)Pv6)f27B)dQ|-3n~Hx4V_sZki;sU7I+GWaq7+oO5E}j? zL43?#k1Z^N2U>;Kia@oFX&ELwvZ&JGyFTske)_P z>kKNG!i9vSx)c3Ifp6W&g6Ze~!MD2UbUh}YH`mEm;+m=1jp`9%g24$LU288NGhBqc zZ54qD_NNKdm1W3fl~>fObI@5X;71Ys>N*1u4h_SDFV9;9@W94OsdLLOy>UnKH$w|J zySz=TIBvxiAk$+Qb6i7s-DN%n7D{NKv#zkqqmRp@s6S<$Y695Gz@KgXuvRL4WZA*Y z=f3!@0g7Kr0NH^au-R!vVjhKe6r(Pwdy|*tkcHs=7%hkIJ|eq1Vxscsfr|vHTK_&+xBU{57yj%1(fRl8MniK{Pl|zU;vc zcICGWU^)SuS4)I{Icmks567K3Ya&iL=B;YA@a+xtSI*MM+OJRi1|MW99Z7eGWmk13 z=wog5H98J3Cc9EOkd&yaMdJl+>1p?zXt`2_>tx z@ZnB&7wn9V`TAmY(94ZJ!3OpGjcA7_aw#11%e9(bgn#7`4f5-4Zzf?M47zr>7v>r& z^ReQYqgT5vZPTxcbBY#MH2sHA+4cRrZU<2F^?h7=y*@8DEcXTUed6zSiaYiFP8~CH~?A!H>YnAA7a`5UuL&jSdX~t+Lv_Fn%)}|v3_CP*Gw#+lM;WNk=hRixv(*skGioGZ z$+3@I=NejKrC_7d0rB$XdQ$@)W*MvHlkh+>S`v|sSa}wIaGix20!6QqKm}M2v=@@K zXA@S<{$Rj24!5GB^Tfwu25RF1l zL%&11_q?z4qW`gzD=B^=zHxoc>Dz`skw-4=`AX$4jvRgKq>~aZ$%`mNI&l?=z8Bd2Me+& zbX7apa-&EtEX6PDbb9yxf_g9!xnkUWS{0F3TH2vXtE!!R`eWUsY z##JfoFt>bcGB89{Ub(JF%rA;gW!6(3eb6;2Xb?KFA$>f)Wc}HA_Yf8-?XrgD?qE#h zw7W`d>E`)~%KcHRJJ%T`kA_1}n}ANrS9<;v6!Y~j3HN9H`oTgxxv4Gm00|XovDGw9 zR!yCHRh1X1wMc=#C{qtcfA67i_HegqrzVThHD?c?0Ld2O6j2zNQr|heXbz;+nXnS* zKTG#S{JRQypmqwo#{FM^@=f#CX!2m|-a34%dFhZSIT4cmbO5@+ALW9d`1@mxx#W-C z#@A3O?-55IU#n1@L&f)(u|B|Ec-3;t&yN+gZYQT$Zy%UdlBJ;xeRHrjHRbuJdM2NG zW}L^TXMP(V!$qk%>g?jrhrT0~sUgZi&I8R2>1O{h*wqACk-^+`U-nqA*waCztPbQS zrxqI}%Q89V|G?5;V0M6_ZlR$%8}Jm8c5tO>+{``$Z+aH*5NAfB#Gg#c zqNvk``D04FDQlvm&T7i*1`ch4>Ya2*^#swqUcL8*`GTIGY!?eeZ)w9u*Wzp2zu8`Y zgQhuf`pqB)wU(xi5}&bT?eD~W-{{M_gdJ;c-f}+H?r?XtJ}K^nKF}`+S1cj@F}xfW zLr-z|kn7EF3+O6=M4_Y&o;}!BQtQlgj$C8{_ay;rzAx1+Jb2hE#K5IXA7dG>pED1n=k$LEu8^;m z;DgN0W83(S7gAm)2VeV@9|j0E;4R`?v?%_A>_$^;!NDtk`X&UyLju=Y8CSqg9E`+S z@mpP4D`3TCz6Ddo=M;zgWO8lkF){-tsB3I&^>b*raG*co4z7o$pt;h{E$8tOke2tR zJw80*lT;iYPvQc_7#IW3`?vrt;rrK>UF`^wsvK{5eMbB5v;qv|CrXMxA8gG!p|uK+ zd$}7sx%|`CCu8rG>@rXMHr-1XLwgQ%yXPS;CGK&fLleuSmD2XnH0PtnKv9BpRedc$58Hnu+{nMh zF{W)ixdvRTj_>#GZqoKyyN@%l=TLS$z>e*DaldG{s%JShaSj8#pSfeg(kng>`x4x| z5tQ0IlbO6;?Uu)V+Fm^G*mh`6#Rcc8|6`FP@xhmfhpyt%LIHO}8G$CDs6Oy4 zKh^cLVpZ{+Ao_xl2Ju2J4!wqxI!K}zb#GP?M5@=C>VazDL;kj_I^i-5L=pWoi9=QVnrGsu&2j;{D;N}$j|=I zWP-uO1wJTnltp3&cPaq}&0sf;=gH4ct!HFv56D)@`Ma-PYZo5mLnX=~3s&I}sx!jsVqGh+U) zp)i7A=0zm=_bUwwEK&4`C3NDEa)D8Nwkj=h2tLc?#0zZzf!TMPkS~>;VI%b;cec{pB z+=)&?p|tM9Q#i9uAO+4Bp!|#zpZRyd{R4CiLhYBIrc}m#-SOZyMG1Ja zmkkmgg`PI!*e^1k8>Zr~|v~`w;jD`Dn4yNaE_;TvC9&wyl z%LL3p)N-ME5U??v#+~(~VkG}7H<}WifpN3c8Un^A7qO;joyEs@=Y4**p-1&m2Bzo* zU@nVY`9(dA{SgNJh{P3^Nuu(kD56N*LqMT;d)$S`9=5RqI0Sjy?qR6v#Anz?CXW7ol1hecN2jLAP+!u3@S_*+jyBaE>?7W`^rG zxR5lwW9Y!SHzvRi_#Sr-_pVW(&IsKn^32RxW4RMaoPfaOS$2~4H9zLBl+taKTDYD~ zLRS22y}7l(nv;$Mv$Jz%3*;C*%Wy5dbx1-Muwf(4E5QftW3Q`e#! zx5b#e8#cDFM=U@j{_nHp)H^4mH!I*{M1_t_uGUsSn(c}tw(zf}=hp!gNuwVY6BVuO zQE_P5lsQsh288SuRA@L!0!W-#@#%>g0ehHy#(d_jtKXxG&vs^;L6-{(9u3!Gr+MPh z!@`+REWEug!Xd%&TZ#ZL%6D(MtQeY~yfD5-^U%uAJmQS}GhAeuA?s~jTV2*qYuA9I zc=7fHYS@*8;8x}rK%90|57TbPQsAks|2eSDZPl;BY;DKM8+=F`5DOjVT*s>2FzcXc z5JP^B4ECG3Wrrwg%2cW-pmSC>6W?T(gXKqw`E>gkU-X)<5n4#)J=vn!w?i^S^hSIc>_o9MN_>{0F;` zvFA3in`~`KW41%{;Qr^V)26L+*p1{lTRSnbjpdJ__ea;qh|HT;lKFGBCBM&9lagSE z?Zy#HiE869U{8>G#@d#+k41Ll3r#00(jZ*O_OA$w(NxBEToH4a1hg+CSGqiJW7(?Q zTsDhdczW{is+d#UbQ-U$J6=$;UrB)_*dMePs^e%xqH4>>FQNY&IxZ;uOoS;r9gpxH}bUsVAo&N6&3d*@C&Ymj?U+wluG&+r+HOk!qzrGr>_NU z#F6A|{8)kyUHm=2UJ$zl3p5j=K@>T-x7qPzz2@?0z96r~U=UoF+c!G~PF5b#< zrA2Y2zqLVUrdRlZ2k9W4NRw7;Y;?+lt1cm!+D}&vAmJ#m1KmN@Z&GfE(ZI>je{}%b z?lpi%i_?;JecAHZr*yV3>Y$4E$lhgeL?_xz#1>l=j)~*vKI!J`i6kTxOR@Ith4lyt z@Uw$C0rVG1_NOcNo;u83(lU>Ekr3PR=Xqorb8aMXY~mG^cp~5Sp$qm=ITlvqF?};jDS#R;SJw zp+hi^@Ohpm`w($KLeRfn=u60N>0I7FG{QylW~8lQ3N~feka@vx+?G9@{Bxve(0Fgh z-PQPYg~O9eLw9=)bHc8qU~+LHknyliA#uFB@i`5EiPktLkpO*E)hq-0nvCIwFSoQIj|C_i$Q}cRzt6o+H1s!wy^s z+h4zkiyzW{Kx>c!Gr#g`;<+k!Ye3a-3_1=mmn>cynb=*vatxmB`iFv~2Z|9K2qm8u zpjU6GiqjfH-rH#o@U`CUdlJBgEJq7-pVE8-je{nH#z%V1ku!O{-XXTrS`%4yIK;mP z=C~jEFeC`_*?-!UJ^i^ZPA$jiFlOKDj~xWbS#@~yL?3KJHa~}qa^uXu(FkBI^~;$O zzcF@_W#JTNS?n~I710Sk1PsT;`nr~FI}LQLo8hOY{?1nM-QWy*`MjoUxY|9AC@GPl zZj2(^E(3jTq0`CprZHk2Lcw2X3>%&5$lFq`N`W-V&+A~b?dh(Y)7&Z->7kU2lRTYa z#eOBZBRQpR+m_TE1g*gmdfjy8iuOC;^>YgRwt^?aZpf#{hLUvkkBu0;lZ3x3D7F^7mX4- z)$LUW$M$+mY54Z)vGUyRrV~tCi_F1PQwU+9dmII>3a;Y73y6zevdkA^r=$#((_W+c zhj!RLDlolBYq!biqzDbwO^NFz-yzXd)eO_R9t;aaS#S@YVaZxr=xQUajq{u{O+Pa_ zwumv1_~csNZ%g-{84V`kQz}jidC|Wa#hRo)?OXPwgkkH0-C7C&LYU_?))z@MQO8mt z?>2krSQ2T;OSZWNk*D#kGLpjW1o~N$_O`wGiE`X8n?a=BkzmHFEyC$HUAVrclf5Zge(zx>lQHS6}%xib6~6SBX*bD zTR>}vyRHPQ$GL9Czzlqqz=(Vko5UD=u`S93MN~Us#@=-A3`*r z;w~!t#J^TuCtoZ-dpdPINqFd@>_5&SJQt&yZ5gksaW{(V8pzILv}lkg+fg6$i+G_i zoj<9|$T34|Abt9K3dmaiA1Rwc-n>PuE%{)X_MY*N7_?5YWyT5%xNSHRuAg=z z-=Xf|`;;qS>4vE(+iNCmM@aM+M^}D4IEgDxag1p((NFt7y#NqAQ?4}gU4~{fU~-I! z;V)NS7C6^C#me@;>`^^l{!axzR0a8JrxQFDX9d{(Y9|fBg+N4UT!2iHdbvtzR)Zg$ zzTj$>vK0qxHh>#&do}B9NuK=^So_(5PmjUrJ3K@T{sfq-qSO}07$LR+z34Qgfz=>Pr#U}fAh`Xn6j8ixyIo}ouc z-0ePS-SmrBhc;%y{9CQ?essXuPk=uoy*i!&-S=_pnKy>GzSTel|4#sHLDQQk=If9M zw!dM-3_Zixx{wW{-Xs7M527nUkPUXru5N)hIe=9)&gS3kB2F=gfdS8&`IC4~O#(Q1 z?H+Q&26-a2=z&|SC!c@?*`$~XLAT}Jqz@&Q(5%p;p9(Sb=-#9n3we+CfjXF3hvD=+ zI-HWTxdcDfc91hyavXcF8*?ruVpE7)0OubaRZBuFE(Dl8DdZ^S_GE?JR&CL#0gpQ4 zk%|MoseHR1xC?9AvETJw0Uqs-l8kWi(x5(2{X1<+K&r|Rhrk=idS=@uux&=f+U=&M zwp?J!T8GxlUc}F7Pw2&ePisq4j_racN7~VdhUVKmzBVj4It8l?!YlvA*Uu#0;>+UK z=;{uzy0~W%cTrlCRy^fGJxGEl`@TyAW;q7GxIhNxBF$IVP0HEALN>+Vrsk%}Iv%k2 z=_ScHTEmus_Ay^*(EO_O!H@J(@*X7 z6hGVtr}RqV{itHH`r-o7Hv#dCTKI+NIZyuAZ)7wGmmhL-zzk^ixv!Lq^2hx}Iln7q zEx+?^vMF>}HYd#fCNNl1b+jg}IdUgDx4`T8{9Vw|+L+wtfTb^P-He>foc=%5G%)cN z0_HK~7fAAx?Kv{*R%OPlP>sS$S9FQKuu)NUOvz`+`4iu@(NXIW>~331R8*5*n63SU znz?9j330Ekze^a~93arYG!2U+iT*quQ30)lEVrDPTAkMn@vH?oW2p3)yZ%cqa4TGN zcebY)Zgp7c^&grYvx-AZC)JJAY6$jAJ)L4vvC8vk7i9q7(Zl^@hMxl{M!LmJUmD%V z@0;9%S~b%5Fu{NR5ytdo#=b+!jFAIf+n@cc1x#gwDouPu^?vD+T;V01YAH>i&*g}F zlE*DW#^XjzZbiiMpaV%X-;{{Vv6hizd7@g;%{d*fX8X>x&wcZf2sv?DS|>Oj|HskW z0n1Bb41*}Cc#!`qNdNCx{XcJs6fn7~q1GhHTRjcwi*aeOzK!?J?TBM8t;pnDUHbG~ z0blC;gZ|W#JD8PQaGvb7WOfIoSo12%_<=uc;pJI%S`^?nTgp`O>1Zh)}X2t z5lR0hpSh2|d*n38eML((DDtH(4l?AC2kd7@NmMjn90|QYf1p z>Qql6&gIgXW_6dbLZLTdLCa35qi#n0-<1)g!eE$XI)6Z#KI)X3C)dv#ongM?b2?c* z8mZ!TsU#5!y|LSWmdlf$;|%4iWNPR@1J~rMlx55g<`i7Rm4Exb;YT{QTgCkMnKl99 z#bcSQ6;Q9-nkhRikop}@P^)ns3yf#XH+R_nH(>H_MYl1hH#HQEubZQyH&|3eN z$8ijgE~SPJ?>z-Qw7h$L-j&b0B4XsJS^zrX@Cg-;$w8jXTSl3%3nC~_^ zU=T%k5$M4v^r}wnB1PNDBUCYDyKeYF2F8vY`y0^=z)e3$9hoqd|8t-`P;-F!wl8UN z>Zo+82e3yP;id4bR!XEYGc+gS4tXm`s@(o4Hj8}NA2{#WGH(2Na{nra7=e=UU97Vg znHV<$rC+bX&3O~{huPG16>=1Ct}&O!jM<%y30ca7QX*M%a`8JI&(4?Mah!cz8SHhVyh_P( zP)NhaZc_CR(gn^R=;V}XbaSp|4YyR4^;08i6An3y=2!#sn$}IPQs%#5m7_p$f&l*> zK^_0Du&sWXXlbDfj%UtcTIKY(GFu0>W8nvE&1wS()w2QrkR`S%?~8yVdUi^fXBW#p zsbj;C&`nGzRrj1$R~3njJ$bj2sm^DnwO`Q}=3LAS^7+fuYMb7E@d2J1l13Pbl<{sM zE2wFe!(0u&F8u|nSJjvQ_}Hk4d!QX`r5QJvJJtcjZ$_d()LUfm%4ES&A8jxS6ugL1 zpr>JTuERA3HHjvD3Mv%=;A!K87UBQl>Mf(<>Vh_HEJ$#IYk=UvJ;I)m?X8n*ejnM%;ZeSC~1a(n%3ri*~^w z%;ECjZt$Xa98d9wR`9)pAYy{fI2ArLH;SF(F-xJbH&p8^^~y@@B2$hvjbYu71D@;j zh0zpeR%x8WYB{G0JKBO(vw>Q_KPE$kA^=xfhsobYaazDwh*DDRj*+=^8NQcZ1I>+X zCCkXyDyz*6@mUB!MHtD2qtlqA{xDO!!mmQdQP@M!&*!QMwcvOZS?qOXN;%EFU`PIA zYFf<0$EcKcdHyrzlH0~5wFXi}s#pO(S|F`P`9c=hg=#h3%~v}`R8z8%jD2nKRt2gq z$1G(Un|^5K?5l3K<@L4!Aal>Pkm7)x3Bh)=fdl#8bTg#?I!23mCGiyhBS-#s()x13 z83*TarpF2DD!IufMSpJp-5t;g+Q@<4^}9Lqh%b@Rfr<|9>a& z1_qolTB;~Yj4Nw9B~b^apDYR88_%WtmIumVWH49BB>pYik-N#FMm-X6FFm4+TW4FY z4#P~`?Tk`aia#tlXuU1L)vs9wrB^-YLCkQF(d;l&l^7Ky&9RAiAwsmfXghbZXX>KG zW9z#4fqVUD?;+kv`@&azwsAS;sxb}9w&es(x14C11ZOo}qLfWDm(&FsY?#f7zZioT z_yrP?wEz&O)a5&dp3q9vLJSAnGq&gI7K7|MH;L#x+I8IJtWK4|Z4aln88yPmP&gIo z_tEPJoQ8s-g8p{9Cc{pj5}qK_!T55_F2U3cbf=DeCC8%g)mNfA6Su0Aq$M9eB4*`m zD}NyB=>j;$*@evlWrpGI-eIuSrPx>DYhW`dU{ z?&o`5<#cRQ_tQQKpA|;AaG|OB;9|3n)s0PXi$to`Pc0WAGX95u=qH$$wW=w_nyEPe8m`4H}3pS>n_3*9`fy&U;3b=flSQe-~+t)rfHV>+3ai6 z^EU&gu+_tAv{YE@pYk&lLHdEO(NyNR#)5FzZH-R>uv88DJvis09Z zaUSiNpYu0hc+To>LXC_g(zG8giT7Uzr;>Y}eaa}MHMnnF-+*{vRtMLm@8TS+x`c1B za67KSNBmjMZbGXg2d>nt3UNxK!^QueU6InaPd>Y>d}K#Y0!s`zgjxcu@Um|ext_b6Agn!KU0&wL-hBllj6AaRd=*Tk`EA3x(TS~Rsw1T4}i zJ=5}syDny!e&y5P$Fa<_~bnt`p7G&ztaXjWm;510hNYg5O*dWG=+}YeUNzh9ym-&-*l_ShE?BDY^3=W5_1?m zjvQ{FZMB7@5h#5Bi4hDBO|XTJ>TX~0f6@s^sz(X&QzeG0F-FPfkZ`C{cwAY`{__7V zRFC)_U8^2(`1b#9p3l*i5c7PZB`#x=0Dp$YzdV75m-y!`EpZYj_Mj=Ma6&!(1M54D zXV*ExirGn+qBf*f(Ze=l>w|4l)D`@&*Ql5lALnfg^2Qulq%eRAuo%E8fD znF~58h-I8vxru(&)@YUJLgtMbzw_?PPSV*uA2Y(Zh)^KUw-Hi`i2rBxq|jbhnkA#c zGVOF&Uw@4d*QXt?XbkYOQ@E-$LRki;p{DSt~q`;K50C)Vh6td%8E2_tBXFI-e7ue2m`R<5x2I(!RrxjWowMKxO_63pVy!2%*jya2!mJ$a z___S$UqDZi)``xDi4aG?-HeS$+MM|9=1q82Xmvl+Mua{?7L=no2ni!Wk#%U0KPBLP zb9liE!=RSK1!(*+o)u4F*7OESXLV1GJ=HJ`tY;~5b06c#(iCK+IkDGi)*kkKYlTz| z>+7J~?KqYzQV!?NN;3F#hcBnbKb0jfFnEDs+)xhcus$|$ZyrpOn`-*AjSJ)}`M4J= zZrww=^@~=4x)XZGze;=#uW-PIm%t7vSxsy{f)m=u2d4Z%d_Aeh03os?Vm1rMUv9`~ z(646*XiID&2~gm_M2blA$Wi4>dY=wgP>rhVFV5MvSe? zW=4z?{rxE*B`jl3_V?ZYXFE(!{+`c}cvlPSHx;kW=0J4<|GX0n7)DdG z(+GE2JZb#t25jh}Mfh-whfHOEA;j~{ajM~BDJRIriOA4IX&I1`MY=j+q0<`E? z&#G;RvK-XDM@=Ppf8Gx#ty#W@O0)l!_w9(YmVMiCm=DUcYhhg&nfkiYbE8b~m84$Z zjZ)$M$z<*Oj!FN1Xu$`cJX3c$75Os%-%5SxEZI+$32pjgJ)8@aNQW(6AdKkCU(DX>m;UwR?K>pc^ z&Us;=)VM~oJi4LkXAAmLkcp>E?Tnu@9qPl6ae}aWpPTVE)3%oEsyDYD|;~*dI!HS9{vr|U?ZWO&OeOM5DeHvT!Xv8_0{mu5Hj(Py@i@xM>Z4-26 zmecFy%}@caZ7ACz>Uq%euVsNLj~uOOA#Cx4gO$O$+q}(H-!=v4RwV_Zq!!zZ&S)fb zu<=v;)v7B+HyYU|J~<=Z4{NP6O$UfY>leQSnj$?uws?v+DZi?t&1Q%?>mvx{GiyyZ z(Am<=F4s{_77BPZjZwOl44%Y<^z0vWI11Qm*{m+6H)6YbVzGp=AWIH9!w+Pj-Iy0_ zzn|)Lb8bVQxgpExW{nadWeC;)n{aLVbhOV0>LT8Pe9$XyIq5OPsKiy_VqUSo>Q1MNK9Hiiho~5Bj2a_J8GK> zA$R-SR8AwWpFiu4XoUm{vz;>ovY8Slg%Mp-OZ~ots$kzjm5Wpe;MY*vx+=Vu+rtT@ zNc$x)PlFBj5#i!!AHhZ7>B<$E0b~2TNrlOEshbj8)YY*Gel*wcv!Mw=G}*K*fED3l z3vBb&&sSxQEUs>3g;dkHeniaZMr?9@v>_%g2VYE);LrCx7#loqDlyas$>fn%L=VS3 z*ApT!_T%O;l(#!R|9}#Dk7%~^s1e47MZ0lj(4+#qX{1pOL&8e^Wg=)kq2qCRIiT-A zz+LzIVTT!g#OdUHfwY)gThHZ0G*JyWWTs>SCIKI%&Pc0sU4~P?bF5H1id)A zOffj%8bJxOpo?QnFhM=m7#JC%hu|vymmzBQc85b?Z;e}=ga~%jl;egqo0M1kWRkTe z?_o&OedWaFcDVa}9Gt=K?91#!$a*b0o zySqKkl5eY_mN@^+26Q>72}Nd!sh1%_PsG`sYkY`ccUO`39drNdm)H=%;S4Fqaq$K0 zj~ERleqrR+z{SKtTABl~o`S|=hn8^P zE=70vFA$#j6w2$6H%1_+qM&H*B%%Ac3Y=QA;|nwa7jd^*ASaMPo5}pA*MyE?1+>53 zwjKy1&&_z>l4#_<M~E^TSnh@tUeKKugva z;1HO(-;uhX&^;L-b@yFA;jc~A4I$Nt4A}GdaF*ur-nv5)_qyGanIIB-_aKR<{<3b1 z$8=4?qB;SIRFQAtuJ8vUT|@$d9XbP#hF>K~rWe1PcX1gJ4*EPI(2~=CK5Ymd?Ar30iTcj8+(;=LAVdd-E9R143-2Xt&?i5y9w zh_rjY4fiJC*SoX0_F#xZ)WeOA!t-1=Q}%=}IenNAE*{lEpk=f8VtcunCz)>znDR~V zEAXIKlqh9^d|B3SsZ1@;pcq14A>VI3&-P{y27n6u<8MiZ!1Y-nv+o zRCBX1(6>0|y^3YQ{(Ku!;RPeZU8ip77`h5U!(>m>d6wU22jFiFEwpp`NvyhD=Qtit zb^CO2>XAh6$Lnw;?`6hhwGH2&g?|-2kE)TB9ILxZu=Y8rk$TD{Qg{i1&v%?lu}Eox z=Q+pmG7%R*d&!ZkUF5o-`7o{NbvOl3Qg}7tWn8+%syKNN(XXr>EX5?um~Z)FVVCWj zDdVP*7vp*vcW-(`H#Q`^Gs`Z@c=CJ5eez(0mFa%w9xecr7nFIN`_UQu{4-5C?^%@( zSt1@&5wB@0!f*$0mBD3D;Zaahweu&j;6K&)^3xv!&c$me$7Cg`&+35F!@@1$9uSIHwJ!rZWanKMB->oaki zr?4Rgc=^s0HM@M9(39-f_V7I=#VnMn(xGv9KC_qC{sY(1&w~L{741WI4W581!{Xvt zAb?4L>#qCKiT@>L-;$M}O7Ze+%q(FDeA8l}-c>e%zD-&+2Ay|;26yvgwxBatn55pY z!zbQyfLGUZnzn0rbmW-9u)~H&nBPY;-7H?g zm1Giq5_ks;+HbB!oxZ~h^DoyAB#(1kh+8mEyK6F%6YfeVek`w7aDa^jyoqHN_~kHX zkeimOWypMLdnjMyO2{L^cTlb(=XkDe<1ek1lZlB(_ImPY^|_Yb6Ao&t#{muyBy;zrcNT=ooUqEqR%$EI!Y*>+*3_h`$RW~r?Og$HMm|_ z$O?CdSe=!wJGPAi(}}-d;nOX+Yf@6iP8yP~&-}>J=rVfETK#Yf5h60O7}lne{P&Z5 zsCDT(wUM=k3_ArRBk*oEW?>d%l*(|J3ZOrejlv9|O;>HcPKAq-2xh^4)hIf@pRI|> z40^vh3cM|Y^(v7S=U0j5E?yI3`em2=`QH9A_DV1=Z?Hq0Ur-*CxxDAPu~I-@FWTQH z^6Pe_*OPP@-_Yzrv$o!AOrO&Zdv#o)`$K1EkIy?dQxe|F26+sB(dhTpA*A%id`sr^ z;l4=S>^kmCYAv`OfY#39BzWj?F=dC>ispW%Dzhu`PJ}Ycj|AM!2Ji9(A$4byv^5Vh zNLxo$o#1|qPc#QyQP;8$7Tgg(-iauNi;G@eB9fc#qpoonc<;=kx^TfgpT5e<6duMZ znCc?CDl)UnEE>2T`91X{%F9Eu&(A-5fmB~MGoBVP(q(mg`d~Pbw8jy_Lj?U4Ld`1v z3}p<8r_9zjl+!lNZ8@$oNtEY>BGlsoZtn7k5#@I<-J+R zC#9o4`?JNSImbF9+()D=D7XLa>U*bOmcy5Q!cu}m*isa&4&=Z(?j{ncyEe@)l8F1{ zQO+v9uJ7*BnK>Z8fIppdYx*_0>qT@>LWk2L;Oxtl#EM=Va(`HUHQ42f{fT60kk`%KSVb^Bm=xiYvTWspRJ`01P${C$VB&lJ3YjOrG8%3Lh2t zfH&WYVZ$-kc1LP`f$>)Lh?l%%$i`IZg?X27f=oB$-|=b6+l0nZ%MQPC!Ies0(}kXp z$tJwW%!h;g{xgIMmOEY1D|4eWNRa}7BI3B|T6mZy8^?4_J(HC^LHr6y%J0Ed*>*^f zD!3@YLcwKBF(VxDkqrE55ckprrY(D)?VHm2rF?~ zyfCY7IaaZ0=!Q)<<-wJwO}sGe+6p&CAf!nl8_x8uwlL1)%#opM!8mrT)ust-h4?^=HWrs^xSIGtZ_yB5Z70qDOBE8OC zquaRrPIe0B#0Ne<9{fuf+Ci5O0~0g*%U-YejgC%+77X{YEc&G@Yg7jayW&pX=(8 z9%T18FrFQpS6p^B{Yy?UyIMXVKHva#_}q-1Gitw=&^kivlLs z&Z)X)qhp;^N^S>1ComT0xMjHZ(ONjx)8M{;dX%sG21t;wGe=iM7*MkL?J;WmCdqwIBd+_)wJp>r-b zqWyWo1zJh{Ro58wfvk^w& z2@9Im=_J^U;brHU+g~}A9*;}L9P{wQJ%i3kKs8AYvw|6vIDgk3#JwLgxi}YTgP81l ze7j$_W0<`cZ@V97z@JJeHyDHsYece@s0eq~8Pp1joLl|{cAuJ*XG_zdetWWd*gyIv z2LO=4UiOE!1=Pd#V2MwJFhl))sOvA9S?u$znKll~>CV;h;S}*Vh&;2x z%9&PMs2HoTlf3R?QTYd0fWR919+JkGMr@GJ{_>as*K(U1^IjJxncq19aj?ncc0*VB zpEWs;saruEzr7mQ-p9Mz;$Rx>Yyl2Ou_0gIwFrF!wA4*Pb#XW4lVdYys0mCW*}9K& zQ@#}9{DT%fT3QiTsP=1jWiu)$9b^4ONWWlEU%sx>M@p*j!q{-S!J=D2=HJde#kE6f zvhy(pfh1L`_ZDxmT8LDzKs7QlZW~k-|EoI zCf9@VRLb$KzB)1a$&<5G#HS7OIw(P~0sA#dQ@77-Um^n{TH~?C$YQVvf9_$9q{fnp z{T%Iy7(XeNX`;s@y3fv+r{r`bjxHMD@$sKk8szXZjfHo&{xif%(t*JEn-^pP@gMdQ z34&j0^?uUTa? z9}U(gE>kx^5)U5>{pJ^`h;7{p&j#Dr6A8^&ar8MlO((^>&?A?`4Lr#_t9XH51}aY< za1(vE^&ygXmHsz6*VS^OV~pY~#wSb7<`2wH?otNEboGCQk!OOA8^vj4q!+UO%90() zAjwlogm=f&DgOorUyT0ORlM3ktP0h3UIj>Pkw<=%(k(aMRm)IK^(Td+aG1nG(~bxy z#E<2KOZ#Tcl2D*r5LtN0B*)SAo?1}j-ZM>7=B|Oj_{VxEk`o)b5rRa10&T+oc@0qeTBd zZtKS#F&NTX5Yp$tt_6DKpD;eX-b}3ZNwK=o6&W?%)R=b}H)f}4Ctw`QVgHJDA&5?# zz7$pzlsIp@)H3y7ukc6Phyzpoos9Fw4sskQ z4k{eQXDfOPOkP~LUdkWlZ*~GN0fEG3a;OO7;YZ9qQv3*GDb;+krI37oxa0ef-UlCp zht{~?mE-tG^zGBzkjy?NZ%}#5kv{*?GVCMn=Its+$>eLl75E^%M&d+>Z0kEz$}Z~A z)@a{_%_C`x@=oB1ECWIQlhv!W@hUe#${|PTf~9Y0E#9jNdcpyjrvOjd05S4E9i0wl zx=_}@&eG65z!AmAj;EBfSmM4LgIhQXzKcH=TQ8i%BI*mSEO9+zgpAxL%>?L%AWi=y zJ`TQ$#N0UUTrAmMduFWKGD!6*{m~Y|0JE&D`Eb|lffNGjBn_ob%9Sf9vZuSw(hs!Ne@-M z9pa@8mvP7}hJ@9M&bjbK4Pj?u>lWrUozco&`!XbBIol)kZ(KB zQx7@9Zy{L>{e8>#$k&AicRcf^J=H`}@L5A!&E`ESzU{K_B*AX^XZ=Xuvura4-fxgK zxBaPKR1>&Z+Z7_}Q@OtP{*d`VEdbe}+_iWR#R6?>s@~W~a%tV;(bVC`YQoVe&$Wah z`FpgvD93*Iz2LH3hgm>DKyj?@hgoo0o_D}gbHf>9v-LHqL0P484qD{+H^RC)Z}zbf zB30xTSZLsy-O4YW-h9u4=17pb`nswv(&bKg0!Xopeq5kEM0&b+&|7!Ye_h*U`;%Zqq$6&GcRc?1_J;WgJC||&DHqG>zc)O#qK7qMzoGQ zKk?xl5g4Vo%PQrO-T57uSn}C_B;eAnrz2lP|D`bl?O3l0PHqsap*BBbXEQi$spNPf z&qMRJ`SL4}p7jvTP6T~Pa(QxqI%TL*q;dy)I%2bwGfbASR)YW+vsv9E;G4~qPKFHp zYk9!GjkgOah(VgYhCem;KZ3VttjO~;vauJ7mte5YCLn$IFiVZJv+UWSMM7S0G+luP zBE~GePhnj9n`>l=TP^~=NigB0vXV7lXBrvk+DJnFjrgn9^t2z zkfd!jc%ZZWDQI@E8W?CW%&FX~Hutu3ci==P7m0}lHyq?^;XX;B%3OxUvJaigAc3Y# ze(UV@jfm$NFj3kWOAs>TebrSU-o)>0Xn@2_ds;s9zY!VlGWOqW03AXIY&0!!^^wBSrVv_tyAEv z%OY(S6T95F7Rh|)c_@dBBUKYoDk{AB{?!*(yo;q^f7lO~?cD@_!$`9XxIq&JfP6NkGCeLDhVMzskG4JxG1jVbirz zmbLpwj99_WeiA0CjEUr`OR5Dx+#6HXX&!aadoiDfe5xDa4 z*wY*7e_-DD=VOL(IMCU`p@|?tA42fck#BTFn3LGj%7meNcpolUcC%Hs zGrR50Snv?5qriWz$C}s{EiKg*jeGd;A<{@{oK*d@UoFL=5b(_kA5yzQs;cy_h|&s? z%_GnMH~{j^G17G6B)5PHt{-0_voj-g`B06(f(NN0zeWr_Gy+@4`qJ)b|L}A zmQh55{sg>17AW46`g*z`f-n9&2+m~PXPj@$^s{LylE>tMmuilHs*NF0zf#w|?Lb;3 zc_f$|42X4O4f53%!`^yZts~cS@aUQwnUPKxAK2PwhnfX%4Kun&5foj&up13s&Mwz~ z0Sof~4)R#W<}5Gab67pgy~ho;8CC@n6A#oNoW7cF+Yz3+6&7p$F|9YdJ=9Qb{wP^V z3M!$I<0@4N)};f*v`zJx@I|42V;f^S~LOA2wO!AH*JVQcC)602xW z%X{tIdz(ei-o2fS&-K?8f%UyZE)uz>4(>$ZRDU~I5)Y_rEmuvXVoB>A-HjDGP#=8m zDq&edw~o7RrMFbS?ayWZxPAuT0f>@7HEPO0|*5k|38! zz~3GO8kA#DDxeM5VBN0@@yN{!X=VDUM=TCaqv#Chu)}pJ~)CNl|(4S9jV==p#&UMrBBAp!{A& zepii({YgsGfu%s9;0FQ`*a6nbe)P{7e_$@7)X1{uSs9#tmXEju^`*Bh$0I*shuUlj z+%hbUo=d=-j}Lxy7M=cSV_UKOE3ct?i??<%I5LwCx$~>FSXRocVuO*Yv~`;7;2cR_ zXV!heUSC=oSWV%l9CDT}sB^!&hYI2FIddH>s!f{v!NQ!spL96H5V* z8#fI1%76dm`4*;Ge`QDgTHA*~ojzkkeK$G8Zn3RDdMUf)zZ}wPxJc<2G65iZ;+^^% zt1To)aFGPuSztw+NMuDlH!ekwEJ79AONiL>Pff|{U zrRf?hT2X6Vu&l%ey6 z3d2rK(H7cHJS}KxlUIy=i7=O3Oscit+`Yu|n(BHGZirh=$nUH)me73+ljGejbV?8V zYL=!G1S@F)9q~k5L##_AdvuUed&b;}=Ve-ua5AzO-^djKKZY-MYh4PI2Xwr0FnSx* zJPKgT$31yY2ie=k7PrW1O^9Z)BP4s=W-K`$J@tk(JIS3gfQKg)A z%7!kJltv&L;8T2jsPgp%I!EMtJb3lh|@pO7GLF#hWfGLVM4#eY#&Gr3Hdh#G%M} zx)&ZzF3ykT1Fl%Rfzz>ee>8{RM`AOH_9R2RLg-{Z{=-4>dBT^lhjWy$2jJnXjY*3! zJ;>eC2}4_v@ZtK|EtE;LlpGt%sKLMT6lNI-k$<566!1===jbP7{6M1R9eno^Ie#Or zQs9JBXE&gTL#Ud)eH=CNcZ;55BNX0I&~J`xV4_@7CbXaFe0F7RUnCct6f6&|Gk`UQ zFBktirH;yWrKg{i(B|16v~2+PTFg!1S(~d=TTfoI{qyc47PVdaWv^t(h#I#1UM!c4 zj7e81CBcYoO~O;y-(~!Xx=}*T7;OksOScZ$;a~}Neo9hRAb&FZ$ME3m{yvQ&TBQtI z;&f`6MTYrVR@rfZ=g+$d2A`=7HrNuq6z}?+Id^mT?UTA|jU@UZ3_=X}RPxr6C4eQ+!-!Xv@crFWZBMtRr&}pkh0YBXX#lRuXN;=VFj}&rZ6;UCU!Dv) zZBeJzc3m!Ox`sgV-~R9`lfGy7*{@H`ijyqdUqoXXo`aPPzRz~^p*xpM$P1!nG?X6$ z`K_<8#7D5fItjI63vvcY2m-*0Em|HVDJ~L_7o5a~D!$pk=j;9y-VM~wGzpFzg)&ds ziA?v*Bgq+V?#t(0!F=1gp+anbpIgeKscW3fyuxR#jf`W8OeBTRAapH5 z5QPqr17k%gQd0^V$mPyDjn~!8_}&k2n2?`TlU~DnIiu&_UK`@%}r!B$jnFED#D=ya7i#^7@)Vez~wk{*j0Ro zD*zP=cBmV(O`>dk=_d-^c{Quuj=n+3{W~J7OJJea9j-Cuu_Zg|KeoCPG3TUI6qU$h zPG=?d1J0xm!-Mxw{QB4Y$!bhSgz+vitelLFM;%iUDuBF4&Wm!Q>X@(kS525X0j>9=qmCSj1*Q6B^4)-L6ee?T|=ii8Vv@H4< z?6tYK3OuT=D&v6Ca!$Gl+7-g6+wB7$QI~*XmaZSEfVfAw;us-VZ+xMU23hL^SG5ZJ zk>U7hfdG+S(cq-dG@P=rbrx>7sp(p5B}z%!p7)2~puMr}EH0~J8@;jh1hYO3J4R?$ zKeexwCc&#af=_^{esZx4A`FtZY@Q6@7#Ar;>oY@hV3!uL-jm4Qp|C@@He2Q>Knl$K zCW}f*IC*O~4~|Kvyg1-#5OdhSs+O2RGqc-h$T$*@jpEpt6ax3))oKfV5tz>EFm3O> zUuEZ>4zD;OS_4|~3GY`|du#$oXjPn3jH5&q!185_HJNHhf39=|6r_Q$l1}2G0t`8L zIsj#=W>NnGcH|2Gtp@LNbPwesyl+x#1eBjBLV(r0x`px+o844;u`y3We&;F33$haJt~vuxw8Bh#Yb-B+N}!Ph8bxdE8cM6AM6R~( z|FZXFEBKscE`$Pk!q#JipC;l>OK&vA5%9?J?Jl=zb7lQSDH+)TaSw|pf)!m851&vU zbzxbCMa*Z4r%(eIPC*@l5UP&2Yx5tQ&EH6$sGD0Ql<`5G=ki_+2YCb>njnF4ca0^) zCuWO0>|2v3V{_uem;5To^Bj2zE_?sCxc9@g-9+u8_yUq5X64h6u>9SK5SISl>V8&y zo7k$oJ@8amN_>!JJ(-QouKjAR?>-?0tmK5#!7*V!`>sWV1z2}{m)H>TBM21^a6QVN zp&z~q&xitZQgJ-NKue3i9oQ)AG5Jvzk9#Y!kvo`ogE8i9>?Ns^Z!&-l+3s`3 z?=`?PX#z4`QE&}Oz01a}PYRKl-GJe8fR0R0q}Ub^9f2j5R|$=m3z`|3Ve(3lR}Q)#H*h!tt65O~bMJxanFZXA$xv@Msq7Ou>u z)6#HkkfC-jN)lz#!NU8&5__E_SJz)E^T012@&H4KTeF1Iu6YzQq^TYfDITB>>yqRu zFN-!@D+3{o>Qg z85Urpd0et-`l67sN#99dE7V}%aHI82GL|M+>=Ixu(lFML7LfrlnQg*kx)iCbcz+on zQ#~4_i99UjV9>&NYPc}GP^v&i|enT=aJecZ6#aIbO;7GCPzO3_k?OxmI5Ap4_~%+O`ENrq{F`ZP8 zi$5s_QSbW5=PA4>%;KvsiaDZgEz-8ZR%e!&`Y~c|0NmL$)XfdVWNz@^mWKOwGa zr$`7w3q9aTvHB1)3e79BI~+Ldmf)Ht4#~sK!8~y(&4QN#=0vlB);u$5^1E4yd9#%# zEyl#YZd$!G-xC&ujG9Hx+hP|b)@b{q7^TzvECh)jQHV|_h3f7<97WD(&F`b37vzlg z+@LHW+C2Odlp$mlgI4bU@pwG96BJv2-xOw(>JQZ2ALDC)Y$)LHM3 zCq~Mm%Q*ftwkqpy;HUm~`PL)B&15$+iRVhU9J3p?o4H%Ycm2Q~danNW-p^FZj4ZWc zjnRjn0YEc#$XVPa(Akl-AqJjv`*b`IV*iJhfciPzF= z!im++YuK}~_h%rFcE~k)178K(Tz`=0H5jL0B?KdOfj-mF1kF|HDWq}R6y`}rN%Lb907(xxK615`gEnRH%DA2R6axA*e3bOx zCy7~gYUhRFi;JigH*`~2d@w+k)Sx~;eV*i9#MWbS;O9W5Sv|>c2fu^_d)ntfrrpnH z2dk`?TH%g7FW4QEjcag+CZlp{t1XWvQ3Wtvaar1;6lOgl-7k4#Hx1{o869%Ck#zIX~@=1gH( z2$89f5%u34Kh`YRLV?4`9VVsI7G8w|mrl$&l6B{)j8t?Hbw@g-WrR-VPCUWaB@k!! z6vd(zDN2Huf`Ng7on0Xgf-TAwQFxs;fBA%Q%AspWUaF_3m_FN{Q|5`^nCNVn z!`CH=`fb+dVIOK#d$_LxtZIT=n-Ouo6En8ScvAk zjk~RQ#AaFx>B#h+7d5&EAUxcrvxs0emSb*QWN>63)C?!iIkFLK1qutceqT9AcX~^Q zS5^yo%Y$nvUR^oxE5CQlkH7&H-&1XLQlDfIKWVh{a~Tg`EYVK@vzRV+HEaWpO3QRo zn06@-Mc#>}Q)crWib{50$b&pR`z${W^dzjk-jzWBTl%6Ab^H{D9;SZHs}M zU-pgsA|u!f16cMXJds4{(++Y-letPHZw7=YoTcr3)Z^?19tV1cmK^c(?vftHcarrde|FEY&~a~+(Q!5S*!W`v z&}Et5AlYu1ZAKsV0o>W8I84!W)-aKR4j*ZLHDv!+z3MLw_r`7+BLN^5;&6;led3Rm zs}La@)Vxno-$g=0D{M<#7f&m+&Q;cyR}{I0Gv{c=q*9Tal(w{RYu2qA&d1s2I#P@S z21m5(M(n{B1fi43XL(*BnHXcE(*r;^uOXjkhHX{=#S&MfMb7}x986{}ao;ovbsl}o zfsU2J=>3ZTe37e9aDxHYT{&u7V{>5xzYX-#Uur4L%Ua1ok8f#}FD~4cN2(sdjfPI< zaVjUU0R}}94YAdihz<%kHA~G??03Mcnc-)~N>})bz)$>Q6zHOb#Blg54pg?y-+ax) z8U_`lFAo&NY~g03iWLM4-?FtPo`HhdHnB9|F%#>;Yq$N9t;*pG|G27D^`aPww{a(_ z0(Z{IpI8(>*tn|gskPAk_Q-LBv1zhgv+gH-(J0FHi5o<5 zWaL()ZpPzEp@G-lq78G0*K&0sor=oa!9St&iz$?_%r?MUl64bjV%_O0?-Z^^zV)}HWu!of9uyKn7MP0Q+yY2iF0p??!+Ldnc zED&9_O%lNYZF+EDAV=mylC(-SqXl2aD4&$qmOD7Hyh0)kz(B5G7CBk=%ha2Km|kyP z(06=f^T!Y6|Haf>M#b4Q;ktula0tOYz>B-PySux4kRXFwa0?#X-7Ppwf&~V5*Wm6B zd-CnG*E)+o{G9Hmy1KgRx~sxJc(s-V&&cJhy| z*wTR66~#k=M#&|Vo){(r>Z!7RSql#Ecq;F>9FvVX!w>wP@EbQ%k`al{$JC7S&K4ur z!(NgL9yEkp!~$tLncIT_-xdOjVva8u|7J4gw(Q1CFaN>H_qYCGBM+VEneSg(icHqu z{+3Np6&P?8j84KmRVan>bka0o$@7eUZD6$s*m11X%BE=z&lb}_uDyd=k&z3636{~G;^VSFIKU=Cc3f-AGZ@drA) zWDpKy{p7(NCV;GYB|?KY%_BIvQ*6Laqn0-dL9(Z@VVAAQ3eK{>_EK7q?obzQtdC-qe zX@5Uo&q-=%-91@SP_gZpU*Yf}-=2EcPSEX*NMTZ;wSICjL)erBTO228yeYZFJ zdaw;4!&Ap`aC@4XuFJ2M7bBv_Fht__8(sYI3h}vNC-%lMx8}5{M&k(Z1r|V< z9}TVwGKsPoqt?z9b=D$Os43J}%ZcQlrESC%`tbm8D(}u!@0;WcK`nskCbWIAg}7A9 zzPsmbdVE;}k?t3i%jlp@1;Sl&b8G^7gGmlDev)&ngG-kk7*`C$8=T0>i#FWXX z5Onhzw9#%AEBsMmt0%55NZMZaPu<6a7F0p8J9CnJ1bofc3ptu4RMvlA#bD+|&MHCs z<2juEPwv|iRcqw0F<4awi%*T3oXV=L68#*zD!hyw;tJ_lS!B@aUBuul9$Aa;9 z5Um4d=3sWGir>D%9dr?En=f^A|45b5*ld`>C~jx8Ca~YsGU)xa7~tDWcT^g@SNth> zRPlu!jK~jPbavh?xe*wp6T%9%*r`w3DfqPHBx(EqMe7z4&I3~=`4g6A^1B>VC}mw~ zNx}jhd$ip+&$u5VKI}{ytR$D?H*1L19+r5$+u_&vD6jjEzifzzc-K-aIOz8H@x4Co z=k>o@#eCO}?D@I0B8GP}Km^hbG1%2lQ8~LDV)zUz6_=qInjeWj%nXlhF+yM!n zyhRO{y#UQzI$35c$G8dBfe#!mct>lU#<|lxpKl>8ru~K2#XV6Yt}#7Cct#d$u%Zos2Raqy{Mk(Yj2194gA?u^84J-u95 zfXHKne0Ah>5m5a04>m}>hpy>We~e_${~4~lop=r+J@x8TsZe|8mDeurT(>Aw=UykH zwH2^|+fo(Xf}ESY*Qi@>`Mg^$#jW4)$DJ&K3!9^dX1RCBJ@DqE@s; zrhdP}U~rDq;nLN7EQ!VMxE_bmERo0Q9Q*a)WApnPk}ieVhY7)JMWfM_H4CHs{QS*B zmfn|X1IPrlSj=y{ZCiukdR_noQG1F_@=6b_WGM4kEh$cTcfnA-SATOdb z+@vMD7={YNmp|h;l?X0vth%5B#?czFbP>r&LSJonifOA8*^CBE{Ov@C{GkbPA~GD3 zdxEd4w{f+awaR=bdpf<{ncGzZ8ape52K{x!t<&2*nyUw0fhUK&rZdO z9~oG%nnu@)GspUzR}L%foT|r$-{p59Fx-!Y0eVo}m z@p;lWY9w_bH3de*Me`Vq8%3yWaUc6TZh<`!Nkp-Cj5G%!SA~LO)3|f_1TLh73v67R2=pBPczhY z@Y|ZYF%?_rp4RPA_0{b=<#L4osf#|+MJ%7Qu|F}0j1`_M%2ts~>n$i(=vL5(u1&nE zwl?jd_LFv4F5^4=cs}j-X;gB>AdU(hxG_ubho-O1k1(^E@d_><*?q>fQEF!C@FX_Y zJkgwlyrT$vd*N%fP!1CkT?z|?>}XSGSKVx(7FQ6uWL_|-m4O#faVg;dSz)} z?P+4fCjy9heWXakY;|Q1#IT@?SHD)&bJw0yqC07-|G_%ao-HBsE-YVhdeK-K$y{nw zJhP%a^XR{SxIGnvofl0tnco~ZeK2~*fgL`FK=>U2BUt|U>6ol;OtPUIWx+^B;r%qf zFmk*6?_WzoZb$s?hwYew#OTQ(vOqte0?9LZv`h|%D{V__dhS*P5Ie|s^1pVt!-;U3 z$UE5p(M{a2#W+q3>W2&v!XgU3`L%ZYq}wdjPn1`ZP9S4?@%==AD*N;!z9&KeB^ZJY zI&5gd4v8Hex5j-`B!mX)rAqG)?+B_ye46%KIs!$@3;9UhM89_fXfK7EAWPmi{e~oG z7TqrE>LKo{I@Oj0UgX^m!X#(;KT=%%@_9A(b00eV#;^nKFbi{56l?G9Px>p;i07H6%eeCjsaTi_PGXim z_1Hh-%$;dvU0sMfK=WfMdC4l6mzeg1*G`6jn}kg9rmw#b_QQCl#zQZ5zeHqmJITW# zpck_Wvk#%0sVrOL+ey^l$J$JHDVawVwQ^NQJn{U-*&DzAkxXm~04oq>*uy!)I+h!u zI09kg9+dEVvD-f!pw_SDe|aSr3$y?Hg-um0aWFjBe6mayQ>^KqpTE?Vqu$@pKRQR4 z6ymcSb~4l$k(^EuV=9-)`7egpf`6`5E227fwjoqN=a9<`L9FdtH_ww#ey4_5Q(jLgTT(cr$4y9mQQ%Br>BI7GNK~bPC zo-!X2lDw<3)8*wQrzsd9FW>l5)5yG5B!h9Z-recZJm!mU*Sn%P%Pod)yL_Y(*y0pG z@J2AiVbTu%Y)ZO;J*8`S4ATe56?1^g2DmGgT`5nE#`I2>yeL=;ugJ6()ixMWEj;hZ z4#5N<8)JzeLQ~>Yl!2z&CyrFRswZ@%6LJ|ygf!+~t_mT7 z6x2W$CgeY9+M~>z*{jGp{8LG3y=TENq@J$Rp_-Axo|2cNLe#VRnZ$C*phkWw;%QePWu25Dz>Xh}{zx0!^AFozzYIs$ zJd84>jW2~*M9Qjub0B+HFO=x2Q44rR_n?CL$KCM}90Gl$LmcmB^YJ*J>fOiTIDW?H zi&*>wg85n_2TW&wjcy;0>`+gdh9T*kWZmuZYjn4m?wlW8OObPG)Rf0x49f({_lc*S z;U7ZB2AAPC>A)_{f)-&6f_dV5I>D~skDF}y1YjQ!nu}!4_u%{MiY3B8etP1ALwk7? z_Z-B>FyQKR5E(-JmrDj@Bs6?2xB{2h*(0b`V^T+9EBuY6FWh<<9~~_w3w%kuJ6*9! zzt^tOpJHlV6(=WO*W0#gbzQ(x0%r1-FMEs~w)CG()T>^)?!}v^u44EN@hDGz`AYz( zH~Xb<{Sd&OFVK-4n2~19SOU#ijdfY!S^lfL-dOS9U3V%k(Ox-Wc5EJ z-OP}GEO?!g* zN`$+h%E!drMylj$ZA!=>yDntEL6E?P!*)&PxP0Y!oW)?3%Lea51W;p5*u$#3!?z_Z z;`maR+ACu!KRZ)UVzuJ_=5Xd_&1++-{j1(5~s>l$a@7KGAYkclr|S-G`Gfw;^gm$vtofYtONHby`LB&l=PahqQN+9lWG~19U(K_|`$;KQb z^NYYAyTLn^0fNf=;Xwjo)XD$KAo|uXz}6SB0NjTUwiqX{?qWk%@mLZz&7?1^mT*co zCx^)-X8DIrCYtU1{#02#YcAh%&rV(vRvTY!4$ZZhZyhxCT9h#>;-Ijc8j;Xb2QHN4 z(BH{z)%mU?O7kYLlG5Z|te zjJtYW$ed($#9UvE5_E-5%J}WT2qiQMe%zr-IN>W}a>4!hA7_3B@LOsL!aCY+OKvZ;{8Rvg_HS1R zY1K5zIZb8Y)OHJP!5DDm>P_La(3T*tMV&0+ua)Z6{h)PVvwLSDiG;` zc(Ys8CU zru1xQsa8IvvHUyYzXFrjE1uQOQ-e9nLwvif%sE+{}0XulEn6M;vm6k%nKHC@n$JXw15A%WRu zzrx4<#cIX%Q^e2Ax!t)Sd|n@|ZsRILK@DS#nmwF65f@|j@$IA?+IA&&=sQx*CVSUr zU&Gpmnncgz8#Fd8f-__B3nTY9HOz^!NnC)mrNpL{DG+Ro@4W}XdcqG|YTB_4$_aPL zv3LISOkoV=t0mA7;l>K0Z<56VW>~)7T<=-jOgd+k5OBsTbpDW|yHs;)Z07Twxr~`L zXFf(GL z*JTSd%?=K`4Yf194Jwbu(jNwQV2iYrFMz3 zsde(6Xom4e`)S1O%`F&Lv(=9gTKFxadnYY$Dr43#{B~fCBIUg$4K<^DO)^coTsksZ zy*_FviFt6;UgdsyAthg(s#|ECD+LkElk1|<{QgYH>wfT2DT_Ofot@oL`x9;^!&oL) z#pz*x=ot0Q_s?m(M+JDqmoB&gBDfo>vfc!a+5Q+8Z;j#z1HAt5V2#rS00+$7)xj_) zE746W^aKirhIC6cHtfme&@$70=$&5(yE#-_$5j40G{ z6#fz33P)GYd&%id<=1#sv?iCvv|PeTJw&TXKeM}J6W)51%vJ5OZ)>w8wXh0bAs|3A z^FJoq@_$TpAw)`}m8Wwm2`^2S!%EP=TIn>;R>d`Sr@CmM8x9SXkdyZB%j5NIhlfMw zpq{=yVfV#V9X65COVXs+oU}47hpo?9_YIR|j1RW@nH2Z9HS|rk;*2|MB`0yi1ZLkP z58^RwRNF-&5i{d{NrMs0YQ+N~K<~QEX6RkF*IfCM$A*`dG0jV~T8s=8e(+IAhN-Po zTG<_i7u}HEWe=xdV=0@U*)ng~2P$o!ONv#)^q~?<<>~pEdfY4Ox#+l4K8=bqEC>aX zgl!2p@3y77-zqjFe$=6g=NFs{UKSZ#5%yCAo)-;~`ZAJJP?-DF=(n8Ee}SYiHJko| zeG2Gm!ov2f;t(&it2a3~`mupnNRT~*B4C>B!BU)kE%-454i#qANd8Zg(LF!}Kn6Hj za%I~@dJX;E7fb+#4~lDE`ga=2I8BM+#x_NQlyAe6Gt3(>c9H{>>?9$Td!N+I$eZuB zj%^3iESj%Jt&VhSh;OSkf`8XLkD+j4c-&ozclVUL)wU8&?SdP0b*F3wI%qMce{pDq z&`3rKYs?^TFN>__>L=JM9U@F0UNt~8YmND$RiauGSE{C@PbshOH-E5M9EC?jG$nuQ z_{lkt8{~U`rnEDZ(3)D(JQzb2?{l&gvv}WDZa{982gS;TSrPx-exD;#>mk2@1wT(M z+948Y<$gRD+u>LogmHW^LU=(y{6y$d4nqk=>fd497g3lkH}x6@?mdbJQ%6#0aYzXj zCFTNnBtq zzCF|cMFc5fYManpP@%SFssks@I%)U{V;Q{m`s?e@Y@-q%hD&j6^Z}Xh3PPK8dyU!S z4xl@`i+j!`fRQ|xZpVY)*FO9C!f^SH<8F0iWAP&;K1+EfnzOXO@Tr8^LoT+Z37m?^ z>y5<^0$&8ghSVm*b~jG*e#A=Ef{+@<5+UQ!{KJZfI${d_#ggyrDt}mflflN=-%BvF zUgFqMP5bHyzz{57xW^!x{^!X`I3ob}G(QZ%KTFCYWDN)vNSSU2IG zHs~f)S9g@muMl0hiViNS?UYI7yHu>Glv+rfan4~Tk#rjS&)0eNVN80YMI8M;Rw?Uf2`UmxC+3E(1Q-~it^bXnW*9jax zJnf`Ra}VGN5*~H3^7CibZ=goVL$E#`(H~6~z}=p%=-{_E&P0jFzLqU5WOPtVRpW79 z@Eo0dxM*Cx6~zdOE0+P(93ax0!`ah;ZyRGySfF8>AoBI11qm3qDFDX*=T9sDrx1dw z5@0(89=v{LSE|QQbekjIgsi-@Q?8g%pCWf(>?>CaxnkZrYi10(S4xpor8f;Fq}iR} z+vxv1moqZn`kzXTr2YB-X|HsSed`1j+$f>72usft9WUsKxF{CMW~%x-$A6M4EYNfv zJBywtuy`1z?0(v`pl;i?JF$4;Ydtg7BI9qB@4frnub-j6I@I4kS#Q=yOCCJbas;gr zze0=NoL^RXZ&8B@AQJ^T#z!AY`Gt~vFy%4enen!84bx$Yf=&c?UNFKmQ`EL%UZ7r}Oad1%K;jIlKhSzB07L-@0PaP2Bs5)HFd{HB z$_(xWkjZsSB@r|saBQ%_DVn%Kk5L!T;#nu7;#Dgl@uc*0El33{m0Voi2L_wbSs}SU17o5_uM%WC#r*ZRqb4r|(gH>BXw1F9a1Ill$}u zZ4J4IdeDORgE9b}L;D%@HBik74*;Y`A@E>}VF)iso|{^Q(gk3hAPFM*=P{aqA!Fp2 ztj)kXv*Dc$nemxJ20nL0<8jh=3!;+v=a}5w)|_c(YeIc)O4f7RO_1pn41K?289pyz z0gsYkG_=9FKbQM2)nyJZjGSekkx^5{F#Krw*rc<^@|Yi2Y$f0wUSe8JvXI7K{SYU7;yvD)D72g zaE&OODgQ@UkUUF42m#=F1Y(;E5EF;9M#HxOj*Yr)0@=S_+udg{%n~aQT0NaFN_7NW z5$d;{#}Zrh7>|w=nQW>SE7`-QNl<6)NPz_iH-mR#gE`6%CrdROOjNFB3~~DoQagq%xjZ&wakzXkEm$4ah=9s!u1vFZG zuHSxtRg#hZ#kvD?y$IDVbj4OF9M6TSGgjjA#B;H#Cx-PUjEGtf?O*uDe0gS-Ii5_?E&6O=*r0HWG1_(UgHun2@BpWW-i@0)Ma28oA;&m$I zHNu6(m}ML`IsqLgsFFU;f!C@D*oP>tvFmC&XdpIYcK`Rp%wiV+Lv8TT>bx(){rD zddxrR1`GWHL@UN-1cE$Y|5S~lck}$(JJa*wnU-<+@=ZV2N}y|-)q`wBXcLX|Ec~Vs z$98ulRXu^x;O8jSPAtk;4uqirzP&scQ|d9bNtXD&FyhCY7jakOR0Nn7Zft z?Jd&Q0{#4;9v`YEsiQ%tbW5FBDn#-qW()A|(Gs^ZwVlDPy8Ey2Qzoa7>KMnEw3;uD-hz6$%D%I#f{5FSL~+F z7=1@_=ft05FmI> zb}_nYx%tsF^GsS)b<+;Y7y?hO9X9{_dwYDG?$Yioss;}chwGRO0+qT?p((Q^stN_` zuWuxo(+^IzukG93u2fZAGvv_q*msRx(VeP0mPWxBz_8ex7Uy*EjjaHe4Qwr-C9%DA z_#gH#4#9q+d_YY|4C5EfUN&t)TSq_E%XQX_u2=0xJ5dJPE)& zB~ge7%!y?G48hcd8UoQs0?a!?{q^_PpnSk=(A5^I=w%#@8#GDQeDW5LP57IE2L4bE zs=z5PTX)IDyHw<>V+h>0)OmtNNrivshx^Ax=6fJJlu792Jr0jXn$+#Fy?WTEC}@8O zH!&#BBO0Z$2fdrw2_0MdF1wOeGH17W6KX)mS9fO<W7KiAjXn;EL zBuo#i+FU}la&d}WwwF!>!PO)WrU!UGD=YoHYy&pdGrW(?P~% zW)EobsFYq2jBqAGr7?Q#H^)DBnCF_vp=fu5hOy6?y4vw*=7_mbd%}5Y;~%pG-R}tT zF(!tJ$ty~w90KFAx5IElF)}REgU&qi&aXoDvfcHJq1{zJay$LYNn?~q?l+1R!~2Pj z{Irft$wQ0{I)jfz`!gdA3}b_O<~fobHe*7wmlg3hdOH)v?!p*jD3(kZ8$=n<(!o?B z>j|z1Ev+m^iW|@K3ne&$2J(H5VdHx4MuNP|Hqfds7U6D0kMhhbzKQgrYFCQ8Z$?ZO z+?4uK!U}97noQEpZ;Xhce}P?a;3|JXKH4N`#uC*r{q&g@{7;&RW%B3gQL_GiP-TI% zxH%?6nJqOZU1f%+3=>2AgSQS`04cAJPMf-6C#V#4wO!05?@pG;$7LooWtllue|xmPX-X=cw|sTyGWT< zg<&b^LatYPMEy(~#JTAbRWxssuRI@-XUuWALRyrx=XUXHwqob4P&L~-{N0m|W}~>n zuGPqbCoef_WIlG4hOt&1SI_U%QvB3Ox}|^=%SRRD-o^>~`*r%wXPc#+siw~UO1HHj z$8VnNd2c-}mK0L*Pdgh!3MtjDz}TsNR^;#=DNZQ}(0Ed-Aw4D z4c+;L+6=|z#v@H9`zzY?zf0A{-y`Bl)m5V-a3Z(G;cD$Ri7cJrFBl0~h%E0PA^Ghup}L7@N?ea;3a6?Hu>I$Gk462+A(juVrP^E2niNkY@s=aZ!dp3g*T>0Hx@ESS5Yr3wOp$k z7PlKngY(?+34%3Xf46hiMU1|-v_#wY!H0{w%;uV0~a()7H821l)k&EQd9o^BwF z;t3m6;seSud?)tKqTHA}cUamNYwkh5T+~g9D<4E`!W>XG5acRU4U@#XyrOQ$6H!6Vo=m`~II5NfjBs<{Cp$Z*h70#|PEos2UdimG^Px%Wf9R}n}is!86m z`gZGGnip6JBwu=<=B?2B#bZ!z%qv|)^l_}@t6a4A)Cx?MZ%H0&|0A6x-8XDCbZr8Y zq2pD|P2QC0N@sK$?WQ*n?4~;;++iUHv$V@(5xNMfz4ha-f>3zJb72RHH|mQ0gUWq4 z!B32g^sz~V)D?E(d9F~Z@#zG{|1JR`2m13;v)6I=#UV=F>W zhjk`ExFZGnK zR|HiPx~%(u8%uDcy7KbqKG5vte{Ccvo5+T`z@9T$C@W!+5j!JN?NWA^e^_hNY-#Hd znbv}{Fjs6_lF)j@@ZGbk>qF32Ci;(zmP zafOH|z)(fQlp!bsDGHRA$pKt$`EyUCs!YGzE0Q}JBACy%LPJwS~7m4eA) zRmCB`oW-JAjXRU{i%IC2omT3|Z?Uiq+MuTaXa4z)Ex-wfgph{)D@hQPh6k<~x5zi?d;D})a3s7iZ(81Dqj@hpM zXu%Fa?+=5(wWx=>;Ku^geef zpwy(`X$fxG-#H2+S4xE4D#N`g4#{+s;v-7z?I%WCKR740i4$DR2`FN{(eUl^vvH>2 z?PoRPTsj?P?w8Y5$13=#_9jUdLHTCV2}jl*v+zEb{UvS@HR~_JV@uv4Z$g6;9st;T9w9(LnZ+1;`vi@v8p$G^_{*h?an}PuGv6`K7A4hmRA|11z84 zl$3pGNl`}VKZcdhnHIU^ON*dj0oLh%yc9A)F>_BwrsSWiPXo%U?<%pfyJT!SNN8j zOPu{s4-wZ~`Ke(6iwGxyO?EH!>ZsPbtkEn}NvhyOtp z^O=E0qNlJHO2KwPX(R&*xnE$XgAIatV7v}^uzy#>)WFU@RDF9O4N|nie89pdC6Nqk zqDR?L)Q)Y^2}{Bevr+KPRjC6KGDw8)tJ6EEGFNL28bs(YvJdo|34?&fKGnw79U?x6 zUvtwH&4vQ42sjT{q0ttn^8(%1s)8SEM2r7rq^$=sF?F@V<#c9BD2eFG^QgRL;hqS8 z$fG;!DrVDhCYjX4Jsx4g6-<}al(gMoX*7(MM#W39s`r{RrhOMukmviqaxrykzLP}9 z%COfJn@UCkTJjTL#6$N~NVkH{CSVp8YB%A3z^dI0`l?wzN~xvg+Ce z{~jh^galoGnEp*F7k(0FWVwp#uLd9U6N(>+apeJs33EUA82gf}r$K>Z*Fl{V%ObQZ zNCy}$IrIGx;*5XsK69!>xr-h0hugW-jtr^STUE%=UPExbB%@>g$uC@|fY!*XovAOM9(14`| zpLh#y)gRR=Usnc)eEG^@-p(8`pkVgUou+-Xt%)AZ?l{kq}&YhjcJ8~K_Yk98Y+Mg&c$s4LI&zH@)QjGf%- zV7EAZ^t8w8&YwRyFijc?N{&%xO>wC$sOI(YOi#yJ?FpX2pULBkB`5b5Zswcn`+6{S zdsqwpYq|tCa#J=mA|xMG5QQC{!BsT5@V(^YyEd5~WrLwWHjP(g$nVfeZaR{aVHVY@D+HYCGh5`Xt8Pzg)J^%xx@@Cdv1SA`u}Grm&5*m^o{4Tp+ze8U)Ttz}Q z9RKY&%oSW)qWoOF@CASKY&@tkv1}JqjoXMBVPEX@87vllj@_O6=6<1Aahqe)cvv?f zm^}1e(sD}qdOMNurqd9>2YS6BYNqb9v}8@M)A5U2T*S{{&0dyiEfbgt_jb%)!xf3_ zQO;!~u<>oWpN@-gE)`*(C?Q!AIE>@4IBZBxIMxxP&+bmVAzU-uvyMq&qsc3Fo-UjG z)7*JuojvRq5Dy?y7lc5oL={14{o&X|Aze0(+~9D6)&^t{j!PnJ^)I zl^X{fv+VXt+2faJI}W}-c}Sj5>=?YriU2}Dt-2mI2>$`WH1vfo2u&De8Y-RpRXoFk zSB*-;C2=I~-qlmqpo*Bxkn^JQ89h2m9JWmP*A!8xzDd!dXx>|InW5o-0Ob-?>+8$}mg6u_GRLi=@CiWzA^7_VY^-r*0b0@0&Ww+nHK;roZlPof7tU zb^W*r!R`^h^Qp*5pbwNK(+0W{KL|^e1^^`F6ZE-~-8Kun)?n^k;OJFc7~7@&$IF8?|}ELWJD! zxE==N=2RWC&s;LO6G2$u2~lU!L`DJwdzOpYZQP>jB*N`Iry#T*DEAtKCKdyHFlOn` z;~*Hnhow8BBfBu92`ZYp$ zu9mF!&Z1)LN3zNfg36Ej!Q56(g{>1k7V%$sz{(u_gQlx$l39)15V4qJCK=!)A)|jR zQgo>nmyT*)n{e~P$fry%E8(?7)^E6)c`{a^Shj9Y3ofkFY5)2O23S3*x#_Y?eUMVS zF5ly9n1+8aXUP|-d!Z`%nu~x%CaMV2u;uNLVQ{$KcrSZ;_14fn7f@E3sC0N{ac??b z&pj3bGc;%x^JkB<_sOs#9%I5^Qi&pK(jawEz4b?Jvv3n=cfO)agY*2#@9FWkdnLZU z+QUVCQE{r%PS?Rz7m zh$trKT>UQO3pXZi_zY0%B9r=`b`h(@Qx((ypp``=mALmEe6dVh(XA#qNRL-0PrKUB zE6wh=Vdt;NyIPIPI!5dks?l`gXqZ^8EJFAjSU1X_4|Oc z|8HWu1(rs?X@rxx-#WX#YbGXW?AMhiL2s1NzDZ6 zeXMH)FKGN_MP^wTGqJpJ0oEB0Qz7!nc zX!;lXuWHq+ldS{yAQI`v2}!2P%k=i}qfUqEtyRiB-(xUk6voVu7D$Uo8HRr`hxxBb zeq0NW?EO7yXF|^)yLfIQJ)VC2oy~b!_%q>*^{Db(1j0ZcUqr=IuKp}nDGF1?FDxnE zSA5^29;G~agCobJvkks$W+9Iw0)15{=CzMraIaZW8Lh9U2cm1;nv`8YA+Ia%XpE>b z8wNp%EDj!xPB;?QWH0gMDxQ-AmrTz~8TxM)IMlhf7JW%_wJfcy>AzcLejN^f>R{gE z=TG(uPx90%ZIE-Gyq24g+nH(!F^T!cOc>Rp^$(K%)g+6y5x-@g_lA2~TWBd`Hc)<~D*0Bj z$Tg`iiPKvyl;xFHi+f=i8wJ{nMdQrHwBXMP%ycvTIP5JRswg4ixlXR3|Ao*B+wxEA zfBRc{Y^B<|xZbsIcEfT=8|fHKDqS~;hw{I0M(9UNai8_oTQ2OIDJ;UU>+WQhsLNU- zIfq@DH=1|Ngl>caO=RU?Nr-04T>wp3qA$AgR!S5}* zd?#}LZ_Er3)AJ)|QX!{}G)L08Drmz!CE*0i88I`540mvn45Kcj?_QeP!8?Gnk+o<0 zwR_}Tode|Lw}qXspwvH@)U_cWuVK6svJ;8MM@13JSSEnJN1D{dHL^=6buo{y4l$L~KF*$;wAHTBa6jm97Lvp^7=>0}7K^q3*N#9X z1Jx=|>qR4O%ONq=({47f+^vCqOB5RuX*Zq^J3gKYYtPb%HluAvV$89Bqf?Co0N=|3ZQFSQYU zSmYdU6P9}RW018K{r~-ee*!_BA!YSOodFusH1UN~Q3;dZi@fB|&I8+29cP41w)pIE zDteYnXMT-q`l=Xl*q_!)5H)y>Wd?LV1n!mNF?6YJv+iq-R=%n^aZ6dSES;UK?R5zj z=}@;lNS(YuP)6;b?ftE*Qz}g@D(oaR_@iDe#~SV(EtfExMQnOg25aBRSezTpD#J_E zEvlA&fBfO>ckpu=X2tg_Xczyb4{uC<#LW`da{4NES3MK`%@3Be2e4SVmr-R2HX0HC z)f&_RAo6PrrZ&Npl6Xi!7K-4_{T1W)nUtA&tAb5Ac3W$b!|kA=U+g)O3~J{Y>%Bp_zC*=3~SMv zr@j1X$$|WC@3)*J7q7!$QOv7{{%HT-`Wz^ASJim#ihxR)4k3bsAL($WETOdVAKEbU z)TKq>i%p#R@cnO_`Fg5%k_7sf<3JkH5{1X~V(X=#M>Y?_%U`4O{nBUx_=&$bll+S86Gh`$ADF#9>=dK`V4$*5&=WBu*B z{nm9w)4-?6U0kh+C$P7Pu8b^~bOzZ8a>~7wN3Qt1c1^UEJC(gp>PFX&AH`w3~hM5GrTPzC_)Dc3RmK`X!-vY!#Tdn}+x%EFvN|Vj$Gj z@fRd1JnMtG+^zrP5IaHr!}|L*Vx}&#CU8-fLy3uElll^MbPX`^rvYcw=syh__zS{* z#jc=BqD`i8&H_PnQ3;)JT7@4?(LvgT|A(uu4vVte`W_fUVJHFVkXE`I1|>uq=|*B` zh8~)c5~Yz&B}G!Y2auBPhM}cXI==C|?|II7zw7>ki@9L-y7yjt#c!3m$YYl|ov~ua zvL5qX_;qMWe!QIVOW?mB`Dw&LgB$Y}-rjE|+}qC8CaqN?`YD-Hr|YhOpZZTwOKD$q84G7PIUfT=XU+3y= zTA&r0aa9|s6=LvSV~>Ig=FXGfN&0Y;xSUIXAP&EwqhQPAKDRm{+suy^?6eBwr$PeD<7ejX$*TgDf z511{1=RlBr@U~gY9gjeH{@?7yGChNCT`@)6t6QLb4ULWb9b#-h79Cw zH}wM#i&~Q=y7}|F!;V{T^l;|=t?MC?U86N*3f3dqKNh8p61Kg6taOb|7Ay=pTW}y% zJvd?{hFr9k&YDG0A8W`NTzx-m1ukBC$J?Ku{bZy zXwQe@C^Chbb1HgQ&LFw4vlmB{IizwqUwHxCRtrE7`X)Nw3sP^p=5E(rhIkBbB_JmO zv15OnopV^5Ve@2klusy`0@X*N(SI}F|NIOX2XG%7Cih8UIuH*xK5vnk8`{?YB}yIv z5`4p54}P$AR9AAfN$CrP=|EpSRb$!C@_gSpkCt`sk2!-jfKg4qlrDgug6{XYf;hr8 zszW7<@@;6Ld{L)Y9;_<&(RG=BxDvI0&qo`@{}RXlE1IV%k<8(0=n~Lu96jIgV|Rx^ z-w_z2X~S-AL{i~9wXZxwIrz&@q6w4w#_vDxx@_+x%Nwt%TRx>k^$z|&&qxdwESJU^ zVF7uDYR8Zn&ZCW~19b6Q!;%cR8jYqRLy-Bg+ z8H&95h1k!Oa)la*9{=CH{rlVXG?+_|8bFOPV|@QEhXEn*)7@op%J*3^{0-B`1tDLZ{>IS%{dA&iak)_*iDXZC(qOr&qR`D)EAWs#`yyZ6 znt181+rrl`t`%FKnGurU*F;+-K1;8{ym`b7Ng%tLG>`Ybq3{a^wJ1*1B8H78w$1Ob zl!(q~aGzqOU22|ZuF5I>56SQ%w9xr$Fp23Q-!L%P-@Sv0zyMPQ7=vuCIZo`>P=-c^ zO8vn3@mqO`!oc4HPcqW2b0&RtYA*f5FqK7G{5|KQ|DMSwzOsdiPq%A-#*6&e<|$-5 z$K^*Z$O54GAIGwUpQ8VG>4Y`Imf)!0dUEMQ&-1uK&M}S$`?a0k`nSp#-0mcKxOi;% z#Ys1lg?@eiA}?-^6hePhBsUt#PDvQ7pj(}v-Mrc`re->`BCFQM?>VETffE@`@=ze6#f;6%`d2l+DjCFy2~f7RZCf`ew6V`yi`swzb4x2nI5~DC}NrB%_@bUXL9z}NDdt7s9g~K&Jy1k1*Ajd ztwwxXE(uNXqw8q)S@5axC-{6kEf(u90*dfVIeL()(LUW(LdnFJDVJZ3j-kLW&Xz63 z#>U&i4b7U#20hPrYsv7>0uugkmHp)iRht|Dwx;3RF7;VDObt&Nq0Bm$Sb^jw!GouB z>brDeh7Tl;-&*39Ui(D7Cl!*rNO7tz?solU153(l(XQw zT`S<@!Xy1Az#hjsfIi{&E-;-M&R%Y?1}tKQE{K#+-;o%T{}RRARt4e!6^S_7S=8S4 zeEv{F12^%CUT2OT+Bk=lF&#fXFTB>Cf9n6W_^GkNN2zg-iy}VOYh`WD#*ZZadEXD% z_6H_D7Rhnzz_MtS*GWBPF+(xWx>JR>sX^7h>7|nTl z^%O4c)ze^53zEOZY4Dco&qd$@HoKWtt)eByyR#@E=#q>p-;U?skht4(9AIqg0CB#* zA`+;Yr~MKKph5}*b^#C@2EOC=&1Lj)K>FK>^OBdJ$1x+Al^cI!;j%=|v2w`fa6KmR zw>~Bk`Wf|`|IRBxNHo7bY5MG5LYkS&6V0-SI= zrRk12p~O5mFubGb&auP~XuRswz-0x?7;&zzkL4oZVHODg@J`RxiTWbf+99Te2%Ddj zrnN2gYYr{0yBYBLfW}2?i9cX|hbi*_HQ`K;@z=moNO58I(8UB>hD%k4J};%k=XOc# zwO1y#{7&64Iw7?gnhA?oQTzMA%YMdATO2PlGmVfWET8h_o|Ffh!fyO^E#I6w(=qdR zth_b$=2)d9d;!sHTCa_sc{3Dq*=t=|4|T6bv)f~1laI%YCsT-i@Rzw#7P#8Tl9GM3c#!FT@i^E3fO<#HPyBjd>6konP}HV)lN#`#l3O z8u|k>+ZKa-ElGX;3XYx}_IXg@-oi?%^`3EQq5cblrGZI_WYKq7or4X_88H_FPX&as zOV3KmhlOq~7Kev>HsUVs-Lt(NcGiUSofJ?VL}Jt7dFN=0WEbPs^fcSw?qm6b&rNN7 z(GkqizUaPaO6WQ|;e{h!BV}?+L^3jE1Obo8F^`r6XnGYnQMs_h01!?20Qi}u2k+x~z zYwY{}wx$jvKIFU3)N&b4+;qX4l9esk?N-mAz#*C2;Kg(r?u{w3Cv)jgpzUDNr`&qz z_L^?9e~R1DqhQirFz|Mj(CzfS@YP|pvG|c))rmrzaKe1+&_-O3X{zUQ82MvZ;jrVt zs9Qv$fhGMaP4)0d3_tKFMi`yVO1qo$@$bBRc3_PtpEs<$^jJTG z$O(ayyN-Fwa}M0X*7nBEB4POnZwO$KPute}Ev+!62uzpY5EIbyI|R%CLd7FlWVcE*faW zY+%{lt!svU(_T~mprUD}0}H`D-!vCq^;+v~m1y}xL&J5?z0ec~uP2ntNHA-|H9eWc z=XOm!^KC*M8kI|7xBV9XH%G{)+&PBb-{R%G{?@#k>@&or{1tZ?p6|1B%Xv)$H7wfRJMMt1^CBUEgLLhNcu9V9fG-Zu&DS2)qF&JXl4C~qaz>rDrVIa z?I)9Ln9543c^WA0eTzkH$U87dfC;!j!VsI$BR}!YkPOSfsK+`^v*IDcS^=T|G# zy4&H-f7~ri&X>b(3I65c)5Z6wGL1-0S-x%EyE8mc(%hxep!GO7uiuvpBM zHTA-rTP-#19n8{dmwR-@PCJN=Uo2~fon0HIPxt|E9llM5QB?J6UJ6{?$W(r|Z9S1% zi@HHnXL{P`YFmv5-uJ;`7OlntC3usi1-BNuzdu3c1by6qxfLfZ65mbP&ZcYs-P`{m z?=R=Q%W)zYjy&$tzC&3$;WJEK$83DuQ)F2fMPGVcMQ~o~-;Zq#p zRV#PUC~-D4EB@)_;#THtE#bxGv>x~w0OWk#xcPkIImsckj&;=JGK;2m)YtD=!v=9TO|>9CuW zRDV13Fx+o{*1{m#v&n9}Rz-OGeA2{k%b_9K6td+w)-x=r+dsKHbkD7LQAazsyLbDT zPA#-6i2Oq)THp!CHmVx?t}gQ8?M>OoZ=#h~-H*xAl31nqw)f9@m&OTFYl+W$BB#r{ zJAF;J1%-Rt)5W@AsO0aGnOGwuyGf6zi>ZE=)04ov`U!Mgs>7(!ZZpMqWqU`Q$L4iU z6HNSJLn5cMdy-1$l7bFQFBRK)99lPt5=2_v>|Meaiq#*227ZXv`g#?;;xFTz9SD;e^9AYdH+I8TZP38hHGM9rZ~9v48+P2qrEH$@PNw^E zG@%`Wiy$3jO$o=x=>@@pI=Y=b?@=|{!lKc zTj~_ZcJsH)O@+`3l`8DTFH*KaoX1ZlUS>hGYPoHfZ&0=%7Ine&8S1hk=rJMIWO)S{Y16GMvv;_7i ztSJ|p2Cn&w&kpF>!%Hf|W`*DEQYfSita+u=_F6EX?lBhZFRZEbOP0^F@mo9$np;Yx z;IeL@hUQru3^`@B(abgTfAb9X+;JvTaMqL{DZqNF6EnviS@`lY@{^LC)@xs#)A~8i z0+o58#k_f4U#^?0yHMKU`pZ0)&}s@@-_UB(F4eL9?=3~SgfzN4yeJV`iMl)_ePF93 zk(Ye~lENudSuM)r$6M(W995p`=~cR%b=K zVl@2@HBAqAg3iBUSWbT%2T($mKgLZoreAkc^uvgy&ml;@y1V|ofVSGh3zI-6I#-$fV?{#%t<=O-t`Xo}^O`R|6B)xHpTTkoiOF=T`mEq;@?LNW5cG{avrZVgmD z+&lqO$P2(E@1?X+6pl{v2Yi8u{g~_%Aum{Tw^$u<3mEM0E;OlQM_~NGpd}BIK2rt;7V+*7!MG#MjpC)|Dbq}`ITox*(}Ec1}U>R_z{-jg`jrUPv2AbmZRLyfusLh^q}NW z5eKlZ{7bPzAk~*Yd0xpVIv=ZDwVg4s1B=sMAFM=zW8|Cq$y@qN$(_#Oe{X;|$IU5zQ>8o{fg@sSg%uBV2 zuF>->a5?#IX<{)JcvJ&?&YWj3N?HD1L;Il+T}(I=R>^zHJ+t&9nK!l0v@f(pH>>}u zVLM#dzme{oueT?a&*3u8`)KN1f?$<#tca0yB}RQ7+FhqqHG6b#XqT$3>{A$be-L{R zCZ`gfmEk|_I3=n)ekDw0x~$a;Y9ET7>8SMgX0)%_Unf!Q?qnr}iJQP1Q=1az^`0=Y+mj;#)#So}ju0ioO{X zjInJXUKUuxqPrtI@iKOp6K%?Uhh?}8p=}+s7TgSXb`xjx;g)b3uyoLWv=Uxg>aMqx z;>2g`bg=T;uEso|_r~3bOQ@v}zu+p2RmEnyO16t{1XV8{E@mTWEj$h`T1v$oj&rd@=kN>$)vxnt3JZcYUcZY`Pl zNZcuUD)}`lO?mdPjr#yv4X4C%gRLFg`FaM7r?*yHbhG)FSm`L7JM`98fKxj4n1SO< z3Ntx}LZ+_ayr-OIb6g^5%gVki7vAIqhpjc435(1>_PKA;YHyql)T714YH1sTkELjw z(}tXcAEkcVPN`Bp-JnUf655v-%bQYJ(s8aE#<|w&fffYQxuPpair)OA;Ne&n zK|nrsY8hAEkkxaBy8Li<|rHtJZL;5JeF?iCTb+6(l1 zfP2Ohux#A>(PQPqDwn6FYiDwCiBsyHCByxXdy0I~ARF(B=ijDM2sDdtQbE?S4uic9 zo9#fOiDvSHzHm=Dg<~n7LJQe1&p6Tt*ae=Pl7IB>*An^COCexTe#JTSMS+zGOlacyJ;=KZ%D-?ZJ8jZCLu$e^rLICsrBQ9!SaqDDt8R!^a1C zxVnT)Kkm}DuV7H3{+<|VFx~HcvI;vr|48uk-6E5$67{KTDaOA&Bh;lF_zCDy#YU4v z+f#Eoa*Yxt&(WBqV$-hw)VU;M-s&qv1r-{(A5)FhE5yg+Ei2EBQ&N=%vY>rO9|lZv zwZaQ1s_mqeL$|jE%tV)(t-={Yt6FX!Op}Fmi5~Nfe56;>|3|OD!yq}NAPKq(v$~La z+!YbwsubFZWkx7fyC_8_lPZQPrbxo2aE^{FzfFy69?r;ks*qc=H?CbmZ1V)0%s|Q& zmhx&d{j-xF;9~EB`l+A0P}$T<*#oQ%Z6lWr1V?TsqYPH{=vbdiJ%{8kLM)y@t#X*b zKhplK73r~xqtTpRkCvw!=mVqD!#HLJ8y8m7WcUjVOgmS^UDE+86s>!pG~=@!T9qsN zXtW-%YjdARu7TS9I=T9PSD; zalVBrG7je)(@4LW0sQLLx%YA^IwCw0g#w{8N3`mw7D;dIoh;MT>L2aMen{qBQj;b> zy%X;Cec&AToRp5dvMSE5y!z;?Vz<)e7CPrtKV#|C$U$~cTX@)9aMfL?j6*kmj@dXc z6AaG*4Sn@OuG$R$h!jBXQIqQZa)kXt~mIlh7$(DIq{frTt~#thAz?bwv6I51REmc zM?RKhj`e75PxGhR$};sOPR`8jton+%IDZ`z&MJGG=<^yWLmAG`bR=y36n!40t8hcS zYi`2de1HQNG?OrKG#*FNESb|K*l6|rK49Fhdy=RAY3`d=%dC1bI(xmDJY5q;s>*vE z7R>4cY)0FYt%Cg>HA%?(6}5+<)P1Xk=Qh2*tFT-c?1q%J}PpNj-2Yc}Cwv?B)O``^u z{u#)PymHU5y}n5-uoT)a3y*KSnKl-A`S?S>HP1V0zU`k%`N+sclicBH{JxB zTbW@Xm|q(Oyf$y%Fne;_#={qTgk&&HsS>Z6c^Ta=$-sg>{=VLC!!gcp88$zHZ`+7*7R}bC`)A-w6^nYk7miM}i#7t%FIy6*zvMkch#l@y~ z&hWt7KVy{by1o=_Op&=Br}CDkOznqVu58Ym@NJX&i4*hNZKc*$&Gm2LxT88pKOSEj zC2$oSn6Z=NsPW?`D`OcRHKlUn)d_wX4d6QS@4JI}@CPSRL+lj)krqWnSSI0F7z$~I z0AI9pG){mO#u0|3+%Ngw2l|oq&$%w$8|q2`&jbS22DZb%SYOs4{-8P%?H}j-Za!Pt zfUCteoTkn-`_jn6l{N};%#ue&T?skG`Q`qqag$@6B?ifS{d)!5m#4};9wqC$i#;&Y zK=Wn@9k1iVPp^qU226PMnShEyrn#oQvxDX*{PU>tfn2Y&fi~_=3oNhvGR`1YX7*C^ zG$w22CA zQ998+oy(@Qwn7=iZzOOjVq;48yUZo*h3owJg;`E}9_R0J`&%7i%!$KiW+YHly%tzM zT3W~Z`n#egPsu>6hw|xjo+R{RJ_>TR$fBsw#`>Ox){y>8#ifs~PeN>krqF`x@_^D~LxP#H zz>9&+2-oy;$vNSnlugNp6kBopVZn-1i}5n-)U((cmGPB8VaF|M`!VsDvnc*e`=%4+ zKr9abaeh$Rgk7hf@JXS9+Cz)*VUx4+t`rs3;o>wy6*JAYM1xCnpRESbcbdt)jVuz0{a^5Ik~ zBhZ#tx5Sv7;_Y93cp(+QO_oe4ws28cw=7Jyg#wyj4V>8Wj_xZ|wRu?lz*n z$$Xk;OGxtyxRe}k+`zHEHo#4pl9qGso^|b`VVMk+X)|gxYEsy&elaxel%3cNYOKe4 z)nM-#ky}NjU8IEb@gGKgms3D70y5aAu(`jdpKMipy%}vEDSRT5a7}X|)_(HJ?}8)B zYn)FbJrsInK9|~Wi`)9lNm^*|gi|k6e0Ps!@{?&w?;E?yd>~zkwx2~Qp{>{zUqbs9 z+JLa>)A*J>?+g?1r60oS_Bjkun+Dv2oyPu5|2P@oV~~WpA5mfv0O*OOqD0LO4v0{> z=C?ulrSd~{}^LvkT9r39K zc54@}%Z&_*4;Koq&d#A+UtVUt8P1vf3a=H7CiN!%2_Hhs1d3t1$F3EVk)>D&S^rDZ$dF z^6>pL(~Eu>U5kam#bR}5{OiLyQx&S#^eLY++HSM8*aH5tp-DBhaZ_%dZ=~VZhwZZ% z0p(Hhd5`_o=L=Xj#IWMmREj+;UY=(636>=NpL@xGx(8`Ok0T^_8$X`WHY_Tee!W!8 zrby>ZsIx->eb(J#xbGBtmQzWrC!1}hsZM7@QcrxjC9JJMU3)Y_kB{rW^llMHU3nM? z(Fx(E@`}vuvB@o`QgWNh9$R+jC<(mf)hc=Me^z|+4vaFQg(G4!W!Sf&)33iMu2p&m zkgZx(WE}5v@o5MC_-pPZj5jevu}n+W{I_YGW|0#I8R7Y}`YB0dYQfe}8fTZL`dTz; zC*nWwkT{6ru{O5WmqsI0oP8L3R%GQ-h%YuH2xgrlOUIg!c z3Wl;DVL~a)qP9!mQyY}v&#|bOcH3!jzn6S}B zlfTQPb0Sxbl=$wOSNl1D^|j|Z4y8)h%r*T%{J|SA$c`HK&!aEDCQ>!OGH%M836Cn$ zSnSiaS(Lo|=gy(vDSs^0S7hhtSex6wNWE9TbEaX<`)lWs;1yc@Dj~qDvD8=QZ=GIQXGA1GuBD|qhA<9e9@7)BdDsE%(BR#f5%ZM0M zctH7#HAv~!C_*WFR%3e(0Kv`np#1PXz|iX3pJNO2^qDu*UZC6lnoxTWzFtFdP~JXi z2f27~Eueatcm`MR2bJUs=*%0Tk9dV(Lk1w6Kp4(9n7;^qEBs?~5du9r0O7wpQ@UiP zd=QV!408y;vjAAfg?7)dj{2HvP=7%Q6xnT+A`nd}BNF6~o6}z9N9viv?@Nc_g#!-B6QZa``dZv)7)M?Ld`8fZ49ufyM8q~svRmasg;715rUBeT zVd~kk|3!gz4*-&5^TCD1F~H0esh=~qZo($J$d-}+)up>$p7NybOWWsylD8gh3qM2C zLmY69>TfBKVf?L2X)-`!5Bg}hCEW?r9DCuN?7X(cKYNZkRSclf7!0ohADmk{w=Tx^ z(<*wQ-NnQV8!4PaX{pQBesNwu@ZCsrA8QWgo^ePk3DPs}lbb^2acG``M094ue}k6! zb9wK{5JOoVyZt+tR_8ByesRa$tz5a%RnYJNL`aZ(soZ=c9K^2wc+Mz9mIicW%-@Qd zRab2nzoNbg2y$xpNhvug7MxVROu+(OOv^i`_yEJ+=Rey~4$9ut1J>g}y|m>j=vT%T za{;a6wWLs19-80ub4+VAVfv3A+Q_7}`W{u@G%XA2|U5Yt*A{TKv>GAhhgIV_W^ zGMQX%ER-#ZXBRTnRo3@cpLd!9yIHK4>!W6@(e|Vpc~Opn*WS_vm_hxK zUVzSWU^(Cc_jbM`6Stm>{INSr@-tpL-6T<>;h3Qf;`_KZpq{71(F^r%Te%4nNzDP? zdQv-G(wLykk}r`MW$Tt;k~KR6=etCG~OA)l}ELR?**ep#AWPE;d8x+DwW1M70W%Zhdl|isD^ff=8Il- zOM}KW`OC}g8q<~=y~K+ffwLQkrS}vXeogSK8CU(6!l8PJb>55xJ*~KOb!S{Nb_EDA z-{FghLL?yY5Kcj~PlG~Y9m07;`&qf^SB}-+sfC22v19zk_F^~^;=zJZrl3uvJ4E0+ z0v)J#$cKfcww+v7Z9UzNJQ)xVPWhodmzl9ork~x0(>n>)2ZHjG`C-H_Omjo9VB8c2 zFB?QYf#j{c0Jvg_w15X~)q0&4w^zr`_S#os6TVhwwqoE{j+{u*90N;QwhkAg&+4Y8 z+eNZnlTU%)JUwWfeJvaOw_SzRng1jhEq(T8tn8j$Iy&9Fg&_6JXw2T`Q2fSI4*+Ii zMne#BeMb*?u*XO_rZ;-$8pwL}RlN{V5u+!U-xIoPXLVZMctyiuky7kjvS-8yo*?Su zE=NkD7&O0n#A##)oDUoJ2~a3of>}_Eq+JpN0oF273 z9{KHR^$5Kjod9^d96#O!$eLzFU(eWX!`?w51CHn;M_wE57_e4606zL9G~zT_0nHiE zkCyoPZsfA)5$2TFs!RkjvywHNA%F>oR5vLDSN@Kd>n2j1}#&-~xvS`rrQL(m2Kt zyNd>`(of((dgNz=)?ANjB#{q&8JD)v*-PKPKq2kf;);2+hhp2*BNJ%=8*_m{Zq5sBrshLHh8r2JqN+Tp)7_In$71%S7!7JfT>tqI6|5d8RU(H zm}SDh=LVjm*);p(9X%+$x>N&8{;L0=>cVz{31*BAOHE`(Hgsd5hAc!2T>%*1lzA~2 zquyNSaK9*%0BZ>d^#GEX^f7T&`-k|=$8RbW8!T!R3=$O}LVU_pZbm4NX(WUd)yW+BYWw)L8r3%E8K&SB3ioez`XAh^kYGdnoqf;kiRsZY3G zB2A*tHc73=mSa7EH5O{h#(OKlM2&BCNVxZc+qt8xWD{%Wr)jhWVL<^A%>Aa>i!cS` zRgw;PxI-KR%DJ0U_2S~}Q*FFN2|byqO^714fYfa2_xKVUH*l7uB zUNJ04Q__|lnZFGbW_b4CXR{?_mL#!18bph8G;{Jyf2`v)mt3?iWx#4^mjK+qATL@> z_x=qxrbtdJ=?nd_#ZqF#Q?|xrCh<6+~%;+*EJpOAndNPfoMb;;tir4u?9%SYv^!QY9u;3y|uhg ze07{ne``xvM`DD3f`z#viYz0&>}fYGXd^p}G*BR>Ez#aty%#{eVOE!$S{zBe;GA z`i&~oB^ecVn1BGlTx=x}+?+%-1^nO#bfiL1PjJu2j(SFa(|bBvZYj1O)3GtgHj<*J z!2Q6HP&4UTba{Zp!8!Ty4BeyGFZvN+fnm2oNC+h^{lV#n%e-8c+;;C^`8HGqiW$if zAL6(-wb}cPT{fI+NrvhpZ8XXnhjFfO*gbQ_=PN~zM0dh~Ny*`;75F6G|6VBhg5OGn z!%DLrbckm51c3&$LO;Srn&m9z8TI6h!a&(%G2skTFH?ZLS%#hnPs!U-ux`gL0Nb3} zQojKwOU*Gy#@IyGcqOowe-wlfeHyggklqxvkNu;{yQj)K^hfMvJ*9Mir-? z0y@kqZevO5@wIH-ogBQDdaEvkb-#lW9+%K?$BI24glAA9BeF6qI|{dHnDIT3;8zKieW5?;SkeQpAOwe5bS*#W|@HWtGDwz-OlHw8v>AjTsK=84L#j$AnSrE`I+F8WO zmm=qq-{xRE-Jv?bJ`T+U1gu=bB$T3s(IGwjnRu|(|K{2H@mx_rxhptFfWPUfQzom9 zzM1P4gfTwBKmB^ji~!!+kDSxj7Xs(8SA1N5d4MRRdX7Y0giu_M({KF&Mk7L6L6!)m zrE+p-oIZ&it^s1E-$(sfcPR{6)qNDY%h`Ls4?GI%a&y5B;se%GA`wf82}CJ^A7EHq zbhJ=}pDAJ8qXi{&>C8i#w%tLp;YJh80C&FA6rSiyt`84F&`#BQ6H2fzARKs*VlP*S zygwR#5)v46BaehUlyG1XJe03oJNkSnn_S*BeUh~i)E^WR#1Ht@i1^yd-Lx0q&3Ncb z94sRCisoVAD4RS)I;>x6gc+g(a}Z0&kG%{;P?@A?X4)N?y>z=+Izz8y-p1Cb}lF zK?==;kx(3=_BLiH_K_&U-o|NRG!iV?zy4w9xciT)te>WapFy+p$c!CSR3w#5kFp+5 zgs}ce5!v%;UK;`0&Mlxb$Pg$DNDq7BZM+inNo~*zBLMB|PeIokhF9EXgotp=W}yXq zfgc03JI($%Pd*M|c5b_EXyTkwM>_bTACxZssY+t%q-MK|i`pbwdJV(d-O76_jbCyJ z_(htfMqO{XGT8eOHPC5ZzT*$JnCgtrY1jN&*#b|vP+UJM!5|tmO6?UlU)-NGc+ecw;0(eB=U4>XvqhTY!)i`dJ;AGp)9{WJq{EjF z&@1&5WtpLzQxUCCpiqV$p!9~uZ-bww8Q!!u_$NnM-KkZ;_Z1#b;f3LN2#?+MPAvA* z_9~vEh^V3`(}K|srr+kq5iIa%NnmocJ-`LLMVY5s>*T%fU|b(xPAJnF4wMjT#=pgc zW?vT&)$+~wAkV#SFA!?(Ed30}T0uQgTm2abf*@ItzzbmYph3OHYo&aJ%P1d=cRO`% zmr}11o2A_%*0K;LLB5j4H0IH-aEX?L6)-5g%Oo%&(GRc-fTAwW0u8-lCr1T7RN$AT z^}>u)WQF}Qy|xGyOr2j;&FpUUJdel^<)Y1+f1HzzBhv4o`pc*Sne9A#0H;wKQ`|I`Y6XCsfOS!EjHcw6&IU% z!MvBnm+i^N{ik6Sd{45l(Y&B^o6%~$r2_hDNG`041a+ettOkM52RT}P%I80I5ja7K zO)D!=B2HHo2{@v}^0~3X{*)RLPct4HVLj>xv%Vv|ZGoF+)DJZWx<1h#en308n^H{D z1qomP?*m(?124nO%0G&w+O^Fa(Bajfa*^Q^kIYze^eBSS{}-;AXo7dEo`zLYnPH=e zBLbOtFnd|&8CHzxmjV5>B}FGjTrU|*M+!fjhP+roYb%CeAYtmYp;~){?=RN7WV>Xu`Isx{k>rcdwkLCunNsZrVW5^ez;qKjdh_$KU$sxTcEh|+7nZF-3%)C^ zIXY^Z(H0{gBpSCchBM=wWRVXoa(U5-%13w_E7uWvB)M zoF@_zD2XQd3CmnMU8aoOh6n5R!2l9ikF}O--L$(PL4ByaxKiW1qg;9QFC0X|k7&r$ z%CYbkxO2*4gYLejh+T@KERXj0y$|wCqq_hE}f~SHD%5g-3 zfJfNpaE4c~`~7Mat#}T_t-v+g5QUvr!o}R0d_h0g23UWNexMX`tA1#!y}-`e(lN?S za&bx_%aMoanKe{6UH0jU_=)iaSn_vD4jeZvwTZx|kz4e2OH2~4y*dSo?@4x!u_2)j3pU|YHCQdw)+0h?7Mq!Lg#`U|%o;`8Xp zU1|jpP^TQaBHsnAo|ttWcDhLb;YT8O{aEWCQF%uQI>=nRb?~2-vc}!Nxo-_zjlk-4 z1C>^00+lR~#3O2a2cQEHAeuYF8-|~QCWw~90W!iX=?-F)(0g#{Fh69HJp&$~U#dp1 zH$PvZll8+*4sLJ(V6()a{dtj33p`GFh(wz8;p1OTq*0%uwWk7xt1g0w0qj?iM)+A5 zSe_LWD<^FgF~nuM&X+4nBDa!aL-0GcplLj*iD%Q{=xu0zdau?TEP^`P4+0R}pousr z!WoZxzEm5zQWLvHu;CNb-5w3Xa1~mpgDaq2d!YqjLs@=EBYqg7eU1TSHR(vfw=B#? z=~v64!hlO?e`4qY;8qP?0RQt##XEmTqJK*$B#X#2l3{mrgxnJ^uA(`oqUxT zmuP)=q(C;9dUV;J12CI0D(Ey`-s|8qmxNUGe^+5~E>kCPC3$*(XxspRKL-qN%m#;$ zA~g$Zf+7&MLH&{qQaEPPOXy)hlDb2uO9qT{%p@x&OC{u^AV;O2U`F1N?jhF%LPbY@ z1l240=xO=HTTozO0t%^~S{0kfQYjnWZ!DwR{FYbP?Jj*1jhawUNdK61rYe zS#A^z5;ZW8pK1#;gAegrt({IIIb)Y=Mpr4o89>A=zmf+ZiTD&Y-xeedTt+L46KC(x z=m^Km29&J5K=S4o#c#N1dKar5n`R~siL4`Cq=SHa70qFf@Hw18%+%3GP5|sL)ZjoI z4)j}`=EoxQ<;|j}dnq$(!7;EeG}y zY~)ofJK}UQQJbiq4p{=)P%?ucpCcq&bs7BV!7EpRm=(l`(>qe6deGJdd5~EBOCTy& zfCurAz|P^t$CFI;_`wl?H;M;WfDeUi-N*nu+(R=QutOT&(eAj5LrH+KR6&dWrZ>Zu zFalJmX3UAdUi+xhENu6E+s9NDWDdfpd~ z1$@0EmcxeGrrmwhwk>k&>562s16sn2UstO}X-=^nvE{@HFg-3kFt7Xq)nootIefdl zveK6-8V|c`Br=07fEGv!t|(}Ka??q%JU3zHGSZiNVV}dS=$b$G>jZVEgI>d6_S|4!VPN@S`Y|b3a!_Ro&I?S zTr}1rpZ?}Mw9sAivtK#h2#gA78T2wF?AIP*I@FSjmmn&&5U$-tFkpl;MSc$@VSROXM(CA2oN+fTEDqPAkhtPhgG%k8lhnTrZ3z{ki%D7rj7 zKAuP`sDHiHux++>036L-&?C9iLkw-MP({TDrsYCmH^flZSZ-6^;{m1T1(E4(;v4De zMCI+x2l6#)OFS{#h3ydoYqi7nXFXs1#tB=oUmIEGFsn9DlKcxzhl`mbEzc}OP;nKH zpbCzt4_*L#`XyY}7aS<4e36$2N0OnDu`uT-42%oKh4K@IFA!>VBx35Kb9ZalV(ubK zrd}d!dAE-+c2-;+%RUy>YYlw`KTUfaPKf@Et%8{;Qpg2bsTWG&*Z`?N zo8tEUfN+4{N8cA_*%KY?|3da<`C{ew9#F#upn~njWPauB)Q=xw=EAf<|7qbGm9#0j4dA zu(mo7SeP?;a@@jJvmA6pQ{9G62ZZG>1WG&M7*X2Np?jg(1LJ@3Anf6?pou3yDj9uW zg0-94<|Jrv_Q5ZB5n@W|!Z1jw++O=>=<0g7zf1Dxq&d19!l?ekhM(1A9RZ4{5GY$L z8bu4{lSw(D3lgR53uQ(MC*k8yNwq`czn^Jdq?0*cFn|y}aJq-|(`EQ~BSIL!! zL!F0l6UH@;7-wcljWm zhp-D8TgKQ1GU?C-Yc9ToSc@}^rPS)*746Q)Ou##bX3&eFVc2OYbYJ$RkQ#wCWpkMe z8MLNBwU!h2`@>5k!jk5j9bb;tT5XZMf9=kt`!tYV>1In6rO}6VLZ(Vk_z|>3n8-O1 z&0u3ye1DaN1nRk~K-N2cRla!Uiynan1&j;nAEtO6an~VVe3?K=6?m^<&g9nNa8839 ziDiqI^G+qTN?jV4S^M~Ek-t)R{L-95pkuURokYzsdfN!9M34rSk;S;!d$KrI2Do3% z2*Nq8?Eyc-SDiSKdA}g+ z2}hJ!Bb!qyk3K-v1>A>5da2{2ldJ6b)N!=kp`0luonx&>kS&MrMR@sTVc^xOJuhRdX#uyM?qos=eB3`R2D= z+o0vyjHdyVwWLah-HN8Yym}BNNz!zCyu@S;EDD#eIM3lra4GXY{x~);RdBpu*6atQ z4x;1tCRgW1*u;T5kgAS;+uPj9js|W^#EbGGV>EI?mOtetyn`}>>pYXxwyVSbxXy+p zIt;D2jFWIa6; zAUzKuRjZ}`)y(_~HgRI+i$)Zq3EWuGfgDiS}$6*X){=Qao%P z)JOLCy2HNIi2tm!_P42PYl?};bi5Yn#Ox@(V$E&wx zGrgn{8g=)#ly;jYSXJ;;zdP71DrR(Cjk7ZAf7&zIk=8KP_Aj_(0UiX)pIa6pW#3^= zwwGNd?sv-HQHjjnULgryZ!e&yys2aRbnvAA>*Q;9&m-Pa8%GA0(#il5kl>dJ8i$m4 z2yu)X!bV1v8BiY&=v&x?67;NoDM2gHPr}*XW)Pgd#)la+3wrVBtyqRTkz1~{vA5Wz z4nA%6=@3ym)@__vC#0b(NM=7uh6#=N<2~t5H`Tmb$rYSm zJgCeGnx5;W>^X9pX>dVEL8e~;>Dh+u*oUS1|L%w z_K_~)%}|;o8M~LniNR!SWhV`a<7!z4j9^=0Fo3n!2Wzz!*BQGl!q`dQMTvhIgolDz zH~6ta^wj;gk z&k__IT)CXt+sKp0@TdI5*W+glw3d#1*Rw*M0f$rDp-#<)1oXU`T}~#`hp&82M@ES{ z9N#m(i)s!@S^b_i&5~o=NpH7U@6!a_$ey{Sg3R!GZFYX6&l8NSeRYZ1H{A?EcgaC0 zad?!Hz4Jrp3MrFqcj&)<<=K%Lp-6~C(~H>KN66-itO{iHC`x%aE|>@g@c`d3QyUY8 Hk!Q@GFp_r^ literal 0 HcmV?d00001 diff --git a/docs/source/manual/fpga_verilog/file_organization.rst b/docs/source/manual/fpga_verilog/file_organization.rst deleted file mode 100644 index 181be2295..000000000 --- a/docs/source/manual/fpga_verilog/file_organization.rst +++ /dev/null @@ -1,20 +0,0 @@ -Hierarchy of Verilog Output Files -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -All the generated Verilog Netlists are located in the /SRC as you specify in the command-line options. Under the /SRC, FPGA-Verilog creates the top file name_top.v and some folders: lb (logic blocks), routing and sub_modules. - -.. csv-table:: Folder hierarchy of FPGA-Verilog - :header: "File/Folder", "Content" - :widths: 10, 20 - - "name_top.v", "Contains the top module and calls all the other .v files" - "name.bitstream", "Only if --fpga_verilog_print_top_testbench or --fpga_verilog_print_top_auto_testbench is chosen. Contains the bitstream programming the generated FPGA." - "name_top_tb.v", "Only if --fpga_verilog_print_top_testbench. Contains a testbench used for the simulation." - "name_autocheck_top_tb.v", "Only if --fpga_verilog_print_autocheck_top_testbench is chosen. Contains a testbench used for the simulation." - "name_formal_random_top_tb.v", "Only if --fpga_verilog_print_formal_verification_top_netlist is chosen. Contains a testbench used for the simulation." - "name_top_formal_verification.v", "Only if --fpga_verilog_print_formal_verification_top_netlist is chosen. Contains a top fil used for formal verification and by name_formal_random_top_tb.v." - "fpga_defines.v", "Contains all the defines set as 'include_timing'" - "name_include_netlists.v", "Contains all the netlists and defines paths used for the simulation." - "lb", "Logic Block. Contains all the CLBs. The logic_block.v includes all the CLB and is called by the top module afterward." - "routing", "Contains all the routing in the circuit. You can find in it the Switch Boxes, the Connection Blocks and the routing needed to connect the different blocks. The routing.v file packs them all and is called by the top module." - "sub_modules", "Contains the modules generated by the flow to build the CLBs." diff --git a/docs/source/manual/fpga_verilog/index.rst b/docs/source/manual/fpga_verilog/index.rst index 85d278ae6..2940943c4 100644 --- a/docs/source/manual/fpga_verilog/index.rst +++ b/docs/source/manual/fpga_verilog/index.rst @@ -7,7 +7,7 @@ FPGA-Verilog .. toctree:: :maxdepth: 2 - file_organization + fabric_netlist func_verify diff --git a/docs/source/manual/openfpga_shell/openfpga_commands.rst b/docs/source/manual/openfpga_shell/openfpga_commands.rst index 7f72414d0..1ac4ed69b 100644 --- a/docs/source/manual/openfpga_shell/openfpga_commands.rst +++ b/docs/source/manual/openfpga_shell/openfpga_commands.rst @@ -140,6 +140,8 @@ FPGA-Bitstream - ``--file`` or ``-f`` Output the fabric bitstream to an plain text file (only 0 or 1) - ``--verbose`` Show verbose log + +.. _openfpga_verilog_commands: FPGA-Verilog ~~~~~~~~~~~~ From dcce782a468358da21045792462c61955311c3da Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 24 May 2020 18:01:34 -0600 Subject: [PATCH 553/645] update documentation about Verilog testbenches --- .../manual/fpga_verilog/fabric_netlist.rst | 4 +- .../fpga_verilog/figures/preconfig_module.png | Bin 0 -> 26664 bytes .../figures/verilog_testbench_hierarchy.png | Bin 0 -> 32203 bytes .../verilog_testbench_organization.png | Bin 0 -> 290654 bytes .../manual/fpga_verilog/func_verify.rst | 12 --- docs/source/manual/fpga_verilog/index.rst | 2 +- docs/source/manual/fpga_verilog/testbench.rst | 92 ++++++++++++++++++ 7 files changed, 95 insertions(+), 15 deletions(-) create mode 100644 docs/source/manual/fpga_verilog/figures/preconfig_module.png create mode 100644 docs/source/manual/fpga_verilog/figures/verilog_testbench_hierarchy.png create mode 100644 docs/source/manual/fpga_verilog/figures/verilog_testbench_organization.png delete mode 100644 docs/source/manual/fpga_verilog/func_verify.rst create mode 100644 docs/source/manual/fpga_verilog/testbench.rst diff --git a/docs/source/manual/fpga_verilog/fabric_netlist.rst b/docs/source/manual/fpga_verilog/fabric_netlist.rst index 7ce403300..46c8a934b 100644 --- a/docs/source/manual/fpga_verilog/fabric_netlist.rst +++ b/docs/source/manual/fpga_verilog/fabric_netlist.rst @@ -3,7 +3,7 @@ Fabric Netlists --------------- -In this paper, we will introduce the hierarchy, dependency and functionality of each Verilog netlist, which are generated to model the FPGA fabric. +In this part, we will introduce the hierarchy, dependency and functionality of each Verilog netlist, which are generated to model the FPGA fabric. .. note:: These netlists are automatically generated by the OpenFPGA command ``write_fabric_verilog``. See :ref:`openfpga_verilog_commands` for its detailed usage. @@ -33,7 +33,7 @@ Top-level Netlists This file is created to simplify the netlist addition for HDL simulator and backend tools. This is the only file you need to add to a simulator or backend project. - .. note:: User-defined (external) Verilog netlists will be included in this file. + .. note:: User-defined (external) Verilog netlists are included in this file. .. option:: fpga_top.v diff --git a/docs/source/manual/fpga_verilog/figures/preconfig_module.png b/docs/source/manual/fpga_verilog/figures/preconfig_module.png new file mode 100644 index 0000000000000000000000000000000000000000..33b3ff6c5b6afe8a54ddd915b0a3a71f73c4c454 GIT binary patch literal 26664 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 literal 0 HcmV?d00001 diff --git a/docs/source/manual/fpga_verilog/figures/verilog_testbench_hierarchy.png b/docs/source/manual/fpga_verilog/figures/verilog_testbench_hierarchy.png new file mode 100644 index 0000000000000000000000000000000000000000..042fb8eaf9212cf82e1f391990108b6b23cb045f GIT binary patch literal 32203 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 z%0A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

i;E?*mXe|B-7(79gy)>zLtN6BSneq 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 Q9q><1T3M<@!YJT>0Wham)Bpeg literal 0 HcmV?d00001 diff --git a/docs/source/manual/fpga_verilog/figures/verilog_testbench_organization.png b/docs/source/manual/fpga_verilog/figures/verilog_testbench_organization.png new file mode 100644 index 0000000000000000000000000000000000000000..06f6d58fa5f4d7ab37289cb91448092464ec2102 GIT binary patch literal 290654 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

`})bP1+w#SIC%HU&g@-*z!u(Eri^gelzgge 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 j6~jZCOrkEfVPS({5%EAf{gH%ddl-7&W11k%9k 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�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

@aNoW@T&Jdq>cpzE8fDN=8DId@hYOc)g`@ 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 z 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

v(0U&o?1}({jF{6#m!YQ{?}H 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 is the module name of users' RTL design. + +.. option:: _include_netlist.v + + This file includes all the related Verilog netlists that are used by the testbenches, including both full and formal oriented testbenches. + This file is created to simplify the netlist addition for HDL simulator. + This is the only file you need to add to a simulator. + + .. note:: Fabric Verilog netlists are included in this file. + +.. option:: define_simulation.v + + This file includes pre-processing flags required by the testbenches, to smooth HDL simulation. + It will include the folliwng pre-procesing flags: + + - ```define AUTOCHECK_SIMULATION`` When enabled, testbench will include self-testing features. The FPGA and user's RTL design (simulate using an HDL simulator) are driven by the same input stimuli, and any mismatch on their outputs will raise an error flag. + + .. note:: OpenFPGA always enable the self-testing feature. Users can disable it by commenting out the associated line in the ``define_simulation.v``. + + - ```define ENABLE_FORMAL_VERFICATION`` When enabled, the ``_include_netlist.v`` will include the pre-configured FPGA netlist for formal verification usage. This flag is added when ``--print_formal_verification_top_netlist`` option is enabled when calling the ``write_verilog_testbench`` command. + + - ```define ENABLE_FORMAL_SIMULATION`` When enabled, the ``_include_netlist.v`` will include the testbench netlist for formal-oriented simulation. This flag is added when ``--print_preconfig_top_testbench`` option is enabled when calling the ``write_verilog_testbench`` command. + + .. note:: To run full testbenches, both flags ``ENABLE_FORMAL_VERIFICATION`` and ``ENABLE_FORMAL_SIMULATION`` must be disabled! + +.. option:: _autocheck_top_tb.v + + This is the netlist for full testbench. + +.. option:: _formal_random_top_tb.v + + This is the netlist for formal-oriented testbench. + +.. option:: _top_formal_verification.v + + This netlist includes a Verilog module of a pre-configured FPGA fabric, which is a wrapper on top of the ``fpga_top.v`` netlist. + The wrapper module has the same port map as the top-level module of user's RTL design, which be directly def to formal verification tools to validate FPGA's functional equivalence. + :numref:`fig_preconfig_module` illustrates the organization of a pre-configured module, which consists of a FPGA fabric (see :ref:`fabric_netlists`) and a hard-coded bitstream. + Only used I/Os of FPGA fabric will appear in the port list of the pre-configured module. + +.. _fig_preconfig_module: + +.. figure:: ./figures/preconfig_module.png + :scale: 100% + + Internal structure of a pre-configured FPGA module + From f079c61bd3f7111d2870464643a320f31115ea20 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 24 May 2020 18:09:48 -0600 Subject: [PATCH 554/645] re organize tutorials --- .../manual/arch_lang/circuit_library.rst | 2 +- .../figures/Verification_step.pdf | 1300 ----------------- .../figures/verification_step.png | Bin 183723 -> 0 bytes docs/source/manual/fpga_verilog/index.rst | 4 - .../design_flow}/figures/Layout_Diagram.png | Bin .../figures/fpga_asap_10x10_final.png | Bin .../figures/fpga_asap_10x10_floorplan.png | Bin docs/source/tutorials/design_flow/index.rst | 10 + .../design_flow}/sc_flow.rst | 0 docs/source/tutorials/index.rst | 2 + 10 files changed, 13 insertions(+), 1305 deletions(-) delete mode 100644 docs/source/manual/fpga_verilog/figures/Verification_step.pdf delete mode 100644 docs/source/manual/fpga_verilog/figures/verification_step.png rename docs/source/{manual/fpga_verilog => tutorials/design_flow}/figures/Layout_Diagram.png (100%) rename docs/source/{manual/fpga_verilog => tutorials/design_flow}/figures/fpga_asap_10x10_final.png (100%) rename docs/source/{manual/fpga_verilog => tutorials/design_flow}/figures/fpga_asap_10x10_floorplan.png (100%) create mode 100644 docs/source/tutorials/design_flow/index.rst rename docs/source/{manual/fpga_verilog => tutorials/design_flow}/sc_flow.rst (100%) diff --git a/docs/source/manual/arch_lang/circuit_library.rst b/docs/source/manual/arch_lang/circuit_library.rst index cde9b5fbc..e483728ed 100644 --- a/docs/source/manual/arch_lang/circuit_library.rst +++ b/docs/source/manual/arch_lang/circuit_library.rst @@ -339,7 +339,7 @@ These inputs are widely seen in FPGAs, such as clock ports, which are shared bet The global inouts are short wired across different instances. -:numref:`fig_global_ioput_ports` shows an example on how the global inouts are wired inside FPGA fabric. +:numref:`fig_global_inout_ports` shows an example on how the global inouts are wired inside FPGA fabric. .. _fig_global_inout_ports: diff --git a/docs/source/manual/fpga_verilog/figures/Verification_step.pdf b/docs/source/manual/fpga_verilog/figures/Verification_step.pdf deleted file mode 100644 index 3c3d3b655..000000000 --- a/docs/source/manual/fpga_verilog/figures/Verification_step.pdf +++ /dev/null @@ -1,1300 +0,0 @@ -%PDF-1.5 %âãÏÓ -1 0 obj <>/OCGs[7 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream - - - - - application/pdf - - - Verification_step - - - Adobe Illustrator CC 22.1 (Macintosh) - 2018-12-18T14:15:37+01:00 - 2018-12-18T14:15:37+01:00 - 2018-12-18T14:15:37+01:00 - - - - 256 - 148 - JPEG - /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgAlAEAAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q888xee/zGtfOFzon l/yZ+mNOtY4ZJNUkuxaITLFJIY1DxsGblGFqpIBI5ca1xVLvL35r+edS07WobzyXLYeatPkjjsND kll9O5DKrSuL1oFt6IOR+Fj0p3WqqVRfmD/zkSLDT1m/LqP6ykkI1G4W9gIkjCIZjHBzqhJZqVc+ HY4qlXnofl2fOmsnzHqdvZ3/AK6cYZtSazb0/Qj4n01miFK1+Kmcx2idR4x8MEx8o3+ho7V1+uxH HHDLJGHhj6bq+KXcknH8lf8Aq/WX/cbk/wCynMHi1n82X+k/Y6r+V+1f9Uzf7J3H8lf+r9Zf9xuT /spx4tZ/Nl/pP2L/ACv2r/qmb/ZLin5MempOuWXAkhW/TUm5AFRy+s7022x4tZ/Nl/pP2L/K/av+ qZv9kt4/kr/1frL/ALjcn/ZTjxaz+bL/AEn7F/lftX/VM3+ydx/JX/q/WX/cbk/7KceLWfzZf6T9 i/yv2r/qmb/ZO4/kr/1frL/uNyf9lOPFrP5sv9J+xf5X7V/1TN/sncfyV/6v1l/3G5P+ynHi1n82 X+k/Yv8AK/av+qZv9k7j+Sv/AFfrL/uNyf8AZTjxaz+bL/SfsX+V+1f9Uzf7JdKn5MCRxJrlkrgk Ov6akWhruOP1kU+WPFrP5sv9J+xf5X7V/wBUzf7Jbx/JX/q/WX/cbk/7KceLWfzZf6T9i/yv2r/q mb/ZO4/kr/1frL/uNyf9lOPFrP5sv9J+xf5X7V/1TN/sncfyV/6v1l/3G5P+ynHi1n82X+k/Yv8A K/av+qZv9k7j+Sv/AFfrL/uNyf8AZTjxaz+bL/SfsX+V+1f9Uzf7JcifkwVfjrlkQBVz+mpDQVG9 frO29BXHi1n82X+k/Yv8r9q/6pm/2S3j+Sv/AFfrL/uNyf8AZTjxaz+bL/SfsX+V+1f9Uzf7J3H8 lf8Aq/WX/cbk/wCynHi1n82X+k/Yv8r9q/6pm/2TuP5K/wDV+sv+43J/2U48Ws/my/0n7F/lftX/ AFTN/sncfyV/6v1l/wBxuT/spx4tZ/Nl/pP2L/K/av8Aqmb/AGTuP5K/9X6y/wC43J/2U48Ws/my /wBJ+xf5X7V/1TN/slzp+TAVOWuWQBFUP6akFRU71+s771FceLWfzZf6T9i/yv2r/qmb/ZLeP5K/ 9X6y/wC43J/2U48Ws/my/wBJ+xf5X7V/1TN/sncfyV/6v1l/3G5P+ynHi1n82X+k/Yv8r9q/6pm/ 2TuP5K/9X6y/7jcn/ZTjxaz+bL/SfsX+V+1f9Uzf7J3H8lf+r9Zf9xuT/spx4tZ/Nl/pP2L/ACv2 r/qmb/ZLok/JgyII9csmckBF/TUjVNdhx+smvyx4tZ/Nl/pP2L/K/av+qZv9kt4/kr/1frL/ALjc n/ZTjxaz+bL/AEn7F/lftX/VM3+yYF5/k8vR63oX+Gr9LmzOoW6u8F410pDV5rzMkld+NVrndeyW bPGNS4o8WWIP8Nipc+Tsseo1GbDE5zKUvX9X+b3/ABfXXm7SfMmp2EEHl/Wv0FdpP6k116CXPOL0 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 - - - - uuid:9E3E5C9A8C81DB118734DB58FDDE4BA7 - xmp.did:9f41edf3-0dec-4756-a203-f280702f55ca - uuid:f06d5b60-09aa-4341-86e7-81aa11cef064 - proof:pdf - - xmp.iid:3a5d0884-4bc5-4a38-9606-8aecf1459763 - xmp.did:3a5d0884-4bc5-4a38-9606-8aecf1459763 - uuid:9E3E5C9A8C81DB118734DB58FDDE4BA7 - proof:pdf - - - - - saved - xmp.iid:732293ad-e525-4cb9-91ac-e00c5cfb6e9d - 2018-12-17T23:00:02+01:00 - Adobe Illustrator CC 22.1 (Macintosh) - / - - - saved - xmp.iid:9f41edf3-0dec-4756-a203-f280702f55ca - 2018-12-18T14:15:35+01:00 - Adobe Illustrator CC 22.1 (Macintosh) - / - - - - Basic RGB - 1 - True - False - - 1024.000000 - 599.681067 - Points - - - - - TimesNewRomanPS-BoldMT - Times New Roman - Bold - Open Type - Version 5.01.3x - False - Times New Roman Bold.ttf - - - TimesNewRomanPS-BoldItalicMT - Times New Roman - Bold Italic - Open Type - Version 5.00.3x - False - Times New Roman Bold Italic.ttf - - - - - - Cyan - Magenta - Yellow - Black - - - - - - Groupe de nuances par défaut - 0 - - - - White - RGB - PROCESS - 255 - 255 - 255 - - - Black - RGB - PROCESS - 0 - 0 - 0 - - - RGB Red - RGB - PROCESS - 255 - 0 - 0 - - - RGB Yellow - RGB - PROCESS - 255 - 255 - 0 - - - RGB Green - RGB - PROCESS - 0 - 255 - 0 - - - RGB Cyan - RGB - PROCESS - 0 - 255 - 255 - - - RGB Blue - RGB - PROCESS - 0 - 0 - 255 - - - RGB Magenta - RGB - PROCESS - 255 - 0 - 255 - - - R=193 G=39 B=45 - RGB - PROCESS - 193 - 39 - 45 - - - R=237 G=28 B=36 - RGB - PROCESS - 237 - 28 - 36 - - - R=241 G=90 B=36 - RGB - PROCESS - 241 - 90 - 36 - - - R=247 G=147 B=30 - RGB - PROCESS - 247 - 147 - 30 - - - R=251 G=176 B=59 - RGB - PROCESS - 251 - 176 - 59 - - - R=252 G=238 B=33 - RGB - PROCESS - 252 - 238 - 33 - - - R=217 G=224 B=33 - RGB - PROCESS - 217 - 224 - 33 - - - R=140 G=198 B=63 - RGB - PROCESS - 140 - 198 - 63 - - - R=57 G=181 B=74 - RGB - PROCESS - 57 - 181 - 74 - - - R=0 G=146 B=69 - RGB - PROCESS - 0 - 146 - 69 - - - R=0 G=104 B=55 - RGB - PROCESS - 0 - 104 - 55 - - - R=34 G=181 B=115 - RGB - PROCESS - 34 - 181 - 115 - - - R=0 G=169 B=157 - RGB - PROCESS - 0 - 169 - 157 - - - R=41 G=171 B=226 - RGB - PROCESS - 41 - 171 - 226 - - - R=0 G=113 B=188 - RGB - PROCESS - 0 - 113 - 188 - - - R=46 G=49 B=146 - RGB - PROCESS - 46 - 49 - 146 - - - R=27 G=20 B=100 - RGB - PROCESS - 27 - 20 - 100 - - - R=102 G=45 B=145 - RGB - PROCESS - 102 - 45 - 145 - - - R=147 G=39 B=143 - RGB - PROCESS - 147 - 39 - 143 - - - R=158 G=0 B=93 - RGB - PROCESS - 158 - 0 - 93 - - - R=212 G=20 B=90 - RGB - PROCESS - 212 - 20 - 90 - - - R=237 G=30 B=121 - RGB - PROCESS - 237 - 30 - 121 - - - R=199 G=178 B=153 - RGB - PROCESS - 199 - 178 - 153 - - - R=153 G=134 B=117 - RGB - PROCESS - 153 - 134 - 117 - - - R=115 G=99 B=87 - RGB - PROCESS - 115 - 99 - 87 - - - R=83 G=71 B=65 - RGB - PROCESS - 83 - 71 - 65 - - - R=198 G=156 B=109 - RGB - PROCESS - 198 - 156 - 109 - - - R=166 G=124 B=82 - RGB - PROCESS - 166 - 124 - 82 - - - R=140 G=98 B=57 - RGB - PROCESS - 140 - 98 - 57 - - - R=117 G=76 B=36 - RGB - PROCESS - 117 - 76 - 36 - - - R=96 G=56 B=19 - RGB - PROCESS - 96 - 56 - 19 - - - R=66 G=33 B=11 - RGB - PROCESS - 66 - 33 - 11 - - - - - - Cold - 1 - - - - C=56 M=0 Y=20 K=0 - RGB - PROCESS - 101 - 200 - 208 - - - C=51 M=43 Y=0 K=0 - RGB - PROCESS - 131 - 139 - 197 - - - C=26 M=41 Y=0 K=0 - RGB - PROCESS - 186 - 155 - 201 - - - - - - Grays - 1 - - - - R=0 G=0 B=0 - RGB - PROCESS - 0 - 0 - 0 - - - R=26 G=26 B=26 - RGB - PROCESS - 26 - 26 - 26 - - - R=51 G=51 B=51 - RGB - PROCESS - 51 - 51 - 51 - - - R=77 G=77 B=77 - RGB - PROCESS - 77 - 77 - 77 - - - R=102 G=102 B=102 - RGB - PROCESS - 102 - 102 - 102 - - - R=128 G=128 B=128 - RGB - PROCESS - 128 - 128 - 128 - - - R=153 G=153 B=153 - RGB - PROCESS - 153 - 153 - 153 - - - R=179 G=179 B=179 - RGB - PROCESS - 179 - 179 - 179 - - - R=204 G=204 B=204 - RGB - PROCESS - 204 - 204 - 204 - - - R=230 G=230 B=230 - RGB - PROCESS - 230 - 230 - 230 - - - R=242 G=242 B=242 - RGB - PROCESS - 242 - 242 - 242 - - - - - - - Adobe PDF library 15.00 - - - - - - - - - - - - - - - - - - - - - - - - - endstream endobj 3 0 obj <> endobj 9 0 obj <>/Resources<>/ExtGState<>/Font<>/ProcSet[/PDF/Text/ImageC/ImageI]/Properties<>/XObject<>>>/Thumb 19 0 R/TrimBox[5.0 5.0 1029.0 604.681]/Type/Page>> endobj 10 0 obj <>stream -H‰ŒUMO1½ûWÌ19¬c¿¥ª>„Z !ÊÞŠ–dYRH6,©ú÷;k;a#B!9DòxÞ›yžy™\ÂäòTÀÉÙ)°6¹¸м2o—Úc,ú¢Ð<Ðw¤<§µBP^s/¼ÓFÂ|Å&?VÎZv} -­ËP†‹€Î«ˆä­ Æ[P=—ÆÙ $ÐIÉDÌëš=ä¤,H(˜ô1F?ÆrDJY*Žª)Wl:Útc鹄QÛÌæÏíüi|Wþd…äÒ; ‡å‚^çÕº˜?VËõl¹—¿r)qx£Ýd -‚J%ˆQÚ>ú9‘Rê¢Ý!Ñëap:Š4£úµÞ~@±¨¶U§B1Ãàæ©fíŸmŒªaä¾^ÏWU÷´ #×ò€»î"»¡&»ÙÃsÕ¤WZCA/i¾*†Ÿ‰‘o#ÿ/F8.F#G>cÇ}\Œó’I¼ŸJ*ì×Óð4\“B 7Bæ PxCZÅÕ¢õ€®f7ŒÒTDHNRHÇroyRrî0–©÷`Ó¶RŠ·ôlÊzn@íˆ+íÄ·¢€·ÏÕº†ÍNȶéªÕrÝÀ¦î–íbp­(¾Ç‰…Hu^dP‰@ùžÑ’?ô ‘ ‡m ¥ÚàzÒ’Dz‰® 4eC š eÐéì/èÐQ7h —.(Ô!Û&[1‹šR› î,È¿ “ÊÖÑ,XˆîÔ‡Vi`U"´ðLê]½J0¿˜xøÚÓT½™çaÏMOšÝ¥¦—Žt7@Ê¢q>û¢>(£W)Ù¨z×€qŠò×/&¾¸ƒÌΗsý(åôÝÜ‘ËìÅyø,H$k Nƒr”ä}š‹ÛQu;î@@Aêd\µÛÑýþTÒHkÌÇótLÓr~IÿMÿ{*‚* endstream endobj 11 0 obj <> endobj 19 0 obj <>stream -8;Z,'6'?mD%*W:siI+H``iB!`rWu@_K^T=;'S5J.4*5YRCHr;-H4gjHhVT:_"Cl'B -'G5Z["ph)[-sM?JE\\6S5l<07e$]jR'/GlA6prPE6t:'G6/]u9k@id]5S9tue-(C. -&PK_#[fN0I'(VtOd>G%p/@`tHg#c^Qs$q@QOq0G(4L,q8",3@f*gI?\ObQ=$Gr3;4 -las<[\fdD-,lq[p*iT6ADE1c3M(Z62o[LH?`8gfs]H:"ZL^T_BJQ1Qt>GpdJ*mKD# -T'K3@PB,1O:dA@/FU\rDUl2@J$,2ThBnkHUr(0[WX;5U.K7T^CWa,GRp\M^*NA%"\ -#9CD[6hF)PhaaP)W?/YnF%9.1`iVBpMq%Gk+=]!omM endstream endobj 20 0 obj [/Indexed/DeviceRGB 255 21 0 R] endobj 21 0 obj <>stream -8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 -b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` -E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn -6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( -l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~> endstream endobj 16 0 obj <>stream -H‰ìÖŽ£Hлµzÿÿ“ﲌ4Ys×à`ß“fa醨a¨øë/€ß÷/€›øû“Çãã£É–I¶L²e’-Sj¶Ôçî-“l™dË$[¦Ôl©ÏÝ![&Ù2É–I¶L©ÙRŸ»C¶L²e’-“l™R³¥>w‡l™dË$[&Ù2¥fK}îÙ2É–I¶L²eJÍ–úܲe’-“l™dË”š-õ¹;dË$[&Ù2ɖ黳ýüßèÀtà :p8ƒÎœAçΠsgÐ9€3èÀtà :p8ƒÎœAçΠsgÐ9€3èÀtà :p8ƒÎœAçΠsgÐ9€3èÀtà :p8›Îœáï¦Ç£;™G¶L²e’-“l™R³¥>w‡l™dË$[&Ù2¥fK}îÙ2É–I¶L²eJÍ–úܲe’-“l™dË”š-õ¹;dË$[&Ù2É–)5[êswÈ–I¶L²e’-Sj¶Ôçî-“l™dË$[¦Ôl©ÏÝ![&Ù2É–I¶Lßí翦ÿÎçË_³íÿ½Om'ŸWöÎ÷ï;:ùzŠý'|ýdtr/áòúv²Z£íuö}ZÇ÷U?z3_ß½wxù³ý¿÷õûöé¾ÉOoæãQ'{ÛV´;yÇ÷÷ó—’÷YÛŸüþÛ?~ÿ½gÞÞåõ-ùßþ´Î³ô&uŽj²Ú{êÉj¶×Ùw´CÎç7SçXëiCç`ÿ^þä÷ßþñûï=óö.:G¯Iô'Ç¿ÂÝÉjï©'«5Ú^gßÑ9ŸwÞLcy¬§uã®Fþ&÷öïåOþìíù=ã÷ß{æí]F:Çú 3ú,{÷{~?êiï_˜Þ÷ºó]kýÉñ¯pw²Ú{êÉj¶×Ùw´CÎç7SçXëiCç¸+c¢sôšDrü+ܬöžz²Z£íuöíóyçÍÔ9–ÇzZçÐ9îJç˜è½&ÑŸÿ -w'«½§ž¬Öh{}G;ä|Þy3uŽå±žÖ9tŽ»Ò9&:G¯Iô'Ç¿ÂÝÉjï©'«5Ú^gßÑ9ŸwÞLcy¬§uã®tŽ‰ÎÑkýÉñ¯pw²Ú{êÉj¶×Ùw´CÎç7SçXëiCç¸+c¢sôšDrü+ܬöžz²Z£íuöíóyçÍÔ9–ÇzZçÐ9îJç˜è½&ÑŸÿ -w'«½§ž¬Öh{}G;ä|Þy3uŽå±žÖ9tŽ»Ò9&:G¯Iô'Ç¿ÂÝÉjï©'«5Ú^gßÑ9ŸwÞLcy¬§uã®tŽ‰ÎÑkýÉñ¯pw²Ú{êÉj¶×Ùw´CÎç7SçXëiCç¸+c¢sôšDrü+ܬöžz²Z£íuöíóyçÍÔ9–ÇzZçÐ9îJç˜è½&ÑŸÿ -w'«½§ž¬Öh{}G;ä|Þy3uŽå±žÖ9tŽ»Ò9&:G¯Iô'Ç¿ÂÝÉjï©'«5Ú^gßÑ9ŸwÞLcy¬§uã®tŽ‰ÎÑkýÉñ¯pw²Ú{êÉj¶×Ùw´CÎç7SçXëiCç¸+c¢sôšDrü+ܬöžz²Z£íuöíóyçÍÔ9–ÇzZçÐ9îJç˜è½&ÑŸÿ -w'«½§ž¬Öh{}G;ä|Þy3uŽå±žÖ9tŽ»Ò9&:G¯Iô'Ç¿ÂÝÉjï©'«5Ú^gßÑ9ŸwÞLcy¬§uã®tŽ‰ÎÑkýÉñ¯pw²Ú{êÉj¶×Ùw´CÎç7SçXëiCç¸+c¢sôšDrü+ܬöžz²Z£íuöíóyçÍÔ9–ÇzZçÐ9îJç˜è½&ÑŸÿ -w'«½§ž¬Öh{}G;ä|Þy3uŽå±žÖ9tŽ»Ò9&:G¯Iô'Ç¿ÂÝÉjï©'«5Ú^gßÑ9ŸwÞLcy¬§uã®tŽ‰ÎÑkýÉñ¯pw²Ú{êÉj¶×Ùw´CÎç7SçXëiCç¸+c¢sôšDrü+ܬöžz²Z£íuöíóyçÍÔ9–ÇzZçÐ9îJç˜è½&ÑŸÿ -w'«½§ž¬Öh{}G;ä|Þy3uŽå±žÖ9tŽ»Ò9&:G¯Iô'Ç¿ÂÝÉjï©'«5Ú^gßÑ9ŸwÞLcy¬§uã®tŽ‰ÎÑkýÉñ¯pw²Ú{êÉj¶×Ùw´CÎç7SçXëiCç¸+còýÒ]¿§]ýçúóÎQßOçàþ®ßÓ®þóGß9jÇŸü® ²e’-“l™dË”š-õ¹;dË$[&Ù2É–)5[êswÈ–I¶L²e’-Sj¶Ôçî-“l™dË$[¦Ôl©ÏÝ![&Ù2É–I¶L©ÙRŸ»C¶L²e’-“l™R³¥>w‡l™dË$[&Ù2¥fK}îÙ2É–I¶L²eJÍ–úܲe’-“l™dËt}¶Ÿ7ÝßuýsÿÙ2É–I¶L²eº>›Î±&[&Ù2É–I¶L×gÓ9ÖdË$[&Ù2É–éúl:Çšl™dË$[&Ù2]ŸMçX“-“l™dË$[¦ë³ék²e’-“l™dËt}6cM¶L²e’-“l™®Ï¦s¬É–I¶L²e’-ÓõÙtŽ5Ù2É–I¶L²eº>›Î±&[&Ù2É–I¶L×gÓ9ÖdË$[&Ù2É–éúl:Çšl™dË$[&Ù2]ŸMçX“-“l™dË$[¦ë³ék²e’-“l™dËt}6cM¶L²e’-“l™®Ï¦s¬É–I¶L²e’-ÓõÙtŽ5Ù2É–I¶L²eº>›Î±&[&Ù2É–I¶L×gÓ9ÖdË$[&Ù2É–éúl:Çšl™dË$[&Ù2]ŸMçX“-“l™dË$[¦ë³ék²e’-“l™dËt}6cM¶L²e’-“l™®Ï¦s¬É–I¶L²e’-ÓõÙtŽ5Ù2É–I¶L²eº>›Î±&[&Ù2É–I¶L×gÓ9ÖdË$[&Ù2É–éúlŸ;ÇÀ/Ð9€3¬;GåñèNæ‘-“l™dË$[¦Ôl©ÏÝ![&Ù2É–I¶L©ÙRŸ»C¶L²e’-“l™R³¥>w‡l™dË$[&Ù2¥fK}îÙ2É–I¶L²eJÍ–úܲe’-“l™dË”š-õ¹;dË$[&Ù2É–)5[êswÈ–I¶L²e’-Sj¶Ôçî-“l™dË$[¦ë³ýüìŸvýsÿÙ2É–I¶L²eº>›Î±&[&Ù2É–I¶L×gÓ9ÖdË$[&Ù2É–éúl:Çšl™dË$[&Ù2]ŸMçX“-“l™dË$[¦ë³ék²e’-“l™dËt}6cM¶L²e’-“l™®Ï¦s¬É–I¶L²e’-ÓõÙtŽ5Ù2É–I¶L²eº>›Î±&[&Ù2É–I¶L×gÓ9ÖdË$[&Ù2É–éúl:Çšl™dË$[&Ù2]ŸMçX“-“l™dË$[¦ë³}îÿø??Ï3ø=££òxt'óÈ–I¶§×¿îþÞ¾Ññ;”˜­û/c4Û¼£>½Îæ_Ÿþoû;êé?ý}Ç÷­ï´·vϳ×¼¯Ãû§ÎñþcM¶L²=éß"7›Î1v_ãµïwÔ9ŽÉ–I¶'ã[äfÓ9Æî«s¼ÖáýŽ:Ç1Ù2Éö¤s|‹Ül:ÇØ}uŽ×:¼ßQç8&[&ÙžtŽo‘›M绯ÎñZ‡÷;êÇdË$Û“Îñ-r³éc÷Õ9^ëð~Gã˜l™d{Ò9¾En6cì¾:ÇkÞï¨s“-“lO:Ç·ÈͦsŒÝWçx­ÃûuŽc²e’íIçø¹ÙtŽ±ûê¯ux¿£ÎqL¶L²=éß"7›Î1v_ãµïwÔ9ŽÉ–I¶'ã[äfÓ9Æî«s¼ÖáýŽ:Ç1Ù2Éövëp§uc£¨7ïÿÊU„Rb÷À“™í»ÖŸ;³¡ ßͱŠn›Íqì¹6ÇçÏaûD›ãï´5i»±9VÑm³9Ž=×æøü9lŸø¹9.4ú3.þ÷6J[“¶›cÝ6›ãØsmŽÏŸÃö‰6ÇßikÒÖ¤­I[Sµ­zîÚš´5ikÒÖTm«ž{„¶&mMÚš´5U۪硭I[“¶&mMÕ¶ê¹GhkÒÖ¤­I[Sµ­zîÚš´5ikÒÖTm«ž{„¶&mMÚš´5U۪硭I[“¶&mMÕ¶ê¹GhkÒÖ¤­I[Óü¶ËÆè]óÏý{´5ikÒÖ¤­i~›Í±§­I[“¶&mMóÛlŽ=mMÚš´5ikšßfsìikÒÖ¤­I[Óü6›cO[“¶&mMÚšæ·Ù{Úš´5ikÒÖ4¿ÍæØÓÖ¤­I[“¶¦ùm6Çž¶&mMÚš´5Ío³9ö´5ikÒÖ¤­i~›Í±§­I[“¶&mMóÛlŽ=mMÚš´5ikšßfsìikÒÖ¤­I[Óü6›cO[“¶&mMÚšæ·}¿9Þàír™}~ßþ·üÓßúö>›€#lŽÿ³7ÇWÞßG_9j{ª™žß¶mMÚšÎÕvý+òùÕ‘¶×ºÿü¯w^Û®ÿüxÊöoæø“?Ÿðݽ·§ÿé]nßùxÒþúö5_?ÿþ}ÞÞn¿·û»ïïûØ -¯¿m‡í»Þ_Ý~ÿþœ_oŽë3mŽ{çúœØÒÖ¤­é\m6DZ'Û6ǘs}NlikÒÖt®6›ãØ“m›c̹>'¶´5ik:W›ÍqìÉ6‡Í1æ\Ÿ[Úš´5«Íæ8öd›Ãæs®Ï‰-mMÚšÎÕfs{²ÍasŒ9×çÄ–¶&mMçj³9Ž=Ùæ°9ÆœësbK[“¶¦sµÙÇžlsØcÎõ9±¥­I[Ó¹ÚlŽcO¶9lŽ1çúœØÒÖ¤­é\m6DZ'Û6ǘs}NlikÒÖt®6›ãØ“m›c̹>'¶´5ik:W›ÍqìÉ6‡Í1æ\Ÿ[Úš´5«Íæ8öd›ãÓýÊ°9öÎõ9±¥­I[Ó¹ÚlŽcO¶9ÖÞëÐÖ¤­I[“¶¦j[õÜ#´5ikÒÖ¤­©ÚV=÷mMÚš´5ikª¶UÏ=B[“¶&mMÚšªmÕsÐÖ¤­I[“¶¦j[õÜ#´5ikÒÖ¤­©ÚV=÷mMÚš´5ikª¶UÏ=B[“¶&mMÚšªmÕsÐÖôª¶··ç_ûÛ_·ýü©«xü÷¶n¥ÿßš´úÙÿ{—;£÷ø4i{œÍñ\6G“¶&›c=ÚšlŽ£W×`s4ik²9Ö£­Éæ8zu 6G“¶&›c=ÚšlŽ£W×`s4ik²9Ö£­Éæ8zu 6G“¶&›c=ÚšlŽ£W×`s4ik²9Ö£­Éæ8zu 6G“¶&›c=ÚšlŽ£W×`s4ik²9Ö£­Éæ8zu 6G“¶&›c=ÚšlŽ£W×`s4ik²9Ö£­Éæ8zu 6G“¶&›c=ÚšlŽ£W×`s4ik²9Ö£­Éæ8zu 6G“¶¦Õ7ÇüÅå2û¿m[øÝWßÝ÷È»<ᆵ®_¿ÿ;¿Éÿýëú6?gsŒÝ÷È»<ï¾u7ÇWÞßG_Ù£­I[“¶Ç¼½½îy÷×öm׿T?õ™wþÜûûõ¯äõùïðñï‹G¾úÓß÷íµÛWûï|ü?¿¿ýêv¢ý}ûWÞ?e{ží«÷•÷›ãó=?ÎÛŸ÷í}nß¿¯¸îý¿}|eslikÒÖ¤í16ÇóÙ6Ç+ikÒÖ¤í16ÇóÙ6Ç+ikÒÖ¤í16ÇóÙ6Ç+ikÒÖ¤í16ÇóÙ6Ç+ikÒÖ¤í16ÇóÙ6Ç+ikÒÖ¤í16ÇóÙ6Ç+ikÒÖ¤í16ÇóÙ6Ç+ikÒÖ¤í16ÇóÙ6Ç+ikÒÖ¤í16ÇóÙ6Ç+ikÒÖ¤í16ÇóÙ6Ç+ikÒÖ¤í16ÇóÙ6Ç+ikÒÖ¤í16ÇóÙ6Ç+ikÒÖ¤í16ÇóÙ6Ç+ikÒÖ¤­I[Sµ­zîÚš´5ikÒÖTm«ž{„¶&mMÚš´5U۪硭I[“¶&mMÕ¶ê¹GhkÒÖ¤­I[Sµ­zîÚš´5ikÒÖTm«ž{„¶&mMÚš´5U۪硭I[“¶&mMÕ¶ê¹GhkÒÖ¤­I[Ó -m—;£÷¬pîߢ­I[“¶&mM+´Ù[Úš´5ikÒÖ´B›Í±¥­I[“¶&mM+´Ù[Úš´5ikÒÖ´B›Í±¥­I[“¶&mM+´Ù[Úš´5ikÒÖ´B›Í±¥­I[“¶&mM+´Ù[Úš´5ikÒÖ´B›Í±¥­I[“¶&mM+´Ù[Úš´5ikÒÖ´B›Í±¥­I[“¶&mM+´Ù[Úš´5ikÒÖ´B›Í±¥­I[“¶&mM+´}·9Þžâr±9€ß÷ýæøÊûûè+{´5ikÒÖ¤­i~›Í±§­I[“¶&mMóÛlŽ=mMÚš´5ikšßfsìikÒÖ¤­I[Óü6›cO[“¶&mMÚšæ·Ù{Úš´5ikÒÖ4¿ÍæØÓÖ¤­I[“¶¦ùm6Çž¶&mMÚš´5Ío³9ö´5ikÒÖ¤­i~›Í±§­I[“¶&mMóÛlŽ=mMÚš´5ikšßfsìikÒÖ¤­I[Óü6›cO[“¶&mMÚšæ·Ù{Úš´5ikÒÖ4¿ÍæØÓÖ¤­I[“¶¦j[õÜ#´5ikÒÖ¤­©ÚV=÷mMÚš´5ikª¶UÏ=B[“¶&mMÚšªmÕsÐÖ¤­I[“¶¦j[õÜ#´5ikÒÖ¤­©ÚV=÷mMÚš´5ikª¶UÏ=B[“¶&mMÚšªmÕsÐÖ¤­I[“¶¦µÚ.—ÑW®uîçÒÖ¤­I[“¶¦µÚlŽ+mMÚš´5ikZ«Íæ¸ÒÖ¤­I[“¶¦µÚlŽ+mMÚš´5ikZ«Íæ¸ÒÖ¤­I[“¶¦µÚlŽ+mMÚš´5ikZ«Íæ¸ÒÖ¤­I[“¶¦µÚlŽ+mMÚš´5ikZ«Íæ¸ÒÖ¤­I[“¶¦µÚlŽ+mMÚš´5ikZ«Íæ¸ÒÖ¤­I[“¶¦µÚlŽ+mMÚš´5ikZ«Íæ¸ÒÖ¤­I[“¶¦µÚþ»9Þžâ~gØÀoù~s|åý}ô•=Úš´5ikÒÖ4¿ÍæØÓÖ¤­I[“¶¦ùm6Çž¶&mMÚš´5Ío³9ö´5ikÒÖ¤­i~›Í±§­I[“¶&mMóÛlŽ=mMÚš´5ikšßfsìikÒÖ¤­I[Óü6›cO[“¶&mMÚšæ·Ù{Úš´5ikÒÖ4¿ÍæØÓÖ¤­I[“¶¦ùm6Çž¶&mMÚš´5Ío³9ö´5ikÒÖ¤­i~›Í±§­I[“¶&mMóÛ.£wÍ?÷ïÑÖ¤­I[“¶¦ùm6Çž¶&mMÚš´5U۪硭I[“¶&mMÕ¶ê¹GhkÒÖ¤­I[Sµ­zîÚš´5ikÒÖTm«ž{„¶&mMÚš´5U۪硭I[“¶&mMÕ¶ê¹GhkÒÖ¤­I[Sµ­zîÚš´5ikÒÖTm«ž{„¶&mMÚš´5U۪硭I[“¶&mMÕ¶ê¹GhkÒÖ¤­I[Sµ­zîÚš´5ikÒÖTm«ž{„¶&mMÚš´5U۪硭I[“¶&mM ·ý#ÀÄÈŸ endstream endobj 17 0 obj <>stream -H‰ìÖAnd7DÁißÿІàW£¡‘åbRkÃxýÅJ̯_•ø¥4NiZK§Ò ‡¥Ïþ°EJóZJ[:•N0¾ë”æµ”¶t*`|×)Ík)méT:Áø®Sš×RÚÒ©t‚ñ]§4¯¥´¥Séã»Ni^KiK§Ò ÆwÒ¼–Ò–N¥Œï:¥y-¥-J'ßuJóZJ[:•N0¾ë”æµ”¶t*`|×)Ík)méT:Áø®Sš×RÚÒ©t‚ñ]§4¯¥´¥Séã»Ni^KiK§Ò ÆwÒ¼–Ò–N¥Œï:¥y-¥-J'ßuJóZJ[:•N0¾ë”æµ”¶t*`|×)Ík)méT:Áø®Sš×RÚÒ©t‚ñ]§4¯¥´¥Séã»Ni^KiK§Ò ÆwÒ¼–Ò–N¥Œï:¥y-¥-J'ßuJóZJ[:•N0¾ë”æµ”¶t*`|×)Ík)méT:á°ô?³ÿ¤_¤4¯¥´¥Sé„ÃÒgØ"¥y-¥-J'ßuJóZJ[:•N0¾ë”æµ”¶t*`|×)Ík)méT:Áø®Sš×RÚÒ©t‚ñ]§4¯¥´¥Séã»Ni^KiK§Ò ‡¥øžý'ý"¥y-¥-J'–>ûÃ)Ík)méT:Áø®Sš×RÚÒ©t‚ñ]§4¯¥´¥Séã»Ni^KiK§Ò ÆwÒ¼–Ò–N¥Œï:¥y-¥-J'ßuJóZJ[:•N0¾ë”æµ”¶t*`|×)Ík)méT:Áø®Sš×RÚÒ©t‚ñ]§4¯¥´¥Séã»Ni^KiK§Ò ÆwÒ¼–Ò–N¥Œï:¥y-¥-J'ßuJóZJ[:•N0¾ë”æµ”¶t*`|×)Ík)méT:Áø®Sš×RÚÒ©t‚ñ]§4¯¥´¥Séã»Ni^KiK§Ò ÆwÒ¼–Ò–N¥Œï:¥y-¥-J'ßuJóZJ[:•N0¾ë”æµ”¶t*`|×)Ík)méT:Áø®Sš×RÚÒ©t‚ñ]§4¯¥´¥Séã»Ni^KiK§Ò ÆwÒ¼–Ò–N¥Œï:¥y-¥-J'ßuJóZJ[:•N0¾ë”æµ”¶t*`|×)Ík)méT:Áø®Sš×RÚÒ©t‚ñ]§4¯¥´¥Sé„áñý<é줿é„í/:cû«~Ï=}Ùý¦F>“Çò%û‡zö±¬ÛþªßsO_v¿é„‘ÏtþXf~Ü÷æþo>–?ù¯n(ý3_¥oŽïLÇ÷ÜÓ‰‘{òX<–Æ7Í=0¾C<–ûß4÷tÂøñXîg|ÓÜÓ ã;Äc¹ŸñMsO'Œïå~Æ7Í=0¾C<–ûß4÷tÂøñXîg|ÓÜÓ ã;Äc¹ŸñMsO'Œïå~Æ7Í=0¾C<–ûß4÷tÂøñXîg|ÓÜÓ ã;Äc¹ŸñMsO'Œïå~Æ7Í=0¾C<–ûß4÷tÂøñXîg|ÓÜÓ ã;Äc¹ŸñMsO'Œïå~Æ7Í=0¾C<–ûß4÷tÂøñXîg|ÓÜÓ ã;Äc¹ŸñMsO'Œïå~Æ7Í=0¾C<–ûß4÷tÂøñXîg|ÓÜÓ ã;Äc¹ŸñMsO'Œïå~Æ7Í=¹§Ï±ÿÔ0ùX^´ûMÓþßm~ÑÛ_tÆÀ7‰˜à±œÙý¦iÆwâ‹ÎØþ¢3¾i×ùuPš×RÚÒ©t‚ñ]§4¯¥´¥Séã»Ni^KiK§Ò ÆwÒ¼–Ò–N¥Œï:¥y-¥-J'ßuJóZJ[:•N0¾ë”æµ”¶t*`|×)Ík)méT:Áø®Sš×RÚÒ©t‚ñ]§4¯¥´¥Séã»Ni^KiK§Ò ÆwÒ¼–Ò–N¥Œï:¥y-¥-J'ßuJóZJ[:•N0¾ë”æµ”¶t*`|×)Ík)méT:Áø®Sš×RÚÒ©t‚ñ]§4¯¥´¥Séã»Ni^KiK§Ò ÆwÒ¼–Ò–N¥Œï:¥y-¥-J'ßuJóZJ[:•N0¾ë”æµ”¶t*`|×)Ík)méT:Áø®Sš×RÚÒ©t‚ñ]§4¯¥´¥Séã»Ni^KiK§Ò ÆwÒ¼–Ò–N¥Œï:¥y-¥-J'ßuJóZJ[:•N0¾ë”æµ”¶t*`|×)Ík)méT:Áø®Sš×RÚÒ©t‚ñ]§4¯¥´¥Séã»Ni^KiK§Ò ÆwÒ¼–Ò–N¥K?ð#<ûOúEJóZJ[:•N8,}ö‡-Rš×RÚÒ©t‚ñ]§4¯¥´¥Séã»Ni^KiK§Ò ÆwÒ¼–Ò–N¥Œï:¥y-¥-J'ßuJóZJ[:•N0¾ë”æµ”¶t*`|}þoÆ×ø Œï×øfÿ¾i^KiK§Ò ‡¥Ïþ°Æ÷~-¥-J'ßcÆ÷~-¥-J'ßcÆ÷~-¥-J'ßcÆ÷~-¥-J'ßcÆ÷~-¥-J'ßcÆ÷~-¥-J'ßcÆ÷~-¥-J'ßcÆ÷~-¥-J'ßcÆ÷~-¥-J'ßcÆ÷~-¥-J'ßcÆ÷~-¥-J'ßcÆ÷~-¥-J'ßcÆ÷~-¥-J'ßcÆ÷~-¥-J'ßcÆ÷~-¥-J'ßcÆ÷~-¥-J'ßcÆ÷~-¥-J'ßcÆ÷~-¥-J'ßcÆ÷~-¥-J'ßcÆ÷~-¥-J'ßcÆ÷~-¥-J'ßcÆ÷~-¥-J'ßcÆ÷~-¥-J'ßcÆ÷~-¥-J'ßcÆ÷~-¥-J'–~øßûµ”¶t*`|ÿƒÕ?Á"¥i-J'–>ûÃ)Ík)méT:Áø®Sš×RÚÒ©t‚ñ]§4¯¥´¥Séã»Ni^KiK§Ò ÆwÒ¼–Ò–N¥Œï:¥y-¥-J'ßuJóZJ[:•N0¾ë”æµ”¶t*pXú ú5ÿòìŸ`‘Ò´Lgê*ùOî÷ZÎOé„–RãËK=–Äÿå÷ZÎOé„–R÷”÷l©Ç’§4¯¥Ô=å=[ê±ä)Ík)uOyÏ–z,yJóZJÝSÞ³¥KžÒ¼–R÷”÷l©Ç’§4¯¥Ô=å=[ê±ä)Ík)uOyÏ–z,yJóZJÝSÞ³¥KžÒ¼–R÷”÷l©Ç’§4¯¥Ô=å=[ê±ä)Ík)uOyÏ–z,yJóZJÝSÞ³¥KžÒ¼–R÷”÷l©Ç’§4¯¥Ô=å=[ê±ä)Ík)uOyÏ–z,yJóZJÝSÞ³¥KžÒ¼–R÷”÷l©Ç’§4¯¥Ô=å=[ê±ä)Ík)uOyÏ–z,yJóZJÝSÞ³¥KžÒ¼–R÷”÷l©Ç’§4¯¥Ô=å=[ê±ä)Ík)uOyÏ–z,yJóZJÝSÞ³¥KžÒ¼–R÷”÷l©Ç’§4¯¥Ô=å=[šz,@Nâ*ïÙI[d|¡^â*ïÙI[´2¾ÿ¥yJÓZ:•N0¾ë”æµ”¶t*`|×)Ík)méT:Áø®Sš×RÚÒ©t‚ñ]§4¯¥´¥Séã»Ni^KiK§Ò ÆwÒ¼–Ò–N¥Œï:¥y-¥-J'–~Ž uëÙ?Á"¥i§ç÷Ç6óh¿×r~J'´”_þØùc9ûïsZÎOé„–R÷”÷l©Ç’§4¯¥Ô=å=[ê±ä)Ík)uOyÏ–z,yJóZJÝSÞ³¥KžÒ¼–R÷”÷l©Ç’§4¯¥Ô=å=[ê±ä)Ík)uOyÏ–z,yJóZJÝSÞ³¥KžÒ¼–R÷”÷l©Ç’§4¯¥Ô=å=[ê±ä)Ík)uOyÏ–z,yJóZJÝSÞ³¥KžÒ¼–R÷”÷l©Ç’§4¯¥Ô=å=[ê±ä)Ík)uOyÏ–z,yJóZJÝSÞ³¥KžÒ¼–R÷”÷l©Ç’§4¯¥Ô=å=[ê±ä)Ík)uOyÏ–z,yJóZJÿf·L$‚òù'ý14íUGP†a÷”ïY©Ç’4ߊÔ=å{Vê±ä#Í·"uOùž•z,ùHó­HÝS¾g¥K>Ò|+R÷”ïY©Ç’4ߊÔ=å{Vê±ä#Í·"uOùž•~,’r}»¿\ÏNZ1ã+ õíþr=;iÅŽÇ·i>Òt+NÒ‹Œo=Ò|+Ò'éEÆ·i¾銓ô"ã[4ߊtÅIz‘ñ­GšoEºâ$½ÈøÖ#Í·"]q’^d|ë‘æ[‘®8I/2¾õHó­HWœ¤ßz¤ùV¤+NÒ‹Œo=Ò|+Ò'éEÆ·i¾銓ô"ã[4ߊtÅIz‘ñ­GšoEºâ$½ÈøÖ#Í·"]q’^d|ë‘æ[‘®8I/2¾õHó­HWœ¤ßz¤ùV¤+NÒ‹Œo=Ò|+Ò'éEÆ·i¾銓ô"ã[4ߊtÅIz‘ñ­GšoEºâ$½ÈøÖ#Í·"]q’^d|ë‘æ[‘®8I/2¾õHó­HWœ¤ßz¤ùV¤+NÒ‹Œo=Ò|+Ò'éEÆ·i¾銓ô"ã[4ߊtÅIz‘ñ­GšoEºâ$½ÈøÖ#Í·"]q’^d|ë‘æ[‘®8I/2¾õHó­HWœ¤ßz¤ùV¤+NÒ‹Œo=Ò|+Ò'éEÆ·i¾銓ô"ã[4ߊtÅIzÑGéŸ$I’ô=ûK_Œ4ߊtÅIzÑGé³VŒ4ߊtÅIz‘ñ­GšoEºâ$½ÈøÖ#Í·"]q’^d|ë‘æ[‘®8I/2¾õHó­HWœ¤ßz¤ùV¤+NÒ‹Œo=Ò|+Ò'éEÆ·i¾銓ô"ã[4ߊtÅIz‘ñ­GšoEºâ$½ÈøÖ#Í·"]q’^d|ë‘æ[‘®8I/2¾õHó­HWœ¤}”þ 0(Ç© endstream endobj 18 0 obj <>stream -H‰ìÖÑm1ÄÐsú/: äÄÑŽŽ4ùkÀã·’ÿúõ®×ëí$Ù…v_BþÞ>»ÐîKÈßÛgÚ} ù{ûìB»/!oŸ]h÷%äïí³ í¾„çûúm¿èºÛ_wÑíoütë¯öÌÞgµ;½Ýþº‹nã§[µŸðEoÿÏf÷%äïí³ í¾„ü½}v¡Ý—¿·Ï.´ûò÷öÙ…v_BþÞ>»ÐîKÈßÛgÚ} ù{ûìB»/!oŸ]h÷%äïí³ í¾„ü½}v¡Ý—¿·Ï.´ûò÷öÙ…v_BþÞ>»ÐîKÈßÛgÚ} ù{ûìB»/!oŸ]h÷%äïí³ í¾„ü½}v¡Ý—¿·Ï.´ûò÷öÙ…v_BþÞ>»ÐîKÈßÛgÚ} ù{ûìB»/!oŸ]h÷%äïí³ í¾„ßÿ­ï{bï“ú›ÝÑí/üt·¿ï¢õw{bï“ZÛ½n᧻ý}m¿èëµÝÛgÚ} ù{ûìB»/!oŸ]h÷%äïí³ í¾„ü½}v¡Ý—¿·Ï.´ûò÷öÙ…v_BþÞ>»ÐîKÈßÛ÷Uønß¡§ëÕŸîö­ÿïözº^ýéìw¦ä׫?ýÎt‚üzõ§³ß™N_¯þtö;Ó òëÕŸÎ~g:A~½úÓÙïL'ȯW:ûéùõêOg¿3 ¿^ýéìw¦ä׫?ýÎt‚üzõ§³ß™N_¯þtö;Ó òëÕŸÎ~g:A~½úÓÙïL'ȯW:ûéùõêOg¿3 ¿^ýéìw¦ä׫?ýÎt‚üzõ§³ß™N_¯þtö;Ó òëÕŸÎ~g:A~½úÓÙïL'ȯW:ûéùõêOg¿3 ¿^ýéìw¦ä׫?ýÎt‚üž9ïÝ_ôsú ¯¾ê_»}kŸ­W_îýõß»ÐîKÈßÛgÚ} ù{ûìB»/!oŸ]h÷%äïí³ í¾„ü½}v¡Ý—¿·Ï.´ûò÷öÙ…v_BþÞ>»ÐîKÈßÛgÚ} ù{ûìB»/!oŸ]h÷%äïí³ í¾„ü½}v¡Ý—¿·Ï.´ûò÷öÙ…v_BþÞ>»ÐîKÈßÛgÚ} ù{ûìB»/!oŸ]h÷%äïí³ í¾„ü½}v¡Ý—¿·Ï.´ûò÷öÙ…v_BþÞ>»ÐîKÈßÛgÚ} ù{ûìB»/!oŸ]h÷%äïí³ í¾„ü½}v¡Ý—¿·Ï.´ûò÷öÙ…v_BþÞ>»ÐîKÈßÛgÚ} ù{ûìB»/!oŸ]h÷%ün_UUU¥ný¿Å'eÚ} ù{ûìB»/!oŸ]h÷%äïí³ í¾„ü½}v¡Ý—¿·Ï.´ûò÷öÙ…v_Âïö[€ˆÇÒ endstream endobj 14 0 obj [/Indexed/DeviceRGB 2 25 0 R] endobj 24 0 obj <>/Filter/FlateDecode/Height 119/Intent/RelativeColorimetric/Length 142/Name/X/Subtype/Image/Type/XObject/Width 1014>>stream -H‰ìÁ  þ©o7 €?`Uòž" endstream endobj 25 0 obj <>stream -ÿ endstream endobj 23 0 obj <>/Filter/FlateDecode/Height 310/Intent/RelativeColorimetric/Length 447/Name/X/Subtype/Image/Type/XObject/Width 1405>>stream -H‰ìÁ1 þ©g `–Aô endstream endobj 13 0 obj [/Indexed/DeviceRGB 6 26 0 R] endobj 22 0 obj <>/Filter/FlateDecode/Height 321/Intent/RelativeColorimetric/Length 711/Name/X/Subtype/Image/Type/XObject/Width 2164>>stream -H‰ìÁ€ÿ«ë -€ÙƒCAÿ_ûŸxî endstream endobj 26 0 obj <>stream -ÿÿÿÿÔÔÔÿÿ endstream endobj 7 0 obj <> endobj 27 0 obj [/View/Design] endobj 28 0 obj <>>> endobj 5 0 obj <> endobj 6 0 obj <> endobj 30 0 obj <> endobj 31 0 obj <>stream -H‰Œ•XTI€«^õ0o:óÞ ¼1±& ˆ¬k^]ìéíšVEAPÁ´«¢¢"ðt1»ATĈs@TóŒ€ù)®i=à ۄÏï¼ûîöúûªº«»ªßëÿ뮊U!üýú6mÍÊeX`X@ÄÊ7¹—Рê¦À‰‘rÅrµBí†àˆ‘avÍYš€‘¡S‚Ó2óê>à¶:*(`ĵ&уŒ¹SëQ|¢Âß„\yŒ -‹œ\i+õBÃÀ{ü€͹í09¢b=¢#WòØ€° —¹ÜæûhzD„Oˆ,u9V *µl=b|P¥T€½#õŽ€DÍ*x¢RÞ÷¢$œQ#Z²ÓhtÄ -Á¾`rgZögЯwgŒ —kÆ”tC/­wÌ,x@³5½Ê¾Œk¡Â\9?>B7.v•›”m&e>Ÿ7¾HLc§uöUªVstªîìâZ£f­ÚuÜêÖÓ$Ùhr÷PÌõ4lÔØó‹&M›5oáÕ²Uë6Þm}Úù~Ùþ«;uîÒµÛ×Ý{|Ó³Wï>~þßþ­o¿¿÷}ÿ þaÈÐa0rôXæñ'O>söÜù Y³/]¾’Wsó®]¿qnß±XïæsˆãuæGÕ‚3LÇRA;Èü(œ¢h:ÅÑZO—é«ÊüØ\‹æœæ™æ­>D?FJŸ¥/5Ì0¬6¼6|”jHz©«Ô[ú^ ’~¦I{¥ÓRžd‘^Jo¥ÙQ6Éf¹™ÜRö‘}åöryˆqâ¹"€ùð|gâ2DÌu„&‘ëgv®vÑÚÄ -.Úârý¡BDOø‹&º‰Þ\'öÿ·ù>7°LÄÁ•VÿÿíýYd3±Å§q“ÿÃßé?¦6B ÌžC"<†¹°âa-l…MàqØlX -¯à5,„å 'y{ ë ~ƒ7ð6Àv8ga ‡@H€Ap.Àe¸Ùp ž@0\…+i0^À¸¹£à)Á|Ñ0 ÆB„Ã8ˆ€ñ0¢ &Â$Pa2L…)ð#LƒŸ Öà ˜Î«ëLxÏá &âr¡lPŒ+p%®ÂÕð” jQ„R\ƒkqþ‚I¸uhUÐ7àFxïqnÆ-˜Œ)¸Sqnǘ†;1wánÜà:Æa<îÅ}¸3ðVÅjx¡#:aut†B¸‡.芇ñÖÀš¸â1ÌÄãxOb-¬ ;!ë žÂÓXë¡ xÏÂGøîÔPF#šðžÇ ˜…1/áetGTÐŒW0¯b.æá58„õ±6ÄFðáuJ§]´›öÐ^ÚGû)ƒÐA:D‡é¥c”B™t¶Ð :I§è4¡³tŽÎÓÊ¢‹”M—„ÂK^3®ð y•r)®ÑuºA7éݦ;Â+á5YÈJw)Ÿ -¨îÑ}z@é=¦'¤ÒSzFEôœ~¥ô’^ÑkzC¿Ñ[ú'½£hšI³h6ÏÁsh.Í£XšÏkT<ÏÈ yN^Lïé}$ÓTB¥ 2cLÃ옖‰LÇìYæÀëY5æÈœXuæÌ\˜+ÞÀ›x oã´ˆÞ¬5küY[æÃÚ1_ö%kϾbY'Ö™ua]Y7ö5ë.¶}Ävb+ñ±øDTŧâ3±H|.þ*¾_êfëbtstsuót±ºùº8]¼nn¡n‘n±.õ`ß°ž¬ëÍúðšêϾ¥­¢¯ø%ÛÂ’Y -ÛÊRÙ6¶í`il'Kg»Øn¶‡ýÉr™€çxeqüÜsï=ïù”ÖRûÖ -"; ±„Dj­(-µw̨%ÔRkl¤j„$‚™KGg˜A¤¤Ä¾…E,AŠ‡N'ÄÛ÷ö0rŸ÷yò|ß÷Þ{¶{þ¿³Ýì0™f§É2?š]f7œ‡k&òÍOfÙkrÌ>³ß0Í!sØ1GÍ1s.Â%¸ Wá˜&ל4§LžùÙœ6gÌYó‹9gΛ &ß\4—ÌeŽä(nÍÑü·á¶ÜŽÛsîÈ1܉;sîÊÝt%]Ù~‰^;ÜŽ°#í(‹%v´#}n¬gÇÛ ök;ÑN²“í;ÕN³qvºagÚYv¶·sl‚kçéªü1wçÜ“?á^Ü[:[1ßçüñg܇ûÚmv»Ýa3íN›e´»ìn›m²{ì^›c÷Ùýög™'ɳj©{ªXÝWWÔõP=ROÔSU¢ž©ç*@½P/Õ+åUÂ4€x¨Ñ EB=XJá;XËà»ø–ÅrX+àû*+b%¢be¬‚U±VÇXkama£…BuT#Š>* ëb=¬¾ØýФÛöã_ùßäÇü„Ÿr‰-Â@ Â` Á†ØC1 c Ǧö®½‡SqÆátœ3qÎÆxœƒ 8×ã<œoïÛö¡}dÛ'ö©-±Ïìsû¾´¯¬×º¤I“!KD1y¨½C¥© ½Ke©•§ -¢;©U¦*T•ªQuªA5©Õ¦0¿ÑÉ:…>¤:z…^I>TW§ê4®WQ=ªO¾Ô@gèÕäGþ@DÁB ©…ê5z­þ«Páwz…QcjBáúïz½Þ 7RSjFÍ©EPKjE‘E­)š>¢6Ô–ÚQ{ê@)Æ“L© u¥nô1u§ÔSzÊ÷ô õ¢Þô)}F}¨/}Ný¨?  4ˆëèÍ4„¾ ?ÑPú3ý…†Ñ—4œFÐHÅÏø9ÅÒhOŠg…g¥'Õ“æI§1ô¥q4ž&Ð×4‘&ÑdšBSiÅÑtšáYåÉð¬ö¬¡™4‹fS<Í¡šKóh>%Ò7´€¾¥…´ˆÓ~AKi%ÑrJ¦ZA+)•Ò(VQ.Ÿ—â2LÂ嘌)¸‚_âJLÅ4LÇU˜«q ®¥ÕüŠ½ìò-ÏÏFU ®ªkêº*T¿:õœúŽ¯ÓÀñsü'Ð r‚§¡ÓÈ uÂœÆN'Üiê4sš;-Ô uSèsšŽÓÓ…Ìgòy¾Àù|‘/ñe¾Â|•¯ñu.Ä;øþç³ðoø.п«Æ°2်Û`;ä_ öÁ|¾‡Ô"µ˜Ïq]®Çõᱺ;Ü@5e?öÇÍÈAÎM==ƒ<ƒ=C ŠD³×C’Š‚%*ZMTËT’Z®&A–Šãýƒþ§Þ‚‡ùåc|œOp.ŸäSœ‡Gð(Ããxsñ$žÂ<üOãÍ¡t‚Vª”ì5V±×….¹ a‘|¨(ßÇê6ÈZk³dç¡òé%ÕOŹûÝ|÷¤É Q‚à¢èóÓ^ör²g4caµèxuh ý…W¦Â&8¬ê¸ ŒÓ °=¡!t¶y$vÌÝâîsÏA X‘òöHÉÃz‰x~¨Û¸ !Z>é°ö\ѵôRw˜D§  -Ê„¡¥³òMw•¤ó\ŸÂ¡£xÔ_)Aˆ,EÞÝ$ìµUˆ(r„~ÂUSÕN%ëÌW³½Q2#VŸ# ŸÄñB‰p‰¯tØƪƒDo€ÊÖEf‚ µ‘.¸+á=Ù9VÈ+^ˆì[Ø(÷DÞñSSÝqnâÛÜEB_ùÍX‰ËlYÙ’•«B=ÅÊ ugˆn×p§ÉÖÚ‹¥]ása¼QòëYÂë„O ë ‘Õ¢‰R#Ôu=P&³:×æÛbo¾;Ùý—[èÞËëJ„zC9+^â›(\¹vK”¸I-”È©UdŸ@5PÅ ñ}'¼tF=ÇŒ•:ÍÃk:L/Ó7D›_¯M´·i¯÷´ÛI¼P»*Ë -ÅÂOÅëa˜›dÿ,8 {ô •>–J #úª&²š‹µ1ª«pfªÚ]l…ݱœ4FºÈ6 ºšö×C¤3ÿÍ„™ÖfŠ(ôóB4u¡Ýì ñö¦IŒ+¸!n·ªHŽ£$:#¥ú' Ï&@²põVÉùN©ÓK!¡:±à®Pöx.[Z³œÄ9BEJ~_ÛÑO UcT‚Üäm¡§¥7ÝR÷ÞèeÑ«ŒÄhŒ¥«¥KO;ˆwuí«ôx!¯]Âg„¥æÚŠ’ýPc‡Øé‘›¤³ut¾à²œûÊÿÕU¯·­w˜w…÷·®íöw‡¸kÜuîN¹+‡Üãn[üvv`™ª@ ¹…r"%ó¡' ”5ZnÉ4Éü\X ÷" R%Ê[ÄÏ\©„<™nËLñ@9Ýê.8ío€“þ œ’8jƒÌ’FÏdßó°r[ n¤#¸n£e’y~Ùž…Œü)ä©óx š$;Q·*½¹tµæ(:˜ ‚[Ñïð~¨Z™G$f£…ÁGÖMËü ¾zo]íÒš%ÕU‹++¤ò//*s»œâB‡°`þ—ì¶R«¥¤¸¨°€7s¦yùFƒ^—Çj5 %UDÄ–„ ¸ -ã[[+U\쉞‡&ŠS-sy!‘cærsÓq§9ƒ³œ˜üÈ_Y!DDAù0, -iÜÕø@XŒ Êd^™ƒæà|€X D,aAÁ !¢´<5 Gax]Ê ‰¡~}eJé RJÄm)\Ò„s)‰4¤ÊË¥”R1Q¬bXÕ@¡®HOŸ²º# ÛŽXe…‚CÅ^‰ÍŠIʱ PNŒ¢ )lNŒ0¨îíRãòhšC½ ÉØ'öõ¬*´'¦Ê0K 7¬”$',Px9Šî{˜j£rÄ2(¨¨,ï”uD¦:Ô1ƒwÀZâjIÈ- zT5¢¥ -QÕW·2½©~1¢Î$¶ŠNlä- ðG©¬ 5»'JKƒg³×QiD×FE‡°‰±ž°=Uˆä5»_·ë\JeEŠ3O[35Ï4óúgi9(Ç®Bmkf͉UÄ劰QM¢"l¤^ú둼±ØàðJé7 *ºPBæ`žS×+' -òÜ.NÞœ;Ó33£uq_ Tƒc6¾€~V$I)/Wã‚ #AǦ^WYñTš\·q<À|hu–ŪÀæ‡êÕýé êDÙÓÆÔk;‚URL! •2~ŸRô˜JÙsŸ2»O‚.èô@êõxx/_W[æEæBU¹?óCcÇëÂíÎï¼:–Iz×¼»/a|ýØE_æ¹Ìó½ÿ™ªA‹²PÐAÉäÅ%ÁnâÁ~gãâ6çi‘Ñyq9’ ‹Œ’³Ü½ -sÜMÞp)¯r#E#žç\‡kx~:ÿåE·³ˆqñN‡ÕÈ2&½ñcÅ€ ?A #ñ®Bžw¹Ò4uiË[lMšF‚¼Õj2çXÐdàë¹8×(ä|Ú">;~ªÀ÷ù4Y,¶ëª*øj;¾mÇv±ž/áëõâ€E’8ÎEÛáEöìøëóM{:;~Ú -Ï É'IRlå¤j4©trBòKÜ8²J“‰[ª8?7˜ Lì›·XÒ<ͽ-wã“ïC>Ÿ™÷a3_’³ívߎ)«-*,^Z¾.ÑjÅ…îºZ¯Ìœƒx¯giMq‰—­-s‹ !0î³zè¯/R³®Èeò‰¾÷ùËÎ'šZÖ¯yut¨¿>Ì9;­ü‚ŽÐóbQåâæº ôнÍ&ÖÈ ùíüð®Æʲ-ïvVÿðñøk[×?ò•o*Nëü¦Ìwåºÿ°^%ÀU•WøÜ{ÿûnbH¸$ïeƒ$,Y ²ôIH[ -a `›Q¶dÈ D°" UIgH*‹PÒ*+“ÄLH"ÁQªŒ:4R,–éÃÐH+Bòþ~ç¾û²á´Úé›ùæü÷_ÏÎwÎù_ZFAéIö_ü× F…S"ux› -n`æ:s]S(_ŸÒª¹0y“s“«:d«s«ë`È^ç^×ëÎ×]ahªxÊ`EbD衈ˆ¡Q®áNçj3‚(Õ všg¿<ó® ááDCÙq£„#jLPPpR²lÆ0xŽá Ê»lHClŒ25ƣɸlFt²ù¶š†Íy‚‚£’‚#‚“(éöÝôWf˜·aâÔTv”å¡<öKŽÙ~£B‰þvÎZóndÚ‚g°oÜFˆ™“SÁCgØK)pRûÁe²[’”,DC¢‘ °/rÕÌ Ë7†Zq¨pyîšgÎþë« Jz¸s;H™á=™–˜ï./ò ÏÏ–¢—v¾:×}¨ÌûõŸN, SE†8µä1w?Žu =ÖQþ“‚‰#^D4Sl¿±“L»<Ñsú) -鑱aÑáfXX0ÒÝkúÂØ ÁFbƒòcÏ=ˆÎ+0A†ÒV®„?d‚¦o$;óXzLôטGÌ&S3£‡È!m -lT£R†-÷%#Vb{¥rã±öÔ–±¢×š×#Ó`•46e¤P‹¼ >’2KãÙ8N›¼–uºÉš¥)±‹æ—o.Þ2þ˜_UöÔÜÌ‘c³ú;,^¶|ŠgçÍ«³&Oß2å€2ö­’‚ ósR²’¹BƒÌ‰ÅÞ/×/~zh1T&Ê”Y‚h&P訥Ïnš¯º•<µ–Wkeúo‹i¦JøËQKÁWU·,F0 -x^<0Xܦó°f6 ð] zÓqPÞÑçÊJàC}.mÖß“õh7 Mú{ôk‡[6j1²Aü -ýâ ÙhÄȘ׈ñøna‰±&±Jž_Ð~|7cý7F u ÿyôñº÷qwU7í…ŒÁùcÏiЩz$aZeA¦@>¨ÖzùÌ]bEaMŠêöÖc¬Ú±°ýÑøNÇdl8 zJŒ'a,{¸¡ƒãûµ*éÁX›z–æ)-T£ž•ãp~’}ïJëÞ|gûN¬¿­Ó}`Y¿žÀ™½Ð­[@¯^ :ª¦ã+€)ÐõŠzŽJ ÿ,È»_¿KO0HvªµÊvØê-QBéF•¬‚Žsô㔉ïXî³@ò†xYnÒÚ©c©Ž]Ø«„F«?φP¥º”ð$¦d¬MÄyq@ ì–,.âì*ÂziísÕÒÿa œ?Òe'Øƨ¢#1÷&ôús¾f@¿a@"¯Çùilsö»2×[ñ!Ð} ૼۀ­Xßîÿ}xêÈ38'Ã>§¡‡l`îõ„í?šý°l_K¿êƒÐ¥?°˜‰ï È8ðN¢}Ù⢛ˆùÊœáoæsƒ9}3Xwßä;Ç.Òdpæ·°á:` ð¤ƒèYðŸ\ÍñÂœµâÅÞ›¹ÅœñKæ· EQë”ò=™S]’c¯“R,òÝÁ-¿ä¸cî³®"•†ð·Fké ¾uK‹Ké~Ù}WÚ¼±‘¥ððYà:¸è—~[tÉVÙäH+3éb¸E Úd fÒxè•![16Mϧ§Õ÷)Ðh¦xø²:T÷‘»F«²\o¦KVþ9GÕ‰¢U*Z]¯“×Å ¥Y¯S7pû~Ùþ¹,=Ç~hÿÿõS½Ž–¢ýw½¶m¥í\#Œ6e0Ø/Ñ Ø HUv”* Æ2Á›vàIøá!ÝCÙxåç y`§ôÏqìJi*ìU¬z”#]¼—ñˆÜ$Bå‡<ÇšÇkÊeÛƒmÔÓÌaëMÁ{ž£–=ÜØ«ƒnªòÃá¢KÆ;ò3=T^Ô3) `•|CN®µêµ—Æi;(Cm—mÚɼ7öÈoµ8y¡«‡àÝ4~Z'÷ˆ2»v[5\:8~ø½ÁÑßö½'¬5£égx§0D6åèUôSí 0[^Ó/`¿8ËÞŠ4ŠÔä%m¶/Ò÷–áw‚·~u:ŠcŒuÀyhÕZä µòKƳå!1Ÿø½˜I$ %À$ûû´”|s~V¡>\@ûQð5¯ÌõèËæw ˆ—ïj§äM—ë´ç¨ܹ¥®AÝÜA "’41˜–ª¹x›¼DµÍòM­’^Ö>“™ò_êJZ n—;µßÓE·7XŒyu„¼`ߎ«öí\LGû:÷Þ;åƒÕ®Ášç±O%dŒÝÿæ£ÝY6xѹ˜`cú -€`Ÿì¸Êü¼ï]òÿ—ß]¾§´ëOŠ]:ï«)?DNý^²Wíñûÿ¿Éî7doé·ƒ¿ŽöÐç?Ö<¿Q{‚s+ç7ΫœÛ8Ÿr>ñK«Žs^û7ëedEuÅáÓ}»ß{3E”L)Ê ‚0(ƒKdI¤¤Pq[d1â0"—¸Å…qÁˆKLŠ‚aF SqT@`ŒÑ€+et4h R:ôÉw{y í0”ÿøÞéwûvßsoß{ÎïØܦÇVÇ^ k3♥6žù ìbãLaÝÄ/ l½jêÐÌ·‡uck¿JºÛØ^wC;RŽÏkòZæú´"žXÍ{’´c¼}^+)Àžeµ†¿XžÏ 뻢$/Æ5^¡ÍY™—¥ÔßüÖæ3ëK®#š¿†ëÁaý‘ µùõú Ï~as¦»Gîb?<i\êÀZÙå”ûüge‹ß[fqÎûåuí/t,,4u5¼Î¸9ó‰ 6­ô-w5QaPÖŠ¯ÈB§øßN¦®ÐÊÜÇZ‰vèšCßdOÁßRTÀ\©…^È<-mñû@ò}Y²ýMh«±zæ5W<çôÞ´þá×p›'›<—ÝEžœ¥òºÿg,>+KšØsë…\¾nH×GÖozëôWæQ˜hDþüLúzÃì>³k,~Ê—–ÉX¬Ëš£É䌘çBRÓšçO }UT?qöÔzï)q­2—hà]©YnõPPÉú<†Ù-ÃÌ2çú0×Úýæà+´ëÚýÇœþîm‘"r}¯˜6Ô©KBP¬•¡¥° ®æ½ítw#ÖÂASÆ÷ï@ÞýŒ|²Úi…»,¨ -%>öMÆùÞâ½EÞ•¡Æêžµ‘b˜Ó~S(Y»v!|XaJxW‰”aß6•jëÝ©áËõ@¸f‹ì<‚'™saN´uIžñ¿–®™…ì›oX·ÒÃû‘tñûH—ÌUA•· :À©¬Ñ»ºÒ,%ç]ã”cËe€Å½A:›“¨{Ï´ú…:dº3 Ü:GºÙy=¤'5IOy#¬í"+Ül¬›ëa±»B‹aü•ÿ£¸×{ fÞat+VÌqŽïΖÕN©\ïÔÊun•”ÙqLKy< ý'DèØÛX‹/½QòxŠþixÖÚih·öô4q{Û4´[{aÚ/l£õ;šGköÎ߃G{oÇ4´wlÆ¿’4´—|?Ž¶ÎÒÐÞ©?§¡}pÚ4ÔH -è"ECËÙØ™ð5lô™ÜíÑZÿŽú„¼¯Ð^ˆî>Ž{h­`Ü¡·ro9×{·Y¡#ƒßsÿرnìMÛÉЇ>Œtƒ3¸^‡‡E4-°øÐ`;Ðסš `ë»àüh¼ðùU‘Uô¤ÕœA=×VCðNù| èN½/x1öÝÞßÙp\ë7ó—¾`µ0>j)L†×€6½˜¿¢õ4ú<­nlðut"gµ;áZï|jÎÙ‘YÞÆz‹ÐÛ?S½Ùbâ!ÄÊ;Íh ¾’Y«·x§±/çK+j©Çˆo‹2/ë»ä¥rÿi©¶õºæçæÏ:Ï«’ÿQq¼{eH¦XÆzƒ—hƒiÃ=ÆðÆë7V¿„õ™³Ó¹?]ÿhs˜­UÚŽ\RÐ_æÊPôM±yJ÷ä<ò_c^NÎûŽÉ•¢ê´M6ÁŸ¡ -ç > 6·Ò‘Ôž]2“d³?Z¦%y/s®ì÷.!ïĶ°z§;í˸ÿéY0C¦fn°¹38!?v¤µŠÍÊ7Ó›ÁÖÔº€ýp& ²>‡yfëSróËV;ù_ꯅîÄŸüy#¯–Þ<é›=À|rÒ¶ ǼOA_¼ÊZ4XŽÙ<½ÈæHÞ¿÷g ™i »^kkõFJ—žèÿFg{'ë‚H—êªDŸ&ïȶ£¼M°'^Jëš¼ŽJôM¬U“1âù¼~ª±ù3™~Òºã\tßpýÔX¶…ÚbN¡^cœµ²+»\ÚðýØÙJ\2xΊâ\Ÿ‹Ûá×ô{–¶ -âÈyü§& ¦ðÿ¸kúÕQŸCÏcߥ½vstö…ZO.ã9bYð®9kÎAìâ˜5NhaÛ­sM¤åÓ6¯ë­êš²Þ‘ÿuGÓµ›ÎŠí;yMÈÌÝAU¢k=Ù´¥&ˆÿ³¯æ££®ÈëÙDG§,ûýúmJÙ¢Èoؽf÷qÚæuõ‘Ö‹¬î?Vm™œ³üyKtuhuSʶÉëëcÙõÍâh>r¶/¶óy?¥Û‹±Oòì¡oÕ’#e?ëß {+öj‹®{%6_Æöp ×O‘½)¶sìÚX¿ì³a?ö’ý®ÞTÖ¢ì¾ Y¯ÿ IþÇDúþÛdŠdÜ•]-“¡<±¶VlŽ¬#ãàžÜ ™ ål­%¬ "¼F×y¨ÇÁ]þ2ÊÙZKøí› ó¼{sí¯=þŽÂßQRŽþØ™ãeo¶ý#jý-Ò§92/J¥%7”q†ò̽Œsohk-ɺ'똬K2¿Äßdüä½ÿïwdŒ–Íq¬ïò}Í»Yß.¹ßj’ØVº”ÏvÝZffÊ\˜”½JæÂ$Ÿè[á\|xïèˆx·,Þ@™ë­ê§uÌÅé=àµ×+,ùÿœE‹¿RæÂ$˜ÍxÅÔ7µ>ìµzößVÖ¢Þ®“P?´—³ÆÛ-Óm|·$±/w@7 .ã(±¦ TVyÕ2)Ò{!¡öc>…V/q¿,ŠwÒ•¹ö¶q»‘ø3Ï‚Îû'ìÁÇNp×âýô9¬Á–a·ãÓBx˜ëBÚ¾0Ýu),áº"Òâ24ÒÛÁÒHŸBÛ¨=ñ-(Ä—j{M‰þÒ«ÐþľBS§;Mß®íæ@/àgi¡Ïxü®;ýK¡;}ìYX »ÍWR”À{ÄÌ’Bîõïoà™}Zn>“‰ñmÌléb¶KÎ?‹þup¹ ñÖËw›^ÃX_™ó$ãMbN§óÜHè}ˆå^O¹Èê"3‚ë 9Õ93d¼œmN”ÐÏŒ•Åfšï¼"§»“e€ÄÝ+]ÙG½ÌêØ¥òcs}.&?ìåÙÒÍýXž1È‡Þ 'ã^%Sàk‰ûSÜaäÉØób:Ç dŒRù©Û}ù¤Œ Ïw4ígÀè˜ku£s·ÜâN”1îý2Í̤Ok¹Ù­–vn©¾å¨Üî^I¿ -¤Ÿ;¥ZãlÕw"´ÖOè3ÄQ­sçh¥[¡;èsµ?”ýR.+ýú_ïY]‹^Ÿïgô#ï_²Ú?[gy)µÇ¡°ÔÂ÷Ýç‰Ó ýø…­ rX—*ÍÅ:#¢¶ðúOüØê-ª#î µæ¥:Þÿœø>úå#›Ãl {xfªÕ °ÜæÞ0ïµ’Ö¼åuöW%´‰Î€nã|¢9ЄÑ9êæíÐ n½î¥O}~bkI÷-çÿJ«§Â3B~$^¼ÊÙØîQ ñþ¦L«íXìÇ—ÂñDÿÇ~ÕGU]ñsï}Én° Ÿ*²÷­† $˜5›`TvÑbx„†©a›l 즛I1-ÈØ©LÖ©vŠ¨Y‹!åÁv¥ý£ôC)Ú¡3ýÇB[u¦u†×ß½»Lí×Ðé8Öy»ó;çÜ{Ï=ç¼ûν÷¼^QL­ÆÕîOŠá;i;¬ö›Îëî9#ì‰Ý¸oOºßUu橽¬ö Öá6ëgØzQÕ] ÕŽ\-R÷t7¨:-/¸ªÖãðƒ>w/ßwñ U3 ž^¥sLJ÷é£räˆÎ6à> ¼À+(À—ÓV¶†z€m@³5îãà÷·±‹t«è¢UÀWùr7ýV~ÍVù$*aë=š [{Ø݈¼Z÷ÃG O"·bìrm9=^À˜×W8þ¼§¾pn4ó¸{š‡‘c«±·³¨ÃîÏT©ï¢^÷7ê=á<9|¹{ kpíßáoëËÜß—Õ•6«±­àR½Œ÷sA©Åwèzrv¡~8‹3ª¾‚Sõ¸â°¸”oÿP·M¤QC/}sa}Æ€À;¾ \õ€3õâ·ógëÅk|ãh–>a»P/XØDZn·éÚn¡ûµKµø'jm]"¾?mtÇÄÓȽ—h!ìn°_i60þöÁOBåW~'Ò– óÙ÷?Û_‰õsœP¿õàÁƒZÏNC“%Ê&¿µ 3ºï+ÈœÆó÷ ²@ÿÇÙ ÛE´ CÞQÈÑódQ-À? ©…6Pܦ$%€4õQîYŒV -²¢1ôwj -ŒD© ‹šÑ·óÓÔ«[qð8´·€¶C³ãݺעåà÷i­$úb°daTÄ€´öÑ5–¢MèKRÇŸ²šÐóóZÑêDKEdÑJH1ÝÊ{N ·R[°´í :~‹ÚÐÚŒQW§Ö®xÞªZ° lµlˆ[v2‘L÷õÄ­ÅÉTO2Kw&V´«Ëjî\¿!Ýk5Ç{ã©-ñöŠ¥ - MËÊ[:»ã½Ëã÷5'»c‰+ç×'»Ú—¦c]mv˧£¢Ç, ZzÔêìµbV:kwÇR›¬dÇ¿ ÜêLXiŒµ&:Óñvke:–†¥X¢½2™²’IYmÉ͉tª3Þ[ñ_Lž¥Ôû ÔD˨ü©”O¤¿¦Ñ -¼ÔùTvæ-Õ/¸ ÚmˆªEÛ]WÙ¥ÓêÓ±ù¿då3µÅÔ¡éžmwƒtœþÉïUjoŠ“Nµ¤èñ:-"À уâ ÃÀ)Àp‰cûª"Yðò -Í97TVÎ5³ªFÅ1~‚f“Dǘ3uºuêë B¸6/ìŸ;¿êltœ¥ó£bŒæägíŸSQu!+Æ°]Å»|ü°û ?áÜŠdù‰ý׆ª&D§ÃÃ: T -: pÊ€¾«¥µ {´T ºhâ'"Ï Ü"’«­«ŠØ ÏÈWä1ùcyN­í2-¿. CN–eò&¹D“H>$Gä¥8•{;ÇÉ=™Î͹\Î~ÛæØOÚ/Ú£¶aÛ5ýFïçý‚— -vJœç…+ŒGÅ G…Ñ$ÖŠ¤èÆ æGù)näN #?ð¨0¤¨‹D“0ú£Ñ›)©éZM›4]¤i¥¦RÓRM]MÏ+*:œ`Mi´ŒŸQsA³€ÀZœÁZœ¡¤n ò“è?‰5*•À"`-`ð3øŸÄÿVm2#ƨ„qòÓ´iH›‰Wú#Ñ+ùv¶›‘ÉnÕtª¢^Qöš¦{5Ý™0? -˜¿˜¿˜gæ[ssÀì ˜ñ€Ù0GødªÞžÈ5æÇ5æüsF™åS^)]RJ%#| --á[{†Ìò›[‚Õ8ö\y„W‘ͱ¶¼Â‘Éh ŸG’ÍD»|ø\Ýoò9,‚m-¹dݺ?@!CÍ›á„ÎÁÚt'›êçË,;îØ°£Žýؘcçä6’÷Ä9r=,²ƒ°¸m‡ÂÊ{™Âì ðŒÁ¬a'<„€Ø‹(uÖ£ûðø³Np†à«Ážq‚u`O;Á{áâ)ªÖ.îWŽ°>²µå-*€è¶9ÿl¬ž»À{ -“àªSþ™X§Ú¦\wPP÷·Qæw8öu*òÅ•^…øøÍÒí…Nè&v‚𢠶VåÔ ¡)átšWç—hš¼ìJ§:æwì>°bG9á‚ʇحf?‹L’çCóäï`üýà2ù.žé¬eÌ‘ï@yæù¶ÌÉ_jÕƒòáòt0ËV9ò§ušålÍ~d¨XéMqvË“‡Õëtä¡,\!_ÕÉ×Bµr Sg:r¤nį”÷³n(ï˲ȡ.¹7˜“OWgÙ‘Rùí{XúoVŸÃa’5ày[°Vö«éåý¡e²Oi”_±gËa˜Ôaß)Û‚;åºP³¼»nD½Z )yÂñ³²ÏØ”÷¶,¼[6Vò#—Öe¹ -òóu9Ùœ+ÃÞÌÈTYo7Ë(V#Ú)†»dEpœ‡ÙNŸ,Çr¨ æ Ig«HÙZs„ßE>Ç>¶#Ráû•ï9ßnß*ßm¾›| |7øfùfú,ßdÿDÿÿxÿçüãü~±ßðs?ù'gÝw#óÍäâ ŠŠZžÀUÕ·9S[Þ˜ñ­¸gõËŒ}달1s¬¿le>Zy}–ûÂÝ™¢ëëYfb#5¶Ô_å YѲ:Ë\5ãÓ3¯>ŒÓê/Ä”ÏKAÇg6)I-õ ¢% -éIº–ºîtñ"ÙÁ…Œ:x‚];v3½xÍÿ ¤ÓìQÿ“nþ'öÞÌ¢ºõØ}oö}?û̾âp’ÄXNl›By‡$^*z%~+ÞX¿¸Gß³éì‡é,%Þ­¶øLÙâ «”ÝG­Ãûö\koÜšk# v{NsÚ˜ßbžæ,°‰Ñ>¤-¤Ç -ëÒ>b0s]‰Õ%§È08FÓÈb@ ¾²â -²)ùå‚SR\!8•\€ª¶URâ–W*I*½¤UÙ´š^ʦa„<ÃäÜ@Ä‹xF\ʧ9£ä¦’›R.nä %w”Ü™ý‹=™Fy¯eÒF³í‰i×ïULDŸ¯å$Å>®†ÉMn}‘³ÅnÖ¡¬I*EËô¸!"w8n+q‰Û¸^o‡Å6 ;pc#£¿&Bg²JÒ_Ê×ò5”à¯AiÒû¾¤¿è=ó¥(¤cÐ[ç= . ˜Ë¸åºß:ž‡q½Šò’`Œnq|ÍÆÈ8ëÀò[€ ÿÇþ endstream endobj 29 0 obj <> endobj 32 0 obj <>stream -H‰Œ– XTG€«æÍ«3"· :óÞ ¼‰ְȺêÅsÝD#YÝͪ ‚*‡ â™¨dƒ,⌊·‚xá ("jDT@Eðžqo㸚¸~&f€Ù`ÙäÛ/_¶ç«êê¾þ§«û¥NŸ .DŒõN´6“¨˜¤è”ú auà>¾ýºtÕhEI§÷ ouëøvÐoz¼üÛž¡¿ëõûÞ}úþ!ìÝ~ýà 4xÈÐ?ûÓ{ïñÁȈÿc¯LðLgóÅÌ[Å$”r…âÖh§®Lz8;MpÚìÌÞ•—cVåÖ&ƒáWš*DÁôTÕ¼ŸÌ¥¶èy¿ò¯ ‡¨2ÛFé¿–é'‘«"Ûí±ÿ‡X»§SOü¶A:,â"!A,‡%° vÁvpƒ,ô X ßÂw° Ö@&"{G_ÀfØ ÿ‚—ð -¶Â^8•°&@ dÃD¨‚X8àTC \„ÇWà2ÔB!L‚ç°®BÔÃdø¬°âa -$@$ÂTÈ…d˜)0fÀLH…Y0žÀ˜sáS˜ŸA äÁBXÀ^÷Ïá)<ƒc˜ƒkP*‘4âZ\‡ëq4A3 -èv܈›p3nÁ\ÌCgT¡;àVܯá{ÜŽ;p'æcîÂݸ÷â>,Äýxâ!< ?À5ÌÂ%x‹°Kð(º`G<†¥èŠnèŽ`»è‰^xËÐ;áR<åxOáiü}ÐöÃìŒ~x+° vE jñ,VÂøîÁ}QBêñžÇ X…ÕXƒñúcÊhÀËX‹W°ëñ*”â[Ø »c <€‡x²h -¥e´œ¾¤lZA_ÑJZE«)‡ÖÐZ>€ÖÑzØIh#m¢Í´…r)¶Ò6ÚN;h'å+ã• T@»h7í¡½´ -i? ƒtˆÓe¢2‰Š¨˜Jè(£R:Net‚Êé$¢Óô5¡ -:K•tŽÎÓª¢jª¡‹t‰.S­²QÙ¤lVÚyàÙ‡ ÏñJžç‰x'Þ™WñjºBuTO×è:Ý ›t‹n“‘Lt‡ÌÔ@ºK÷è>= ‡ôˆÓVéOÉJÏèŸô¯ã ¼‰·ð6Õ‚›à.xž‚—à-t|_¡³ÐEè*h­ -’ ôjOµ—Ú[íª6«ÿMs}GgYdaçÎwgî}ï|@è=´„$š"½÷^,€k¥œƒ¨ô&5tiî®(ˆ¸k[eé ½†ÞkH€Ùq÷ì÷¯9sæœ9¿3ó<§åŒdÊY9'çå‚\”K.ËÝwÙ.Ç=pÝ#—ë»'î©Ë‹Qe+ÚJ6ÎV¶ñ6Á&Ú*6 㤨³£í;ÖŽ³ãí;ÑN²“í;Õ~d§Ùév†igÙÙ6Ãαsí¼àPpÚÎŽØv¡]äß®Åþ [j—ÙåöûWû7ûwûip48N‡ƒ“v…ýÌ~nWÚUö »Ú®±kí—výÊ®·_Û v£Ý$Å¥„””RRZÊHY‰•rR^*HE©$qRYâ%!’™C"#©15¡¦ÔŒšG†R jI­¨5µ¡¶ÔŽÚSêH¨3u¡®ÔºSêI½¨7õ¡¨/õ£þ‘y’(U$I’%EªJ5I•ËrE®Ê5¹.iR]Òi:Í ™4‹fSÍ¡¹4æÓZH‹ècZLKhy¢: ʪ[궺£N¨»êžÊRÙ*G=PÕ#•¤rÕcõD=UÉ>Sà&hˆ‚  ¡JQÈù¡Ä@A(…UU(EU5• -Å 8”€’P -JC( ±>›MóI£‚JSÕ¡¢J‡J•! ª@’ÔšrLŽË ¹!7å–ܦm )PªA*¤AuH‡PjAmú¶ÃHxFÁûð|£a Œ…q0&Ðï0&ÑÚI»h7í¡½´öÓúƒÒ!:LGè(£ãt‚NÒ):Mg(“ÎÒ9:Oè"]¢Ët•®ÑuºA7éݦ;t—îQݧlÊ¡ôÁd˜‚0†ré1ÄBô„žba,‚E±åqÀŠ‹c ÖadÖ‰™CvXKai,ƒe1–£œós,‡å±Vä.È…¸0á¢\Œ‹s .É¥¸4—á²Ëå¸}PÕ'u¦¾ ¯èú–¾wà.܃,¸Ù°>ƒÏÊZùRÖÉ#É•ÇòDžJ<„G á <…<h¥Akë1“±.ÖÃçðy¿»6ÁfØ[a;ì„Ý°—ŽÅ¾ø¾‚ƒñ |‡ëxé3Ñh‹ãq"NÆ©8 gø|”sq>.ô}r‰NÂeø ~Š+q ~…ñü¿ÇŸðßhvàܧSðÆãxÏé4¼„×ðÞÃÌÅ<ßw¬OïùMŒ)dŠék¦„)ãÛO9Ÿå+˜J¦²I0UL²©jRuMSÝç±:¾=çs~#ÓD“ijš™æ¦…iiZ™Ö¦ikÚ™ö¦ƒéh:™Î¦‹éjº™î¦‡éizù•Þ²^6üÿ~t¨E»ÿÝécú›æ53Hþá”CÇ.êb\W•qå]œ‹w‰.ÉUs鮶«ë¸Æ®¹kíڻή»ëíú¹îU7È ‘“ÑbÑâê¤:¥N«3*Så¼0U¡#!†&´!…†¡„.Œ†ùÂüa0&, - «sê|$;’yyyɕݲGöÊ>Ù/ä9(‡ä°‘£p®ÀU¸&[‚õÁ×0ÕlQ5‚MÁ7Á/êB°!Øü*[ƒ±Áæ`’nçûf'ßž:Êá`‹š®fÈ6ÝUwÓÝuÝYw Ÿ†y÷Õ%Qª¶h‰ÀÏ‘‚Ĺb¥€Ä¸]n·Ûãö‹‚Á¿ƒA†jÌT Õp5[e¨9jDðO5JØ 5ÃÌØ*ßÊwò½ü ?Ê¿ä'ùY6Ã6ø ¶Ãï°vÂ.Ø {`/ìƒýpNÁi8™pÎÁy¸á’×YßkìŒ]°«ŽÕåty]Á›€ñeï´=vÀŽ^i?ì/z¹­± ¶õÖ~Å-¸Õ{Û‰»p··û.Åa^ñ›ø¾­ãu‚NÔU¼æ÷p¾ï%Oñž'yÏyßê$ìUÏÖ)ºª®¦Suš®®Óu ¯4 ïc¶{oàMï´€—ZðÏ3½Ó²f°·:ļ®¯é«~®{— ½ÌÆ^úÌij^o¢7ï 'a3“jÒ¼é8ï9Å+®kê™úº¦®¥ïé,œÄb›ÿNi=7(y™~ÎûùßUÕuÆ¿ïÞ÷DE” ›ËŸ ²PÄ…fÁŒÆñ8l)hõ˜è1.©&k:š[jÕž˜ã[rÊÃ… V%jcšàVkŽG=±&JôôDÓ™×ß5t.3Üwï}÷~Ûý¾ßï¦/Ï|¨Î%Ý7Çl•¡`-;¾>1@ä|kéÍ¢/„$?OnR€%#Q"GQ>‡P8€UÅ‘NùT@a”<ØXs(}Ç9ôÇÐÚBh2õ¦Làî­<Þü¸ùWSÞÞÅD9×üŠ -©ÀügÎßÄÁÔ3A¬›W°ÃBZC€cL*¦êVìR@Si¾ù1•ÐY.æ™fšü¾‚6Ÿ¢ëü7+ªé¡Tð‚W€ÂC9NÖ˜»(M½ÐeŸyÜ<v1kÐm‘ ä˜ß“ƒn*lV„Òp´ùàt™#8UfS0¥à¬Y`õ22æ‚9l^Îõ2ØÜmF‚¬¤V^ÂÍ®^PïšË¨ôK¤^°™OèÝÂn9É\€Lñ§‘'Jd­âPj•:å¨òPí©õõµÂêÖ>ChÚ,p¶e°uÚ1ºv Æ’Ìc9;Íæ—ø5^Ïïñ60§F°–3ü-ðÝÂÖ¡²³òì+ã¥S¾/[»rQùÉVÚÞ×wÄwÇìj&˜ÃÍõæó’Ùæ÷BD|e#ºæ‚Ù½ ù8è‡È{'é¼…Ÿýí:xè}ú ¬.l*|IJ„v3Øͯ‚¥m›û9ú:?’ì& ÅÃå‰ ¶Ûþ -¡ËL¹Dþ^þC>P–ªÃÐêÔ}ê݀붘À–‡›Û¯øÈWí«õm6S‹ˆ¼PܹÊBÌåÁË`¨/ƒ¡ZÜtÚ*Äa=Ún:HŸñžF»D—ýòZÍb¹?øÙ¨€?UDë}<“hñp%|ÛÑ–s ÿv#ocýöíà}_¡’܃NŒš)ÆC£1SÌB›-ÊG׊½h§Ä—â2ï"{"Ó’.ù ù–ôJCî•ÿ”ç•X%SÉUæ*'”³ÐýHXgM“´LvÙ©w8žxår ôwOâáM+pÈ|ÁA…ò}Ô2úlتš“íÊZ>¹†_£ž)O…éêÙ¶3Ÿ7¬éë&(Ÿïú»óÃù]…ò±gƒ~ÌO‘j&œ?Ô5ü©E å·Oº'»_"?±¶oE`ãå ¸¬Á±§vNaücÛ¯ ˆ:†7éˆ:¶ ~~Y—}u¨œ©*¦è蓤&ÓBUL ó%†b&A%dhhB#ª1,¢h -P§O>ŸKá…œ’ÂäNWßR±X„6»˜ø¿^Rè—ðíGÑ0+Ã8t@H¿ðð&ݹx•ê›‚ïÀ™|é|éÂ_×jÛ˜Aý?}9¬Õ—úL_ƒÅcEÑÅA¤³FÁC¢èäWÔîÚÓ¹@”&hþ‹ë^A4C¡;Þ¤ú¯Ò'KO–6œ$éæˤ2ë/ÿ™yÀìfà…5uÞWñ}ì}Öyßuß3[ýW»ÀIœWD’It‰ž0W„+ë\ï1Áuà ®{•`ë*6¯bž`m7!X] -êÀ>Ï ³ìYþŒiÜ<&L0ïWÏÂY³ÑÏ -¬A„"M¢¹ªZØëÜëù&ó Óaçáêqëuézõ¬ûoÜa±#KUœìF§ïÀà²Aû:5'pc7r÷i¤\þŒù­vŸÙûþžÝŠ³ 9‚Ýšu€½¯¸ÒU™b†øcÏSG.ðG´ôb¹:Td&ätI.ÄZÍv™¬“[†žP"K(›É"C³+ U2pÑ(JŠÜP!×:(/Ã,ÈeIâ¹Æ³öS(ÏkF{Iö„‰¼ÄÚ»bK˜ -åObtÎœwÙœÊê5?¥`. k s(à‡l05kÃb•mm±Ç1 -Q"©ÿNß)*½~ç‡ä”Õöö®'Žn?ûÕ®ÁgwŸcž1•”~]*Ý.-þç64ÃõðTï»o”~_º0ñbTƒÎ¿6ãåb©›®(Ý]‰a¡ ‹NzKך¦Ž@zøÛÒÝãħž.ÿ›ú#5šA -mÑ,Æ ÚQ-Õ;?Å‹Siݽwõį'à·¸‰†7S7Þo¸˜m¸ÿ¸Aˆs]Ü–Š-âæø ¸—? ÎÅ/Àëð:oR9x,}–þAÃÍ4H÷§Ÿ«Nˆg—à…ö[𣴯êO¿˜¤6ñÈaw ¤>Ëm1ñ¯$Œª<©$•ú:¥^Vê#)õ¢úŽJÑêµO}I}M=¯¾¥þ\ý¥úµ¨©PMVò~ÿužF|’ïåòßåÏóüþ7¼`äÝü!žª´ó”dùòÅÈÞÆä&ÙÆF$i%f•|Ò3ÒAé¼tIº%q’>‘%J’4 ŽIÈÇ!£µÞWßXŸ©§ë;#VÙ'#ù4 -aT¸%Ð~‚°€„|GÃZúXiéá4JÿÔnýïêúë2e7t+ ·¢Ö(£åØAÒ¨‰Ñ˜~f˜¡熶íR6ŸÐ#%›SúŠ¹ùœò‹,Îææ³Ù‘^Ê.ÌeA&cO(¤Ÿ¤–ù".â¥ù9\´‰‰lnÄ®["AM"ïò8eI¥@V#—Y}³¹f’ª%²CÍM$t¢míž S´•P@6†!‹×æ&¿à…5ÁvªÕ °Çì…†Úè¤è^ÉÌ«þ2¹àH. È s -È‘6™ Ö„â±9 …‚Äã•úQaµµEVÖVY¥GÈêÍa»"¥FÑæ‹cýÏ`\Ôê[çò„6'3ÛG>Ìײ˩sÚtë_6-´Å¡P8mÄÀÄÂ--qÒ¬—¿:ü³á£ï=œ8WþÂz“êææ–ðÆLïÒÒÑܽ±³ÿ=ÞÔ²‡¾0Páq#yéÇ¥áÑ`rsû›Íö·ëuÈÖòÜÿ˜.ÿØ&Î3Žß{çÜù’³ïì»ó8ñùοbŸís’3ù‰}„†¦@€‰«ËÆKÇh~L@)Û(´¥@+F×MT¢)“JÕv‚5”¦ ¦)•˜*a: ±öЄª)4¨&µ){Þ;ÃÉïs÷æüÚ~Þçó}¾/µT,Nܱ¶Þ£Q‚E#ìÛÑYr6~ÝD#™F7Ê‘Yi­²™ý²ÝÞ8="¾/¾/Íç¤3ÑsñÙ襤@²HPÞ–qj¤†®#Ò…$D"U”CáÐmòý3”jbÔ!WïE^áèWp´"¬ÏäšB§àÍ'“·@#ø¥…lé`êÏáx¦M7k —ËyM&œè>dwF½ªðv´õá“v£››úgpÝ3aÛá"¶ ià.ióC–Ì.œs‡±t*]§.€Z@YÊÀìØï¯oÞuõµ÷»û†Yp.JQ3¬kYûº;¡ŸìDÍŸíä/Ö÷<²bS%î>öâ>½€YY ¬ +Q"ž³âoxÞñœõ|pùý]n"*DÉ ’gÝ¡ãJt6Î3 – ðs§¸øöGnýEŽs7)3hƒîTSK„[p“î,Bd(k'Ð âÑJDžB5e8Ld8Zäk•Q3ÈqcÊ %…RæÅ’ñ[PV\B¸Ð½'ôPDqN!ýKçn®Šqeîîð2'Ü›ûÝ«:È<„¦MËzÄD2ž$iª-I“´7©‰©4‘õÀô©i”æudS’µ)1Æ=ãâ¸6ž=e\0èqïnÿöàîøxfW~_ð`þ Ï‘ÀÑÜÛ÷rçrÞçù>ïbuĦÛpè6êtuºñê#DÕ'(7”ð†§ -©ÍV¼$Ú;þ`Ë»¨Oiw¾û›mŽ-™}|ôÃÑÅ£},Wxyé–d(i˜ù`Ûº Ë¿úd«¤Æ\êð/×”§öþáÈ­çÌE¨yK µ%;¿ï¤¼ùÖïÞM‰* ªÀ˜LÄPÉZGû—IUiL•¿Ú)1ÉÆäÇäEßeò2uÕsUþ7õOãnôR”Í5ÔfjLÛAíÖ^ öyozþ.³Y÷ýr³¬ŽË æ¦ÜÕ†X€@K3¨ít$%2à·£Ó\°Í5ìnÀ -kfài„7°Çyjòš8Z!_‰h6´Š¶A»¥¹´X†G -`Ø!ÔɳcÔïÄTÑ´«†ƒrª H«u«Xï†ç«70ƒºŽ‹ì¡MáÝyìãïVo áâ„]!Ð&[“¡`8HÒ-~%J4K(Šú"Q”apê"‹í¤Ž7y©NÇÃè‡ýcÌ°ÊTuþ>»~ð»ý»µå3;k[ÖÌ¿{èò¿âI9nª}èÞ¹­^¼6ptÏÔžó7‘üão=«ø;GŽÆ!pL€³AéÖ“–hQI^nä‹¥“;6Â6Ãß,¡>’ŠÄBb9úÄúVRhâ+¹äËìþüë™]gÙ2g -·÷ilìdKtÝ[Ñàl3lFéV†”WÝ/e²'ò'7YC‰Õ“ ÕË$¤rÆcp¶co†b/[þž²•J›e+ªÀ ‡ÌbáOûCfy†rY²$aD¥Ö®#×j”e´›Ô ÕbqPÁíG f0ÕÊÙ¨ù+8ZðmcChh(Ô;s¿fK¯§õv„&M* 2pw£h+“€“S¾b ~@ †TO -ö¤€xAHa†j°¤”Y„¥Hñ¦b’¦¥¦ôþ<fsV[ÆÌaÃÌçÆr?ÏQ«rµ™Û1 vÙvRÀí~¼ßÂ\(®óÕ‰¯¡Fæìi]w@îŸ×ûÁsØC×=±d)ª©Ìé¶èÎìY¢ ?; éÃJܪ˜ ÃØTà Õ#~ùzìj¬#UŽ‚uã“Ö°wvuØ ®),Ú]΀ÇÎÆy¦Ã®4ÊiÙõ»ùkÔ7Ý.†ÆÎ/¥'ó »Ê¿ýtåÄè{ÞùYmýàS{øã}Ï^?U]Ú»jå‚þUùضÍjÏöß¼rŒl¥Þ|¦½mAߦ×W7ôe²`½ôÄ+j{ûÚbá±°59¸·Ø>õô‹åm3¿{æØô¢âWw|J©sõÒÅa_4€Õ‚puCÏÏ¡kg úþíšz -6½ËJfÃ’\U¨H¦¡Ð)ÚÅ{È)Ar´ÿ¤÷¼—Œ BL(Þòs˧¥Š×Ø„â‰Ç[Š:C~f}/Þ–Prñ8ŠÀ[‰Ðf£©ª×ëit+,b³’h©‹*¢5ø¨)Z K¢µ^=½pSl‡!݃ž‡AKÀÕ-Z‚ϼ$"^D1ñ’H -"ñQÌ¡€”©iÆq&Ê%üC¦a);Âjv„í+Ù1W°£å8 -„cã²mi{ -¾Øí42ÒÒµ4…§¦»zM;;v„/e?ʶªf:œ_áX\YP¡ MÕ~¡~zIƒƒÖµ‡ØôƒŠA· «`ëgOSXºPÕv -*&¸©¢ÚŸ!q/8#çN xàÄÖk…y"BÅ‹«W•*ÖÁò‡ª“P³:”¬ocÖá8)\ªç³Om4ƒåñÿæÀÀÿiøùÁu?ý/áåÛ¶uÅq^R¢”t¯hê-’’HI–JN*Yq­FTœÙÊÃŽ·¥YãÀkº<€-à$Ûº$èì ]ƒ ŒµC¾Öë>ìñ%^œ¦î^q‘fm‡164M· PcH€yÛ#ºYÙ¹”ç1`‚Ásu)^ƒ÷þÿçüN®û©VfSD ±î]pW+Óñg·¯òÙCçfZ¯­8tÝ‘ŒF?üZ²ú™–p(’rê:Ÿå.¥ìLSä/5û1F`âÌŸÍ 2åÕ±Ÿ™¸ê'"‰ó!])L¦¼ºê§-¬«ñ_¢¿êóð¶þroùx“Až8/úÝ.ºq˜e\ÄźL.çñ`¯êe½ùpÈ„åCt3ž¬Ð0—ÐÊVì -YÑ,=åÙš!« 1•Q…U•ÊŒ2«ØJJ]™†Á‚rKáå‘H6²²Ü©@õe+“X[]@ë5¥·ëá}†=Í4Æö›æØØï‹-ÇE*nµ³&Ls«5v°jÓu6:Ȧ`˜wnw¾ î,¢¿™l"Ð÷ûkßMöû©×.»¢B&žJ¥´jüiï!ïIïóþ)ï÷bß÷žÇçÉO£½—ðMr›H,æˆ+»E{;Ý™I¤äsR®§„ÛÒNC-2Ø‘—B©´ªu7•åêµk×ê«×êË´±ò`iµ3O2:S$:)öhvŒ‰,ÇŇ WÕíTw0Rƒ9]Õ!°°j€Hj@ÕUMÓòºZÔ4Îþ6 ô°O *> $QŽK°ö*rœ`‹œ=*SdÜ.Þw#^”÷+ÀŒfX×µ`ÀýqÏ?zØÉÔ°ØæFrÍ£‰¹œ¹çÑ…‹¾äÈÇ`¤˜Áø(–U™•ŸW3*Uc>Ÿ£2 `ÚRn!·˜»•³å"¥ž_!ŽI2#h‰bP8* PÅÝñ¥Õ¥••ñÕ¿’•ŠP(|D†ÉÊJxu‰ -ÁjØœçŠß äÛ¹b¸0NG㌿/Lë" Œu}pLœ5gíœu…æ$ ƒU«½P@3ÉÏ;]Á¶=­zÁ9 -ëí’Lô£ÚØ‹GþòmÀç–œóoÕº·´äŽ_ÿóÒ'c1Ý™NsOLjýæp -Ôö…¶ ÜÿËÅX´WÏê =€ÑDêØ"š¶3äN˘k¦„yô˜çÖ-¸ëß߸o<î½/QãÁÛïbþëÀnƒìˆ©±HUÓ­T¸‹©1ƒj$íAõV#º*ͳ^JºÚ SJ5tµ¦¥°®viš™E)]Íγ7ßÔÌ~TÕÕ~›ym«®jš#eô&ȦÔ6±)GÜn›ƒäkýÝY©ËÝ4‡,{ZI•™æLs¶¹Ð´5Añ>ŒUÌâ|4%3Bëãk‘+‘ëÎŒLGØÈd*_4à–aÝ2®× Î4¦ Ö¸ÃàªZe«ù­ åTù@ãVƒiÌ6\ .‹ ®jγŸŸKÒ‚Vhó¸UÍ,«­®ÅñZÛ ”¸jôC7~˜,“û9ƒý[¯kVK¥—6ÆdÁkç{2ñÌF{QA¼C¢ -òxKü&ÅÌö=§LQM8] §’µ«®d–I$D+(T¸³gž1õÍ[M–÷èž²ÇlÞì»í»#®ÝÂBÓ¾™ÝÍïö|ÊÛhOpüÄ>«Ä6ARAÙÚè9¨óó÷þ5EÖŠPz¡—ùçýè÷¶ç!Zß±ÐþŽ;÷Iç9ˆôûÏ…>f½ÜC“¸þqÀ*áÿ_Œi¯bM9èÜ#~øÅ‘±ÓÉÑWGŸ;idÁç}1Q*È…g ¨ÑŠg ,•bÝÉRî)Và~|fÏÀž½c£û¾s¾uöXj´={½ò¶d½ÞrŽ¦© ´ŸC¯Lšz@ÝÙr¬óVZ8Æ+-´y± -¾(°6Ê‹·ßú\<2¨–6﬌Ȭ˜æ¹ØÜQ.ÀW€"¹èã+bdׂê#IR¸€¯`'ŠÅ%]ÅmvÌ/j)7°¤ÅŽ ÊŽ ˆ² iÉDcŸ;rÄÎÙ±yôìÜ"BhþÞæÞpb˜ï¶h2(NJ },¡„t]b%Š–`¥D±R2+½p”¨7$ -˜eK‰²¥DÙ’HH¢@‰UcÖ`KÆØhÒèФa£C•F‡"]º´öUñNÙÉf3÷±2ƒJ™…Ìb†Ët°2ÓÁÊL'õr&²a'-š$à$̬Œ¯k˲#éðäJá8àdm¹–1e¢Í”‰5¦Ä”)kL‰)SbÊ”˜2%~”)¡:=`eÌÚQóÿòãš½Ú|i×þoJ$™­„ˆXˆîÝ‘­´²yž:¼³ïõÖŽYH™ŽD3'kÉ3-áË›É6sǽ%îMС—I¢=føÝ(Êzø§/ãEŒ#”q¸œ‚lÚ¬ý†4j33…2¶![T£/´³b…¡v¨[a®ï©2¦Þ](/h‹Ëh¦v@£C»©½¦±U‘ÍEY… Öµ",Mãe¯,DR°ÆÔ¥leóqš9Û‡7¼<>BÖØÿ.Õð2Ó> Ú²•·¡$I³iUI(,/uºXžÏÄâÑx$ÎñØ+fá-e]¢Â„rù=¾,R8Ÿ‚ºÜ!…‰ÛCY¦“c -…|!Ÿ‡Œ Épc7êCÛÑvrÊcŸà'=“d"2ÅO{¦ÉTä=ö·ª{Ò1áÀ“áiÇ”w -O‡øãø>ÀD³“D›Y¶RC):ÖPzÖ*(=Ï jþÃWŸþðKw®?±=äšECÉz¥L:Ê]ýÖíï¾ûòë¨ûêû¨04üÉ툤žz%6)ÿ—íªmã,Ãßw¾;;¾‹ïüÛ±ÏöϱÝÔvri’&Áצÿ?ùéÖ@Ö†0Ú²©C´Cb-#c-Œ‰þ1hXéФR:5êhJ—MHd´B-ÛD3!XŒ†IÛ”˜÷=_»H`ëýžïçý¾ûþÞ÷}¾ ž`vy Šð ,ÒG͈¯è’xâ$^…—²—÷UàúšâD2! ¿à©Ú¯3¦6;½>x ð]x§GÎÑœ‹úʵóE˜îê1ÍXá@y®Ì”Êfy ›–ÔûL‘–DSgÅ9‘#¥>89`|ã–±ˆ0L$…Þ|vº!iáÕ°‚æ0l…?yOÕR-×T˶jy…êûp”,Ô^ h㥓úª†D$SÐãz6³ª!—¥z’|´9K›3YBì£-Ô‚\—fV6*& ‰ }bûh`"2ÿŠ:–(|=pB |¯á\â\ú¼v)p9=¥]üLómRg;ã gÀ@C­+-4„¬–<|VÏZç ¤í™^ —6-½k±&úÍrëÖ¡‡.jÏîìmiúìÕèÔÍëF—/n12&þŒãOøŽ9º%Y|òã§Þ=šŽ^<Òyÿíw=ƒk;!Ž/À ÈѬét¡SˆrͤÀ!þu:¦›ó>qUi³ŠñD­Z’-4³!è¤p¦À‘z¯!ÅI‚䔸œs< †Âa’¾ $,ª¾¡Ä-ªªjJoS\u·Hf¢<^c{Ezƒ Éñ‰¸[!îWè(aéèKgœsη¸¯˜ÉIa^Žy5]»oi+†…±¤…fÀ2fÓt,MIZN3é?æûv[w«ÆUáÁ›baAž¯½$Á -x9œÖåÀ»A -Ôæµ@± -wÝmí™­Q> £eZ¾2«[aäĺŽÞu«Ûúœîúx4LR§XìXvö\n½äxáon¬ônÛÀò¡tåÁ/ý¾£SŽE@ -:0Ü@¨1Êa¼¬Î3oÀµ0Sæ^¡”+¬\Ÿ ÈñËB™úä¿ËÈÎœœÉwÈkòO gÕ³Úeá‡êŒpM8‘«wå‚âfa»È›‚)2¾…œgJ1îPSðUžÇ`N7š~rÞW„ -£x§Ð DÎÇ”h+¨œ‰Òè }ÄLD·îø|œ^púºO°íØô ºÇ—š©¾}­.ÀïÆŒé® 0»IJN1)< AŒZ)íÁòZðߊ‡z¢’A‹F¿1j|ј0®¼ás%qL™Ý’Kq1.:×réh® '½¥&ÚdEQðüM‘VtùèñÇ ;çFÐ/üÔ•„0êBµ0tq™TÅÕT! e k³#+†ˆ÷Al¸×5•„²–Rc¤> ýq%Ó0„…0Š…0âÕ{c†ç è¿Ì5›`“½È1HÐå È%@‹ ÜU‘o/ÂÔ©¼°¸@äÛ¸S*šno¥hÖIÀZP •jZøåL3L L}nº†°T ™f !Pú­Y™L3ð’ÌLõ½ip§€ó/¡'n_û1»&ã`-èÚÀ·Q¿jQiŒXì=gÖ¢:Z1„GëÉêmXS+Ú™ïJéžcërkIªôêK©PJN7ÿ`S©§ûásÍëϞܱ9æõ…¯.¿zêáv-ÉÝüÖPßä@^h¡ÇwåK›6ìصïóW2’¤¢Ó«w˜Iv‰Dȳ¦ç´pZd¬DId†^‡óaGðCù¤PLÁ!ª;àÇ õ˜qN¸.Fc”e‰Ä)Ãåý¡àc€ß„Ý÷ã•’áýVôÏúçü$ŠÞ. ì/ÅE‹ì“!ü@‘T–æG*ðxCZ¸ØMå_•KtœŒSokPµbAK{¸æXÚ¼*ø’v:óæ›’.¯[›¼>|Ôë>òÕŸ¬g—–§ö-ý|°ßšÝדž¤¨Ã¿x ×Z©Î³eÇ $MŸy™h0»Kð"Ðæ4¦NŒ‰yq«ÈvŠßo¼Ü8ÓÈþÓù“6…z#…‰Ä¿ÂÉ~ö-'­:){NU%Mñ«jBSÒªÊñœ;r Np $† à Ÿ·#x‚G‚ÏãçäóHòyä÷ÛažƒÖx‡IÚüð]/`Ä íAG–üˆ¾FžÜÃæˆh­t¬u}¼&ÀÍÖœþàq~+çÄtVßù3È-{n[ÿGp^+…}ŽVÒøˆŠã3¿5ï"Ú×r’.©.ÁºæABì~"Aùo0ÏAîiÃ2ˆÇ¸Kìs0§EÒm~’¬†zƒ)ÃûYÍ\$|†ÔÁúÝ ‡­»‡wa?¹Σ -XϾC¢Ð¦èp†Wì}’qo Œçû_îË=¸Šò -àçîî½7Á0PBx$@HšÐ HWˆªT12&%WsƒÆgÍ£aBÜ^}»Ì‘|äDß>ƒvêÙçdÏ>[Ûe@z¹Ecwªú)ïŠz>;[ã£Æ8‘ç4Æùóƒ2¾~læ?üÕÄáC¬ùu_ȃ|Æ¿áÅâ°»ÚÄùîòh™»Üâ.Œt×FÎ"ç¹+¬:wa<§:òE/–õ÷s©É£û%ÙÏ£á©õbšæÝaárS,šüÍ9æ™ü–O»‡ú¡ñÁHªU‡^s¤“S,Õö+bÛ“É›ô;CˆÉ:¶TØ祷³ŽX÷C÷œý¤Œ6ys‚B.«s/˜ý4_!µOÏ©–1  Mî]àÅã|ýöIIIr$ÇÌ9Dl:.©ú_Œ*$ÓèA×>FAÅ^ÑÓÒ×iôÐO1kþ-)ªÕÑ'tËÍfÏã&žu6{çoÈ %ÒW*¢G‰™ú®…2'Ù’Aá&÷”—³ËɧåöÓÔA)"ÆþIŠ],éäÊ2ñÎ#è¼–¹Û½ºB%qßäûóÄ*l$¼N*M=¡cß¡îyMÆ+ιƒøXBì_.½#è¨J²Œ]ß{7ýå¦>Ñ<¥u‚úËhI‰Ìa=~aΠùF÷lt[ŽŽIêDny@ºPFj©uc¤ÒÖÒñ‰6z}½c2Ôß:%³t ›=Oð¼ˆ[cò}±äÛÏ’ß'Æ¿„=Ü*£­e„õ}á$S›âù[2Â~6¡ƒ:÷¸Ó“>ŽþÃ÷X÷ôÙ…±2g7v°šµ}xþ›Œµ÷ʈð·iÄV‘Çáën’õös²>ÒUÖXº›ÌþJ]Ë¿ÝO×A/õ¬>mžy—¤´yÞqŸ3~Æ6Χ{è¾fÎ)v£§·a`L¶L³6H=ì´Þbmƒ¬ =åîC¯e&$¶•¡µ0g¥ì@AžfØûá¼3]l׿ŒpUP¬Wd¦JÆ¿…cþX"úž¶úqÞu÷%¶ÃE2R±òÝ}J«ù;d˜óubl¡»O±W Ò¿M"ÞŸ ëíð Ùì,–>v¥ØéÓàW˜ ÇÒkù׊ÖhšŸ?«ý®¾ï*øªÑÿNjlèu„öË}¡¿»—ííQbméeô¹ƒ¼ä}'úךþÀ÷ÃVnSûy¥øíàwí¨Í¾óñíÀ'Z$¥ŠsŒùl'=*¥JDm,¿u;þÞö¨’aè©Ì©â,'Z·‰!Šµ„öÆߥF‡x»ŠüQ³OÝf)èzŸbà> -v%c•fþíJ‚^gª^í]kÖ›ïãÛyðû°Vœ×É/ïH&Ͻ‚2Ñgƒ6ìócI[s¾QØÞžÿOà;€&hü\߃‡[…®`jÔEÔª÷á‡ä‘V‰\yUäjÏW‘Bî$GôBþ -è{ -9y fìCò%{KµÓK6{u%c-S˜÷8¼Û§¥ÏCØÿ,üÖÑÿ¨†~ ó*<–1þvlmËÃȵ´/#WÀAú*™óÏÏÂlžÉÿW/Á(ˆíw…yW^Òz¤{èg+Û¹\«ôï¾ Þ!®K.êXïþ÷ïHúw‰6¤Ñƒwo:•p÷ùÔ;Ž/±ŸäD¨¥³¨)3µŽÖZVëg­}iîmÔ‘Þû»%ÈÎZ¿jí¬õ+ÒÜï§e*z?—ŸGb«•/A➌eΟ±µ Äž.¡ÝîEjËŠ$ÍcpòÜ…˜ûjh¿{yˆv¹,ÙÏi~lmc[ç´Ïµ}½9òrêùüþjàxG¦ÌÅ×KG¹û†sy;9:1Oÿ¯m?Ïû$ß.EJ´”s—¶®Kƒu@GíŽêÜëm뎄ö ʧŒ›v°.ñÛAZ·¶½X=Ó ó øÝõ‚ŸÞé,vøþêŸ!àÇâþæµ#«dÜåËÐnD ë½{WÏä3÷›ÈYIW¥(éçzïuɱîÞXÌqgér}èWÔÒjÔiYC;J,Ö¹3=fudÏA»ÕúÜÔ‡èÌœ}#ßâ)€H…`aü[s÷ä݇í©Ô€ÜsíwÜ‹ìu±½Z°=É=o™Þ÷hw¡Ý…Øœé$®`¼V¶y²q| L#fOFfEÖš9w26ÅÉ‘*òælg¤ŒwNºMÄôéš"c¤Ìúšäi¢o(sÇ‘K{Úç¥wô»ò•p“{¿æÍIµ2-’K :¡/Ç9‰ÔJ%kËÃ%äªâ|6ïñeEdžL SN19oµõIsv±v—Œ·ëÑËNIg¬ÈI&¯w—áºÖù”…ÈO¬-²Ó%Ýé{þ¦ IO®’tîjS’gH62›3ä%7IFÒRɈfH™æ+?¯šœè=S»æù^ýß´Ó=9ÉÿÏÁš@ÏÇÙÒ­=nSâ{ýuÑ’Î%Wòn“+Û®mF}\«¸çÈñk½\&ø>Öe3¯Ì“ƒ¹°všêÔèg‘,±ZÌÿˆåjrvx>ßÖæ[x:ÖYþ»°ÉsíÕB~m™ÎVìekÌÆX?&[ž òFÆ@C²wëÙÜyÎ|Ö)¿AÔΨ•=§¢öeÕI¾5IrÑÅ1h±ôÅ®÷Èp»ZwYSHý¢gìÆùŽPÿ5I9ïï›ÀDÃpìx¸ôå̹Šê˪q×虬?ºÖãúll)Ûèô‚”³w>{§²gš9ë8éfì3]ÒôûÃÚêSF^‘ÉFW•œa7ëõ?RSñŽåœ÷=§³ù“ý5‘“èý²L‰.åܼw"~ñŒdGâ—ÿá?¿·JwûˆŒ²¶+!´Ì=a%óœLŒìoH{9r.”J£}Iù[8g°YÎøVŒkO¨?ãÿe¿Üƒ«*î8þ»÷œsob€`* T"2 ÁG(Ïð -DlÁ‚T@0"ŽPS°µRéhÑv•2-¶:€E°µŒ©€êPmu¨:Ú -"¢,ÔÎ8uFrúùíÙs¹Ü¼¦âŸ›Ìçþv÷ìãwvÏþö»Gô½múÜ(MÙ0ÙhˆûØ,ä@½ð¸W"}“ó¥(Q‡o;c~0Ž×Nʇ6ßµ”i{ætº<”Ǩ|h«¶>”«í•-??ÊÕVåCyU~4W¯9?š+ï彿?šë·"Ê+Zð¯:Ê«ÿ?š›çžùPÞ³?&çCùä|?ˆO;á%î¨'°Ü%Â%ØÝØo`_ˆž‡¯Á›ÃÖ» -FEè_È6ߌhXeËל.3åôÝðNô,G5FX #¢±´mío;O™ë«ñ7×÷Žp(OÛ›±ê#Þkë̱ã>ùÝÐ;/ª¯ÏõM»çNzQÙ©nØz—‚éÉìÍRö(û4YÅ’dX¥½&¦tM<ƒe?›}œ ²Ó"÷ùûŽ(M_vг A¦sÅ\ºæœ5Þu2Œ³·Ÿ?‡x÷2ñkÏ=)2:îsHü½sª¿úÇäRba‡@øæ¶s¶-#F½Í=îÏÄÏ LÃ7üÓ1è_cûd=wuçFúé†ÅEï£ÉNq. “BoˆHj¤´ î–îÙóï^Ú½%b[t½ósÊÆ¿§e|៺MÆ3ÿeñرÖJþ^Pc OÀó°~+òe%LTŸÕ_£Ñ.#Ö€þF;{‰Ï¿à<ÆÙÛñã~ÏvÏv]¾®÷nÉ÷\ø–߇]Öê]¢¬)¿9%u€±‡Ik¨+–2û휄O­=Ç4n)>*ËÿM¶¾iÓè;'ó ñšì3{´4½˜19E8Oi®Œ 45?º.wSu›Z ¥Új¯÷ƒNèâ»ǾÂg¥·Æ£[ -Nâ«;^”Vïí·Úoû¼\õ’/áqï¦PÖÝÄ„ðçjÎf`¬»,¯[VZíW ”ÀÓ°)¯ú Hgž¬³í5Nzßá.ò,mfJ[o£Œó>”ÀŒ£}ì_u‘w#e{%ð*yoåèfyö¡¡?d> ý:ôE·®ÅÞ ½íõäùGX­S?¢ÎK®üŽ²™ØuVŸ—ZËl«ã·[ݾÌjzÕó,Y-¯:…©×Íèú5vœ+±³°ï L…¶¾Ö‹ëTŸ®|Š¶^ÇùÚAƤg1‡W…õ‰Oð½VÚ±¦çÀ`ÖZïC;¬ŽÚS`ù“É]²Hñn“"à a½·¬ ®'Æ} #ƒÑ2ÒßÅ·ñdx0E|;,Cƒéè?(ÑO5XßðëWã×IçÄ!|9n·¶>õWéW¸’uGe±W$¶É-€M\•™´ª·-‘"Ó}kÜTR®H­EG®5ÚJcO m~Èž«0» èùZ%—Z W…_+±ÃÍ7>NÊi³ÊîßUA9q…ïÊêÀ:Øš\%æ™\ÇŒ”®¶­Öá>ÞeçoýnÊ0%yIø…ô…t½’óükɳùwWÐ8ÏZN±œ±®ér¥â¿G=¥VyÜSüZÚn=Ÿj'ý•ä-ä×5‘/áÞR€¶Ô¶3ZÏ'KwÅ›ÆOkœgüË•ì{·’gW(ñ·–ýž›{ÿ¥áQw7K[bxGÒiÊÞóV‡_zƒÃùŽÚ£?3ß’H/êuå,™z™ø;‰øý})§M9±k°Ò¤+mUÜï&©–V­çƒÆu«ª[U“z›ÂkŒShû¿Öh¯ßj }6ÔÄÙÕhþ£ì t¨ÞÑ4™¸ÒøQÅŸÄÏ”ð‰dU/ˆ¥‰Ç¢˜dÒo²êlŒZÅ( 7S¿Kò¡(^yÄŸÄN­Ç<Ʊêc;%ï´qˆ=aø)\3u4XÂ¥Ø癇Ý&^•Ú88v¤õÞbôÍRÉè¤ÞÀÖ´çÿ6xöäØc±mMÚ6Ê;Jþsïˆôà[:ß«'ÆÌ0w·ÁóR˜½s‰tÔó:õˆ¹_Lι‹¯ë£w=³Nzw*ç[Ü(=òïþ0ËÙ9ýQ¬çó´äØ9á¹ñþçšž¹«®¨Pí w1Õ êñ6T”sß‹ïqæžÁ_ô•Jï=¾Á™J¿—A zPœ ¿Vô;‹¢­Üt–lüê$j#’íΞøæñ•™‘¾V¤¹)ú‰Hñ"%—ˆ´}©·\äÜÃ|çGtB)ž·F¤óS"݉dP™=þÑyáßE.ú“HŸ½‡Ãáp8‡Ãáp8‡Ãáp8‡Ãáp8‡Ãáp8‡Ãáp8‡Ã’9g|&#äAIIRÚI™!R0.¹Gò"%²•_OôïFó«é´¼F.!ÑßàÄP›ö¤Kb±Mû¤ï³éé'm:-7$öP3ájŸÉJ›NÈðäZ›NJIò/6íQþ›öe¸WnÓ)Ò56?Þý²I22Pð)©ZY(ó±“d‰Ü ËäN¹Å”Œ"w+iýKù"S£O®’›øÏÈ4Ên ý2Yjró±ó©}¿ó¨YËóŦ4#“±·›ZK(›KOžê“¹°ÌŒ1:úìV©£l‰,ø -þi¯7›£vW“[DN=ÊÈtRsM.ùfJû›2¦ï…ÆÿŒ\Oî{~žÿÅÈÿ«dåõG–7“Ÿ‘ø»ÔaÕj»Úm -å5µfQuÙaC÷$¼ªŸäJ<àV `’b©~þ•¸U„ –e ,ÇÊÒ·žEà]$¾\Rýví:S4ÛUÕf^h¥Sø>~OÜJ8UÞÜ"·¥J婸ìNŠ,>²¯Üjïm+Ú]nnY nŽë‰|°€,pX¨ÞG¢@XT鉸pæE¬ÌV÷$êT/z¼rí^¢Q€‘.ù²ŸìQ+±+•¤S+ˆªVqÒ¯_Af·ÉJ™½­A*ßaʾ~ƒ9¥2:Bn':¾¶Nö¾{÷Š±sWÙ°#õæ\¢J%d *Q.²Ã æÒóðöñ(ŠheïÙÞÕ˜½o{jL+áeï@É»DJ%öÉáyýb"vÑ®r›^Ä/ÈŒŒƒé[€ˆ_´kÖŠô¯rOŽ›ã±Æ²a{×™©Äjö[ÔóKö ýº ú èFè ìE¢É:Ÿ°=^3ùÎ#ü<;Iî@÷Ø)¼Õtö${ˆÔÉ°kÜ]žçGÌD»ÀNË!ö i„ö³ãÜÔIö„¸ì Ûéõ½Á½kÌ)ö;NV#ê&¢juÏ Q@¬¤h;5³¨fE,³ˆmÑQ£BÆ$[ì%ŽD˜ïG,OÖ¢oš=LÖ@Ÿbð5zi’ýM†½-²`¾s¸1BlÍm–NvNÜv ;~KÎöW{ë.“$¶²o’@±©7`Ýß,lÖŽiG³€£Y@ ¸´„½‰ž7e×I–½B -Àl)Orìài„Âæö5v;áÄÞ)h}ÈvºEe§¹¿F†xë›!íEñ³â‰ÌL²oÉ¥ìuubÀo¸³[÷ÕòY`à)qS,Ï‘;ñ°Ü‰çàâþ³¯ËÁËvµÏÌáô;àfÀg€«À" "¬kè CxÊv{LÏ$»G>ÀÝq}ŠíÇÒ÷ËÝÚÏ×dÍûV ÕÃë6™Ï ƒÔã3ÌTݪƒGõÓìÓ¸?íìïÑQûaŽ¼bà!{W³›d‡ä^âz°ÜÌkn“Ƨ¸³|¯öØU>QÉ^¸Wºeó¶•G’EìÕµ¦Ž{Ú,W¬ Çׄ£iÂs—‡aÚ^?n3åŠLÒŒ€Š3Æ5æe‹‡íÄrw’e€álw’%¯v'iÎÏóÀ*ÙÚP´Ç0C¸PdŒÂ÷‚-  Èã@ X*È4«Ç<õˆŽóÀ0¨8«í¨c;úüÌ ïW¢“±š•É)9šc95·*çÍù*­[¶›Ö1A ‚ ¦.gÖ™w²˜Ór¦œÌë4œ´¸\âÍqˆåw4Ç_N¾ž|7ÉüMG¡‚N'ª™F¦/w}ñ:›®Ÿ«_¬gV²®ÙlêT2JN9£¨ºUZ•vEíd–cg˜ª³(kÅ]P»\YWÞÅb.Ë•r1¯ËpÑ‚kÜ5á*¹®ºVM8JŽ«ŽyÇ’cUÊÑåÈ:òŽ‚cÜáÐ+¢­–C]J졯`SÇÁ%ypAZ^ÙS_•~Aú]à¬ô-pJZApLX@¹^F\\Dœðƒà˜ð Þî×ЖJ¯Y±¢Þ¢$¤,…”«¡ù•B´”h¦³²ÊYT9+«œÅÈY9÷,ò‚¨vFÆÍ nFÆÍ NXÕÖÎJ˧¤Ç„Egx°É“¨¥"c'x ˜‰‚[ŒôtA[tÔ¾};~ðé(ߊw$$P–Me٠žm½Ù™ðÐQ¤EÊQ$ž´ -o¹DGø^;Â?Y–æø\¢ ¿¢¢”r ¤<&­(¸UZeŒçC¿^¤Oó°b—… IÔP†½×”É?•<&ùÛ’?+Ùc¹‚Ú;AíçAíBPKTÑ»HÍK’_“|Ìr‡´?…´BÚùv.¤M*7H›­õííwír@{*  h÷´Ãí`@¤ -ãkP£+÷KÞ`ÕÚ{†ö{Cû•¡½hhÚç ­Ù@¸r ¿§šò=Éß‘¼ãr£¦7jµg(ÞLʽÜCœ“”*÷UñH‹^dN)t3OnlàɤŽ'@Öóä ¤†'Ïê 'õ(—ð±¢S·r©Rh5<ŒnWY*yä~È*ù„^T>à‘ äï<½ò.Oo‚¼ÍÓ·„<«ü…¤)Ò(æéÇ^y„EZåU²•þZäÉVD_.Ï®UÀÌg7“U‰‚õÇÅW؇*g(UÌá] ¿…1ÀѶz…½©l± -< -eë+©ýìrò»˜Ä@³uŽ-ÃA.€M¡x-©ï±Ü˜ÈøœzŸÍ‰3˜Eq¢—5¼Tœc3S@“ð\Ž‚éÈØ]¬t*ÙÝûìÕñ{¼…éU˜o¤GŸ‡7ÃùðB8ÃO…Ãá^¹GŽÈ'äcr§,Ër›,ÉD&Roõ°‘æþzõvDPt´áÚ&ö WX°'‘¨,Á?ZîÉ)™ówœ›Õðáœ;ÁMW>ýºíQúŽCM·vž˜ù¸ûÛ|¢J;ÏœuÛêö˜Ä\ÈDìJoW)Y°«ô-¶cnOÖÞ'”>³}=†rfûºã¾©èTÖýÂŒ~Ä’k®†Î[#Êù#ŸúÝ÷ÍyÛý¤ßqGqsØï˜îÐ||ÑÞ—V¥‹†¾/]BáØûtYZ5æPO—uh“‚F4éЈ…hÒ"ÑúŇhÔµîiZ@:E=$Á¥9%HgRöaRh‡f)Ú¤A@ò€€i@k_%ª¨¶¯ -Ziž¢€§¢‚oT‚§Œ -øL Nðí¾p•Ò>¦Ù&‰""(R8ü…Ì0¢»©5Û($Œ\Â(À̹;ËQ÷j>÷Ö6ˆ»!%—?¿Œr©àn$ -º»–Ðã^Ê>¶N%tØÆ‚íÙé‚î§Ò)#±¤;»³[ëĺö ÖÄÖζÐÙÆš]?^Gxc­c¬uŒ5›ž±Ì¹ 5OÛžL2Nv1»RW'Ü–\lÐÉôE.kâêLF7cwÚ½Iº¸ãKdÜã0žžF®4B'@ýXŠnNÆîЛM(êîD†”£ÆŠ¿%årÔ¸T -j €27„2ìÊbö8KBÛÄˤÒœ\RâYÛ³,#º¢Ç ‰ßž›;%Âys1áÔ¢Ñï~WGßsßX?Z¿Z¡šèðë0¢Ã¯Aw_‡Ù€ TÓêZC Õ¬ºÕîAý qª ׇáñfÊ¡aë§ÂKTs*N+Ή@Ò°ÁSÿS†’Ê¢00½0åàˆ?0ç­M)+Â$ЖZÏ0è¾\áÿMíß Ä–G endstream endobj 15 0 obj <> endobj 12 0 obj <> endobj 33 0 obj <> endobj 34 0 obj <>stream -%!PS-Adobe-3.0 %%Creator: Adobe Illustrator(R) 17.0 %%AI8_CreatorVersion: 22.1.0 %%For: (Baudouin Chauviere) () %%Title: (Verification_step.pdf) %%CreationDate: 12/18/18 14:15 %%Canvassize: 16383 %%BoundingBox: 9 -594 1016 -13 %%HiResBoundingBox: 9.98410490726747 -593.1220703125 1015.96983737134 -13.3836098402899 %%DocumentProcessColors: Cyan Magenta Yellow Black %AI5_FileFormat 13.0 %AI12_BuildNumber: 312 %AI3_ColorUsage: Color %AI7_ImageSettings: 0 %%RGBProcessColor: 0 0 0 ([Registration]) %AI3_Cropmarks: 0 -599.681066784218 1024 0 %AI3_TemplateBox: 512.5 -360.5 512.5 -360.5 %AI3_TileBox: 134 -587.840533392109 868 -11.8405333921091 %AI3_DocumentPreview: None %AI5_ArtSize: 14400 14400 %AI5_RulerUnits: 2 %AI9_ColorModel: 1 %AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 %AI5_TargetResolution: 800 %AI5_NumLayers: 1 %AI17_Begin_Content_if_version_gt:17 1 %AI9_OpenToView: -131.060833149656 36.2016751157107 0.9074 1428 851 18 0 0 6 43 0 0 0 1 1 0 1 1 0 1 %AI17_Alternate_Content %AI9_OpenToView: -131.060833149656 36.2016751157107 0.9074 1428 851 18 0 0 6 43 0 0 0 1 1 0 1 1 0 1 %AI17_End_Versioned_Content %AI5_OpenViewLayers: 7 %%PageOrigin:0 0 %AI7_GridSettings: 72 8 72 8 1 0 0.800000011920929 0.800000011920929 0.800000011920929 0.899999976158142 0.899999976158142 0.899999976158142 %AI9_Flatten: 1 %AI12_CMSettings: 00.MS %%EndComments endstream endobj 35 0 obj <>stream -%%BoundingBox: 9 -594 1016 -13 %%HiResBoundingBox: 9.98410490726747 -593.1220703125 1015.96983737134 -13.3836098402899 %AI7_Thumbnail: 128 76 8 %%BeginData: 12146 Hex Bytes %0000330000660000990000CC0033000033330033660033990033CC0033FF %0066000066330066660066990066CC0066FF009900009933009966009999 %0099CC0099FF00CC0000CC3300CC6600CC9900CCCC00CCFF00FF3300FF66 %00FF9900FFCC3300003300333300663300993300CC3300FF333300333333 %3333663333993333CC3333FF3366003366333366663366993366CC3366FF %3399003399333399663399993399CC3399FF33CC0033CC3333CC6633CC99 %33CCCC33CCFF33FF0033FF3333FF6633FF9933FFCC33FFFF660000660033 %6600666600996600CC6600FF6633006633336633666633996633CC6633FF %6666006666336666666666996666CC6666FF669900669933669966669999 %6699CC6699FF66CC0066CC3366CC6666CC9966CCCC66CCFF66FF0066FF33 %66FF6666FF9966FFCC66FFFF9900009900339900669900999900CC9900FF %9933009933339933669933999933CC9933FF996600996633996666996699 %9966CC9966FF9999009999339999669999999999CC9999FF99CC0099CC33 %99CC6699CC9999CCCC99CCFF99FF0099FF3399FF6699FF9999FFCC99FFFF %CC0000CC0033CC0066CC0099CC00CCCC00FFCC3300CC3333CC3366CC3399 %CC33CCCC33FFCC6600CC6633CC6666CC6699CC66CCCC66FFCC9900CC9933 %CC9966CC9999CC99CCCC99FFCCCC00CCCC33CCCC66CCCC99CCCCCCCCCCFF %CCFF00CCFF33CCFF66CCFF99CCFFCCCCFFFFFF0033FF0066FF0099FF00CC %FF3300FF3333FF3366FF3399FF33CCFF33FFFF6600FF6633FF6666FF6699 %FF66CCFF66FFFF9900FF9933FF9966FF9999FF99CCFF99FFFFCC00FFCC33 %FFCC66FFCC99FFCCCCFFCCFFFFFF33FFFF66FFFF99FFFFCC110000001100 %000011111111220000002200000022222222440000004400000044444444 %550000005500000055555555770000007700000077777777880000008800 %000088888888AA000000AA000000AAAAAAAABB000000BB000000BBBBBBBB %DD000000DD000000DDDDDDDDEE000000EE000000EEEEEEEE0000000000FF %00FF0000FFFFFF0000FF00FFFFFF00FFFFFF %524C45FD11FF6F754B756F754B756F754B7575754B756F754B756F754B75 %6F754B756F754B756F756F756F754B756F754B756F754B756F754B756F75 %4B756F756F756F754B756F754B756F754B756F754B756F754B7575754B75 %6F754B756F754B756F754B756F754B756F756F756F754B75702A2AFD06FF %A8A8A8FFFFA87DFF7DA8CA813F6339643963396439633964396339643963 %396439633964396339643963396439633964396339643963396439633964 %396339643963396439633964396339643963396439633964396339643963 %39643963396439633964396339643963396439633964396339643963397C %0028FD05FF272727F827FF27F8272727CA81886488638864886388648863 %886488638864886388648863886488638864886388648863886488638864 %886388648863886488638864886388648863886488638864886388648863 %886488638864886388648863886488638864886388648863886488638864 %8863886488582829FD05FF7D7DFF7D527DFFFD04A8CA752C332D332C332D %332C332C572C332C332C3333332C332D332C332D332C332D332C5733332C %332C332C332D332C332C332C332D332CFD0433572C332C3333332C333333 %2C332D332C332D332C3333572C332D332C3333332C332D332C332D332C33 %33332C5733332C3332520029FD08FFA8A8FFA8FFFFA8FFFF4A3333332C57 %33572D272D5733572D2D2C5E575E5D5E5D5E5D5E5D5E5D5E5D5E5D5D2D5D %2D33332D2C2D33332C57332D2D2704572D33335E5D5E5D5D5D5E5D5E33F8 %575E5D5E5D5E5D5E5D5E5D5E5D5E5D5E5D5E5D5E5D5EFD045D33575D5E5D %5E335E335D575D335D5D5D522829FFFF7D2727F8277DF827F82727A82727 %CA513264395D395D5D5D045D5D63335D2C57638863886388638863886388 %638863885D5D635D335D335D3264335D5D63335D0A335D5D336463886388 %638863886363F88863886388638863886388638863886388638863886388 %5D8863645D886388636363645D885D646388637C0029FFFFFFA8A87DA8A8 %A87DA8A8A87DA8A8FF4B2D2C332D2D2C332D2D2C3333332C2D2C332C5733 %332D5733332D5733332D5733332C572D2D2C2D2D2D2C2D2D332C2D2D2D2C %332D2D2C572D572D5733332D572D2D2D5733332D5733332D5733332D5733 %332D5733332D572D332D572D332D5733332C572D332D572D332D33522228 %FD0CFFA8FFA8FFCA4BFD0BF821FD43F821FD15F800FD05F8452E2FFD07FF %7DF852FFFD0527FF4BFD0BF80021FD15F827FD15F827FD15F82700FD16F8 %27FD04F845592FFD07FFA8277D7DA8FD047DCA4B04040426040404260404 %042D04040426040404260404042604040426040404260426042604040426 %040404260404042604040426040404260426042604040426040404260404 %0426040404260404042D0404042604040426040404260404042604040426 %042704260404044C2E2FFD11FF4A04F804F826F804F826F8040427F804F8 %26F804F826F804F826F804F826F804F826F827F826F804F826F804F826F8 %04F826F804F826F804F827F804F826F804F826F804F826F804F826F804F8 %26F8270404F804F826F804F826F804F826F804F804F804F8040527F826F8 %044B2829FD0BFFA8FD047DA84BF820F820F820F820F820F821F820F820F8 %20F820F820F820F820F820F820F820F82020F8F820F820F820F820F820F8 %20F820F820F820F8F82020F820F820F820F820F820F820F820F820F820F8 %20F821F820F820F820F820F820F820F8FD08204B2020F820F8700029FD0B %FF5252275252CA4AFD04F820F8F8F820F8F8F827F8F8F820F8F8F820F8F8 %F820F8F8F820FD05F827F820F8F8F820F8F8F820F8F8F820F8F8F820F8F8 %F827F8F8F820F8F8F820F8F8F820F8F8F820F8F8F820F827FD05F820F8F8 %F820F8F8F820446F444B444B446F6826FD04206F2829FD10FFCA4BFD0BF8 %27FD43F827FD15F827FD05F84B0529FD09FF52A87DA8FF7DA8FF4AFD22F8 %27FD15F827FD15F827FD0BF820F8F8F820F8F8F820F8F82027F826F82045 %592FFD08FF7DF8F827F8A82727A84BFD0BF827FD43F827FD07F820F8F844 %44FD05F8202020F84B204A4A2044702E2FFD0DFFA8FFFFFF4AFD0BF80505 %FD15F827FD15F827FD15F82705FD0AF820FD07F820F8F8F827F820F8204B %2829FD07FF7D7DFFA8FFFFA8A8A8CA4BFD0BF827FD21F820F820FD0BF820 %FD13F827FD0BF820F8F8F820FD05F827F8F8F820F84C2E2FFD07FF52F8F8 %2752FF27F805CA4A20F820F820F820F820F8202027F820F820F820F820F8 %20F820F820F820F820F8202027F820F820F820F820F820F820F820F820F8 %20F820F8272020F820F820F820F820F820F820F820F820F826FD0620F820 %F820F820F84A6E75929368996E684A7592936E4B4A4A69592FFD08FF7D7D %7DA87DFFA8FFCA4BFD0BF805FD43F827FD0BF826F844FD0420F826F84B20 %20F820F84B272FA8A8FFFFFF7DFD04FFA8A8FFA8FFA8FF44F8F820F827F8 %20F827F8202027F820F827F820F827F820F827F820F827F820F820F827F8 %27F820F827F820F827F820F827F820F827F820F827F820F827F820F827F8 %20F827F820F827F820F826F8272020F820F827F820F827F820F820F8F8F8 %20F8F8F8200527F826F8F845592F52FD0427F8F8272727F852A827F827A8 %6FF8FD0A204BFD152026FD172026FD15204BFD15204BFD0520452E53FFA8 %A8A8FFFD05A8FF7DFD04A8FF44FD22F827FD15F827FD15F805FD17F805FD %04F8452E29FD08FFA8FFA8FF7D7DFFFFCA4BFD0BF827FD43F827FD15F827 %FD05F84C0029FD06FF2727522727A852F82727FF4B20F8262020F8262020 %F8202027F8262020F8262020F8262020F8262020F82620202027F820F826 %2020F8262020F8262020F8262020F826F82720262020F8262020F8262020 %F8262020F8262020F8272020F8262020F8262020F8262020F8262020F826 %20202027F820F8204B2828FD06FF7DA8A8A87DA87DA87D52CA4BFD0BF805 %FD43F805FD15F805FD05F84C0029FD0EFFA87DA16F4A204B204B204B204B %2044444B204B204B204B204B204B204B204B204B204B204B204B204B204B %204B204B204B204B204B204B204B204B204B204A204B204B204B204B204B %204B204B204B204B204B444B204B204B204B204B204B204B204B204B204B %204B4B4B204B20444B2929FD0EFF52277DFFFD6CCAA9A9FD0FFFCAFD82FF %CAFFCACBCAFFCAFFCAFFCAFFCAFFCAFFCAFFCAFFCAFFCAFFCAFFCAFFCAFF %CAFFCAFFCAFFCAFFCAFFCAFFCAFFCAFFCAFFCAFFCAFFCAFFCAFFCAFFCAFF %CAFFCAFFCAFFCAFFCAFFCAFFCAFFCAFFCAFFCAFFCAFFCAFFCAFFCAFFCAFF %CAFFCAFFCAFFCAFFCAFFCAFFCAFFCAFFCAFFCAFD12FF6F6F4BA07B7C75A0 %7B7C75A07B7C75A07B7C75A07B7C75A07B7C759A6F6F4B6F4B6F4B6F4B6F %4B6F4B6F4B6F4B6F4B6F4B6F4B75757C75A07B7C75A07B7C75A07B7C75A0 %7B7C75A07B7C75A0756F4B6F4B6F4B6F4B6F4B6F4B6F4B6F4B6F4B6F4B6F %4B6F6FA075A07B7C75A07B7C99FD0CFFA8FFA8FFCA4AF8F8042C042C042C %042C042C042C042C042C042C042C042C040A20FD18F82C042C042C042C04 %2C042C042C042C042C042C042C042C0451FD17F8043204042C042C042C04 %6FFD05FF5227522752FFFD0527FF4BFD1AF84BFD30F82626FD16F8042DFD %08F844FD05FF52527D27277D7D527D527DCA4BFD1AF82020FD2FF84BFD17 %F8262CFD08F84BFD09FFA8FD07FF44FD1AF84AFD30F82027FD17F827FD08 %F84BFD08FFA8FD05FFA8FFCA4BFD1AF82020FD2FF826FD18F827FD08F84B %FFFFA852522752A82727522752FF5252FF4AFD1AF84BFD30F82627FD17F8 %52FD08F84BFFFF7D7D525252A852522752527D5252CA4BFD1AF82020FD2F %F826FD18F827FD08F86FFD0DFFA8FFFFFF44FD1AF84BFD30F84B27FD17F8 %27FD08F844FD0CFFA8FFFFFFCA4BFD1AF82020FD2FF84B20FD16F82727FD %08F84BFD07FF7D527DFF52277D5252FF44FD1AF845FD30F84520FD17F827 %FD08F844FD07FF7DF8527D5227525252CA510A2C042D0A2C042D0A2C042D %0A2C042D0A2C042D0A2C042D0A2C262D0A2C042D0A2C042D0A2C042D0A2C %042D0A2C042D2C2C042D0A2C042D0A2C042D0A2C042D2C2C042D0A2C042D %0A2C0451262C042D0A2C042D0A2C042D0A2C042D0A2C042D0A2C042D2D2C %042D0A2C042D0A75FD09FFA8A8FD04FFA8CA9AA07CA076A07CA076A07CA0 %76A07CA076A07CA076A07CA076A07CA09AA0A0A076A07CA076A0A0A076A1 %A0A0A0A1A0A0A0A1A0A0A0A17CFD06A0A1FD04A07CA076A07CA076A0A0A0 %9AA07CA076A07CA076A07CA076A07CA076A07CA076A07CA076A07CA076A0 %7CA076A09AFD0EFF27F87DFFCAFFCBFFCAFFCBFFCAFFCBFFCAFFCBFFCAFF %CBFFCAFFCBFFCAFFCB7DA1FFCAFFCAFFCBFFCBCA527676A1A1A877A17DA1 %76A2777D777676A17DCA7D7D7D76767D4BCBFFFFCAFFCBFFCAFFA17DCAFF %CBFFCAFFCBFFCAFFCBFFCAFFCBFFCAFFCBFFCAFFCBFFCAFFCBFFCAFFCBFF %CAFD0FFFA87DFD1DFFA8CFA8FD08FFA87D7D7CA8527D7D7D27528352527D %7C7D27527D527C7D7D7D527DFD08FFA8CFA8FD33FFA97FA9A9A97FA9A9A9 %7FA9A9A97FA9A9A97FA9A9A97FA9A9A97FA9A9A985A9A9A97FA9A9A97FFD %06A97E7FA9A97E7EFD06A97F547F7FFD09A97FA9A9A97FA9A9A97FA9A9A9 %7FA9A9A97FA9A9A97FA9A9A97FA9A9A97FA9A9A97FA9A9A97FA9A9A97FA9 %FD0BFFA8A8FFFFA8FF02FD0C0028FD06000101FD110001FD0D0028010000 %010001FD130001FD0D0028FD100002FD05FF52F852F852A827F827F827A9 %28FD13F800FD13F826FD0CF80000FD19F826FD1DF801FD05FF527DA87D27 %A8A8A87DA87DFF282604270427042704270427042D042704270426012704 %270427042704270427042704270427042727260427042704270427042704 %280526042704270427042704270427042704270427042704270427272604 %270427042704270427042D0427042704270427042704270427042628FD08 %FFA8FFA8FFFFFFA8FFA928F804F8260404F8260404F8272604F8260404F8 %06F804F8260404F8260404F8260404F8260404042DF804F8260404F82604 %04F8260505F8260404F8260404F8260404F8260404F8260404F826040404 %2DF804F8260404F8260404F8262626F8260404F8260404F8260404F82604 %29FFFFA8275227527D272727F852A82727FF01FD0CF827FD06F80000FD1F %F82800FD26F827FD10F801FFFFFD047D52A87D7D527D527D7D7DA928FD13 %F800FD13F827FD0CF80000FD19F827FD1DF829FD11FF01F8F8F8042D042D %042D042D042D042D042D042C0500FD0FF82D042D272D042D042D042D042D %042D042E00FD0FF8042D042D042D042D042D272D042D042D2627FD05F827 %FD09F804262D042D042C29FD08FF7DFFFFA852A87DA7A928FD13F806FD0F %F80404F8F827FD0CF80000FD0FF82DFD09F827FD17F826FD05F829FD07FF %52F852FFFD0527FF28FD0CF827FD06F80000FD0FF827F827FD0DF82800FD %0FF80426FD07F827FD0DF827FD09F82604FD05F801FD08FF7DA87DFFA8FF %A8FFA928FD13F800FD13F827FD0CF80000FD19F827FD1DF828FD0BFFA8FF %A8FFA8FF28FD0CF827FD06F80000FD11F82727FD0CF82800FD18F82727FD %0CF827FD10F801FD0BFF52272727517E28FD13F801FD13F827FD0CF80000 %FD19F827FD1DF828FD0BFFA8FFA8FFA8FF01FD0CF827FD06F80000FD1FF8 %2800FD26F827FD10F801FD10FFA928FD13F800FD13F827FD0CF80001FD19 %F827FD1DF829FD08FFA82752527DFF5252FF22FD0CF827FD06F80027FD11 %F827FD0DF82800FD18F827FD0DF827FD10F801FD08FFA852275227A85252 %A928FD13F801FD13F827FD0CF80001FD19F827FD0DF826FD0FF829FD0DFF %A8FFFFFF2826042D0426FD07F827FD06F80000FD11F827FD0DF82700FD10 %F8040427042D0427042D2D27042D0427042D0427042D042D042D0427042D %0427042D0427042D042628FD07FF52277D52A8FF7D5252A928FD13F801FD %13F827FD0CF80000FD11F826FD07F827FD1DF828FD07FF5227F8F852A827 %2727FF28FD0CF827FD06F80000FD11F82727FD0CF82800FD10F82704FD06 %F82727FD0CF827FD10F801FD08FFA8FFA8FF7DFFFFFFA928FD13F801FD13 %F827FD0CF80000FD19F827FD1DF8287D7DA87D7D527D7DA87D52A8FF7D7D %7DA901FD0CF827FD06F80000FD1FF82800FD0FF80404FD15F827FD10F801 %5252FD0427F8FD04277DA827F827A928F8F8F804F826F804F826F8260404 %F804F826F806F826F804F826F804F826F804F826F804F8040427F826F804 %F826F804F826F8040528F804F826F804F826F804F826F804F82DFD09F827 %FD1DF829FD0BFFA8A8FD04FF01F8F8F80427042704270427042D04270427 %042605270427042704270427042704270427042704272627042704270427 %04270427042E05260427042704270427042704270427FD09F827FD0DF827 %FD10F801FD0CFFA8A8FFFFA928FD13F800FD13F821FD0CF80001FD19F827 %FD1DF829FD06FF5227522752FF52F85227FF2804F8270426F8270426F827 %0427F8270426F8260005F8270426F8270426F8270426F8270426F8272704 %F8270426F8270426F827042800260426F8270426F8270426F8270426F827 %0426F8270426F8272704F8270426F8270426F827F82704270426F8270426 %F8270426F827040406FD06FF52FD047DA8527D52277E28F804F804F804F8 %04F804F8040404F804F804F805F804F804F804F804F804F804F804F804F8 %04F826F804F804F804F804F804F8040405F804F804F804F804F804F804F8 %04F804F804F804F804F804F826F804F804F804F804F804F8040404F804F8 %04F804F804F804F804F804F828FD0BFFA8FD05FF2AFD0C294DFD07294E29 %4D2929295429292954294D2953294D295429292954294D29532929294D29 %54FD19294DFD0D294DFD10292AFD0EFF52527DFD15FF7EA9A9FF7D7D7DA8 %A8FF7EFD05A87D7D7EA9A8A97EA8A87D7EA87DFFA9FF7EFD47FF7D52A8FD %14FFCFA8A7A8A87D5252277D7D522752277D27525252277D5252277D5252 %277DA8A87DA8FD63FFA8FFA8FFA8FFA8A8FFFFA8FFA8FFA87D7DA8A8FFA8 %FFA8FFA8FD3CFFFF %%EndData endstream endobj 36 0 obj <>stream -%AI12_CompressedDataxœì½ùrÉ•æùx‡;”µÔÓÂ÷pMÛ˜ X£²RI¦¥»zjÚh‰L±‹$X\¤Êzú9¿ï¸Ç €©T+U­6C)ò†{xxørüìßßü?ÿå._ÝÿæîGé|9œýÍß\¸»ýtÿáÇÝ=üäÍ›Ï?}àÖ~ñÃChç‹UºüÉúbTü/w>¾¾÷ãCŒçA…ÏyúW·Ÿ_Ý~ýîpýÛÛÏ¿{}÷áüЊõúÓ›;«`¾þêõËÛOöô‹ŸîÞŸ¿õÕgìæ³ÛOV/Ä‹°Úÿ!ÿ8ŠoßýîöãÇ×ÿFaMk²{W÷Ÿß½zýîë«ûýñ¡~Tz>„%ÔÃÅÿÏë_Ü}<­sÞ×–Ü—kËGÒyˆqiK -±ðt9ﵯ©¥R¦¥s{Y]ìÁ%®½[»Ïî_~~{÷îÓÏ?Ü¿¼ûøñúþÍý‡?>\sûîðÓÛ¯­äöðßîÞ¼¹ÿýáêÍíËÞ=òüþÝ'«úÓo>¼¾}eÏÿèw_~sûÁªüŸ‡_½~{÷ñî~ÿ‹û··ïl‚®îß¼úɧÛ7¯_þôW_¬ ¢Ùü?Üݽº{õçyÉåOÊ‹ç¯ßÜÙ<¿½ýt°qY¸⋫ϯ߼ú‡Ïosg+À†‘Ûé…†å×mÝ}xgs3»óïøî›w¯^ òu÷jÿþ¢÷óö9rÍVçÏm½þìÃkºqo/þöÃëWÇ5ÜâaõÿÓÔœÛð'„—ûw½Óõ§ÕPVû¢ïrÇÍ–È'û„9ÏñÅõOwûk9ÿé/í+죯ïß²N?B8Y ¶çÞÜíeÛ¿Ub~öOg©_üËçûOw­­7w‡^/¾þpû»;#ÎëÅå+ˆûÇ×/.?XñÅõË»W¯ß¼¹½¸¹}ùùÓÝÅ?|2¢pwñ³Yíìâ×Û·^åV­]ܾ|ýÁ¶ÐWoîþõâöXÇŸ¿Uã/gãwzòìâνÛ=z·=úÚ›íu^ïê¼Þê¼Sóg÷^÷ÞëÞïêÞouï½+Ÿ½êg¯úùXõìâóV÷Õí×_ß}¸xe¼»»xiã}a‡Ü‡7|ÅÇ»—lÒ‹ß|~óæîÓÅûÛŒÀûß^ØFk_ýæÒÑ—c±¿n.îY#ï^Y—.îÞê/-db5:x›úu¼ï7_½þÝkÈ6hÛ˜ÿ·í__}¸õ½ùüá^=ÕNÙú­_jîìâ«×öÁcyØ›/ÞÛ{î_±@4×Ç}ö›Ûw[õê~úíýç¶DÎ..wKôf÷ïK_7[çn|h~â÷²_®?Ù*ýÄ+ýÌ+ýl×ÞüîŸy_{_ï›ùµ½b¦Œ3»Ø=íñööåÖ»ŒªvûR·´ï賋ß~~÷õí‡ÏoßÜ~þdûЈò?_¼¼µçÎ~u#ª\þîů>Ú{áW>ÿé_ð…¾¹×_èïwëé?…=59$]غ¼û—Ï·oìG½xýî+£KŸ¾ÙQ;Ö¢*Øae…-t~œ…5@ôŒè~z}ûæÕ믾º°o+Þòâý‡ûWŸ_I~m-~âp±æ×~ñ³·w_ßÎB/F!`‡ÐÛÅí{{à_G/úzñìî±ü1äA5ŒÚýÛÝ»¯ï1/T~ctäÅ/¿yû›û7/.üï“//ƒ5³iø¹mŽî³x&yëço>[Ñß~¸ÿüþ'ᄎ?û hÿÅÎ#Ÿÿv÷êð³ßüûñCD+Ýû¨o:¼º;¼ù¯á¶Eÿvw~ûúý¿µec'>ܼОÔÏù÷~úÙÝW&7†ÖXÝÃÝ¿ÂïÝ éòæÝïîÞÜ¿O»þÀnøçonßÙîÔýÇíþüΆí³õûÕ7ïnß¾¶Sèðê?èA«Ë£wŸþñ;¼Ã˜¾÷Ò¿›°½ïVØýáýýëwØDn& -Û[wÂø¹­¦ooú—ÿ|÷éåo¿µq¯ò?Ù¼Vïëoíñíßc^¶×Ë7w¿üÆx˜ï0«¿| ïúápõáóÇß~uÿæKk±Ó&Õöíç¼i<©yî/ë]?×ï~öÎþñÛïþÆ»ƒ1:¯¿bj>|ò‡ÿÒ_º=ó­/ôW±8Œ;dwÎó¿É+¯oï³þúåwxëËcõý›÷­üqójŸúÆøÙ_øìÿ‹¡Z›¯ø›É„œÞ}ÜÞù÷FüÙï¼€ÆD1ø üÖ:¿üý­‘–¿ý›o}dçö+ÛŒ¿üüú“Ž·û·ïï?ò%{’2+ÿrk³Hˆß—?úÑ·œ£ýpõnWü·åƇ¢­|óŠùÍÏŽÿ´S9®^™ø»œ—zNqÉy‰=æ³Åî…¼.-•ÔCj«ßŠ(„J]kl¹»±¶¥¬kL%öÂjwºþÕjö’³|ø›Wžz‰T§/Ñ­Ó—½äðè%ö(CŠñ³ËÂû~yö7/þÝÞwõñÏümÁßõïôeÛÛì»®žm‹p.¦§××ß—{xf§«-°ã¿­‘ì+ì?ŸÅû/Ä8þK‘?É®<®2þ߯:®f—ÿÿj—õ7^òÿgö—_Wúo^×úï:>Óu£ÿnâóø<-ÛÆ÷×YšrÊgÿ7]öç¨ÇÍšZêé*]§gé&/9X½œK¶±Ëkîù*_çgù&?/‹õ?•\J©e-½\•ëò¬Üœ•çÕxüjwk³YéõÊ®gõ¦-vÅ–Znµµ¶¶Þ.Ûu{ÖnÖe öÙiÍ«=°¶u]/׫õÙz³>ïË™-ƒÔs/(òÕåçy\Å®úèjÏí1]—'ו®ëq=×Íó›3û¿ïõÏÙɺl’·Ë†n©ÛÕƵž\}\—Ûue×µ]ϸÎô×͸žÏËvªýg×ñOÔQk!ë*vÕqµy!Q­¡û¥.ß»l¦o’]¬¯ Ë^§»±Ëÿ\Ûue×¥]Ý®U—Mì3›ög¶&že®³g¶¤´`ƒ]ê¿fâÆ.Úð?Wv]ÚÕíZíjºª]¶¼®mñ]']¶øÏ®ƒ]6"L«º¬”ݼ±^]_]]]^õ«õªÙ:,Wù*Ù -W‹­ˆëñõåÕååe¿\/Ûe½,—ù2Ùv —‹­Ÿ›3ûœë~Õ{_{³åWlµ±ZÖç¶:ŸY¯l­v[±ÍVn±œl%#RÏÛ Áu»²UÞmµ7[õå̲MÚRéò2OŽÿ|¦iX\>—ãºÚ.¾üÙÉÅÜ<Ïüp G½´íTmf#1¡.6¡Ïl›^Úvm¶m@–ŶÑ3ÛÔ—¶¹«mód d± ¿N— «摘#7¾õ®¶íÔ·ëôŽÌ›²j³ú5·ož[ãËWþ#¯Ä žÛ„,ÇS¡ØKúus±AŒ6¿Ùæ¹Ú|¯6ï—6ÿ׶nŒ>,¶2’­b+¥ÙŠé¶r®l=³•ôÜZ°••l…•§ú¿p…/^'¬Ém),ßÏŸ`ogLêvÑ”KÛ-ƒ`ˆ,¤ 4}“ LÑ&`øÅé³øv°ë™í†«~¹Û¾'ÂnO\iOh?œíl;°|+`«ì£WõÒÖ `.Mvùÿ©~Ùh}Ó‚{Hí¯Î¹ß“üã5J|¿ç?§ >œt_É¿ÙÿÜŒËÏ£}O7ß:7ãàã5N6v7ÏϬþ@›¯olÑÞزfÖnÖ£F7Õ._»-é²3½)° áDæLöSùÒ¨ÖµŸÍv=×ùõ5rFÛò9¶ª@:«±ëÕ¸ßôdênbb)zê(fØáyìQ)çƉ‡å/©SŒUÎaí‹-S—¤zŽðñ­V Jro…ó\–ÝKwãÞÏmÎêN„û^ýc$µ_¿{wûöîÕáëqËDX“Ùžº{Ô VÅr]ŠÁz&f*H†Ë’ÔV‰`׶žKÀr‘©HhZSCÇe̳SàËA{«Xé$&zó|-†¹‹Q®b‘“8ãEñ3?öT¿î˜§tBûõ×u³;®­‘pBø¢÷­ÇµÙ_%£Þ Eè~²³p¸ØÖ̉òá{kqlT;‡Ö” -BÑ®µ‡â»Ýy°õÓ -„墳?iáå§Ö]>YvãàK’ «ø´+ñiÏ%È9—V$l‹SK3Ž¥Ù¤2ØçÙ¶:wkÓX¹!Ô…Xw‚}‡á;ŠöîÃQ¼?3ÁN}ã ]Æ7)ßø/„× íE2£Tþ¸Ào̵ñu×Æn<—h†ØŸÅjA;ŸÑŸñL|ÆžËHâ2ÊÐ8¯q)^C܆kv G<Ï‘ÅŽÖó€÷˜ÜÇ3±¯Ï7)À倣$à²Ò€ä³M$XŽlKgáò{üô¥æÊ…ñ9_sÆ꘳>d·kÉi’ÑÎvÓW‡ÎçrSêÜ -óÆøLιôÙóyö`J][sœV&v?µ>¹B¬Ùeè˜_fXs|Æ$kšÃ¦yN4Sý ÞR ùœðý”I8åûIcÒ§hÌ%~ýhÂ5ÛgƒÉlC-ô`Žê˜!ŸŸ§g§m;ê87aNÍÙ˜ÛkS´ùÜìO€¹Ï|§Í½æ»Í&ç,ç1?õÌo>Ÿ¡òÇÍÑ~–þ¸y²™:ÛÄ€¹AÏ×2”ñÁVm»y۶ꙦN“÷ç¶ÂÙ—%ƒ ÂT¹íUm{Û¦\;óµr²Z¦”\ŸÜÑÇusÜл%s¶ÛÏOÑä#E>¥Ç,‹£šu§dÕ¤kºóP±^JÁú|¨W‹fy{¦ùŠR«2/¨TŸmj5 -yi”Ñhâ™Ää„pŠ¥7D¯$~®b@ŠØŽElÆâož9œ³§Y)û.¥ækRï}gÕÝÙwÑÝ ÕÝåwQÞ}7íÝ^yg»è™í§+ünSІž-¹¦í̶%ʶk©ÛºnÕ&1Ké¤v»±=-Õ[7ZФ~˶¢TpÏmY<32re‹¤§õÌVLµ•“SúëýuŽþ:GßËíd£ÚÎrJM G™pn¯ËÆ[¯&`‡äN]ŒÌ§¼Øk– Y»”d#¡p…µr'nÿ=-1ý™ßã¢üæ,ÿ¤\ô¥ò?IÎ)OÉ9å‡z×å^çÍ\Î)@ß³aÏÏž’ÁOL—ññòr'¡»óÙ^Š?ûã÷æQ |ªþ=*/7åïCE@þ’"àl§ x65R»:x*„×£RøžàOX?¶;:ª²ØTô^Ó&s·S‚=öŠªúeÃ÷Õ⟴¾ëS뻞¬oŒÿ¶f¯mí^Ú^W,½fÄ,k«ÜfÝÖüjk¿`ªrU­[e,¶þbå•øSkâb³lM»GyW{²gzãåÉ;JM•mIW[«­‰K[!׶^nnžÛBŠ²·”Íž~)C -örçäÿW<º£×¶vé…CÁ¨©Tç"*F#ÃbÇŠæÅ*•u±”ºQéK÷ªŸüîHý\††ý| öÎýöÿÒŽ±NýÕñ—o‚°¥þh}ÂúP£pöP¥ð¤Fá¯Ká¯Ká¯Ká¯Ka¿vÂ7QÛÎâVìh³¯À¾×z©ÁþOŽçüÂFc]²îô–uð5û´Ž+·®§Åù?Ûþ\ú”|̶2"ªM ÿ»×¿»3A^r;·ï$%a?¯ÉÖj#¬ªYwJ9ïË’ºËÇåC?·5{Î6¶véþ¥þÿï?îì¯ÏúgößèçßÙ?ÿ‡Ýü½½çðÓÃ?ý÷åðÊŸü…ý5ûðð ‡·Vøûqøû§j=úž¿ß½è[ ŸlÿýwqùáÓ³×JÅtûá›ÃyíáâêþþÍá—?ãûÂ#Ö_\ݾüg›eü{ñ eNùOöÀÿeÿ=+ýó?~VsÏtçgú7‘‹ñs½h¼¬hb,kGüÈl^YuŒ\³í³ÚŒïtN,{}0K:?°Øüã-¯Hå|I%tã/×fw[>︾¿…F;TìåÍ~©8P`”FOkZ)€°¨@gÑd~/ˆéð’æòzl·t›#WBܳÖ”w¨²ß¡­ç9ôn6%Úç[׌xöEQ1ØS*X3¿S²¯9¶Â´VjÕ+lª‚¿ÛæGïîk À芬Éf¸–ÐzŒ^â6bvÛÞó’^3³C²l©§…O$¨$“­ô|µ11šoŸ²„C6ë«W©çFÀ)&ŠPÖ5ÆÅÆѾ··óhR‰ÿ®ŒRhšœ%žÛÁ`'‘ ö°wì‰J¹†•J2ýQ!)ÔŠÕ·*]¦í-[²-/ìRÕÈ• v¼Äæ58.­ÆjÒòö¢B¯¬PÓc…­Ù0QÐÈjh{ÝöHñÇM~Ò ¢÷ ùmÛÅQ=³Óx´Ï­Û6!­V‘à$[c‰ë«-³ªuig¥¶ÈG‡j¶)aÙ­9/*oC_ùòÒ˜ØCMÙ*°÷óAÌ.5«-Þ”š½¨/¹ñh=ovžZ‰p´_mkÙÁ&k,ƒÕZÏí€^ˆ0YŽäs)’mï™Üi½h›³ \ Æ+°Ÿ¹J‘à(¶®%¬;¬¿e¼)ø€Y-ãAŠ×J7±=Vµ£Yµ-fÇ£·SQ¡FnlÕ°WSK5¬ªaïNÛ›lˆTKÚqjÙؤؔ$f“¾Ô–ÔkÐj„óea1p35|S7zÆ7EÆ—1dÕHöÕþ¦xnogv q^+ı5XúmbOÅŠQ¬Móù49_ßhí†îí¢qW»ÍúGV^[Dˆê Œ$GÆ·›µÏí½ã-ÆÏ{õW, ºf²Úh,Û:5i èv†Y~Ûp¯ÆNØÉžm¹qäÛT²†ö[Ρâ£e9[Ǻž7`PQzÆhL2bNãv¶P@ø¬fҾ߾«1åat‰ÂÌZjËÚmfÖƾê©TeÍÀÁØ)ûŒ¬ûœùü®sÈí^ˆëJ]扲4ÚX¤‘ôŘ¯c#+ѱLy4¾o{é˱ì0¥GFò5iœj68¬I94˜QP VØ}ƒ‹Â}kdY{¿}£Ø"Õ ª´g¶¼"¶6º·f"¡Zã´‚ºû\?²Y>vÕõ=Y¬»ÝGÒ6îªãƒ(jÏç%›èk»*ØÓ<ÐlYÛTÛSv¨,~D#/Ùònþ¥V5ë‚ÒÄ(Fáë+ß`5gúf”Öx4£Ap¶ü¶§ŒÝÎpWÍ„b£«ÓfuIZûÀŽ3QëÜNdDÍmÌ,B:i¶ŽDv çpÀ6X4yÈe0w.ÅvµqK‰·Ø¤)"Ph/).œ¶YË5è©Õ+õVYO¡Ï :ŒŒ9FÑζ5×!VV˜9«¬Ð—'Ƙ«À±ƒæ¾•÷àBs+&û£ÍÞ ¡ÊdðÕÚ9Ø:ή׊úh‘zÎ刲RÍÿÊîËœBÝgÓ$L#·ÖÊBʹ•5ûâïn ¾¥9²¥–¥Ñ]Á(AÇm»¬UcèÄö®ÛAj…êTE‹ë¼²had[µ±ûF´Ä¯+¬SD÷ÝNÙ^îi½6$U±ç6meTŽ*àŒ À˜ÊêÍÛ¤À¨ž¬sí”ÕÑVÉçc“}~“…YQ$Ú PÚà²Ä£û¶àëd5–­C¯m=pÛAƒ*a\Dl1 í¼üpæmßåCÆ»«ÌùÈÐèTûúnËÁµßñ‰‚h„0óéa!fô5 ýxª ®?HÎã}igtƒ9öÕel>Ìšd9S¨}a«èV1®Ø~ÌÈ(Ñõc+œ4ŽUæ€ãûrº_;Œ¦Shâ|v­W—]ÊFϸ -Jq…\¶Ã‡‚îSºfk©‰¾GWŠ1ýÒ¢ Ue›Qôc±í¾ˆ§R.3·°su0^Ù„V“qàv>tôiPÙ$}“³Ùƒ˜›4aü&£'¾“¢Î(-NcnVžZÆF2î™&B7è´Çh•TëFlE5µ6Õ."d C4º¤2ûŒ®‡¢ë)”ÛF¢@2-úŒ.z™ ÝTkF6¶ÖNg+Ì$*£ÎT•íoèã¬À„q·Êä‚%ÊXV -â‘ôØ0z¤5€¢Å–ØŠôÄ·"xvý'n9WÑ LQÈ'FR?lKnê V®*Vn3=Ä6…4XO„´œ0¤1Aöm:ž"&;žja¥E¾nçÖ…W%Ì"0¤Îp„Â[f#7&ED™¹ØÆBvãám¶¶Hœ1*|Ü¢Æp±wŒîgônö¡Iiép¿ã¦n’™lZCb·ÖzQcÔØ˘M*J˜ìr”¶dT¸Õ.¹©Êâ–†ÍDgïtÝtç§c‚´#7£|±Ñ«8,TIF뚘˜&˹¿ŒïƒÙ݈ÿÈ -/¯Î0E‰¾¸4@m&dõ±Ñ31|c/|»àÓGívÎìè)iŠ+ -Ô¦??)¨ú-íÕlE* «l½)"0«²ö0 -¯¸êÝÎrÂÅ™pFíÒ ‚mÿdä1É… 9þ‚í|§Ûý/&[C±PWO,ˆ¼vZJC7yî'HÜÆšÂÖFRA–1ÖIꌘY¶ßSmˆŽuPu÷sŽ[eß~Ûi-‘ÓŸ÷°7®™DŽ ÍCÚÇÖø’À>åz[‘V8Aí|ãÛlèߪÌ +vt VÇŠUeR‰ Þ·q^«¸"ÝMækN`êFé4Ö®W¢Pb嫆°!ÛÃ1Ï(¥tÌÒöó8Üô•]d¬•óŸI»¿é-Äã å†Ð*UØ «¤/µsÊ*eT3Æ9«FKÅ9”˜ ,K1¨ž>Ö;ÄîQCœ¯‘Û›¬+µ°.<ý¦È†´%! ¨Ã«wØ֞׈ä´XmL“wÃÔzÒc”YšYcmq‘ÿq­“ U¼¨uÄÔ9Ÿ†™3ŒÑÜf“ŠÚVÔ7gÚY5ï‰/P3 5¯aél2ñr-*_lÆ`“²Œ;³a6R`?,ƒvˆ•à_¢€¢«ˆZ8—‘>²°ÄÔˆd¦³-;'Û4“ƒFŠ -v3ÍÕ›é“&Ö¡(ŒI "Ý y-_Ž[.ÕˆXèŒR·99)MŽTNÔPj«ê`'ëñCð%àCD®Qº>Ñ5ðtb/âšÂæCf< Ca2Znì2¦m ¥…~J•gGÑ:MdðcWFäêãM‚µ]ctÎH)ç 3I»0 +â0EiÃôD$øˆéLþv¶ùÖ1!:n¼:´£›±LªY¤2¶OüHNö}HãSSܬ¥ß´‚î&g›ˆÅÈòxÁ -ÓiL ,‹Ü…È°j‹ÉÉÆðœ£`‘<á_ÆT­öÊH7Œ 6ºîßSMPXضïÛk8ÇE$ŒÑ‰×E¦:“ÿ£–Úaî Øô*Lj «¦îè‡ñÌFµSëâ{Ùd#~lé²@v+˜Fc¤lâ‡M&qêc*x­ZõTAÓgM\‡Ì“FÇìqXA¯¡q!ƒ5ŠÊOÂ’Öí¬MjõQÉF®‘u¯FàëÑÁ>•}’hB“Â1-ª‡²­…T ÑO¼ÇjÝìT¾_P„é”KçˆåFB‰_¨§¢Û"û ¸²ÁfÔ·ò¼Èþ¶fRÔf¡kN?v|c›I¦ÀØ\ã£mÐH,s‘Õ$ÖU²6›"˳N¶bå™Â¼-’/†ëàfMj“f£pQ ‡‹‚)0º`ƒ¦ä™W$­r@SÓ\xèMBÅÚ§o“ÕŠ}U;Æëy7"®FH¤¿U;HÔXü¤1 G²œ¨À§ñ¢P]xë„c[ €Æ‹ì$p“§m;¾JÌòž“íµUÙoÝ2e5ôˆÕ(ع0ˆ£†ÛZ¦/ƒŸBM‰…G³Ãw–1À“%Ë®‰TÅÑ ¿A΢yv[ÈΈÃ¿„׃Œ˜›±9'ó?ùIÁb­…{êèHÃø½æäý6‚ÕW™^Ë.¨Êl£[QçEÓò'À8P“\²¬Ï*¸ YAÂ2ÐuìŠWŽ¬äHªøu—v"Ê›ìŽ]Cbqí,Ô"a¥Sˆ1dhBíøsVØÖ™¸F'c×2ÎÒ$Œ(ºÄigÿ°xaBCOÜK€³ÂHp®±œÝlõ¬ÐhTQaß)þUFŸ›’šQ÷+ dî0Âi.~¶V1od8›U­e4§z ¢q–Î}¬ºo„Q}c]{Áæ+‘­£|3×Õ‡¸.>„ÚMÈŨ’úðƒä„•ª5'Ÿ+cÐÓ0ý@ÐQ­öeìã2H±’ôú8ÿÅ„XßZrË‹âºH›$Ùs¬P§™Ýoò‰xýÝÇm¹FYA¶®üúfkR¬fÜð»¿ÊNv{J–=EèÙÔ\ïŒzT¬Gw ;ËV8M`€09®(¸'¤uk~&ر‰ÖtGjí ¡fûÏ;…ºµÈ<Ýyu³1í)œ+lË`d‚-+Þ‘vä8¥çJ_ï%Z›è*’•‚¢绺nË/Ñnã2¥ú1“|•S¯*+ÕyRÙí¾íHÁÖ¬}ͤèØ&kÚ]!*±ÎòQ`×!Z²[Ð¥ª¡@#QÊ%Äú/_T>!ŽyÃÔ‘³ -¢)ÃÝ„û:“""=àbRäqÖ1b­akƒÀ8Õåhè˜R£×•DcÖA»-QW*°¬FH2/•ï×ìµ°<Ú¡ `ë=6v/FŽ—„¯cSsnR”óË¢áNM=Õ.,·kçŠ8<§"^e&ë&¡Â$@ÄîÛêÇ£ÌøYy¦NK Flb‘z²Hu!v!Ê1Þö;ü)¶E ì«·DªwÝGm„«ÛT«p]ÔZÁNƒ‹lÐdaiæ+‹É¶Ñ‰¢x3BT*2æ^ ÕÖC$WIÙR{‘WÑ®ntw#2«Ù -Ye¼{XñnèÛÄŸ–Á7÷ô·yg)I¢^W§Ÿ¶Ë;ÌHî›*e²XÂy _mÁØSàp#lÍuL¦äU O¢ÅïöŽð#«Üˆû‚ÃsŠíëB²EO½+VãVŘ-k­Ë³l~§D>§°ô$Zºƒò"k¬yËzJ3‡…«+6ª-5Êd¶Ê¾?Í_bZ z<üjΛN FS€¯lÖÆÌO½‡ïš½Ë~²ÙÀâÂ_jÏɽŒüFx¤/Éî2Âc× Ûn¸æ”s§n¡(k†sÁßblì/FÐã"38ή~,pŠñToC‘‡Ô`ÔsUDL•¼+Ìä…fS‚õUÂ!þ.‘Îe¤öٹťeœ»-¡´øA§7œ1‹Òº˜P'W±/ -×CÊø¿>{2°öÓic*™’€džF%-„§2¯âw‰µ•U{‡‡»¬BÚy8ïsÈâŠ8ˆÅ•u€ƒ$.•og!–o -mPèW2Ã2Äd4 º¿èT­®À2AÜ÷,˜ZA ¨‡L`@zŠ£ „:À¨:¡ÑŠÔÊ_‘ ~˜ä¡; CnRá~8æÊà÷P•Y¢ë9‘Çeò6. -ª%–h¾ºø²à`%¥ú‚n¯E¦»ât¡š ÈM+3ïÍÃÙm54†ßš•ÉU]À5FãpHѽ½17N;/X§ª7ˆ»$û¤ú ¶€ë™S€Ì§Êã@F±uÄa„.¸ÀÛ?Fî° -Îi‡¢êµïÀÖNgƒ¢?‹p† -4!²Ó¦ÿDa”aw8ÃØý®Ýe -ËF4&¢šâbØZÃ…‰Â7‡E…·&fÛk#J5¢ -t ×f/w÷Ë{9ìŠ8°…aLjƒ‰è(ˆQVá" rÂ+ý¯­*iϲDà Óm3c†’ÐbÕµÆYÑÜ-f)ÓܸHð#d -x -‡CãœÑª®:–\ƒ€ÂMļ‰—3•™Åµ*F°–èþ^qžë2bH1€×Äê„VV vVŒB'©¨£7 ÅBê±ÙyGÌn˜FuðP¦-嫾¸V`Uª -Ò2?­+=3W_ƒî¡€Y|é^À38ìù£ó3BFë“;Ùù‚Â}dËíŵœÅ>ÂØRÆÒãL0%)pgAG"7Iù•Š ë DZñi‰nv±,Š~fF"ƒñ]™A˜åDžwñ›ýŽÂUÙE’âê5UŠ\"†µ¢+3:@lGŒç]~‹+4Ä6¨(8ž‚u²`+Ô'*jÀv[ñ™zwzÂ¥d¿¯c—©ä¬@¦ ŸãÙØ‚—WQº -_{RS4¯JfI -#ŠeEìã5¶­×òì«S8¢¹ˆÉÖF`õÝñÕ¤}Iâ Ufå6ì‹E!•Vš;U!TñC¯Ð†e“KˆZPR±`-ºRÞæ^†’*¶V`Á( îÆ©qâÕl«"£±üouP r”ãΫ‚éVךdD\qå°b+LèQR…JØye3ß -©ˆ€•§JâÇ@¹â6õ -é -¹þ‰h -ž7jœ¿qùó8¾ÅÃ|%„2«SÂY¡IöŠ‰uOGyb+a #.7{sÐP*"ŽÏVܽ}ñï¦PrpAhk”åѺ»r_ùRR•$ßmija-ÇÝGg°Âñ§‡ß#ÖÅŸ4o - *„Y)FU4ÔGlu,ÂÀ™ Ô4Rsxè$ò)Pâ‘÷,ŠÚ"ˆÈÓ³´+pÔšP(Ï5ÅQæi¸ßý>9Ä(ðÐÂL‚§º°ãgUÁ:wâG‰=‡H=%ãXPÀgQç -C¹oÎEŽÁìy¶¸Û -‘¥K‚¶–ùé×\¤¡¹€KŠ#æ¢5g7 `ÇAj–Ž³÷õÐwxïÈ_IsÙM¢ --VÚQuŠayJ½JŸ9— á/SAI!!Þ«kwù-ö¦ºÊS ÅZ'\9(GÀÑ™OÄ6Yˆó*^Xý!yz–Öñ;†ñRФÃ-“–nË4aÈxb©ˆ¦ õ¤.[ô½·pOÕ™à¥8[Kw ¹ ’ÃÅ%+ÏŽ¢‚eå\–j?í ÄÃW -V<¼ ®Ðï4½®¸'ï­ÕÓÃ@õ‹’ ”‘a…`f -8ŸT ?+ˆÄàQ0Íè ±¿ræX¤…¤¬Š!›.vpƪÖÑâÜ‚÷©N“ ®9r“ÄålÄÓÉQã¡ß¶©ýì¸à’k„`Z¼Z?«ÄÏLŒFrïwuBbî"•Ø2mý,¾" ç͹ÁN[ “?©JÏÞýbIØ‚ÌÖ‹õÇG¡LŒ…/Jq“/‡ïˆ{_&“8}h³Ãê’›ƨÀå½$¯2~ÏMîy"2¹L¨LÌŽÝ—­2ã5|í#p¨ ëµ¥5o}ª¼·Tr7Uõ„o«k €Ö9ÝĘu·p“Çûºx¼‘»,̼‘Ì*“p-ÿ[á猨(rÝ–&Q!úTv^é²ä»²,ä Q¡ºÄB•¡ëý¢7ZO$fûÁz7.ýðpñËO^¿ûúðƒ««Ë—/?¿ýÅý§[ê>ÈÈöl²À¸öƒ6TÖÙ¦À¼d‡ËøBÚ.ŽÓ¦\7^‹l¸r›[š+ö IŠÉ -#G5ŠçÏP„!n‹ v‹Çe‘O#¼ ²FÉ%ÈÄJÅTkõlOAÒ 5ª:Lá0²=¥ªlOað©.Ê6$F¯1œÄl5M- þ¸&Okpÿ@y‰7X‚ÐÇ'á·DÆH}š:œü᩸ùNba%Z??®áÜxõ¨‘Çw‘ -¼±d¾€õ– Ãôߪ$K›B^u¯}Ž«22Œõ0ò‡ EM“§5YºåˆÑõ¼â—à¢B*1OT$ç9y¿a5Ÿž -1Eªü¹TKܾ¢HM`›†ïo Órw3§²p*Æáé,Ã?‹Äê/PÊ9òH¯djïŸç”ŠFײ›K=ÅI!vvi3ò‹àÂM½ætªX5;zFFÐX[é¶ÝÉè#qõá}’hpœLÙ:Ëi|D'áè$…IÑ9F4p#ÀG­DÏ‹ÓV¥b!ï_Tº¥©+¥Ð•˜Œ¾)ÍC=)P°ºÉV!V%Njü}"fè iEfsò|Æ…X½¹£œ -äÎ(€ñxè˜1¨ )iSšVr¾¤úÄ‚Ù‘ñÊj>`ÉuÿÙNGìÿMÖù‘h‡cÜγH²˜³™2NÙvDÄm#ÁvD,×CZXöýG *ð,@p]•‚ÍÄ !Ô¿ß„+tˆ²”(  ^ÉQ¤?XÜ߇Š“£““‡ËHYÑðÙ+òï+«LëÞGÎâ·8#â"o1…þ]O¦æÈÈ,ŒÆí¼GºøŽ¦³Ÿ9ü.Ãð»ÄÛ §Ÿ·æ*–-#‡ Ê°uq%Œ´CêS–¦š004¸æ%OG4’ Xa瓯)»e$/îÅSZirbQ¼ÅŠ AÁjûpÓsÉᬥ¥)&µËCé`1¬ FEsZè5ò‰µÖˆ!¯ÁÌ[[ÆŒ¹Æ3ü®”‰cÇ£"»l#¨ŠˆÔ¯‰Á¼“”Šf)3š7y)Õ#šg%j÷ÓSæ|p ‹³9Ý=ð…J¬„~,p?šænÍ6v²‚í”_˜ -K";˜"3°ê“²*ÉÊ”@¢Öl’wR(6þ¸7 ÝhâÕñsZ’[ˆl×’ˆõ ôt›gRru&†.@tÖ6Ò€!ºA ‰$Úx -’Œ^•è+è8¯ÍM³9E~˜\lKc¥Ð¹B -ðø¦ÀÎ: -ŒEó÷h#ÒÜâùG¥™N…“25<@£tð¥䞎K‚ ‚Íߣ,€ $¶)Í%Nseu§9™¿Ê{/¹Ïq"@ýj1Š’ÀÅ(SB£'ž¦~6§Ñ¹5Ù‰Ú·‡Þ‘­@ŸdƒŽÖA: EX%7kPО+ äl¼‘D³2N¡œ®ý!Æ›>O~&×½ŸS‚`,\‹;SD¹wЉîѧr†:<Üxô<].I§Ðp¹$Ð+sK÷H|"ä‰øD•› í g^kFS4ü3 UÎj -Lăj 8ýÄG 7>BI6e2žÉs µ!®iÙÉö–°HTe§cûTë¹X‘œƒ¬Œè4ƒ¥Ï*ÚÎ+B|bÉmsÒ$Y¥Á®P\Ê"Å0FH#œ«{2Ò0•ÉOwºÏB?­ÌâQ÷JûÊX.ĺÈヒÁSUYwjîén›†ˆ[bê„`‘›°2¹qb»s vH9öº¸­l\r×+„©µ²9p&¹†ÐYøý†Õ±Êvª¹S±Ž{eçÂצŽ Œž=J|šû›â[•u’ý…·¬lBVMÎ2‹¸»T”®({BFô5Yþ‘Ȩ2„%eÿóÅcÈ£[ðÙÉܤ„¶ & Î_iÊG¶IY -dÅ3Ê•ÜãT óŒ&xP†Ë­¬%ÏK  -ë,m“K“’PY»àÓH«›‘"ä œÝ±»m§` —d‡°‡Ñ=íî›AÍaØü$}W¸â -?b÷ÃUHªU¨Ûñ¦ÝÓº@â œ±ÉÉj’G<öà}“Ç -È+뎜°ö¨’´¨†\–’Ì4èµá Ñ@zÈ›lrÆH¯„bÇUNê=¶Å›ŠÙ@!A¶NF‡‡ùu&+&Ã=g/©EIêðÉnVw•3ž„šV·n)•3‘£d!Ÿ›eMÆbÅ¿—ãSKœf:[&Õ¶OÊBÜAx¢* -'¦8}Ûתž¸‰¢ ƒ ç ãâdíÝâªoך|Ÿš%Iq GY›/ö(=_ÓÑ‚‘%ÏÂÝã‡åMnlS‡4<×böC¬š^•Pùo¦1ÉÃl@$ŒÂ<#Qûž¦c%›ƒñË -˜¨ŠöŒñ˜3ÎAAÌ®tºwQ¹EÑÁó9&=åç ÎU˜ÒT°¢Ò“6½¨ Ï0p -;áÍV‰è=6'M †È˜Z·Nx<–6 UR¢×Â*z—•CôîŠ:¿³­³çh¬,Ž¨P¢›ì4R¤íXå‹“g1ö^3é5EXV%ÆRˆ ¥q§tí¨BWÞ@ D&'Âÿº¶t¤tT®@%ÿôññ,@e&³ -þÄöe˜Åª'Œœ3Iš°ª§ÜÕ+ºÿ/¿•k¯¬JG‘œ;vJ°YÖ©@b01LŽw€iáøi«{¨×Bv^-Ù~Ô -\!²\5A®) Œd!RsxF®@rAá?]åäÉéCî@fnR”à@ÕGFõÜo‡ÄÓɺ•‹[k" ÄÎH™fkÙ=¢*îÓxÁÊdUJ… s«o‘2³(ɈHeͨÏ~?ªóð©¯Pý~iYÜíŽÜ$Ʊ.òĹl<_¤aýÃAô˜¡M¬•’nÝDÇÛÕ< Š'CQX> þf[Ú>lrIÙSñ]ë‡-¸+â.#‡£@šåm'‹1Y[C£(ÈD0ˆG5I;j[ÚI -‡!«Þª„‚UÄ£ŽtyžNZMJjr˜€½0¬Më°ß“úì4¨‘§XãB„#\ƒÄBOÙ«űŽÀ Ü;Z86§˜¬‚+ÓXŽ.±î]ÐâCçÜì-ÛWTº.[|õ­¿ÔäÀo´]¶Þ6(¨Ò뇑^ÇÑuÀ[”Á½™Äh̵û]©Yª,˜9êÁÄ“Ü޽ʴfǼ¸<슇E'SÉ9qgí[,'=©J(”+1îå »kNŽÃSÝ :aä…@RÏhsÌ0C!Is -ö 1X$[ý)c£T ð>r Ëk@µÒ Pnøà -&m2g•³ÆÈ*ºËÇÛŠB­LedZ·9ø=0®ž± _ÄÎ(Ï]IÌBvc9û -°)Ý&N G/T\‘›ª˜,“½P9ÀäÀœ”+p) -äXèNÚ -A)¶}3m®ò&Ár¥Pù¥^NÓÜÝF^|œ¸•²+¹É—X¶p͸/T×>Î#›\áå9+å·(O -TîÎÙpUŽKŠ(À¢ŒN#z#¬®:ÌÁ«h¸¯®Î›}Rþ7¬ÅÈßi&‘®C EuI_J8=A”.(Þˆ|å+¶æàúh-rBÝ» X ÄIà0ÐZQ GurαŽ‘PLˆÜ‚ÝÕ\ÕJs˜˜|¢ßpÁKÃOi›!2¤m¡+Èä‹,ºË+ìȺ¢ÇØà ˆu"àÿTF6k‚1ñR%³`ÀVFÎI­«+ñ”ãKQ8žÑ)W<°û+{ªÞÀšÉî¥TVW™œÀöPèù®ªÒ£w>ÓOeS”<Æíu* IjåÉ… 4U‘GvºyfOãˆHjÊÑç®æy¤£¯áù¶zœº¯.è“z„nR¾‡2[åÏCš²QÃø¤ìä±*X*yߦKNC(‹°ëSÌ÷èKÃ¿Ú -ÇI²‰P€¬-ÞœĹº’sâ¼B9CRv‹pÅ'û¸ÌNIYÁÅÒk[¬39krbX²¼8“³°q쟎›µÆ¤ EV*󈱌vœ¤‘P^!DɨÈ©!—`BÀõPqD•Á„äQ_GÂyÀÍL͵ÍŒB…[![{$ÔýUñŽi܆¼Lžæ3É,ÇïcŸš'"oŠo™QZ?åXXÞ]9ÚÈÀ–&§rÛ.ã(CÀÆ!ƒ›ÆSpYHÿq<(œ±ÛäÃó¨Uy ¹™& -IUJó±(u±<0•½Ö¡(÷(ž[zÊÀ(oèZðV«Û@):h¥9ù–|P ïQ´¯¾h²ßWhRœöê÷gŒþ”,(Ù[Sª}ÄÅÃ5dž¤óè9Ú¦ÚšÇQ•§¿”œjõä®…"lÂýÓå$©`kAŘ°vü ± «¤mÙ)…ÅðŽ¢ÀS³ŒCšü‚#÷Ëp-[~K¥¤‘å<®äPZ¶:ÔHÁ½ËÈRèÀd˳±‚¿‡ŠŽ”‹1œ%ðÉžŸÉ9dž²³5î¤ Ç¼­K¶‘u ¢Štá©ŽÍnœ –ıÂG`v8x­9~Í‘$È¥;’rarnxW2 ’ ²7¾Ü0j»¦Ê¯;FÞC˜Ÿ¾X0Î*Ú:–,Ô#a4OR*0/[Æ8ïuòÐ}y|:›ÈQ»vÀ^Y=‹† -𓧠¯ê«/¨/I÷ß« -C8¸vÓ¶ã ÚÆÐÚ'ž={=zžÖŠg VžzEôvNX¥ÄVtg—¡Hbç Ö>åÓň¨ÈERØØ![³'ûU¦z±L¶$[3å«‚­2Í)QÇ®âúØ° Ö¼oåA!Îå‚yäSÐ"²¡’çd8y`‡$2ûûÒYàòÔê^ âÞ¶=Õ7˜³HÐ^ö8Òþ>*XŽaS -×:øŒ·§Ï# %‰7ë-ÀXc{g;/Oj òË1¦/eé_õ”‡à& ¹Ÿ Ð}{pŸ]°žˆ¢i@Õ¤ºw#ý¶8˜Ÿ¿=a2kj®”í®·Õ<:½‚ºŒ §´fð´öÖÜnÜJJO "¦jy1hä±fîé©|w=ynLcÒ•`ÊJ“ÈNY/[Ì„ -=[í¨â‹Iòtw½UëãèîqxÁ‡¨‹VP7"ØG -sGÙˆ¬”CášÕA:eQV¦ÂÈÌôÖ¶#=ÂõH Œ@®9˜Y 矻0:`ì -ËHý&"Ó=¿‡\šå²4‚#Ù•«0;4—Ž7SšyÊ)ã‡kUn屧@ö+(Fû4a™úô0ŒVÅ\«æpè§9|Æql•R€]Ðb@ÃFêàÙœ\`‰«"i?…8w*'ߒ姣§” ™ûJæ±öÝB¾ËÓš l™g•$W)Ër.V8ŽõÝü"i¿I™ÚÄi÷¡dÙ´àæ ÇòÄä> áÙ¼ZÁÕflÊJm‹tEòéC;•—нæ/(]Hy¤’Ërnž™Sê: µ¦ÕBB _O\ƒkÒAj„#»õCiÊÉa'ËXO>†u kïê€,<Û Ä³’}•ø¯ ¯¶‘[‡›MYZG¶ÐŠÕ¡”§ÞÚ–S[‰Lûcúwv¥*TvPéâûð¼2rÐAk#8B|NN„Yàƒ†&ß“-‹~ÊsßöˆÔ5Ôa…*Å.ÁÛv>cj §X{\Ãψ怯¥„=?ÑÞ?I«CɹŽÔèÆXiÁmZŽNEb§-ß@w{E‘÷²ç]<÷qÍúL¹>‘ÓÊÜðBWZc‚E£¿I! 6ÏŸ|LA°ºC6dØ#AÁ­TñÊœ¦ -È«–”†^š4¤…åÑF&ÞBÆÏñ¥ ‰vÚÜõÁ—aË° n‘èéyƒñŸçƸvÙIæ­(#…r¹§â¨í™˜Fj*ÌÖõSJ’VTÈ\òwÄeÀÎ`ü´8ð*ˆŠjÙØ‹GÅUaÔ$AÈ•#¨ŒLrĹöè@¥ÚÂ?±„©!oC£„`××a{íÊš”µ–ŒUKŒBÁý¶Ëü«83ã(s­ýöT«Ù³¿ÑÆšŽª0-î™N7”?d²/U˜Àê52r(N7‰3É©A® !¡HæF¿Áý —T«Žž<®ÑÀ”€AkðÕsª9Ó­ð+OgæP¹ÆåeÝ&ÀsŒ˜,G†SέE)÷M}eƬ§â>B0„÷£é0-D»”r˜x£¬tìÁEƒeàæeK½"»Œ™ˆâ‘ (€„|9 -‘d=±ÚÈ5?Aüfè¾ýˆ@ñOPF,<óp™žú -‚1üíH5šKpó®Œ[Œ®eaˆºmî¬vý=ÊŠSÿ>úB;OÀÃÿäã‹ŸÞ¾~÷âúþý7/î¿\üß~¸ÿüÞÛ{ú‰_ܽ¿»ýt÷ê…½âä½ýðƒþñ¿žýDù,ùåNÂîÉõe|êQ’ðÁ’ç¸Å˜5ñã±fgPùv°ñ f­Z| £Ý0MhÛ…,пM$|ÊD‚Gc…âgOP7÷ßtu½ó;—p÷®gBÙPÞi·µ¾»cìéýÒ KPu'”;æÜfsáî…à*#ø;àv¢ôI>>ñÚI~(÷¹×®dˆu}§}_x‚Ïž‘–ö–=lÚúÓhìyâÏ?DcÏHÿ1=aÏøé†ð4 -»<ÔbœàëØjK|½÷ŒÜƒ¯<Ú¤ó èº\qsx ºŽÅ¡‹Î=µŽŠœÌ§ ÖÉ})O†‡Èêr/ŽëÓ€êxcz„£^,özºî3]e½?†Jç-q-Òé[™ÐéÑ ŽXÒý::œI®UðÐQiˆŸ‚AŸá´ÐÏ•yI ÅÐ󺎀žÇPç(-Ryá¼!½´u6‡ï[âÓpæâkŒbÞFÊ…G -Î63¡=_êD-'Wغ”Ç`åm‚‚=…QŽœ,$χÐäm:±‡ˆä-{²À=yë>ñÇ×â*ÿ ;ÞGR³=Ú8ù1(Nqû- ÌçwXO0Åyv{@‰÷‘=b@ˆ#MÀçî‘ÃWP}×u ‡’áv3qÂé8ÎÃ{xp"ŽÐRLTpå¦t¿?rÄ… \¬†£ÜÙRYÚ *¸sZ¤7©,¸Ø DM+hÈÿüQ¼Õ\aVîºÚj®\QV¡npàúMœ$QW¤ŽÝÃkÅË”üé68pæ Uñ„_»rö0à}8ìOôo'kÊ’X^ÜпÅ*Éb“e6>AÿöB¹´geÞœ@Ü8o—]n¯ÐÖâiÅÐQµ ¾ü±8{Øð¿ÅÈÖõäa7óeñj'¸ß}'Ü7™Å±8O”o²S”SlïîÙO'¢·k`Êä݇cßÍ=6ý„íF)Œä:ѺéE Êó‘æ!B››¶´#$7ab¨‹wHÜMa)·V¨m”¼ ‡‚3Ão\`4sf»tÇ“ž0Ûe$·ß£k—K:ѵIH ˜ Úd¢FÙciYTÊ¡MÖKÞ3!´ù£ÁC‡ˆÕƒÏ„˜­lÖ§ ” —–Fò-¬¥°n°ØÕ]¸&6ÖhTk{0l%ˆ¬qb`²ŒgôNy¤êØ#^g%× â5¸d@O kðÕõß|GXkò×’74k~CÍö ÖÌobWËß¡Ö Y³CTï¡ªÓ Ç¡;­°D˜æµ,§xÔ’ù{š8Ôqœ¦~ú!O<-Zz {ÀõlšDn …L’É»ÈØ!¦aT ä{diÏÏ6@iy9v™g¢—o9ˈ†œ¨Ñ¼§ÊƒÃaŸ ™^—r‚­,rÈð{J÷(Ð,G„Æï™oMþUL#ÿ™¬¤>Jʬ†KJÄqkö\¢¯¨åLGJØýZ³òDOkÙ€œùM2•‰ßÌo¥ªÛÁ6ã;YÜ×H˜Ú K› ÍÛÏ63¹°8ý&$³É»Q)åFÚÿ‚ijÆmÌøÆT7Üe°Ÿë’7¼e¸Q{˜eµêÉldX„–ml‚*SÎ,ì±”‹'žÊ8Á õLàäBF›XNð’¹7Î<‘#~Ï!s ä#Ÿ@"ÃQÒÙ‰„Lð»a “%Oˆ=î1ItvÀîÉD9'I‰wàÆòC]ò†iŒ¿gr¼LQ!à…!Ï{cà±)LàbÉ\ñˆW |¥öñ§˜h–”ã†S,PÊr„'æ7ñ{Xâkâ†FÝT11ˆy$•äá”|5Àa¼Òj=⠧䲇ÆÅ$º TË€qï—;›W:B?ÁÆŠ½ÃÆYVÒôD ^4ÿéRð¹|“&B0§œOÑ8õí¨u$Þ#0‰J;¢#íÁ½>ÁüIªµ#Ô/ƒ³ø]Dæö°¾œK¤1šh¾Äk%O\ãùŽ²š=†/» t/–æ\ˆ½Ý!$N€zñôXÚÐKÒ¼¡òÆeF»—e“`¼ªãªnÄ–æo=Bï6'Fâ.IÈ5<‘vçï=Âî¼7‘u»ëP:.ðœè6v@º¤“÷À¨ØŽ°¹ÛïZî¼7ArÉ^B«6îXf{HÜ®´—yCÂM®,ßpA#ËøvsŸ€·8£*òº]jðá–Ý)×Ö qû#^¬®°m¥™ éÓ–„fŠ"˜P¶°Uü¶²÷”r\ ŒY‰^­-‘À ¦V:ˆtŠNk{“-0QiIÓ¼âd?Ñhy¸Ç܆BËXŠ™è³ ðÚÕ}ØÄl.RÅ°YS GŒÙeÀ,MlY”[i=”Åâ’=ŸçÝ¥¤ ÝAzÝãÆr 5:cÉ•Êi2qbÁ.é–£_ea)2úyƒå÷*”·£E>Ê— mد859€ _¬å¦´…Ö+lXMaÃzeŒ=LÓ“Å£‚Òkì:ïM@Wü„[u`F 4‰¶Jõ1ܪ»Â’“€´€KynU`ïT㺘Žh«4?*KMñ1ت6ñ"ªM©!ž[U-\„\³¶Ç`«ÜX‡ödU7Ñ”ÕÿiUÇTͺ‰™ò1Ȫ&`Õ–°ùdUÈ©Ðj,sJ¦Q\Ýu¦ -Ðï1Ȫ¬àr mبûcU­aÐoêªìOƒ¬bt–iÜJðØPV™z‡+Å÷cï)ʪï0Ä‹-9 é(«òÀù\UP–¤Äw:?ÁUymëw¹P?®Jﵯ¤Xý tU¾V^xúò!ºªú$ÔôÅyf5ÄþTÁ íñ1̪\ÆñçÃe<‡Œ˜UÕÕ³°yOâ­êM -OïØfóc¼UÕÀŠQD)öo•JKCD0Oâ­ -'tl·˜U”ûÊŸÒ›4Q™w€«Òx×ú4ЪÜÜ»*ŠñqUªGÁoY«N¨ ®ö2"ÇžB\%õm~ïèh@X“õþ!¾ª„¹ÌÁªR˜c}§ªgZyŒ¢Ê[úÓØ©hž÷2UÊWÅ—ÛÉéx*C%ŸoK‹Zx žÚûT{ˆ™ÚÃð|•ŠRavO!¤¢Gãü!u»€Q)€ùzu#2â! *rz:ͪO¢ž’DZ´ÇX§Ò;'akºåÖ©œñ¶EÔ¶Æë ¬S¸ò9Œœ(@Oµ8Ý´™ } z -Æ ˆ{°Ó®Tâå°4Íâ”ßHœ;dSn%‡•Ò—çe@¦Â1\Ê ~i+›ôüä£m~© nÚ>8-y2…<+Ÿk3NÕcž2Õ¹KÈŸ¼êJ} dª%Aà™½Ì©æÉkZ w@¦2ݵ´á—ê·rvØ”ÁîNS(Ev„»=€©[®E,Jщ_êFãÜhê`*+3‰\±M¯¥ž˜2=`"± `ªTžUÑ©€)££pWc^ºê'¦JùùýFxïD2…#rZÈa›'’©fŽŽ˜ÌPNMU†ÿ"³E¼Õ@4U"DY½ÐA…º!šR Dǘ’ðÅÜ#šR(|Ô†·SZ7DS(YsJßMUPt¶ãyOM.„²ÒÇ•¼!šrTwPPHj‹•}B›r8È)ÐÎ&ן@›z QD1lЦzJ „=%ÒÀ6 ž&î >2œÀœRñ]³B[eƒ9áGŽn”¸=5垇ÖÈÐwD5Usù&¶¶¡šª¿) -”Œko -ƒáNªì@uØbѵ‡lzt¨R®Ë2kqùÞ”B¥„«J¤V7|S -”ÎÜ -È{°á›ª@Y±­ ¶p‚tê…ø†Y¡Ò ¤S½').«lX§€+OrÑì±NÉÜ_ ÒÒ7¬SÐOŠôWjìuŠh¡|5F‚V°¦öX§Êd$az„oÊL¸,É &·Ñø¦â%!T"¯Ýàè!KÎ"úöéTÞïƒÑ88¥]OÃCæØî3qpÊl*$‘Ì2 Æþ)€S¢—… C±ŒˆÚ€S6±çZ!”9†Ç§A)„ BÎC{àT)0äwºØ•øà”ÃC7<8•î'q«‘¾·8U¡ÞEYŽœz >–IKñ1ÀéT.mZ(ø=™É€NN=Snd­  ®Ò) „bWI^T{šH§¾Ñ`‘’ü¿ONU(Ç7ÄÁÐ7¤Súà!¤‰Ä}C:¥ÀƒN'ETØ#ÊÁŽlôVÈÁµAªÀ9ÄEÇßÄ:õÂB€Òðl*9zG+€rmX§¢„!m§0R˜ŒvȦrJAѯLüD6•OP N)ÇàZOAM9PYó™ùák„iåä²×î¡K¹·8œ)¬?åmà@¥'’h‡OZ³;ÜLXÒšÝÚ3ªðS÷w ¤µmfnÁ‰µ:àCê·öH£ë@Ü·ˆ¼ÁiaŒÎß;€ÑíÖ]ųqEÁ¼äÚ㊶âÁe[Gò(¢ãç;tÞˆ¡ÖqÅ«ÄÐuÑ_;œÐuä¡œ8¡ë2l÷Ã)n]Üä²G]×ýNTPZŃd‚®‹ÿî1@íCd˜ÐŸm„ÓMÄO2¢Â›ï>˜øžúòë‰%vd¤ØÐ<¹ÇêŸ žüÎ;ìN¬µ8ì!;±ÖÊ+g@vbŸíŽ4 ¨NÙkc8Aè¬ÉóŠL`N¶£1¯0g‹n•ßãq‚eQ{Ú`8+*›\7ôÍÝonº©[î÷)!QÔ„Ø$AqœÎÖ°‰ & &~=Á!T1ñ3V±œàg*˜ƒ`6/%¥ p™²x§v‚’‰§ò› pLBŒQ-NPLz!H™¦zB6Y¥Ã*ò¥ÐOphÞ^*e -ôhà\*¿ -kkÀ[Û8¢Z‚\#ùn€Y¶áE°lûØ¡+I¨+›Å¹ a9I kpûë†O‰?¹g^uºkO&åÅ؃P*—îÚ'ö$?‘®&öäLл‡œ\³»?MÈIbk1ÒN¤I`8q÷ÝLv7un¸’h´1'œäü½C‘Ün ðHòn Ìp`F¢Pjé)²GÏN0"Q›¸ooÆŸµo‘J=*ïK“ËVÏ-°DRèò3Jë¸!EÊoRØF3u@Žè„;¢ãæ#<²“7æ°DÊ­[ŒIc¼9G“æ àØ÷~ÉqØåÐIGO°$ „˜ähÂ…\‡?ÙÍþ.R—ˆž°¶ RŒ‘³r˜ƒÒ ¤x"YP6Œ Òù¡eu~H©‰§Cv©L —åò!4á ¹/<†F,‹0ýFÀ®†ÌJX¿‡ƒƒŒ}·÷ ©4¥H/è¿çÛà ¿Èi]ŸýÅÁAÊ&äq ]®'p*”%°¹cò„ƒtƒQªn0*eƒ}”}'{ @ðöxÒ1»1Å8EJi°•™UNï‰ØÂ0!ûÈ<¹Çdp=ɉ1ƒ¨q&ä#ÐÄÄÈ¢àL"¨{>”‰‰¶«ä€ºÁ@ª@Ÿ¾‚„Y7HÈŠgÔÖufÂÚ:à€&ä´óLøGåÕ!myuËÙÃ?Ê:  ¹˜ßãÿ(mæâû;ð…þQ*[çƒMŠáþñ?àµ#=3hÁ5Ñ 9{ôǧ -§Ü£Iv¿Ê1¢O»B”“óôQœŠ@¬\Úú ¥E,‚ 3Ïî@œ¡L¦AH»è£„A·6QYo*I¾tÇ¿ÛP fÇagÂ>’›Î÷H¤q;GÜ©á]&¶#rDtÇA(¢Ë‘ ÈÌ\N ¥ï•=&Û‘ú†íèQJqD)Õ¾a;ª ôHG¿WÔJàæÄvTóE’PPRî‰í¨NI -"áˆ"ÝØŽnÈ‘O¼ýiGGiw\ ~‰âº•9GªþNPÑ\GË8¿ŽCgb:*r;ö(Ç‹0ðÛ:3‹;~có4!{ÔFn¥X£²w”uÃhlÕe¸=4#þøʬïÉVfæblÕóúìeëºá.6ܳÒní0¤²Ø°œ…º+VD‚%làŠÂ/맘ŠäœW>Ú©Hrz%«PŠJVßN¹… eà&Vi~'Xb¾L{ŒDyµÖ°¥¨jX#pµЈôÅð‘Hd(ÎBTÊ8ÇþaÎá{ØC9ö¶¡ææ±ä|²OAQ;àÊ3± ‘Ýd20 ùMÀàÊ?Z¢Y&‚!.ÝÂ'À…Ýå´B²ýà­5A - ¹ƒ6dB|y ;Ür¯•#!À¥´ 0adÏ=ì`‘øˆ6Xpý>y„Ü'Ђ¤£S¸ßH€*I+× H8kâ÷ød!å56 -œ'Z 踵ìA¹‡’|‚"!Op@ú¡6v˜€ÄE4 q "ÐaF3—á= ŸŠ¦oÿ!IJT'ÞIˆÄÚÃüõ!ÄNt?åÍ(}õ#hŠ•°õ#LÕ×Äò# EË„ð# ‹î¹oŠMÀ¾ÞüÄœ8}üž¯™8}2•ºò;“½uÃé›ÖÌ Ï·..øîQùH…"eÃ@åšBë_—•j=ÁàÃz«ää)Pdá´ª…™¡Ý6Ã@öôôAù=Ÿ -eéµvÛØ@ø‚ Á¢¬ðXT7>Ù%®,ðOë …Bön_Ö²ÁñÁ¿¸†]`€iƒã Ùž‚:ÓfŽ‡G¯¸|()s èŠ7X>…‚&¥¬!—G>åS¦C7!„Z6\>9je9 Ù /ë†ËçÎiî{„]è˜O|06Ù -½æ ˜OKÁ]¬3!ú¾ÈVOmì÷ÑׇvêId¾îþëðø0¥aeØÃðÁº„õ è{½{Ö#н>"ØaíÑÇOBì)×Kvýý ²^Y‘â¹’Øó1Q>®á»NÝ#c×ðD%vƒSfÈáOö;ìö$öå'ö$Ü7GóØëIN¾’&a|‡99¸±×ûðdv`=}urÿŽ<=IBYLé3ò—ÛÐ=Žd=yþŠ<=_Š\6B>÷ÀzªAz1ÚÙk<ÖS»¢=ѺkRïÖÓ„H60^œ4ðXïÑý=°žŒN8 -6ah† XÏ“Y!«<µ” YO^8w‚0‰ÃÍYOöFP»VãÖã~ŒÉ é0oÀz©b:ÁÓó«XòÌÑ“7<=Ì¥«ÿöÀ1*j fì)ë žž> aô„•'?&¶Ñ £§ÀT¹q) -öEOe"¾¶pÙŸ0z*Ð2±,ä FO‡—‡ÓZï{8Ñ¿èyüV¶&«L>Ê =‚D6P:Iƒ·‡Ñ“ò'¹· pOOΰÒÇU× OO´}'0zÒiFˆÒImƒÑƒL¬ž›Æ–U茞{ý$÷úÃÚÃè)Õ§Vj ÓAÛ`ôÜ«æâPëæ8"R€Øïõ”÷•0arN§<=î»›éŠ@ÝðôÜV*¯€çó žž¼U•œ•°nxz*Â1÷N<=µVNPôð,ñdÎäé^ú†¢'P“à &,§ ¢ÇšÚ!ñDÖE¦S:BÝ@ô䎢X’Gs”¥{ì<<ËNLU©Ÿge[‡I@gÝÀó°nk­»€Oß=BùÒU£HtæR"½•n FL£eƒÑÃæ?’g›¨ž×=!@H$¿ä Eû‹’<ƒHÚ†¢§©à’,Æ'(zJ¥-rà7cÚPô<Çö"  Ò?M=µ&${ Æô=ˆ^Pp"‡•°¡â¢G -#·"›¬Žr‚è IB¯ªYÛƒè¹kÆ2\3ZÝ@ô4òÛNŒAÜ@ôððLF¤Éw”ê DBïœ0Þó¢”÷™OȲDO¦7ùñe¢G¡!*¹ÃŽ zzHÃmqÈO=½_®ãíCGaX ”W`€é±:•¨¿‚Þ]ƪ÷¨`¯÷¸pàìÑWˆ’Ms9íÅ2âagOÍùh)€Ú“àǧbót[¤j놳÷èþfïaÙDÙ w@²2ºM”½¹A÷àz&úlxzÊ@…œÒÆ!• <=9Iø"Ø¢€t¯ûîÙ§Ô©&žQ­è;i{䟠,á&Ø'GåÛ¢³EäÀ¾÷á“í©¯°Þ×…¬puÂ詶£ÐfÖv‚§§þ˜* rÝõäUH¨©n'ˆzRZ;m#åPO88€Ox&.7û4§0aû'Gwñ>ÔÍ’d@vëO õȰĉ¨ÇÏêÞ(H4놨G %K|["(ÊüÝnÏÞg\$Rªøf·§n S¯ö1Îsm󆮽Ƀù²Û³°òcSÕ/.’é€<º¤¹¯^eoÓ5à41¶×R¹›}z%´ÇT½(g5ôŽ>njw Þül¢ü-REë¾Ò ÙŽi|D9ÌŸÓÀÇ^‘Š,Ϻžã4uGîâëÿä‹Æ¯C‘`Û¡ñë}kªkE[‰îÛÏ.hôcÚøxŸ‰S‹šØÑsÏøÅ錿ªnƒ³­”µ}ÍÄÔ­ðg;3=Ú‘À(³öQ™—µRèúÙ¼ ¶:LÛ³ _« o«2zs,¬?;”©0‰7e‰vih¦´êüéÏ6dü Eªí>†°¤à‡2ã{HŽ~ö£¿ o[Œ‹lg1T@úû‹Ÿ˜xÃùe#†ò'oMù‡q ÿülÆßèo»0ìqè¨n—0¼4ïX}\¶’ë6ãߨ¡–9ÿ‡ø³)7ÃFyµY¸Ãòƒ‹~$ò±þBüBù“è0„c}!­!Œú'/™ÛåÞ¥­¹ìïÇÍ Hμñðþ=¼ø7ÆmÝÅ¿¡UýlÙuõ(q}œºzÍšt šëc.šÞ%"¢$ØM$Û†‹¹*Äð'÷­õ7k[ä„m‘`x›mQ‘¦Ø÷³ÙÖ«rÚùå±Õú²ÖbÕŠ?Ý—£ÖˆÚîÇHë¬Ý²ü³˜ -ýüÅ>‹$ª²\³çïø˜e±!úsòÈB0Øã¶Æ¢sÎmˆõuû« V€³÷«»¯ezu•0Æ/¦Wkb‘î¼®¤ýdq¥H]ûÕÙê/Іû—¡U6ŽÕ_š€Š_l¬è ¢³¶Ý«˜ªzG—i•Ùö«WUKܲ=ª®Ú•·5•êV£ÿâH(’¹|¨È«4•ØöSHQçoöSëÞ÷þq:qŒÙ;žª8?›Lªßíc.¥*µîŠå*u¨ø”Xòc+Eë3Œe'Åbx[6RßBÜ¿eü̪y'öÿØGhcÎlר­Ü¼Í¢€4 ?ð³G%Â¥m …ò1év„¥9ó³«Ð2E‰à½‚7Êö‰ß÷¯nOãŠ6ð6y0gb{;©¤9û/–N£Ô{v -e½íÅ„òeÁ>ÆMÏÑÌý7*DeÓ„$lÁ­?îLüa»3½gt†Ê” ßÖÆŸ½˜è¦‘l &hò,V{ € -°Ÿ —Vwêše¸ô´è`n뤻p?Û+Qr&ÀØ›,=Œ-W¥ýïŸÍ”ö߶‡Ò]V;Ûé.ÏŸ=“`X°@ì¿=g¢‘íôù÷OÆHûoÛé®æ¶AºKKðg÷#è% -z–éŠêDÛìè.:ÌÏG|戃‘µ¶[¤fûx1¹[>óiÐàåcã©,Œžöí\ƘÕÏÞEuÕÂ-¥LÿuaïŸ5ñsüâPô”Þê6&²lûžc"Š¨²O~ò#z*·-¢g“Ê~hƒc~rÚ¶Ûlèy¢üÍcè)ÀÝ­…ž‚¥lG¡—Tåè#!lXöòoé°¸ û™çú¸A·æùÙ$H%æLyi u,d_ã' äsÁìl w¤ÆüÍ÷Çû[Þòßì~žr°øÝä‡Ö¤Uñß½}¸R4õy‹—ð͡o–ß,| G+(ò' ÊË,*쯜Ñ?ö|ú>=ÏF‚”=Ï“>îöÞÙËÇÏ^¿ûäÌ£L~7ÊùøÉ)çëo±Ê™¥±úͳä¾6ÈøÎ82ýÔo†8PÄúͧ¿‘ý£lº1ÿ`€#oþøf{C¾um?œßÝnè,G‰é7·›þl—‚ßLn8ðTUö›· öàI¿YÚЦýËÉF,Ó÷ON6 3Èë¶ãÁ(öèRQLê @„ßê^þbWÓ¯@€¿ÙÕ¢¥¸øÝ¥¦S6=ÿ`…ÄÐòó¤áÀ˜)áuÿfF£<åûåAó{À÷׿a0ùÿ{ÐüåAC7SX2vê¾±‡=0z(u÷¸Ò8JˆŒº9 -«eF5dm„°F(lãˆ7øËvÍÊø”1ò@ó ÎûüB‡œÐ¹WÒ3µ« θ-4Ÿ¦'|á§àïh†ô°RcÙx­iÎ;0߈x­l«c‰Y¡Dž·ÏÆÍ3èÆñ®i}Çî±\ÿÅ^ý4ŽáQ#+gF™ÜÎзD:Z@º¤^m7ï¸p€;Pðî-LÞ€M[bŠ‘ÛX)Àq=ex”Ë$»°U…2Ž¤›ƒq仑4~P D¹W:H"¶r"LZq¡°ÐqÞÜMÍe"Ä0Ñz—5ÖuD×Uò}Keì”ÉŽ“<¦–ë|†_t›ôG1—;¼g®§·¼£††íí£­ä3í kFˆ%(ů±‘}¿Û_ºî`[J•êã÷鄬zý†µ9·û &;§ŠÈ×yF0ÈųuÞ¾ "]ë>]E EçØÊ4¼;: )F£ƒSŇÄ·—£¯1"…Ü0ª×WzÈÆ\ÜÑxš -—ïë¥åòX+0aû]“ e¼]ÎqÔÁò³fŠÂÚOÞò›öÍ[OÒÄ[¯£3%P¶5Í¿5b„,Ü1¢¿ò=‡kËqm§?Ú´Ý—šœA'fLÃlÑÓHèï˜í–ííI³FÀ±ÈT¦AŸëÔŒh]q -®³`Iðz–×ò -¼^ºëtš?AF3»»óm¥ ¶RJH¹¥n–ë]g‘ñ/¤€\pŸPøuâî¤U'wöXOÓ¯ßyæ+˜õ…¾X|œ®Èô9Q.»qC]»ÉèÛá ;¡S± 4d‘Íß‹ FÏ ›Ya}#–nÂ?O8qÜó9«Â4=Þœ”èÍA¶2P#EŠ`}5²á_g-Ãzl|¿š¿¯‰+2¹îcÔê~du_‹lûñ̓œ»£ž– -x¯<Ñï}Q \ñ··ÀY—¿ÒV -Ö‡Jî¸âŽ|¡_ -tP½¸ÁG­L‡Q]½L£^&Ef6¿+­·ÏOq?a”yüäÁ¨¶vÙZ#H \E(ÁÃ8Nìiq%³@x-+õ5 ¹ÙØ™ö³-hÌ(qä|Ùy噈ƾ柩܌Š…×»O`÷jJ¾âéôE©<È>5}¾‹^7ö©õ˜3pã/baDâã!0°Pry:ßj¼”e‘=ÆǽYž×òÄ1~b¬Y¤©¬ß³ë ‘²'ˆPŽp=dü*»KÌ”:“¡Æi+q=ÿ•®ŠcF»€<Ö™=ÅpM]vžÕ¿WAÏr¼Z¦mLÖ¹£xk„#sýÖ¼»Õ‡S]¾XRfŽÇämÚzJïrIÖÛQ#F÷ôcSúeœXçEĹÕ(BF)4ψ(ˆRL¬Ë|ÁLvÚžð‘…Q¢Ä5õέòºy]ú>Àäù¹Òn¯82íkûR2#ÍáM•_–zÍJñFÝuÊÊ>ˆêRd˜Ž:èê@¸E¬p(\™>ІɣE*-ݦD¸:Ð1®ÔgÖö©BkÿÌ^ê<CW.3S 2-FŸ'…‰J>Ö€÷²— `ü¾©æˆÐû× J»Ëªª¹(ºîÀ¯@mg¯R™¹x­zúþŒz¨—Ï(ëžs;'Ð&eö)Ól‥ªgć†¾ÖĹá+%>×ÛÁ¨¦+£D'J --_   -¸Udn™ÓkDs§e©*Ù­ïE;²Ø85¶bä¸Èצåò‚ô#Ì]{ô¸)¹í¤¶1ðâë>ö#Ù­Ž–ã…Þ¿HÕ`Tâ¸#%ÿí≠ØZÄ;uŠ]e‹â.R`OÖœµ~¸û1i[+réö†°ÜyW.Ô9‡e 5°×E|”‰ÑŠem¾·¬ðèͼøD£HÂJ»¦Òk™¤¥Ã³F€:p€+kókUþ0ÒzHyò=[L “f‰dÁƒër=ýôºÑ ^ûÛ/ÕÐ5¢uÅJ_ŠsÐ-db›­ÇFÃxCX+}öRfúw8ogÎÚÎÈ…r©¯ÀþŽS ÝBÈ£Å((} v$ zÇG„—QìVÔêéß3ª_ÙTufÀp£FÐ@öÖêk^/L”5‚º¼#¬˧ڤ¨¹cXÖç'éÜLÚø@”òØ8í NÁhÞÞµûdœEà\+¼€|“žÅDRΠk"ññ@ÍÖÉ h]Ü£1EŸéE’ÜPú8õœƒr¶V‹{«Ý’”‡¿å›hã»"™X4'õZ)šýf¼LSF£uwº8f³&ïé¤fä/+Þ©˜¢•×òƒç™OgÍÛ3Ÿ–‡=Í£F>þš[¶aDØÊb‰Ë)µÝ5àÞ" ²•8IkŒêNd*÷9ËÁ¾Í=ˆÐ>¥Y:(Å>Ñf–Â÷`Ó™É6$ü%8:+žä0„š•uÊÊÃ7/ORXe{BçE?fÁUó¤¯åÌÍø2tLtÔÈ|Í¥ŽŠó°:'^œÌζ9sl[¼ë ÛCãöu¡—”•âp¯éÌ~ôb1§$'(v8ÏÊ¡ãSp‘3LãÅ,D3:ÅZ~t IÖ41«âÅ637ñ7Ï -;²âÇW(ðèmXB‰|õˆ9Q)Þ­(ìž%e~¨AλP?ä)B=ø³t­p:-Çž”sX®UcöNS$Èi8ÞOr[©ò¬ ¥qé"Ó¨Ò²ÞåV¥Üä$²9*I5$[q|Ûä` %Š©gU®ëæEÏå ŒDÁ Ð"¦õ$«—[ûzª¥]#3íIª=Õ†ø=F)©¤dÑ_CU]_xÈØ­uýKc!/áŒßØ -w3âM"MåcÓL„ú!„–Sìz8b3±‘=ç¨"o‹þc¿h8ë±¹‘êîõû §úœrÒ¸¾Yl“}§,r±J»¾+g8¾ÏRY5Ô¼]O¾¤ÊõsÝN…ÿ©2 ßéçñü‡á]äü¿úþr³ÝUb¤åU¢1ÁùØðØo§ÝÖþ‡ µoRYäLJp’­ë{‹ëö: ë4h˜x’¬HÒÌ`R'fÝ*Š ·:sæÉÜnwßÖã‰ùÚÕ®3S·V=ëæ ƒÔÒäïYG¥Cºp>rùD^z<åÔ¿öÙ›“HSRÙ ""¶³lÞ||ÚŽxáÞ;¢kµNê«9±PtÚˆ+½lfd€¦Ñ†ZÏ-µ6N¥L .‰ô7rN€\;d«Y¹å»Œ³›ù lŽ²2[¾^Ç­Ü"zjÊGÜç"Åx¸š;bĉÂèr [?žÖÈ×ù!‰~mp|µ”Ë ,•}Pì— -ò/B5N3b´Œðøùº¹³åkô„OsíRÕÅbÄù7ìu„@M¹Î¡<ÜÕFÌœˆoŠ Îc&ò»®Ý]ˆ#E#…•píUxÀØ«¼Y{©›½þoÄzè>iu¤Ù·fP¥˜) -k±¸­– ®ßd+É•¶‡¢ï{|îšï¶ÄZ›V´óÎÔ‘¬=õœŒ«öyçŸ -ÊýLB ç÷»èm=_?k¬ÈEÉ=O†ô9+Ôòp”ªïƒ`ÔxÊÀhž4)Ônb9ØQX$á¹¼µÚ¹«£×ŒsÅ'”Åc 1ûG4^ùD³N·¸z+nN«¿,ƒ.NR)„¨y_½dÚÖFz‰¢à“-u^é"y÷2©¢-הו¸„XU©¶N3#ÎÔÌ.z:Á`;xœ#9]s1eÄóÁqpƒŠ;û7ßeánúñ±² 7Lú!+wD°=”ëSšªí3æç f¬QM퟾@i‰5B~‘×—ë¾H·ùûÔÀ4x-ª,7»¹<>kðj²t×ÿÅ–q‹‹åJmmVhØaW5M: •¡Ï+u"Uz‰Ðîƒa‚Í•ÏÌíiˆfÀá(Á¶"?ý"u¼×>¾^$‹!ã`@µ–Iø–D” WbzIç—ÈɳYo~ßè˜7‹09èÂ]ucÛí}ÁÃ`ŠŒVV(V·'îÑkHU·N§#èDq×ÏcÑŒÑ.¶œÇ]w´XS(Ù–õ-õHÄ$b†þ§#è 3hφwm XéöûõC|)Uwnj®Äbí­ù"[™¨m›;ô8û²¿Æ™äLco®£vÔüñȬ`hŒ+ØJgÕ,¼cÂŒÀ×-BëF^€—ÐæÏ…œ{ -VμVô®q)íôùªöxißÎýFzä%t½AüpåJø$â3=üe€0(Šª–ùÍÁÅ\vž¼eîóL¨§£×æ6€^@\U´N ‰œ5½a'`™-jN»x $&G܈8ðt'ì© CîØL¸²õl3êɨNþÁñp®g„wÚ/g° º5¢Ûó®·ñ|?çµ®ƒ†:ÏŸQBb(¨“ÎùÍ48ÖË!Œðá5¤[žºð3È¢ ?ñÞŽØ{÷%Íþx¸Ó2–%‰†–˲HÆQËÅ]Ë/,þ‡°—ó‘ÚLDqð_˜–½ZßâÞ.¬¤ï’tZùóÌÔ9Àì®ÍµðXM_AÜï–h‰¡W'väà#*”>ôå‡ôK£ž+÷šO7 (—’¤ámYÛŒO5äŌ©VÇÄ¡¬ùÜ"ù.½±ùiAP¼?Yîgð`-.¿k–Ëák»…gCYf½'¨RºÍAÉ`‰hk›i¹?õ•mŠ"$Ю³ãN‘^&ü¡Ã÷öBMî,œkÑl0¾x‚ê®!*-œPÏeÐ y‚\ᔋå5á`ndcÇ6öÒ,¼’I°b¡~€õÒ×pú)u‰h|9pIÑAHí/…uEE2Exz___ œÎ…£ž£¡ª'ÿ­º#Ž@´×€®7â{f©â‹Ö1©iu*M;ÜÓà@äÎzËXgÀxR¤L\#¦è}xê% »MÖSLò>ì¸wëQùbsѱëCáÑ€HY(ò½Íhyò,ägŠfÞë´m„Œ趂é7(0û¬¸lB¤øÑÕüOË à:€dH…£Æ •”Ys@\¹r©Ã6B{Œ{êügT.uS¥p(;QóFŽ}½Ê)B”„PCÄÖÏßZ¡Ø6bøgðdD&¤‹cê³%©»ðßl¹xq¾õêh!ä$¦@˃ ¤õ¾_åš·^ªé¦¦"8{$?EÞ1Ü[^Ö^új/([Úß|$À< êŸü$; -}åZc¯¤çÉâÿñ5|nŠ†ŒÐ/¦›=9€Z¿GPŠ¿»ó‰“}ðÄ?X«óž TÂ|r~>ã#ÇsÞ+xÔ§ú’âá,_X»Œ?»qÕˆ³‡ ´Ãîw±=ŸŽzñœ €ÒN¢pÏ£?·½¬gvPÖÂu©stÀùCî¡¿X:˜¦ù6¿AÂqS‡ÛòQ»ÃÚ à:ƒô1:ôL͆kЩúÔ¥_6µ‘^Áši•ƒ@²q‚-åS›ŠS­ NGˆw[#Hë²³îîWî®ç`'áG1ôeãdZ¿¢°NÔs¸žg$ Á‡ -¢\±®Ÿ ½l2ïËã§uzäjƒ~l´–>û´Úõ²ˆLÉîÁ,·]ƒ?¸«\Á[íþZø4º²¼¿¾ý âŽ><·çYoKõÿ.åwü%ôW† ­dt‡¶žÇÈŸó°Gï¢é[ç4°¥îÕÛöáê÷œjeЋ>|hP€ñïÓp‡üC—²‰BÎT³p˜` ¡ÈÕ_#ŸŠ#9ÇvUcÔ“èþ•uý„${©Ó½‚ÃF<×éå¯)Šº#Î:-ì&lëNÈà~³B1⊊»nÇO± f± JäŠRK¦«"ð¹¦Ù-ÙÓË\ûâ“€)<P˜\Á,§•“ƒGÎxOX~ɼÒJn¾q5ŠŸÆ(ðŒ0h`Íù¹Ì”µÒa fHEFT²ó&>ÞxjñbëU~ؼ¼¬Œ\ k'Wò^ÅÚ¢FOOŸ.–¿þÌúEÛùE͵ Ϧ¬Ö÷tÝ ¯ -¨a³5~@¯l_Z8œüˆÀU^h‚,Pk²¯ÕËÈ -*ØÀˆ¤jδÂèušþæ‘ŸÌZUºc;Ön1¡‡­Šš­{n)[ÂÜ:c¹ÖЖwY!Ä•½œëF"IRBƒ -°îí¬j;läçö*L¯lÇ:x:y)>½°Tx'¸¯Â¯¨µõ3«+Y/eIèaÖA4ÂÉ™s¬]•ñ9v‚¶Ù î“jåpñXÓÆ·x¶p¦2ÇH“Üé r±±Ðù©„WZ®Qgþ©.S³‡´è’¨R‚Ìêµú¢Gïôá -³Ï;¬¯:p§–#â°ª%ÈŠ6Óš3ºÔD2éUY8³ÁžU A+÷“.*m*z´@Z.À*ùx³¢…y¹KFê½°0Ò½Õ첆*QÃQ|n•ÓºƒÞ™à<ÙàÄÛ/—Isí [®ÍQt‚y+0#òŠ¥g°EÖÍå{Z䥶7™TB€P=Ð1îIÕ¥Óáqœ·z+ÐÙX‰à,q £z²°¯eŒ¹U¹6upF­…Jí_ñ‰gð‰Ãý`H!x°½ƒ;7+;uóZ#zÐ׿0;Ey®™ÝËþüᙧ´nlŸ£Õˆ+#>ìm%&0š½2Júˆ´ÜäcR»ö%ô™¸Sk»7# Tê<"©^`n!ŸÔƒ ?ÐöE~¦S[6% 䣫î™GF¼$¹ºÜ¿â¤×N -]‘Ümæ†g¾=â8Hy’ƒ‚D˜áu Ýó#ôÀP›»µ‹°È…„®Ä{PFÞkEWÌuð\)Ï7…su£æEs,_õ Drþ› ‡O«¥}?ñ&½Èy\û4W°ée°Ê¹),1]—däà¡…ö9TÖ\! m-?wP„$ŸÕ.‰l5ÓxU‡l6ù `Кï.ÊJÒ¿`ˈç¦ØÂë¸"œ[ -P½[Cˆ™g-CD=”ýŽÔ¹%”°…¾¸Figì7Ró@ã4ò[¼¶BgÃDÅèŽÄÛòMgÛ ®»”™×(75‚0“A‰ŸŠ’Ȩj~úòýcšTÄŒV4’3’»§.û3øÀ€cAJK‘ §j½ÊÓ—­3sη›+p +ʵ½t—ŒHÍF§B.Ë%ÏrŸ-;Æ]µþAõÉtÚï3JË`‚oï'u”H8]rËŽþqò¦\¹)ã–“8{ŠHŠ¯€¹sãDl­×y]œrY7u]†jùå|¸¶8HÎ0×)Q)äh2¢%&¸¥PÑ”ÑVdý$Ñœºï0ݧñÙ»{‘ê(‹<±X«¨õé'´]1í+þR9MQa”¯,…xîÛŽúÛŸŒ¨îY¨nUõFÄà <‡‘dÐ@½®r½TõHêá›”péQ€“_Fd‹Ek“•ø¸K©î÷ó¼å7RFŒ·©“k¶(Žµ-„3¬d þÆ'4°ým™c$0è:fX«±±àÿcow&]óy’ÊóÈ$6Uöýþ -=Zy½CxÖAê[5#¨“BÓi;LMçÁæü†ÈƒYrœu}1)Zï„V9•% -s³ô˸óÎÞs·ÓN«GÏ3ƒž¶Øõí>ó§îã8ŒÜŸ†Ûo£ÞˆÉO`n¾Oø4I›…Ù"IÒ5<ÒÞQYTG]½n¶9 Ï&({ -k×ö^ºåó­¤þImçÚEÚu]†Âõ‚|`ë°õaf´’0ž;øû]?·ª½ñ'Œlºã® ªÆ(‰AJÑ¿{›i}Oz¼Fœ›rеEy¹heúí‡Ýu‰3‚š­·„V`V½àAf2EwKÈع0÷¬Ó_Áÿ’ºfÔœìÙ¡C{þÒÛÑN­©3€@òWU‘PG‰¨l -ö»©sM ïçQ¨(Ó{1åà^ }Jîcŵ=°±‹Ü×e/øŒÛêÚ·çÌH‘xv'CÙ;ȧÄ:Â_3½YóA¨€®ã–oNõîÜ[cV¢Â†OOÜ$°_îJ'µ™¦Š -dÞ &)Ú¼ -û]çNp‚-xÆ9ëg(¹¸ôªŸa)oJØÝ¿ã˜Ñ ê­Y·'ëÜÀýõÖ?î„€|Žt‚þë•…¿V BºïzmJ+R0!·¾{F‰í8]ã€Ò -ðü JàzqÙSÝS± þDU·÷£Õ¦ŒÛÖk[_ò³b›0œ‹4ö8nýkѹ”„â -©eG) Áã ç;è,iöq‡Ï -Z‡”\R8!!È*á<3Œ4€: Ÿ¾Ø—h´XÉ Ŷ<À ¿iåæz‘¼5ñCÔŒ á•ÈóÂ>÷9ŠÞᆩèTEéElœŠ -Mâœü`í:BÒ¾5™æU­¨5Í8ñ¥÷†H {È<[qüûúŽQ)i`+QÅ -¸†+Hî=Ot·è1å4•¨f¨Â)n¡Lw^QH¥ÉC»Þ¯/"•fL‚kŒ¼"‡£¾G:&ÉËSzk…¹_1ÚÌ/Jå¡Q#æQ#>¯z+a ã½è¡¿—Š¡CVÄî­ÃmÂjÓz³ÒÈŽ¢IS4Vf¨ª,ëUDõ¯µöþZb#'¾eª†Üš¥‰Šl§­Ü]ÝŠí:´sºTŽ qÞ24oKüÜÄÌ÷QOáø?Õg±¤ =¯¢Ý¥B°.åy"‰eóÉ ñ¡²–Ù)z´éö7¥=´rbãäVÀÅüÏR MÓ/¶¹œG®p•2pöoÚ äMT€»íä{G|Ãîc]Ir²lw—RÈ&5Ò[DT| -eÓT í8t+TÁ ×o>Ù^*š¸cXw¤Dz÷ži1åÓ6cîl‚JƒÕLË¿@;GŠNÌ`×ËÁ¢ò%s¼¶vÖ&Àˆ•X~íTD6àÛ¼8€òP°IÔƒ%°‚½v†C/Éö-uè;æß9½’#•®æYß}«ç@Z¬Ï4(4ÐŽÎeTìÕæÍ"å¹qlàŸ\•‚Zñàl(UQ%+‹€`L|f”"[OÌÙîplLÝ7VíÈé/+?„ô.•·.§žu?Ì#uz&u{b.÷RΠxl¹jU+C¨|ðÔBT_ £°u`9T®‹l"û‡C tSP`gƉçnŒ4§a 5Kb3×,åPøž%æI®Ú®¹ gD^!öèÍí×#ôvÔkȼ‰”CôœB:ÔBWkÊ;B’ÇÇYÒ-ͽ+®¯‚’ñÅЗuÔ¼”g°sÏù¿`Xȼç:Üo¹òvaÁ#öÍ93>_”R¬b–õ"aøÚ ¹ó*±s°5ù*¦.Å«?“˜QC •¸Ðß‹9Âé• Y“R¸~•©ÇÙ^ /k–gä !É\) Û~rzãH«BÁZ|Ù0>Å磇ýE•åVµ¨ Pƒ~6¸½r Ïè ¥äuÏP{EÌ¡ñn] -F‘c0ê¼³äApkߺPš!*-<¬„8LÝáøJ²ÀÞ!OýéÞ•·ø<ÉW¸â¼û¼)j»n1=M0û ý ã›Ôƒïíjßúñn àtJO%•|ˆW~‡ô'F#¬óGm‡³ts‡Ž5„Ö‡Êi—£„ -(ÛKl­µŸ4"ô[£îYRCjµ3<ÏSöúš¸ZÜ+y5"yÕß|µÛ ,>òª?__PÖ¤u;Õ¹¹¢sCÒYƽGÿ\òÓÏ«ô’ q@üµË^£²Òù"d÷¥î\Ž „j,@߈ŸÀçv¸x@Ô{"ãÄVŸnŽ2€!ΘÒfÕú¦vD¾£ú&¹^íó%’¥?1¼Û¤¢ñ*BX¶ÿ‘Bp«Bp;‹#Ùúè!Y—ò.º Rô¾ÕwYoÂ,9…»TÅ××Ï|Sè -žü¦; C’Á›ò¨‚£7€Æ¢jñ“Ïc+¶R0<÷cɦULæ0›•6´…»¥è`+ÑÁÍÛ\þg3¬N3‘gÿÜ3pMxµ˜NßÕå>Ž+™‘Ì›!“ªÿA6kFL×kT&[AW-¤‚ÅC7Lò•jk@‚œÏÚ y]ð^³qüÔˆó¥®èõæm‹WªXou¿•I®Ù×òÕÅ@õð=1×Pí¥ÌµÞ±Àºã­ôâ½Ã½7Ö%ŒÊ9h£låmÑ%dù°ÙuDue­<×ñ»@³×LÒ¦ÂJ{¯€q'nkTD÷HcìíÕ®8(pÝWæëÏã2gÖòTˆ›†f5ó·…"s±lIa5ô_? {O!/.)oœAC|¦O`¨F…nO»ësKZè}ZªIjÒ*í,+"ž2à[<ò+œë8"Yš‹—j¤_»ùu”Ž|ý; áY5ïÙ 1ÙaG%øyž?è^%EÝAny_Re&s¼wÆy—¯ßšÒ% ¢»²ˆÈóŒÖÑx_ÒÍ;ªÓYB8 -C‘OkŸ”VYy‘ƒõ.©µ¤ÿ£ˆ Q j²­>¸úùýï{æì­‡Jpßõýv—,½¾88¹ðk·ø ŒyÃ<ô¸ý~t«§ âõ´`¨Dø!½«fÃ|£±@Íòùà®·†+(ó,&§X+–õØD~šëò‚>š§Ý±µî'Rè1ìv»@±GTôq<ª<"fèõ²—ô°Ä®|ÚÞçšc—تµÜ´q:×]Ëj&¨ðiû=t·ãfÚ£¦`G…‹ù&jÿ|ÑÜ`Y`0"Òï15¯ÉÙòsn Œ*åkDoy‘NúW\o¯ä.嶻_o·Ã`–g‡Œh•hZêkçk2|‘ñë7Ÿ¿Iò§€fkJ]탨tÛKd¤îï#’äNÿðñ?©ì~XÅ红¬kTiù¶ÈQ+Ë #•¨b”~¾ªÇ3å!«ú²•D|Ò% íì«aŒ(¬ŠœS‚ˆ $ 7.ð•+Ø”ià(…yµ¢ -®OÚãÏo…Íst¯>¥DT‰Oè†ÿ ßtl#û8ž|“ü5ªgñF.´„ÓÏ©E+_ ØôäU -qè›70j»O´, Àµ˜Ê+n‹ºµp%”ÀdÅý>Bg†»ŠpúÆ÷-aÔ]ã{Ó,ÏÐTYî‰þ‡´¹3 -èâÇqUáRÙÊ¿Ûq~^‹ôœVüü⺣ÜÃJu„Œ¢ Íj04<ÀF!˜ÙžÙLRžSXÕ]#VìÖ>_Ô`yBjQžQ2?î@Ú!{l°0#Ž"øHí<ŠIþ¡—Íö¥ßºSåˆÜ'…qk¯NE©¥{ÝWôÑu/)€#JÖ)ŒÂü•f´B“«”{/-æìRt˃‹ ÷àÝ0è´. Èü…oȵÀ=4³ç@ò-Eísþº}+~¹“sµdâ‚vwÀV½$Œ[3µî@Çžu ™ÀšU(bw•PÃÌ@(ù Þ5]Þß`óËÆ TÍÖk¥@ž+„-tÈïªS v‚¬M ú=ôZEá‡r°§‰Àrœ˜ìÈ>êéÂ.)Fdºœµp»B±@}¾) ïµÆØdxT•×7=ÏSÛüøMÁë±¾¢Ná õ¤ÀÌæTÿ®6Ø’ÄðÞœQgð7O–R½è„˜ý%wœ=²½ï·ÀÉò§Ñ4Í7EŠyº<ºý!ô½£F4ä•àÝÍKaÔ¼‚Q5Ì6.AöÛú…Öp7ó™¬üãS^¥ºèœ´0š„ÛüzX‚‡F*â]Ñ{Önì`ø>!4;?pKU±ÿùÀμ’<”'øØõãÞ1àiâ3´hUƒCvûû*³¤¨ÉâÊvGÏÀ¶6¨—ëýÈ@®ï!×Ý}ï÷§§,ߢµHÖÚd‰C¤Î¼èèî¼ñ1JŽÌuǽ” «¯5h=ü'VÐ=ƒÔÎÉ4mçä@S[HàþB1ÖBw)úÞáa¬p”NaýŽ–8‚/G)ç¿V0 B8m³54Ðz÷>)Î¥/# m ×ÈTßYåYPÇK¼´Î€ì¡ã?ŽJ¦Š¥®Xú)#ð+dDÊšGCÆ$ߤBråÈd½vàÁ<6{¤*5MEÿ;ÈM¶“ñí}ÁäîkÙ±ƒƒ}.A—ßÞ@º’>›¨Ùù„ð1ô 9W4c€­ç™›†ørj·«Œ‚¶¶½ÚT°ÆQ¿°Mæx{;h¢la‹V?åÁ¥øÍ(¶!4 -½ëÇ |·Ïz%Ò1‚®>#Þ^ (a¤Ã’Iêo‡ -U%¶ßFÅ¡¦ÄÖ,êÈuËÛ)fä…é»KÈJÕ¥µö)°¸I!Êö̯ßÞèû­ºt•êÒ“ù"שüÕUÚ὎é×Xh>“ £s«å›îù|KªÅ'šägtJ¨t¢SrŽÈâ¼g„~¢ê@ða\|$ð駙!Ó‘U÷ÜÕË»p©´fŠ®š‘tl2Ô}énÕ¤GR)”²,:¡4À>g¦ýÔäiæí¤ÊH á—¥ÚÛš?]|Ò9«ö™ý9Ũ¼®é)ïAkýù>âƒ)#¦¸ÚøÃyŽkƒWPš•·‘g3aJÅá×׸U¿p ص¾o£fä -×콃ïì:ššFŒæö¯V-ÿ–•RU©óßÏøP»ž!È‹Ô¦¾ç’ú}âáx±ŽŽk¿¾…bœO(«÷úì­*áU|¢œQ2wl×óQŸ<çPú÷I¥Î–ÅÕ«±û€!j÷ExzýIéßõrFZóAÝð°nóž­Šu«cŒJÇ¡<«CaUnGbáÀ®kÏZÐ ÅÏò‡¡>­4­š…kHp2Bêôí/tªÿ6(GPð”îã& € žÁ_ʦ~ÿ&€Á]…ÅC¹í߯8#òSD|-ŸÏO?E ô™FÒJŸÆUÚ™jL”}¨¯  õÆçåUi˾ § -€¢ xžHÚr”—}('if4õ#N;û£p@ÍÕ¡-QrŒ&NýúÈJ‰RNi¿7íÊM;·ù‹.ÚÕmeóÙÉìB–5Úô‘ÁË9¼/ˆ*mqúQØôý¾ÂWZ£Îgƈ&˜þ’TA)^F4¹Xß"Õ‰¯ÌÇ âùú)Í/XóŒJï Ý·øv_žPr)£ñv²±hñÞ³rŠ³cº“Wiˆå‰ívøXû®Vã}†«9åF5'Ä©l·B_&É¢×P(•Æ¢,üiÅpG)"..,`'mfìd@ŸEÖ€.‘Œ‡-ü&JhñÄC_¥³ôBÑP<ÕZx²lžºf‰S~E\ÊyFâºg×µåÜÞ²„ýø’B'7¯¶pd(›1J. ÈGúÓç°ÊMVvDÁ4~4˜QѶC—aíÅÓµõä˜Πt¬…pqW>¤}5td¡fýŒXÈÜ`Qª¢<F<•xêRÝt¦¢måPn. bÏÈf‹þ†•Ö¾¿õ´{¯!ŒfåÕ#3Í:ƒgÓÔF-ÊÀ…å¿Aà—¶ô»‘)P•™5yĆ‚س‰Ã~ÊËòû•ÕW‰÷µ~$Êð*Òw÷"RЃQVöæßâŠê#RkD7½›¥³[Câ=ꆢ„ÄÞÏ7)Ë1î2ä l+] xKŸ:0EJø3ç ¬íñŽ9b -Á$–"òD.›ÏG¿y– ´ ú†È »–—ù+Zï*þ”ÆëÜ!|ÚW—J{£î'Áaò¶t)×)¬ge #"í:á²fϳØ>&aãéêªôÖÄ鎥F´ ´šu¿À›Ó\9x£P]-›ŒRkéÎãÃYˆF}| [Zn¢!¥Ù±”ñiÁv)›w î­èÛýn-8ûð'ZÚrœ ^M}LïÉ—æÚ¥Ô<#‘|ö£h…Ã8F· ud´¢%Ò¶Ûá.1G½F Ýí¶‹æØtõÓ{UËÉ0½„¢îrÀ•êµ\é£2ëæü‰6úÈÄ,+®æ-0‰|¿ûö‚­ÙSz7´…@:êZbg5¿‚¹® G®°bì¬à~) ç»÷ו"~ª†e™“¨!(O¸µ›Ô«µƒ×‡¯«ŠÙÍã%°ðH- FŸ;ýø`Q7ýb/"Û˜ë|^õ3–Ê(™yð‰Á×”¹6m;{5¦ž¼!7«Ñňù–Ï•¿ý¢Ô÷eŽÝ¤_ðq¤3ÙµÞr¿í´­Í‚æ×kD¼q Ó4sMåíßØø:¢}").0åû D 2³uHÂï“Ÿʉ²œ–•œO¸ó ”¬Rê­qÎöd¿é¤˜°}$P§¥ÄB1Îl,k…,“³Ò¯hÌ%‘bP 1fÈ곜"$ºÅ˜¤ÇêÅc“1 –O¿3|îɦ§£zd]玢Ä–œó«_°íc»³õ2fýêhFÑ æ*B|˜±È÷6í\ëGÙ ØÑ$·ÒÐ¥Pº¾ûgÞÉÊ>£Ñ´;¥ÚÚ QÌöŠ¸Æ¿ŒPbOú†O ݺèfH·¸£ÞðìüÙ ?„®OÖ»¨ó_ÏœÇLùÐ+î\=^rncS•V?dÖÙê4(`S±ìGà”Õøš¶Ò}wiŸN8PWžU@sÚ¼õÚµN§õñœC$çÍâÞ>ßdÍ™ƒº’Ý@fc)(‹=%²;÷þ6vÖßç™+Si€ÉØ~"ªý§Òu·»(RÃWåX" -íYDÞ4 ±‘sÑ` %Øe]òÔÁ¡œj§b=Û¥­5CÆ.˜÷P¥±3Ê?œ¥ˆ»ž‹Â½N}ÁŽ³FœÓ:Lô‚;`ÐÐK>ÈQ‡ã–eñ7è‡VL]®Ô^D–‘Y~rSù%Oÿ¨ÂR~#²¹UÈÊ« 0AÌÔ°Wæ,ßú\Zxç,‹°ÒÇ‘Où-4É('ÓoÁ‰R ½=ψYÈ´ž™ú'É{¥kÊ^ˆyT{ÑýÀKI^ü$zé°Û·59J†láAÅ…PvÇ]S ágyr„ÐefË%#ZÉE„H÷^E…˜ÜúžàeÉ: ¢ùú;ߤÐ1à «%ˆC#û<Ä×\n©¤ûÑe÷mž±F´‚u™Î±rƒèõM¢®0¦µQh8<*æ'? ‘=Q)7™/×߸ »ø¢EûÔtxÄ$Î(}-z&h‘jZ½Ø¶pˆÌŸ`šÜ(ñ=Vº%Ž¯˜jsgH˜Í-.¨.å2dlƦT§#*T<ªš…u@9ÖS-I4‚SêN0}$¤€2vûqWÊ5"—ÔFõ‰™iùKpX#°ôˈóë ¤°A$q{7§fe¨ŸUq1o]Ñ{¯`]Ûœú‰–­^Ï»‚ ¾‹w1cg:bg꾈L¦vX¨‰ÜùÝ7‘Ýú‚Cvüû¸6=+ɬ6ëˬèÏ^žÐ¦fŸF”ï¾î¢š¬•Ex=š `¤Öª1uDè4I Ù÷ÐéŽçRäwjJŽësÇ쀺SÚÑM¢‹–ÜFˆ¾¨.$.,.Pô|$|Ÿ 8òIw’µýç¦äl"é3ty™)ãºFA4Š…tF ÑneÓWáëÅÌé¿^ûü›VZ•ò©îÙ]uUH)¾Ðˆ-”B…×HÂaBŽÕÐU˜XùxÊAž7¶:JÝì%b‹L™.¦C諸%f†4º^½œÒfúUáèžj~ —Ï„©I^B´ z"/DâÕ¾Â]Ý°«}ä†ô8ç2êZse7xãF!ƒA ’à'pôpPk¬«ºÔÚ>s`R£¶`½ØJOÑå]xçFY eÎ -±N‰'³EÞÙµ¿¯èw®LšËCFøùà# 2.“6dð37­Ñ;î—Ñh -%­þbÄQÞó'”ãñõ¡÷Î3SîÏʹˆf¬DÐjêô=â`dNÊÄ_Ì ×Ð÷¨0Ùý£ŸÕ˜~1LíƈÃÓ­ #b`Ž>2BæÚ‰¾âõù3"¢º“¨÷-?—5ªkPùÃz¿ð¸¾ŸÇRôP ‚ÎŽ"‰ÎǶ”樮É`óbª¬'`‡½LÕyží¤qò {4ú=× .÷ûˆ$ÈO2ùoGlQ9Š$p(áôÑÝÕêÛˆw3)W,¹ƒß×Ð?œþ¥2ÏYK|¢Š›r7åŒzO¨ ÑÑ÷ûˆ_ž3‘…¾fûÃyÄBŠ²¯ž×qi÷ýïGhâhøŽÝ%ù}”OÆÀÜ)êwhŒhVFKç>¢¥ãŽõlfàwžaˆOˆëìÆSÎÍå=¢E€~IxEê:ÅZÁê4ç©2_WîÍ„™ hg| Âà:I·ú×™Ê}à[gÆ`ë ¬ÍP,šåhÞˆo<ÇØú噳mŒï‡ó5wKW8vjtʨR£4)\¡F­‚Ä´©]ÒèÑ® -ÁuêcStÄ·\‡1L‘ èÓÀnÁµjä€Ë›¤„кˆ€â®¸ÜR)1@D»É»xB¾òÔ¯­˜¹V›‘— ¬,€·ÿnõ Âå=/Íe<àæs¡Ô³×–66êÝ5(@`3wÍØ!H7åQ¾ÙÂI¡”ŸÐÿr!FÎ0šçùE^7I^3œš˜&ÝÄ×=zlÇÀ…ŽµÁ½j¶Œ¤Êû\y<Ù”N²ÑË^¶¤üfYìþZëÒc渥砄GWѺ X¬3¡ÐikŽ1Ñ:|²þ‡_Goel­¦³:çk”n¡o%xCbàÌ7uW”ñðüÈ+¬ò¬á&¾å7¨ß?yLÈþBèòÍeh0tKªËßì¯ë<îKhgKb„j¹¸†3)ñè8‘gÿtÝ/¡`†‹|£ ýC‹4ŒÀ!=lƦ•Âbú[#æ­j<êÎ iå­‹FÁW‡Vãà¯.Å=3Co&w¥(á#3ˆ3ÒägÐð!°sC^.•ìX@ɹX»²Ò0xI´Ø{×åãʃŒÅ=¡XGý^”¿|EL¯ÖúÐieT^a䑨lÍ€{æòâ_‚Ú×=ÿ|2½Öˆ5¡¶Û¦"Kz@ÔM™„¹áïɱÅ°ÏDÖ?O{É¿o´ßFä‘œö¦õ”’|rh‹•ýGvðš4£¶+…¶^h…9¡9辦óu‘–X  ¡kÆÎC@ÜÔãUQשT¸±ÅS²•RhiqÇ¿À­„ÑWâM—~Hø ÙMw Ñ¡GçãOùÙ‡=NÎsgÖŠjoàc;‘åˆh¦`"úMÿ†µJV~Ø]³úÒ*‰$þøCY½¿ŸŠ…µ%¸ë`<ÑKÜ]à!3moØ€ºNms9¢üw|þ~•b.·ÇǾ°Ž‘aRYýJT+[g-Ïv€‚󊯯…¥—E¼² CÈK©žyÙôþ>”nÝ’IqpU‰wf"ŠP¼t²0ÄPÐ&µ¬-ÖâIˆ8”h¢F±jå)aSÆx­’3òI×Kªé¢6Žõ¥šRit^µËR¬¶ÜoÙrÏ»€W"ËPþ¸?ßt@«r¡J˜GI€:WOt—;…jêQar[d -ÝÈ„áªÈþ|Ò „Ä·£3iY4ÏK€4㳡ô†Õ“„ bVÛDQ‚Ç—šêYb²Pú5•™i3—»Øo^3¶`¶’ºÕÿË68ðB‰fwxÖLw›zÓ4ðA­ðƒ¾åU{=˜´Žž bs`MµËÞ -b¼¢„ND¼ÊÔàÝ{`\_@_ó(-çóüÃLG¢¥µÈœº=q¡î;ki3zЊ;‘÷Mð£#b¸óPŠö3´¬ÅÔ ¨ýlÆÝ -jºf\»ãŠò[Mà¯ÃzƒZs‹ý ç9ü÷ÿã?ýÓÿõãßüÿöýwÿüÏÿðŸÿãßýÛÿûïþ—÷þãßýõŸþÓÿówÿôþÝÿôïÿÃ?ÿÏÿùŸþËÊùþü‰ÿíþÓ?ü»þ‡ÿwë+~ùÞ÷Ç¿ùoüÿûóQÌû/ÿÍŸÄóÎÜõC&5Nèz´6%‘*uxÔzE@@ÁEš †rz×G%OqßBãùÏÐûˆç¿Hç©#Ÿ4AšˆÉòTýËmÇ‘¡CÝiàïÀÕi æSÆ—˜P^½þ­JëÚ*=mH»>[òè1 „ìðc½õÝO”;ÙÙ)"P½Q|è%Rï>›¸§U»±1Za»A”t*-øZˆæ%ºžI"[N—bK$BEWíà -BqPßFŠ9ï]å‘yTr/óe?IÚðR?$Mx7+‚Óåí£m s5Þ'Sˆ¨'€ª~÷ß»IÁû–0|'O}*ÁQ7åÂsÿ i«rô;Cx[7¦ã(5yØA¬ÑÀ¹6îØúÔT½’d™ûLUãY/yÖ¾@Ñm#/ªK èøs;¼Íx•{uŸƒå’³r1?ìhྪ<@ oíZë€ê»a@ŸÅ€~êt ñи½n¯ƒ •ùT ëºúˆ]–ìÉ 9±Ü“»G £HZ¢ç¯¤*Y¦#KȽ;G™à‹¥ú>Œ‘㊳íx‘¢xm¨^ö,ª³z ÅÐtç]½#XâYDÌ_q=£ -„–=ü~±ØwHóhPþ‡&Çó(òü -+®BüB.£wïÁ¸âbtôð)å+/7<ÿB~/ë&ÿD‚€è?dù¨.rÊë‰^ŸÀ"ýHŠ¹_¢«d¡80 -°‹¿¬Ô WQ |¹;Xϳ¨¶ -‰nýà‰ûæS§;sºÍŠÑKi&`{ WTë6\š›M™òå@fÍ)õP„œì7ÂÆe—hÜÙé'­_r³Þ!ÙÇšØr}Š€÷ÈCÊü[‹Õv56Jžq‰´zÊœk QUþŠÉ`¿|¨ÈÜ?"Jßràô—>"ïêt¸Ã– ž¬bàΧ¬ {ªñÑt[ -Þººóш’äçtOŒ[]LÂu›ÕëÑnï(y–ù`Òðã†b÷-]ÚÔ_s²×Eš5yUįÏñS(µÿ ²Ð,±Td–9##µ–PÈ^…¨G‘‡½8mXÖo§Ž¥»Õ1uOåßg$B\ ©VÂÅ2E{Æš­ƒóXO—¯ömã@<³P;90M<à¯`±ól+Ç»ŠÄtj”éÔº—8ïé?} .ªìêºÒæÃ|åÜgÅ»(ÿ—rÌ‹OÒ™ÕÓÎ*§î>‚ØMdn€>9)lNúEXÊ ½»*Þw=ÞõõÏGª Æ “n6Ðc‘ÙJIâõ4– Þ½ÇS2OGÉ<=!>ðÀ’/ŠFþñ1Æ°†A—þŠtP„_z ¿Ø©Lx©(9íÇ1F8…>ô%K+ê,­¨í–ª† øu¿uØïèÇJ'¨¢foKz›ò³Ç "àÅ:5®d$38pãÌœE…=ÅÑVR7Q釣ÙÝõ%GôãŸÒú…SEñý8#>‚s©Ê€÷[ö1°÷7 ÊQ²*QG¹åB‰/Ó—*÷­w$¢^KÒÇu}ª¯·ÒVoy3®Õj»2ê~ã< p‚ƒé%Að9[°Ýüûuøº)zb?™9Êõë)½TZŽDZ ƒ¥©|vÑŽ(HµäJD\Ñc« ™i®(ÕÂx#Ey–ØؾàÀïVòz-òz£à´Ô%Im¼Cë|uÐ'ºËf(u?¼Úw}¢VgÛÊt –ƒ—– E>5ãêô›8Ó /ÿˆ–ƒ–,l;Û¼ƒjõ3ò›4/ÐvîÉš×Ä0"Kùhÿy€Vû—‡6ÇÏY-Ç~¼Û|{®}ñ™!Ê §´dÙ’ß¿h¬ú˜ -+.X¡û8Ñ%Û!m -Í j@'·™ˆCÕ1Ê T¹cXýo5ˆl;ËmnxA͸S2WElÅ8èqRÇÿqŒöbVP•ª?™~åîð»Ò±úEU¨ÉØ“Oq¸+ÌêÊ(ç'Æ1µÏÝÖ^ýYèñlJ×#T?J-ä&ËøÝ¡ª–…óÊÉAŠÀØñÙªæÞWD´‚+¸åíïnõEõ»îí)Ã…+tÿ0³.¨ýC ÏØÑ7}X77Œ‚ 3‰¿ù²àk°ÞÀÎQ§]ÿa›ÖF3ßGR8)upÆ÷”Yô&Õ9€v“1UЧB¬}†E«é㟓ÎQˆæA¸ÕU©‡H̵7®?ücÍÑo„®Õ\5Ýs9¥ÜõJ®§AÌM°$Œ¸X‘V¼Ã8dã?#Ô!Êûb­¯Vf¦ÀâFjŒJ¡JCð1¯øw h.€g{4Ä|¼:‘}¿¦0}ä×æÚ1¾ð©Ã—âµøý r´oÐÆ·ÀÅÖAV X¹ù+ã_sºÖ -½— -”³×­éI€ܹÜ™kr‘½®ØcȪ¥åSšP㪗#¦Ì:¶¢ˆæ/* …ßÿž‚ ';r²·ðgC×ú|SäÂS%åS¯ª·­\û€õœ9 . B.­£">¸?’Ýúf¡¨´NÇ%U¥$f󺃷üsà)ÛÊîó“Hw BX<‰ÂãæXa?ü fZTÞµfú<ïSyc´Šiâ±fÜ%ØïåϾ-¸0%ù¢í}Ä¿È>òÄ*Ñô*H'ÎV!²Z‰¥†,Rf„0ƒ©{À†¨Úé‹0zÉMBmÚ„-~‰[Έ×Õ>D×°iª7>%ÒÀsm=É+í jFp{·˜;lŒRr]Ó%,š¼¡ÚÃåÂ‬w®tµY¼`¦¹7¹{²æâdÛ€î/mƒuû0Õq¨Xi½7{•ëÂÒMƒÍzýô>^zÜ·Y.›8‚×%ö¸Â¹‰n#–ÍÅ/”V Êéñ! ‚à0¼¦þ-„í‰×~4Ò3…a=kN`~sŸÎýH ðK€L”Æ, ]FY jŸ?Ú±—î£pL2S³m4)c)ß* C›¡„§ iU}½ V¹¾ÕßPó émnŒlã™ÓEK—*”rÑYÀŸêÅcì©È±/i~ägypýÀÑÓø~¨aýƒ¾ŽÀÕù¤à–^þƒÀ„3n©ƒâÈs•Ã£Î9lÎb¡”]O¼‰FHØÒ_Öã8ßï äB¶ïÇê”ãJ¬ë6U«©ÉNÉQ‹Ô žÝ¡ù‹xó8Út‡º~äDú Ä(xh—ž0¹ÇÜ©iy¬Ï”m›$bÜnŒ\²z!Šù7ˆHB—ÀêŬà9÷é¦+o‰óreû3ë0-çïB6&¾¦€ÙN$ÖÖCzžjÁvq™®%yLæ¸fQù7þ¯UPèØ8ƒ‰\þ¬ -/¯—Z=ðåÖÞt UÔcL³ê©f•ÐŠ¿Äņ × r…3­¤*¡œ áàˆ ÷=­Èo%þÕ–GaÅ)ÄóÏñï¥ÜRbòð‡ð´óDU>DàÌc]²°K -ý˜œ|f·Kk[£|(ç[•˜¤&½`–q£>Zš:ÃZòfîƒO™o\§Hš«€¥+Æ¢‡6ÔgÕÌû»¿Ô§ÍïŸ1¯"ó=œàÏ NîëÀékîIö߆«)wÃìÇuÕ7 yŽpÛ×û8°¤Iä_….”É$j­ a·|Ã-Õ”A„éKÓƒöWžjÙ 2”×ðÐÝïNjãK{‘4桽ú”"É{~"êR“k%ÌiŽ¦t‹_bR±þ@‹ã¿;Ô›™<·l8Hw£ãHˆÉ´ŠÑ -èꑨc«˜ ´¸£°ó »q“:ôaÃ%ë6<íŸ.¤´JC¾œNoy@¼];$2L.C-EkÜí;Ý"KÖñˆ™&Z¤—ÌLŽg¥ÜÒÑa˜ºÇ!,³È$Z/sºÍ£>LêËTáâŠQ`GbÅu å„í ¬…÷Ï‚µãQS5+t¸.“à,íðêÚŒòx ÌG1ÁI1ÌDNJxkbZ -(‘6C݃¦cø0¬Ó/Ë)7nEkËÁµœ]ÈRÞ8ãÑ=¡Ò‡ â:R47\\ÛZöNO7ën%œŠ«rh´$ °pAp nk½’ǘI„„{8ýÔ¡fwÛ ‹µòT2ñÉ=ÏbèúHʪӭ›S´Ã÷ ò¬C­î¨æU+ ¯>Sµ§‘+R I0Ï=žÜÊxƪÙþ»ôk>ÖR§XaíY<¼ÏÁVœ­9@ÖžÄI×ó䧔Õ“ çÑT’¥¤×ŠTï«®.oÎUp1õV–Ñ”Ü,ƒ;·Ñ;X>a=„wkJƒ‘ü~NçÜ!Ù…T1Õ Û%ÒàSC¾u‹¸+Wñ¤}·T¸ÏÞËq‡¹îÖ"GÄî×ÞZÈíB’å ¸ÒŒ"ã{ -Íùü„œÃç:ÊJPPÚ$‡¹åáÖI‘ì^ÿ…<>¦©ÞÊßTÎúžíÅÒ+HpGyMCÁÏk3©_x™d_{$”ÖË -'µ°qôPò¦Ý`bïÿbº¿«l&Å ½»bÖוwª8Ý𮿤95«ÏsQ¾BæEe]$•.¨\ô¯5P„líÂÅV~l1öK8±$­`á“Þ‰ ´Ž³`è.„ŒêJˆL ±UÅ`ìì0alL°àh µçÓ{ô Û÷ZN¥–HxÀMÏêY«‡é>Ž”ù¸Å6…ϧËÊšÛõQÊé _弘 -á~?/}`÷ˆ~ -­Ë:ó÷Gîx'ÊÅ"bk·ÊNÕÒ‘q+Rü-â ˜‰D„ûiÄ F«•ò‰<:º9GêÕ9 ä ¸ÈiîCà¦JT½”¨Zˆ÷Á2¹Â ‘‹nàZ§†/å‰8Ò®Vw Û7kQ‡¤£õÁç®›Ùno¦l'¦páÓ"êùêÈ‚ý[oêŒ@­Šë>7p2ì)ÕκŒ7,!ÈŽ²„F=Q¸Öʱ㣒mÖˆc?’!æqeÅãý|í{~Áˆ¤adóëÌiND}ʱ·*BÏû¨AÐInÿdhÏf]ÎGë"ߨpÝuoM˜ƒ$~B¤½#¼.»•ÊZTìá¯XIs>erÑi@uäÚ22h¶”¦]ôØÖ¶(Õ2â&Œ=Mh¦*iÄÇV®²€žô%Jä­•ÈÛ‡_ «„8 -¹u߀Fh‰«»„.3EU”¼^2dw,ŸÉÚ®\´Ëu-ƒ(88BE%Z½Üâu÷÷]é:¥kÔ“Q資”ÊÞ)õ*´ªêGòz¡ºx ²Ž¸´ïd€/P(k¨€‘ÞsDƒG :Ê)¬`WÀe ôõ“Ÿb\QÞŒ@˜ñ%韧¢zn?ãæCÁîr 9#¸{«dÑkÁ¸kÁxCW›¥$ äÚ¤·‘áyÔB(«:ѼN¶æ/æȦ[d¤×§]¨G¤«Ñ°xD×aPjFeg£ Ln LÚ‹±×%$w„Öõâê =M­¹ñ—O [È+ î»­ps-’D:"ÐI·Å†÷°ðîß]W2ZD‘×Ûòº„Í覭 ¢å"“Çì=#ˆnjBŒ;»„ç4îˆÖIT­ JÉN­€7(“XÅô¡¨ÉKŸ„—Ê1ŒR|›éj=¸Ùö,»[‡æø1"Ý&¤Ò=»l£gDѸVÿZè=pé÷ñDQ -ƒÍÅ~(äAT< -ã¯çEpËѼ°*ñ÷[KqN×cDëFwÞ{nš##‹h@l+ÒXeÒÃQF4D(“Œ ‰W¦ ŒìõŽqçcy ûĈ—Í_k.®§Ë\Œ€ì¥|›ëñD8‰Ti½ÌEô tƒ:‰×1ÝW„*ƶPŸ rîä] V©7\­pk59’g€ §£Dñ4’Ój¤ÅZ8¨¾,ˆgHD࣋7Ò”ûA‹BøÑ"DÓ׋ôVc ² ­d®`‹ê­ë-;´bãqírìõ–,€h£<Üt7ßRÀ[kàSdË\øJŽåÛ2‚v#¢ZßBù‡}´@lÕš?Ù§Ê@9l({×Y’=’ïeÉ·lO×òpXP¦/h «Cã7D¸ÆföŽ²…˜š ÌŒ®Aó¸ê<š–RÍì¥7­A¡ÔåñŽ²™Ä«L-ÕVæÊØžçóM2˜åÖ­|v+ùì'¾šª¥s-îáèVŸiq^§²HGĈ9Ÿ¢q¾€¶—,„µ›$á||ºb^­D #ODíŒó¾¯}­-R=! -]&Ÿ'Eþ¨Ëæ(¹Õ2CØ Uo"·tÆ^NÏeCÖéènú"´RÆ‘Êû›Y•ÇGÑÁ›Þrx‰CÛ -û'ÝÎ+jQŒDé“»ò"€>ó»Q„ç%X¦—›#ú‘==Ô7ä£÷ò¨BLƒOh”wÉà¨{¼Ö9M¶N¤LÛoÕó ©Æ%ð-3–5âcÿ«Ò)¤«ŒiÂ.Òa5"œÍßÅŒ‚~œ…~œ[õ)i†ŸOÂœÄÂèÜQÉ”œ=‘úi —åk£ç(ý'­íätÍR^{œÇNzQ]ûWüÎÒòf·"ìši<)æFCê(=pjyßOµ¬¦­)Òшp3™ᾶ#£Ž`ž…tðúžguÃZuÃîò%“}Ù-¥Ã¦¢ìy˜12 ?^Øóy6©!tQ…’Çâ;JÞŒsòÁ‰ÒYìÃòÎBQ°h8“f;½ß -Ǭ™~´KQ%LäY…VZ•R‚mª+®ÇYì©?•cô²›¹‘nÇ yëË ™êlaYJØJáR0Ž’Æ=õQŽ°î–²Æ)¾ÊRo¼ò¤–{ß sê€dmx©]-FE:[ïÉ{ÖÇ3¢aPò#ÚY#â¡Ùñçø8õ–ÉåDbߊP9R3ä%E‹!"-o² ´¼euü«µÅO’¸ý‹À³:;ŅSŠUHÖ¼ºÄîGÌ"a çJ7#P³iu:5jY¡yú·-®ñ·Ð…1!ÕC1ÒŠ[ -ê‰i‚:?‘lÄÀ¢Ü9 ·:x'0"B0zŸ€!âãDëþüŠuá?3Ê^·£äx'‡ç‰âÓ -eís¡¾k°ï5›§.²^ã; ‚ ÞE&Jß®ØxY|`µ¾á WWŽ=/­ÜZUN¡÷X¸é76îm%Yˆð®"Ôª ÞØÝÃ…‘û‡ìì?eHhA÷f[˜s«ð··ä™tÔ$8äÅ〈Q…I½øY Näá<Ó“˜Ö•TÚ}lüöYà‡²Æ§‚då@çp§jÀëÊ'Ühøž ;Ÿfz…#TX—]ymñ@+,´Ð¢“2fƒðw_æ#){7†úpÛhm·x1k?¦ØÝDìz˜¡­Im^¡¹xßÅÌÊ­kºªÃ8Ê—g”¼ÂéTn'*Òá„^|s ]éQN ØÎÙÓjÛÈÁx¸"R£l…è^0SsG308Ú„Õ‡@7y’¤) ˜-œRû±Ðý啬àéUø[úwÑ0½a¬ù:´|Ê;³ZŸתʤ–à…ø —rí‚‚"ú*À†¿§¥¶"P’ª„9ž·K2W”Eõ -m·ÅÂQn -ñoþÍNÔJà¡¿ÿPŒ·ç»}©”ajòóéÅ=cŠ<öŠŠùˆ½`÷•}ýÞÐEó•ì¬˜ÏýÛß}™› ->Žt‡è¸Âˆ»ËEît³l?RŽí÷.: ^ÂüýSoÊÈZÌ=VfÆòþºðȳìõ>˜ìýq -O¥Ö;Õ%$Nºd>qy@—s‹¯GAœê¦î"tÄÁÛŠº¾þEŽ^Ϥºôé/äàR´ÐPJ¶Eü¼ï%FiQÊ¢ý¿.˜t¡Ÿ{“y‡¢¡Ùº°!\ú¨lå–CúšL NbNS¥~|[KîQÀqHüî&)ä7«†V¡cXbËÀƒëܯñBBMÄo+D4I°‘ µaÍÔ‡vÔ%Òo?¬2kìáÇ)1¢=õF&ðÎ؉®‘|ßòùDš¯´5¼|Ž´Ôm1Á·#zAÔ«š gùU´\º%Dß ¸”hõo_Åu¿£–@r%„ž:ÓÈoµ^Iãñ‰úê}Ö†WYL?™ëUÁpa¼#¯Å{Õ³xÐ -t_ÕuÁBíPJHÃÊ:,[úé*xç·žJÉþ}Zöý¼²Z'gÀê¥JyÁ­Äû'Ëp“G»}T\Áסdœ³‘až11I퉵Þ%Ê(WJ³ìÂ'»ÓUµf·žÊ­ÏK‹™É§éI™ã¦µÜ"Ë\N[¯Þðv=çåâ¼iFd“ÄM-5)ƒvD©q>Ç rpxþÓıQÀéS!û£À3´]­ ¶£4·×Œ>³;¤‚„d­÷åö‹Ÿ‰#ºBn6ÂOô¥Ekc -õú& [ºiÕ|O)G#íýBzNšó×÷sæÇ•cÍëb÷à·óÄ6 äµµÈÁÀSuŽ¸@¶8v¬3Ê>î!Ï«%møt -¡'4©-äˆP}ˆå£U^hâ¸ÝHà˜a !¤ÈŽƒë•]GJɺP¯ßx&Ï*§ñ÷Zk©ØiøÛªYF© ™T#N8J= CL½ -ßxCxbËÌ“Ž4í.¬ø®fôÀ/„”nG‹7JªRïM/¨®¥?tÆ ;û,‡-×J -Ü+@—3t:ÁºVënbÄ«’ÌIçåJÚŽó£CrëK'­’VâÛ×çf¤î{…Þk6Ò~Ñ8 dÌ^@OŒ“c¯“^·‹fÙë5è`°÷ÍÖцáÏ”s@Ä0XÃ’‚Ð*jžß”îea÷@äö%Å\ÿæÆØ[ío­›HŒ¦ -yÆýƒ ‰¿.Œº €bÙ’pká˜ÈmzdK†/ WY™_¬Ò®95úÉ÷@õÀªºRÅðŒè¬Uˆ(ä€Å™u`C¢ lŠj–þ|…þ nýžL\ÉíQ@–YÛ—g+ /@?NO÷þ%º¸ëËüH¸‡¾ûÔo ý`Å›”`Žô²z¢S@€Y'íã—›ž3D磼Y¦CãI‚ŸºÌw9, ZéY‚8v+^¦*Ó­“À¼3÷Y¥ï)yXΤuD’yMM@[U[m¨»õæàðtsí¨~Ê&ðÎö*Kç÷·RÆÛú>V&§ ­Ïðr¦ÇeáªÇp¥x^ús¼oÖâtê ôàÈý×”–rq”«4 eîÅ=ÅÔKª——ÔZ84Ô:R=“Vz&gßW7°I*­JÃR-2J¾KFQ ’îúž§ÄšÙ¸ËÒˆ`Ìú]¬LOâ»ûßÕÆSlJ.€ÔF~Cá¹( iVÓ<#ûû¶¦¿Ñõ »'È0t«®jkÑ,Øå³w×/ù¬{¤¢°âTF©¬uZ”á,±TªÀd‡JoO¹zÅÞJlÌê[¼ô†5+_'¿Ë¹— -¯E¦¢‰3Ò¼½´« —×¥NîˆkºLºAD¾¦*ö5QŽáÀóážAWÓÓïW”it®£ÕEH°1kºûè¸ÆŠ´1¸ªZBÉ}nvõлͮ‰ãlOy}Èn&ËQ׃µŒÉWJµ@.”ÑÐr>n°?õ`éý6l6Ôi¥LbfÎ2Ç€`§ZõïÊ#ú"ľeVãhæËG[›QÂq¶83ªX²D)õ­WŠ”ú¸t¨b¦ŠªRê½àeæ aªsœÊ£ªuÂn…2 ŒF;KÄÁ!\ªßyužíŽfpG:«•ÝÓÊ&ìý6"‹z*ja…û‡Óœ(¢0àäY•œeQB¤{ÜÂÀÎx"Ƴîq­OHH*¶Ríä…*rFúH TBˆµ­ŸúFb)r!”?6œˆ¤ýÊ>¯‘YÂmŠóu¿ùRƒƒzÀꇈ¾ü–5â8Ÿ$2õöq_-‡,¤¾oÄ ²ç¯wôŽŸ×°*·ý -\&?q r=³³=‘©Š‹´°ª»Q»w:Ø߼˴)>Š³Wìô4d…hO˜¾"[gü¯o××3VOíò¯U½×qˆî˜ÍZS”ç°a|žôôBÐY»YïAô‡Ä•Ìx×Ö¹Þ'ñl A@7e òv˜#™´™L‹1,~‚pëÿ/{oºI‘å>Þ!¿´ŒM ßÝc°þ@ šËŒh¸@OsmîX™²(Mk©QIMÓoôŽûb÷üÎâR¦J™E•”Ù ¤Nxxøzö…%u´àJ …Ç­>pìö [µ|(%N–Îh¹¨qeĤ¶Øë$5œ9>òJ¸") ظAôqìð©+[B<|×h!—¬yt’V…å>vâG$.·à<’œÄqzÚ; $x9Dì{—6­¬Š]FÖñáCêèj¡ÒŒ8VÏâGÜ‘Å+…w\Hu°²”ÄÌTѱ¡ïæñæCè*¼&Ÿ,iÅÜ2;ðÂMU¤¶»U.<@¥6ÎîàthÃ_hÄ,„|W,‹Ä¬Ö>T’r²W’ð6…(¹ÎØFF¨ ¸[È—œÄÆ8´aI?¡J>&d‹×tî¡òµ†ö».i€DpÈ>CÌy̆‚o4BR[v-CéƪVnâÒ@”¹Ä“ãr”ŠÁ¸ÆQäD-¼yÓKµ‰Ô&aj`?Oå‘Â…+A7ljRÔlË»vJ9 ŽAi†«²Ê#ÐFšÐ9Ô|Lòz \p»rN*n¡ŽÇ\Ñ•n…TƒKª@t¢²þ9G4ÙéYKœ˜}àbõœH Ž¯°²pj]œcæ\œÖèD•µ‰ðq•>„9áJi9Ó h­“KRÁ‚ƒZàÆߺñË‹ñ˱l ÆçA…W”Y’ÎBÝ—#ßÓ{Q8^Š=Hà¤äª†íq´ËåYà™8bÓ ~Gnø_3‰`iUA9»:•q´JÌ°J9—+ô>)‹a³ˆW9´4ÃÊ.´à»}'‹íNò4 ÁÀ‘ŠÄæÄ1@Jf8’åŠÎ—ÜNO já’•Hx¢¡1ƒµÙÖ -Œ86/iŒ¸îw,–Aüg9„ó< ]ýG3ÃɃ3àg `GÁÈFœC98 IªŸD㙘ÉýêIB äÛFÊ xq¼0°¬ÇZª4sP ‰;J%|…2"[`ŠË)‚-p ct¢]¹ÆSÑB"*WWb•Š„=¹EÉ=Þ848ŒvˆƒòʉðH¢/è)ßÑ(#k2ŸG8<˜•Žê³0u±cK9a©fQ’‹¶¹í©‡ÀõV^Ãöìg¬Ÿ«’=žcˆ¢,½\ŒÅKÁkÎgŒsR³Táa²”"b«lJAQ I‘0øKŠ )6šœ7)ÒÞâ¤pºh¢¸«q™½àÈ,%‡XÉŸ1llDPGŒJ_);„D¸\¹(k1s Gä&ðÒBbŽ‰±âØ„„0k?ôª-8ΆxœâÇrÁŽ¹ŽaQ'$ë… Èp¨Ä…;†òÈžgÄ ´ÄI3°ó<[ä$± Bap2áËIÅsÑ®]Ìi#ZbÜ)¤_¨¨5¤|Z“Toð´•Üä•—– f§Åªgœ…‡¬¼çÂ_Q«K@¡ëýøÚJ¸® ¸6© i|¨eJQŽ=á@hœçÒA¥«8»U»ø^™œÇÖåÅzÆÐa¢Ö»}bÇÙÑ¥IQòM rNÅi™ëèqÚG¶!ð¨+‹Ø[³¸£Ì !­qþEˆ•\žK³ƒù,„s°wÓj1^ÙÇÊ -+ºD¯£!ù³ë?W(âGSØáçŒdAZ²‚é››â—õV9ùìÍ1ðƒ Ø¥¥rqÑ>¸”0°Ù…Cf•%îEÖ"ק„f·ûè͆_Øÿ †Ìa=ÅY¶\–,[\ëo—Óy°›ÔÉðoEÆÎ5.ž?––°¦áå,TaÒ^ÒDsE:ö#‚«üÂ"w"¹Iùq0ŠN=;CÑløBbëKöœ‰)þ².Sáµ…±¾ðÎê‹eâxlku³îŠs’hÊ^DŽ²õçƼØ$ßÔG „Ì9߸ð10»û\ÎTŠ&JÀ¶¸W¡"ÚØ4 Ô˜P¢tŸ¥Ú$r ¸‡6HB¦“:ã|5ìÊÞìÌ!°Ÿš¾RjŒlÛάìf:DüƒýŽìL/þã¬×"2™‘»’½Ly+´¸›¤+Žø¾$x˜ØënœÙ8#«k*²ð>ŸˆfÕ€ ÀدÉ3à{A|‹Æ“™áb±+<°‘.¦(5îÙ¯š -EÈ—8ÈKâ»PðÆ#a¶¤Ï½y%bÐ7¡Õ,ì">¸åîDPa|à ?£@k>¨lIœØñÊ1Nêíx¹ ÐYCÌ£ÏØÅ¡|‘«^q½#~0°?"v‡{“™B'Çî' iPë-qé-¨â8Dî™tœÑ—„ -Ž}c§çƒÀæx(Æøæ7Ë‚ˆ@9yUñ)’?ÎË,Éa‘0ŠS½UÚÂmˆKçë†Ë*R8ùbøÏE±@óɪtѼš±i'Ž› -ª¹ø³H­èÍ…ª°ÅCPÚ:IG{BEÍDH0 -àôo!ü¦µn!TÑúÌðë‘Ýg -ëa1Œ“gC9ÞFŠ#ÓƒýIá\èµÂ5š?]ܹp -ØuƒÓë@ÌlÈiØáEs*A.‹¯î8NsE"GÒÀIÖ ž ¼æƒ: -²A¸ ¹‘ãñ¸I>„ˆã8tÿƒ¨nëìÑ–Ø:$Ñ,ºÔß›ÎT–P |JAìXØØRÆ0.}ß øDä±ÜCR§VòÏAøèÃUPm9ªGLÊ¢ØF1Ø6Æ|õA”§¢®á$ÙæÞ(Vˆ3Um²8p -~Tlàø2¤¡†¿RAµíÄ^Q\X ¥+\^K|j»Vâ…«jAÙž¤pEæ‚“â+C Ä"z.‰…{âU˃0¥õ -[!ª3-§¢›Œ¸[®Æ%N´ƒÔ£¢\þcñ©èpˆ¡Û~⸠„zËœ>"¢šÖ)³n2'föLáIDs ‹E$˜eÖDtàòâ~Ã)wé˨£Æè«XÝ ¢œÈ`ÄÙY­µ*QúÆb##»É~ØÀAeÜwhwÆÕ=g!në»rXiÕèE’jœfüïåûðf-| IñÀq=Wõx¥„+8¿¨Äe•ªæ)¨}jGüb®úCI˜ áeƒâÃŒ’³V“Cî~x'ÈÕ—8NwCW ‡X;$r•tÅšÁÊs ¼Å\œS˜‡üX4{))ô‘Óâ¡¨ä «øá1žõ-ñ¨á"ë |Ѳ7UEAßh½9fAJÕÜù¡ˆœXŒ[¥êçÙDä.§ñ£=Ô–¸_)7'§Ìa礼Ž‹nW§IDœ¢aXÎLƒ4y£©>SïÐnÐL½U"èðj’ÌU­4#€É¾Zlú’Ll¥°£jwp×N‹‰‰3û«T‰ù€äÅþ"(³ÅW9„et$liŒÒÀP<£IIœÆÿ.œºb¶þ ³µÜpNø€ÜÞŠø8˜} z`í€Wv”ëO“äËçrPGoXÎag ‘ø%<䊋ÀÁÐE2ÔˆwhâØ< <]Ù[)õ¡Æ’gÍêáe~˜¡Ä†…ºÇr 0 ½8óMc=“¥ f9Ó”M‰¬ƒ•õ‰`Kxu$L J”BµT¯7œSU¤*Q•SCw˜Ë”®L°Tp9I€¬ámÍ ó€\´<Ç™ýÕÅ ¨,§9NQÒG³µ1Z–qh,ñR¥rfèÉ™¦Àc†‚»^Ä\òMUÑr0¦ÔzL'U’ëÏÉõÅå'æ•£ÖH.…ÓÃII…,”±i02ÐI-¹á32è"å.ûUrPŸT5‹0´:Ñ}IbivS lh’YüzQÞÐãÖ8Îà‡êÄžKÄj*oÈzLÅé¢lŽj«F!4‡¤B’´¹©FYbúX­Í$o€=á¬Ð¦¹ª=‚ÌèSΡ…âÂ,wœ)#‘ÃUsA}¤ÛïSÍb ¤jÅ‹¡\Hì,*5]yHàËÍê©ÂSŠ ´NŠÔ i“i;‰"íEñ6ø,\ÁÄÌÆZ ÌtX®Iê äÌtHjiÍw‰UGiN•pPF‡Ù{lLª$[£c?þé$”*!a€ªÎì!?frHXq¼,³ à#Ä  Ìj¥‘† .ÞˆóV]ÒyH¹9xãÁƒÀ#åxÆ[’ˆqÒGʬ(ü€Éí rhFÊpžDwâVíÅž”YÿÄQ‰S¡peT>5™ÅÈŠº¸ÚݵÜ.Ùíw5ß)¼ÕÄ2Ï…oˆ£ÅÂÝ°ÿì ŽfH¼…p\½{¥û¢[sì–Ìð(Sä¨t:'ØÙµƒ}<‰ÄBe”¦ *š2DõMºUhÌŽYm`bÝ’$!å¿Ñ†{Y]Ôµt†°‚ô¹â= ›Ï{†^¸4#‚T|¦’’•C“ÊYD±PêwÆe¾Ù 5 »K‡úu‡ƒF3±3+ey@p²½—D«ð« ‹Œ -„œ%ª"ÐqðûQ­JhÍ ž”Xí¤ -~ÈVgåP3Ò6 ÖÝ5y U˜$®¤!×!ÈPtÔ1]+;²Ão©ua/‹’ýµrž"ö¹çâ‰pKK’·•iRÕ˜ç‘›É HÆÍÖ#IMŒpšÍ´ 0ÃBÕŠÀ\ hox_9ý|Uó¦ó*V^2)ùŠg’ÁÕIêAøA0ÛGhí‘MÞ÷<8®ƒ„%bÁL ÐŒ½ Q-὘ Ð/7æÌzœ³á礢E÷ðš+f¤pÀæxÈ +è!xï3¶†Kî]$V¨åÚgØó›C¦L¸9-''µ§¼†´WMÜø8ëõÀÊk' +/<àõ™@F;8v°¿*W­/Àt¼›°KIÊψl 52¥VôÍŠ–Å"|ó!üÆ5U»”zgŠÒ$å *|[ ½D$2PfÞoœêy7®’¾²¾¨jlSˆj¼¤i)ª½œ™B@’‡Iær?AwU%§:Ë -Èï º /¿¬õX÷‹7jÏR’9ï^Ôdc|$§uÝ£•€7 "æìAÐÔO(œãÆBúÐK€:'éC¬Ÿ–×ãD…œ¾®Wr¬Ü“êR}©rêòªb^‰RþèE2“Ãýœ†Ž´\2Íktì¥H’)äƦK6ôEĨ&Óä°ò…m’ð÷ƒM© Ù r&¢ìn X!¨~¸ÆBqü©½S´å’ú)} -nŸPIqAù ¥¾<öPQlÅ áR ÄÆYY¨eá+ï£Ô¥ pï(bÇ…+–<$Â`£ègåŒ{×óEɨļ¹„)²ýÑÑb%V•ìš’n)øQ–6¹(žãe„´" G^¬èl£‡HR<E£<`¥üŒANX5É©¦«·"ªÂ+q…Rsðañ$Î#(§CpbvHË`ÌeúÆuâé+›Œ"‹œ¤Õ«7i‚C”‘k!!>²™"ª;!ÝB˜Ý,öޣР.ÞGG·ôãgŸû*¾øâìHßÄß¿øóùÙ·tæ.éØ={&àçóŸi~“;~'Mž<¿¸zóÊúùè/gg§ó£YùxÇÍ>£ÿÿø˧q¹ÿåÇì*nöïôãôË,;žý×»ÙÞúnç\-wó iMè_§¨Nk:(lÿŒ®5øþýé»Ë`òîÙ8¢ovs‹06ªÒzÉèàlÚ5‹žty¶¬×qCà4 -Üõ9¾zÙm%Wã,§ÔgW£ÞiïÛ¸<¥*äýЗ‡ƒAtýÝ×8àÚ?çTnŠ2š†Ü\µcv^>‡m9ØÐ2:è'5‘œ€…ôp£”‚ŒžÂKG°×1WšTŽÔ0¢ö†çxKSÔÔ¬už€[Š¯¨¨¸‚5g ,(%³ôs6Ž4¹’ ÂÅ©‚a¬ÖcXt£’LAs*°x=Utjl‰¢Pi '¢}#ªUO2" -u Ø5¹(I·t`ãˆo|ÐFŒìrº©Èýºo­áaÐϲ’õàáòsË<ú˶öPÀ÷C4è q”I„¶SŸaÅÔ.ƾщá|1$€³vª`Äš ˜Ëmí+¸:¯«JâbîàXïñxÚžK[*8 vaPp&À’dr’¹ëÈÐìiU©;°±ÐDø;Â@-cn -„FËgô.öù¦#@Äöê‹7¡vé\€Éw4c"Ë‚ÙxcM‡Áš*FLM5` ÖéqXÀp>F6ŒX€³³Ž´HgíŒiäYß²Ö6 Ff½OCî8ëÆ÷m`šû àðT¡ìh)¬ž÷$éo„‡tÂc{ä/1Dî½£ÓœÅô¡æÞv0öÖ¡í²ïöC……óv´m`¬C1´iGŽÃÍð&"”å!Ý–¶tEðyÒ/|nÜ8Ä„È1}z[{mkÔö—w2"<¸*‡ÕÁÞ À¢¡íh3r°kÞLìè`(Á8X<•«ã|8zRâ0„¾ëˆ/QIÅúÖ6÷êñ´tã †~^¡1·™¹¾'K - ñæý*»œÊSï—«}hLídhĈÍìcÖ6„6¯À†{Nç§míÔÀή²ÚÎ8G` : ŒqoÇ ‹ « ¤ÌÞ(ùƘÆB<‘.0RôuD¼íÁh͊CËdZlì±uL!£QJßâl‹ ÅëÛÏB’ìcÍÚÖŽºÒÐ7¦0’Ä¡óLÌtIPŠV8ÓÐ)Zó6 Ö: -°*Û¤z„©[ÍÄjw.’J£BéÀë#ØëËÎöbéî7ýøvNÚeÅkpV‹zÝ€%UN8vG¡åεxßYqDO-ÁCó] `YV9§X³]Œ!¿ýDÅ2ºaçC³¹™bå-W3wpv©/N² ?–O ¾òr‰²à »™M…Ì„èۨ׈SÈ20$& /XKuç`Ӷưï¹5ª!¾ýfw$}YÄ pNT ‹0B7½;È*rÆQ äÚxM€(«­peÖšFôÀ¶è(’S³¶œ@ŽH¤¨Àª¬BörG0DëÀ £¸F…Jo^ÙÌàDÕÅÀVÚ8‚{°o`”äb°Ý*D!4‘\7eéÞÚ¦s¢|ãTÁ\QÀX½}k=äNšJÒ¥¬ˆ]ÔC¦7Õ¾œrªL¤úfê1ÇeóÙ63ó±½Q7GõË3ÖõIìwWcîrzÙ~6> ÿP’M#ãbŒëª›Ã)I#ŠËÈdÈàh3¤° *Ì9x8 _SÞ££o¤=3Öa%„àŒ+#æåHZôãZíZEµ_Jàj‚Ë-\J*Æ•wJkYGÙïsšï£u-X&rQ²ê ½%äkë  ½ß®½¸öŒ2`fy˜ÆnKèx°Ù*–ÜÑ3DÛ°j,J™vÐ)®7•¾5Ml²ýKe“…±n;;T'w$€ˆ-žN?. ,¿†õC?¥Ùuw|_a©Ÿ¡Qžða\ºzcSÆÉç¸dK–ìê­ˆ#ôá3›$@W ›ðÙ^ze'ªå+4lÅd£éi—3g°ÐØ$ -C¡CCGÎIvYÚŽÒV3ÜŒZ‡³¥Gu)Ù·}YTU¶A˜°žFN -Â]Î -LÕÖ2v­+½ú,‚áü‰€yËÔœÏ:~êPÚz…•¨këŒ2Mék»Î3럲¯Ô™!š40á†dÀ®‹ôƒû-Ê¢k³I IŸŽÁèÂÚN1£êÌ-#VÚ` -M—M,pC/ÞÁCÕÖ쓹ȌDíQ`ƺŽ$·³¥£˜ˆ9ͤ@וɣ”ãÅ¥ÀèøˆžÌ´ÐƒÒ4d9,£¾h×G3&ToÚ0z‚‹]Ù_Þ‰ö^¸êO=aT&.p–Þ!Þ6xáT -vƒlYIÝ"PQ¿)Í–ö« Ub×ç¢vŠ‘h€UÁÈYˆtQà¬üâ `°Á™Ñm±_ý`D®ˆ®àAÕéS×n¯¬M8ŽJp‚Ë,]“—Í -Á£…NÍnÇýv’èë—UèÞÖ2[:0qæJ’Š3P4\FÌ\BTÄÅêVæÁÐa ¢æ°cÞo|O;0 ØÇdm“2nIx ÆaD²-öÔJÛªGí5|8½õzc -ãÜÌ6ÊÎxzÞ€-Ô>‚¦Rd1Y. -"ž’ nh¢…+†À»Õï!Kuµ³’ºYJòÃF3‡¾¯`ŽþP°``.u%ßSº´_ý`hbªŽ×õNÑlæ1(@¤R rz •ºz€L,BCç•àc]åa,äâÆ«gö£01ç dž“2KH¬­R2“pvöñ²¶gýv½jQ&¢p~Ó™ø¡¯ÀVm˜X¹då©‚9¬€Õ -Ê5ÂMÈA ƒë>stream -mNK©*k¯uˆ¹wÕ°ŒÀöÁÚFõk(⧠\CœäŒÎ#iIµSÇf…}›  Û#Àíˆó^ÿAïBlæi€p Õ@u,XŽ+B› â«XV¸WoûM/‡>x; ÂÀ'ÔO€ƒ‚L}Ê6­ßlÒ4]mƒ¸&(åÞUÑ?Ž£g'v‰I¤¹a2~ÎJ¦@]€*J8æ’u ­ë·P]\9ÿÔ¶f…‚>\ÎfÖ¯´jZV®ñ- ‚ü â;Iï&õ E¼œ,snRf+vƒu`*(îÅnŽO8¯GxPÌÌ»Ó1³ÉIƼ^­ZE À§©_Nfkç!à‡ÐgÀõx ‘g«Zºb¾ ©@õÁ*ÍÃÏd‚óhlâ{fm‹: m°s€k•-bµ÷ù0 -qÉr2r–²ƒz\„vCÑìf"hO;@x§^·"ˆ ¦¬dX èQgû–‡nm3m« ‘AÎ5´‡ÕèvNÂœ1/ï”Ïh±Øܪ|Èê‘…üÕB:Ø™F(î¢eÚ×΂gUAS1˜iÏ°&[…œjf‚Æ¢ž.¸œ¥´lpª®Djí"´fæÅÁߡlj*ÌÁA'ÛÀÌo+u?¶Å™F9:³Š0ÝAÞÚš|–Ä«½ª¼BZGUTêöI?*’y³ðæÏÞ¦Ë2Ú~ý¸¶A7§ ÂÖØÑ93ŒÖAó;)Ñ,À0Ø"l8 ³ôŠª˜ÑÁ`kPÑ)ä¶ÚH¼8hQÁ¥d»b]—uBÕÜw!©ü±qufqo~?ƒ1—$ `?aœ+¬ºvÝ„]$Ž@€A&ä_övèä‹ë *VBj~ÕŽå]ezá^c{ž•TŒ5aôÙ_W sîNßÙMÔ~êâD÷Š1«t]7ºññU Òª"ºq¦bËÒ|¿Ÿ¬[v+Ý7p5}bU¯0L!Û°²êð$ŸV6>Ê=V«Ï‹y2a,t ¼ññMp©"%³·¬fý"„G€>Ãj>0küG%cï š®Þ¼hSí®µY¤mš›_žP[d^êøopBXP¸Ñ°¡¶£“ÍËs¿Iî¨7¤/bA½qëµÈÞ W¢‹Ñ0°A õ’OF„c-›S:Râpé"uý]D#@s†Ê@¾§bOIE!!Äx?›H˜TZM©û6ÉÐbâ×TˆbF2wò„z¼†ª’*èj“±Bo—*M•TTi”PJ=ÛéR¾Ñ®ƒ- ¹ØëãILŠRìŽ3¹«ìÑ2ØÆ$ŨIÕr2ÐAG,Y¦’¹ƒ -*\’"õÌ-tä›Ñ6uÉSÉ]‚¦ÂðÙ(j%λhcõ©·Æó¥­WvÔ›hDÓ÷õy³ú¡b«rH¹38h[:âÞ>fa%¼ÖkP†²tMvr¨sh{U„Tº,ÉØ+$Òs¶,Ìá›hpý*gArqÀBìtµ€Î,9wûSPc/¼Ä«µeyV7QØ.$•ÌÁ &Ñd2d÷W=êFˆ™)÷bR–£¨Ë(Ò2¨.¦7óΉµ¨†ÌÆëÁ0æ‚÷µéhKwÊŽq$Ç|¥Ú`G„ŒDt}°¡_Oö *(CX²ÍÀ|·‹Ø°m°¾W»o]lá>)ŠC:BåЪœ 뀳)2xd¿cí,Vgà‘gBq\‘ªLˆs`&wdOf‰b‚oÝ’XŒ® +k×þæîa€œ1‡5ò%0Ý #ª8EfŒÚOÂ` lìwõ&ØFÓ¥¥â2¤ùUó Pp0 òÔ/œ®Ò/ó”Ý@­½70ÓŠ½"Ô¢J£‹ˆZ ýÚ%#¬\•Ü¸ýl -ìÔU›u\­QÇ¢tÄ:ÈÕpº¹2¢×`úúVÅ–‚TÉܸ؂u`QxcfŒÝE,‰†]€ÌH AèN7Q] åæ*çD®vUkNûDÉ«à`‚ Wû'½sÊ¥r†UŽÔºš;Zd¿HÝs.sÙ—ZD=z©ŒTÀŒ Aò¶°s/ñ)Lc¢ ù|ÐNãac7G‚ªñ@ã­Lå¾ÀŽ­Ým±SØ2‰œ=¥JPeĸíûJÐK'|l"V‚ìN^}'nu×Ê=:om›³³òÛÈ ÚãncI‘£ÝÙ:5‹®ëç=Û¹…«‹ÎrXø2E«K&PÕ0òŠæêìDküL¸øõª -4VÉ m 7— V¬`±QaôKÉ]êðv¥ÐXƒË¶µO*q î怲,Ôªu ËܘÉ6ÑÏœ|s›&¸™œºÅRbßD.‘ Àèì0ÑOœùѲS1‹ˆO×´ÐfÒMË9f[±P^®»b¼—YäÐRy$-Fˆ‘,¿ZÛˆ¤ð:õœhoÙ¡kÁг¡°ÅÁÔש³«%ç\ƒM#´`ZmÚË{U²s6úŒ”š]ÝnŒVìÁÚpiê±û¦ûƒº½ÚxÇŒ:§Ò¥4cLQ ³±}ʹ»’iÏ4V“éJ¿£{Q ? ‰ÞGõ_{&ÖœîÌ\&Kóí(=ÿÔ%—¦wiôÜKCׂãÐ8³R±»‡¬aîŸ7« §FÓñg×pM@$@Û\MMõŒm£5ãü=3 ¦1,]F‹È‡ÐÃèuìSÌZ±3jÆ?"ÔÅ7Ć@ÖÎiŽæ;ßIœƒÖÌ+¾ò˜‚É‹Q¦[¢“EØ´Ñ<:¯—ÑnBu”¼5Ö!~å™Úe‡j¤*Z€-k”0BspÑ"‡LäÊ=–dÉ›GÊhoë8´±ƒd—ÃoŽ!Ùɘx?˜[6ûPš›¥vmNæ>‘‡ž¤ 4N9N¬.P%7 ÍŽj©„îEÉJìJR¨y²]¸Ø™6©G¬cz¿¯àQ»Ó{J§â¸áIa)Û·J5¤<æö]Í<Ñr„p±T'm9€,Š£„.Bg‡`£­("ežÙnè½²ogŽ@¹ÉYd&aa(mÜEÉ+h.Oæ·–ºÔ—§Žw,|¦º,¢)JQ÷-ÚQ¶Ï<ô „‘ -çA¼,›=¹Šñ¹*4¡ƒNÄ ó×ý{ƒx5õŠQפzBN‘Üï­Z²‹ëY#ü$¨°ø®hôcxˆŸD‚X F@À؇C^ñœðï%L‚ÈÙQQ€Y£§q8ÒØÁ0 8/š[\ÖK p%Mˆ~ƒ{‡t&ÒQs“hV=‰·…Ȥ Ûa± ®/¦ur(J&Àس¤jÄî­Áüʛ׶u’= ¨ªÀž©v[ziuGëº÷È4¶ÿ¸5c?-`³ ´Šµ -4Ÿ(Ø^>7LLäÁÜS‡n£× áƒàŠ{l|3&¥º m¼‡*Ù’W? Z5ÕK6ù˜3Ñ[ÞÈ€YH¹8_hakìVOlº¡;€»ÐREn¬Ê´]»ONøiJžåý1æ%Zhyf5A0¨ÃJ-ÝÙƒËfØ÷áÐg#“ù*—7¥ž"«Ê¿=m‡X•j‘º†Qœ·ÌimU9’¸¼·­âoνš“--h”Œ½ƒl„„«ì+¸¹‘ðI—ÊÕ§,îESˆè 5sÎ#éwèqï± ÄÍOd©¬ªÔk貧sçZ˜$^ålN<°I(B”$_N¥N½CZwÙ`qsBU o¬oúžuìŒZÉS„Åh u°‹Ã±¼žºëë(Ì4æXLWéšÉ,ÇÐüÚÍC¥ÔÛ¦fkkúÅ<Cg´8“vÐœ Y9pR>&y†šUCE2äHJ´äcE²»9ÀyѵhHºô|¿ÃÐmúpÈ2eÏ0ô3£.+û -α{Wj”á0L²&ÃQNÝ‹®«ý†A8_Ñæ\O½ˆÐ*½_ƒè´dDzy'âcÅÜŸ›JbCøV°s¹ÍŠQ$óz¿£ƒÖr8 £4Y„¯`ôæÈJë ôð…Ìkt`×8sò/³!”æÍM)©]k(9,]¨A@YjgˆÊL™\§¾h[ÓÛUI5ülyx¸aº¬à¤®€ŒVmÉÍ9üY{ðž÷­w`±»Eо¢`#Wcð%ªZ =’H# -˜‚ôÕí–È!t=cÈý#Xƒ‘¦zQ.¨ÖÝ<‹bQèÈR?ú–# àdáQ\Œk_Á£³®y pô)”êƒh'‹d:býZÖÕ"”D©þÐ]Xƒ½o–Æ -×`wJt(²Ökbyztw›”tÐ:—T„ÿÜWpQZk$mëJ¢¬‚ !læ3Þ+ó=Õ¼75M!~ú’ù? ÙÞÁ¶€]ÌäýÚ5µµ¬´Ò-¿£×bCXÅÖÎH€I‹æE\Õ߬ÕI8œÅȵÒO•ù¡­ÒBH×Á>f·?K#bµ©%F0˜¯RTY®á2èáVS0ÊÒu·K6huæ7›gVe1í\(%fî7ZTÈDÔ€‹¤yþ#²Så‡Þ) ï'°vŒÔ¢XFd±œ‰ÕLi+€–°Ø×Ò:µÔSQtvQ£Øj™û+$¨f몫ÊGÍÛ­XÔ¤¸lžŒE£õ›jRårGÑ, û°ëž¶^}ªÐ«3×8à »“êè{Ä£ÑV‹ìǺÇ7j0§"Yïm-˜³±%ÖÆŽuÔ’z–TqN8ìSèa@ªh[Û]˜dÁEj›ØLIYrge¹Eìš{yÓÌâ×O¡ï™¦ËÄ”Óü$åÇ©í+8g#aECMšïUb¢Od‚æ:Qª“ô†¨Øê DMÂÝ\·z(6”–a0dÌŠÒ“àÙ]­Ñœ¨“¹Wóªà’S5ꎒ™e@¨Ë¡Q{îtjÉŽ•0ýZs~Öž•¦]éV{Òž:Z§ëT_Õº^¨v‚Æ|0QÛ$Œ—£4­cèj¿ùÌý€Z5ŠÚºÁ&üq¾{ÓÑ6Íj<[Ø®%ýªuŠ|«ÜÇZ{þZÅ{ÚAêñ"­ÓU¨‘¢a*² ìøè=5Ә׸ˆâD€¹s¦ê ^K¯NSå—i§|¶e1aPšu÷fs_BiÕfœ—ïN uÌk?úGCqg8j ®\kÞîS2³/ê¿ÖkÓ(Y”w–­'X–¥›`B5OØmÂ"EÛ8f Pèi¤:f¥>JUªCÐõCU«ŒRÓ*øWÉ“mzK³–"î£ØÇÌC©v?@¬€ï©\?Ÿ\›Ò8žîYS—Å4AÛŽáêóS9.Õ.Ãxq’„·ÉÒ¨ç{MÝÝ w¤™F¶uÎ~4âÜ,àÃÙY†!É‚¥„Ä°‚a4 ða…pɶ‰–¾Æî‚1¹Ï£€¦Ù<hŠ^bŸº¿\ =ÁlëÁéu´wŽqÕÐiéæ‚`ç꣦+}V¦œ„¦|Ì+ÐÌ<CŸA³Õ±€L• ŠÏX…ŸÕ9±JrY³øN-8lÕl –áIN™JOb! °B$‹­O‡Í¢Ç±[Ì(zíh#ª{"S?‰¹ô”­'GÀxåÄpVC^¬m QÌ*']:¨Øí¼kÏuKíÞÈk‚Ì4~ö¹ZÆÌøR$Ý©‚ãx £&dÓJYq¿„ŒÞr³ ûD¿hÉõìiLy£ê¤ÅXš>ß=³²ïS7 1™ÕÈÜi„FM=Ú–8KöLsTêáÐKsh¥3ý¼0K¤”ÅM*Š.|Ф4 6µZÔúCGzÕË8OœÆ¸ מvbÏz›²ùUÆhŠ¹Ð_¿þý=ΚÃ;Æœ¦šõîÔÀÝ-ÄB#hF§¨\€¹;¤u†àÁŒd–(pljò[`­{zpÉ{¿ç8XÛÚm°={à &ÂI÷îõK¸ê¶97¦Å}šùŠ™Â„%Û3Û.t‹OþË_lQÙü–»\ÞJχi:ö1‹>ȱöÏÞ–Àü ¡ìm×£òÓ˜Xàú&é%{Z˜mði]g¯<fK6bjê[QwçúÌf_G¯˜j»½, åÒQiz)ÄxyJ¿;Ì8m;=VPÏì["ÒRVÔŒùQZ¤Wµ!/ŽÀ®µÖë‹ì%Ðkö s¹VVÒ‚jëæpåu Õ¼l-NງQOA‘B«ïÔ´@_ð“S–Siq`c*}S_&‘zz*}o.%d«{ÔI©ÍjDg1ƒe¬m}‰ÙgYßÒ“Þ§Ú“vC.~Õ)¦ ¹8°qC/yÉ|¿­±©’µÀ-ö>J.„gÊQÌT5:¿Üýö’–Ô°Û7³BF7ËëoG«®ba`Ò}¨|6àñÛÄ\wb}±ÉZ»Ž Q*êcG\ìz´¼.z؆²5gËøè„•¶–1oacÞ¿Ö=qCîYÿ, Bèym̱¿*ÙPç~w ÚŠ=Kˆ•šbî^:@5Š7¿>Kùª‰ží øl©ÑÙOÆ.Û˜Ã<÷zs‹ì "ûauL©iþ6 kaO–€Jƒxè{̦…†Iˆí$˜ªö±Yz… ~2ÇX—é¨öÆ  Ónm°„w-!ò4!tVóÖcYÜ#EBWM¥îïŽ^“ÝV¯X`qZö”öŒël‹Ì»mºoŽ)30ë…÷—wb[T{;Ížtª`“Q\Þ|î¨ÕMªFHgŠí¡|Uü»Ø­“øØ`è“…ÇTá½äSI1ø°l5Jç4Å©‚}OŒc¥û²£5•éŸ'yéBé¹4 çˆÕÁX㲪X'ö Ü —Mjà…MãÙ’ÕTT%“ñÞòoåZ3m0Û"-%ºq*UË­üÁM¡ÁûË;±Þ[ÏÚ$ -çTÁf¿l7%@Ó4ÄñšÄ´-MB'öÞÖ…ÙŽúöÅ`õÿ²3Ì>úxöã_¯ýúÃg_µ_œ=¿¸zóêÛƒËËùÅ™ŸÏ>>»þè/gg§ó£Cgþ‰céÿ?þbìýì³×ôÛÏÿ÷Ç_é§ÿC _fÞ;žý×»Ù^únçY-pPv± å -Q¸Ú$7R+&F¹z^!~°¿ôwfKc/ö—õºJþÀ&‚|³ãX|‡–ƒP/סBœ!±ùѧnq Ž0Y€¸×äÝ‘k‰îÉî‘„%êÇCƒ€ŠÄ¡âÿCòžÎ } *¿vÒìkâ€ÿŠ†ì8. -âØ_ú1§DðR‚‰³ö•{‡FQè ’Ã5D¶“øÖ*M'u—̇˜†€Œ—€Ä쉺÷ÌŠÕ ‚ðn‚(  &š=.AhKè†Ùj0DWEàksëZ¥Îc~ö¶¥ݲ^‘]¸Ê ÆxÛz­Ôèæç°vt$ˆµÔ¨4X8ú!WUÍÁe&¾ŽË˜{’M -C>I° ;|†‡$Âìt°qÞ>ã­ª‡JÖ”×J³¦ºvwÍxùÚÙ½‰„‘b¸eÆ+5Z¶vwïÂËߌ¢fiI­ƒŸ ÷\[Ÿa€_Ûâõ3¸³Ã1ÜücD22ú×ÿ>0¶Yy?Ý;üáÂûN™[à›ÈÓ$®ö $’¼7™íÍK&«û{Mæè†ÕÚô\òåÁež ¿ƒ=  Û¸* –Í*ŸB üÞjm:ê[òåÁpoŸ˜ ä³£ÍAÊrÛ¹Ù¦OþÖU¹kI'ü̱r¢i1ÁŠ¾üpC™Œ!‘€ODFW2-cá¢o5·4ºñ¹“UÆ´©üÌ°¸2#&¼ñ`ÄŠýüéíÂA¦#ú»ýÁÍ®6 ôAgçZ8ÇÁéµiNŒó÷ü›ÐW*þ~Ûƒ…®6ã¾/›Í8ÿ·aBýn{°lþËWìºxÝ#²‘»[Ïã{»Î Ÿ[©ÑÂçÞ…£É¾øóùÙ·Çg—Çg??{&`ft¦vþüO¼“Gßÿç—:>¡nv>é?gÿ¶óÉ_ïÿùühŽŸŸ^ŸŸ\üú¶ŸÎ>úÇéÉ=zFú8þéêrþæãÙ¿î|òÙÅÅÁ‡¯ŽOŽ.ægxfŸ|uv9>Ã?.}=dzÎúŸùáåóó«³#ûóó|<ûä/gLJÔæ{úÈÙÏ×ßûûÁÉ•¼ø’gBM/ß¼ý0}xƒFwí½ï©~öÕ‹ÏN^¿:xáWãñµ¼cNhsïSqXu -¿®¼9¿>À<¡®<“WóãŸ_]®<k¾Á{sÇ]›Læ¾7¿]¾Zy6Úú_Wø¦ É³óï//ï˜ã¸ o¸õÇ'óÕ±ãµwî}»ÃªS;»:ýæðòàïkÌlúʽOìòêâ§Uçv1su²:Ž±æ÷cv]^uN?¼™ÿébþ¿Wó³ÃÕ©Á·d×®NèãóUç)¯®8=ûÎ;Þ¿eðËy† ë4ÿaÅÙ#~ËÜóÎ|~uq8ÿòâàõ«ãÕ9©³•·æøìŽÛ]ãgrþz~qpy~±ò|Æ6õŸÝA®mQxÀ˳w~úúüÍñåwçCŒƒe™U‡ðÉçó—³O·"ঈ€i+.™ÈVÜŠ€ïº7[ðÑ‹€+#Å­¸ "àÊjÌÇ)¾¼8 sòçóã7[p+neÀ­ ¸•? `ô|þ÷ùÉ÷¯ŽÎy²gáÉHéé HëìΆ‹HkìΓ‘V Þ\}>ÿûñ´†p4}éÞwZxV=Žç§“«;¨×{‹ˆCøòàê͛ンçwÎqì•oÒÑêèñè!ðã:GlÓ¯Ë꛲:Gqô,ÅùË—oæ—wߌÇ{û¿á>¾{¿²UÿÍÕÅ˃Ãù÷‡'«k<®¿´Å¿a.o^Ï¿¹ºã„=>­â x·žŸœ_üÛ/¯î±§LݯëœEi}ÿ ûÊÓ¡¾:9¸Ø;?{syp¶úæ-¾ø†›ugùÅ?^ŸŸÍßa–ã‹IR!”þ³ê"ýsåUùçCÓ~¹l¶²ãY^c*kjˆùöüøìr½Ì‡ÑUο×»¯8þñqGkÝMç!ž¦Ááô$kÛ6ûä=6qéÌ)›‚V¦»C|Ÿìšn.cú·Õ/Ëßâ®ý+;(?¶k\¢ GÕ?­Î <·òŒ6Ms«³iÛ¬ K'¸ÍÚðÎ{s‡gÊ„,­UòPá$¿CŒ½ù~?­|Ê õYyF›N}¾[Ù&´÷êàìl~òýüd~¸ŽnjñÅû7³®l•|×I.¾ø`äèóã7¯Oç§ó³Ë¯^?>šäfößÙÂOíçªÛÊ¿V:¬ù&£•M'_+ë† Ê_/?è£üÓêje§šÇĉï!Bõ릷‰hoõ(ÔG˜)eÛ³é˜meu×£Ál+ÏèèŠ7åöŸ­¬œ88<¼:½ºÛ ;YŽé+÷¾ÁsfWžÞÑÑñåñßט\asuϗ竱ó˜ÈÊØÿåÅùéêÞ­Üøþwå^{b\=“´½õìÊsù µÝV×ÊJëûwÙ:ùåàוçDDéòàb-"&í@˼ŽõËã““u|ÄO`F'Çg󃕃hN¿>_=bòÂýG²­ÌÁN¨’}þÏ<…UIÓõ·H298;>]ƒ[ø@1*;ýÝÓq›iOÅmæðɹͬ>£­ÛÌÖmæ7rü+«¯›ßÌ·hÃqõá“ó›Y}F›®Æ~ú/·Ž3›KŸVfâ‡ãÌïeo¾ãÌá“sœY}F›N~¶Ž3[Ç™ £IwxˈfýIÕýiòñ%\`9ýN6yG6üÆÜ5¾Çi†zG£áÆöŸŸ¾>Câ7Ww ®÷€æ5ìÙ^XY‰ø·;ì=“ý@Óû—^WžÈê7æoåÂl:Æ^}sîp5šnN|µÂʹcÊÓ‰ø‡Ð'\_¾:_®Nj-]SLxL”ôç£|¤ôñ˜?Þik·Qˆï0Ž­ýykÞÚŸ—ÙŸ×Ë–¹µ?oíÏ6¹­ýùƒ¢YX ƒût-”»µ9?„p¸µ9omÎÛú)[›óÖæ|Û ·6çÐÇnmÎ[›ó‡ß‘_ç''翬º''Ç?¿º¤çÏ‘¡såý¹ùÚýóè+㾫‹—$‡}¿^–ük/m®áF®ýzs»öÎfã‹ Çç+oÓÑñË—Woæ{çgÄ|Ÿ­ŽÕÞ»÷)êžÕZsZÛ²ÄÂ(oUp[Ü}21Ÿþ|1ŸŸ}J4cþ)ÍøøçóOÿ~|~2¿üôb~ôéùÅÁÙ]¦¥G¨›ÛÖ6¾{6[ÝÜV7÷{ÕÍ­#±mtÊ\••-Góúc-õÜäû§Qmå‰üóøôêòŽ*«ÓgíLûóù1 [û¸fì;ó¹ˆ)ûzãß 6í©+¢ü“ÖD=%uÍ›×óCE/î#$âÞ ú+B]„µuQ‹/nòUÓÁ~ñ×çgów˜åøâV™³Uæl•9¿u~P݈2G5;¬ÓÙ*s¶Êœ­2çýcÊ­2g«ÌÙ*sÞóVm•9¿E™ó÷ê©ù%mfê{•“¯jê´›®àxÊ:žXTñ}ê¢[mÓs<|zŽÕ'²áé9ždmÕwgÃós¬žhd›Ÿã)éº^OEOŽ/¿=8¾K½¿%¡[ºÍpµöeÙtê¹Ínµ¥ž.²= -ºÍlõ¤2[­½­Û¬Vï0Ž‡ÓÉjõûJýôý«ƒ£ó_~ßu‡VNzµM\°¬ö½$.x Šò¸ƒýW.ztVzýcÃئ_—Õ7eåÁÜôÞ'rþòå›ù%nÆÅüh-ŒöØ°À7<Óß•Œðî›û„……MÙœû±w=ž}Ù -q[!n󄸼÷žçòÌç?¬:‰Õ™•‡àU|t+ÏäÕ|ºÞ|£wguþþ!Ø{ŸVß_ŽÖð1ÕÖIýQ¶êßÆ·l°lúØŸÇ­þh[õÇæ]—Õ7e«þØ,°UlÕ›„×·ê­úc«þØ\õÇåÁN^OÑ‚}vþýåñåábêT’CëŽOÖÈZpíû—ØWvK<»:ýæðòàïkLmúʽÏìðÃyEÝ÷LVV@>–DF«Ïhí ÝûöêÝ]9[ÑOo溘ÿïÕüìpu‘èÆ[÷>Á—t…Oþ|~¼zH‰¼¼âíK‰*ló/mó/Ý‚Q.ÿ’Û]¹RÐåùê×ó|³±êË‹óÓÕYjn|ïÓyJ顶é”TžÛ¦Sz°tJkpR¿ÓlJD“ôÃÕÅOW'´æPÕ¸†Ä±áÒà6ýË»‡Ý=*ø¢Ó¶jØ­v&jØí¾¸#Äùx¢­î´á~hîéù¡­>£ ÷B[což¼ÚÓ7a¬ìˆòØ,—„øŸšæßíº'®0¿\QJúàêò­ øîÓV -ÜJïùêl¥À­ø^¤À• èV -Ü Ic+nîÞl¥ÀG/®œa+n‚¸òv=N!pƒ¼¦¶bàV ÜŠ[1ðÆ8¶bà£ÿz~~ôóÅÁê÷~ceÀgþÉHùéIëìΆËkìΓ—Wf»oNŠ'V=æ^²RlðœKÕ¾Çac›`t¯þ6Áè£Áhç+dÖx|8í½å ¹ogïuª,?–#¶Ö¤6=löå ɱR ûß~:98üÛ§3¿>8<¾üõßÖп¹üõduÅ©¶~°;õ'Ló‰_©M'¬OSkú[Rmz£§ÏA¼á\³{É?÷¦ ½_^­.~¢UÒŸ­€,'{sóµûO^™Ò^]¼<8œx°Á½öÒý«ÿÖCëÍíÚ;[Y÷>¶I«Û¹<¸«ÌåTð½ùÞ½Oqͺ¼…p­[nøý­M!eå\Zóúc-Õúäû¿›qe -qðÏãÓ«5L(½ý½OŠ©î‡KCõ@¬ÖçÇŒÝö×12~ lŸ ^ØWîfËûmy¿-ƒôäÃõë?beÞÏÖ`mæoñÅpG_w–_üãõùÙüf9¾¸åž¶ÜӇ㞶ÌÓcž¾×«üx¹§w W›N‚Ÿ¦Ùà~œ­7ÿô=¶bX«¾2¥ÿÛFàÉ~ éýÇ1­<‘Õ/Ìßâ¾>›>Øùókî#É£ï=ýéüdç£çW——çgï¸ÙgôÿÁùΕüë_®hžŸÓïovFo¤¥?þ~ü~™y7ûzö_ÿífGxÿ;úÇÏ;¡í:çòì™÷³Sú‹ìï<E·<ÿñ,†Ù³a7¸64ù£Îövð£rÃ0ã‡-ù%ïv‡a˜¡3z}oGþ½ß?Ê ©kü‡õ@Ýs§{;ò‘ýé—í i:ü½—4¥Ïw¢§WOñ¯¶[r ³0пKÄüû—ÇédõZVŒ‚Æíè›ÚI ™Ë|½Œ‰àiuÖ_}F=Éêé'žq×ã@ù/þ²^ð ézO¦°¿óü'ÚÔþr†v4ûùâàè˜XÍYû˜w÷Ù€öÿ3?{þóŽÃ‡&ÿ¡. «ÐHn>Jôµ’ò¬¶f?ž.¼J¯aâoµÔÙóÃwþêówüjpø*6üùóùËÎ0s»t&ú¹Ÿ®÷étôdz¥¿¦{xmwmcžéÎ\; §FÎÓÞx°ÿÝáÉÎäxŽgV›µé!™žé4o;/pÚ^óÀPãRn[ÿ‚­óË ^w½ZÒmf•¯.90+}•ÐÑx`þ²ó‡DB/¯q -x‘f…Ö¤Í/Î'ßÍNÄ׌žÍ>"Œ¿%2<¿8¾?9–”s„®¿¼8>ú9»%üáEVb3iü!ñ7—lúAéÎ^„ņȩuq96!Âõüüüäz›3ˆó_^ #ð‡Do^ÐñÿèãÙ!Kȱ.?£„árBrÅ4ש®FEÊ®Ý2ûE׃0ÿõßü³,üh»žÏ4ý1L?~âôãß|üù‡{ÝÛ'÷n;ûûýO.b)u7¿ýðcrç'ø Aæ€AÈ/k`/bª6ý[OÕ;Q`¬ÌZGSvï¤Áw½üV*¼Â—o§Ãw½|ÿ¥c"ZÜ}ˆÙeïsvÀ(©ùœ’Jq¹ÐÞ¤ºR‡š‡¡!5‚Ä–ªw)z7´!2¤8bô±†œ†?}æ«Ò/?èØ`»Ñ[{¶Oƒ¾…ÐFÖ'O¾Ÿ\¾Z$~öÙk&~UA‚¸“àws Aøîv'rìë®àÒË®‹.Òûöën1=ôÃøðÙäÅg“™ÙòîK™d {þ­}Ú¯ÃûžA&ï]ënµ…i<µô‚—{`‡§CZ‚üIBMÂHkv}ªyV|J»5e/7?²ÀãÕ¦0Ä8– tOW1æÔ²‹…Y’:ÐÍqÃn*CÅÝÓë³ô0^ÓgÌ>ùóùåwóÃó‹#/ŽÕÆwóï}öÕ—:Û^ž_œšS=Ÿ$:iGç?Í_|öÕð‚ô=꽇19oX¡n¬h¸cE&æ^ qtfäõuòôÌ·Ý” ÷ÐF„7è¬T³Oô û[€0Bc@ œ@ ă+»C!zQvãP˜ïO B ßr Ý 9 -È»ƒo @ÉÔ µ<ä—B “E ˜œ´ñ”,ïºZ Óã6ÐÛ>í6 è=&]$QÌÂn+>*-£ãIÃÛ--6–Dˆ ¡Enø›„ŠH½tÀ!ˆb‰•ðܤ ¡Y‡Nª‡4C›OëD‹@ïÆVðá´›{&·D«}€B $G4Ÿ ò4?æ -@!$Ý€“K€!4·xÉ:Û «>ÑÙE/­O€TkfÁ‰ ¡QÒ1<ëº[#]kìÁeÞ•ÆæeŸ*dXÃœúFÞØì½nmþ¤ŠÆœˆw¥IÐGqÄhô~É©Ã'kt-ÆDÒ[v÷Ù”nëPh¶Í7šín.”hS›§ﯩbhS‰•X|È®ÕJtd×:“&Ü4è¹Þ-.Ò舖À×”¤X¢‰¹5Ú­0$p¿vô”YÜu çå –hŠ„sQhîñ,dâöânàUçS@;B»Fk;äâåìZYîL‰ZÀ ßvíp§€®–—~ ðFÚc—«ç—j´tò†RùèÐ -2û5T:[{|U]¼ãtì˜"óZÓ $¸Ä‹OkÃ…V¡^; ~ˆ‰H×ÚÐX䥭èºñ—èlz½òy ÓI7ÜåA4\t;‘º®Ä¨d¾®Ì - •8?M¼)vHïoôÖ<ð¿åÓ6¬z¬•/°£2Ý–ë[··ó'ÖÕõaÜSî?åb„1xÜõà¾( 1ø Ñ¿drÀDР¡õŒFKÀC§õ¬Œœ_xþPýMû3Pé‹<7:?n\šC$b‰BÀ6²²XØps{Ât{\MÓ6•fиMŒE(xblE“,9ÉyòÅ{¨@vùˆí3“ˆW^!î,õhvôÉ€^r9Ð’Ô]º,û¡“PiW1Òù"@8’· -¹a -t'<ÝÝq…oîÂÞâÆ€Sü—¿³È”訓*0ŠŸÓ?VóT7—|aO®íÛ!Þº¹¿' -$ä]€Ê<Ö‰1>aØ*^3ç®n.wµ°Œ }s3dX76M†E{é‚“•úÊÉ<ÅgêpÙb¡«3àóÏÎà9k,À¿\©6û»÷N?ÛI³¯YûI…‚Ÿ‰7$"~J¢ih^äyO8 \pgž.rU‚ÏØoo‡æˇêã ,S¡‚˜DosF.Ðb]nz‰¡ƒÁ‹çùàˆ‚uÃß68û{o‡FD€w‰‹][LŽG`z@KKfÒ[ ŸÐl2uÁxh¤®1Vo>M§“ G„sã P@ÃqyiŒ.´Èœ%Qõ:.ËÂÒf´6> C[‹`@¡Ch mYcTàªÜe†W‰½ 1dúåøÂ:>ÒÔ† ~d¢b€ž=3Z» ÄÌçÐûaháî‚“S!ä s'¢å@×tt":Ñ.–(h‡J"¾$ÝF¾ˆã>Øß{;œ1¡inŸ0] ô€.Æà#ÏK "ô!•ši°Ô¶p…]Ù&X2~ —š7"ûÀÊ -:F4|9WtFRW@mèxÅ–å¢UÈåôÎMk:D·AÀb¸æ¾77loa ÷Wã½ %¨™¥øDÂO˜ù"´A³\ä¾*I#QŸ¶iHE¹¯›PÈJ„ƒ°aÉô:-Ä«xˆ¬Í:Ýù‰D±téáóH© É^•fY™€ž­ñqFS+`:ðƒÃU§e'Î)@ЩЄ‚Æ´£|J@déàˆB¯Ž &Ö«@8m ì/~ÿV«Nú'ºññ‡ÐHø¸Ñ@:ÎiûJt•0 $ Px·Áí‘H# *Ÿ<1Åà —¼ÊðÄš—BKÍì-ØÁ6à~ãÜCÃH$ $Ü¿FÈãGê#3CiÄI&BÚÏ"av¢ó„ºhÆ`¿VûýÃÕÓÆÒ·¡hZ|jDŽ•X÷7±çïqb!šC¤§´PŒÑ±"!ß‘LN’ÍÖ7*‚Üìî"WgG³7¯^Ïg§lŒïRw¸®™¹Á”,9ó \ÿÔÓk‹ç$üæÉláì/É>Óúß:}别”_øFp²jÁ}Θ4X)‡-ñ¥KºIòraÊÍdmDt -€ -µd¦M"rCï¦A*˜ -¬\ñJ2#™,”3¡Å2;¦´ôƒNØZÖÉR“O:x!r„¡‰*AÃJÌLdö+EÕÆ?hô!ƒà(!Ó9^*÷éþ05$’„£Ãeæìañˆ=ÃùÆ0i ¬ˆ¨ÓQ‘´Ï¢Ü±!q`œú&Æ‘•D«Jnž®k|’ÒÂà¯Qî[BaÒ†6GÄW3ùÊ1 g@t{€¢¤òÅëâŠI€]LL%¡z–;Ht¯PKŸ\`¥_ îx$VbkUø‹ÄL>SÇähÞyÊ,¨Ñp c–^\ó˜QÀžˆë‘þÈŒ®~ÚB{K€æ™“!fÌåóˆu­<ƒ–«E‡P…»"Ή†wóìÞjÊM¬á  '!T¢€‚âÇSKh˜§JlÇàkJg‚Ž%­<ú2¸Ïg„  -°ḠÌqÄq׆ÃI ¶8;:—Ik¡óNǨœ"A¨þàBX|–<¯ýIø×ñҷÚy>Z%‚Ø;™ØzV=?¼¿Y=³Ê8ûÄÂdIˆ‚¾q -btDˆÀ*äÜ·HÎ÷28 _9B.C£‹NdžæÌÚœÈhM‡@}!ûÂç•MAÙO1…Ùë çôÌf‚¾—±F5®c0,8Í7"ç,'¨רÙ5r¤½€Ê´â~²âŽÎ’Ã}ψ™(¹gz'´ˆ0³g…„ÚWQqÑ«͉à$x’« “·À¢²ätcŸî¸´¡%–Hé½ ú`ºý$$,cí «&4R!²d5¡Úu ª –zá~E؉„Nâ6ˆGò²)„ ˆ·!úá1Ñ‘lF§0ÒEÁñ©­”]Œ©A†B×RÍ9„A2K  ‚çŒDM";Z­ô­:€Õw…¯8»°q­}®U:ñ¸¸÷5¯çïq^™È(IÛ™0,Qo0‘„s uBäÒçt͘®²ÇÔhAG;ÐY& ÜÏ©ŠÛUD º—ôAhI -‘lÑe]ïê­¢j!óƒ|GŒÄH,ž·N°!.$3§QñBM@ÁÞEÑüUYn‘»¤üÍó­9±‘лêfãáË€+‰†‚öˆ—g2ñ4çFH©v¢@H„³cóôý”Wý˜{ê…š4àÏÚ2ô¢D€2™öåÞæõü=ΫѨl„]S9‚x®é¸ërÄrÀâÙ[‹¡_8KºÜŸ*ïˆûqüí[$ê=ÏÜ"›eëÒ|ö›afwÄÌþÃü—Ÿ]Ìn5´C¿Iœ8$Gœus­BƒB”Î):âr¡ß$æf -¼´ Ö$¦jNÑo.ufÅrÌmöË,]·ßWx%la§ñ}ýã™þµø‡þ~³Ãº×ñ“Ä"Aí>ûe§6ÛrtÝÿØ×?žé_‹èï7·%åÉ}.øáhU2Ì„ƒßM…)9ÿk#ÎÕ‡qßÀ¹úêìõÕåRG¾ûYlKìÌÎG×_ðî­o,ôÎ]Ð ßüýàäÎö?œ_¾ZxÌ]¼yeoÓQú挶ãÕµW÷^\žœÌžÍþ4§‡ó k4vqíï/_¿>Ûé/éûû㓿Ϲ‹‹O>žíÊ&Ñ®^Û¢÷¼Õ·¬ÑÇH>îþÄ–ðÿäÿ“¬¸oë;}ZN ¶’?é¿„²*ØüO?ü>;NßN×} é.?Žo^ɪþßzÃxŸ'äHÆzmÄv¦é§ƒ%Ôúå–ﳯ^<¿ ¤x2ç·÷"¬übï{j^È:ã´¿x~r…Z ÷3Ÿe÷šæ±x±?FV”Of&}•Ofö_ZýOø~2cw7?Þ…ÙèTéxrH}?» ±`¿€YnNHZPã% YZä eYº“5p·­Á{>§ƒ<1ŸÅ7;ŸüÇÙù/gü‘{u1øäÏ4pŽ¢$Vàïs{úÉžZ$híi×ÏfÒàO ÈL6áèÂÿ<~sLÔ.öðýåÁáßÖèáùÁ›ãÃñuÙºï//Îÿ6ŸYrFûûúÂW''WÌlœ_ì¼&ê÷‰,-½4×ÉôÅË—óËÙѯ´‹Çÿ{Eùjvôÿ_ˆ!Ú?ÏÏ.Ï9+ÕÇ—Ü}àïž|s¡#ú×·z§~"ÍtgÄ—cU–Ô‹x,1š³ÙG“±q¬çWgoŽ9äÕ‚ƒ)”g>¡4o](_Ì"Á_%£¹|ñׄ7žÏ_žS§ÿ9¿x£YΫØGf½:¿øçr”‰˜xJÕœk͉5¤z4úGdù?|öp×H aÇ‘ÒàS›†Ö“Q¬ƒÇmzsxÂyû² ú'ιMg£ƒ‹¿M“F¾¹8Ä߬³Ô6篧m~:9;ºF¯ÞÌ¿ý~ÿ¹vÞ—èïó‹Ëkí^_¬x¡ž¤Ôúï„Ÿ_|ùýgoqöoaÊø}<[Ð,Ïþ‹ãOàÖ…ºô†…“z«Æ5ü÷ìõoÀ(w!þ X¤ÔhiH¥º8xÇÁ\® ¬A5 €øJŽÐ¿ºÒf¬€Ính] ‹÷ZWÇâ T·íš“@úmøy#ùÚÄß\aè_žœÿ2˳ח[<¹Å“·Oñûbm÷¯æW³ù?`ÎüPŒí£äc—q©a5.õ÷ʃþA®N.ÿ{‚]¿?>}}Ò±«è}F˵—Íûî}Ÿˆ·˜2o.ƒ×ñ-!ùË·Ì–bvôÿýšàÓ˜[ø¿8;íûw: |{p2¿¼œó ¿ýé=Ïé£ÿšzõý÷Ǽ‚?þsç&|çÛÃÅ%þ误œŸ½oÜB׃¨·}wwH5ù‘Þ$ç^ ˆ[†Œ4lò=ݼûÝ—ÏgßÍtdn ¾ñ„è&n…8ÑÀ|!b0†Øo{ûÿ™ŸÝKÄf_èýc®e6j6Ǿ¼˜ÏÏ´}F˜oR‚/ªcÕ]úRŽÄxfßB_H{ï×{}€µ ˆ‘«Îs"Ë„dôO¤ÜJ–$ÖM^Ž|Ïò:±³Á9Ïq,{TúO¬p2†ÃBìc_ÿúàçùÙåöà µ¢…)#»Vu >ÃÀ¹— ‰Ynæ*<Öˆ#'NAf´TS Ô)«ÍŸ¯¹äÒ9ov‰ý‰i¾ü#½òü)OΚD3Hn€ÿ?òå¦ÖJɈ„ð!ÉÎÞðúuœKhâ‰È´EÌúUäJúò¡ÑWcé_¥­¯‘ÞDkËœ¥—þ“i‹h”Éå _M×D ¸‰-ÎuùWéD~ùÇÁ]ÿjvt• ;ˆ³â308H|´‰â©r¨†k Œ¹æëévýõ%î_Å\=ý“>ëúg#R ˆØð 1àŽcA@¢/´ÓòÙ1U!ü9kÄÔûš³mL[¸fŸÍ˜,§œúcì~Ò ãP º7ü‰†Tˆ{™îlwœá)Íø¦Lÿ3ãÜk»oŸ ØÙÈ[õ³]¤íË™ŽU•Lw$ º†Øy_†¤)ñzSˆœäjtáö¯z>O4úÉWéRA*ˆcˆ’² ýeWé¾"{™.€­œ~ŠoñÍ­ 7¼‰ôò$‡50ÙbŸ­tˆ½„xiŠY'†€h‘¬ )`aºÂŽ½è¯Ýœãpc³å³™B×þX“~•°u‰ªChH”ƒ;éô §z,|_K žP3=1g*¤zÉôÍÊrŒg ßÅ(¦s÷vÀxê Ç«ØñjC¥M×É!Þ•¯f@~‘”‘/¬ðJbD\šGŠ´ kK— Ò -ÓÞdU'$wsãà uÂdÛž³­,„H"­D%D*·—¶b ,_¡g2±p¢×?Ý8òјúú{ß?KT‹P­&./.­zª„ª+áPÉiJè³!½ U†Òঈ…W­Ü>×LM›Ö›£ M[V‚¢%"ƒCÀ¦—À…T¥ ¡Óø"s¿™µÜØb Þé°ìÄ'A*˜:ÍÓ¶§šŽ7²f%^I"bÄ‹„€˜g½f²­×°Ìxí¸M§ê#¦ÚšÑê -‚Gw+8á_è¸åÊDZßEnšsŽHU&l8JØ!1ÂÔ7áÚaZræu²…†‘xÁ“ÌÕ % ©9â22‚<ùÓîHoEçnà»í[:½$|Îôxº¾`lvNx¼1,EmŒÙ@³¼s¶ë¹Ðþb|‰g¹UÄu×DLA²MFò]â‰hþÄ2¡LKØðP×I›â6DNë…ÙÛ!ÏɥЈY*tÇ°¯Ì$΂z¦A÷y m\ Ñy!/cÔjìá/ý*áÚj×â@_GªX&ztß2p!þB©ð b£}g=e™†)eY”xm`Þ>›}‹<ÄÎÊ‚BÑ¡«tÞ=- eRÏ6ñŒ¥(hÔg®ŒP‰à§K÷Rh8ò^£ÎË6þÆÚM ¶óÃÈ6€T -¥—•M ÓDj ,émœÙ Þ锧Øm¸Æ™E>oÁý¦% ‰ƒ„9Ρ"I;}ñ93 Ö#Þþý…¸Áûwh° -üH[D5œƒ-ŽêXãäÊ:àDñ’?¬-¥¨Ðv̹ö)Vh‰>Ë«0šp($ øƒD±ý—'ŠW©£ŒuÁ[±¢`CdÅ@ÕGÀ B„àA¾¿P»•oe»"Ú=±d8»ÍKl -• +’«ÆkxƒxjèÄoʬ4º€ƒˆ\¢ðê ‰ “ £]M_.4ðŠRFÃdÈŠ£*ਰ[cÈ¢Q†nñrX¼Õ G` ʈz[—(@À˜€âÁÌžEvÛ¢äFê–REŠ!ªQ›ÖÍ„”ß‘ûœqóˆ tŽ&!ÈVm ÄäqdgËÈ4çy$ОQR×®8 ï -ÔFÃŒ¨¾Á0Þ@‚«ðG8E‰’è¿dóÊÝåàR¾=_‰á†– Æ=2O¢þÃ~;€=‘bŒ´ð¢9Ò(ÀØ¢òümÜãrž‘‰&f4Nô(ñ0`H©w~Ní°d ÔQxÌ.ô#D)œaRÀ¢ý¸•¢ ŸÆCé+(ŠŽšq­Á ðÉp0D¸7%Élú…Õ›pëÑÈx³Ö Ë0êi~bHƒHÕ%#×Ä%,…@EžŸ8÷€×¢‚Fÿk”a“ü?ÖüG,„b6Ä)haÔ~ÀøvË"d[¯¡bxÍ QHüD¶…EŒ/aÇj¡ºH˜ò;™ýú¿0ƒŠ>®¯àÍ€xÉI ¯À! ¿€2wzf  4. * ±õÉz#?‚Jƒ©wCP„ ½Ã8ÒõA….ýâ›M°›ÿG|m_øRTºL|û _ÈFvëÓ‘ú€XÌýíïp¾)t7]Rm…ý¾[{¿ efwªVk%0õZÿý]œ •BªÂm%J¥T-Ÿvë|ýùîjpèÔU…¶øûZ¥WQ•ÉZòíÿRìÌó¡ÄÌýép`3\K÷v^½É|¥ Ì ˆ£5ÜÜ’IèÏOÜúDË$&´J [> Í‚÷!|ÝUý¿i£Çÿax«cÓÿ0Eãôú`í.~ÿ³œìßG‡ñuC€PæÁ3JŽÿö}ÑÂÌÿc†òÿ‡]ºýóÿ`iüÞk¼¾íÿòUö€­í¥hšT( ùèOµ)ÿËÈ"ÿë?ÍC‘˜7ö-z¥išÒ›Ô÷p2þ6Â9ø–ÿÙéÅ0ˆòƒÍÁ«a#zóû/C;ó?=+°ôC0±P$@ƒÅï×Õ?'ƒÍ·‘™± ÿóã¦զÓ[€•ú[Ž6w« Ø•Ff¶ûÎÿÊክÅvÕæ0%ä?®€ ûOáw¸é@Y:v±#Çq9`½F¨KÔ˜¼üz3L¶¿çÍáz1Û²Ë|7Ù*EóÎP¶ËóÒ ïŒ›WçÕp=Üœ Æ%äÌf«±ó›áúû¼Ù]o†«É“¨gæ€_òÆÝv³ÜntÞ…Ù\½;oñ:Æb¹]òíù»R©snþçèµ»ž÷0i€½ÅB -Mwµé-º«Áy1[¬4;›ÎÁî^ÀÐÇ«7Dé¨jwç4´r4 g[0‡–ew0þí®§2´^.Ø rIÊ‹;äÇÄˉWÖUw64·lõ<»Ý,xô ΣÇÓ"-j¶Yuçëeȼÿ/˜êdp¾æ»a1v¾ì.˜Ö“ßí¬+¬²°ýÙ‚éåÚÍ‚+úbIIö~ SÚüë¼>üÇp&C©"4âÉ–k6™Ï×$Ûk½»¾J7ÃÿÚì¶ ‹Zvç›É9 ¢»V^Z@4ï^„'>æj_4/²kØNkþ¾`ÿ¹'ÄmÆb2ß 7°7†«õrHRýðváÏæ±öai¶X¬:Ýùdý x í¥ÛeçEÔz³î|HÒs&ˆ¶ÖvóZ«á»îø~K\ÒA4¸O9"úÄ}`=0Ò‘‘>”fg³çQ¬„¢Ó -\2ˆüp6+þ×f¨‹¿íl¸bDtu>þWkØ_ÌÙ­JâiŒ@æ~(ØÒdµÖy…†úJ«½Æcä8úà»9<ø÷w¨ƒã Qv¡Dv*èõ{ý"7ÍVKÛٌ۠lî<•ò79ÑÝÝ‚”7ª,€§.摹PmŒäÐâù%Òj—_lET£ÜŽÐw¡áêªM‰ê¹ìöa½™¦xT­A#MÎߊƢ”FÃœ 'Â4ÔhY$£ú8™)‰ #áˆÖ&à‡ªÛRk£µšŠÕĪxk4%LÁ/hÏ'ûî&ÜÁ@Ñ¢MTHg2ü'l‹Âd½éÎû<>´¦Xêö‡Ùùx¦Û˜LRÖZ}­Ië¢ i‰6~­5 °Îz¤‡¼~—ª‘ŽÅTÁÍ*7¬¨âáJŸ“TzVž-zÝYs¸ÜÎÖ¼Š$F‚bF‰Lݶ‚§ÛtÅox=ÍoIIÔfb8¬þ1<ÇÛóâ`ê‘/lãp à»åxq¾;ÿGwÝë¤E~µXâ¡0sýÁ®¯Q^ÛIue‡ÛÎ`õv†*4`OÂC3;°\Ûň¤³t”ì…y7ìUB {¸Îñ²oìä>¨¶œ™TüŽÈ69— -Í0óbˆO˜QQÑ@@#|X4™A0d¼XOøMOsãò~…’™±ÜÈ ’µuì%·Ò‹ WK_í|2gJæ®'’g°ª8eJŒHÒJcó2-Œ m¬>q}wA‡† ʈC Ç Ñ…_.m)"…6”l åH÷èµ1ù¯á 4 -¼‰d§!í7ÔpGoù^ü³2Èd§Ù¼»Tàw»üƒÛi>ÁíûYô¼½Éæ·‹lT¾/™½,n¾ÿN½=df‹ÑÈËø¼YõCµù/ϵ¼9¥0içÛõFçZcDýÕÀ»X½ßRSO>G²„=Ö®…ŒÁp=Ï»»üI±ÇîšAžÖAByŇUš.FNWå´’ ·É/ðDï #»{ -ÿó¿G“ùä|ÙÙ=™-sÈ•y'‚«ÊjB/œúEg;”_u­Ö¨Œs«¥„ -슉ØP§¦£þ\îBÙ¡¦!)®5Po…=m&3±HPîiÍÝO£Ñ†Ð®6¬u9볊ƒ]g‘°KQ`ˆ°êN GÈÍD ¡¾e€­ÍºKÍ… ðAMç\rªT¢Gݤ§%PÒd>Z°sÏ/º »Ÿ—îÊÍl vþf¯¶îÎ):Œxè8í÷ßÚèéÿÂŽo%Œÿz…zóJ#[ Vkïh;ïk¬ÎxÅu7ÀBä¨QbDÿµô®Y…õŸ¨¯.ôù†¸KEÔ®½óá¸+ØPj„”±ä.v5Ä­ö¢KDÿ0,16Ü”w.4ÒZéÅ -=àÈ"KðcŠÐjY¬P›ÕaôýÙÊË«f=&C‹Uì;]²‹eJuˈö¨XofÞår€t<›‹ Ì`Ëcó rà³í €áC|·FXÈ´ ÞæflÀˆàáÊ8F˜ös±ú§®'Pê l‘hïLn4ßx³åj´뎊ÍÖÛÞÚ8Ô•ÀlWw -ª€êM˜¡ œ1 ÜÍL8ŒSa=h¨ 7ÓÜC¤h6sšòÖ…fëïî`¸j` GE!Ô.w˜ ´ÏJ\¢R«•\é¥TF†n‹9LT}dÐÌï…Ή­Ôr¬W£Gæ¨S6_v冫b— 4{Z,û kí±,–ƒ­ŽÈ•èeŠHg$3kîqˆ×a äî|Ϊ äN+] µ Æ Öÿý×T]¦CCäDÂáÔkóþ7·¸ç¿‹ çó¡Ž€œI™Öh¤^¡ŽÒ†~Ü^w¥¥±HÙÞÊ€·݃§¹CAa‘î—P,¨m`ˆÀë,1J:¸‘­­ôa ng-sÖ3·xfŸàÝáè®ÉrmÏ·ŸžOÓ¢œÍ8¼Ž¥ºžN– ’̧ÚÍVÀÅWëá‚”ï5_kóvX¸; ¯7#Ñ[jÃEe?­–¢úzÑ«‚!B47“zw½áÜÛÕÂÁÎÉ|:[o`üÁ,7€ê|zŽIhÆÙþ*ºþ–æ‡+»JcÅa5 -¥O_“ÓÞë\[‘Z³c?¬ºË¥–9ËõÚr¨ -†èõÝßÿù7À¯ÄÓ>؆s–… ‰'l`ÛÎÏG“þ÷d¸ß|>œ Zl½ÓBÉ{ÞF;}=üïó>Ó3°ÆÒLÿXláÁêþ÷?€ëþ÷ù`ìw0a÷ðy¾ØþcØݞφ<ÐuÈ‹­´³ìÿ>Ÿüb¨´—¿ºÏI³IŸ yCˆ0Bœî÷ùf‹yþ·µUçâWr#ˆR8Ð0¤yw²Æù-»0§.ô¶ÞLO€ƒQƒÕ¦Øxµ©ˆ[D€Ú_M–ÚŠ׸µéΰƒ4[I·¬cÎ(Í=h)Ê\ËlâÇaO¯õ?¬&¿x*·Ö,i*;ÒjÊ8M´X*×´9„Ÿg•äìJËž– ¡)Ç:ë‚Ð0Qp„%òÀ{— õÄ{ÓEv®ÁwÙ‹tÿu,Ðê8Ù×ú4-škcUÃ*â^!5Oôƒµì5‚ÐŽ8Ük’x¾8ß4ÇÖ­¦¹Ï½È6®ÎûÀȌ͓áÛ¬F™í¯½î¦Þý×p¥MÎFÂ0•ÞÓŠ³4Jý¤XtN)}ß(fùÛ†"šQõyfÇǾ2YÆ„=ûÖƒ(FWäS}­µíˆáMœ‰†§ç™ÄíXZ-~kýs±šò|¿×DŒl¿szGKªo -ìPÍW.—!:îkaß¾"D5^¸YÌýoÚ0/8Jj.Ü4¨è¿\XüsΤwVÑÉöÿÐf2°ÂûT­7ñ^ ÑPÙ¨lÖø …uÞ|.$úèµVD QÔ~M%šè”¾»ð`øMÑX5B¤ˆ#ÚJ:ʨׅ£ˆKm|÷Ô¢½²—÷!NÙ«éåX…ßÛߨÉ3myCÄb{=$öAtº§äè«iÀÅmŠÎŽ"ŸTG§"í÷•Ý"ããñ`¢ùʛpBYÙû©Å,äÄV04eUž·OùQ²dçÝåã7ÌãfñÇçz8F n­Ó pþ½ZÑêþcx³m&0š¬ÔÂ÷4þ«ÅdŸ 1ø; Ö*…»,<ĪûOÅŒ!YÔѲ;ßIBá;Á`R¾“¨_ós½èßkk}.ì{_nÂy ²­|µ †¨àÃ`úÎúêJ>^ÙÝ'÷uÀzçÉeVåßïøxnº.™Üv[~Òõ®Íáv¥¾ŒgÚåÔM0¯¿Ùn2«m?R*Ò7Q  ^úýëÂOaìö›3‰¯3“t/×™uöY2‰ºiÅ5ºÞäÆ•ûz&¶ò“«T¿àõÚÆ; êƒg€)”,ñÈKySøyÏ_<îì†Ú|»RáËm©4?æ~f¶Ç3Kaä¿î)vfŽÄF‘Îýë[ö!ïí¨·‹¿g’ÓÒ{&¾öþº -n˶d/Fg‚¬Òì Âèý1’›efOñQî{“ÿŽ¼Pt|Y }ªþ—I¦mL?0äuþcü±€OÖ¿BuP5å<Ñs¶å¹œ3cxê¶g–ØÝÕ/öC÷öüwð3‘ÌZVWîÖýåÊämíR~¸u¦:ח߉~¿;ÅOWqTÿf S~_7²š˜¿â“ëAnfIÛ<+×Û6[oYÿpüŽLâú;pf ':ï™ì¼oûu]Ý$|‘ß·«I$â[ÙU¿J¹¦qŠï±_¸^wmÛ0òð⓼¯ ëKÝ\Ù=îaniü23x®[2ùjòò±èŽ…Ö°.Õ×ðe*’_|¸’Ákœî]¾“nSs L(v^â’¼†Ã÷sÄS*7u„=,ivu?õ~ySðu“Ö’Éõ²B(a|ðAz!MÎ,þÞE5H>»R¥$û)ùX¬1ÍóîâÓýLWtŸü®Tªè¦ éñÛÏãU21ø¹ý +Éú»Ë…X(Ð(wÍà]e¿jb£aü2å -ŸÕ`¦ƒá—ðO?ûPøqF¾Ú_±Ûµ™sá^û>Ö°<µ³wù\£0jMþ2ïññ™%|~ødù¼?)W'|ÊÞ• -?ŸùÉOØ—ýZÆ¥üÈIS_‘Hs°àE[¿µì]ÝY+ƒƒÑ íÃêo–Þ{WºÓýc&” -G»™ÄÃæ"ûp½ÙîNM†Y¸…xZ™¸®Z°sîò›3Kñe`Ó_ÉtÁ_zË $¿’¥P‡ÓíÊ-bòµ’bV¼°ÜB0”“þ^o –`.b<]×ËÙÏkŠPLÒµL|•ì£š7ëO>¼ÐóG’ˆám36,Ù­KGþ;Üœ]uoI TØÏ ä0­â)4›ê× -S»täÇßÅu$Ño7³‘úQ¾ʬ#éû¢\ô¸{1¥%‰M‡µü™%ûp3p‡IÅ -¹úóTi´¤¥¨]ù92‚MS¤ýt9x³K9›FÉј%J…Ð íJ•¿Þ—¥â|ûE[b¿Q†²Üfßk4Ò¯_çî™rþçß[v ÛgKöþ¶ÕAWGƒ}ðø`±¼°ú#Óm¯á¯/½³òún¸2 -™á0r؇Bæ²”Y­¾ÛÁøÍcštfIøÓ‰/4®óþQ,_õ}·ÌUrÁmÎO¹Ûk“d ^?²±DÍÃ?xþy'YxZtgVו’Óá³zãÒ=˜ŸY@:5K¥O‡éçÈ${óÌã½Ëÿvµ& þb?„ÎEÜyÇTñ®WÎ/ez1SœŽß¿’ÎÄûW}÷Òᬕ•€*»=Šìþ >_¥ý+ì^Ñ@p÷‚%¾å'ëI¸0ò¼‚ž\üËËú;³p²ˆº½7äævoT•ÚŒ4‰SQÓmÉñÜØdkÏÔ€YÄ«Úí 0ø¹ùb¿(Œli!_¹d$›gaJy? #¢¯Bñá—uÈâ©ÒðåfÞ&ݶæ‚Ú¿mGß‹Ÿ–WúØ”ùË}ØÏ,¼Äúæ)¸nØlLĪ(sÆÅžïe"ÕaÇ~%®V^×Ç@R7Ë–Tr‡YJŽ$Åañã®÷ ºI"FŽbßåËí±{`€QF`Á.3ÉtÛZ¨þN^s¡Î“7{ïlŒ³ÍW©l)¿GrSj°+*œöÊW¨¾Ö¨n4I#àÎÉ–Òà£Vs«ôyi}.Œ_¹YÔì.\ÛvA¹aõˆ±e -»HŒÌ-ô)IÁ"Ð ˆÑæºèr†Gbñ†kµüÌ}d:æ͆‡\‡E›Â›z©™ =x=’6“¿úZN²:ŒH¾ùÍÏhŸØîo@¥sgK…çŠSij’v!Wâ}ñú¦Ô´>¶Qǹí'îdÃÇ”¸1ã·ãĤè^¼PÀCT¶*½—7N¶ûa8Ì ]®A’ÜÆí#~*çoó´Œ:Øã•éÔæ¿&,ræO¤²ù½€ÞÇ%9}RX/9å©…ÃW¥¿J²0«‡%˜]ÆKSaÔúZä'ævð.;OU -ù’»[ô—±›‘ÊÞÅ‹µL"·„])Ò¼Ù‘%확'¶EžœÉFÞ·…QìËW|n¬2|Æ~V¶ûÏÙÅ÷SF*¼ºTgVÿM< *]ÑIê*P"Ø4[|ÈFbÍ1ªŸ…ÑýºÆ0 -v¬•ËR>û>.å3ã晥b¾¢ªÙ˜ošUn”uþd:Õn)Y؈Fœøò"Ž3ÑåêSä:(Ò'Y+bÃÆ·ê\e3eÔQþµû%œj‹Ù§vŒ‚’C!MÈ0?JôƒÀùăU­Ï‹ýÐ|šIz¼dÙ²‘ÖM Ö๑]dG;ü¡ÞkQY’¼É¶ÜX—±s.#Íb{ñ™z¼¨† q‰9 ˆ£ù0œáNû½¤ó•ü÷ëàm¬‹Lò>ðQ¨Õ’üY½üµD©k-fïmP:#÷.?íþën 2w´Ì6g¡G î>eÖŽ§‹Bí:„™[*1T.h¢ñyøÜΡ]þª0…/’îíÛ$œúsÁrþá®,õYjä5 ^jr˜Õ8Ö¬QÑ°º®2‘‹ Ý€¼ç#­Á"Y^š?<"c0è?Ä鮳ÆC=”Ÿ^žæ°.›œàÍcÝ~ô¶¥¯÷í7»Øˆmä™»‚;Ëíßt™Õù<°ðm{vÞ¯ ocÖEÒãýKd (ÄEÒm”×- ð=«÷[éÓÇìC;ò à.6ÍüO†™ÿ>J\–ˆå¯GQKÿ:ëOÞ.E>Q‚¬œ-ùPpN|ý¤û¹–ËFÝUìÑ:‘©~Þ‹Óõ˜Åa5¼"¨S¹~ö®VÁÓêñ3b‚– éþ„Ãð5Ñ}\ü|ÅK©[LŠOßm`8žgÒ«’‰›…Ûw¯?,‡©—ÞTû.96‰E±v„r7Ç$2¯Þ»v}Ñ÷ ÷oJÀù Í/EÏstœ ><¢ÕŸ|¸ÇÙ‡ÂÚþ«uÓW`ÛÁ¼JRRxL?Ћó*Ñ‹O¯ -ï“çK¡a/è·„~ˆYRçW­‹À}þ;3ðª³ü£Läûéû·Ë’½–‹f/î…hö²È.7LÐ {ÅÀ%_Pu¨ ˆÃšó¼ÄˆO…DHI=.ñb÷ë-‹yÚÔ(}ÙßiÜ o…kÓÈ/Û÷çG¢zWi†ãOíÌeXbUD 5–[\ûǨæ9À›ØSŽ_¾Œì -Å>]ÅW öË {èa]ì§*ÐYÛº4ÇÚÅWslü¸‚qUÐ+ïýÈ>|÷78Âÿ•âs¥A‡r±½‡÷FS‘Íjx¾\Á§ðÿ‹‚„´ºŸEoÁ¼,¿±Kܯ¸r×Nªc‚¯(-À‡!¦La>¼^‹{vFBOÅ¿(FºR¡¯ˆ+Ýéùü>×Ç•þÞðLÞÇüƒ{þy¤69Ð3ÊÓÊeóª‹BŠJ»®šáo“cµ -šÜê™Åä‰z¢&WßÛ29¾FQüµhrB&ÇdZ7Ùͯv“ûæ©lrÊ¿Þ@=fr¥Ê&kp0ß“!Ów_)åë:Ž2Jåp•Û]7õÇB­jj À>/L½¾u¨’ØŽ;ÁÂåÐ]-®ÞVÑl%¼h%ÒT'Z¸l‹…´³J¼\çêá€}iÏ¿W·åh%9 f|g!÷uó˜}µÈ@Î,!SÞqÇL“àÄï0G1™ë:úH©­c–ìThòº¶™K% 0—í—×lï™›oJsu%éºÚL×——Û2Ðàó‹¿” ß °ú²¹^”~¢æy ÑPê/úU V:ø\T -s°¹¼zVFpé¯Mçzx1¸£÷L?hÃf“­i`Õñ} gÊ™í¥«úºz/tÔ±»ªÁ÷àÆÛö(]V®•€‚ €`÷þÄ%«´¿úˆROÊ@o‹ÎË¿ðöF èúr™+ ¸+wæZIÓsêEhÐñìJÌÚ·J@®ÎÌ£³+ -P˜US*_š[UBèó»¿ô^i*Îô¢´NX¦¾òd…¹úËîí­*PÛpœ¹Sš4ùVëeÃŒ@PäüÁ÷%g:’1!‚#K9Ðz$÷Ì}öØe@íú´Ã-¾MK’™¾düõ×b HÁ²s­ü­#ÓËû°"Ðçj¢ -43ýÌge@ -öÕáoŇke 5Ï[m0to¶>Ó U ·o•ëáüJs}Íû;¶§¨2Ðúå¶5î ¢Š@;uj!J °`Ûe_y¦´æï, iGçãñ+«ô1Ýs®T›ëçgvó¨ô-è¿{p(½½ÿ<’N(H1ìûÆ{¯ -ô·á²>©-ù?ïÿ’Ê@ï2à–¯Ùu^q®ÅOXh´bz¼õ× Ý‹MYºiÒ«íãkºXFÌ•î.Žíï'½ZÉ~9›fè4æ” }[ý7¬¤¡m){E -Ô sY¯/¬g—AÔ½¦·Ëp €fÖ;2õmc€¦íE· ½¦léÎÆ}ßįQŠ‰YaÛ•¨\]#Pß.+lSžHÝú@K[9ÐÕ$í`Æï½Ò™‚™ÙÎY¬I6n×ë’¹^¾®C½W"iü»L?f½xÚÜW(½ÃWÙÞ¼í²’äéŽD^­rQÛs»þv­øö¶kJú«o®ÊS[Šê®—²§·¼(•Ü&KÎ\Àç -l­7GJf -ŸºvŸ~/Ãs[8 òt¾:³DîÞ+!•ç›M”J½D”ŸV­¦ÌÝcñ^åé&Q«^_®ÉS¦æ¯»\¡­òÛµðçm&µµ©<­ñHÇ#{ÊË—ÛÊaO¿¾ãs÷.§ò›ùµÜ¥øzØÚî¾S*O“ uòÉÇŒÊóœóѼšäTžVÜŸùðÛòÓ›léçÊp§ -»m~ÿ¬?‹.å·oŸ~~k­òô÷oᙣ²§<=¿78íYáíwç+OÝ»O?ÿ:7TxÚ}æêÂ))?>Ý–6wCå§#ÿû·í§z¡øÔòx?èØM·ieŒ­VWŸ÷̽ݎϽ»Oé\õî>‡F#½Ã„VÙÏßµéÝVž¦–Žä’…ÂX`ÈfR±Šù‡p Æ>Ë{œMàJõˆšMÎZÝÎZ‚2¹ ÍG4²[hd?˜ì¯Ž-~j€%Œ¬ÍÚgHd¿¥®SO+Cà }7ѹ LÁ§-rt;°ÇQ’zé›\õ £]×цÏ/[ýÕm»jxå-ÃM]lÄô$b휩ÀpÅ@ƒŽ7U g`aÒÅ%–Ž -PPmÁÎùTúü!%ú˜d®eSH(Ú9<Ѐh´‚Úÿ´<€¢•”4½‹çl™Å¾Ï4E@V륔hÿ*@Cߨý/}L çú¬ô¢4ö«%Ú¿Pb%…/Qÿï)Ív5€–ýqU D£écÙª¢NÑVš4¹µfZµÊÖ”rƒ¶@ìë%ùÌ.Äív Ôr§ÝÝåÐH»‹»+“ŒÃ(·\m?§_9K¼Bâ ïºí"· |Âc2Ðáª1nù_¾k–oÜ쟴¿*ëˆïûà:ènŠwSà - ÎöšgÇÐmæ`˜ñÅ™%µ´ä¬ ó} üsÉpŠp{FóZ œ¹Ÿ6 H|OÀôR©¢çÿ1€3 -ó=ë3š7¬\vºÂý×!‹…üAb˜ -r_Ì àJõE˜/·ðÕŠœoëÔÒZk%É…Ç›nò‡Á§¢;”Az}«rÀþ×ÛÕ,æÅóKSµºÞüÈŸîSAiýع ¤;7ª+Èþqß,Øù›Fq~ §lýxûEww֯ܚ@A•ÔFV쬳8šØa¿ ²ü#ÛßÓI(kAuͶŠ ïŒÛó¾½vΙE Y½õÁÈ’°\—g÷\Î|Šoù¥?ÿ=YÏG‘*¾¯ËŒ·' ?!°\³²J™îŠowÍÑÎîÆÔÑ6õ*vœîஈVpM…m+îJ‡ê®ü(̉ZýÈ©½]*M  HPmeŽ–ýÃîg[wV+™•@삽C~v/¥‚â° ¼:GJV¢tÿ°áµ2ÒWi™Rå·?} -¡…‘/;ˆÅí/jRúž ùÃS²KÇ£¼÷•åc‡t&ù=z!ßuÝ‹:3g~]ÝwÝßDe%ÝEr^ ÿq«ÅRìrÁ^àB¶ ŠË‰XHÜÐmˆWfxC©væû^¸®˜!‰ÇÿSýGç^bÄ=à4XÑswή …i-É4º”- ¼Û^ dÈp~Z‰ù賞2"¦¤"–Äã™EYg­ï Løª†B ÇâÎêòRlÝj &£Ú ¾g’‰%ÎÓË# gèÓ#¹iìBuHÂh„!!%«êûRMVf„=k`ýpWj«tŸ[›†Î/Y?U…Ž×ÆuW_¿/¬ Ï-:غÅc(Kè -Oµ,½:S¥øC0¦¤×Œ1)¯=cO; 3Ö¯Å{zöH × -n”¢DŠªoR:|ãL¬Ò*ïƒqEo!õZYS¨ìZâïÊq…~ßæjš4&7”™óXì¤-†Õ}~4âè2ãfŸ -v`¹¿:sìO.ÅDÌçÊ€çQd#«yl;è±ÑÃŽˆH< 8S±>†AÚ~©2]#Êô”UiÜ/ñûËc‰ª¦u"Õnsd£‚pY“ -BŠØƒ -û%~ô~©É$ XS2Ê•° “TøéÐùÙnÈ8Rzºm œ`­8‘§ãëÓdËš\Ú©¡…•ûªˆÑsÓeÏ,ˆˆìÓev—4zfõÔb¼=Çï;„U9ŸÔâÓâ}è|÷(í¤§Õlg>#ä¥æo•vå?ž'矙­_]¹GºŸªk*ÉÜâåÖ\®oâo2–¡`‰õ{agÅ®]IF«qè£xÆ'G¥h]v[•Q)V?µ8šSàh"(í“q´ÎBÆÑÔ<$Úm=ÕaBÆ=ðØ™ú¡Ï^ ¯ ^Oéc¥o „ÖÅñ{¿} G“­þñ­­ÄÑ}}úýœàì•ô#åh’£ÿpÓö±T¤¬ H,á?S÷¨JŽ•µ–ØØÀüM9eA]$ìAsuèìSGÛŒ>³QYqA äª2YéÞÇÎÓê•Æ%Ä>(œ½d3#k4Ø̸.jÛ¹øVpÏ ŒÆíG¡ìíÑïÇ&¨Ö ã!!ýø÷sµªnCèJîõÓÓ’†nýþQI -Ür/iøö·—gCÙâCKG€õfaWc4f@¿‡ÎÔ·Ø«ChÝ‹¡ùx)öxýþQI ŵõ{ÃRìñý^Ò + E<ù`iؽ¸¡µeá™e/iˆAán™4”ž(Mų̈4|Ò’†âÐ)! C˜½ü$Gö¡jJP)lHš -´žl|wÇí:òìlÝ·‡ô¤(al¾—^£j\> »ÜP”š -cþ\i¹®ÅaÀÓK¥¾I l®¥Ôóåð*¦ç£4þ$LJ Ád rM<ûgY$½Ì;ª·½ä¤’{šªÛFûEÛbgD‹úÙˆócg›Óñ³A²ÚÙ€˜’{kCÁ·ü™¸Š&œ÷ú4‚oÅáÓ±ÄaPŽañª‚ºÀ„„+Zâ|9{Ó1¹¿Z˜WÇ‹j>µsè´3èxíâÈ:í :„rŠ:í : ±“äÐigÐíd ˜C§Awf9MÝPIã·<>‡N;ƒŽ¹Såø:% BB¶àA9tÚtìêC§ÝŽDªœ ‡N;ƒåþ)rè4ãJ.(>=C²a]° ÅZ$HïÌf´£Ï. i¥g‰#=Ù ¤î¿Jd¥š'¿aÕ‰ 5îé<5äÑäáɦg&Šº1‚'¹ ß3s*¥‚íœñ¡7Óu =I»rËì…~ƒóÓÉ›ctcÉn^íùiIœù(?Ì1ˆt…!É`ûì¯Ù#eN§dŒ`ŸÝ«ý½~r‹¯¨ ²G(ÈGQpkzz ¸Ÿ=FR‰ÎôrÃdáû‡‚`ª›<òÀd7ƒ#žÞâþ®*Ù2‘¼WÌP3 ¢Õ²÷"Lg’l#6$JMšÏ6kÙ%U"Jz6¹ažÜ½xÒr0šçURL9³w’)׸[2ÀÈXâlû’æY¶ŽãìL¬ëqËèÚu“ ÊÇ$IO¬¦Ñµº›S!L+ ìU”B|lŸöÅk5”ǧ«{Ý,§•ÒùRÆŒ“k•Aé_G!’V <¿x†šbf=5`<¾¹öm†×@ÙÉŠ9˜TnAËý=:ÓI‘‘wE´>ÕÎt¢ê÷ØNÎÌ>“T 8c:W!èaLz"Yí„:mRêLHUmT:KWt“v¹Í®¼5w²å”b®ôºÐe8óG7óq“ÖÙÝF=âØÕ¹ëFÐU1:÷|a5І± [-*aå4sFtWH~2¢fñ¨GDT ì}=|>}ÊÏÅA‡QñÆÝ£¢¹T ìsu•”×"Y»’~_÷¼R=²*×#uÌLõLŸ¹É0%•w&ã;j*äOõ˜ôd ‡AIj5²ãß7v«ž>fO6£{–Ù•êxÚÓ½£ê¹ÒÎSCRóÈ‹o/ºüì?$µ »½<2ºIq†=Wác;q§Î‡#þ±Œ¸½ -É6T·;è’½óᘽ¯:w¢|8~WJ2âx(ºùpÆN}™|1^d,_¬¤>g6 -Ú &‘ß¹dØXL¤HS’v&’8,sŒ×ßµO¬´s•: £×je£3ýìq­…j/Ef>8+M%Þh&ösø]¤2Ë‚lu‡˜‘ˆgi¼³‹]}é6ìœfâ2ˆ<«ÒÛçöˬ2t0Ù„,õmX|ëØ9:×Ê9VÏÚAÓ¶aGÕŒÞCG)~’Û€I?ÇoCèEº º»ƒég[ZíÌ‚ô³¯j¯Úré3z·M)û¥É8?—̇…¿éæ$ ï¤Sd¤¾ý0#:;UFêÛßI2R© #I7º©Ý‹÷ñjôâ=EF*ô£cCËH…~Ž2Ÿ8niüh=—Ù ²+S¥4¦±$߆q»{gÆíº·š܆û¥Â霋(ŽóŽòÉpÚ†û©p*÷ÀŸ8ÎÆN -ÇØ•OMøCSᔽ®©?$îÌ¢©” -·«ªåVç½'»4bœ‹8Dâȼ:•Ñé›Á3L9Û·ð„ªo;3p‘¡±ì5¼³K|Éð¡w‘6ÕémZ·ób–Ÿ†N[8ìÞ¦EòêŒQ„–p`=@÷¤f©™¢`éH*†GF¯£Há¥Ôie|›\½¸ú¸ú¼z(L©|ÎwýX¸^· -iWëájñå -çrÚÙò¥§·Ò€¶¥. -l¬ãîù“; -Én·iq.–,Ùí²óÜ»¯$ `@Éñüë³Z²Û“jÞÙjû§TúK~çDîË’Ý"MÇ»Z²›f†Ý2  -¤Xùò¥­2×ð¥­ÚóŽÕ’Ýåâ!Z"+¥)`k[Ä*”z•'»a‰Ë™J²›C3Ãî¢Ô—C”'»Õ’  áKKð¶ð¥–a÷©™aW6‡Õ–ï/_T€F+–»ÉMW=î©àÛZGh±Ø.I×ÔÌ\WÀ}âVµu^ù ´\my¿vþ/g<+kGòÅvZºâ‹MQ0ëç <Ö†?ÂQ§yÍ^ý[Y˜¼3rHOˆ -JáMÒ;U çÑ\¸ªYä5Ѭc¥}™¸á!åW{Å\ª$çR‰¹:m%9%ïÒn¥<ݲ#ñ$¾ôÌ@Ì•F]4¿Î-âò¸¾“‘S튱+ ‘3$¤&c™ÂzxÒÓd·œi•¼Ó«>«1$‡ÁA uL%–)µˆNž1÷M*⌸ÁtÊÜ‹†:¶Ì$ÿE­ÐÝ^ñˆl:žÄ_|Xµ¬}ËÜ)îy¡»ƒœ1â2w‡Ç'ïSæNã–¡ÐÝE¦ÌÝ~•&-sÇS²±(ÄCÊܽ.n».!"_–µåY¤ã÷ŠÉ¯»¹¢úJ7L-n?:—gY3‹¡“ôhÔk]Û+©H#‹óø|’_(“•"ÆHòë™âê»Ø9.ŸHâµF¹c(ãoÇ -:6Þì=E‚Ø颡Z§Œ†jí_ÖU)ó‘l½ã3’ØÏŒôs`N­4û û96šéE-ˆj¿:‰Ž M*ÖItœ0±Ö3ûEœX;°^i'!Nû‚ù‘üJvF¬1$’]j‘{ß¡Ó*E‡’¬ -ÁEuÊb¨A_sÈc¨á7"Mª®?…ì§#kTñv -†Å +vÇ\ Ê|<ºN-)¼æTíeCü=ÅGV“~N’Ï™Ðç7Ø4±B¯(rKÕmDÃþÞÿ„u/çN*ƒíOg! &2t˜8Øc·¡^…»³}¶áÁîvëVKkÜ*:¥îŽÉ|4^áN§RÞ ¶V¸;Q¥¼“ä%[áNÂajÜíÓ™z…;Y­´“$ÖRrÄÇö¾üXýÞQììÓ@.¬5‡d¦ôŽr0ØÖŒq2žùx`b­4Šôs|b-ô¢`Z+ÔI4ÐϾ—T)qKìGý^¸ý’˜ub÷Ïo÷캒Iª”©¨×glÆí:)+JRL5)çsu`“âý0O'»xºB³]c†ƒ±°3-³]×o)ÅØIL -Cq»×€ ¯“Äd»êø4äþž9®0$!;åÑÌqÍ{=;ŠaÞ«[óCѭ檪áó1åw$2{úV½©aßryo@O3žãš÷IK=3–ãš÷¾›Oc²øCH¢§øGñMýW,’g¬Ü£è¦VÍAWî‘å–ÂþõûJŸ%x\¢Ù0¿Z¥) “¤÷Ž<^JÏ…—â*“­„®ó9o?ŸÏùjx“JkÉ ËLŽ1Ö»$«ÃÖ^Î^•óá’¦W2w÷ñ†ô$Q’çJôïDixò:l–¤i¡–„§˜ùÇVþZ_”>1ýÊrÿr÷*@×’,19Ð/T䇑ºóÄD@åuØâ®5Tžš|ú®'d©i¸. ‚/쪩i«í¥ž„çJ&~Õ+ΑÚz‚çJž†÷ªUüí—Vê/-d@ cÁZëÃïСVÞý“:ÐâíkYtf!G°¤x¡èý)ÁÖdÁ“O,ÇV_¹eÂPÁ·ºj;6“‹ÏÄl0-Y1Y+(ÜÆ-mgrÑ©æ%füÉ%y§8É1 —ü~9’jc<~LK6¿ôBaä6³FAçoXÿNË Câ•NŠÓQkºGTfy- ­Ruçò_öWš:ž”«ÌJÓ«b<+M/AA…ž”ºBù²Oœ–Æü4¢´”¢µR÷Šúㇴ›Å©§e8ëŽ÷»_4¢´2Ìh”*MHÙ•w±Ã®žÝËý}ÌJÖëGñèãKÙ­qÊ÷À<Å+=·>Ó7€?Š'8åɽëû(jûµ úÇ`Í9²f£nˆoù›*‚(vÔXàAW6*S²Vœ–á’ÜœRÝ£$R’5Û½¨k_½kŒÃôèí©ld£Îà3îà’æ•ÀF’:¼Ý‹õË®ŒÜ<$õ©0÷B ž(²ÄËúee úÞe™hÜ÷ŽQãitc4‘ŒóÁj¤~hßs°WŽ•žú&ñƪix¡4$ÃÚ¸8#JÆïõUã”e)¿?"•3f9Ó)ijG*gÇ£oYïL7çg·+å›Ád.Ôc'é?%ÆÔÏÀ˜zbÐÓ-òc|\A Œí¤ «k‡z9€ì‰ÕA‘ñŠzª»ÒP Ñ@UmÜP Ñ@CþdÕ,@£9k`#‘h4ðì¨,@£+ÄDCšh4Ptþr@ Ñ@‘ïâ€,@É„4r•ldãY€Í„ÍάK⸾_Q>R‘mnÚÃô>¤(Ÿ¢îäEùv½pÿŽ¢|"ÝòßX”ODÉÿÆ¢|\>ò¿·(Ÿæ]j'+ʧ*ÅNZ”O_S2Š§zðMMwn»J¿®Ÿ¶•«u7Ô>uý´«úæn¨ßë“Ý ¥Y×Ïp´íQuý„¬;¥hŠ=ï†R­ë§=!¹öк~ÚUý̯ܩ맳¢9¼W]?Õãpž[ž¢®Ÿ -îت~GgrQF.—bî :¾®Ÿv<“Àù«ëÇMM¹ªß^~Kº~Úô)­ö{x]?É„vªú¡ªX×O{ã*çWž¨ø_ÕOŸ’ ¦iVõÓ¨_¹W]¿ã³ÒŒÔõÓö/pÙµÇÖõ“ŽK~yH´R]¿ýiìº~*½t:«¿_]?í^ ÔãÛs*õBhìuý¸!)gáʹå¡uý\šUý„;ˆŽ«ë§T ZÉhϺ~š¨$ÚÅ)êúñ׊Uý”Sä= µªúí••¦Q×OûôB®[Z×Oe\lU¿£ö~Û¨þ®_ï𼡗]/Üaýh_)°O=¾Ã‹s Ù»uýÏ{Wõ“ßÑ}h]?íª~ -öËAuý´­2í¬4ãuý4Õù<‰ˆ8A]?n\ÊUýöÒÆ5êúí¡Q×o§É&<¢ß7{hÖã;|Š{M(UاŸ¼3­ª~{Þ¨£Z×O/óñ4uýŒg¥¬|ñNU?…Ìúƒêúi¡·6íU×O»ªß‰êñ¼½áغ~Ú½ˆêñ_}·ªë9f-¾£ëúIN• Ü.߆ÆêúiûµÔo¡Ù¯®ŸæÙ\žœ$ž ®?IEß©øž+ã9O»uý´Íväɧ¨ëgc'¨ëÇå…*ðÊv¥ -ãÒ¨ë·ôà!uý´«ú)Ý6H]?µd6JØ•E׉QÙ[k§?2'ïÇ×õÓ>±Q\—êú© bõ³kÕõÓNˆ=ÓqOJ×R½®ŸvB¬,‹óàº~JC’çŒZ ƒõ wG8Ôƒ¢ïæð2’AïS[À›úd®]Õ3L6FQ½W -­’¦-Ê]ZR–U²íí¿’ua3_7Œ¤ p)PQ±»[ €a&û«ckòøl7Þ@=fæšÔ׫½ÎšœãØ/>%n <Þ„²¡°ký]õ-¶][e9Ó¯æ— Suã0eKMßÅóG8nn=^,Óy«n~/=áVýï32ÔÇ™é×íwmØŠÅnß*µ¬Zß÷“Y»ì»ÙžY:Ÿe‡ýó3ïrü<‡~î~®ÄhézÉlV––ݶZ̦‹Åpá³øÍß ÇK½ßqÅ\7öÔŸù·îXæ…Õ*o˜œïå»›¹ý`Æ_ò§SþÒÃcÉ_¾\ÜbmÁ»ÛïÕj’ö¬¶ß)ÛÚæ¾íáÔMl¦eê¯èºŠÞ¾â’˜HÚ›¿Ø‰|®ÖãëK¿ïn¨ÈŽØu!Ù¥©õæÌRx)e륫ìUŸ$˜:‚¶Ü_SY3*Øún›ÿV™yøÖôx[s©Ïtûå5ŸYœVËSÃMÌr–F«|mûjU¯‚1ëMØÅ'‡Â2½=‘ºõÈÂUZ_”ªÓjòîÃÎ&ˆ–»•t7åÅÛ‡ä½J|«¨N°þÖœ05Aúˆ1‘ŽØâÖ,>æþ:ßÆYpÅ‚=_®HW2ðÛÍufÔ~¸ÍVÂ_°.®Xè*]Š™›ƒüûõe…Ì•Î=»Š M“Zx©ò£OX쮂۲-Ù+Õ*Uüˆ'ó“®ÂÅ™ûÓ¿˜ß÷4õП•ÛïëþyP›1"ÂC,¾K¯+uµ˜Òí /9®áxû¥C~_(è _c™|»ÈWØÌÍ?øšñ0_ß×C7ù0'“_¥OÇkÍ?úÈŒÏ,™DÝ´‚q×ì®bç©’­=>¾ûv{œh*îÌþ.êkò@<ƒTÆ-<±UOqJ^уSàóÌB Rîe~ÌãDˆ)çþÐy{ëÿÖ¶÷¸Æ·nñƒqpÀ?ð’äHªbÎø™Ó3Uñ”øˆ"¸£*é&~½÷Ý»ð¢ç{7Ó¤oŽÄY¢öGè…ê{®(lâÃì§_ªŸ®Fñ+…<™¼sß&ÝbZjòqë¤df ´ÃÄë?<PÚ‘Î$Jï»mæfÞ±3Ûèê…ZÕÔì}¿p+`^ê:’–ähG¥”4Ú£Ròóèñ¾~Å÷˜ mæaùý%:ζ¶æIñeÐðã¬(žT¾ÀêþÈ° ÿü(éÄÓCP˜8hJ…t¤Ï-Ù£‡ .Üç}øÕÇœÂ'è»÷îÀO+ÞCDv-ðS€ÿd:(&~‘y¼0ý”Ï>Yø÷ö‹Ÿÿ$¢6ú‹þ˜p£y ˆ¦fvWNBÜ\^ØÌ\¿Ã…]IÏZ–¢;æøÈ$ü³˜‘€¨c!3!áës`åu*ôŽ;Dê;üy~´ã'BcìgZÜòfÄß‚ÂxøÑÜ—¾:Ïù‚+ê(•Š7(+ª7¿!ÿ°^Âïýñ=$ŒJ J6¾ZZD|“gkÆùf(˜„OÉëŒo½LŸYû,l»;E¾É3fæ<š]°ë_ÆÉæ`¶u÷âš°:פó‡„íEÛ‰¦ô¶Æç>ž8ßØPðe'<™…¯'Ã2»­ éÂ?váe’ uTÅ^€tVÄ"IÎ3jÇ_}JÀRßä«(·í‰½æ1!wlƒ®òšKü[Ýå!s©1TM¹ÇW¾Âurá—ñUÔðf¹ÙżDyÌÕož:m0,¬\¿ðÒÏ«Ëï+g\¸j7¤˜4‘è…+—ˆxp°Ò¢\÷ùC½2[?Ë<Ãu¡ošù­÷añÐÿ¸Úq] VâÜ«¶ô×ímÝ÷=r%Ùïååþö1/K ü"iX¡f‚ê[÷‘¢Êùª²'[t ñûN•?ž2üêÇ–þ²ü`LáÒµÙ±r+¾{Ks:¢(w¾[l¹ü×eW¾þÖµ ëñØ•}óºL¿o,WŠÄÙ"*›y-¡È#¤¯Áºö"»Ùãã4ÚUP^IyD„rM#QÊo⇽¸³ÙůE¤[=;ìEÕפha–×ew!”¸„¸ \Ò zËNßä²ò#‚ÞÏ+ƒ'÷ñ³º±+viýt¥TBƒ„ìfýÃMÕµ3+êöUý#C<Š2ôK_”‘÷â^¯aì¨ú‹‡È¥×IJò4²Dé5¤äý^4´$+‹QnËqwƒèPcð0Ý`µl4³Rt¡Žì%i¸óJ”5'‘4E´s(Úaûäqã‚Û-·“ ÁqbPßRN‘ÂN\cæâG´°æ}aÒÛÿ˜~‹nc¤¹µ­‰¦I~3mìk¾‹ÆNAgÙ¬dY÷ÕÕÂ)fÖÄHz˜ƒ3 ús*´=öö"¹*ÊdÉ™ \}ëN9Óé€*=•)ÁŒx ·@ËèfŽ§Õ©6'…o⎧\x ÷[çûíyå›ÜÍ]‚ç#îÜÖ¯;ç ãMä[Ãk¶ˆ[íÌ‚ðj²Ø%ëv›µoÅ~&r9ã躢o–3óèìGs£“q«ÙÓ¯3‘ Mn]‘»){A½¥è…p«z.p4ÏÒ;¦ÆžL _‘¹Fl•{~%x"@í=sóÜm,Ëo«|QK b$Ðã{ηèx£@vÇÛ4ºäP 7ƒ1÷½}qHú$àx¦¢L¼ÜÓ³GéøtΡÀi“ ÁÃ#ÊÞ¥F–DŒ\m8ÚßšœYLÁõ«•G‚¤ã KØ9GGAåÝ{ñÓæPàõ(ÑÇdž¤„ƒÇ8¥BJÊ]°G -äòuZ$qJzÔÈFŽ>¢[â©÷1Ó W…jm)Ø•:ÓhÇ}®³+Á:w¢Üç¨zšÕÖâRì@Š²új£hS½it“·B×w°ÌûIcª£ÐâOs•"]¬/Ê•œ‹¿õÏùº -ü‰Ú•scë‹Ò%´´o _âRÝOÛ‘46üÚ£1rú¦ÕÁ˜f Gc¶oû±4æŸ; t ¦±.ê.·.™ŠÇ°3ˆW ±CVƒ¸¹Œ.'£]ìtQ®_8u1¡5†rÛ.â|)ftå¯2*Õ–óÌ"_ò8¨»ãµÇ°ŒË ›Å˜áiTÌßþT)άÇC˜ãöF%\§”Æpf1¼7*é&­3†UÊGñT‚o;)–ÈèëÔ'*­1ôÆ.—ê8‰¬6 -¶‹%µQÉÆ@<$Ï1ÜOüڨĽ¯)‰úá¼á-®<†t5-¡±~å.#þzßΊ¿¾¼æD -¦Ãì‹¿z‚ ñ×p<)ÆÖ]‰ŸWJ)ñ×ûzZüõ¥™í?eÅ_g9ÖK¢ìDÓ%‡,¶Ýr=ÿ~ Z&s„Êxx·ÿý¾¬YcHtôÍZ†¤«Tû¡õA‚!PìÞcœ’êÑ·øÅ?‰4ò1\<]%§At=Î~b°¸@/.p#.Í┺,Å6da‚ÞxìÑù“žšÙÙ“Š»áwàÐÂÏßÅ¿Yýzˆ ÒáÍBP'‚Âu”3ÿú"œr¹¤.DiT -ãŠâÐòu¥Âg².­%zܼ™MDÑ®¬Ø‹Oìýù ä|×”»cºAHÐ?lä³Ì]Ì">M=dÎ(„îDñÔÿQ|ž÷µ(Ó¶«§õÎg-‹Ê¹Ã?²—ìjÒÁѸї@ûköׂ‹‰­cÂÉVªL•0F±x¹"giT¯>ÈÑ…¦'ˆîgéÔšLgÜÒ9 b˜ã6îtꉡÉk%cïËÀ2@GõúJê¢1¢‡š¶¥eÔàülÖÄú¶V@%—"Õo†CèS*Vê¾ïÅU–vl0«M7Cé{&jÎo÷¾ìzi<×¾I(ŸEŸ~”þüõûÍÜ^<¦¯¶Õ¯Ìñ d%wtøn,aÊc 7H@0©w ›LWÈ-ÕÄy bvKš»¤1ðâØ&÷ßòÙäþþ¹1¹ÜŸ1“ûæ©irÝ_MÙe?%LîÏþ?åL`qgáë `rÓ¥[lRÁJø´„½TMîÂ=pŒ–º5y¢ž;“'KCß/ö$| -ÔJ{,›\³ <ØÆÓø[ðûÂ&Wß{ÍÀsw>ŠØÕƒ´I -{(ñ %qÜè,–‘„ha -Á¢ÄHÜ…˜ájEvšT8ËfÊy·pàÉžo?½rQEÀZH¶àÛŸÂY“h­¤SÃ+ÉoÙ@н|ìŽïþ¹%¾¼k“$þTÝfÖªce}Ju[ÚECÉ a/àÙ«z·´GéÄJp+®Mﶂ(Ð``u;Ù€©r•uü"w&1W¬˜È$&ç‹—r‡Ë8âelÙ–þ/FvÉ¥\½ Í™PßôÂKâ€ëün­ù˜0tv6ÆÉœñ3Ÿ@Í¡ØמK.ßMkEƒÀLÓn“À€·ymb×ßÕóŒ0=ñ;3åNõÅ7ZÝË)9À†Ý)]nÍw–› l&ŸØhqµÒñ{ïîÙ+—¤Býb>¥ãDfMñS Ý®×ð\L¯x -¬@2)„¥Âñ.|?4§˜`¥ÜK Âf@rÑ@4rÛM_±„!>Ü­‚è|¯ƒŒ*È>Æ®9ë¨ÉC É)wûµè¯åðî²I¤]†ä#ÿÌŸòrfC^éÈîź{4ì¤íãß| [¬Åü£ö:Kõ-fî–ü]…¨ŸÒ¬çMû -„c密ÁS zš.¤×ähø‰Ñ H (ˆUž·x‚Ãá›ô> -¤ -UFÁq±·7‘%Þ?¯EüM€ÅÈ7v—Ù¸ (ñhJ0`,¾±ŸMEaÎ:w´¾GmŒ˜úkl•¿0gäà¿øö!›™þr(šÆ`Y‹‚:%Q¦ì˜P¿Ã)S íð:kxžåRT¦Xù"è¿À]H¦%ƶgÔaªºÞïC›U‡ù¥•†k0*py9(QåOs˜[v çGåU <9Çí4;PrŸ‰l3L¶ôxǸ¨ã·?µd ·'ÿ%kWŠCý?~Ôãû­8>PEÁ©×¢öêIÿõs> t0õ #Dùò4íÀrG-ôçK&3 ¢$A¿†ð²â8êÉf< “3chlß²óÛØŸqáC¬Ó”ôþV§§(,<|‘½?m¢­2 !Z³Â“ó5ÐÕ‹·¾ï‘%Š†9’W=|ìýŽýRîxf0}aK;°¦@/Ó¶ŸÈÿÀÞ|&GuLþ¾MºÈNÉ"×ìÒEQÒ‘„Í„Bªl1ŒF“Í8ÉëcØ Þn*a€26ƒ#cb–…hjFЉmÍÁ€j—€Em|vmnW¾üØ*)~ðbné€ÿºLev›!·)`M¼ª3ÂZˆÅÌ¥_5Ïú³Ýe»+p£È\FñuA¹Ä0ø+V ˜‹3PÓ°Œéå˜ í4Åʧf.|L/Ë\ü¼P8¹¬IU‰¹{v -æ²IýiË!&nühÕV¢¿÷ UI¤)‡tÁóKŠ€:‘ÊÐöF8†}¢Öç°lÖÎoCТRþ V¿XÖTÊ Y„)0 -Yñ Œž±CI4೓ïú0’adH/C⸠aðdáðרv‘,-I±u <°çã»uìdª•ï}«@˜OçáFpß1™¦âÎþhA “jÚþ/ëçWðØÅãÙúh À¯;5÷(±=æ1sÓÈùÕ‚\‰B§†à‡PܤÍXÈ‚á­Ða98ž,åþ6•‘@¢tÁkªÉøûcJ)H9GŠþ2ÅB@ ¹[5¡qé2ªrf9†sB•ëõœ*\QmyqM ÌÞW%ŸšKOB 3l©Õ¶ ¸óY‘/õ2ý¾ÞP;òÅá ò%"ø-Ý—(_b,Ñ(Ê—òx\&bråU!–J¨É—4¨äkÐaÀÙTå +xõï–/ÚÑ4Ug$_´*•œ†‘`VDÙ­ª…7O~´´éDJÊnøm“Â@ĸLÈÕwY!㸋 ö°lè"=¾<}'Ti‡ñ"ƒÝœå)'ˆ²·ªÆaÚK!D_.Ip´SA4®iþßÎaè¯ÇÚí1fýwsˆ©§ÁvßA1Ìg1<Æ-äŸ ªgñQMORyŒqoÊ7)Wó¸~rÚÁÕ…5™1_Š¨'aB*ôç¶ ¤ÛÁ¬®ûÏ^Åæ!Ï8C“}0vé[™¹Ã;a\ˆê®±s o„â${˜ZÇ\ ÆêüÄÜÉß]q¤T¼‡–¥4wUuEH¼|ƒÑúl÷GÄ)¶JRA;ã¡”3ð™\*'»NW&&ôÍdRV“¹éªü‰â©Ãà?½1§ OxTÐÀˆ"søàÚÆZxøPǧM< -¸Öê12G ÓE>{!rûóá¬F}ãå› ¶6ã -¾eIÆ­“Kk-Ù&å¸CÈ~"MxQs× äPÚÀi'{zl )Nrþ 9Iü¨dð£úG‘.¼z*œã—J#µÑ\®¯(=)h‰É)$TÇFª0éŠ -áÃÂùCÛ<åÌ -ŸŽ^ÒÏl˜Ô¡ª¶ò÷Nþ hÞè JU³,ý —ÁbøÉ‘+tؤk¥ƒL<ᔹf熸wldb½sæz·ˆgë4¹]ö¨…—ªOÎ#¬ÅZ5¿BNP!Òw2ãŠ÷)ÉÌ«é·ºïFt ÃÍ(“ É‘T%ÆÉ}%Æ/‹2©š¥¯•º?ŠôW²á}}˜œCû˜{HŠoÕ•ÊŸ†ä‹Ù·ô笎ÒwÁ±½› á‚D`¢ü 2í¾O!Ò߈¢$1EPÝi¬¨·RúÃÞÕI.Y!°q ¯ÄaÈ~}Õ@~†+Œ4ü2ÙrhØUJ„è]–ªd’Qà|µ`ÛwæŽ%VÎ&`¬z€ß™l~âQ,Ö`k³xÄþ›ͬ[@×g…zØè/-sñN¢ ÄYâD^`Ì·2'Ѯŧzh«pªÄÇX¼ 1J¼¯ïd1J§O²ê?'ˆ±PŠ°@/Ü1®¹jŒ…R„¬¾(ƘÁÑ1J¨Ÿ6ÆB)‚©awÊ % kÝ€òó¨ªÅXøð:ô€Ñ Œ°¸AÞ–ö_w¿iaùûûÅXrñ˜«™ý5aŠô1Fs¡Üö«Z}Eõ½ž0F²8ɽSÌ+r)Jb"f&£ø‚ˆe5Xé cÜ…äÆ/?‡‰‚¢íÚ¸xXdÛÓnrÈ-3…ˆ¬t‡èϪéš.ü†A¿­a@‘+ÊÜ›¶#Qe#œ½Ï0’üSŽrSÕ<ËH67Un/‹²=þVؽ6E¾~¤A£2ѪlD´.vc'õ{ºîæ©Þ²›5zæÄž‹ ãÜã+ÐarEtàø€Q6‹l­4t爛?ÉM¦>0{[T›ƒ˜ÊHá"^ -dú›qœ1Ûñ -å>XùÐcsÍ8‡ˆÕÆQáá–àºʘY/`}ÌÕ Qã…1] РL$ë q¼þìÝÙzWÂ%غîïÓ ô•<È©YÔ,y5ÓJ+-Ï£ìtzH§-wuÝT½d¿€dHà `€Mj­Úõ› â6 HÜkÞßy¡yWç½—'‡õÁ÷fO7Ÿ‚LžyŸøãÎä$uöü¤·&ÏRÎß{㭇ߜnΑ7Î}áÖ{Íj|ójÀƒ×Ϲ¸ñVádvòrs¶¼ÿÂÕÙ2Ó­Ïþqr~ˆ&?x2›œÇŽþðŸ¿_=õÇûŸWœv>U½ê‰'ýOOŸù&߯kÞâ»õžÖ¯¦­ñ«£‹?óÌþxÚ÷¬lþÐtø»O¿v±ã›3žxkÆ«,¼5ãõ¯Þ™|ûüv¯©îÅ3GN|q}҈߶~*k^¹*¼Ûb‡³ÉŽ§™›g6_Þi¿ópr}yrëQñxëoB¾Ø~ª2}ðxòi@çŠcñµ¾…wD,¼·=èONÎ¥×ßjÏqú7 §çƒæó&?\˜–«ÿÝþ¡aûP6û“ƒyúîÅ&?ŽŸÜ¸×¼saúG!&4ïo~Ïö­ædžÛÍ™ßLæýåãg>‡yûíæ%–Ûo½ÿÙÆâs˜æmØ›Üçî‘fåëpó»å·Ï¾òãËšß¡;Õ,±ÞœþòUó;:Gj¿]l¯BþàÚÕ^¿õÑ^?òóå7®~ÿàõÛ7?¹}éÞëÇݼõû矽{õÔãÛ§î^¿ôÙôO]ÿê½—Þ{ó½7¾{ó曇ݙlüå?š\pÿòÏߺv²‹Ë·&?gÞ|ûÚÝ ß¾?ù?ßÿ}òÃíÞ˜ìçú;“ÓÃËÓ]L.ûóÍ›~ýî{ÓË&ûùõÓÛ—&ÿûêÛ—ÿ¸róÍc“!oùùéßí¹qýäçצn¾ÑôÏžMç}ë£ëwî^ûöƒîMÿ¸Oñ§ŸÚwcÎV¬Êg ßDÖ÷m“Íúþ²Þ3¸}Úš}.ÏÆØ»mvÚšëôˆ´ÛÞ×úûÉ»Þmõ!hýþ~Ånwz»`͉§£tü÷ì˯ÝzkášßÜhîýë§~¿?ÙüÒÅÉQ¾eþÒýÑÉ“ÒÏß›ÿe´Íçà_MŽÝ×WN?ýàvó>žé»çîÍÖYŸ|—Vó“²¹ûÎæ_hïí[g˜“ÃÎ0gnÞýáÖRÏ0Í_yöÛÈïœùòÞÑ[Í>ÝþÔ¿…‡–òêø±7šwVœo -ôÆoßûl¶ü³u^Ù>«L{ò¼rôî·~zúËOÿí—»;žW¾?ýËÎ+O¾¿sã­éYeöyIOžWŽœ缲ݒöO¯›ç•+å·?û¼rbû¼²}VÙúwcœWš_ >¹ÓãíçüÍ=øíäÈ¿q³ùÅâsÅÕÇ®‚¼xi¾Wxßøù—g‹ƒ×þwò tæµWž|«Äìí™›oÎœ¾3fþXñÄ_ô:rqrxÜغO?›Vjú[œŸ¿}ú›/>½:¹¾97)ç¿oo–êƒIEþþΕ³×n>œììæ9[¥Zü±}û_l~lßØ*ûÉæ½£;ýà>{¸ýJËä©vó{g¯}t÷ýf·Çv;]jÞÜíwÃüS™ŽçPŠµ˜­Œl<óõݾ<õãÇÿ¾»ËZL¿÷¿>yO~æóÖïñæ¬^‹¿¿õÔ⶟YŸß>m8´ø„áãËÓÓÕæ,Ígg?óykó–Û{“›öŸßd¶bõŒç­gn}ýÆä)äßÞÛù½¿Þ¿ÜÜMç·ÞûûZó:þåí3Hs†™ŸCž|gæÆÍÂ9äøÙÎ!ó‡¥oÞ³yíðòä«7®mŸAš÷ölžCš§NoìòÒ}™þ&×æ9äû_¿±ËsHÓ±“Å3ÈåòôsË]ž\6Š'—Í5¾gœ\þù·›CN.Çf¿õÜur9räXÍûÆ¿}iãôñÿ¾|k¾–½‹÷O~ä8wææý÷Úïdm=úÌV _ŸÿdÜ/iq;¬ã<ñ²øŽÌYZ•›¿Îßû-Ç'Ÿõ¢ùö“þäò[[« ‹/›o½hÞ<7i^î>tôü._4Ÿ¾ö£æS+/^9sóç_.>9Ö‹æÍóŒæùíË÷Š/š·c詵F|Ñ|”ßH}æ‹æóOüÜéeó_4o‘ /›×¾h>¹õ›üóËåw6Ÿ<|còÜòóךß~¿>ý•æYßì—66Ÿ!tüq…'^Êxê+tþÒŒÒã‘íCNóÛ§7k¹l‡ÏGþæÇcƒŸL>õTrþ›õ[O&W=™¼óó©É³ÿï?,þ’ûSO%gŸcõä?8Ù¼´}¹ù«vxtòƒÖáÇÌÞ?}§ýô~uæ‹æ·ÉN¾>ûùäÉ__mÿU'_ø>Ó|“ÞÞù7œO?|}‡_L|ò+lÜèø«ͽúæ²ÿªÆ©!¿øÞ¼åÿîRÿªFç+ç­ÇÊ_;ç•óéï$v¼vÞýÊù™#¾»U8¹¼ùÖöïƒM;¶yryâ+œâ·èŸzô—«µ'—é©eú—ó{ž\6}sûÕ'_ã8:éùékÓO—›¾ù«yoÚ±'Þm»ùûï¿Ü|ìÒÆÛ‡¸ùäyåÜæg -w¿ôµùCÏxñ뉧}~ñýÕû¯¹õÂåsó¿t4iòÓ[¿£s£ý³æâ68ûÊG}Ðü"Î¥'_Ò:=ýÛHͯJÙ~Ikáoª4ØàîäñîÖÛMc.Mîýæ:_üqfþrÙù«oœ¹ùíûÍ« ¿AØül{õÌÍ»oœ˜¿›íoŽlßÏU½ótûló·^Ÿ{ò©ô±ÙSé£7N¿6*}ûË/7­¤ù+Í›l~Å䇮ëóΚ³ÜÑy'ÏÁ››qlÞ»éÿ|õpócßÝóOcl¾w滘tçñæéñÞÆö¦mÜýæÞÃùÇPnüºõû/o·?òøC[9ùöñö'6ïÜúÐÉ·O¶¯óÛã]y§5Þ¤ì¿]Ÿ˜;¯N,:Òþ\Í;WmmüÅôs5Úü§ÛÛñõ‹IüÞþ)éÎûÍ{LÍŽÝ©;ÎL ™túñåù&?35›ŸŸ2ÿ~ïÄæOçÝZÿû;'§ÍŸ€þýþÆäŸoNOÿÂìä>S¸z6¶÷xáÁ¯Þ¸öà•×¾ºñ¯‹oÿvíÃk¿~Ô<¹|æú§G¿˜V¥ù=‹·?#õóÛ|Ù¾¿ygû£P¿žJÉ7÷§?4Ÿ<ßœýP<ùaïŸÓŸ+›Cþ£³¯šóÏüw«'gï×ïM¿žßµßüÐü¨øÒì—`&ß4N6?/žÞüÝÆ—NlýâȉöO´?}}fk6ßLxðææ?üpnû»ò“ó[·å›É¸·ïnža?9qóÔׯ¿òê¿þíÁí×oû÷SoáÞüT±Ù§˜½²±õAã[/7¼>}οÕÆÇŸžšþ4ÝüÒãÑÙW·>{ëØì«Ö¼ÿpvzÙæ«íÙ\øä—»?ßüþÐ^}ý͇ç6gäÊùÏ›4?˜4&Ͻ²õ¦ðë“Q]Ÿ½å“%÷Ú'K^ûßW\¼pñòÁ‹—Ξ>xêþŸ??úýýßüáÇ_?ðÚS×î>ýà×ïþsû÷G>|ôÿ>¾ùŸoÿüåѯ¾zðÔµܸwïÒù›¾ýÏwÎ~sù«‹OýR@ë5‹Â_õœ}îÍï/ßùéîK÷ßøúæ÷_Yø¥²ß>üoó bwj~‹ìPóëòŸ6¿Eößýãxó??ž}œÌö‹’ ¯ªL›~ô¥›~¿þç­cï¾óÑSà;}i÷òWÞycúiªî_=õǯŸº{í“·o~rûŸÿhÞ4þŸÍŸ¬Z/˜mžöÊ¿2ý;_yáÈëïk~ÙíŸÍoÁ57øå›—N½püÈÕ«Í¥Ó¿üÞì·ä&ÿðfó„¿kþÔïÝŽþ÷¥ßÛ¯³MÏÄÏ|r0= Í;~yOæ9¶êÉÁöéêÊÅ#­¼nÝ‚+okýCëÄuåÆä;«õ‘×­Sî•{§ÚßY¯ýpmóÞ?½ý??Áßyãhë)Akä;7f/“Q¦ÿÔ»y=½y}ëõÈ#“ðÿ9ù‡û§§ÿsrnaò?ÿ~u{ß_N®qî?“ËnOÅnŸ(›óÓ¥ÙëÛ—¦Ÿ·>ÙèýS³§ ×þ÷åÉÿ|pzö¼`öµãK¶/›?~üÎö™¯6G™ŸÖª{˜Ùþðʧfš?!ôëí¿N\µÇÍU‹é¼uéƒË—îܾú·GÞ¼÷Ý/l>‰xxzëc®gÏ«ý§ïζþ¡õYíg¾z÷¿› ý§_.t?ÛñYØöÿyúYØã‡'[ß/ßÎËòø‹æýƒWO¾úúôæWßm?•ùvþXòøßçfsþñ?o=r嵋ÿ<ñâ»·¿z÷Ðåùû¦Î¾òå§4Úß|çÌ÷7NŸþ–Ùôùè©>>9û ÿzlº¼9ý‰wáYvóÍßÂyêÞŸþòÝüéÒæ©uvû|þ-ðÔyõÅ{¿MO©ÍÌ7?’ü4{2rü‹‹¶~›<‰¸øÏ»7¾úéÚwOý µýddú©—æ+>wn¢~h^½Øúý ÎÜ\<üÏìSoæOˆ&?cÍNž­3ööZó?lðÁ槨Ï~W¢õqí×ܺ?ËqöÌôÙŸå¸ýjmûÓÇþ,Ç­¿Yñħ9ŽýYŽÛ¿÷Ñþ4DZ?Ëqúù€ Ÿæ8ög98´Ó§9ŽýYŽÍgP.~šãØŸå¸õÓôŸæx¬÷ç u–ãüS žú4DZ?Ëqú»x Ÿæ8ög9v|r߈Ÿå8ýTµÝ|²h¯ÏrÜùSÕÆþ,Ç⧪úYŽ‹ß•Í§9ŽýYŽ}?UmwŸåøÄ'^m}šãØŸåxàÐNŸæ8ög9:6òg9îæ“ûú–ãûÛ¿ëØâg9nv¬z»ú,Ç';¶ùiŽc–ã¤cÕŸ£¸ûÏrÜüä¾'?Íq7£ó³·?K·ëÞúYŽ³O½vo<û³§/Ÿ£8Æg9üœÓÊÏrl="/~ŽâhŸå8}>Öó%ç»èñYŽóïý§g1òg9¶Î–õŸ*Üû³ϖͧ9>ë³?3qó‡?1qkm«ð™‰“ç‘Ÿ];¶ù¦ÔÃÍŠóô‡ëí×É›•¸ùO|ï?úuþ -ÈßN4¿¯p|ú3÷ìÝ&Ïx›àì«æ·Ì§oÓ˜- ÌÞ{þÕc󟺧¯gLæ:ûŸŸÿñhúzÜߦ?ÝÌ_‡i˜~Øú±·=ÑÞ«˜;­aÎ_¹jÎÙß·~¸Þ¾ÎSK³Ÿ›ßyñë­ËÚk “Sæ·[ÿpâ‰õÓÉyl£¹ôÔæ«Í:Àß_ù¥974Om[K[ûþàØl“É7MS¥Nlý€ðòôÏ4—œm2)äì¹åÇÞ<|ó÷¦OÍn?˜ïv§OɺrþÃù¿9ráÕ­óáÉöÛP~þÇ¡[Ç/ùâêkÿ|ùØ“×qö7ïÏdÖlîo¾ÿóÃSÝ{Üiuîq£ýv–S/ýñú±¯ý~áʃsï]»øùw‡gom8òÉ?^Ø|5òÓí×ѾÚþá¹ùÝÕ?¿ûió¦Ô*KóîæyäGó_H¼ùɽÙ+)Í›ÎÏ¿úùÁ™ùW~úÅìÍx·Ž|ýåü«3?4/º}t~¾Fß¼K¦Ùã''§B²ù ÃÙG˜5o©Ÿïû«#·ÚöÉ™Öl¾zí»oýùÖÊeû¶|õÑ 7¶ÿJÚõŸOþpôÚß¿ýþ§VSfßûÍû7f¯kmþŸCÛošÀ³‡þ¼·õŠÓç§æ« Í›‹ÎÍ—_ûôüü«›_ÿ¸µåÙÙùà•þríéÙ¼ãÃÏoßüùÅo¯Ýÿð³¿ÝúæÄÕéŠóë͹dóUÛÇ¿]Ø\¥Ìny~þƒy³º1û{ëÍ9kþôî¯ó+¾ÿèèì(n­.O_Ϙž§š—%6æç©n}½õ[r³wÙÞ=<{ipò3kó½æW¦ )§_<5_å;ÿÆñ­—'ß‹ÇßmÎc7N^¹|çý‡[+’Gæoå:ûÔöG‹h½Ð>]uYX?žþÕsÓ³üÉö[$®¼:½‡Ûçä+WÏ^œ<¯Ü›-,ÎV6Ÿx_ÆäG¥ÍåͧÖZ&=˜Ù;om~ÇãÊì<ÖZÞœÿVÁ‰ÖßjÎ]Í(ó—Íß'qç~kßÛk-³w|¿ù¯?þ÷•¯8Ô¬V~yë×ïÚ+•š\òGÿü­Ùàü—×ýðã¯ï|ý¿ý~àôÁÙÿÛ˜ü¿æÿ^¼|ðô™KÏœ??ùç›KßùæÀáé¶O9øί6^»uàЗ§®ýþøæß>þñ?¿~ýûÿ:øjsÑÃwßypïæÁWήðåä -¯<<™ÒÆ—“­'ÿt¤Y#ýr2Í/›ÝLþÿÃÿÙ|ñèÀŸ6擘\ö¿&ÿã­Éÿž\ô?žÞ8øîÁO?ß8ø]³ýýÙ-¸ýãÏ^mݘû_ÿñxrki®çÀ©›þŸ¿}tÿÎõƒß;ðéÁ“ç6N_8{ùò¥K“l\:;êä…³—Î^8wúü¹ó'ÿ~éàésçN^>ñܹ “Ο>xâôÿ{ãÌÁÏž9}áÜÁ³gšÙ>ü×Èûn®¹µÿ­/.<ÛÜG§GèÜäÿM6›Ü§Ó~óëÇ_¿zðÌdØËgÏxxïÿúÿ¨ö,ÁÿXŽÿ3µ¤Ã.è$it’4:I$N’F'I£“¤ÑIÒè$5ô„4:I$N’F'I£“¤ÑIÒè$it’4:I =!N’F'I£“¤ÑIÒè$it’4:I$NRCOH£“¤ÑIÒè$it’4:I$N’F'I£“ÔÐÒè$it’4:I$N’F'I£“¤ÑIÒè$5ô„4:I$N’F'I£“¤ÑIÒè$it’4:I =!N’F'I£“¤ÑIÒè$it’4:I$NRCOH£“¤ÑIÒè$it’4:I$N’F'I£“ûÖ_""""""""""""û:ÖVDDDDDDDDDDDDêcmEDDDDDDDDDDD¤>ÖVDDDDDDDDDDDDêcmEDDDDDDDDDDD¤>ÖVDDDDDDDDDDDDêcmEDDDDDDDDDDD¤>ÖVDDDDDDDDDDDDêcmEDDDDDDDDDDD¤>ÖVDDDDDDDDDDDDêcmEDDDDDDDDDDD¤>ÖVDDDDDDDDDDDDêcmEDDDDDDDDDDD¤>ÖVDDDDDDDDDDDDêcmEDDDDDDDDDDD¤>ÖVDDDDDDDDDDDDêcmEDDDDDDDDDDD¤>ÖVDDDDDDDDDDDDêcmEDDDDDDDDDDD¤>ÖVDDDDDDDDDDDDêcmEDDDDDDDDDDD¤>ÖVDDDDDDDDDDDDêcmEDDDDDDDDDDD¤>ÖVDDDDDDDDDDDDêcmEDDDDDDDDDDD¤>ÖVDDDDDDDDDDDDêcmEDDDDDDDDDDD¤>ÖVDDDDDDDDDDDDêcmEDDDDDDDDDDDdÜX[©ÏšüŸ©µ  t’4:I$N’F'I£“¤ÑIÒè$it’zB$N’F'I£“¤ÑIÒè$it’4:I¤†žF'I£“¤ÑIÒè$it’4:I$N’F'©¡'¤ÑIÒè$it’4:I$N’F'I£“¤ÑIjè it’4:I$N’F'I£“¤ÑIÒè$it’zB$N’F'I£“¤ÑIÒè$it’4:I¤†žF'I£“¤ÑIÒè$it’4:I$N’F'©¡'¤ÑIÒè$it’4:I$N’F'I£“¤ÑÉ}ë¯VÚ—,nSóõbv·eýˆ»½t­!Û ?i£/n9dæ}g’6zéZ¥}ÖÏdÈèÃÃýˆÈð,ãûºþ 3îþK[.nß=“±F¯95ûï{ÄêÀòî…UŽÞ}ºG/\>ÿ¹cÈq0ú÷B÷èÃ÷_s¬~ôú늌›q­ý戈ȈY<·9Û—®ëdwÇ-mVõóÜÕ-zÆÏ8{÷H./ë½½»{öؾdq›š¯»Cý–õ#înôÒµ†l3ü8¤¾¸å™÷IÚè¥k•öY?“!£?Cö#"óŒïëú3̸û/m¹¸}÷LƽæhÔì¿ï«?Ë»V9z÷qè½p¹µ•î‘Õ^]‘q3®µß1‹çö!gûÒu=‚ìͪ~ž»ºEÖVöØíÝݳÇö%‹ÛÔ|Ý}ê·¬qw£—®5d›áÇ!môÅ-‡Ì¼ïLÒF/]«´Ïú™ }øq²že|_ןaÆÝiËÅí»g2Öè5G£fÿ}XýXÞ½°ÊÑ»C÷è…Ë­­Œp¬~ôú늌›q­ý戈ì›$œ]G2ŸÒu“Aæ–0‡šYÕÏsW·(bm¥ô]¹Wî£!··½·q•æ\·ûëîãP¿eýˆ»½t­!Û ?i£/n9dæ}g’6zéZ¥}ÖÏdÈèÃÃýˆÈð,ãûºþ 3îþK[.nß=“±F¯95ûï{ÄêÀòî…UŽÞ}ºG/\nme„{dõ£×_WdÜŒkí7GDdß$áìº8úù”®›ü’0·„9Ô̪~ž»ºEÖVÖ|{Û{WiÎ¥q»¿î>õ[Ö¸»ÑKײÍðã6úâ–CfÞw&i£—®UÚgýL†Œ>ü8 Ùˆ Ï2¾¯ëÏ0ãî¿´åâöÝ3kôš£Q³ÿ¾G¬þ,ï^XåèÝÇ¡{ôÂåÖVF¸GV?zýuEÆ͸Ö~sDDöMή‹£™OéºÉ sK˜Cͬê繫[dmeÍ··½·q•æ\·ûëîãP¿eýˆ»½t­!Û ?i£/n9dæ}g’6zéZ¥}ÖÏdÈèÃÃýˆÈð,ãûºþ 3îþK[.nß=“±F¯95ûï{ÄêÀòî…UŽÞ}ºG/\nme„{dõ£×_WdÜŒkí7GDdß$áìº8úù”®›ü’0·„9Ô̪~ž»ºEÖVÖ|{Û{WiÎ¥q»¿î>õ[Ö¸»ÑKײÍðã6úâ–CfÞw&i£—®UÚgýL†Œ>ü8 Ùˆ Ï2¾¯ëÏ0ãî¿´åâöÝ3kôš£Q³ÿ¾G¬þ,ï^XåèÝÇ¡{ôÂåÖVF¸GV?zýuEÆ͸Ö~sDDöMή‹£™OéºÉ sK˜Cͬê繫[dmeÍ··½·q•æ\·ûëîãP¿eýˆ»½t­!Û ?i£/n9dæ}g’6zéZ¥}ÖÏdÈèÃÃýˆÈð,ãûºþ 3îþK[.nß=“±F¯95ûï{ÄêÀòî…UŽÞ}ºG/\nme„{dõ£×_WdÜŒkí7GDdß$áìº8úù”®›ü’0·„9Ô̪~ž»ºEÖVÖ|{Û{WiÎ¥q»¿î>õ[Ö¸»ÑKײÍðã6úâ–CfÞw&i£—®UÚgýL†Œ>ü8 Ùˆ Ï2¾¯ëÏ0ãî¿´åâöÝ3kôš£Q³ÿ¾G¬þ,ï^XåèÝÇ¡{ôÂåÖVF¸GV?zýuEÆ͸Ö~sDDöMή‹£™OéºÉ sK˜Cͬê繫[dmeÍ··½·q•æ\·ûëîãP¿eýˆ»½t­!Û ?i£/n9dæ}g’6zéZ¥}ÖÏdÈèÃÃýˆÈð,ãûºþ 3îþK[.nß=“±F¯95ûï{ÄêÀòî…UŽÞ}ºG/\nme„{dõ£×_WdÜŒkí7GDdß$áìº8úù”®›ü’0·„9Ô̪~ž»ºEÖVÖ|{Û{WiÎ¥q»¿î>õ[Ö¸»ÑKײÍðã6úâ–CfÞw&i£—®UÚgýL†Œ>ü8 Ùˆ Ï2¾¯ëÏ0ãî¿´åâöÝ3kôš£Q³ÿ¾G¬þ,ï^XåèÝÇ¡{ôÂåÖVF¸GV?zýuEÆ͸Ö~sDDöMή‹£™OéºÉ sK˜Cͬê繫[dmeÍ··½·q•æ\·ûëîãP¿eýˆ»½t­!Û ?i£/n9dæ}g’6zéZ¥}ÖÏdÈèÃÃýˆÈð,ãûºþ 3îþK[.nß=“±F¯95ûï{ÄêÀòî…UŽÞ}ºG/\nme„{dõ£×_WdÜŒkí7GDdß$áìº8úù”®›ü’0·„9Ô̪~ž»ºEÖVÖ|{Û{WiÎ¥q»¿î>õ[Ö¸»ÑKײÍðã6úâ–CfÞw&i£—®UÚgýL†Œ>ü8 Ùˆ Ï2¾¯ëÏ0ãî¿´åâöÝ3kôš£Q³ÿ¾G¬þ,ï^XåèÝÇ¡{ôÂåÖVF¸GV?zýuEÆ͸Ö~sDDöMή‹£™OéºÉ sK˜Cͬê繫[dmeÍ··½·q•æ\·ûëîãP¿eýˆ»½t­!Û ?i£/n9dæ}g’6zéZ¥}ÖÏdÈèÃÃýˆÈð,ãûºþ 3îþK[.nß=“±F¯95ûï{ÄêÀòî…UŽÞ}ºG/\nme„{dõ£×_WdÜŒkí7GDdß$áìº8úù”®›ü’0·„9Ô̪~ž»ºEÖVÖ|{Û{WiÎ¥q»¿î>õ[Ö¸»ÑKײÍðã6úâ–CfÞw&i£—®UÚgýL†Œ>ü8 Ùˆ Ï2¾¯ëÏ0ãî¿´åâöÝ3kôš£Q³ÿ¾G¬þ,ï^XåèÝÇ¡{ôÂåÖVF¸GV?zýuEÆ͸Ö~sDDöMή‹£™OéºÉ sK˜Cͬê繫[dmeÍ··½·q•æ\·ûëîãP¿eýˆ»½t­!Û ?i£/n9dæ}g’6zéZ¥}ÖÏdÈèÃÃýˆÈð,ãûºþ 3îþK[.nß=“±F¯95ûï{ÄêÀòî…UŽÞ}ºG/\nme„{dõ£×_WdÜŒkí7GDdß$áìº8úù”®›ü’0·„9Ô̪~ž»ºEÖVÖ|{Û{WiÎ¥q»¿î>õ[Ö¸»ÑKײÍðã6úâ–CfÞw&i£—®UÚgýL†Œ>ü8 Ùˆ Ï2¾¯ëÏ0ãî¿´åâöÝ3kôš£Q³ÿ¾G¬þ,ï^XåèÝÇ¡{ôÂåÖVF¸GV?zýuEÆ͸Ö~sDDöMή‹£™OéºÉ sK˜Cͬê繫[dmeÍ··½·q•æ\·ûëîãP¿eýˆ»½t­!Û ?i£/n9dæ}g’6zéZ¥}ÖÏdÈèÃÃýˆÈð,ãûºþ 3îþK[.nß=“±F¯95ûï{ÄêÀòî…UŽÞ}ºG/\nme„{dõ£×_WdÜŒkí7GDdß$áìº8úù”®›ü’0·„9Ô̪~ž»ºEÖVÖ|{Û{WiÎ¥q»¿î>õ[Ö¸»ÑKײÍðã6úâ–CfÞw&i£—®UÚgýL†Œ>ü8 Ùˆ Ï2¾¯ëÏ0ãî¿´åâöÝ3kôš£Q³ÿ¾G¬þ,ï^XåèÝÇ¡{ôÂåÖVF¸GV?zýuEÆ͸Ö~sDDöMή‹£™OéºÉ sK˜Cͬê繫[dmeÍ··½·q•æ\·ûëîãP¿eýˆ»½t­!Û ?i£/n9dæ}g’6zéZ¥}ÖÏdÈèÃÃýˆÈð,ãûºþ 3îþK[.nß=“±F¯95ûï{ÄêÀòî…UŽÞ}ºG/\nme„{dõ£×_WdÜŒkí7GDdß$áìº8úù”®›ü’0·„9Ô̪~ž»ºEÖVÖ|{Û{WiÎ¥q»¿î>õ[Ö¸»ÑKײÍðã6úâ–CfÞw&i£—®UÚgýL†Œ>ü8 Ùˆ Ï2¾¯ëÏ0ãî¿´åâöÝ3kôš£Q³ÿ¾G¬þ,ï^XåèÝÇ¡{ôÂåÖVF¸GV?zýuEÆ͸Ö~sDDöMή‹£™OéºÉ sK˜Cͬê繫[dmeÍ··½·q•æ\·ûëîãP¿eýˆ»½t­!Û ?i£/n9dæ}g’6zéZ¥}ÖÏdÈèÃÃýˆÈð,ãûºþ 3îþK[.nß=“±F¯95ûï{ÄêÀòî…UŽÞ}ºG/\nme„{dõ£×_WdÜŒkí7GDdß$áìº8úù”®›ü’0·„9Ô̪~ž»ºEÖVÖ|{Û{WiÎ¥q»¿î>õ[Ö¸»ÑKײÍðã6úâ–CfÞw&i£—®UÚgýL†Œ>ü8 Ùˆ Ï2¾¯ëÏ0ãî¿´åâöÝ3kôš£Q³ÿ¾G¬þ,ï^XåèÝÇ¡{ôÂåÖVF¸GV?zýuEÆ͸Ö~sDDöMή‹£™OéºÉ sK˜Cͬê繫[dmeÍ··½·q•æ\·ûëîãP¿eýˆ»½t­!Û ?i£/n9dæ}g’6zéZ¥}ÖÏdÈèÃÃýˆÈð,ãûºþ 3îþK[.nß=“±F¯95ûï{ÄêÀòî…UŽÞ}ºG/\nme„{dõ£×_WdÜŒkí7GDdß$áìº8úù”®›ü’0·„9Ô̪~ž»ºEÖVÖ|{Û{WiÎ¥q»¿î>õ[Ö¸»ÑKײÍðã6úâ–CfÞw&i£—®UÚgýL†Œ>ü8 Ùˆ Ï2¾¯ëÏ0ãî¿´åâöÝ3kôš£Q³ÿ¾G¬þ,ï^XåèÝÇ¡{ôÂåÖVF¸GV?zýuEÆ͸Ö~sDDöMή‹£™OéºÉ sK˜Cͬê繫[dmeÍ··½·q•æ\·ûëîãP¿eýˆ»½t­!Û ?i£/n9dæ}g’6zéZ¥}ÖÏdÈèÃÃýˆÈð,ãûºþ 3îþK[.nß=“±F¯95ûï{ÄêÀòî…UŽÞ}ºG/\nme„{dõ£×_WdÜŒkí7GDdß$áìº8úù”®›ü’0·„9Ô̪~ž»ºEÖVÖ|{Û{WiÎ¥q»¿î>õ[Ö¸»ÑKײÍðã6úâ–CfÞw&i£—®UÚgýL†Œ>ü8 Ùˆ Ï2¾¯ëÏ0ãî¿´åâöÝ3kôš£Q³ÿ¾G¬þ,ï^XåèÝÇ¡{ôÂåÖVF¸GV?zýuEÆ͸Ö~sDDöMή‹£™OéºÉ sK˜Cͬê繫[dmeÍ··½·q•æ\·ûëîãP¿eýˆ»½t­!Û ?i£/n9dæ}g’6zéZ¥}ÖÏdÈèÃÃýˆÈð,ãûºþ 3îþK[.nß=“±F¯95ûï{ÄêÀòî…UŽÞ}ºG/\nme„{dõ£×_WdÜŒkí7GDdß$áìº8úù”®›ü’0·„9Ô̪~ž»ºEÖVÖ|{Û{×Ú”ˆˆˆˆˆˆˆˆˆì›xí1!Ž¿´Sú®Ü¯=)­ƒŒkí7SDDDDDDDDDöM¼ö˜Ç_Ú)}Wîמ”ÖA‚Í¿ bè$it’4:I$N’F'I£“¤ÑIÒè$5ô„4:I$N’F'I£“¤ÑIÒè$it’4:I =!N’F'I£“¤ÑIÒè$it’4:I$NRCOH£“¤ÑIÒè$it’4:I$N’F'I£“ÔÐÒè$it’4:I$N’F'I£“¤ÑIÒè$5ô„4:I$N’F'I£“¤ÑIÒè$it’4:I =!N’F'I£“¤ÑIÒè$it’4:I$NRCOH£“¤ÑIÒè$it’4:I$N’F'I£“ÔÐÒè$it’4:I$N’F'I£“¤ÑIÒèd„¿ª³&zB$N’F'I£“¤ÑIÒè$it’4:IŒ`mzÒIÒè$it’4:I$N’F'I£“¤ÑÉÖV '$N’F'I£“¤ÑIÒè$it’4:IŒ`mzÒIÒè$it’4:I$N’F'I£“¤ÑÉÖV '$N’F'I£“¤ÑIÒè$it’4:IŒ`mzÒIÒè$it’4:I$N’F'I£“¤ÑÉÖV '$N’F'I£“¤ÑIÒè$it’4:IŒ`mzÒIÒè$it’4:I$N’F'I£“¤ÑÉÖV '$N’F'I£“¤ÑIÒè$it’4:IŒ`mzÒIÒè$it’4:I$N’F'I£“¤ÑÉÖV '$N’F'I£“¤ÑIÒè$it’4:IŒ`mzÒIÒè$it’4:I$N’F'I£“¤ÑÉÖV '$N’F'I£“¤ÑIÒè$it’4:IŒ`mzÒIÒè$it’4:I$N’F'I£“¤ÑÉÖV '$N’F'I£“¤ÑIÒè$it’4:IŒ`mzÒIÒè$it’4:I$N’F'I£“¤ÑÉÖV '$N’F'I£“¤ÑIÒè$it’4:IŒ`mzÒIÒè$it’4:I$N’F'I£“¤ÑÉÖV '$N’F'I£“¤ÑIÒè$it’4:IŒ`mzÒIÒè$it’4:I$N’F'I£“¤ÑÉÖV '$N’F'I£“¤ÑIÒè$it’4:IŒ`mzÒIÒè$it’4:I$N’F'I£“¤ÑÉÖV '$N’F'I£“¤ÑIÒè$it’4:IŒ`mzÒIÒè$it’4:I$N’F'I£“¤ÑÉÖV '$N’F'I£“¤ÑIÒè$it’4:IŒ`mzÒIÒè$it’4:I$N’F'I£“¤ÑÉÖV '$N’F'I£“¤ÑIÒè$it’4:IŒ°¼µ•ú=‹ˆˆˆˆˆˆˆˆˆˆˆì¿X[©Ïšøý&Òè$it’4:I$N’F'I£“¤ÑIÒè$5ô„4:I$N’F'I£“¤ÑIÒè$it’4:I =!N’F'I£“¤ÑIÒè$it’4:I$NRCOH£“¤ÑIÒè$it’4:I$N’F'I£“ÔÐÒè$it’4:I$N’F'I£“¤ÑIÒè$5ô„4:I$N’F'I£“¤ÑIÒè$it’4:I =!N’F'I£“¤ÑIÒè$it’4:I$NRCOH£“¤ÑIÒè$it’4:I$N’F'I£“ÔÐÒè$it’4:I$N’F'I£“¤ÑIÒèd„¿¦¾ÍÒè it’4:I$N’F'I£“¤ÑIÒè$it2‚µèI'I£“¤ÑIÒè$it’4:I$N’F'#X[žt’4:I$N’F'I£“¤ÑIÒè$it2‚µèI'I£“¤ÑIÒè$it’4:I$N’F'#X[žt’4:I$N’F'I£“¤ÑIÒè$it2‚µèI'I£“¤ÑIÒè$it’4:I$N’F'#X[žt’4:I$N’F'I£“¤ÑIÒè$it2‚µèI'I£“¤ÑIÒè$it’4:I$N’F'#X[žt’4:I$N’F'I£“¤ÑIÒè$it2‚µèI'I£“¤ÑIÒè$it’4:I$N’F'#X[žt’4:I$N’F'I£“¤ÑIÒè$it2‚µèI'I£“¤ÑIÒè$it’4:I$N’F'#X[žt’4:I$N’F'I£“¤ÑIÒè$it2ÂÒÖVþ Îì?ŵ¶""""""""""""ÒÊì?Å]ùÚÊXü~it’4:I$MN'×ú´š 9„¤ÞjËt’’u=›Jëä⫾¥¯»ÿuñëel¹»Y;“½2ÏÕ‡qo]·úÑ»/¯qqÎ5ãοžþ§ûô¸}·Yš´sè$it’4:IšœN®õi5Ar: 3:I½Õ<–é$%ëz6•ÖÉå­),{¥ ~VËXSÈŸçjŽÃ¸·®[ýèݗ׌¸8çšqç_OÿÓ}zÜ€¾Û,MÚ¹ t’4:I$MN'×ú´š 9„¤ÞjËt’’u=›JëäòÖ–½RP?«e¬)äÏs5ÇaÜ[×­~ôîËkF\œs͸ó¯§ÿé>=n@ßm–&íÜ:I$N’&§“k}ZMœNÂŒNRo5e:IɺžM¥uryk -Ë^)¨ŸÕ2Öò繚ã0î­ëV?z÷å5#.ιfÜù×Óÿt7 ï6K“vî$N’F'I“Óɵ>­&HN'aF'©·šÇ2¤d]ϦÒ:¹¼5…e¯ÔÏjk -ùó\Íq÷Öu«½ûòšç\3îüëéº@Ðw›¥I;wN’F'I£“¤ÉéäZŸV$§“0£“Ô[Íc™NR²®gSi\ޚ²W -êgµŒ5…üy®æ8Œ{ëºÕÞ}y͈‹s®wþõô?ÝG Ç è»ÍÒ¤»@'I£“¤ÑIÒätr­O« ’ÓI˜ÑIê­æ±L')Y׳©´N.oMaÙ+õ³ZÆšBþ=n@ßm–&íÜ:I$N’&§“k}ZMœNÂŒNRo5e:IɺžM¥uryk -Ë^)¨ŸÕ2Öò繚ã0î­ëV?z÷å5#.ιfÜù×Óÿt7 ï6K“vî$N’F'I“Óɵ>­&HN'aF'©·šÇ2¤d]ϦÒ:¹¼5…e¯ÔÏjk -ùó\Íq÷Öu«½ûòšç\3îüëéŠGà¯ÁY“´sè$it’4:IšœN®õi5Ar: 3:I½Õ<–é$%ëz6•ÖÉå­),{¥ ~VËXSÈŸçjŽÃ¸·®[ýèݗ׌¸8çšqç_OÿS< Κ¤»@'I£“¤ÑIÒè$it’4:I$N’F'©¡'¤ÑIÒè$it’4:I$N’F'I£“¤ÑIjè it’4:I$N’F'I£“¤ÑIÒè$it’zB$N’F'I£“¤ÑIÒè$it’4:I¤†žF'I£“¤ÑIÒè$it’4:I$N’F'©¡'¤ÑIÒè$it’4:I$N’F'I£“¤ÑIjè it’4:I$N’F'I£“¤ÑIÒè$it’zB$N’F'I£“¤ÑIÒè$it’4:I¤†žF'I£“¤ÑIÒè$it’4:I$N’F'#üU5ÑÒè$it’4:I$N’F'I£“¤ÑIÒèdk+ГN’F'I£“¤ÑIÒè$it’4:I$NF°¶=é$it’4:I$N’F'I£“¤ÑIÒèdk+ГN’F'I£“¤ÑIÒè$it’4:I$NF°¶=é$it’4:I$N’F'I£“¤ÑIÒèdk+ГN’F'I£“¤ÑIÒè$it’4:I$NF°¶=é$it’4:I$N’F'I£“¤ÑIÒèdk+ГN’F'I£“¤ÑIÒè$it’4:I$NF°¶=é$it’4:I$N’F'I£“¤ÑIÒèdk+ГN’F'I£“¤ÑIÒè$it’4:I$NF°¶=é$it’4:I$N’F'I£“¤ÑIÒèdk+ГN’F'I£“¤ÑIÒè$it’4:I$NF°¶=é$it’4:I$N’F'I£“¤ÑIÒèd„¥­­ÔïXDDDDDDDDöwfÿYû4DDä™™ýgyÛçß.k+""""""""’ÙÖ> yffÿYÞöù·k½k+cy>¿i­‡œgx>;I2$N’F'I£“¤ÑIV¤õŠV·„NzeföÖQm϶ÝÉöå‹·¨þÅÔõî[Qs­Ý8î+]kñÖõÝýX}·é{ÝÅùϾ.'kf^5Ï…5ˆÅù”Ö,jnc=Wï§ê~_¸]õãg2Öî– áñtõÖ{v¥ÛóÙI’é$it’4:I$N²"¯¼Í$tÒ+3Ë°·Žj{¶ÖVúnYºî¸G¬ïkïõû¯«ï6}¯[Z5°¶RºViÿ¥ÛU?nq&cín OWo½gWº=Ÿ$™N’F'I£“¤ÑIÒè$+RñÊÛLB'½2³ {먶gkm¥ï–¥ëŽ{Äú¾ö^¿ÿú±únÓ÷º¥Uk+¥k•ö_º]õãg2Öî– áñtõÖ{v¥ÛóÙI’é$it’4:I$N²"¯¼Í$tÒ+3Ë°·Žj{¶ÖVúnYºî¸G¬ïkïõû¯«ï6}¯[Z5°¶RºViÿ¥ÛU?nq&cín OWo½gWº=Ÿ$™N’F'I£“¤ÑIÒè$+RñÊÛLB'½2³ {먶gkm¥ï–¥ëŽ{Äú¾ö^¿ÿú±únÓ÷º¥Uk+¥k•ö_º]õãg2Öî– áñtõÖ{v¥ÛóÙI’é$it’4:I$N²"¯¼Í$tÒ+3Ë°·Žj{¶ÖVúnYºî¸G¬ïkïõû¯«ï6}¯[Z5°¶RºViÿ¥ÛU?nq&cín OWo½gWº=Ÿ$™N’F'I£“¤ÑIÒè$+RñÊÛLB'½2³ {먶gkm¥ï–¥ëŽ{Äú¾ö^¿ÿú±únÓ÷º¥Uk+¥k•ö_º]õãg2Öî– áñtõÖ{v¥ÛóÙI’é$it’4:I$N²"¯¼Í$tÒ+3Ë°·Žj{¶ÖVúnYºî¸G¬ïkïõû¯«ï6}¯[Z5°¶RºViÿ¥ÛU?nq&cín OWo½gWº=Ÿ$™N’F'I£“¤ÑIÒè$+RñÊÛLB'½2³ {먶gkm¥ï–¥ëŽ{Äú¾ö^¿ÿú±únÓ÷º¥Uk+¥k•ö_º]õãg2Öî– áñtõÖ{v¥ÛóÙI’é$it’4:I$N²"¯¼Í$tÒ+3Ë°·Žj{¶ÖVúnYºî¸G¬ïkïõû¯«ï6}¯[Z5°¶RºViÿ¥ÛU?nq&cín OWo½gWº=Ÿ$™N’F'I£“¤ÑIÒè$+RñÊÛLB'½2³ {먶gkm¥ï–¥ëŽ{Äú¾ö^¿ÿú±únÓ÷º¥Uk+¥k•ö_º]õãg2Öî– áñtõÖ{v¥ÛóÙI’é$it’4:I$N²"¯¼Í$tÒ+3Ë°·Žj{¶ÖVúnYºî¸G¬ïkïõû¯«ï6}¯[Z5°¶RºViÿ¥ÛU?nq&cín OWo½gWº=Ÿ$™N’F'I£“¤ÑIÒè$+RñÊÛLB'½2³ {먶gkm¥ï–¥ëŽ{Äú¾ö^¿ÿú±únÓ÷º¥Uk+¥k•ö_º]Ý·¨;õ[®KÂãéê­õó Ïg'I¦“¤ÑIÒè$it’4:ÉŠT¼ò6“ÐI¯Ì,ÃÞ:ªíÙZ[é»eéºã±¾¯½×ï¿~¬¾Ûô½niÕÀÚJéZ¥ý—nW÷-êNý–ë’ðx -m:I$N’F'I£“¤ÑIÒè$it’4:I =!N’F'I£“¤ÑIÒè$it’4:I$NRCOH£“¤ÑIÒè$it’4:I$N’F'I£“ÔÐÒè$it’4:I$N’F'I£“¤ÑIÒè$5ô„4:I$N’F'I£“¤ÑIÒè$it’4:I =!N’F'I£“¤ÑIÒè$it’4:I$NRCOH£“¤ÑIÒè$it’4:I$N’F'I£“ÔÐÒè$it’4:I$N’F'I£“¤ÑIÒè$5ô„4:I$NîÎ_Ó<Ÿû\Þžgû·“{ëiœ'5v?Ùd?ÑIÒè$ižŸNîçœUdMžŸž°Wè$it’4:¹;{edo­,X[!Óþ8Ojì~²?:É~¢“¤ÑIÒ ‘ŽÌþ³®-—1ϽxÄö÷<ÇÚ{?ë=¶ ù¿ÿÇ_i¥gVk+""""""""’•ÙÖ> ‘ŽÌþ³®-—1ϽxÄö÷<ÇÚ{?ë=¶ Y×ÚÊXžŸßob¯ÐIÒè$it’4:I$NÖ[ë $k°ŒÛ[³ÏÞl¿8ª½²Ïåíùyë|ɬ“‹/”–^:]Üfñ5êUnYŸ!û,γçµÚuóš9×Ük}çÙwôš[TÕϤjV‹—·öÓ÷ØïÍÎËŸ±e{>…ýÔ4ªæ8Ôüëû¬¿ÛVÎs<Òè$it’4:I$N’F'ë­õ’5XÆí­Ù§µ•œ=?o/±¶R³Ïâ<{^«}Q÷1¯™sͽÖwž}G¯¹Eõ÷WýLªfµxyk?}mñÞì¼ü[¶çSØOM£jŽCÍ¿î°Ïú»må<Ç#N’F'I£“¤ÑIÒè$it²ÞZ_ YƒeÜÞš}Z[ÉÙóóÖùk+5û,γçµÚuóš9×Ük}çÙwôš[TÕϤjV‹—·öÓ÷ØïÍÎËŸ±e{>…ýÔ4ªæ8Ôüëû¬¿ÛVÎs<Òè$it’4:I$N’F'ë­õ’5XÆí­Ù§µ•œ=?o/±¶R³Ïâ<{^«}Q÷1¯™sͽÖwž}G¯¹Eõ÷WýLªfµxyk?}mñÞì¼ü[¶çSØOM£jŽCÍ¿î°Ïú»må<Ç#N’F'I£“¤ÑIÒè$it²ÞZ_ YƒeÜÞš}Z[ÉÙóóÖùk+5û,γçµÚuóš9×Ük}çÙwôš[TÕϤjV‹—·öÓ÷ØïÍÎËŸ±e{>…ýÔ4ªæ8Ôüëû¬¿ÛVÎs<Òè$it’4:I$N’F'ë­õ’5XÆí­Ù§µ•œ=?o/±¶R³Ïâ<{^«}Q÷1¯™sͽÖwž}G¯¹Eõ÷WýLªfµxyk?}mñÞì¼ü[¶çSØOM£jŽCÍ¿î°Ïú»må<Ç#N’F'I£“¤ÑIÒè$it²ÞZ_ YƒeÜÞš}Z[ÉÙóóÖùk+5û,γçµÚuóš9×Ük}çÙwôš[TÕϤjV‹—·öÓ÷ØïÍÎËŸ±e{>…ýÔ4ªæ8Ôüëû¬¿ÛVÎs<Òè$it’4:I$N’F'ë­õ’5XÆí­Ù§µ•œ=?o/±¶R³Ïâ<{^«}Q÷1¯™sͽÖwž}G¯¹Eõ÷WýLªfµxyk?}mñÞì¼ü[¶çSØOM£jŽCÍ¿î°Ïú»må<Ç#N’F'I£“¤ÑIÒè$it²ÞZ_ YƒeÜÞš}Z[ÉÙóóÖùk+5û,γçµÚuóš9×Ük}çÙwôš[TÕϤjV‹—·öÓ÷ØïÍÎËŸ±e{>…ýÔ4ªæ8Ôüëû¬¿ÛVÎs<Òè$it’4:I$N’F'ë­õ’5XÆí­Ù§µ•œ=?o/±¶R³Ïâ<{^«}Q÷1¯™sͽÖwž}G¯¹Eõ÷WýLªfµxyk?}mñÞì¼ü[¶çSØOM£jŽCÍ¿î°Ïú»må<Ç#N’F'I£“¤ÑIÒè$it²ÞZ_ YƒeÜÞš}Z[ÉÙóóÖùk+5û,γçµÚuóš9×Ük}çÙwôš[TÕϤjV‹—·öÓ÷ØïÍÎËŸ±e{>…ýÔ4ªæ8Ôüëû¬¿ÛVÎs<Òè$it’4:I$N’F'ë­õ’5XÆí­Ù§µ•œ=?o/±¶R³Ïâ<{^«}Q÷1¯™sͽÖwž}G¯¹Eõ÷WýLªfµxyk?}mñÞì¼ü[¶çSØOM£jŽCÍ¿î°Ïú»må<Ç#N’F'I£“¤ÑIÒè$it²ÞZ_ YƒeÜÞš}Z[ÉÙóóÖùk+5û,γçµÚuóš9×Ük}çÙwôš[TÕϤjV‹—·öÓ÷ØïÍÎËŸ±e{>…ýÔ4ªæ8Ôüëû¬¿ÛVÎs<Òè$it’4:I$N’F'ë­õ’5XÆí­Ù§µ•œ=?o/±¶R³Ïâ<{^«}Q÷1¯™sͽÖwž}G¯¹Eõ÷WýLªfµxyk?}mñÞì¼ü[¶çSØOM£jŽCÍ¿î°Ïú»må<Ç#N’F'I£“¤ÑIÒè$it’4:I$NRCOH£“¤ÑIÒè$it’4:I$N’F'I£“ÔÐÒè$it’4:I$N’F'I£“¤ÑIÒè$5ô„4:I$N’F'I£“¤ÑIÒè$it’4:I =!N’F'I£“¤ÑIÒè$it’4:I$NRCOH£“¤ÑIÒè$it’4:I$N’F'I£“ÔÐÒè$it’4:I$N’F'I£“¤ÑIÒè$5ô„4:I$N’F'I£“¤ÑIÒè$it’4:I =!N’F'I£“¤ÑIÒè$it’4:I$Nù«"k¢'¤ÑIÒè$it’4:I$N’F'I£“¤ÑÉ ÖV šN’F'I£“¤ÑIÒè$it’4:I$N±¶Õt’4:I$N’F'I£“¤ÑIÒè$it2ˆµ¨¦“¤ÑIÒè$it’4:I$N’F'I£“A¬­@5$N’F'I£“¤ÑIÒè$it’4:I bmªé$it’4:I$N’F'I£“¤ÑIÒèdk+PM'I£“¤ÑIÒè$it’4:I$N’F'ƒX[j:I$N’F'I£“¤ÑIÒè$it’4:ÄÚ -TÓIÒè$it’4:I$N’F'I£“¤ÑÉ ÖV šN’F'I£“¤ÑIÒè$it’4:I$N±¶Õt’4:I$N’F'I£“¤ÑIÒè$it2ˆµ¨¦“¤ÑIÒè$it’4:I$N’F'I£“A¬­@5$N’F'I£“¤ÑIÒè$it’4:I ²„µ•š]Šˆˆˆˆˆˆˆˆˆˆˆì­ôÙÔÚŠˆˆˆˆˆˆˆˆˆˆˆ<ïé³ézøý&Òè$it’4:I$N’F'I£“¤ÑIÒèdk+ГN’F'I£“¤ÑIÒè$it’4:I$NF°¶=é$it’4:I$N’F'I£“¤ÑIÒèdk+ГN’F'I£“¤ÑIÒè$it’4:I$NF°¶=é$it’4:I$N’F'I£“¤ÑIÒèdk+ГN’F'I£“¤ÑIÒè$it’4:I$NF°¶=é$it’4:I$N’F'I£“¤ÑIÒèdk+ГN’F'I£“¤ÑIÒè$it’4:I$NF°¶=é$it’4:I$N’F'I£“¤ÑIÒèdk+ГN’F'I£“¤ÑIÒè$it’4:I$NF°¶=é$it’4:I$N’F'I£“¤ÑIÒèdk+ГN’F'I£“¤ÑIÒè$it’4:I$NF°¶=é$it’4:I$N’F'I£“¤ÑIÒèdk+ГN’F'I£“¤ÑIÒè$it’4:I$NF°¶=é$it’4:I$N’F'I£“¤ÑIÒè$5ô„4:I$N’F'I£“¤ÑIÒè$it’4:I =!N’F'I£“¤ÑIÒè$it’4:I$NRCOH£“¤ÑIÒè$it’4:I$N’F'I£“ÔÐÒè$it’4:I$N’F'I£“¤ÑIÒè$5ô„4:I$N’F'I£“¤ÑIÒè$it’4:I =!N’F'I£“¤ÑIÒè$it’4:I$NRCOH£“¤ÑIÒè$it’4:I$N’F'I£“ÔÐÒè$it’4:I$N’F'I£“¤ÑIÒèäžñ×4k¢'¤ÑIÒè$it’4:I$N’F'I£“¤ÑÉ=ÃÚ -´è$it’4:I$N’F'I£“¤ÑIÒèäžamZt’4:I$N’F'I£“¤ÑIÒè$itrÏ°¶-:I$N’F'I£“¤ÑIÒè$it’4:¹gX[$N’F'I£“¤ÑIÒè$it’4:IÜ3¬­@‹N’F'I£“¤ÑIÒè$it’4:I$NîÖV E'I£“¤ÑIÒè$it’4:I$N’F'÷ k+Т“¤ÑIÒè$it’4:I$N’F'I£“{†µhÑIÒè$it’4:I$N’F'I£“¤ÑÉ=ÃÚ -´è$it’4:I$N’F'I£“¤ÑIÒèäžamZt’4:I$N’F'I£“¤ÑIÒè$itrÏ°¶-:I$N’F'I£“¤ÑIÒè$it’4:¹gX[$N’F'I£“¤ÑIÒè$it’4:IÜ3vµ¶ò—ˆˆˆˆˆˆˆˆˆˆˆÈ¾Ë欭ˆˆˆˆˆˆˆˆˆˆˆˆ<;›ÿme,~¿‰4:I$N’F'I£“¤ÑIÒè$it’4:ÁÚ -ô¤“¤ÑIÒè$it’4:I$N’F'I£“¬­@O:I$N’F'I£“¤ÑIÒè$it’4:ÁÚ -ô¤“¤ÑIÒè$it’4:I$N’F'I£“¬­@O:I$N’F'I£“¤ÑIÒè$it’4:ÁÚ -ô¤“¤ÑIÒè$it’4:I$N’F'I£“¬­@O:I$N’F'I£“¤ÑIÒè$it’4:ÁÚ -ô¤“¤ÑIÒè$it’4:I$N’F'I£“¬­@O:I$N’F'I£“¤ÑIÒè$it’4:ÁÚ -ô¤“¤ÑIÒè$it’4:I$N’F'I£“¬­@O:I$N’F'I£“¤ÑIÒè$it’4:ÁÚ -ô¤“¤ÑIÒè$it’4:I$N’F'I£“¬­@O:I$N’F'I£“¤ÑIÒè$it’4:á¯ê¬‰žF'I£“¤ÑIÒè$it’4:I$N’F'#X[žt’4:I$N’F'I£“¤ÑIÒè$it’zB$N’F'I£“¤ÑIÒè$it’4:I¤†žF'I£“¤ÑIÒè$it’4:I$N’F'©¡'¤ÑIÒè$it’4:I$N’F'I£“¤ÑIjè it’4:I$N’F'I£“¤ÑIÒè$it’zB$N’F'I£“¤ÑIÒè$it’4:I¤†žF'I£“¤ÑIÒè$it’4:I$N’F'©¡'¤ÑIÒè$it’4:I$N’F'I£“¤ÑIjè it’4:I$N’F'I£“¤ÑIÒè$it’zB$N’F'I£“¤ÑIÒè$it’4:I¤†žF'I£“¤ÑIÒè$it’4:I$N’F'©¡'¤ÑIÒè$it’4:I$N’F'I£“¤ÑIjè it’4:I$N’F'I£“¤ÑIÒè$it’zB$N’F'I£“¤ÑIÒè$it’4:IÜgþ endstream endobj 38 0 obj <>stream -€ÿŸ½;ínêLû¼Ý¯Ÿµü ‰™Ë–çÉ£€0! °Á fÌÝ]oº?z *$P%@löÿ8ú*îËòåý;µ÷Fñ¢ A##kÛ÷W×_¯Ý<=4²tfúvç߯¬¿z½ñrèÂÐÈí±¥—¯W·î½Þz¶½þòŸÃ ÝߺyþÜõ3«Ã ÃέÿsãåíÖíñÛ‡X:3ÞºÝù„ÎGé8súÈÔÚèO‡_-\ŸºyèåÝù_nÍüÔzÑž[øîèæÑç'Ž-¾ºñü»Éö£c¿O=´újòþ‰s·VG¯l¾>ÜY?±zvñÑ©›‡µWï??>4ÒýëK×nœœ9üêèáǧn½X]Z9ýãã,ÍÝ}þàðò“³‡Wöo.ýqëöÄê¾Õíñ{ßÏNO.ú~­µyà—öXëô¯#­Ísë'&—&÷·ÇÎÿ°}²µñø·µñC‹·~;|ìÆýsC#‡mý~£ûÏ×îî»·r´½öúUçKÍͶ¿Û>qiéÑȉ‹íµÕ3·[c#¶O-lx1öìÒ½•ÖØô™3ݧ]jX˜í~îLûПڋÇÖ—–Ú{V?ý~æúé™7Ï<4òæ¹g^ŸkŸ[»{íììò“Å'“o~oq¡=úº}òÜæ“w?÷ö—?É­åÇ+¯NùýÀɵ}Ïnêü^ûø›C0¶=÷`¡5³òìäÒÃK´~8~ýÇ÷ž¢ûËŸßË¥Ûkû§W~úó{™\ÛÞ·0¶xc¤5²º92ziñèÓÇWÇn¼Ø÷©œ[|zð»Îûó)æ7Öî>]<½vwqd~ññÕÖÒÕ'{^¶×æn=|¶wÿ§žçæòå}{&Ú6ÏNtr_þµ½újîYkúîÃ;‹ 7î-ýa¢SÿÀ棥ÕÍÍgÇ—/l­´6.NÙsúÄ?ãÔÚ¥…sK[wη¸ðjôòòã‰ã{¦~?¼uaüþžöt÷°›»úâé٥ˮÞh¯Új¿·›“7×é¼H·Û^¬ŽŽî0iß¡«Ý=?2þt­½:}rÏäíã­WËS7ïþ°úÃáý§žo­^{÷ß>ä·+—öµ÷Í´½ÿèÂo+§ïýKÎëeíøëµÉ;Ë×[>Å»­ürmik}}yê÷»?Ý|3+Þò¯{7¤Ý>:1~ûÞÛOë>iw’¿ÁÓ¾ÿ¸ïïý×$ݘ9õ~ß·z9݃Þ=äW7÷ŸžYºvþù‘ÕÍÑ;Û/ºñèßètùóC§þþÙ¿{3@3ǃÖòãý·/µW6Ïv^M­“ßwöðóÄÔïG¦¯¼ÝÍýçFÛ«O¾ûG{mâࡹkû~üuíÞÉ}ãí•åùCûÄØÛy‘Ú÷æAlï¯øØçî[1¿zzüʃöɧ—ßž>¶î<ü¾}ðÒ“£oà±SOÎ?ýË°wN#+·†Ff~¾¼~º½rÿÑÙÎ/?>nýpéâñÖÆëãs÷Î_Üœ¼µµ|~üȹss­Í‡{–;}ñrmßóÅ#KW®ýòdæõñ³{Úù'íË7–žŸ¾ùÍ“vÏc½<ío“KsG~úþÓOûxiíÚÒìü•oòûáÍÓ“#??:9vþÚø¡‘ñ# OOµÎž{ujôñÖ­ã­N]_þè'~ä¹ß‡Õ'{V7çO]ŸÙÛÞ^Y:{óò­•­W›‹Ý £k?߿ݹæ>qé׃­û#OŽw.˜{;''Ó3£3gçFWÇn_Ü¿vwì—ïW7¯n¾XÝxqmíƒjãk“w׎^èœ.œ_<ú||dùñ+ÛÝKÇò©‡žüðîäx©sĦn]¾²ôlõ·#‹ÇŽÿròƒç¹~ãÝ#/Ìür-¼üæ\òæÔÓ™ä­;nÎ^~1¶ÔšÙ¼¸oulýäþöÊâɸ¾ô—¿Ÿ-ÿû/½ž}›¯òé_æ§?úåûûUþã÷²÷ó¾ÿ¾wéÓ—ÿâ#öY#ð º|d¬ÿ柿åôò½|ý—ÿº#Öë|«.Ž@Óõ¿åôü½|Õ—ïÃëa¾i—w#0úßh>ï{ùÒ/߯#öŸGàÛwéŽÀàê÷¾à{ù‚/ß×#öÉh¬Ë§nëÒ¯øÒ#6È»ñO@³¯Êo;=|/}¯:bƒ¼ÿÈ àœüÍF ×ïåëFàëØ ïÆ?A]‘¿Å|Ö÷òÅ#Ч#6È»ñw#0Ðû±>Àç/_2ýÿýÁþ±þÀ±Ï¾wô»£_òqX_FàkŽXï#ðmº úÝÑo9½}/_;_}ÄzoØeÐïŽ~£øŒïå+F ?Gì¿À·î2èwGû?Ÿû½|Ùôñˆý‡h¤Ë ßíë|Ñ÷òÙ#Ðï#öñh²Ë€ÿkuŸFàËØçŒÀ7éò·hüU9àÿZýõ#ð•G¬Çøv]öü~lÀÿµú«F Gì¿À7î²wà÷cþ¯Õ_:};bÿqšè²wà÷cþ¯Õ~zÐOúéA?=è§ýô ŸôÓƒ~zp`õ¿tüô`˜ŸôÓƒ~zÐO¬~Ï#৿i?=è§{øò3üµ#£SC#K“«GnÙsuæÿžèþÅ'oþ“‰áKOÖïmܾyþÒp÷/cyïoE™>ppøæC­á¥¡ßßþ-í­' oÿqyãÁÖö¿þ¾–Î[Ã7O ­nüÏÖ½+§–‡o^úe¸5:3>73>>95153?ßšè<¬ÕýÝ™ñé¹ù™éñùÉéÉùñáñ©©Ñ¹™™ùÎoNLLOL ÿcrfjtf¶5>1?19Óšþµó Öôðäx÷n>ì÷“w?õÝx÷sÓÃ'˜íüÒžêü¿ÎÆFFÞ|ëÝ£µ0<>Ùš™™žºyæÿûoü/"©ŸLýdê'S?“îÉÔO¦~²RõKm††©ŸLýdê'S?“îÉÔO¦~²RõKm††©ŸLýdê'S?“îÉÔO¦~²RõKm††©ŸLýdê'S?“îÉÔO¦~²RõKm††©ŸLýdê'S?“îÉÔO¦~²RõKm††©ŸLýdê'S?“îÉÔO¦~²RõKm††©ŸLýdê'S?“îÉÔO¦~²RõKm††©ŸLýdê'S?“îÉÔO¦~²RõKm††©ŸLýdê'S?“îÉÔO¦~²RõKm††©ŸLýdê'S?“îÉÔO¦~²RõKm††©ŸLýdê'S?“îÉÔO¦~²RõKm††©ŸLýdê'S?“îÉÔO¦~²RõKm††©ŸLýdê'S?“îÉÔO¦~²RõKm††©ŸLýdê'S?“îÉÔO¦~²RõKm††©ŸLýdê'S?“îÉÔO¦~²RõKm††©ŸLýdê'S?“îÉÔO¦~²RõKm††©ŸLýdê'S?“îÉÔO¦~²RõKm††©ŸLýdê'S?“îÉÔO¦~²RõKm††©ŸLýdê'S?“îÉÔO¦~²RõKm††©ŸLýdê'S?“îÉÔO¦~²RõKm††©ŸLýdê'S?“îÉÔO¦~²RõKm††©ŸLýdê'S?“îÉÔO¦~²RõKm††©ŸLýdê'S?“îÉÔO¦~²RõKm††©ŸLýdê'S?“îÉÔO¦~²RõKm††©ŸLýdê'S?“îÉÔO¦~²RõKm††©ŸLýdê'S?“îÉÔO¦~²RõKm††©ŸLýdê'S?“îÉÔO¦~²RõKm††©ŸLýdê'S?“îÉÔO¦~²Zõÿ°,˲,˲,˲,˲,˲,˲,˲,ËÚI«Ö^Ð,õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOV«þ–eY–eY–eY–eY–eY–eY–eY–eY;iÕúÑ š¥~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ™®?,k +F¹×~²½¾Pîœ?ð±·ÂWŒr¯ýdû®r£8èb¥¯å^ûÉ>öVø"@¹sþÀÇÞ -_1ʽö“ |ìºÊbØ©`°ÊÕ¬°ÙS¿ÆgOýdÔ;ï%+÷ª7{ *W°ÂfOýB²ï÷Jm¦+ìT0XåêVØì©_Höeˆ†yÛ!S¹W½ÙkP¹úƒ6{ê’}¿Wj3]a§‚Á*W°ÂfOýB²/C4ÌۙʽêÍ^ƒÊÕ¬°ÙS¿ìû½R›é -; V¹úƒ6{ê’}¢aÞvÈTîUoöT®þ`…Ížú…dßï•ÚLWØ©`°ÊÕ¬°ÙS¿ìË ó¶C¦r¯z³× rõ+löÔ/$û~¯ÔfºÂNƒU®þ`…Ížú…d_†h˜·2•{Õ›½•«?Xa³§~!Ù÷{¥6Óv*¬rõ+löÔ/$û2Düí©Ü«Þì5¨\ýÁ -›=õ ɾß+µ™®°SÁ`•«?Xa³§~!Ù—!æm‡Lå^õf¯AåêVØì©_Höý^©Ít… -«\ýÁ -›=õ ɾ Ñ0o;d*÷ª7{ *W°ÂfOýB²ï÷Jm¦+ìT0XåêVØì©_Höeˆ†yÛ!S¹W½ÙkP¹úƒ6{ê’}¿Wj3]a§‚Á*W°ÂfOýB²/C4ÌۙʽêÍ^ƒÊÕ¬°ÙS¿ìû½R›é -; V¹úƒ6{ê’}¢aÞvÈTîUoöT®þ`…Ížú…dßï•ÚLWØ©`°ÊÕ¬°ÙS¿ìË ó¶C¦r¯z³× rõ+löÔ/$û~¯ÔfºÂNƒU®þ`…Ížú…d_†h˜·2•{Õ›½•«?Xa³§~!Ù÷{¥6Óv*¬rõ+löÔ/$û2Düí©Ü«Þì5¨\ýÁ -›=õ ɾß+µ™®°SÁ`•«?Xa³§~!Ù—!æm‡Lå^õf¯AåêVØì©_Höý^©Ít… -«\ýÁ -›=õ ɾ Ñ0o;d*÷ª7{ *W°ÂfOýB²ï÷Jm¦+ìT0XåêVØì©_Höeˆ†yÛ!S¹W½ÙkP¹úƒ6{ê’}¿Wj3]a§‚Á*W°ÂfOýB²/C4ÌۙʽêÍ^ƒÊÕ¬°ÙS¿ìû½R›é -; V¹úƒ6{ê’}¢aÞvÈTîUoöT®þ`…Ížú…dßï•ÚLWØ©`°ÊÕ¬°ÙS¿ìË ó¶C¦r¯z³× rõ+löÔ/$û~¯ÔfºÂNƒU®þ`…Ížú…d_†h˜·2•{Õ›½•«?Xa³§~!Ù÷{¥6Óv*¬rõ+löÔ/$û2Düí©Ü«Þì5¨\ýÁ -›=õ ɾß+µ™®°SÁ`•«?Xa³§~!Ù—!æm‡Lå^õf¯AåêVØì©_Höý^©Ít… -«\ýÁ -›=õ ɾ Ñ0o;d*÷ª7{ *W°ÂfOýB²ï÷Jm¦+ìT0XåêVØì©_Höeˆ†yÛ!S¹W½ÙkP¹úƒ6{ê~¿÷GÉE#jâÀ |ì­ðÕ ¯ýdyÛÁ²µh„sþ>öVøjP­×þÀü s$«5Š7ð±·ÂWƒ¼ö“yÛÁ*´h„sþ>öVøjP©×~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ“©ŸLýdêgÒ=™úÉÔOV«þ–eY–eY–eY–eY–eY–eY–eY–eY;iÕúÑ š¥~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦~2õ“©ŸLýLº'S?™úÉJÕ/µ¦þþ°,˲,˲,˲ú¼üÁ3™úô‹Ñ²,˲,˲,k÷-ðL¦þÞ¾(b¨ŸLýdêgÒ=™úÉÔOVª~©ÍÐ0õ?àmb¨ŸLýLº'S?™úÉJÕ/µ¦þ¼í@ õ“©ŸI÷dê'S?Y©ú¥6CÃÔÿ€·ˆ¡~2õ3éžLýdê'+U¿Ôfh˜úð¶1ÔO¦~&Ý“©ŸLýd¥ê—Ú SÿÞv †úÉÔϤ{2õ“©Ÿ¬TýR›¡aêÀÛÄP?™ú™tO¦~2õ“•ª_j34LýxÛê'S?“îÉÔO¦~²RõKm††©ÿo;CýdêgÒ=™úÉÔOVª~©ÍÐ0õ?àmb¨ŸLýLº'S?™úÉJÕ/µ¦þ¼í@ õ“©ŸI÷dê'S?Y©ú¥6CÃÔÿ€·ˆ¡~2õ3éžLýdê'+U¿Ôfh˜úð¶1ÔO¦~&Ý“©ŸLýd¥ê—Ú SÿÞv †úÉÔϤ{2õ“©Ÿ¬TýR›¡aêÀÛÄP?™ú™tO¦~2õ“•ª_j34LýxÛê'S?“îÉÔO¦~²RõKm††©ÿo;CýdêgÒ=™úÉÔOVª~©ÍÐ0õ?àmb¨ŸLýLº'S?™úÉJÕ/µ¦þ¼í@ õ“©ŸI÷dê'S?Y©ú¥6CÃÔÿ€·ˆ¡~2õ3éžLýdê'+U¿Ôfh˜úð¶1ÔO¦~&Ý“©ŸLýd¥ê—Ú SÿÞv †úÉÔϤ{2õ“©Ÿ¬TýR›¡aêÀÛÄP?™ú™tO¦~2õ“•ª_j34LýxÛê'S?“îÉÔO¦~²RõKm††©ÿo;CýdêgÒ=™úÉÔOVª~©ÍÐ0õ?àmb¨ŸLýLº'S?™úÉjÕÿò¬÷VŒZ'"š¥~2õ3éžLýdê'«Uàʳ¬R+F­ÍR?™ú™tO¦~2õ“•ª_j34Lýdê'S?™ú™tO¦~2õ“•ª_j34Lýdê'S?™ú™tO¦~2õ“•ª_j34Lýdê'S?™ú™tO¦~2õ“•ª_j34Lýdê'S?™ú™tO¦~2õ“•ª_j34Lýdê'S?™ú™tO¦~2õ“•ª_j34Lýdê'S?™ú™tO¦~2õ“•ª_j34Lýdê'S?™ú™tO¦~2õ“•ª_j34Lýdê'S?™ú™tO¦~2õ“ÕªÿÇ.]ô Ö(Ò,õ“©ŸLýL»°ûÀo5-˲,‹ì›z¦~2õ“©Ÿivø­¦eY–ÕóÚµ—!z° ëÓ3õ“©ŸLýL»°»û½žíÂúôLýd¥ê—ÚL¸ õlÖ§gê'S?™ú™vaw÷{=Û…õé™úÉJÕ/µ™þpêÙ.¬OÏÔO¦~2õ3íÂîî÷z¶ ëÓ3õ“•ª_j3ýá2Ô³]XŸž©ŸLýdêgÚ…ÝÝïõlÖ§gê'+U¿ÔfúÃe¨g»°>=S?™úÉÔÏ´ »»ßëÙ.¬OÏÔOVª~©Íô‡ËPÏva}z¦~2õ“©Ÿivw¿×³]XŸž©Ÿ¬TýR›é—¡žíÂúôLýdê'S?Ó.ìî~¯g»°>=S?Y©ú¥6Ó.C=Û…õé™úÉÔO¦~¦]ØÝý^Ïva}z¦~²RõKm¦?\†z¶ ëÓ3õ“©ŸLýL»°»û½žíÂúôLýd¥ê—ÚL¸ õlÖ§gê'S?™ú™vaw÷{=Û…õé™úÉJÕ/µ™þpêÙ.¬OÏÔO¦~2õ3íÂîî÷z¶ ëÓ3õ“•ª_j3ýá2Ô³]XŸž©ŸLýdêgÚ…ÝÝïõlÖ§gê'+U¿ÔfúÃe¨g»°>=S?™úÉÔÏ´ »»ßëÙ.¬OÏÔOVª~©Íô‡ËPÏva}z¦~2õ“©Ÿivw¿×³]XŸž©Ÿ¬TýR›é—¡žíÂúôLýdê'S?Ó.ìî~¯g»°>=S?Y©ú¥6Ó.C=Û…õé™úÉÔO¦~¦]ØÝý^Ïva}z¦~²RõKm¦?\†z¶ ëÓ3õ“©ŸLýL»°»û½žíÂúôLýd¥ê—ÚL¸ õlÖ§gê'S?™ú™vaw÷{=Û…õé™úÉJÕ/µ™þpêÙ.¬OÏÔO¦~2õ3íÂîî÷z¶ ëÓ3õ“•ª_j3ýá2Ô³]XŸž©ŸLýdêgÚ…ÝÝïõlÖ§gê'+U¿ÔfúÃe¨g»°>=S?™úÉÔÏ´ »»ßëÙ.¬OÏÔOVª~©Íô‡ËPÏva}z¦~2õ“©Ÿivw¿×³]XŸž©Ÿ¬TýR›é—¡žíÂúôLýdê'S?Ó.ìî~¯g»°>=S?Y©ú¥6Ó.C=Û…õé™úÉÔO¦~¦]ØÝý^Ïva}z¦~²RõKm¦?\†z¶ ëÓ3õ“©ŸLýL»°»û½žíÂúôLýd¥ê—ÚL¸ õlÖ§gê'S?™ú™vaw÷{=Û…õé™úÉJÕ/µ™þøò,˲,Ë -Xô`ÞíÓ3õ“•ª_j3ý1ð+ eY–eY–ÕÀ¢»ðnŸž©Ÿ¬TýR›¡aê'S?™úÉÔϤ{2õ“©Ÿ¬TýR›¡aê'S?™úÉÔϤ{2õ“©Ÿ¬TýR›¡aê'S?™úÉÔϤ{2õ“©Ÿ¬TýR›¡aê'S?™úÉÔϤ{2õ“©Ÿ¬TýR›¡aê'S?™úÉÔϤ{2õ“©Ÿ¬TýR›¡aê'S?™úÉÔϤ{2õ“©Ÿ¬TýR›¡aê'S?™úÉÔϤ{2õ“©Ÿ¬TýR›¡aê'S?™úÉÔϤ{2õ“©Ÿ¬Vý?J.Qki–úÉÔO¦~¦rÝ~«iY–eY4¢ÜM R?™úÉÔÏT®ûÀo5-˲¬WÑË(WŸ©ŸLýdêg*×Ýý^ƒÊÕ§Aê'+U¿Ôfº\†T®> R?™úÉÔÏT®»û½•«OƒÔOVª~©Ít¹ 5¨\}¤~2õ“©Ÿ©\w÷{ *WŸ©Ÿ¬TýR›érjP¹ú4Hýdê'S?S¹îî÷T®> R?Y©ú¥6Óå2Ô rõiúÉÔO¦~¦rÝÝï5¨\}¤~²RõKm¦Ëe¨AåêÓ õ“©ŸLýL庻ßkP¹ú4Hýd¥ê—ÚL—ËPƒÊÕ§Aê'S?™ú™Êuw¿× rõiúÉJÕ/µ™.—¡•«OƒÔO¦~2õ3•ëî~¯AåêÓ õ“•ª_j3].C *WŸ©ŸLýdêg*×Ýý^ƒÊÕ§Aê'+U¿Ôfº\†T®> R?™úÉÔÏT®»û½•«OƒÔOVª~©Ít¹ 5¨\}¤~2õ“©Ÿ©\w÷{ *WŸ©Ÿ¬TýR›érjP¹ú4Hýdê'S?S¹îî÷T®> R?Y©ú¥6Óå2Ô rõiúÉÔO¦~¦rÝÝï5¨\}¤~²RõKm¦Ëe¨AåêÓ õ“©ŸLýL庻ßkP¹ú4Hýd¥ê—ÚL—ËPƒÊÕ§Aê'S?™ú™Êuw¿× rõiúÉJÕ/µ™.—¡•«OƒÔO¦~2õ3•ëî~¯AåêÓ õ“•ª_j3].C *WŸ©ŸLýdêg*×Ýý^ƒÊÕ§Aê'+U¿Ôfº\†T®> R?™úÉÔÏT®»û½•«OƒÔOVª~©Ít¹ 5¨\}¤~2õ“©Ÿ©\w÷{ *WŸ©Ÿ¬TýR›érjP¹ú4Hýdê'S?S¹îî÷T®> R?Y©ú¥6Óå2Ô rõiúÉÔO¦~¦rÝÝï5¨\}¤~²RõKm¦Ëe¨AåêÓ õ“©ŸLýL庻ßkP¹ú4Hýd¥ê—ÚL—ËPƒÊÕ§Aê'S?™ú™Êuw¿× rõiúÉJÕ/µ™.—¡•«OƒÔO¦~2õ3•ëî~¯AåêÓ õ“•ª_j3].C *WŸ©ŸLýdêg*×Ýý^ƒÊÕ§Aê'+U¿Ôfº\†T®> R?™úÉÔÏT®»û½•«OƒÔOVª~©ÍtýaY–eY–e,QînŸ©Ÿ¬TýR›éøв,˲,Ëj`шrwû4Hýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýd¥ê—Ú S?™úÉÔO¦~&Ý“©ŸLýdµêÿaY–eY–eY–eY–eY–eY–eY–eY–µ“V­½ Yê'S?™úÉÔϤ{2õ“©Ÿ¬TýR›¡aê'S?™úÉÔϤ{2õ“©Ÿ¬TýR›¡aê'S?™úÉÔϤ{2õ“©Ÿ¬TýR›¡aê'S?™úÉÔϤ{2õ“©Ÿ¬TýR›¡aê'S?™úÉÔϤ{2õ“©Ÿ¬TýR›¡aê'S?™úÉÔϤ{2õ“©Ÿ¬TýR›¡aê'S?™úÉÔϤ{2õ“©Ÿ¬TýR›¡aê'S?™úÉÔϤ{2õ“©Ÿ¬TýR›¡aê'S?™úÉÔϤ{2õ“©Ÿ¬TýR›¡aê'S?™úÉÔϤ{2õ“©Ÿ¬TýR›¡aê'S?™úÉÔϤ{2õ“©Ÿ¬TýR›¡aê'S?™úÉÔϤ{2õ“©Ÿ¬TýR›¡aê'S?™úÉÔϤ{2õ“©Ÿ¬TýR›¡aê'S?™úÉÔϤ{2õ“©Ÿ¬Tý?>ËÈÈÚöýÕõ×ëC7O,™¾Ýù÷+ë¯^o¼º04r{léåëÕ­{¯·žm¯¿üçðB÷·nž?wýÌêðÂðsëÿÜxy»u{êöÁá£Ã–ÎŒ·nw>¡óуÃG:»²±þdøÀÛgî|løâË­[Ûß¼zoýÉÆÛÇíüoèúýrÿ{ææçk£¯·6?þåÆ–·Þ~ÚðØÒÕ•3gæ¦W7î=»¿ÑýàÔÉí­Å…‹ÖÛÇNXž>8súÈÔÚèO‡_-\ŸºyèåÝù_nÍüÔzÑž[øîèæÑç'Ž-¾ºñü»Éö£c¿O=´újòþ‰s·VG¯l¾>ÜY?±zvñÑ©›‡µWï??>4ÒýëK×nœœ9üêèáǧn½X]Z9ýãã,ÍÝ}þàðò“³‡Wöo.ýqëöÄê¾Õíñ{ßÏNO.ú~­µyà—öXëô¯#­Ísë'&—&÷·ÇÎÿ°}²µñø·µñC‹·~;|ìÆýsC#‡mý~£ûÏ×îî»·r´½öúUçKÍͶ¿Û>qiéÑȉ‹íµÕ3·[c#¶O-lx1öìÒ½•ÖØô™3ݧ]jX˜í~îLûПڋÇÖ—–Ú{V?ý~æúé™7Ï<4òæ¹g^ŸkŸ[»{íììò“Å'“o~oq¡=úº}òÜæ“w?÷ö—?É­åÇ+¯NùýÀɵ}Ïnêü^ûø›C0¶=÷`¡5³òìäÒÃK´~8~ýÇ÷ž¢ûËŸßË¥Ûkû§W~úó{™\ÛÞ·0¶xc¤5²º92ziñèÓÇWÇn¼Ø÷©œ[|zð»Îûó)æ7Öî>]<½vwqd~ññÕÖÒÕ'{^¶×æn=|¶wÿ§žçæòå}{&Ú6ÏNtr_þµ½újîYkúîÃ;‹ 7î-ýa¢SÿÀ棥ÕÍÍgÇ—/l­´6.NÙsúÄ?ãÔÚ¥…sK[wη¸ðjôòòã‰ã{¦~ßû䇉CÇ.ÎtÛ±¹«/žž]º|áêöê¡­ö{»9ysý—΋t»}àÅêèèþó—öºÚ}гÖÆÝßVÚ«Ó'÷LÞ>Þzµ>7qïüÅÍÉ[[?_ï¼|O¶6îYîlôÅ˵}Ï,]¹öË“™×ÆÏ~ìi矴/ßXz~úþå7OÚ=õò´¿M.ÍùéûO?í㥵kK³óW¼}Èï‡7OOŽüüèäØùkãw†FÆ,<=5¹ß½FoÝ:ÞúáÔõå~âGžûíqX}2±gusþÔõ™½íí•¥³7/ßZÙzµ¹ØÍ0ºöóýÛkîá—~=غ?òäx点·sÒx2=3:svntuìöÅýkwÇ~ù~uóêæ‹Õ×Ö>¨6¾6ywíè…ÎÙéÂùÅ£ÏÇG–¸²Ý½t,ŸºqèÉïNŽ—:GlêÖå+KÏV;²xìø/'?xžë7Þ=òÂÌÏ3÷;swýÌí¥ ·§ZÓ¯¯­¼ÙÒêæâúÁÕW'§º5®üå{^;ü¨5Ù}½üzm¼sä/ßœ¹ñbsiiûÞþ§Ó{÷>¼vdîÕh§ËOÿþRsO=ZZ»7}ùÀûgΉâ#×ÂËoÎ%oN=IÞºóàæìåcK­™Í‹ûVÇÖOîo¯,žü؉ëKùûÙò¿ÿò×ëÙ·ù*Ÿþea~ú£_¾¿_å?~/{?ïûï{—>}ù/>bŸ5ß ËGF Áúoþù[Ž@/ßË×ù¯;b½ŽÀ·êòá4]ÿ[Ž@ÏßËW}ù>±Fà›vy7©ÿFàó¾—/ýòý:bÿy¾}—î ®~ÿGà ¾—/øò}=bŸƺ|ꆰ±.ý/=bƒ¼ÿÔ4ûªü¶#ÐÃ÷Ò‡øª#6È»ñŒÀÎÉßlzý^¾n¾þˆ ònüÃÔù[ŒÀg}/_<}:bƒ¼7½ëó|þ÷ò%#ÐÏ#6È»ñ·_åóßìïëß|áûÌè{—A¿;ú%o÷÷ˆõe¾æˆõ>ߦˠßý–#ÐÛ÷òµ#ðÕG¬§ø†]ýîè7Ïø^¾búsÄþÛ|ë.ƒ~w´ÿ#ð¹ßË—@ØFº úÝѾŽÀ}/Ÿ=ý>b&» ø¿V÷i¾üˆ}Î|“.Æ_•þ¯Õ_?_yÄzo×eïÀïÇü_«¿júqÄþû|ã.{~?6àÿZý¥#з#öG ‰.{~?6àÿZí§ýô ŸôÓƒ~zÐOúéA?=è§VÿKGÀOö÷ˆùéA?=è§ýôàÀê÷<~zð›vñÓƒ~z°‡/?óÁ_;2:54²4¹zäÖ‘=Wgþï‰î_|òæo1™¾ôdýÞÆýá›ç/ wÿ2–÷þV”ùá‡oþ84ÖyÜøµÿózxa¨5<Ö~¹±q}ûþ³ÎßüÛúÓ3Û÷7þOçßg‡Ç®¾~öòŸþ{÷ïTé|ÒÍãCãÃKχÆ'§Gçæ'ǧf¦fçæ§ç‡ÿ19;7ÚšœšžoͶ¦æƧ†ŸÍÎÏM·¦:¿513;5û‘ûȃ&¦§GgçÇ''fæf&&[³ýýËõô |¹í¡›‡‡Ž­<ÛÞÜÚÞÚ~piýõÃÎ÷71<Ö=.KÛ¯·ÖŸl­¿ê|¤ó»G‡:¦5|óþÐÌgÁñOÁÉ™ÑÖÔdg‹3³ãï¦5?:953>??97>1=Ó9xã­ÑñÉ©ùñ¹Ötkzv~ú#:÷±Mηæg§[ssSÓ3soËß¾Z/ùûkîÐÍü‡C÷×ÆÓ33£³“““ã³ãÃ05ÿѹûÛƒ>6w“³ã£“ãã3³ÓS­©ééOTúÈ—ûڃ׾Òý­Ñ¹¹‰É‰‰Î.Z­™Vç5ߟ™š˜é|7mu>m¼ó»¯;9?1>Ý}yÌNu~wtbøæÒÐÔðÿž>?4??;:ÝjÍÎÌtÆfn²||~tnvb²ómu^[“Ý×ïøÔÔèüôìÔÔÌ\knúc9÷÷ÇŒONÎMδ:G·517ÿæPÿí‹õô |µWŸøÛþË¿·þü¤Ö·®lÜ{½¾ýàÉÆÂÂʳ—Û/¯¬ßßúýÕÂÂäÛæ3ÛÿÓIqÿàðØõí­îßütõõËn–öµ>ïüóÄg~î•g¿oßï|àígÍWn}ÕWûÙsÓ£ÓSŸíœSÇ?q¼~ܺÿúáÛÏèíŽÕæ>÷[{ÿ°üùMLw&b¾óÒ˜™ŸŸýÔ®7¶_o¼¼ù_oòsË×MË¿>{rxìÌöë¿=öÌöV÷Trù÷õû/×·_¿û+Ô–Ÿ=ûȶž<»÷øo½ÚøÏ»_êþŸÏùÿUnv~jn´sþé\k:S5ÿ©±z{ôzû9­©Ñ©¹¹ÙÉÖtg*§¦>ñ9§7¶<ü×wwôíGÏmýÏÆÕ‡ëÝCti½s9yõ¯£üÁg~üX¿÷©§×·ï?Ùxy¡s9úø³¿w[Õ9ÏÎÏOuNÊ“S㓳ÝÓñdç¢255;>139773Ü=wFp²óщéÎÕf|~¦5×=A¿wvîœ;'íÎéy¼sJïSJeæ“úL)õ™Òþr<©Ï”R™ù¤>SJ}¦´¿Oê3¥Tf>©Ï”RŸ)í/Ç“úL)•™Oê3¥ÔgJûËñ¤>SJeæ“úL)õ™Ò>ûòvÈ‚šþÒ°¬T6ðÈÀ—ãie.¨ià/ ËêqAe |}‹ã •™R*3ŸÔgJ©Ï”ö—ãI}¦”ÊÌ'õ™Rê3¥ýåxRŸ)¥2óI}¦”úLi9žÔgJ©Ì|RŸ)¥>SÚ_Ž'õ™R*3ŸÔgJ©Ï”ö—ãI}¦”ÊÌ'õ™Rê3¥ýåxRŸ)¥2óI}¦”úLi9žÔgJ©Ì|RŸ)¥>SÚ_Ž'õ™R*3ŸÔgJ©Ï”ö—ãI}¦”ÊÌ'õ™Rê3¥ýåxRŸ)¥2óI}¦”úLi9žÔgJ©Ì|RŸ)¥>SÚ_Ž'õ™R*3ŸÔgJ©Ï”ö—ãI}¦”ÊÌ'õ™Rê3¥ýåxRŸ)¥2óI}¦”úLi9žÔgJ©Ì|RŸ)¥>SÚ_Ž'õ™R*3ŸÔgJ©Ï”ö—ãI}¦”ÊÌ'õ™Rê3¥ýåxRŸ)¥2óI}¦”úLi9žÔgJ©Ì|RŸ)¥>SÚ_Ž'õ™R*3ŸÔgJ©Ï”ö—ãI}¦”ÊÌ'õ™Rê3¥ýåxRŸ)¥2óI}¦”úLi9žÔgJ©Ì|RŸ)¥>SÚ_Ž'õ™R*3ŸÔgJ©Ï”ö—ãI}¦”ÊÌ'õ™Rê3¥|ƒ•}‹™·¬þ.¨là/Ëêq%sSÚ_Ž'õ™R*3ŸÔgJ©Ï”ö—ãI}¦”ÊÌ'õ™Rê3¥ýåxRŸ)¥2óI}¦”úLi9žÔgJ©Ì|RŸ)¥>SÚ_Ž'õ™R*3ŸÔgJ©Ï”ö—ãI}¦”ÊÌ'õ™Rê3¥ýåxRŸ)¥2óI}¦”úLi9žìÿÿ€Ã²,k,(Ìi9žì¿,Z–eí‚…¹#í/Ç“Àµ‰ÂœEÙœE)Ϲ´¿Ov×& -sep¥<çÒþr<Ù\›(ÌY”ÀY”òœKûËñdpm¢0gQvgQÊs.í/Ç“Àµ‰ÂœEÙœE)Ϲ´¿Ov×& -sep¥<çÒþr<Ù\›(ÌY”ÀY”òœKûËñdpm¢0gQvgQÊs.í/Ç“Àµ‰ÂœEÙœE)Ϲ´¿Ov×& -sep¥<çÒþr<Ù\›(ÌY”ÀY”òœKûËñdpm¢0gQvgQÊs.í/Ç“Àµ‰ÂœEÙœE)Ϲ´¿Ov×& -sep¥<çÒþr<Ù\›(ÌY”ÀY”òœKûËñdpm¢0gQvgQÊs.í/Ç“Àµ‰ÂœEÙœE)Ϲ´¿Ov×& -sep¥<çÒþr<Ù\›(ÌY”ÀY”òœKûËñdpm¢0gQvgQÊs.í/Ç“Àµ‰ÂœEÙœE)Ϲ´¿Ov×& -sep¥<çÒþr<Ù\›(ÌY”ÀY”òœKûËñdpm¢0gQvgQÊs.í/Ç“Àµ‰ÂœEÙœE)Ϲ´¿Ov×& -sep¥<çÒ½N-+mAI®Jì?[–eYÖG”äŸ`à'p˲þëê+×&ê3¥Tf>©Ï”RŸ)í/Ç“úL)•™Oê3¥ÔgJûËñ¤>SJeæ“úL)õ™Òþr<©Ï”R™ù¤>SJ}¦´¿Oê3¥Tf>©Ï”RŸ)í/Ç“úL)•™Oê3¥ÔgJûËñ¤>SJeæ“úL)õ™Òþr<©Ï”R™ù¤>SJ}¦´¿Oê3¥Tf>©Ï”RŸ)í/Ç“úL)•™Oê3¥ÔgJûËñ¤>SJeæ“úL)õ™Òþr<©Ï”R™ù¤>SJ}¦´¿Oê3¥Tf>©Ï”RŸ)í/Ç“úL)•™Oê3¥ÔgJûËñ¤>SJeæ“úL)õ™Òþr<©Ï”R™ù¤>SJ}¦´¿Oê3¥Tf>©Ï”RŸ)í/Ç“úL)•™Oê3¥ÔgJûËñ¤>SJeæ“úL)õ™Òþr<©Ï”R™ù¤>SJ}¦´¿Oê3¥Tf>©Ï”RŸ)í/Ç“úL)•™Oê3¥ÔgJûËñ¤>SJeæ“úL)õ™Òþr<©Ï”R™ù¤>SJ}¦´¿Oê3¥Tf>©Ï”RŸ)í/Ç“úL)•™Oê3¥ÔgJûËñ¤>SJeæ“úL)õ™Òþr<©Ï”R™ù¤>SJ}¦´¿Oê3¥Tf>©Ï”RŸ)í/Ç“úL)•™Oê3¥ÔgJûËñ¤>SJeæ“úL)õ™Òþr<©Ï”R™ù¤>SJ}¦´¿Oê3¥Tf>©Ï”RŸ)í/Ç“úL)•™Oê3¥ÔgJûËñ¤>SJeæ“úL)õ™Òþr<©Ï”R™ù¤>SJ}¦ôýaY–eY–eY–eY–eY–eYVÉÕWþ›õ™R*3ŸÔgJ©Ï”ö—ãI}¦”ÊÌ'õ™Rê3¥ýåxRŸ)¥2óI}¦”úLi9žÔgJ©Ì|RŸ)¥>SÚ_Ž'õ™R*3ŸÔgJ©Ï”ö—ãI}¦”ÊÌ'õ™Rê3¥ýåxRŸ)¥2óI}¦”úLéÀ42²¶}uýõúÐÍÓC#Kg¦owþýÊú«×/‡. Ü[zùzuëÞë­gÛë/ÿ9¼Ðý­›çÏ]?³:¼0|àÜú?7^ÞnÝž¸}pøèð¥3ã­ÛOè|ôàð‘ÎCLJǮl¬?>ðö‡;¾ørëÁÖvç7¯Þ[²ñöqG;ÿºþ_¿Üÿ>³º~þÒèë­Í¹±å­·Ÿ6<¶tuåÌ™¹éÕ{Ïîot?8urû@kqáâõö±S#–§Îœ>2µ6úÓáW ×Ƨnzyw~ã×…[3?µ^´ç¾;ºyôÁù‰c‹¯n<ÿn²ýèØïSÏ­¾š¼âÜ­ÕÑ+›¯·GÖO¬ž]|tgêæácíÕûOçtãúÒµ'g¿:zøñ©[/V—VNÿøøKswŸ?8¼|ãäìáÅ•ý›Kgܺ=±ºou{üÞ÷³Ó“K‡¾_kmø¥=Ö:ýëHkóÜú‰É¥Éýí±ó?lŸlm<þmmüÐâ­ß»qÿÜÐÈác[¿ßèþóõ»ûî­m¯½~ÕùRs³íï¶O\Zz4râb{mõÌíÖØÈ…íS Û^Œ=»to¥56}æL÷i—Zc'f»Ÿ;Ó>tã§öâ±õ¥¥öžÕÇO¿Ÿ¹~zæÍ3¼yî™×çÚçÖî^;;»üdñÉä›ß[\h¾nŸ<·ùäÝÃϽýåÏcrkùñÊ«“GF~?prmß³›‡:¿×>þæŒmÏ=Xhͬ<;¹ôðÒãÇC#­Ž_ÿñ½§èþòç÷réöÚþé•Ÿþü^&×Ƕ÷-Œ-Þi¬nŽŒ^Z<úôñÕ±‡/ö}j#çŸü®sÄþ|Šùµ»OO¯Ý]™_||cµµtõÉž—íµùƒ[cŸíÝÿ©ç¹¹|yßž‰öͳÜ—m¯¾š{Öš¾ûðÎâÂÍ…{KçG˜èÔ?°ùhiusóÙñåË#[+­K£ÓGöœ>qãßÏ8µviáÜÒÖóß-.¼½¼üxâøž©ß÷Þ95~O{º{ØŽÍ]}ñôìÒå Wo´WmµßÛÍÉ›ë¿t^¤Ûí/VGG÷?˜¿´ïÐÕ¶6oŸ<×^>¹gòöñÖ«å©›wXýáðþƒSÏ·V¯½{Š‡oòÛ•KûÚûfÚŽÞtá·•‡Ó÷þ¥@çõ²vüõÚäåë­ŸâÝV~¹¶´µ¾¾<õûÝŸn¾‹•‡?ï?ù×ǽÒîGŸN¿}ïí§uŸ´;ÉßàißÜ÷÷þk’nÌ¿œz¿oçC½ŽîAïòÇ«›ûOÏ,];ÿüÈêæèíÎÝxôïtºüù¡Sÿìƒß½ Î™ãAkùñþÛ—Ú+›g;¯¦ÖÉï;{øybê÷#ÓWÞîæÎþs£íÕ'ßý£½6qðÐܵ}?þºvïä¾ñöÊòü¡ÎŒ}â ìí¼Hí{ó ¶÷×|ìs÷-½özïÒµs¿ü¼vûù™çïåêúÅwOñ×w?ú‘Ctð»•'·Ö~»x÷öÒöÝíïÖÖ×÷øp7ï=¤;Éï?èc;ëÔû‡ÛwnŸ˜_==~åAûäÓËoO[w~ß>xéÉÑ7ðØ©'çŸþeØ;§‘•[C#3?_^?Ý^¹ÿèlç—·~¸tñxkãõñ¹‰{ç/>˜¼µµvaüôÌÓã­Í‡{–;}ñrmßóÅ#KW®ýòdæõñ³{Úù'íË7–žŸ¾ùÍ“vÏc½<ío“KsG~úþÓOûxiíÚÒìü•oòûáÍÓ“#??:9vþÚø¡‘ñ#+¯NwžñÐùÑÇ[·Ž·~8u}ù£Ÿø‘ç~{VŸLìYÝœ?u}fo{{eéìÍË·V¶^m.v3Œ®ý|ÿvçš{øÄ¥_¶î<9Þ¹`îíœ4žLόΜ]»}qÿÚݱ_¾_ݼºùbuãŵµª¯MÞ];z¡svºp~ñèóñ‘åÇ®lw/˧nüÿí\kSÛFýþ*34Ibß÷&†IÀ@Bš¤­ÆVŒRÛ„äK{ïÊÚX6z¬¬&£aìÁöJW÷œ£{¤ýáò·Iq<ÄØ›ãf£»÷ÇÆÎO?¿ÞžÙÏÙ‹ÉÈgâwÑÝüÙxv2ħ¢CÚ{·ã=ØóûṴ̂Ѽ•óþÃ÷ˆšóåí)äÏÅ‹ï°uÝ{øpCõ]àå•ÿ_(õü×÷ý?¾?] PÄôÂ㨖D¥”üÕ9—Ç·H¼{þýÞ–·}ïñ£í¸Â•õm¾ZÞýv»Ÿeñ[]óØðv£,Íe-]þÖy±>3b©$P/1X!ûÑÿEJ I.ùÃçC,©ŠâeV«f¿H $Î%Wx ˆ%@¡¼L$P -ûI ].YÃÛBl¹ŠçÅH <öíK C.Â[El¡VÆË"C¸2^lI +beºñEXíYY¬äbA¹+ÓÇH „š\˜’æ’Où+ÓÏJ ¬Ž\„Rå’Y–+ÓO$Pª³,ô¹d‘€MÄÊtãÃ(éïÚEÌž2"–RÖy)ûîh–[Äv³"<ˆ%—@1¼”}w´H $Ë%¯r#–HòRöÝÑ‚$"—°ƒØ](š—²ïŽÚ—@Ú\²IÀ"bK$°^ʾ;jU™rI-ÛˆÅK`•¼”ü´Ú’²#–F…ð2'•Ÿ•%?­Î/œˆ%”@q¼¬•îÇJ~ZK6»[ó²Vº+ùiuV XCl©VÁËZé~¬ä§ÕÕìÁjö`5{°š=XͬfV³«ÙƒÕìÁÒØÏ*jö ]ĪكÕìÁjö`5{°4öK š=X(/ÕìÁjö`‚ðbfÙ—ÕÖtoãÍÆw'âŸ_ÌÂ'Ñ*&Ä9ºôZ~Û9zä˜ÅX¦VEÑÎýÎùËršðB®f’aI¥b„s­Ì7˜`­9ŠHθƒ /äœ7j̹q˜óÔyMó÷9íÄŘ ΩFˆ -g“Rír„%c”)Á+ƒ])±VB"„ s6 ã.‚ …‘âœ;‡µ“ëµÜñYaB]¦F‚%Æ+ÄxA½~„~½þÈ~ïÕÍ\D9(WR¦æ‡úagp1­3QJ0bÅŒîü|´<Ë!i˜$‚ðùáÍîÀ3ß;“µjÆ¿òO.¼0èÈëyWýᯓm8[ga`VŸ9ô‚°3·Õ/l_ú½gÞÕ‚vˆ\„¦v8ÞÓîn£Õº¾Ü”Z$“®æˆPÅ”’\Æ‘kÆ$¹ÐR+‚ùZÈeD€*•8MÉ-ûú¹ÅNãCT”"”*1BÎNøJ–ð1ÀËQ p±BT …Ai -ƒKLQx•Œ0uµÐŠB1Á”Å)3æj" ð Raz~Ìa̘5ÍK4h>Z?›,Ñ4ûM¿5ð ç ˜n/ô{M¯\÷ëu:bå üä]íx–ç¶>ýb')·mv¯Ã6ü0Ü:Od”+2[B»ÈtŠ5f/ÀëeÐŸÉ Ź.mjÓ°Œ#\¹Œ)OBÃ\tÔÓ¥#S<š–|jmM­ƒp07ö wy|íµ{^8˜,ĶÛíÆÖe·õ÷MÐ÷—ýT‰L–àˆ¹%í`ü™ž@$”l„4§ ¶yâ‹Aâª=ÙÒVéŽ*2Š*²«Áoð¨nq‰°Žê1×B»THG–lø‡£ú;mÆtA¡PÅ §TA/†¹<Ôm - PbÜ.™„b)•ql‚¹`Í0 £ l•£a­\‚'œ(|gÇ&ˆ»æTjÁYÞ±¡!pèVàÇÀh¤“¶lèïÐá¾;­×ÕHCæ˜Cœ¡—b81€^3 ‚ˆB´0Ã.ƒo¥%vA(VÙUŒ$§|ÑT%6dß »CCæ‚wÕpA¦ R£Òœþ.?“˜ÀE O~—Pø•p¦¡Fd®Üð”;Cp-‡oÛ30F¦®pÐÉŒr£çÏæÅ´[ÕCL¼×t¸DƒbÂU­@‡F'cøÖ Ô‰@f‹ú}eЊ1h³ "‘Cv WìHk-ÿ×m«q€ñ©ÿyE€yÜóý³°Ý…Ñ'ØÅAØö?Ãgn*~·÷eüšŸÙ®®¯oŽd£öc ªþƒ‘ó´Ýck×Äè]òP4i¨ÌPƇ»“5 £¥77këëG^Ç?íy`^ëô½O¾ã…¡é†þøÅéôü>ÄòþE÷Æ|›Œ‡¯¯ï?\ûG]C endstream endobj 8 0 obj [7 0 R] endobj 39 0 obj <> endobj xref 0 40 0000000000 65535 f -0000000016 00000 n -0000000144 00000 n -0000056614 00000 n -0000000000 00000 f -0000072742 00000 n -0000073131 00000 n -0000072556 00000 n -0000278138 00000 n -0000056665 00000 n -0000057198 00000 n -0000057961 00000 n -0000112234 00000 n -0000071478 00000 n -0000070284 00000 n -0000112121 00000 n -0000059203 00000 n -0000065140 00000 n -0000069018 00000 n -0000058026 00000 n -0000058642 00000 n -0000058690 00000 n -0000071524 00000 n -0000070780 00000 n -0000070330 00000 n -0000070723 00000 n -0000072486 00000 n -0000072626 00000 n -0000072657 00000 n -0000087858 00000 n -0000073433 00000 n -0000073714 00000 n -0000088131 00000 n -0000112308 00000 n -0000112538 00000 n -0000113927 00000 n -0000126308 00000 n -0000191896 00000 n -0000257484 00000 n -0000278161 00000 n -trailer <<26234F01CEB648099183139963F89171>]>> startxref 278361 %%EOF \ No newline at end of file diff --git a/docs/source/manual/fpga_verilog/figures/verification_step.png b/docs/source/manual/fpga_verilog/figures/verification_step.png deleted file mode 100644 index 7512f10291fa57aba6a2f0c373e52ebdce18411a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 183723 zcmeFZbySq^*Z+$M2%-X_pfn=VAT83Oba#Vvcg;{D2HoA=-3^L#*U${zFw{`Pz`6N6 zzwbKFcb(^Z@Xxc>d0Y#YYvy9+y081X_I~fZU;Fl#mlb>bkmw-_3d&;%@sElqD3}%~ zC}^jc7{DubSd$khDEDa0MMUH+MZ`pGt!y2X>^>VB3me%Q7%Pf>VCCU?hk`;IZm6%X zD9%XR{rRcBe)j+a-9ragML)kFMSbtzZT)RMZ5?gBZHaN(+KVr67u!+L-V#0cXzXmm z%wXC1+W$MM3%gEHF)-5Sgq&hfmYJ6Rv%Y>mN=0;^)I*_NG77ENbj1C-C0&TOZ_ZE7qZH0rl8o2i%5+sKsjpuk)ECX z{f)mD1*W7TgQQTEqnEI;f(cmE|0~toEXC)1#}RO+XF-pG9=&)WwDd#)$%9Lh5@cYC zYk>I$H<9XB+hS{!kIx#;hyJWspU*x|?%%&r{uY-&dEfVo!2R|Xo}TPa#`1_;WFJuzz&;Oa{~vPxc8ll?hFVk3KHrI_n75eF95Jv3Y)l)HbW zO`tDpd171r#n69-wBb?6`-Xf$LB*uF``1VDFG9hX!j&)oZZ2Ph9$)_52UFlu-b3Yl zg*IUFR~Lx_4Bou|H#4UABJ>RnbKDP?BT@M8mIQM=4d*YG;&uq$Qwn*i55ra%ci)!V z?*VHkgzuDOT&zKG$G)%yl`>5m5UiZjl zTO3BHn?KpR!-Y}gpkF7P3s&U03@cc!s6d^2>Ipx3d0?Y*hRiUVwqsJ@+W0h(@@Bal zuiFtyf$s7E!w*p#9p)>H)fk@5^}mvmB2K6`aw81%FcWv%ku$&Rum8&g2_*s^GIL;; zo<88;*7?7m$p8Bdpz2&7Zi@JS{$ngt04LEx)@vt<9_JB8fPmY0`*E6Ecxd>0Dl zs%g(-{D%1RX$9nEzA z@+|f-F;{nIFmZNH%^Q;$Q4!qk?(Q(WcCqfG=L|;2IWmfBdJk@g4^w9Q2Ju}OO}BV9 zGLBi=g+N&=EzSKN*5S{wS)9;LH=33*>NcqtSQgj|Q<$JFuPm<_@q4MMsP%nCKhDY}iJ&R3GBgjQdTTXhw-pxgD%Ja%2iX{n_%l6SKUykc5vi4* z4?(E;%M8Yh9)T(hH;!dk?EH9i-b3lIurz+E(;ro0jr%q6XnjQS$$VW_m!X|9r%6&% zEVt>J@rXYsvFW{QP0@_%u6EgvV!MYQ7HP4Acxq~c_CP_>dQI{%R#19p=PO)?C|9gR zoJ=9u2}l0cf{MU3xSDaQJeb)0b5(X;Cd)7rox*m5b9lT~ZFhgfo}z{Z=!D_ZV_eKg zp>zgaMfbC)TD9V^dhk_AJ|!-lbk#F_bW|Q5o`{@DXrE=l>OeAYRz}9FhZdin<6?#i z1ze*<%rqxjjZ!pDR(Z$9#{SIJmlURWh^EaP``U#rYIIZ)^x4!uDmwa;aB&_g>Jq*< zBYoKACHMmSOz!tjNeKli`NUM3>FMdClaoC~6_w|l@PLkh6Iodvv$W3i8Eu7fWv=If zZh0LENqWPFKk^=pFOJa2?6aBt&*$g=K1u)Qe@o8+jn$MA_2d8X&i{CnIOv4ZL?b!+ z@31rf+V{U1fZ=ZZbdUc|!I>!n`uB}_oX_7)=9?NY`~a*-^fzZ2lVUgv#fL~{^zHq> zn+#^D&_Mxm#TrGe$bAm5rtEnIvIn=IF1mIqKVfsYEt-l?QJL_|uJ^X(SzJhJ zuSsJ+-q^&yw)~VS*e_>4@qK z1q$lvHIh~x((R<{`NbS>u@$cjStfkDjvd$DC{6nj5nx=9xG1|}1|y$2Fc@*q z{&B(+=5}ph4brSJtr}bcln0Y1jf~Y&$!Q#Iu%JR8O1`1lH)<>DC{e-D%sI9OgKFr6 zZfp_}!J6E16wCs;PvI|gmDFe+pO%6O%b%fIe`p(PwS>=P>1OV3LahyI6?)bm{^^i# zFvw8Wg07#>Yjeg}GN2+5wWO^(C%kXEU^Nvhd8Ur&5fKqty0#*ds%m4ciGiIC)}j=w zbP6|Lq_S#$T+f@h5Iwi95sHAre!R;sDX}?V_F8q-cjSdaW{=MAM`*ZyocGj5ef$~^ss?1~-)D$ce82p3jY5{fCI1jr25Y!A zF@jQ170v16<6JtDRzu^^(IGcM zW)N{7wd5nBIekm$G0z`AA}oa1c$rXe){}!C3^ffV&&L}!1 zqt2k0l!a{8GaN79>I-#)jP8~vdkTORJFS~EcT|2z;yP!4lzMX|X4AOG1v=~?L~fh; z5RD{UpX3Vm7>4q{5xhPO^(|1>)g_A(!?@Gz4X2>Hok+MZAgcJc(E4|D(Q-4XSZxP?r_Eu?Rj~Wn ze)M#uXrO`kF=V-O1+SX)gYLYWVBO+}0YQNrei0ooHki}@a!nnd=O)@vQ{l{Z` zL~LXvzQb&Ichzb0>5Qdpv&-%jrA~uWIIss)2YEI~B;@39ET8{2HjlO_2HQ@R2@??! zeW>B(;n6rcJ{B3I!oSnU`91epzj;c=z-04auM8!qkGWAghEe0`xF+`KRGoe1)vl>v zBx1i&jxp~ycQ7%x?DgejYcFdT$x7m0Q|)3P^lF`3R$pwf-!#=zwWML2o{pJ0rgBI? zYrQ{yMQ7C)M<8q?#eI<1+SaO35V<9{=JSBe&Z|yNS~@D6N-Q8+NkL%=O>+*kk$i-N zc|&cR+primdT3IedMkt?&d=R0*122hs;jGI5t33;9bd%(`Hrd<&7@UBqWV@`{@#P0 zvOTEd5Wg&sP7thahn*F8)zftx`)rluNFau(`!LIpY= z;LdJpnk%&@Xmc2o;qH<1I>K-Dc=I1O{lDJuQO5^FRe6nAH9yy@@95}r_FbfuTCQjF zuDrsiG|5g8LF9r9EPhKj$kv;Upn`Jqv79vL($;G*4GI-6FRxjO@f0mtZjwWQUf7Sd zS-+0sH%SeW{A%%m!&8O{%fXW&- zYe`}0`CWU*Km)K?FBTdNJA<$+l7n9zBA_=5pz-dFRBvW-&y#Sz%gs!$zG*5T%}u3T z(dDO3A+BH1rJ?N!pD@S-P2~}^Vp75=Xq z{2wpahl^(MCJ>xhDDYe$?n|&LZrrYz1M$-5&EfO~4W%4ud$NwDfnja}@xy7;)C&_3 zG&d{D4eGwYcRFP_C{A>|F+_-ca)9PPMI~mniYZsomDY4T$eSWz%7H^X-OfF2_N=7` zRifp585_!Yak*2P+!b(clHzgH6Ooj(o4Cg3@F!A1RdqdRbR=Ji##Z0YRJTrd&z{}; zqED}>BiZ}%hY%#En z!*N}DqnzqK#-p(P`NqE7RIhD5uMSicn^vTYsXehX)xjZwYHCo>qt;~)v0r5`3AB&8 zsY1`045?VvK`lL^rI^z7_4QsFdY&UOBXC6EmgTLR%J^J_Qo+glZp}Y$g^JF+wFan)x$c zzm-W9(6Ktfl6%Pc@RN&6jcLsp=Ue%9ll}zGK3>5j+ZvmB`XRxaCcSVj@K0MJR)cR- zE812kIPBg>Jv7wgnA9yh-@xRued6P@qN8=^%Yn&aLmpR$T?V22l$3Wmwmk)kbbhV` zt_RrPh=L7}m+%U}v4s74=0kn5HAeH6!3+%e4`EO`Dko#yAJL1v!op)_Jxxt|Y(nN| z*iL2R^^T@6zM;f6JT6A4s6D1M6IKu=Bh`3SFxj$}sg}u53cqlmYzm*oE;KyLPP4|P zxv-!h2~TU+{yi5_-_cs1$bKu;p}x+Yb&0fM%(oe$TGL9!+C%a_L^VBtOXale8^9Vb zBj3EQWME*RAH#0#L*&y#7QZBsDH?p#1aGl{w#*|Ij&riI{v1O<)j~ePz8Dv4-z^O$ z3RHwYG3hqFqh%p^xm0B}0G_3#1*}Yqde{$Y?3{~1*A^%5)3 z1_RspZuO_Y0%+)QSVp&USw#ig>W>5>b1J)~rlzL0qO>hw9vEw!&-3ukG4Zrm?U1d= zJzF-TZd}{=Q?n>QRk-JJTTM~GT5nohFGrZQ0&(2tcCFIhgDT^n5OWXtiZf)8l@At zWB>(4L2&RnR1LHmF1C8Hks5n);?&Can+I?EkmYm}2OEOv`d`a=*hL_ zbw8SKaK`SfZ3}dVHLLA6>@eFm&JVJAUyh}&p0(aICxN%~WrsT_^}Lw?PvSV-ur!GI z{6K%!?R@EHdovx@?OmV!Y6M7=)vesr7{JsC{sa_CcQE(DW^5PQoyrz>2QavQF3D_6 z5Rg(}->v@f$M-!7d7bHfbIoS(2eb9n2Xu>Xy@=aw&K%oktCOhS;!%IHGdYBXRwkD0 zaecAD1{mRZcGHk4vnOi0pzLP^1Q^AWL@%4&&s?^js-FUaY<4w|hzs0(b#Z)Ojc3*A z9TSy;Ep|mYNlfJEL7qWj+|aLV1%-vkBgVzw+|zlFZjJ?SRub2m^cr1? z0WIB&1&QMWRXA;r)0xGKoBZkOlF~<5Aqaw4%|~AY5mm6BbB*=vo7`lVgeEvd%j+VY zQDhC>rZ78Zsi2nMsU@#g^6TZDDe)m(i1+bpT8p!mrsk|#Z z$@6r2fdt#&e6sa=ExCsKR`o+q_G+PIN98nv7d>)+*e`z+v)eB4VLcySR8wb=Q=bJ= zt%Oz4Kp5d4tbHr>HWZj9wH<>Wy?EP}4+AXFQmqHA6t)Q+3(J`nb5hJwUM;-1?b%d} z7`dl1<3T6)w4o~_?WP%w7yAB??9N+d#su6dO(`zPzJY-OXEJm)yG5aRHsb)klVK5u z%dK1lQd|JAA-Cu6Lyi7gZ*+y(NJbjy){1a&aG~#YI&koVHgDmLvX>A1g3s>pa~RyfvE5=60ap17wktl1O^``=fwUzcp81 zN#cEV=m5CCz5#A1BOuFlD|fb-uHHh`hy8-1@Z$Qr$OeuoQ<423WhuY|t-&Yt&fK%4 zWMpLI(*kmnTo*uH#}EV&vlPRD_%?Dq(N;1qT=g2*vVJgeVx2~pXl&-XHZAX=L>z&0 zAs}L|Nz;Ee~Ct14&QSQzQ1-@T4S0SPP=$7&9O@1uS`Mx54B+pMCJS zDtyp&j7&`*M4z|bAX-OjZS@X4lfIj#d*2|S4*jfM-Q?E?59CxMOX=w7ZsYxnd6*ZC zhK9y%Ai}d>w~{57D!^tn^)~nFHfGzkXRMubkcwQJV)Mx_Dw4MPhT;Avuo>FmT*#!P(tkWgDg7@O%PLYE&u3hlH=f^at~~x-~D8hG9{dIzY!*2 z+Iq=U<-NT0lbis1RlV9mC2xB5ZtcNJiJR-a25tI`YmiaDSBQ7|E>cOBr=xU)0JF?~ zMU-a9i?0@sjDS&n@k+=hQ+6tJJ6x74p)-lc(P(d`dT>SP2M+muP=I|NnD6?0h0S?; zT!e|h;X2uMfer|Nyt_e5{Z(Qy{i%X_CWA>^XzEida*R{2I5|0!5)Wf06s2tMA)p@{ z7JO!Hw@z{_bH&%@Kr$sQr=xBfPNp9skK^ym7BAtyaC0qoO4fxM`QWTB&`7yl+~n*v z+SU5Q`XtV-MCYEeNC`~Q$)&V@Ea*#M9&*)j7c9D=myp~hh}%nU*@|(Czha7YeXnSH z-od6gs!&Jj&exqK5t-yIRtatf!=EBt>w3uMmt7jyauRJGUQ7l1Mw?Q7`S?nnij`G? z@!-$(r&rbEY-Kj#C9aF7@uGKTTMSeHsiHn#xtFGM>K9bna@h-N^5g%oS~Vu?D7sum z;R_T9Ht^_GR8$5Hxvl<$P)o(Qsq8if6MgQBxHF~!42a*Y91f2#;r;x4s=_$%5fO)Y zZ*OnXq?{m9d31C%VeSY2(gyj>`A-kTEYu}tYpR?|m`KKg_3q5MADNG4ek`+ylJU0p z7->a9lG2gWQQkpRIO zEvpG4=H5>eCaRIv)=tuP8u@h84D*x&0^8MIhSDmlr+0eQFG5k+Q>Als8a7*?07M$6 z%&^d7drtABowPtwcsx)vgzF50pHDTq+R86+p?+e_0#p__@es(t^jg=>znO1zJzGa_ zDNl=Tgtg3KjlSRqSK*f(b`sm7F+-NO07WRN2q0LKrXAgw<0_ou9VrcE*8S086gII^ z1)S$J%KFMvTpxuoNzm_Io+${KM&jZ71%}3cJSlw?z}LKiP;y&#YxokG3tuVUp`vz@ekKXmb#G^@mIxsVQt=ZAsWb-y4a!uCFiWyx!ul_ zdNhf(Rxz~;9#Xx1eTKRARax`cb;~*qd(M;M<{I@)IcI5xYdF-i$Pn~42D`oL$t_RV z`-@q@Yvu`+Il;r<2XEe>l{76&_Vo6| z=utn>^uIzJmL_Nmjt4a~VC2{62h+-;S$sSkG&i4MeEUn(Z3+nJx?c_Q9ZQ!hQ0K@d z(-Z2teeO1%`LGUT-e>{E+RS%zb$qK1FOK?Hl2L+yq=?x!Q{OtLEZ?~~?XB6`Qf-%M zV~5je)4niDk(m!;d4>#FYAWh*Y8-}|_BRjBdPcX6=*_iX=nv~Z)HYTeMC~a74G>x?~&~HLd0$w~F-m&KOnv8A) zUgtsb{y4R1v<+DJ8q@G_2Z{)V8lS*ipmYY0(ppBdGbMJ;`sU{Z{mXTv9F@}sFKZX5 zil&rGeTYa}wJMdyjY$p60!8{uc>D1(ri3Vi52&5DBtD^?Z0I)e zw9za#d#7}}RlgJ&s91ROM8vmMi;xVVv!$)0V1~x-B5$i$x5rAvZZ0WBiOq75p;?-0 zmB$hDOcEI83zzi?+^xz#v>^)1Iljf`h$S6gC8E)2aiN?dlxi03rxk;eHc^K@gj zBF2`()+d|R3LVW+Nc398NwUtUqgBqT+3b32lgL~T^Ohj1nCjpZ6)!}&w@u1{x=<>@ zbv_{g9M5l^ST}dbc2K0PaSUy)0BOWOYRSr6S8jLpAKoR*NHFICLs4J|EzdvetEmzS`C5+b-%O3PBJUzj@7Rv*ez;2=gXw2Se|gWi@AIh{;Ys zq|N+C#m3Uebe#xlSCDD^@v@7vbj$+e>^jr4R2{LvT@8EAb;@7Z3SX;d*$?ASEJz+W zX{AtmD2uuG?qIpI#nd|art`^kd4u8)-8lsd3nHUKZ^_lT@Rt!#nosL3Wg^S?%UL&B zBwNEH#d`u}53J`qY++FY*nM9~b}Xt`EL*28oTHUu1#(R#YALrCuFZCs-MwRrQkCe1 zJ>@3f&hmGtR4l^pr!Ij?w>O+^n;*5vBUDWb`+SVnim!}9izZr3Qzx%hh)DUR;^Zy{ ziQ*9s{;OOo?7JGFlMuY_9*M~Q7GmQzdz&p~?bpuE&c&6KlELQpHXNH9RoA!dSvKN~ zwwxV))Qw#YvAbB68H)8>TD-g-;L9I({PmRI2L2+M5*?`s_tN3E}+^)3hV5sueW zSsOU~rW*VWPh`eitYQ-$Hg}E~x@zVw8qNiT!e8WW1l$8Zk{%Qf`(C;~>?jG}l zV6=f#o3g^!^?SPlEHPl;_m;oLz0~wXeL*2~kD0&3Jz^zqBNPN~;_fn(8;t4zGcV*@ z6Bq56e5>o6t||C1)V}>rl2lGQ`p+M?>5@rl7um97vCrPsGFEdx_I$tF#)y~eiak;a zoB_{2u-%KWX&=oV^QRkhvF`B#Karj|mbO+&_PE?&ib&XrSpo-F%MYDt#l-Z{aT7{* zln}+AjXv3O1lK&=s2p8bl}BwIe6DG6 zAh|-KlwGc+rgD6OyF1C-DpEJsXq!{1!Q(pCU}KJLS?fYp@94#Jp|#;*BY0u-`0}98 zsYTb+3%<)`ZQJ0OOy}Ps#obctGF^WW*lHDB;2GRi{~5YXThFifr+88!F(#_#RdMLv zSdgs$QHN4TghCvx$j~0Gqu%6BN^rD81%#hkgvHM@BA%d>eo{bwIqBHJox0}{r&Qk< z((`i*qg&(0n(gZE)x6YQ77KB^GqB2=*;i}S)v}`A%>s0z*QOg6$>4eRjf*{pIrHgJ z<1ejyU%g|yJJF3TAQJC4TnR=j|Evu4hc`)lQY^i>cry#x2(`tVA7I;_O*XFAJ+>L% zK`ucDV;?oE9%~ECm@WpI1{j}p(wh$(@4f$3GPyEdu6=eKJ7ncpl8V^j;nyv8&Kwk& zs>)I>9dhXrLh{WDAQcDL0wYeq>ajb+bxFAb>l{0Jvq>#{E|TfHkez_p{{Dk7kOs&}19eDXB+^#~`(&?F+Mbfy+WM5EMAe_jcHQq}AyBgMkKG$PD+;Y;B znMh5?BrK{M14N2*)Wq=1?n{k?Q%e}UT6J)yMXQwDbgMk4R`=M2ILi~3&or9$F0*9! zb)VoY6Y75gkpDp#e&Ec)oiBTnK3N*~Xmn5(1Gj+wLV)hL*A zjP(wKarr4bd*5FAWRt*Trin_m9AZ;Ri?oaKvibMr0%T%sG))Img8p;+?0fy?1^u)< zWo8LWRFw41wVClBNl6LWm~p(Ftl2M$@o)3wEi8#b%gaYwPRE}hw1+^T#uRt@Bv|v3 z6(Wx=nOY}wx+QhTE(WS;T#x_swdWrUx|v$JH%aQWoD;#&ms*zYQGr6Q(U;-(x z69#VN@p{oS7)yMzu=$(YrT2j0n7H+L|K>=4a#T-1i_!hWYvWui;5B8>aq;q7uHg_u z&enm;dE;wKUU*CO0s{p0<+1(GTJKlCOg;5W7A6;ppy6F}>Uq$1{3Mvp@riCn4k()q zT;LT8fw4c3Iz_IG3EH}NRldxGHZF^JaNB^9;2sO{2qUD!X>h7j_nKa9^vNZX{<1021rthAIaO7s49mBKCKh?h!Hzk0Z_Wt@CSaGui3_R*30{-D z-P8s?3p-mNmD(I^%s4~OOe4?l$L{(%&L{h!UrNvJ73)8}jj$19Z!tK**1=FP+1jZ{}(E z;a$mNwtG3;LxsuU(3XIg>GMMBCJ`q#||RXBN>Lp?HW#xr0o7G zEUGi6(U6?9ziV757r*dM54lAqvAY;CX0u(uT|X)1e;wvHdUlk(5ho3Eh<4vGihi$I z_N~i)Ho<{st1&r=xC^r10zP&*IeYk;ZnkyaMNzw0&`b-ZGoRZjoaLte6Bi3pWI9;b zW=B2jOXNw;#pJe*1>vd^XWRNjG1wcXZbKo+IvouyYH*00|zaQ7{sCx|D>zt zko&pBJ{DyEKA}oY3Hqkhq^IEWw(O|o)jhFFO+<$POOezvr4#ni)+givt0nv>Ju1GsZ^1fN@T-Nqh- zJek-iPB)U|ELa*TywL{g93&@5Gu_x(ImwxM(Hv7|8NtS%6Oyb+n~Z@$zLIlOahSv= zJv2#>RJY&PydV@z8N0THb2>drIl^Q+KkLR=Me+R!IMVnE{)r7XPdW$>La&$!J;o9c zP8uXM<>&U`7alN>(xW#XP)K}NXVIsl6|Z$ckrmAV>E_IajFJ=S8DVgN$DPm#m1sNSSc?P!$x^C zw6l^UZshCsoB0JD7jyj#^dCX3)#AQ496}#`pFh|PU2A68GN{T z=^K2He~7y@8bSJIlhR~ob3fhZq|oqneh=TGh}_@YN866EX#1UC`&=?cZZSXFYA^XL zb+6)TeqL7JBGo_M;8z!couvtNB7O4E4~H{A@JFQ~H1IU)P=GvvD-{FgrlM_F?tcimsf-sh|^Iw)gy315#b8VKa* zoDfI&!4=mlONbkILmV4pX6q?N%dJH>Mt{`#2_^b^X(dmCfM*W*)Z2PHhuhx zXCGIIb~6xm+NfVADlp+72h= zBFo_os=E+FZaTkWlj=&y)W*!UoxyBDZ`lJ2bzJSGwkJdkQJb#cn26j2 z)^kv|ilDVkdh{a7y)DpwcjKSqn`Z<5uFlWZz{jQG92~l*e9^SNo)MmP>G7nf&} zc$K@+1FUh{aY6a_TQ-QA@g^fr!|ZFyuk-l+>q5czT`>|TnPdKnW(0(XSvm@pMHOzy z#2s}2%vhn|kb={5jX>vBP2`w^=la}toYSc5_pcJ4unPx#xVM4F{C>H+GL|LQUvOug z&#Q_neM{kfMj4heT>_d@REJ=QPFiSF?NDp?cN}=suS+*Y_mk}g9``j{s4iusld^yM z<!}WQ4|qr&xwA%s@S*-nUs6k_mCq#paJRViT?2& zm0L5bAG-_6#i7T=PH%VdAg8`ebYOG~xB|2hqV_(Nxe6)vAuSsf;_0$u_}kUi%mc#@ zw$Z9e4s98`)ZpcRqil&gJGe~m>5=~~l%uw*LChUj)ba9OsDOFM)4sbYh<_&c8E7-5 zdgS-)&Z5(QT?&U7kO*6+Er0Y^X)_9344-drqlEE*W`%EUgnAy7g{s97cvPa|sREt} z>hzbDhS{VeEHaPp%9Bw*ZXsAWKZ);N<64Z?EslvfT>&6kGgtwbJnX;Y0d_6?mKK?| z`0(NDEv@E&gingq5s0xvkjG7u%#x^5pi)Xr;-{_pWxmeVZ;+<(tv!AzF!S>_@NMcf4 z94+^PTV_^Hj;OU#43idta&2#~q(-fcj=UF#=^shnjOn%U)3=l}-zFY19V9MfP@TKr1y#JP$2C6{s;OY%d`IVl9LjW$r3~av) zQ`Xi=fDn@~HqO>dG0gxz^cHB8)&!AzUzIb)9A-=Z>sbImVF&moW;Jd5?@D4r6~%`{ zPw=5&P7aRlujtsTnV|v^(F|(60GLa23oYOC8+5s{JErb8(P zKFpq&#`2c47$9K}Gh2<6#^fa_9eJrgfiNpb4nb|HD%Q zJSq+&1nYPCmuGSToyetpt)i;-F|xlPCd}~sLSq6xy7PMh72)1eFE|5@_)3HnbNE0a zH?J@O*Y-CW@%9;cO--T-I-qjT1~5FikBBFJ_f{$}SSbg6`_}39lKAsd!%j&gV1?cr zy=*=2U@=<$<3H#}CMR%_wL4SYm)|~9ZJ8|=N;deOpzL#|^OQKbPo}9lQUo1$Du7^@ z6(FK64%%^iQDx}q>65_G(b3P81Aw8P?rIy)vxQNaj z47e|SCrk?GU>p)1c=6sTk3Hju%q*4MEidc7BSR2&xS1IW&gHkgJl$ge8varwV1*N= z<9P}dpW88RTQF~X1{Of4CctTQfO?)?tdVa02~YSo(-LfF@Qab_)wf_}gUrD59H2kt zU1q@fI%wf%-&8L#yGc;)ly8gZ*fplg2w;2jYQpdC1|oS3Ktu`Y0*0NBS`}W9fmZR# z72fdCQBjHH$Rw_#$$xt|UZ%hfP}{9;1g>8n0Nq(lW0_*1r!BSIsxFJ&gsnG7SaaW0 z-b*ebU)zeVGPE3kE#P?D7cUDmF7&3p{rpv_>-X;u0B=W0-g*RJfDei%M}zF*zRjH0 z*{>vZwK(n1O~1^4J#J{Ui^rL+_%3+Q{@sAgNSRAKnaVld z8UF#Ci6>Cxx|Zm>VC(V7^&GP)a+wKE)tM1xMEBigxmUN~BKx04R~T7URB-QG0Nes? zWydxhlX;zg=2?qUY8pE=*?|-vT^$c0*3d(|ZjXfN#}EL8*0}X!w+9LMcV6t+$5eYR zlM3LA2KPu)o^3yX1Kx6Ix7`cQH`C$TK}9!In`9S%91yK+QzlyN}U*+tommWcQUF z{L@s!{m-qVS&TnvfdHf*lzcz2?aKqL)`o_e(zx5^n_0M)+08ffv?RbT4=UG5f3T)` z%c9KdL9VsZ=#drdS?ig1mOg`5C-_7~l%pT;n;(6to$o}$fc8b;9-L|^D1={~cM0Aa zeVWx#Z-6>m1NL-j699r3^j+qt6siJrI*gD-mn6zH?-9ah7FBc?=+mWA-&G)IH|am< zJL^C5oC~0ViR>yQZ!|A+`J9>yUPEJ3r3CN=v}786hht{@cc)aqQ~+>|t7uke)~^HDf15wuH|Y$puhXH$xvlN~zn88l_--ZVTN3MU0jWS644ci| zyLf;hwVJDAHQNKaZhELf`Ch$z`EsyH!1EG~>7O1W%OtmDf8pbV-mZzcgY;5di7|=k#1T+%dva@`M-O9l{q7-xJ zWI+Kv4ep;v(=i;rgD(IJyEj#ySvPOf+El}a_axG;mJ<@xz9(|$lOceHhNau*I=l=q zH#e`anSXzAOHrm}!vJu!r?>ZL{cLYm?cu|RO=E|LhYmN$Gk3u8F}~FYnwKg}28iTh zjvK6Jxp?-f#;!z%;`rTbl-1M_Otn_iY~@`=0H*qnBF6DP_l0^S=H=&O^;QC)OhejL zTT`34)-P^(>bx)Q-Izc9t(lcc?;n4d;IVtjz%bW&`XajRnMGrbbl!iBWKo`F6ilL1 zoxzjE@4Y+34*BOkZ)&9d$Oh=opGN>ld08AD74;gRgiqGLgGv0?fL6J|#-NR0bnmIn zr+`O{0es9PD603%z59Q>NLp7yc=-bTt*Mk7fQBn-KpKYdtiPbqr?QK$ww&yxN%eRo zc>W9L>g&DJy;;J#yj{Rjve?2Fnt@}CXVIMYJt5vc>y6ArZdKKIDTYjSJF2#aQeE(U zerqi*HYxqK;I*F)kF{I#U8XLJ<571$&sqfTnaNP~-i;hSxeZiJ%s{A|R^J{HqptvX zbM+L?23N)}fb_z-Ma-3YUz#Q@p=p1kKU#~`a}n*uOftQ9A9#jC{Xe~e{{)wpZa^3b zga{zQ>c!xdP{9RW((}$j30pec zp{;=VTR1z-$#)?MMlPwbCi_LZpw$GL-)T-3%>V{wVw=yxF3=~^ecKYe^!vL5p#0un06IP`=mqQV{6gb@ApY0&w{1Wd+;eum z(s6HpfymEWO;bRkKm+NnQ7F)DdfRN(_9@96TlCh7d>G`N>yJqwDI!&cT_X{^Dm)jf zk!!$HJ}@w;pLb?85R`g8;cW%s^QPsfo@ggxhnE6EB(uf)9=D_3~Gdq{J-;t8OekpMMF1ho7;cTe)C#vaF%Rt*Id za_enyX6h6K=)l%b*H;^Yfk2M!Y2kiwTv*l%XUlxysX~EboxuZ9ICR zc5k|H+2r@=CZCW_QZm2i%hb7^)IMLkE2RKPP?8qF3G;ZcL+LF1|%m|Od@(S^vwUEdsJXip)q@j2Ukrd5E^Op;XjItWoNub?db@O)(G~mlZ!G0<-gkCznt_5tG;|_JD&ML3 z*39;QsGYU2M5HzRo}rB>THu)xb#<`gW#g@P*6ZL|=+QthS@Dp5?&gj+l=;+L7g5!O zG}(`OCm}9>KQulD-y$_TK69T&i<`A0JI6mnV^Kh=PbN&-I?!vAeVW=e(r?i(-RwCVUe=nbvY#LQZjaC( zaTd)q_UqDc(ug&%%{orQv)Jp26}j6$6|)bv1rNl#DWt`7C>N?(T&!neLG0KtIhFM~ zyFf|^l)t@s@?4L7pifmRcD;UQhg3_ewww>yv^^03XFs&8uf!_NoXzD+G?dD9b&=X< z?HQGaE+QXsmrR`BJlj#4uYygR(x#=s5xNz&16U-ct&|}TqUED3{O_GA5d&um2|Q4s!f&i>xS+Pf^wu*;6b^}Eu|^PVgWiUX7hV5 zOeY0o$X2R?Q;w^X19!FcS}GxQqa{2={9aujEDKZgk{jGgOi#{V6wDtYhS*6@>Z~U5 zT1;A;Pn%pxoDm@_*CHho^Mi>kpp-S@p@9Y*r@ zk_*3yv%~~4Hgn@4FX1%T?mO7DH$+!%I}Jgf+nCaKqHVU}qBR&(dpc7Ok!7#(un0+E zXrS4tS2=bvQH?dqWnR|yteI{WFEoCZ?0EtCR_@ooiVnInoHr*9jqO;zVdC+1DQID|a$Oii&x43)4C zNS79$+K$E48WEgom>_r@v zGJRt^jykozx>}qGoDJl83M;%jA~mq&As+~}h+v`UR#D>(LLt~|qldAqL47N@hjq-7 zf&JJ+@CbgWQzFb;>G`8*Py;cLWa3URb7QgQ-dw#8IkQTgP$~SLD_9V!Q#oD!^&9YD z6uXe`;Gq2CA{@(T{F6qbJVQNo?hl{;D~LwJe7)%^)JP^JIfh+>j5RuK9zKfTEKNs` zHFs%FjI)@$yqxdZXT^vLPd|fKxKi^%sZa~rjo8ti@=l$J4e`6SKfZ1m4KmPl#w!e; zcO0sLjUr0nB5R}tPfv-1bR555mN?}(uZ$IZ+V(|2(;?VNw$Sim(i8-W&++V7RV|F? ztSPe+_G#?e&vN*g%<~LpU|vE}=v+FypXfIREgj(1v7LPxyzq7Q|NPKSA6on}!9~Pg zT789~bGA~YE&rt051mm3s*}v34COAo;RBls@TmaE=?b;}AYGU3avL?@@h7)D{`M&q zpaKqCw|SIYY==>v%0MsO$zOuZX^=Y@;S+kh;a!93w1k2)TB1558Wywag2ex{mMol@ zuK3I$jCx9x66U67rLO7p2))&=UHc>|>>&c6qbq7f9f%Xi2mSF*t7Jq(1G8e^K}!fIp`*mUn7;3KuJ7#qUK{p*=TDz!G1pvk-gDk#{Ay^0e;>AA z#Zj|$+Wt+CDU4ZQ+9FRsSloIm@S*8jB*RGMvs#IHSCzB9O1{op)A4GJg{=^3Y78A= z3R+sxz|~=;YRwX9)b$&Z8gW;?$ULSH!C+x!ooyhH4i1RnX#K?)lY~n7^N0FD7jJZD zJ0?q4xBgo2?E(NM2NS-nIt7j|SBf?$ZafPO)8F;MD*&umCEqHUtcIAJ_NH2P68o}3 zZCvog<|?>KTMA}dlT*t7m6>^l_(~qyRl5m#qYE^MdT3pZV|0ZIMcAZ20BKHEiZj((|W&Wss`zB_AUdws6 zX8r|duZv#KKHIMf(N})-Df@E0eVzW2XCnOzPdnioH(I))V)RpvGqX%d zn05^Nprg3TC^dc^Ab*jQU_!<+F{D0+> zNl5r@R+VKA_~emSqEx`wQ2*?HGR0L)bf!p2r%{JbA0i^YzhgE<(oX2o2o-#2JTY@u zK+Q~6b$zRRUVRR?bnKv2WF?+Nhm#??`q>hB`tpTqF7~g%2GZ&rWEKkP9BG-muazVk zj`!|d>dA9AV~iWBdMCE>2%UX1UnbOHO|f?|QG2Y$z5eSBV_j?P9z$K8_Y0I)B_>{9 zVhYwd@-KLm90oi7_&Ieg6%!%e6WDG|Ov$2H8%7zy5VVA;CAS1yI+j8tiaP%kWx;w>dn&SmuFZ4$dSw-;^^$jkQa(H;35sJR3{>7ji;|2KxKd| z{JXTz@M#ahVTk2RTwdWZ4m|zX|NE0abl+Y%SMA5*yt^xoOF?6EVHBN`!tXPsus&hF zr@n$-f5Gbko`~M!!+viQB~9V{+<_Y!`5tWPITsituvDC$ao^^1{CO*C(Ttv&v!yC* zd}EJfrHysjv6{7PWQ5D{jvXexZpnNR37Q3VmU8}MrpmR)^mKF==9P_&GY+9My}i{b z`@m)QRTR64;y?;`lN8Hs=JD-rg`avY1#PcU$R%=rvfU>MM;9fr^m5tiRk8vE;X8ne z*E&QbdwlxVN34Igq{$`DK>ol5qbcZ>pDd)E-koI>LL-;nZV+EB8Yk%FRIYCQgI%sa zXNpKqoEp5l{?vCucNAg?T2IFV2`J>=EdB!}h#^pLABxjveo<3Xi^XxZl>m|pp%1R$u1ZJi`N6_ez(JUo?84=Yjt)1wW%MD$WF3f23IFSi zGq6q~kw7Wq{v6WIJ0w{~mRPxIcDY(Lninr!swd-SXICKNG$%RBs_{_5c)aT4oNTa3 zbr%y*#~H{J3WpdfZqB8!u&`N3$O$ePbCQH=CeF(raao%K&hN3UhTeqayh%ndV5gPb zlI()J`DVm^+;tuJSzprnnoYnk$6Pj~0C^5@w|y%;H#^%>k1MdW$;!i{w!b+~<$r_O zE|h(#HX?WpqHmfzzC39i4J?TE=6NMta0CZf=ynO!7 zEfzhW0Xib{{7w#r&dGM3Rhvw7O>&pDN3?$?;jTm?EE0I)Tw2XeI5NDKD_A4|#)pgo zK?K=J#g*ax;{^d8Kg3xqMK9%dKJvyPzJp|3l|?@%>z+8LBvK)8xc0<=ns8?Tk`BV% zf$V@lso6BBN&p)j+ja{p8!(jR=7E zgsEP@L|3L#?!?`}O2@z@1pKibz zZ=^ot=6>k7t``M-X6^%yskZ@clUkp**TyRYD5tcRESVv@XEXQimQB*r zWsmUQ6^K^IpZNe}S`XI^aC0))x~t%wN|~&^{>PU{^P#`o*t@#Ay5MiBY~~}%kOD~1 zv2M3KgxLm597wmTvNy=domz&fb0BTbV+$W2e+DQ<+_b_dAVzqSm_iAIc-MV4lyBI_ zbIAL+b8WP-9vP6nX^-Q<>Ir{&Cp5*b^1ERPpK7DXKZDS}fN@x32xX=gol|h-)J=SX z-m{}(?9&2SobP$hzrjm1e=H=yRexv@H?LZ3{IhYp1lZJ4Gc3LllbfmlXW6>&5*;8U1KrMzXz0C z13t+WR8Ov!fL<7^{}wQnQ;+<4qR8KT(b>hveb{UR&idVw{@TP>9l1~V;@y7c_K+gQ zTJFo|)P!#w!K}q36~ng??{Ce-S??PKr*W-7My-Q>u95-b=o;Q;xhl z1nT8FYT!wi3?k!0SH)+Nqf#G5)(kd`Oc{=_L)0j@Ghq)CNMe#Fd`D{c5lQsXvxi(i z#Sq|y=_uhfc6l%*$xkd=QKW)#@E61$UYhU2B6p!c5@E#n2v4nPJd`lSde#=A0t(#t zAVCC;u^{PvY;3GY`PO1TR@<=QWn{BCw@~*3=rU0b(}Al6zc+pg!0Ri76ms3xJ<7$} zk$9n30%j)>?;pIV{YRzB$+mX#Z@kbarwnAnLEC~wjJCl+ki)dAtj`|sykY}#G>76M zBj+*;lVTugvY&HRTHXC*BSCK6q3TTLWRBJmkj4-KN7SQI>CLi((yBncG70yx=8rzDL-pX@8)^Vmx>h z{;6m>;|DvuIHJfYTgb>vl8j`MMsET++O51}$mTlt%d9C>6)7oX$;G?HiiX>tyuw~- z^=Gc8i}0A%0xeC1AOX{@Mt8oHQwbn6<_p1TlWz-eQn~JeG)WI$UDbX3e;NqZ;p*~i zAjKanZ=UY$v@j$D6cMS6mp4kW8FmOpb6PM#L0)v3oL~KuB~+Km#;jw2;H3Qk$q@Wo`WgUvcCJA;;o2t3^zDs~ZQJv@)LardU2`Q;? zpy;`SdT+uzWyEgStoqw~<*s)DefL59CN3#?Z;QXRFo7meS-z36WCWkZ$!ocP-wi66 z=AQIt$?(dkev%E%QCD~OENma!y?Zi9+6fYuO{If3b(C;*fay)JA0c zCYMzbgLc5dag9-2^y6s$&6b_2cw>^<5l=3P1Mmun|=&vW!M|{?Kbozv# zj7)gt^lr6@KB~dx+my}s(Mwk=L5k2?U$K>vtb+m!d`ZFlwD@)H5eYOYFxS@B79%CR z;6)&|DleJinjS_?f`7yO&!hh2yR#WvJoAv`#@xA9jttZjBY`fke>30g@M^cZn#?c+QRo!%jBIk4}1&fxpVvmr?+z2EeUO?zAL zFV4XOd$-HmukTP@)uiC(*Ca||ju_$^WLb))qK;qW;k{P;;9f^%qeRQk*2phPWbIp| zg$5zsH)uL|UBfquC)guN=rP}Z*)i}N3UtEIdGuMldn@!qsjxThQtg(;SK6$4XK_kv ze}1um{pf-kElFYS4#>c0lMX(wZ_&PBGqJxt(UL5QwZcH!^=UE2|EoWG&8WMP{lgk9r$0+wsSGsDp?2@A zT$MJ9Weto!ktO`r%k@f{I;kzUo2KR}Dmg;UWIs;uE@@9qbqKG%d zK|2z``j+8dh7Ksaj_8Xe9$RhAZ8bW}_ws2yefd*hlTGx*U;){iG%IY~#U$U;;}p{c{WQkkT81VjQp#Ojixw<6c1tI( zr{UpIs%WBJ^W&}ADKdAh%bKd)_%M-_Cab9;>UMkj zEJo|pdzO;7Po zW0=ccV`PE*iQDt5YwragyS%u8YmrcFZ_I_SK*SF1K=FWuu{w+yh26eo7B#@dr9Q67RkHcy;L z`J{b3erYJi9yMbePeZC`_H1qgW3c8^ZoY4HFUh^WrM>%L#EGaWX0}rgKlcX zN}6D`#`r#|c1mEwr??*n_QIOi_O>KS-4*Mm4&RW-B~l*Xx44F-;fYIaD`=xza&ODZ z%Rfkej^#Mh)AKz4^XWDI1q#_H^grQ__XFRa3243W+yFX~jE%w6)lK-&UP+wP)4UA0O#o$s&GKThDf9jTuC?=IDqzj3LwleO`i}qiOVah4uOAjpittvT%@28fz z5EGwb1 znBa_ulbwijrVHia@eTtTj*gb3duRIm)ls;~LOXk3Y|r%ZiQBM*cx_nbPlq1(#m(?o_3J@FITupEl3Cg_NXY-D6a z*@RBtE40;4T{09Ya7TR7yM=Y} z*1SDv3@+bd@#1MY)!A#fOl`KhB$hv*{1!@R}KYd_R zV0af@^Xg4b^H?Oe_L9aI+*9v!?W0SSO4++cth;{ z9$5z(hK}G1=srTB>qjNy5U~XV8phR%z}HCjIpTPMaST-MhtuTAp!C)*87?#kYK!Ah zf=-Gj|7hoT=MyKey+9=1g^FtTS!+rPWpK>WnB=)g30^3g0m% zS<1JzvO;1bKY$zSd{JZEd{259&wOQ$N}-5qi8-n4x6^Hz6NG4Fu2bQx;sOb(6QHrT z!@my+nRO`ZVU$@HI5j+|an%06PzBr22O|Nd&jY`{!D5F*kx2zCHos-Ym!l(i=XAqCJ zAD_Q`^J;-i=0m_;XMx<>5|*R#@f!Uoar$G^ggiF+1R~bDN}r**!{>GK+G--ZWCf5% zE1*c!{QPU8keZwl`f>N5mXk{4eK3MgJTe?9%~wYEQTyb!Z)7j8>AcL2BnVrK-7#&r zLM&d-h|D)(MSu5$98!eWA-4Bm=mwKqV%E2nv8oEhGPn*rM)|ud4z2+4#Ll7ug(2x3 zJ%aUglGs6fVuzd%AsDDoDL%5OuTx=z~vy)g&q% zxT^MP@j%Ga4aQ^t?rb{#DAOU~4cUop!1I8*3q>?iRY-hZ7hDZ9_?R!+Vw3U%;m_mOKZH|@nn1CR?ar5 zaZaM!w|V>Vs=ec+A+1ZA7>4`3N}-?K8Ajt)W$qqsqV7#zu$53=>1_mDYOAd)H|<*9 zjQ<3S{fatdNCE*n#Sr~K*PB75pg}zEcgI)xOK$f#M{UA3n(^(&@L69=0w>BWH;uSp zT)=?XRcIh%Pz@tN7G3?strTO+-Gd_{7GxZJNSrOVHtCC zNAg~F1|P;hpRRQX*yV?=w6u4o8#8~V>QsYfXaC;q^o1B%8b%;VB?h?Y&}%NSne?@Z z2kushBI?hE=h4Y-Q>ft@bu|`!r~c59%5LmqM*nBfG(wx!`8yf#Rt4gpd`qOvUwa+>P4MdriSw*jJ(sCBs-~n+3s&< z(s(#(woFnw*N|Ok``SO-U{wZ{1EYM~ZoYmfE83{^b{E+iisE4}%vVaJX5501fogA69R2TrW_Y^DPp z?)gGf186*1by4J712RHuyRI_a;^lVBNSb#f&?k#?y$Q!WeiM%d3DLu;Up?Mg>Dp)@ z(695tE^rxSne(=zA&su5kex(d9x4hcZ0A>dIiJ{ceO}kiYKd7Hx>=#m!W+M!HKdrs zbGUS!;lke1T!?vBfbrc9AXdu`3tIKo!EPfBQF`7!TmgM-kZ@^ComYn`Q_|WE<#$EI6Wi(CrmXJ2|*IQq0kHZi}67EZ$D%b)ZnrhrU#^X^iy`tK|OUx-jY zTiH$BQRKtF9t5}>DHCUnK4Hu0&T~B!uU}ah_>*-cW1>8oOEhlyw8KGcS~y^uje-8_ z(8*_S6_ShxveOO$k!(4=$_WoN>&@HMD8J$mo%DW8_!HMON)%*b)BjLZH^_T*-a#TU z(z|9lj@x2bsw=NTvBVgrO_ev1v!jd#P`DeUaz|?(TbTO!SaCgqblW1B&0R`sA3Sn$!^}C zcM_BpkHemoZEy53u~?`**Hoe$#av6H&`qNex5$3(Mt$cZ3B(9kxJZ2^^*D!i@jP5YUeIbM7F^ON`}X~ckm=;#o0f5 zj;UYVpp3LX&|zRY zR*Fq5ZVY8O^jA?PHyfTJIr?GhtTsM$qrG#yt*>}W%V2ou`1OxA%2JG&JuT;za#YO= zQ6ACbmvA7dQYDi_(x0f}ZOj|P@q{~-1N-LrMirW-YW)rF?&fh7)hC%|295jL-||pH zj+d1f9yR%8D|Mcb^I-XXb%!eUYnVt_70oN)WIhah0a3`?ELM<^?G29~UzgWXZ-j`G zv>*%(W`uYg=>7FGo*5Y(0#bxg&H|C7K+FG#QuQI|bxUA1REnO%A zLu69EzyfPjtNfIWPYZYil@K|?-(Jlhnw9UL_-b%+uC?gCr9iCoPp#EypS30iSHA(4@^_mpyMUQ$__ z4$D>YGsm6Y5w5mpJY>XyM%K5}>51c<}9c`=eh0A{V%#ZxEzk%~KOs>BCTjLg zBF?{^hs@*h_RhSdB7QHYdf(Vmv}{f57&`Uu^r^9w5TKt`2}Pw9OjeNuJLfEF*nERV zZD2nlO}lX+^=Z`9(~1+Y?u%8}Esv=D<%+vGP|`9YDD1kxdnTe660yXD7^aKs{5 zeY|gy-vCJVY3DehfVSe}^tak4P%g7dW>77nOVS9tBNZBBUfv?EcR}U1k~WnlI1beI zcUGBOBVhy$B@3h<(2htr&xo&GATdwyrV&PDV9?U>m+k$ELBIx`7JQD2@4}())j*hN zd!=?0$DrkvCCJ3E-;R%qTUKm=kvL4yOV%VMC$|8l?Yeb_XGHVb3n3ilPYV0jY|&44 z+)w6srKv}B`I>^rnPFLnfW@@D!LG!7bV&SVo}M3Eui!0^h0r55+izsf40=&C#xPy* ziTC9Ypvu1^=>gQDP-w{_BNN2|?Lhq1$V$w|<9IFU7`VKW2}4@eK`kZ@^u*}hxvidO zGU1Tvp}2JO+Nr%YC^X)uP0kidjwguJioDPB5z24y!zQ>R2Es?yi%sy`SE&_A+;%5* ze6O~&v}ogoMMvL7X3bnC;~h|`hS>>G+_o7Ayuxj}FvH7~3>~R36L=({Q*^I9_pPvo~6Qy zgvdx%7}YxIbD>VInCjsYAJxA;!&6TSD2RMqJBRD(Kp%o*-z7x)t39rVr!G(g;e7tT zIy%@+LQQDIjw0ysQ=iCECpSo{E?#T-L3d$ze|PtxZ&1jWyJ9^ToG+#eor*~!rOCmb5jWAJG-QB182#+E%j-^BOWcXLeV^N4S;)QQ z=J*d^FT}6pbz-=L`G4l9WIUg6ciNm|Q#A4-zgi^`^X)4hU2(0GMt9C^GLd8QSjK*~ zVSN1!qk$jN2W3BdZ|4396G1g`2hg$~juUW@q^W($-RA$3(UePsS{4dmCput98S>`h zrD`uO^V~N(P;>dbBIR0KGgx9qYhv1+r<2P?^o-rkXsIV}{e4pq(H@a5J3gCf)jW|3 z5lZ8Wcla!kjFB?W(#@a74BdH)lLP@=L@h*p*E0_yMWVf-HZU1hJ^7(s5y5Ozx4q0gbgXFnvGE>JpCZpnKam~phGnZzT;8;vq4L2GQ(teh zg9s4~920-FofPR|%PhucY$%hZKA(PvKed_F4+BxS*6L}K+(Xq_GTrQ^0&2LGs^n%- z%io5ymmhqaCF3)#{Da6fMOeC$@}Bz zND2jvf-_GL$=XRw7$#q_E*)jC_aE&$ddFiu(Yvs8zFbYyl6JE<^a?}PfYi}kT>AJ) zKKF^)^2;){JBwa#-ScB+ubb!VVU-O0uqtuMB)ER8pWd!q+K7L7M2EBWO)IS%VZ_79 z@zuzqh_bMVhKYzbwj|5uX}MZ^QWaqlHZQbk29~nNGtM2mc3UtFbeeBs-EbF`bg*o- ziOYM{dSXp`sCje}q-@*vpn#82>f*1TxMDr8lyL2egaW4gXe!~0*bjexy=m_`Zjz__ zP_m}?+?}J`Di2Jom#=gAPHYcz`AWvdJF_qC?8}vYr(9qNY2b6O@>l+r-EKqc=P-Ks z%sXqh|Y=Qq3`shob|FrJAPL<<7IVKSdU!XEziRQz3#!U^gm zpE2tvXRs>3rK{CNglbD(U8HBajy{^64ZqEZ&UWTNXa;{EV&?quo3fbbj&B1Ya|p$s zg6(&F>+SljH1_U~{@hcVlm|lfYW$P4k&+M4qhsZAh5bmn*wT}Dam0Is0!SBatLWTO zVjnnjKZbp!Jkzfbr1*c4@zZs1a6}3_{q#Y@D>+XGMmLL;NDHhY$ z*Ovv`BoIq#LmGKxrxzI>@$m4w-F)gKQ!CPwwxH8;3yMVF zvQDwiK|5?Up#5%7>A@L7c4Q^sOeL@zjq~Y)KYVyTi&G*8?X!mJ>@uC? zIe^kBk?E?Xfe}d$?3R>a_9$cP%E0LtI&B*f3mi!P+n*%#Nm+K&%(XNF92vlm_p&Fq47_Qs{t z`v(ufJ?L@h;>p!SeQS{!_8??x_rWFQwh4wgpsheaKFjGd1FJC1j;Hhoy9=2{XmBtE z!ZCJB^GF49FQifKXecYw0b>x4%mu%_`2Qgkhb>Cw`=;Lw_kZov2g@ix?Dgi?f!YCe z%O)dGY%!41x%cac7PS48FlbRaobMP}(J-=-(rC22Slwwpxhp~V(n;?brt0xB!#jFT zCclJd7j%=YFmwDy6u+yJn5gLco3}wgdNG|jT?Zlw*J1Xn28<%4Pft(p%F*DVm8-|U zpQW5oSL=Ch7JAKKKt?f_n7|XAQ@Jj2i^tAXu!^S}z%2VNM-3QU8dfx+rSfU|U^SJu z8!6ynN~Pd+CiR;I#FA_0{kIZdH^GBQvd%zpU zHbi=0w*Ald_AeiQ-Dl_3`0wa%n5IeoK!IXX8zEBEbr`tmQ&kO-lUBV7N~9m^kTl7o zgB|-&Hci(pg~2lGR7fABd0u2T+&36WV~SYD5#{N-_te_HI6yjwK^&VG-wHB!e(sZV z4zqDz)~!5E#~|{&LaR0sr{7=J0i9C<$?qio&U(ia+_zG{^Ki_r1^henCJ81gqaA#Q zWG(G_Z*dV35kWrIQ&uX4hessR->(mDy&6Ye9U%0Dq*v6glTo4pFLY+>?*tCNIv#;B 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%0D9fiA2G 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 diff --git a/docs/source/manual/fpga_verilog/index.rst b/docs/source/manual/fpga_verilog/index.rst index 9b3af3a12..f851e6ed9 100644 --- a/docs/source/manual/fpga_verilog/index.rst +++ b/docs/source/manual/fpga_verilog/index.rst @@ -10,7 +10,3 @@ FPGA-Verilog fabric_netlist testbench - - sc_flow - - diff --git a/docs/source/manual/fpga_verilog/figures/Layout_Diagram.png b/docs/source/tutorials/design_flow/figures/Layout_Diagram.png similarity index 100% rename from docs/source/manual/fpga_verilog/figures/Layout_Diagram.png rename to docs/source/tutorials/design_flow/figures/Layout_Diagram.png diff --git a/docs/source/manual/fpga_verilog/figures/fpga_asap_10x10_final.png b/docs/source/tutorials/design_flow/figures/fpga_asap_10x10_final.png similarity index 100% rename from docs/source/manual/fpga_verilog/figures/fpga_asap_10x10_final.png rename to docs/source/tutorials/design_flow/figures/fpga_asap_10x10_final.png diff --git a/docs/source/manual/fpga_verilog/figures/fpga_asap_10x10_floorplan.png b/docs/source/tutorials/design_flow/figures/fpga_asap_10x10_floorplan.png similarity index 100% rename from docs/source/manual/fpga_verilog/figures/fpga_asap_10x10_floorplan.png rename to docs/source/tutorials/design_flow/figures/fpga_asap_10x10_floorplan.png diff --git a/docs/source/tutorials/design_flow/index.rst b/docs/source/tutorials/design_flow/index.rst new file mode 100644 index 000000000..e7e9937ae --- /dev/null +++ b/docs/source/tutorials/design_flow/index.rst @@ -0,0 +1,10 @@ +.. _design_flow_tutorials: + Design Flows + +Design Flows +------------ + +.. toctree:: + :maxdepth: 2 + + sc_flow diff --git a/docs/source/manual/fpga_verilog/sc_flow.rst b/docs/source/tutorials/design_flow/sc_flow.rst similarity index 100% rename from docs/source/manual/fpga_verilog/sc_flow.rst rename to docs/source/tutorials/design_flow/sc_flow.rst diff --git a/docs/source/tutorials/index.rst b/docs/source/tutorials/index.rst index ea844c11c..fbadf5193 100644 --- a/docs/source/tutorials/index.rst +++ b/docs/source/tutorials/index.rst @@ -7,3 +7,5 @@ compile eda_flow + + design_flow/index From 011ce5cdf6de11ea6f7e29c7292304273246480c Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 24 May 2020 18:22:45 -0600 Subject: [PATCH 555/645] minor fix on the documentation --- docs/source/tutorials/compile.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/source/tutorials/compile.rst b/docs/source/tutorials/compile.rst index 6844740d2..e7db5a059 100644 --- a/docs/source/tutorials/compile.rst +++ b/docs/source/tutorials/compile.rst @@ -23,7 +23,8 @@ In general, please follow the steps to compile .. note:: recommand to use ``make -j`` to accelerate the compilation -Quick Compilation Verification +**Quick Compilation Verification** + To quickly verify the tool is well compiled, user can run the following command from OpenFPGA root repository :: From 35536ee59428238a08764f430037b0a3b3405585 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 24 May 2020 23:03:54 -0600 Subject: [PATCH 556/645] renaming design flows in documentation --- docs/source/tutorials/design_flow/index.rst | 2 +- .../tutorials/design_flow/{sc_flow.rst => verilog_to_gds2.rst} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename docs/source/tutorials/design_flow/{sc_flow.rst => verilog_to_gds2.rst} (98%) diff --git a/docs/source/tutorials/design_flow/index.rst b/docs/source/tutorials/design_flow/index.rst index e7e9937ae..610b09b35 100644 --- a/docs/source/tutorials/design_flow/index.rst +++ b/docs/source/tutorials/design_flow/index.rst @@ -7,4 +7,4 @@ Design Flows .. toctree:: :maxdepth: 2 - sc_flow + verilog_to_gds2 diff --git a/docs/source/tutorials/design_flow/sc_flow.rst b/docs/source/tutorials/design_flow/verilog_to_gds2.rst similarity index 98% rename from docs/source/tutorials/design_flow/sc_flow.rst rename to docs/source/tutorials/design_flow/verilog_to_gds2.rst index f89d71db2..6efb3041f 100644 --- a/docs/source/tutorials/design_flow/sc_flow.rst +++ b/docs/source/tutorials/design_flow/verilog_to_gds2.rst @@ -1,4 +1,4 @@ -From Verilog to Layout +From Verilog to GDSII ~~~~~~~~~~~~~~~~~~~~~~ The generated Verilog code can be used through a semi-custom design flow to generate the layout. From aa77ee9af6d4fbd3631af8a8e924e4e1ed6e1668 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 25 May 2020 00:31:01 -0600 Subject: [PATCH 557/645] add tutorial for full testbench run --- docs/source/manual/fpga_verilog/testbench.rst | 2 ++ docs/source/tutorials/compile.rst | 8 ++++---- docs/source/tutorials/design_flow/index.rst | 2 ++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/source/manual/fpga_verilog/testbench.rst b/docs/source/manual/fpga_verilog/testbench.rst index e9be51fda..728adb1e0 100644 --- a/docs/source/manual/fpga_verilog/testbench.rst +++ b/docs/source/manual/fpga_verilog/testbench.rst @@ -1,3 +1,5 @@ +.. _fpga_verilog_testbench: + Testbench --------- diff --git a/docs/source/tutorials/compile.rst b/docs/source/tutorials/compile.rst index e7db5a059..5183be503 100644 --- a/docs/source/tutorials/compile.rst +++ b/docs/source/tutorials/compile.rst @@ -8,7 +8,7 @@ General Guidelines OpenFPGA uses CMake to generate the Makefile scripts In general, please follow the steps to compile -:: +.. code-block:: shell git clone https://github.com/LNIS-Projects/OpenFPGA.git cd OpenFPGA @@ -27,7 +27,7 @@ In general, please follow the steps to compile To quickly verify the tool is well compiled, user can run the following command from OpenFPGA root repository -:: +.. code-block:: shell python3 openfpga_flow/scripts/run_fpga_task.py compilation_verification --debug --show_thread_logs @@ -49,7 +49,7 @@ Docker If some of these dependencies are not installed on your machine, you can choose to use a Docker (the Docker tool needs to be installed). For the ease of the customer first experience, a Dockerfile is provided in the OpenFPGA folder. A container ready to use can be created with the following command -:: +.. code-block:: shell docker run lnis/open_fpga:release @@ -57,7 +57,7 @@ For the ease of the customer first experience, a Dockerfile is provided in the O Otherwise, a container where you can build OpenFPGA yourself can be created with the following commands -:: +.. code-block:: shell docker build . -t open_fpga docker run -it --rm -v $PWD:/localfile/OpenFPGA -w="/localfile/OpenFPGA" open_fpga bash diff --git a/docs/source/tutorials/design_flow/index.rst b/docs/source/tutorials/design_flow/index.rst index 610b09b35..e854a08c6 100644 --- a/docs/source/tutorials/design_flow/index.rst +++ b/docs/source/tutorials/design_flow/index.rst @@ -7,4 +7,6 @@ Design Flows .. toctree:: :maxdepth: 2 + blif_to_verification + verilog_to_gds2 From 339bf87c430e98732ad4435b86c3e7d7d1408078 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 25 May 2020 00:31:27 -0600 Subject: [PATCH 558/645] add missing file --- .../design_flow/blif_to_verification.rst | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 docs/source/tutorials/design_flow/blif_to_verification.rst diff --git a/docs/source/tutorials/design_flow/blif_to_verification.rst b/docs/source/tutorials/design_flow/blif_to_verification.rst new file mode 100644 index 000000000..d3b27a43f --- /dev/null +++ b/docs/source/tutorials/design_flow/blif_to_verification.rst @@ -0,0 +1,157 @@ +.. _from_blif_to_verification: + +From BLIF to Verification +------------------------- + +This tutorial will show an example how to + - generate Verilog netlists for a FPGA fabric + - generate Verilog testbenches for a RTL design + - run HDL simulation to verify the functional correctness of the implemented FPGA fabric + +Netlist Generation +~~~~~~~~~~~~~~~~~~ +We will use the openfpga_flow scripts (see details in :ref:`run_fpga_task`) to generate the Verilog netlists and testbenches. +Here, we consider a representative but fairly simple FPGA architecture, which is based on 4-input LUTs. +We will map a 2-input AND gate to the FPGA fabric, and run a full testbench (see details in :ref:`fpga_verilog_testbench`) + +We will simply execute the following openfpga task-run by + +.. code-block:: shell + + python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/configuration_chain + +Detailed settings, such as architecture XML files and RTL designs, can be found at ``${OPENFPGA_PATH}/openfpga_flow/tasks/openfpga_shell/configuration_chain/config/task.conf``. + +.. note:: ``${OPENFPGA_PATH}`` is the root directory of OpenFPGA + +After this task-run, you can find all the generated netlists and testbenches at + +.. code-block:: shell + + ${OPENFPGA_PATH}/openfpga_flow/tasks/openfpga_shell/configuration_chain/latest/k4_N4_tileable_40nm/and2/MIN_ROUTE_CHAN_WIDTH/SRC/ + +.. note:: See :ref:`fabric_netlists` and :ref:`fpga_verilog_testbench` for the netlist details. + +Run icarus iVerilog Simulation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Through OpenFPGA Scripts +^^^^^^^^^^^^^^^^^^^^^^^^ + +By default, the ``configuration_chain`` task-run will execute iVerilog simulation automatically. +The simulation results are logged in + +.. code-block:: shell + + ${OPENFPGA_PATH}/openfpga_flow/tasks/openfpga_shell/configuration_chain/latest/k4_N4_tileable_40nm/and2/MIN_ROUTE_CHAN_WIDTH/vvp_sim_output.txt + +If the verification passed, you should be able to see ``Simulation Succeed`` in the log file. + +All the waveforms are stored in the ``and2_formal.vcd`` file. +To visualize the waveforms, you can use the `GTKWave +`_. + +.. code-block:: shell + + gtkwave ${OPENFPGA_PATH}/openfpga_flow/tasks/openfpga_shell/configuration_chain/latest/k4_N4_tileable_40nm/and2/MIN_ROUTE_CHAN_WIDTH/and2_formal.vcd & + +Manual Method +^^^^^^^^^^^^^ + +If you want to run iVerilog simulation manually, you can follow these steps: + +.. code-block:: shell + + cd ${OPENFPGA_PATH}/openfpga_flow/tasks/openfpga_shell/configuration_chain/latest/k4_N4_tileable_40nm/and2/MIN_ROUTE_CHAN_WIDTH + + source iverilog_output.txt + + vvp compiled_and2 + +Debugging Tips +^^^^^^^^^^^^^^ + +If you want to apply full visibility to the signals, you need to change the following line in + + .. code-block:: shell + + ${OPENFPGA_PATH}/openfpga_flow/tasks/openfpga_shell/configuration_chain/latest/k4_N4_tileable_40nm/and2/MIN_ROUTE_CHAN_WIDTH/SRC/and2_autocheck_top_tb.v + +from + +.. code-block:: shell + + $dumpvars (1, and2_autocheck_top_tb); + +to + +.. code-block:: shell + + $dumpvars (12, and2_autocheck_top_tb); + + +Run Modelsim Simulation +~~~~~~~~~~~~~~~~~~~~~~~ +Alternatively, you can run Modelsim simulations through openfpga_flow scripts or manually. + +Through OpenFPGA Scripts +^^^^^^^^^^^^^^^^^^^^^^^^ +You can simply call the python script in the following line: + +.. code-block:: shell + + python3 openfpga_flow/scripts/run_modelsim.py openfpga_shell/configuration_chain --run_sim + +The script will automatically create a Modelsim project at + +.. code-block:: shell + + ${OPENFPGA_PATH}/openfpga_flow/tasks/openfpga_shell/configuration_chain/latest/k4_N4_tileable_40nm/and2/MIN_ROUTE_CHAN_WIDTH/MSIM2/ + +and run the simulation. + +You may open the project and visualize the simulation results. + +Manual Method +^^^^^^^^^^^^^ + +Modify the ``fpga_defines.v`` (see details in :ref:`fabric_netlists`) at + +.. code-block:: shell + + ${OPENFPGA_PATH}/openfpga_flow/tasks/openfpga_shell/configuration_chain/latest/k4_N4_tileable_40nm/and2/MIN_ROUTE_CHAN_WIDTH/SRC/ + +by **deleting** the line + +.. code-block:: shell + + `define ICARUS_SIMULATOR 1 + +Create a folder ``MSIM`` under + +.. code-block:: shell + + ${OPENFPGA_PATH}/openfpga_flow/tasks/openfpga_shell/configuration_chain/latest/k4_N4_tileable_40nm/and2/MIN_ROUTE_CHAN_WIDTH/ + +Under the ``MSIM`` folder, create symbolic links to ``SRC`` folder and reference benchmarks by + +.. code-block:: shell + + ln -s ../SRC ./ + + ln -s ../and2_output_verilog.v ./ + +.. note:: Depending on the operating system, you may use other ways to create the symbolic links + +Launch ModelSim under the ``MSIM`` folder and create a project by following Modelsim user manuals. + +Add the following file to your project: + +.. code-block:: shell + + ${OPENFPGA_PATH}/openfpga_flow/tasks/openfpga_shell/configuration_chain/latest/k4_N4_tileable_40nm/and2/MIN_ROUTE_CHAN_WIDTH/SRC/and2_include_netlists.v + +Compile the netlists, create a simulation configuration and specify ``and2_autocheck_top_tb`` at the top unit. + +Execute simulation with ``run -all`` +You should see ``Simulation Succeed`` in the output log. From 1150b903a5b88b258240e30649e77c462edc61b3 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 25 May 2020 14:43:55 -0600 Subject: [PATCH 559/645] add quick start tutorial for architecture modeling --- .../arch_modeling/figures/k4n4_arch.png | Bin 0 -> 71307 bytes docs/source/tutorials/arch_modeling/index.rst | 10 + .../tutorials/arch_modeling/quick_start.rst | 549 ++++++++++++++++++ docs/source/tutorials/index.rst | 2 + 4 files changed, 561 insertions(+) create mode 100644 docs/source/tutorials/arch_modeling/figures/k4n4_arch.png create mode 100644 docs/source/tutorials/arch_modeling/index.rst create mode 100644 docs/source/tutorials/arch_modeling/quick_start.rst diff --git a/docs/source/tutorials/arch_modeling/figures/k4n4_arch.png b/docs/source/tutorials/arch_modeling/figures/k4n4_arch.png new file mode 100644 index 0000000000000000000000000000000000000000..4a4803094ddb00ff00abe0d86f80e4e9bbe2f2b5 GIT binary patch literal 71307 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 literal 0 HcmV?d00001 diff --git a/docs/source/tutorials/arch_modeling/index.rst b/docs/source/tutorials/arch_modeling/index.rst new file mode 100644 index 000000000..1b643641d --- /dev/null +++ b/docs/source/tutorials/arch_modeling/index.rst @@ -0,0 +1,10 @@ +.. _design_flow_tutorials: + Design Flows + +Architecture Modeling +--------------------- + +.. toctree:: + :maxdepth: 2 + + quick_start diff --git a/docs/source/tutorials/arch_modeling/quick_start.rst b/docs/source/tutorials/arch_modeling/quick_start.rst new file mode 100644 index 000000000..0efbddf0d --- /dev/null +++ b/docs/source/tutorials/arch_modeling/quick_start.rst @@ -0,0 +1,549 @@ +.. _arch_quick_start: + +A Quick Start +------------- + +In this tutorial, we will consider a simple but representative FPGA architecture to show you how to + - Adapt the VPR architecture XML file to OpenFPGA acceptable format + - Create the OpenFPGA architecture XMl file to customize the primitive circuits + +Through this quick example, we will introduce the key steps to build your own FPGA based on a VPR architecture template. + +.. note:: These tips are generic and fundamental to build any architecture file for OpenFPGA. + +Adapt VPR Architecture +~~~~~~~~~~~~~~~~~~~~~~ +We start with the VPR architecture `template +`_. +This file models a homogeneous FPGA, as illustrated in :numref:`fig_k4n4_arch`. + +.. _fig_k4n4_arch: + +.. figure:: ./figures/k4n4_arch.png + :scale: 100% + + K4N4 FPGA architecture + +A summary of the architectural features is as follows: + - An array of tiles surrounded by a ring of I/O blocks + - K4N4 Configurable Logic Block (CLB), which consists of four Basic Logic Elements (BLEs) and a fully-connected crossbar. Each BLE contains a 4-input Look-Up Table (LUT), a Flip-Flop (FF) and a 2:1 routing multiplexer + - Length-1 routing wires interconnected by Wilton-Style Switch Block (SB) + +The VPR architecture description is designed for EDA needs mainly, which lacks the details physical modelingrequired by OpenFPGA. +Here, we show a step-by-step adaption on the architecture template. + +Physical I/O Modeling +^^^^^^^^^^^^^^^^^^^^^ +OpenFPGA requires a physical I/O block rather the abstract I/O modeling of VPR. +The ```` under the ```` should be adapted to the following: + +.. code-block:: xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Note that, there are several major changes in the above codes, when compared to the original code. + - We added a physical ``mode`` of I/O in addition to the original VPR I/O modeling, which is close to the physical implementation of an I/O cell. OpenFPGA will output fabric netlists base on the physical implementation rather than the operating modes. + - We remove the ``clock`` port of I/O is actually a dangling port. + - We specify that the phyical ``mode`` to be disabled for VPR packer by using ``packable=false``. This can help reduce packer's runtime. + +Since, we have added a new BLIF model ``subckt io`` to the architecture modeling, we should update the ```` XML node by adding a new I/O model. + +.. code-block:: xml + + + + + + + + + + + + + +Tileable Architecture +^^^^^^^^^^^^^^^^^^^^^ +OpenFPGA does support fine-grained tile-based architecture as shown in :numref:`fig_k4n4_arch`. +The tileable architecture leads to fast netlist generation as well as enables highly optimized physical designs through backend flow. +To turn on the tileable architecture, the ``tileable`` property should be added to ```` node. + +.. code-block:: xml + + + +By enabling this, all the Switch Blocks and Connection Blocks will be generated as identical as possible. +As a result, for any FPGA array size, there are only 9 unique tiles to be generated in netlists. +See details in :cite:`XTang_FPT_2019`. + +Detailed guidelines can be found at :ref:`addon_vpr_syntax`. + +Craft OpenFPGA Architecture +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +OpenFPGA needs another XML file which contains detailed modeling on the physical design of FPGA architecture. +This is designed to minimize the modification on the original VPR architecture file, so that it can be reused. + +Overview on the Structure +^^^^^^^^^^^^^^^^^^^^^^^^^ + +An OpenFPGA architecture including the following parts. + - Architecture modeling with a focus on circuit-level description + - Configuration protocol definition + - Annotation on the VPR architecture modules + - Simulation settings + +These parts are organized as follows in the XML file. + +.. code-block:: xml + + + + + ... + + + + + ... + + + + + ... + + + ... + + + ... + + + ... + + + + + ... + + +Technology Library Definition +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Technology information are all stored under the ```` node, which contains transistor-level information to build the FPGA. +Here, we bind to the open-source ASU Predictive Technology Modeling (PTM) 45nm process library. +See details in :ref:`technology_library`. + +.. code-block:: xml + + + + + + + + + + + + + + + + + + + + + + +.. note:: These information are important for FPGA-SPICE to correctly generate netlists. If you are not using FPGA-SPICE, you may provide a dummy technology library. + +Circuit Library Definition +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Circuit library is the crucial component of the architecture description, which contains a list of ````, each of which describes how a circuit is implemented for a FPGA component. + +Typically, we will defined a few atom ```` which are used to build primitive ````. + +.. code-block:: xml + + + + + ... + + + + + + ... + + + + +.. note:: Primitive ```` are the circuits which are directly used to build a FPGA component, such as Look-Up Table (LUT). Atom ```` are the circuits which are only used inside primitive ````. + +In this tutorial, we need the following atom ````, which are inverters, buffers and pass-gate logics. + +.. code-block:: xml + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + + + + + 10e-12 5e-12 5e-12 + + + 10e-12 5e-12 5e-12 + + + + + + + + + + + + + + + + + + + + +In this tutorial, we require the following primitive ````, which are routing multiplexers, Look-Up Tables, I/O cells in FPGA architecture (see :numref:`fig_k4n4_arch`). + +.. note:: We use different routing multiplexer circuits to maximum the performance by considering it fan-in and fan-out in the architecture context. + +.. note:: We specify that external Verilog netlists will be used for the circuits of Flip-Flops (FFs) ``static_dff`` and ``sc_dff_compact``, as well as the circuit of I/O cell ``iopad``. Other circuit models will be auto-generated by OpenFPGA. + +.. code-block:: xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +See details in :ref:`circuit_library` and :ref:`circuit_model_examples`. + +Annotation on VPR Architecture +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +In this part, we bind the ```` defined in the circuit library to each FPGA component. + +We specify that the FPGA fabric will be configured through a chain of Flip-Flops (FFs), which is built with the ````. + +.. code-block:: xml + + + + + +For the routing architecture, we specify the ``circuit_model`` to be used as routing multiplexers inside Connection Blocks (CBs), Switch Blocks (SBs), and also the routing wires. + +.. code-block:: xml + + + + + + + + + + + +.. note:: For a correct binding, the name of connection block, switch block and routing segment should match the name definition in your VPR architecture description! + +For each ```` defined in the ```` of VPR architecture, we need to specify + +- The physical mode for any ```` that contains multiple ````. The name of the physical mode should match a mode name that is defined in the VPR architecture. For example: + +.. code-block:: xml + + + +- The circuit model used to implement any primitive ```` in physical modes. It is required to provide full hierarchy of the ``pb_type``. For example: + +.. code-block:: xml + + + +.. note:: Mode-selection bits should be provided as the default configuration for a configurable resource. In this example, an I/O cell has a configuration bit, as defined in the ````. We specify that by default, the configuration memory will be set to logic ``1``. + +- The physical ```` for any ```` in the operating modes (mode other than the physical mode). This is required to translate mapping results from operating modes to their physical modes, in order to generate bitstreams. It is required to provide full hierarchy of the ``pb_type``. For example, + +.. code-block:: xml + + + +.. note:: Mode-selection bits should be provided so as to configure the circuits to be functional as required by the operating mode. In this example, an I/O cell will be configured with a logic ``1`` when operating as an input pad. + +- The circuit model used to implement interconnecting modules. The interconnect name should match the definition in the VPR architecture file. For example, + +.. code-block:: xml + + + +.. note:: If not specified, each interconnect will be binded to its default ``circuit_model``. For example, the crossbar will be binded to the default multiplexer ````, if not specified here. + +.. note:: OpenFPGA automatically infers the type of circuit model required by each interconnect. + +The complete annotation is shown as follows: + +.. code-block:: xml + + + + + + + + + + + + + + + + + + + + +See details in :ref:`annotate_vpr_arch`. + +Simulation Settings +^^^^^^^^^^^^^^^^^^^ + +The simulation settings contain critical parameters to build testbenches for verify the FPGA fabric. + +The complete annotation is shown as follows: + +.. code-block:: xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +The ```` is crucial to create clock signals in testbenches. + +.. note:: FPGA has two types of clocks, one is the operating clock which controls applications that mapped to FPGA fabric, while the other is the programming clock which controls the configuration protocol. + +In this example, we specify + +- the operating clock will follow the maximum frequency achieved by VPR routing results +- the number of operating clock cycles to be used will follow the average signal activities of the RTL design that is mapped to the FPGA fabric. +- the actual operating clock frequency will be relaxed (reduced) by 20% by considering the errors between VPR results and physical designs. +- the programming clock frequency is fixed at 200MHz + +The ```` are the options for SPICE simulator. +Here we specify + +- SPICE simulations will consider a 25 :math:`^\circ C` temperature. +- SPICE simulation will output results in a compact way without details on node capacitances. +- SPICE simulation will use ``0.1ps`` as the minimum time step. +- SPICE simulation will consider fast algorithms to speed up runtime. + +The ```` are the options for SPICE simulator. +Here we specify that for each testbench, we will consider two Monte-Carlo simulations to evaluate the impact of process variations. + +The ```` specify how the output signals will be measured for delay and power evaluation. +Here we specify that + +- for slew calculation (used in power estimation), we consider from the 5% of the ``VDD`` to the 95% of the ``VDD`` for both rising and falling edges. +- for delay calculation, we consider from the 50% of the ``VDD`` of input signal to the 50% of the ``VDD`` of output signals for both rising and falling edges. + +In the ````, we specify that ``20ps`` slew time will be applied to built clock waverforms in SPICE simulations. + +See details in :ref:`simulation_setting`. diff --git a/docs/source/tutorials/index.rst b/docs/source/tutorials/index.rst index fbadf5193..9b1993d68 100644 --- a/docs/source/tutorials/index.rst +++ b/docs/source/tutorials/index.rst @@ -9,3 +9,5 @@ eda_flow design_flow/index + + arch_modeling/index From 62c506182c53949822574e336017ed888c5a5a7b Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 25 May 2020 18:21:12 -0600 Subject: [PATCH 560/645] start developing frame-based configuration protocol --- libopenfpga/libarchopenfpga/src/circuit_types.h | 6 +++--- .../libarchopenfpga/src/read_xml_config_protocol.cpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libopenfpga/libarchopenfpga/src/circuit_types.h b/libopenfpga/libarchopenfpga/src/circuit_types.h index 448bf28d2..f13a8a5ee 100644 --- a/libopenfpga/libarchopenfpga/src/circuit_types.h +++ b/libopenfpga/libarchopenfpga/src/circuit_types.h @@ -119,16 +119,16 @@ constexpr std::array CIRCUIT_MODEL_D * 1. configurable memories are organized and accessed as standalone elements * 2. configurable memories are organized and accessed by a scan-chain * 3. configurable memories are organized and accessed by memory bank - * 4. configurable memories are organized and accessed by a local encoder + * 4. configurable memories are organized and accessed by frames */ enum e_config_protocol_type { CONFIG_MEM_STANDALONE, CONFIG_MEM_SCAN_CHAIN, CONFIG_MEM_MEMORY_BANK, - CONFIG_MEM_LOCAL_ENCODER, + CONFIG_MEM_FRAME_BASED, NUM_CONFIG_PROTOCOL_TYPES }; -constexpr std::array CONFIG_PROTOCOL_TYPE_STRING = {{"standalone", "scan_chain", "memory_bank", "local_encoder"}}; +constexpr std::array CONFIG_PROTOCOL_TYPE_STRING = {{"standalone", "scan_chain", "memory_bank", "frame_based"}}; #endif diff --git a/libopenfpga/libarchopenfpga/src/read_xml_config_protocol.cpp b/libopenfpga/libarchopenfpga/src/read_xml_config_protocol.cpp index e00600b1a..2daedbd39 100644 --- a/libopenfpga/libarchopenfpga/src/read_xml_config_protocol.cpp +++ b/libopenfpga/libarchopenfpga/src/read_xml_config_protocol.cpp @@ -35,8 +35,8 @@ e_config_protocol_type string_to_config_protocol_type(const std::string& type_st return CONFIG_MEM_MEMORY_BANK; } - if (std::string("local_encoder") == type_string) { - return CONFIG_MEM_LOCAL_ENCODER; + if (std::string("frame_based") == type_string) { + return CONFIG_MEM_FRAME_BASED; } return NUM_CONFIG_PROTOCOL_TYPES; From 3a26bb5eefb7105c3c92c99c211b18360ec26a45 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 25 May 2020 19:02:14 -0600 Subject: [PATCH 561/645] add advanced check in configurable memories --- .../src/check_circuit_library.cpp | 1 + openfpga/src/base/openfpga_read_arch.cpp | 7 ++++ openfpga/src/utils/circuit_library_utils.cpp | 37 +++++++++++++++++++ openfpga/src/utils/circuit_library_utils.h | 4 ++ 4 files changed, 49 insertions(+) diff --git a/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp b/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp index f5392a2e4..f37742a5a 100644 --- a/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp +++ b/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp @@ -552,3 +552,4 @@ bool check_circuit_library(const CircuitLibrary& circuit_lib) { return true; } + diff --git a/openfpga/src/base/openfpga_read_arch.cpp b/openfpga/src/base/openfpga_read_arch.cpp index ee0670268..db9b09b5b 100644 --- a/openfpga/src/base/openfpga_read_arch.cpp +++ b/openfpga/src/base/openfpga_read_arch.cpp @@ -11,6 +11,7 @@ /* Headers from archopenfpga library */ #include "read_xml_openfpga_arch.h" #include "check_circuit_library.h" +#include "circuit_library_utils.h" #include "write_xml_openfpga_arch.h" #include "openfpga_read_arch.h" @@ -50,6 +51,12 @@ int read_arch(OpenfpgaContext& openfpga_context, return CMD_EXEC_FATAL_ERROR; } + if (false == check_configurable_memory_circuit_model(openfpga_context.arch().config_protocol.type(), + openfpga_context.arch().circuit_lib, + openfpga_context.arch().config_protocol.memory_model())) { + return CMD_EXEC_FATAL_ERROR; + } + return CMD_EXEC_SUCCESS; } diff --git a/openfpga/src/utils/circuit_library_utils.cpp b/openfpga/src/utils/circuit_library_utils.cpp index cecbcafed..082349008 100644 --- a/openfpga/src/utils/circuit_library_utils.cpp +++ b/openfpga/src/utils/circuit_library_utils.cpp @@ -11,6 +11,7 @@ #include "vtr_assert.h" #include "vtr_log.h" +#include "check_circuit_library.h" #include "circuit_library_utils.h" /* begin namespace openfpga */ @@ -226,4 +227,40 @@ std::vector find_circuit_library_unique_verilog_netlists(const Circ return netlists; } +/************************************************************************ + * Advanced check if the circuit model of configurable memory + * satisfy the needs of configuration protocol + * - Configuration chain -based: we check if we have a CCFF model + * - Frame -based: we check if we have a SRAM model which has BL and WL + * + ***********************************************************************/ +bool check_configurable_memory_circuit_model(const e_config_protocol_type& config_protocol_type, + const CircuitLibrary& circuit_lib, + const CircuitModelId& config_mem_circuit_model) { + size_t num_err = 0; + + switch (config_protocol_type) { + case CONFIG_MEM_SCAN_CHAIN: + num_err = check_ccff_circuit_model_ports(circuit_lib, + config_mem_circuit_model); + break; + case CONFIG_MEM_STANDALONE: + case CONFIG_MEM_MEMORY_BANK: + case CONFIG_MEM_FRAME_BASED: + num_err = check_sram_circuit_model_ports(circuit_lib, + config_mem_circuit_model, + true); + + break; + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid type of configuration protocol!\n"); + return false; + } + + VTR_LOG("Found %ld errors when checking configurable memory circuit models!\n", + num_err); + return (0 == num_err); +} + } /* end namespace openfpga */ diff --git a/openfpga/src/utils/circuit_library_utils.h b/openfpga/src/utils/circuit_library_utils.h index 7c5552304..b03c03156 100644 --- a/openfpga/src/utils/circuit_library_utils.h +++ b/openfpga/src/utils/circuit_library_utils.h @@ -38,6 +38,10 @@ std::vector find_circuit_library_global_ports(const CircuitLibrar std::vector find_circuit_library_unique_verilog_netlists(const CircuitLibrary& circuit_lib); +bool check_configurable_memory_circuit_model(const e_config_protocol_type& config_protocol_type, + const CircuitLibrary& circuit_lib, + const CircuitModelId& config_mem_circuit_model); + } /* end namespace openfpga */ #endif From 8864920460aea5b60e52bf0754e064d6dc5e9e5c Mon Sep 17 00:00:00 2001 From: tangxifan Date: Mon, 25 May 2020 22:15:16 -0600 Subject: [PATCH 562/645] add frame-based memory module builder --- openfpga/src/base/openfpga_build_fabric.cpp | 3 +- openfpga/src/base/openfpga_context.h | 6 + openfpga/src/base/openfpga_naming.cpp | 14 ++ openfpga/src/base/openfpga_naming.h | 3 + openfpga/src/base/openfpga_reserved_words.h | 6 + openfpga/src/fabric/build_decoder_modules.cpp | 59 +++++- openfpga/src/fabric/build_decoder_modules.h | 5 + openfpga/src/fabric/build_device_module.cpp | 7 +- openfpga/src/fabric/build_device_module.h | 3 +- openfpga/src/fabric/build_memory_modules.cpp | 195 +++++++++++++++++- openfpga/src/fabric/build_memory_modules.h | 2 + openfpga/src/utils/module_manager_utils.cpp | 60 ++++++ openfpga/src/utils/module_manager_utils.h | 9 + 13 files changed, 361 insertions(+), 11 deletions(-) diff --git a/openfpga/src/base/openfpga_build_fabric.cpp b/openfpga/src/base/openfpga_build_fabric.cpp index 708bef124..b1a1fe118 100644 --- a/openfpga/src/base/openfpga_build_fabric.cpp +++ b/openfpga/src/base/openfpga_build_fabric.cpp @@ -76,8 +76,9 @@ int build_fabric(OpenfpgaContext& openfpga_ctx, VTR_LOG("\n"); openfpga_ctx.mutable_module_graph() = build_device_module_graph(openfpga_ctx.mutable_io_location_map(), - g_vpr_ctx.device(), + openfpga_ctx.mutable_decoder_lib(), const_cast(openfpga_ctx), + g_vpr_ctx.device(), cmd_context.option_enable(cmd, opt_compress_routing), cmd_context.option_enable(cmd, opt_duplicate_grid_pin), cmd_context.option_enable(cmd, opt_verbose)); diff --git a/openfpga/src/base/openfpga_context.h b/openfpga/src/base/openfpga_context.h index 2ea114410..8c025687f 100644 --- a/openfpga/src/base/openfpga_context.h +++ b/openfpga/src/base/openfpga_context.h @@ -10,6 +10,7 @@ #include "vpr_placement_annotation.h" #include "vpr_routing_annotation.h" #include "mux_library.h" +#include "decoder_library.h" #include "tile_direct.h" #include "module_manager.h" #include "netlist_manager.h" @@ -55,6 +56,7 @@ class OpenfpgaContext : public Context { const openfpga::VprRoutingAnnotation& vpr_routing_annotation() const { return vpr_routing_annotation_; } const openfpga::DeviceRRGSB& device_rr_gsb() const { return device_rr_gsb_; } const openfpga::MuxLibrary& mux_lib() const { return mux_lib_; } + const openfpga::DecoderLibrary& decoder_lib() const { return decoder_lib_; } const openfpga::TileDirect& tile_direct() const { return tile_direct_; } const openfpga::ModuleManager& module_graph() const { return module_graph_; } const openfpga::FlowManager& flow_manager() const { return flow_manager_; } @@ -72,6 +74,7 @@ class OpenfpgaContext : public Context { openfpga::VprRoutingAnnotation& mutable_vpr_routing_annotation() { return vpr_routing_annotation_; } openfpga::DeviceRRGSB& mutable_device_rr_gsb() { return device_rr_gsb_; } openfpga::MuxLibrary& mutable_mux_lib() { return mux_lib_; } + openfpga::DecoderLibrary& mutable_decoder_lib() { return decoder_lib_; } openfpga::TileDirect& mutable_tile_direct() { return tile_direct_; } openfpga::ModuleManager& mutable_module_graph() { return module_graph_; } openfpga::FlowManager& mutable_flow_manager() { return flow_manager_; } @@ -105,6 +108,9 @@ class OpenfpgaContext : public Context { /* Library of physical implmentation of routing multiplexers */ openfpga::MuxLibrary mux_lib_; + /* Library of physical implmentation of decoders */ + openfpga::DecoderLibrary decoder_lib_; + /* Inner/inter-column/row tile direct connections */ openfpga::TileDirect tile_direct_; diff --git a/openfpga/src/base/openfpga_naming.cpp b/openfpga/src/base/openfpga_naming.cpp index 74750fca5..bf82a1d7c 100644 --- a/openfpga/src/base/openfpga_naming.cpp +++ b/openfpga/src/base/openfpga_naming.cpp @@ -142,6 +142,20 @@ std::string generate_mux_local_decoder_subckt_name(const size_t& addr_size, return subckt_name; } +/************************************************ + * Generate the module name of a decoder + * for frame-based memories + ***********************************************/ +std::string generate_frame_memory_decoder_subckt_name(const size_t& addr_size, + const size_t& data_size) { + std::string subckt_name = "frame_decoder"; + subckt_name += std::to_string(addr_size); + subckt_name += "to"; + subckt_name += std::to_string(data_size); + + return subckt_name; +} + /************************************************ * Generate the module name of a routing track wire ***********************************************/ diff --git a/openfpga/src/base/openfpga_naming.h b/openfpga/src/base/openfpga_naming.h index 9896dddd4..d3cfb04f0 100644 --- a/openfpga/src/base/openfpga_naming.h +++ b/openfpga/src/base/openfpga_naming.h @@ -50,6 +50,9 @@ std::string generate_mux_branch_subckt_name(const CircuitLibrary& circuit_lib, std::string generate_mux_local_decoder_subckt_name(const size_t& addr_size, const size_t& data_size); +std::string generate_frame_memory_decoder_subckt_name(const size_t& addr_size, + const size_t& data_size); + std::string generate_segment_wire_subckt_name(const std::string& wire_model_name, const size_t& segment_id); diff --git a/openfpga/src/base/openfpga_reserved_words.h b/openfpga/src/base/openfpga_reserved_words.h index be47315ba..c556a19b1 100644 --- a/openfpga/src/base/openfpga_reserved_words.h +++ b/openfpga/src/base/openfpga_reserved_words.h @@ -30,6 +30,12 @@ constexpr char* GRID_MUX_INSTANCE_PREFIX = "mux_"; constexpr char* SWITCH_BLOCK_MUX_INSTANCE_PREFIX = "mux_"; constexpr char* CONNECTION_BLOCK_MUX_INSTANCE_PREFIX = "mux_"; +/* Decoder naming constant strings */ +constexpr char* DECODER_ENABLE_PORT_NAME = "enable"; +constexpr char* DECODER_ADDRESS_PORT_NAME = "address"; +constexpr char* DECODER_DATA_PORT_NAME = "data"; +constexpr char* DECODER_DATA_INV_PORT_NAME = "data_inv"; + /* Inverted port naming */ constexpr char* INV_PORT_POSTFIX = "_inv"; diff --git a/openfpga/src/fabric/build_decoder_modules.cpp b/openfpga/src/fabric/build_decoder_modules.cpp index 06defc007..a2accae89 100644 --- a/openfpga/src/fabric/build_decoder_modules.cpp +++ b/openfpga/src/fabric/build_decoder_modules.cpp @@ -9,6 +9,7 @@ #include "vtr_assert.h" #include "vtr_time.h" +#include "openfpga_reserved_words.h" #include "openfpga_naming.h" #include "decoder_library_utils.h" #include "module_manager_utils.h" @@ -18,6 +19,61 @@ /* begin namespace openfpga */ namespace openfpga { +/*************************************************************************************** + * Create a module for a decoder with a given output size + * + * Data input + * | | ... | + * v v v + * +-----------+ + * / \ + * enable-->/ Decoder \ + * +-----------------+ + * | | | ... | | | + * v v v v v v + * Data Outputs + * + * The outputs are assumes to be one-hot codes (at most only one '1' exist) + * Considering this fact, there are only num_of_outputs conditions to be encoded. + * Therefore, the number of inputs is ceil(log(num_of_outputs)/log(2)) + ***************************************************************************************/ +ModuleId build_frame_memory_decoder_module(ModuleManager& module_manager, + const DecoderLibrary& decoder_lib, + const DecoderId& decoder) { + /* Get the number of inputs */ + size_t addr_size = decoder_lib.addr_size(decoder); + size_t data_size = decoder_lib.data_size(decoder); + + /* Create a name for the local encoder */ + std::string module_name = generate_frame_memory_decoder_subckt_name(addr_size, data_size); + + /* Create a Verilog Module based on the circuit model, and add to module manager */ + ModuleId module_id = module_manager.add_module(module_name); + VTR_ASSERT(true == module_manager.valid_module_id(module_id)); + + /* Add enable port */ + BasicPort en_port(std::string(DECODER_ENABLE_PORT_NAME), 1); + module_manager.add_port(module_id, en_port, ModuleManager::MODULE_INPUT_PORT); + /* Add each input port */ + BasicPort addr_port(std::string(DECODER_ADDRESS_PORT_NAME), addr_size); + module_manager.add_port(module_id, addr_port, ModuleManager::MODULE_INPUT_PORT); + /* Add each output port */ + BasicPort data_port(std::string(DECODER_DATA_PORT_NAME), data_size); + module_manager.add_port(module_id, data_port, ModuleManager::MODULE_OUTPUT_PORT); + + /* Data port is registered. It should be outputted as + * output reg [lsb:msb] data + */ + module_manager.set_port_is_register(module_id, data_port.get_name(), true); + /* Add data_in port */ + if (true == decoder_lib.use_data_inv_port(decoder)) { + BasicPort data_inv_port(std::string(DECODER_DATA_INV_PORT_NAME), data_size); + module_manager.add_port(module_id, data_inv_port, ModuleManager::MODULE_OUTPUT_PORT); + } + + return module_id; +} + /*************************************************************************************** * Create a module for a decoder with a given output size * @@ -44,7 +100,7 @@ void build_mux_local_decoder_module(ModuleManager& module_manager, size_t addr_size = decoder_lib.addr_size(decoder); size_t data_size = decoder_lib.data_size(decoder); - /* TODO: create a name for the local encoder */ + /* Create a name for the local encoder */ std::string module_name = generate_mux_local_decoder_subckt_name(addr_size, data_size); /* Create a Verilog Module based on the circuit model, and add to module manager */ @@ -67,7 +123,6 @@ void build_mux_local_decoder_module(ModuleManager& module_manager, module_manager.add_port(module_id, data_inv_port, ModuleManager::MODULE_OUTPUT_PORT); } - /*************************************************************************************** * This function will generate all the unique Verilog modules of local decoders for * the multiplexers used in a FPGA fabric diff --git a/openfpga/src/fabric/build_decoder_modules.h b/openfpga/src/fabric/build_decoder_modules.h index 842aef8c4..bf18e47d4 100644 --- a/openfpga/src/fabric/build_decoder_modules.h +++ b/openfpga/src/fabric/build_decoder_modules.h @@ -5,6 +5,7 @@ * Include header files that are required by function declaration *******************************************************************/ #include "module_manager.h" +#include "decoder_library.h" #include "mux_library.h" #include "circuit_library.h" @@ -15,6 +16,10 @@ /* begin namespace openfpga */ namespace openfpga { +ModuleId build_frame_memory_decoder_module(ModuleManager& module_manager, + const DecoderLibrary& decoder_lib, + const DecoderId& decoder); + void build_mux_local_decoder_modules(ModuleManager& module_manager, const MuxLibrary& mux_lib, const CircuitLibrary& circuit_lib); diff --git a/openfpga/src/fabric/build_device_module.cpp b/openfpga/src/fabric/build_device_module.cpp index 9680285fa..1d65fef7c 100644 --- a/openfpga/src/fabric/build_device_module.cpp +++ b/openfpga/src/fabric/build_device_module.cpp @@ -27,8 +27,9 @@ namespace openfpga { * for a FPGA fabric *******************************************************************/ ModuleManager build_device_module_graph(IoLocationMap& io_location_map, - const DeviceContext& vpr_device_ctx, + DecoderLibrary& decoder_lib, const OpenfpgaContext& openfpga_ctx, + const DeviceContext& vpr_device_ctx, const bool& compress_routing, const bool& duplicate_grid_pin, const bool& verbose) { @@ -66,7 +67,9 @@ ModuleManager build_device_module_graph(IoLocationMap& io_location_map, build_wire_modules(module_manager, openfpga_ctx.arch().circuit_lib); /* Build memory modules */ - build_memory_modules(module_manager, openfpga_ctx.mux_lib(), + build_memory_modules(module_manager, + decoder_lib, + openfpga_ctx.mux_lib(), openfpga_ctx.arch().circuit_lib, openfpga_ctx.arch().config_protocol.type()); diff --git a/openfpga/src/fabric/build_device_module.h b/openfpga/src/fabric/build_device_module.h index b8f8c0ac8..75a9f982c 100644 --- a/openfpga/src/fabric/build_device_module.h +++ b/openfpga/src/fabric/build_device_module.h @@ -15,8 +15,9 @@ namespace openfpga { ModuleManager build_device_module_graph(IoLocationMap& io_location_map, - const DeviceContext& vpr_device_ctx, + DecoderLibrary& decoder_lib, const OpenfpgaContext& openfpga_ctx, + const DeviceContext& vpr_device_ctx, const bool& compress_routing, const bool& duplicate_grid_pin, const bool& verbose); diff --git a/openfpga/src/fabric/build_memory_modules.cpp b/openfpga/src/fabric/build_memory_modules.cpp index 299bf6d70..bbdb01fef 100644 --- a/openfpga/src/fabric/build_memory_modules.cpp +++ b/openfpga/src/fabric/build_memory_modules.cpp @@ -15,12 +15,14 @@ #include "mux_graph.h" #include "module_manager.h" #include "circuit_library_utils.h" +#include "decoder_library_utils.h" #include "module_manager_utils.h" #include "mux_utils.h" #include "openfpga_reserved_words.h" #include "openfpga_naming.h" +#include "build_decoder_modules.h" #include "build_memory_modules.h" /* begin namespace openfpga */ @@ -553,6 +555,178 @@ void build_memory_bank_module(ModuleManager& module_manager, add_module_global_ports_from_child_modules(module_manager, mem_module); } +/********************************************************************* + * Frame-based Memory organization + * + * EN Address Data + * | | | + * v v v + * +------------------------------------+ + * | Address Decoder | + * +------------------------------------+ + * | | | + * v v v + * +-------+ +-------+ +-------+ + * | SRAM | | SRAM | ... | SRAM | + * | [0] | | [1] | | [N-1] | + * +-------+ +-------+ +-------+ + * | | ... | + * v v v + * +------------------------------------+ + * | Multiplexer Configuration port | + * + ********************************************************************/ +static +void build_frame_memory_module(ModuleManager& module_manager, + DecoderLibrary& frame_decoder_lib, + const CircuitLibrary& circuit_lib, + const std::string& module_name, + const CircuitModelId& sram_model, + const size_t& num_mems) { + + /* Get the global ports required by the SRAM */ + std::vector global_port_types; + global_port_types.push_back(CIRCUIT_MODEL_PORT_CLOCK); + global_port_types.push_back(CIRCUIT_MODEL_PORT_INPUT); + std::vector sram_global_ports = circuit_lib.model_global_ports_by_type(sram_model, global_port_types, true, false); + /* Get the input ports from the SRAM */ + std::vector sram_input_ports = circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_INPUT, true); + /* A SRAM cell with BL/WL should not have any input */ + VTR_ASSERT( 0 == sram_input_ports.size() ); + + /* Get the output ports from the SRAM */ + std::vector sram_output_ports = circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_OUTPUT, true); + + /* Get the BL/WL ports from the SRAM + * Here, we consider that the WL port will be EN signal of a SRAM + * and the BL port will be the data_in signal of a SRAM + */ + std::vector sram_bl_ports = circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_BL, true); + std::vector sram_blb_ports = circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_BLB, true); + std::vector sram_wl_ports = circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_WL, true); + std::vector sram_wlb_ports = circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_WLB, true); + + /* We do NOT expect any BLB port here!!! + * TODO: to suppor this, we need an inverter circuit model to be specified by users !!! + */ + VTR_ASSERT(1 == sram_bl_ports.size()); + VTR_ASSERT(1 == circuit_lib.port_size(sram_bl_ports[0])); + VTR_ASSERT(1 == sram_wl_ports.size()); + VTR_ASSERT(1 == circuit_lib.port_size(sram_wl_ports[0])); + VTR_ASSERT(0 == sram_blb_ports.size()); + + /* Create a module and add to the module manager */ + ModuleId mem_module = module_manager.add_module(module_name); + VTR_ASSERT(true == module_manager.valid_module_id(mem_module)); + + /* Find the specification of the decoder: + * Size of address port and data input + */ + size_t addr_size = find_mux_local_decoder_addr_size(num_mems); + /* Data input should match the WL (data_in) of a SRAM */ + size_t data_size = num_mems * circuit_lib.port_size(sram_bl_ports[0]); + bool use_data_inv = (0 < sram_blb_ports.size()); + + /* Search the decoder library + * If we find one, we use the module. + * Otherwise, we create one and add it to the decoder library + */ + DecoderId decoder_id = frame_decoder_lib.find_decoder(addr_size, data_size, true, true, use_data_inv); + if (DecoderId::INVALID() == decoder_id) { + decoder_id = frame_decoder_lib.add_decoder(addr_size, data_size, true, true, use_data_inv); + } + VTR_ASSERT(DecoderId::INVALID() != decoder_id); + + /* Create a module if not existed yet */ + std::string decoder_module_name = generate_frame_memory_decoder_subckt_name(addr_size, data_size); + ModuleId decoder_module = module_manager.find_module(decoder_module_name); + if (ModuleId::INVALID() == decoder_module) { + decoder_module = build_frame_memory_decoder_module(module_manager, + frame_decoder_lib, + decoder_id); + } + VTR_ASSERT(ModuleId::INVALID() != decoder_module); + + /* Add module ports */ + /* Input: Enable port */ + BasicPort en_port(std::string(DECODER_ENABLE_PORT_NAME), 1); + ModulePortId mem_en_port = module_manager.add_port(mem_module, en_port, ModuleManager::MODULE_INPUT_PORT); + + /* Input: Address port */ + BasicPort addr_port(std::string(DECODER_ADDRESS_PORT_NAME), addr_size); + ModulePortId mem_addr_port = module_manager.add_port(mem_module, addr_port, ModuleManager::MODULE_INPUT_PORT); + + /* Input: Data port */ + BasicPort data_port(std::string(DECODER_DATA_PORT_NAME), data_size); + ModulePortId mem_data_port = module_manager.add_port(mem_module, data_port, ModuleManager::MODULE_INPUT_PORT); + + /* Add each output port: port width should match the number of memories */ + for (const auto& port : sram_output_ports) { + BasicPort output_port(circuit_lib.port_prefix(port), num_mems * circuit_lib.port_size(port)); + module_manager.add_port(mem_module, output_port, ModuleManager::MODULE_OUTPUT_PORT); + } + + /* Instanciate the decoder module here */ + VTR_ASSERT(0 == module_manager.num_instance(mem_module, decoder_module)); + module_manager.add_child_module(mem_module, decoder_module); + + /* Find the sram module in the module manager */ + ModuleId sram_mem_module = module_manager.find_module(circuit_lib.model_name(sram_model)); + + /* Build module nets */ + /* Wire enable port to decoder enable port */ + ModulePortId decoder_en_port = module_manager.find_module_port(decoder_module, std::string(DECODER_ENABLE_PORT_NAME)); + add_module_bus_nets(module_manager, mem_module, + mem_module, 0, mem_en_port, + decoder_module, 0, decoder_en_port); + + /* Wire address port to decoder address port */ + ModulePortId decoder_addr_port = module_manager.find_module_port(decoder_module, std::string(DECODER_ADDRESS_PORT_NAME)); + add_module_bus_nets(module_manager, mem_module, + mem_module, 0, mem_addr_port, + decoder_module, 0, decoder_addr_port); + + /* Instanciate each submodule */ + for (size_t i = 0; i < num_mems; ++i) { + /* Memory seed module instanciation */ + size_t sram_instance = module_manager.num_instance(mem_module, sram_mem_module); + module_manager.add_child_module(mem_module, sram_mem_module); + + /* Wire data_in port to SRAM BL port */ + ModulePortId sram_bl_port = module_manager.find_module_port(sram_mem_module, circuit_lib.port_lib_name(sram_bl_ports[0])); + add_module_bus_nets(module_manager, mem_module, + mem_module, 0, mem_data_port, + sram_mem_module, sram_instance, sram_bl_port); + + /* Wire decoder data_out port to sram WL ports */ + ModulePortId sram_wl_port = module_manager.find_module_port(sram_mem_module, circuit_lib.port_lib_name(sram_wl_ports[0])); + ModulePortId decoder_data_port = module_manager.find_module_port(decoder_module, std::string(DECODER_DATA_PORT_NAME)); + ModuleNetId wl_net = module_manager.create_module_net(mem_module); + /* Source node of the input net is the input of memory module */ + module_manager.add_module_net_source(mem_module, wl_net, decoder_module, 0, decoder_data_port, sram_instance); + module_manager.add_module_net_sink(mem_module, wl_net, sram_mem_module, sram_instance, sram_wl_port, 0); + + /* Optional: Wire decoder data_out inverted port to sram WLB ports */ + if (true == use_data_inv) { + ModulePortId sram_wlb_port = module_manager.find_module_port(sram_mem_module, circuit_lib.port_lib_name(sram_wlb_ports[0])); + ModulePortId decoder_data_inv_port = module_manager.find_module_port(decoder_module, std::string(DECODER_DATA_INV_PORT_NAME)); + ModuleNetId wlb_net = module_manager.create_module_net(mem_module); + /* Source node of the input net is the input of memory module */ + module_manager.add_module_net_source(mem_module, wlb_net, decoder_module, 0, decoder_data_inv_port, sram_instance); + module_manager.add_module_net_sink(mem_module, wlb_net, sram_mem_module, sram_instance, sram_wlb_port, 0); + } + + /* Wire inputs of parent module to outputs of child modules */ + add_module_output_nets_to_mem_modules(module_manager, mem_module, circuit_lib, sram_output_ports, sram_mem_module, i, sram_instance); + } + + /* Add global ports to the pb_module: + * This is a much easier job after adding sub modules (instances), + * we just need to find all the global ports from the child modules and build a list of it + */ + add_module_global_ports_from_child_modules(module_manager, mem_module); +} + /********************************************************************* * Generate Verilog modules for the memories that are used @@ -566,6 +740,7 @@ void build_memory_bank_module(ModuleManager& module_manager, ********************************************************************/ static void build_memory_module(ModuleManager& module_manager, + DecoderLibrary& arch_decoder_lib, const CircuitLibrary& circuit_lib, const e_config_protocol_type& sram_orgz_type, const std::string& module_name, @@ -574,7 +749,7 @@ void build_memory_module(ModuleManager& module_manager, switch (sram_orgz_type) { case CONFIG_MEM_STANDALONE: build_memory_standalone_module(module_manager, circuit_lib, - module_name, sram_model, num_mems); + module_name, sram_model, num_mems); break; case CONFIG_MEM_SCAN_CHAIN: build_memory_chain_module(module_manager, circuit_lib, @@ -584,8 +759,13 @@ void build_memory_module(ModuleManager& module_manager, build_memory_bank_module(module_manager, circuit_lib, module_name, sram_model, num_mems); break; + case CONFIG_MEM_FRAME_BASED: + build_frame_memory_module(module_manager, arch_decoder_lib, circuit_lib, + module_name, sram_model, num_mems); + break; default: - VTR_LOGF_ERROR(__FILE__, __LINE__, "Invalid SRAM organization!\n"); + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid configurable memory organization!\n"); exit(1); } } @@ -606,6 +786,7 @@ void build_memory_module(ModuleManager& module_manager, ********************************************************************/ static void build_mux_memory_module(ModuleManager& module_manager, + DecoderLibrary& arch_decoder_lib, const CircuitLibrary& circuit_lib, const e_config_protocol_type& sram_orgz_type, const CircuitModelId& mux_model, @@ -626,7 +807,8 @@ void build_mux_memory_module(ModuleManager& module_manager, std::vector sram_models = find_circuit_sram_models(circuit_lib, mux_model); VTR_ASSERT( 1 == sram_models.size() ); - build_memory_module(module_manager, circuit_lib, sram_orgz_type, module_name, sram_models[0], num_config_bits); + build_memory_module(module_manager, arch_decoder_lib, + circuit_lib, sram_orgz_type, module_name, sram_models[0], num_config_bits); break; } case CIRCUIT_MODEL_DESIGN_RRAM: @@ -659,6 +841,7 @@ void build_mux_memory_module(ModuleManager& module_manager, * memory-bank organization for the memories. ********************************************************************/ void build_memory_modules(ModuleManager& module_manager, + DecoderLibrary& arch_decoder_lib, const MuxLibrary& mux_lib, const CircuitLibrary& circuit_lib, const e_config_protocol_type& sram_orgz_type) { @@ -676,7 +859,8 @@ void build_memory_modules(ModuleManager& module_manager, continue; } /* Create a Verilog module for the memories used by the multiplexer */ - build_mux_memory_module(module_manager, circuit_lib, sram_orgz_type, mux_model, mux_graph); + build_mux_memory_module(module_manager, arch_decoder_lib, + circuit_lib, sram_orgz_type, mux_model, mux_graph); } /* Create the memory circuits for non-MUX circuit models. @@ -708,7 +892,8 @@ void build_memory_modules(ModuleManager& module_manager, std::string module_name = generate_memory_module_name(circuit_lib, model, sram_models[0], std::string(MEMORY_MODULE_POSTFIX)); /* Create a Verilog module for the memories used by the circuit model */ - build_memory_module(module_manager, circuit_lib, sram_orgz_type, module_name, sram_models[0], num_mems); + build_memory_module(module_manager, arch_decoder_lib, + circuit_lib, sram_orgz_type, module_name, sram_models[0], num_mems); } } diff --git a/openfpga/src/fabric/build_memory_modules.h b/openfpga/src/fabric/build_memory_modules.h index 2728d1d81..3551426a2 100644 --- a/openfpga/src/fabric/build_memory_modules.h +++ b/openfpga/src/fabric/build_memory_modules.h @@ -4,6 +4,7 @@ /******************************************************************** * Include header files that are required by function declaration *******************************************************************/ +#include "decoder_library.h" #include "circuit_library.h" #include "mux_library.h" #include "module_manager.h" @@ -16,6 +17,7 @@ namespace openfpga { void build_memory_modules(ModuleManager& module_manager, + DecoderLibrary& arch_decoder_lib, const MuxLibrary& mux_lib, const CircuitLibrary& circuit_lib, const e_config_protocol_type& sram_orgz_type); diff --git a/openfpga/src/utils/module_manager_utils.cpp b/openfpga/src/utils/module_manager_utils.cpp index b1408dcad..44e13c65a 100644 --- a/openfpga/src/utils/module_manager_utils.cpp +++ b/openfpga/src/utils/module_manager_utils.cpp @@ -1227,6 +1227,66 @@ size_t find_module_num_config_bits_from_child_modules(ModuleManager& module_mana return num_config_bits; } +/******************************************************************** + * Add a bus of nets to a module (cur_module_id) + * Note: + * - both src and des module should exist in the module manager + * - src_module should be the cur_module or a child of it + * - des_module should be the cur_module or a child of it + * - src_instance should be valid and des_instance should be valid as well + * - src port size should match the des port size + *******************************************************************/ +void add_module_bus_nets(ModuleManager& module_manager, + const ModuleId& cur_module_id, + const ModuleId& src_module_id, + const size_t& src_instance_id, + const ModulePortId& src_module_port_id, + const ModuleId& des_module_id, + const size_t& des_instance_id, + const ModulePortId& des_module_port_id) { + + VTR_ASSERT(true == module_manager.valid_module_id(cur_module_id)); + VTR_ASSERT(true == module_manager.valid_module_id(src_module_id)); + VTR_ASSERT(true == module_manager.valid_module_id(des_module_id)); + + VTR_ASSERT(true == module_manager.valid_module_port_id(src_module_id, src_module_port_id)); + VTR_ASSERT(true == module_manager.valid_module_port_id(des_module_id, des_module_port_id)); + + if (src_module_id == cur_module_id) { + VTR_ASSERT(0 == src_instance_id); + } else { + VTR_ASSERT(src_instance_id < module_manager.num_instance(cur_module_id, src_module_id)); + } + + if (des_module_id == cur_module_id) { + VTR_ASSERT(0 == des_instance_id); + } else { + VTR_ASSERT(des_instance_id < module_manager.num_instance(cur_module_id, des_module_id)); + } + + const BasicPort& src_port = module_manager.module_port(src_module_id, src_module_port_id); + const BasicPort& des_port = module_manager.module_port(des_module_id, des_module_port_id); + + if (src_port.get_width() != des_port.get_width()) { + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Unmatched port size: src_port is %lu while des_port is %lu!\n"); + exit(1); + } + + /* Create a net for each pin */ + for (size_t pin_id = 0; pin_id < src_port.pins().size(); ++pin_id) { + ModuleNetId net = module_manager.module_instance_port_net(cur_module_id, + src_module_id, src_instance_id, + src_module_port_id, src_port.pins()[pin_id]); + if (ModuleNetId::INVALID() == net) { + net = module_manager.create_module_net(cur_module_id); + } + /* Configure the net source */ + module_manager.add_module_net_source(cur_module_id, net, src_module_id, src_instance_id, src_module_port_id, src_port.pins()[pin_id]); + /* Configure the net sink */ + module_manager.add_module_net_sink(cur_module_id, net, des_module_id, des_instance_id, des_module_port_id, des_port.pins()[pin_id]); + } +} /******************************************************************** * TODO: diff --git a/openfpga/src/utils/module_manager_utils.h b/openfpga/src/utils/module_manager_utils.h index fcc95b8ba..0ecdfd282 100644 --- a/openfpga/src/utils/module_manager_utils.h +++ b/openfpga/src/utils/module_manager_utils.h @@ -129,6 +129,15 @@ size_t find_module_num_config_bits_from_child_modules(ModuleManager& module_mana const CircuitModelId& sram_model, const e_config_protocol_type& sram_orgz_type); +void add_module_bus_nets(ModuleManager& module_manager, + const ModuleId& cur_module_id, + const ModuleId& src_module_id, + const size_t& src_instance_id, + const ModulePortId& src_module_port_id, + const ModuleId& des_module_id, + const size_t& des_instance_id, + const ModulePortId& des_module_port_id); + } /* end namespace openfpga */ #endif From 290dd1a8a68cfa893e77d57075a7c6c5023218ad Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 26 May 2020 18:55:55 -0600 Subject: [PATCH 563/645] add frame decoder builder to all the module graph builder except the top-level --- openfpga/src/base/openfpga_naming.cpp | 17 +- openfpga/src/base/openfpga_reserved_words.h | 5 +- openfpga/src/fabric/build_decoder_modules.cpp | 4 +- openfpga/src/fabric/build_device_module.cpp | 6 +- openfpga/src/fabric/build_grid_modules.cpp | 27 +- openfpga/src/fabric/build_grid_modules.h | 2 + openfpga/src/fabric/build_memory_modules.cpp | 6 +- openfpga/src/fabric/build_routing_modules.cpp | 18 +- openfpga/src/fabric/build_routing_modules.h | 3 + openfpga/src/fabric/build_top_module.cpp | 14 +- .../src/fabric/build_top_module_memory.cpp | 3 + openfpga/src/utils/memory_utils.cpp | 4 + openfpga/src/utils/module_manager_utils.cpp | 256 +++++++++++++++++- openfpga/src/utils/module_manager_utils.h | 6 + 14 files changed, 337 insertions(+), 34 deletions(-) diff --git a/openfpga/src/base/openfpga_naming.cpp b/openfpga/src/base/openfpga_naming.cpp index bf82a1d7c..5c9c81eb3 100644 --- a/openfpga/src/base/openfpga_naming.cpp +++ b/openfpga/src/base/openfpga_naming.cpp @@ -798,8 +798,23 @@ std::string generate_sram_port_name(const e_config_protocol_type& sram_orgz_type port_name = std::string("wlb"); } break; + case CONFIG_MEM_FRAME_BASED: + /* Only one input port is required, which is the address port + * + * EN ADDR DATA_IN + * | | | + * v v v + * +---------------------------------+ + * | Frame decoder | + * +---------------------------------+ + * + */ + VTR_ASSERT(port_type == CIRCUIT_MODEL_PORT_INPUT); + port_name = std::string(DECODER_ADDRESS_PORT_NAME); + break; default: - VTR_LOG_ERROR("Invalid type of SRAM organization !\n"); + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid type of SRAM organization !\n"); exit(1); } diff --git a/openfpga/src/base/openfpga_reserved_words.h b/openfpga/src/base/openfpga_reserved_words.h index c556a19b1..132858687 100644 --- a/openfpga/src/base/openfpga_reserved_words.h +++ b/openfpga/src/base/openfpga_reserved_words.h @@ -33,8 +33,9 @@ constexpr char* CONNECTION_BLOCK_MUX_INSTANCE_PREFIX = "mux_"; /* Decoder naming constant strings */ constexpr char* DECODER_ENABLE_PORT_NAME = "enable"; constexpr char* DECODER_ADDRESS_PORT_NAME = "address"; -constexpr char* DECODER_DATA_PORT_NAME = "data"; -constexpr char* DECODER_DATA_INV_PORT_NAME = "data_inv"; +constexpr char* DECODER_DATA_IN_PORT_NAME = "data_in"; +constexpr char* DECODER_DATA_OUT_PORT_NAME = "data_out"; +constexpr char* DECODER_DATA_OUT_INV_PORT_NAME = "data_out_inv"; /* Inverted port naming */ constexpr char* INV_PORT_POSTFIX = "_inv"; diff --git a/openfpga/src/fabric/build_decoder_modules.cpp b/openfpga/src/fabric/build_decoder_modules.cpp index a2accae89..ada36e3e7 100644 --- a/openfpga/src/fabric/build_decoder_modules.cpp +++ b/openfpga/src/fabric/build_decoder_modules.cpp @@ -58,7 +58,7 @@ ModuleId build_frame_memory_decoder_module(ModuleManager& module_manager, BasicPort addr_port(std::string(DECODER_ADDRESS_PORT_NAME), addr_size); module_manager.add_port(module_id, addr_port, ModuleManager::MODULE_INPUT_PORT); /* Add each output port */ - BasicPort data_port(std::string(DECODER_DATA_PORT_NAME), data_size); + BasicPort data_port(std::string(DECODER_DATA_OUT_PORT_NAME), data_size); module_manager.add_port(module_id, data_port, ModuleManager::MODULE_OUTPUT_PORT); /* Data port is registered. It should be outputted as @@ -67,7 +67,7 @@ ModuleId build_frame_memory_decoder_module(ModuleManager& module_manager, module_manager.set_port_is_register(module_id, data_port.get_name(), true); /* Add data_in port */ if (true == decoder_lib.use_data_inv_port(decoder)) { - BasicPort data_inv_port(std::string(DECODER_DATA_INV_PORT_NAME), data_size); + BasicPort data_inv_port(std::string(DECODER_DATA_OUT_INV_PORT_NAME), data_size); module_manager.add_port(module_id, data_inv_port, ModuleManager::MODULE_OUTPUT_PORT); } diff --git a/openfpga/src/fabric/build_device_module.cpp b/openfpga/src/fabric/build_device_module.cpp index 1d65fef7c..ee2547d7d 100644 --- a/openfpga/src/fabric/build_device_module.cpp +++ b/openfpga/src/fabric/build_device_module.cpp @@ -74,7 +74,9 @@ ModuleManager build_device_module_graph(IoLocationMap& io_location_map, openfpga_ctx.arch().config_protocol.type()); /* Build grid and programmable block modules */ - build_grid_modules(module_manager, vpr_device_ctx, + build_grid_modules(module_manager, + decoder_lib, + vpr_device_ctx, openfpga_ctx.vpr_device_annotation(), openfpga_ctx.arch().circuit_lib, openfpga_ctx.mux_lib(), @@ -83,6 +85,7 @@ ModuleManager build_device_module_graph(IoLocationMap& io_location_map, if (true == compress_routing) { build_unique_routing_modules(module_manager, + decoder_lib, vpr_device_ctx, openfpga_ctx.vpr_device_annotation(), openfpga_ctx.device_rr_gsb(), @@ -92,6 +95,7 @@ ModuleManager build_device_module_graph(IoLocationMap& io_location_map, } else { VTR_ASSERT_SAFE(false == compress_routing); build_flatten_routing_modules(module_manager, + decoder_lib, vpr_device_ctx, openfpga_ctx.vpr_device_annotation(), openfpga_ctx.device_rr_gsb(), diff --git a/openfpga/src/fabric/build_grid_modules.cpp b/openfpga/src/fabric/build_grid_modules.cpp index 12090d43d..b189a20fe 100644 --- a/openfpga/src/fabric/build_grid_modules.cpp +++ b/openfpga/src/fabric/build_grid_modules.cpp @@ -226,6 +226,7 @@ void add_primitive_module_fpga_global_io_port(ModuleManager& module_manager, *******************************************************************/ static void build_primitive_block_module(ModuleManager& module_manager, + DecoderLibrary& decoder_lib, const VprDeviceAnnotation& device_annotation, const CircuitLibrary& circuit_lib, const e_config_protocol_type& sram_orgz_type, @@ -314,7 +315,7 @@ void build_primitive_block_module(ModuleManager& module_manager, * This is a one-shot addition that covers all the memory modules in this primitive module! */ if (0 < module_manager.configurable_children(primitive_module).size()) { - add_module_nets_memory_config_bus(module_manager, primitive_module, + add_module_nets_memory_config_bus(module_manager, decoder_lib, primitive_module, sram_orgz_type, circuit_lib.design_tech_type(sram_model)); } @@ -799,6 +800,7 @@ void add_module_pb_graph_interc(ModuleManager& module_manager, *******************************************************************/ static void rec_build_logical_tile_modules(ModuleManager& module_manager, + DecoderLibrary& decoder_lib, const VprDeviceAnnotation& device_annotation, const CircuitLibrary& circuit_lib, const MuxLibrary& mux_lib, @@ -821,7 +823,8 @@ void rec_build_logical_tile_modules(ModuleManager& module_manager, if (false == is_primitive_pb_type(physical_pb_type)) { for (int ipb = 0; ipb < physical_mode->num_pb_type_children; ++ipb) { /* Go recursive to visit the children */ - rec_build_logical_tile_modules(module_manager, device_annotation, + rec_build_logical_tile_modules(module_manager, decoder_lib, + device_annotation, circuit_lib, mux_lib, sram_orgz_type, sram_model, &(physical_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ipb][0]), @@ -831,7 +834,8 @@ void rec_build_logical_tile_modules(ModuleManager& module_manager, /* For leaf node, a primitive Verilog module will be generated */ if (true == is_primitive_pb_type(physical_pb_type)) { - build_primitive_block_module(module_manager, device_annotation, + build_primitive_block_module(module_manager, decoder_lib, + device_annotation, circuit_lib, sram_orgz_type, sram_model, physical_pb_graph_node, @@ -938,7 +942,7 @@ void rec_build_logical_tile_modules(ModuleManager& module_manager, * This is a one-shot addition that covers all the memory modules in this pb module! */ if (0 < module_manager.configurable_children(pb_module).size()) { - add_module_nets_memory_config_bus(module_manager, pb_module, + add_module_nets_memory_config_bus(module_manager, decoder_lib, pb_module, sram_orgz_type, circuit_lib.design_tech_type(sram_model)); } @@ -955,6 +959,7 @@ void rec_build_logical_tile_modules(ModuleManager& module_manager, *****************************************************************************/ static void build_physical_tile_module(ModuleManager& module_manager, + DecoderLibrary& decoder_lib, const CircuitLibrary& circuit_lib, const e_config_protocol_type& sram_orgz_type, const CircuitModelId& sram_model, @@ -1094,7 +1099,7 @@ void build_physical_tile_module(ModuleManager& module_manager, * This is a one-shot addition that covers all the memory modules in this pb module! */ if (0 < module_manager.configurable_children(grid_module).size()) { - add_module_nets_memory_config_bus(module_manager, grid_module, + add_module_nets_memory_config_bus(module_manager, decoder_lib, grid_module, sram_orgz_type, circuit_lib.design_tech_type(sram_model)); } @@ -1114,6 +1119,7 @@ void build_physical_tile_module(ModuleManager& module_manager, * - Only one module for each heterogeneous block ****************************************************************************/ void build_grid_modules(ModuleManager& module_manager, + DecoderLibrary& decoder_lib, const DeviceContext& device_ctx, const VprDeviceAnnotation& device_annotation, const CircuitLibrary& circuit_lib, @@ -1140,8 +1146,9 @@ void build_grid_modules(ModuleManager& module_manager, if (nullptr == logical_tile.pb_graph_head) { continue; } - rec_build_logical_tile_modules(module_manager, device_annotation, - circuit_lib, mux_lib, + rec_build_logical_tile_modules(module_manager, decoder_lib, + device_annotation, + circuit_lib, mux_lib, sram_orgz_type, sram_model, logical_tile.pb_graph_head, verbose); @@ -1169,7 +1176,8 @@ void build_grid_modules(ModuleManager& module_manager, std::set io_type_sides = find_physical_io_tile_located_sides(device_ctx.grid, &physical_tile); for (const e_side& io_type_side : io_type_sides) { - build_physical_tile_module(module_manager, circuit_lib, + build_physical_tile_module(module_manager, decoder_lib, + circuit_lib, sram_orgz_type, sram_model, &physical_tile, io_type_side, @@ -1178,7 +1186,8 @@ void build_grid_modules(ModuleManager& module_manager, } } else { /* For CLB and heterogenenous blocks */ - build_physical_tile_module(module_manager, circuit_lib, + build_physical_tile_module(module_manager, decoder_lib, + circuit_lib, sram_orgz_type, sram_model, &physical_tile, NUM_SIDES, diff --git a/openfpga/src/fabric/build_grid_modules.h b/openfpga/src/fabric/build_grid_modules.h index f2a8e967b..53e7f92cd 100644 --- a/openfpga/src/fabric/build_grid_modules.h +++ b/openfpga/src/fabric/build_grid_modules.h @@ -8,6 +8,7 @@ #include "vpr_device_annotation.h" #include "module_manager.h" #include "mux_library.h" +#include "decoder_library.h" /******************************************************************** * Function declaration @@ -17,6 +18,7 @@ namespace openfpga { void build_grid_modules(ModuleManager& module_manager, + DecoderLibrary& decoder_lib, const DeviceContext& device_ctx, const VprDeviceAnnotation& device_annotation, const CircuitLibrary& circuit_lib, diff --git a/openfpga/src/fabric/build_memory_modules.cpp b/openfpga/src/fabric/build_memory_modules.cpp index bbdb01fef..f8db73d7a 100644 --- a/openfpga/src/fabric/build_memory_modules.cpp +++ b/openfpga/src/fabric/build_memory_modules.cpp @@ -657,7 +657,7 @@ void build_frame_memory_module(ModuleManager& module_manager, ModulePortId mem_addr_port = module_manager.add_port(mem_module, addr_port, ModuleManager::MODULE_INPUT_PORT); /* Input: Data port */ - BasicPort data_port(std::string(DECODER_DATA_PORT_NAME), data_size); + BasicPort data_port(std::string(DECODER_DATA_IN_PORT_NAME), data_size); ModulePortId mem_data_port = module_manager.add_port(mem_module, data_port, ModuleManager::MODULE_INPUT_PORT); /* Add each output port: port width should match the number of memories */ @@ -700,7 +700,7 @@ void build_frame_memory_module(ModuleManager& module_manager, /* Wire decoder data_out port to sram WL ports */ ModulePortId sram_wl_port = module_manager.find_module_port(sram_mem_module, circuit_lib.port_lib_name(sram_wl_ports[0])); - ModulePortId decoder_data_port = module_manager.find_module_port(decoder_module, std::string(DECODER_DATA_PORT_NAME)); + ModulePortId decoder_data_port = module_manager.find_module_port(decoder_module, std::string(DECODER_DATA_OUT_PORT_NAME)); ModuleNetId wl_net = module_manager.create_module_net(mem_module); /* Source node of the input net is the input of memory module */ module_manager.add_module_net_source(mem_module, wl_net, decoder_module, 0, decoder_data_port, sram_instance); @@ -709,7 +709,7 @@ void build_frame_memory_module(ModuleManager& module_manager, /* Optional: Wire decoder data_out inverted port to sram WLB ports */ if (true == use_data_inv) { ModulePortId sram_wlb_port = module_manager.find_module_port(sram_mem_module, circuit_lib.port_lib_name(sram_wlb_ports[0])); - ModulePortId decoder_data_inv_port = module_manager.find_module_port(decoder_module, std::string(DECODER_DATA_INV_PORT_NAME)); + ModulePortId decoder_data_inv_port = module_manager.find_module_port(decoder_module, std::string(DECODER_DATA_OUT_INV_PORT_NAME)); ModuleNetId wlb_net = module_manager.create_module_net(mem_module); /* Source node of the input net is the input of memory module */ module_manager.add_module_net_source(mem_module, wlb_net, decoder_module, 0, decoder_data_inv_port, sram_instance); diff --git a/openfpga/src/fabric/build_routing_modules.cpp b/openfpga/src/fabric/build_routing_modules.cpp index b00cc5dc5..98a94baa8 100644 --- a/openfpga/src/fabric/build_routing_modules.cpp +++ b/openfpga/src/fabric/build_routing_modules.cpp @@ -329,6 +329,7 @@ void build_switch_block_interc_modules(ModuleManager& module_manager, ********************************************************************/ static void build_switch_block_module(ModuleManager& module_manager, + DecoderLibrary& decoder_lib, const VprDeviceAnnotation& device_annotation, const RRGraph& rr_graph, const CircuitLibrary& circuit_lib, @@ -438,7 +439,8 @@ void build_switch_block_module(ModuleManager& module_manager, * This is a one-shot addition that covers all the memory modules in this primitive module! */ if (0 < module_manager.configurable_children(sb_module).size()) { - add_module_nets_memory_config_bus(module_manager, sb_module, + add_module_nets_memory_config_bus(module_manager, decoder_lib, + sb_module, sram_orgz_type, circuit_lib.design_tech_type(sram_model)); } @@ -709,6 +711,7 @@ void build_connection_block_interc_modules(ModuleManager& module_manager, ********************************************************************/ static void build_connection_block_module(ModuleManager& module_manager, + DecoderLibrary& decoder_lib, const VprDeviceAnnotation& device_annotation, const RRGraph& rr_graph, const CircuitLibrary& circuit_lib, @@ -846,7 +849,8 @@ void build_connection_block_module(ModuleManager& module_manager, * This is a one-shot addition that covers all the memory modules in this primitive module! */ if (0 < module_manager.configurable_children(cb_module).size()) { - add_module_nets_memory_config_bus(module_manager, cb_module, + add_module_nets_memory_config_bus(module_manager, decoder_lib, + cb_module, sram_orgz_type, circuit_lib.design_tech_type(sram_model)); } @@ -860,6 +864,7 @@ void build_connection_block_module(ModuleManager& module_manager, *******************************************************************/ static void build_flatten_connection_block_modules(ModuleManager& module_manager, + DecoderLibrary& decoder_lib, const DeviceContext& device_ctx, const VprDeviceAnnotation& device_annotation, const DeviceRRGSB& device_rr_gsb, @@ -882,6 +887,7 @@ void build_flatten_connection_block_modules(ModuleManager& module_manager, continue; } build_connection_block_module(module_manager, + decoder_lib, device_annotation, device_ctx.rr_graph, circuit_lib, @@ -902,6 +908,7 @@ void build_flatten_connection_block_modules(ModuleManager& module_manager, * 2. Switch blocks *******************************************************************/ void build_flatten_routing_modules(ModuleManager& module_manager, + DecoderLibrary& decoder_lib, const DeviceContext& device_ctx, const VprDeviceAnnotation& device_annotation, const DeviceRRGSB& device_rr_gsb, @@ -922,6 +929,7 @@ void build_flatten_routing_modules(ModuleManager& module_manager, continue; } build_switch_block_module(module_manager, + decoder_lib, device_annotation, device_ctx.rr_graph, circuit_lib, @@ -932,6 +940,7 @@ void build_flatten_routing_modules(ModuleManager& module_manager, } build_flatten_connection_block_modules(module_manager, + decoder_lib, device_ctx, device_annotation, device_rr_gsb, @@ -941,6 +950,7 @@ void build_flatten_routing_modules(ModuleManager& module_manager, verbose); build_flatten_connection_block_modules(module_manager, + decoder_lib, device_ctx, device_annotation, device_rr_gsb, @@ -962,6 +972,7 @@ void build_flatten_routing_modules(ModuleManager& module_manager, * the option compact_routing_hierarchy is turned on!!! *******************************************************************/ void build_unique_routing_modules(ModuleManager& module_manager, + DecoderLibrary& decoder_lib, const DeviceContext& device_ctx, const VprDeviceAnnotation& device_annotation, const DeviceRRGSB& device_rr_gsb, @@ -976,6 +987,7 @@ void build_unique_routing_modules(ModuleManager& module_manager, for (size_t isb = 0; isb < device_rr_gsb.get_num_sb_unique_module(); ++isb) { const RRGSB& unique_mirror = device_rr_gsb.get_sb_unique_module(isb); build_switch_block_module(module_manager, + decoder_lib, device_annotation, device_ctx.rr_graph, circuit_lib, @@ -989,6 +1001,7 @@ void build_unique_routing_modules(ModuleManager& module_manager, const RRGSB& unique_mirror = device_rr_gsb.get_cb_unique_module(CHANX, icb); build_connection_block_module(module_manager, + decoder_lib, device_annotation, device_ctx.rr_graph, circuit_lib, @@ -1002,6 +1015,7 @@ void build_unique_routing_modules(ModuleManager& module_manager, const RRGSB& unique_mirror = device_rr_gsb.get_cb_unique_module(CHANY, icb); build_connection_block_module(module_manager, + decoder_lib, device_annotation, device_ctx.rr_graph, circuit_lib, diff --git a/openfpga/src/fabric/build_routing_modules.h b/openfpga/src/fabric/build_routing_modules.h index d8eb09784..d82ce3a69 100644 --- a/openfpga/src/fabric/build_routing_modules.h +++ b/openfpga/src/fabric/build_routing_modules.h @@ -8,6 +8,7 @@ #include "vpr_device_annotation.h" #include "device_rr_gsb.h" #include "mux_library.h" +#include "decoder_library.h" #include "circuit_library.h" #include "module_manager.h" @@ -19,6 +20,7 @@ namespace openfpga { void build_flatten_routing_modules(ModuleManager& module_manager, + DecoderLibrary& decoder_lib, const DeviceContext& device_ctx, const VprDeviceAnnotation& device_annotation, const DeviceRRGSB& device_rr_gsb, @@ -28,6 +30,7 @@ void build_flatten_routing_modules(ModuleManager& module_manager, const bool& verbose); void build_unique_routing_modules(ModuleManager& module_manager, + DecoderLibrary& decoder_lib, const DeviceContext& device_ctx, const VprDeviceAnnotation& device_annotation, const DeviceRRGSB& device_rr_gsb, diff --git a/openfpga/src/fabric/build_top_module.cpp b/openfpga/src/fabric/build_top_module.cpp index 112b32a52..6124333c0 100644 --- a/openfpga/src/fabric/build_top_module.cpp +++ b/openfpga/src/fabric/build_top_module.cpp @@ -361,6 +361,13 @@ void build_top_module(ModuleManager& module_manager, */ add_module_gpio_ports_from_child_modules(module_manager, top_module); + /* Organize the list of memory modules and instances */ + organize_top_module_memory_modules(module_manager, top_module, + circuit_lib, sram_orgz_type, sram_model, + grids, grid_instance_ids, + device_rr_gsb, sb_instance_ids, cb_instance_ids, + compact_routing_hierarchy); + /* Add shared SRAM ports from the sub-modules under this Verilog module * This is a much easier job after adding sub modules (instances), * we just need to find all the I/O ports from the child modules and build a list of it @@ -379,13 +386,6 @@ void build_top_module(ModuleManager& module_manager, add_sram_ports_to_module_manager(module_manager, top_module, circuit_lib, sram_model, sram_orgz_type, module_num_config_bits); } - /* Organize the list of memory modules and instances */ - organize_top_module_memory_modules(module_manager, top_module, - circuit_lib, sram_orgz_type, sram_model, - grids, grid_instance_ids, - device_rr_gsb, sb_instance_ids, cb_instance_ids, - compact_routing_hierarchy); - /* Add module nets to connect memory cells inside * This is a one-shot addition that covers all the memory modules in this pb module! */ diff --git a/openfpga/src/fabric/build_top_module_memory.cpp b/openfpga/src/fabric/build_top_module_memory.cpp index 21f7a5e6c..28c003957 100644 --- a/openfpga/src/fabric/build_top_module_memory.cpp +++ b/openfpga/src/fabric/build_top_module_memory.cpp @@ -421,6 +421,9 @@ void add_top_module_nets_cmos_memory_config_bus(ModuleManager& module_manager, case CONFIG_MEM_MEMORY_BANK: /* TODO: */ break; + case CONFIG_MEM_FRAME_BASED: + /* TODO: */ + break; default: VTR_LOGF_ERROR(__FILE__, __LINE__, "Invalid type of SRAM organization!\n"); diff --git a/openfpga/src/utils/memory_utils.cpp b/openfpga/src/utils/memory_utils.cpp index 8452626d8..ef0433502 100644 --- a/openfpga/src/utils/memory_utils.cpp +++ b/openfpga/src/utils/memory_utils.cpp @@ -337,6 +337,10 @@ std::vector generate_sram_port_names(const CircuitLibrary& circuit_ } break; } + case CONFIG_MEM_FRAME_BASED: { + model_port_types.push_back(CIRCUIT_MODEL_PORT_INPUT); + break; + } default: VTR_LOG_ERROR("Invalid type of SRAM organization !\n"); exit(1); diff --git a/openfpga/src/utils/module_manager_utils.cpp b/openfpga/src/utils/module_manager_utils.cpp index 44e13c65a..2819cc0b4 100644 --- a/openfpga/src/utils/module_manager_utils.cpp +++ b/openfpga/src/utils/module_manager_utils.cpp @@ -18,7 +18,9 @@ #include "memory_utils.h" #include "pb_type_utils.h" +#include "build_decoder_modules.h" #include "circuit_library_utils.h" +#include "decoder_library_utils.h" #include "module_manager_utils.h" /* begin namespace openfpga */ @@ -173,6 +175,11 @@ void add_formal_verification_sram_ports_to_module_manager(ModuleManager& module_ * The other two ports are optional: BLB and WLB * Note that the constraints are correletated to the checking rules * in check_circuit_library() + * 4. Frame-based memory: + * - An Enable signal + * - An address port, whose size depends on the number of config bits + * and the maximum size of address ports of configurable children + * - An data_in port (single-bit) ********************************************************************/ void add_sram_ports_to_module_manager(ModuleManager& module_manager, const ModuleId& module_id, @@ -214,6 +221,18 @@ void add_sram_ports_to_module_manager(ModuleManager& module_manager, } break; } + case CONFIG_MEM_FRAME_BASED: { + BasicPort en_port(std::string(DECODER_ENABLE_PORT_NAME), 1); + module_manager.add_port(module_id, en_port, ModuleManager::MODULE_INPUT_PORT); + + BasicPort addr_port(std::string(DECODER_ADDRESS_PORT_NAME), num_config_bits); + module_manager.add_port(module_id, addr_port, ModuleManager::MODULE_INPUT_PORT); + + BasicPort din_port(std::string(DECODER_DATA_IN_PORT_NAME), 1); + module_manager.add_port(module_id, din_port, ModuleManager::MODULE_INPUT_PORT); + + break; + } default: VTR_LOG_ERROR("Invalid type of SRAM organization !\n"); exit(1); @@ -750,6 +769,194 @@ void add_module_nets_cmos_memory_chain_config_bus(ModuleManager& module_manager, } } +/******************************************************************** + * This function will + * - Add a frame decoder to the parent module + * - If the decoder exists in the library, we use the module + * - If the decoder does not exist, we create a new module and use it + * - create nets for the following types of connections: + * - Connect the EN signal, first few bits of address of parent module + * to the frame decoder inputs + * - Connect the enable (EN) port of memory modules under the parent module + * to the frame decoder outputs + * - Connect the data_in (Din) of parent module to the data_in of the all + * the memory modules + * + * EN ADDR[X - 1: X - log(N)/log2] + * | | + * v v + * +--------------------------------------------+ + * | Frame-based decoder | + * | | + * | Data out | + * +--------------------------------------------+ + * | + * +-------------+--------------------+ + * | | | + * Din | Din | Din | + * | | | | | | + * v v v v v v + * +--------+ +--------+ +--------+ + * | Memory | | Memory | ... | Memory | + * | Module | | Module | | Module | + * | [0] | | [1] | | [N-1] | + * +--------+ +--------+ +--------+ + * ^ ^ ^ + * | | | + * +-------------+--------------------+ + * | + * ADDR[X - log(N)/log2 - 1: 0] + * + * Note: + * - X is the port size of address port of the parent module + * - the address port of child memory modules may be smaller than + * X - log(N)/log2. In such case, we will drop the MSBs until it fit + * + *********************************************************************/ +void add_module_nets_cmos_memory_frame_config_bus(ModuleManager& module_manager, + DecoderLibrary& decoder_lib, + const ModuleId& parent_module) { + std::vector configurable_children = module_manager.configurable_children(parent_module); + + /* Find the decoder specification */ + size_t addr_size = find_mux_local_decoder_addr_size(configurable_children.size()); + /* Data input should match the WL (data_in) of a SRAM */ + size_t data_size = configurable_children.size(); + + /* Search the decoder library and try to find one + * If not found, create a new module and add it to the module manager + */ + DecoderId decoder_id = decoder_lib.find_decoder(addr_size, data_size, true, true, false); + if (DecoderId::INVALID() == decoder_id) { + decoder_id = decoder_lib.add_decoder(addr_size, data_size, true, true, false); + } + VTR_ASSERT(DecoderId::INVALID() != decoder_id); + + /* Create a module if not existed yet */ + std::string decoder_module_name = generate_frame_memory_decoder_subckt_name(addr_size, data_size); + ModuleId decoder_module = module_manager.find_module(decoder_module_name); + if (ModuleId::INVALID() == decoder_module) { + decoder_module = build_frame_memory_decoder_module(module_manager, + decoder_lib, + decoder_id); + } + VTR_ASSERT(ModuleId::INVALID() != decoder_module); + + /* Instanciate the decoder module here */ + VTR_ASSERT(0 == module_manager.num_instance(parent_module, decoder_module)); + module_manager.add_child_module(parent_module, decoder_module); + + /* Connect the enable (EN) port of memory modules under the parent module + * to the frame decoder inputs + */ + ModulePortId parent_en_port = module_manager.find_module_port(parent_module, std::string(DECODER_ENABLE_PORT_NAME)); + ModulePortId decoder_en_port = module_manager.find_module_port(decoder_module, std::string(DECODER_ENABLE_PORT_NAME)); + add_module_bus_nets(module_manager, parent_module, + parent_module, 0, parent_en_port, + decoder_module, 0, decoder_en_port); + + /* Connect the address port of the parent module to the frame decoder address port + * Note that we only connect to the first few bits of address port + */ + ModulePortId parent_addr_port = module_manager.find_module_port(parent_module, std::string(DECODER_ADDRESS_PORT_NAME)); + ModulePortId decoder_addr_port = module_manager.find_module_port(decoder_module, std::string(DECODER_ADDRESS_PORT_NAME)); + BasicPort parent_addr_port_info = module_manager.module_port(parent_module, parent_addr_port); + BasicPort decoder_addr_port_info = module_manager.module_port(decoder_module, decoder_addr_port); + for (size_t ipin = 0; ipin < decoder_addr_port_info.get_width(); ++ipin) { + ModuleNetId net = module_manager.module_instance_port_net(parent_module, + parent_module, 0, + parent_addr_port, + parent_addr_port_info.get_msb() - ipin); + if (ModuleNetId::INVALID() == net) { + net = module_manager.create_module_net(parent_module); + /* Configure the net source */ + module_manager.add_module_net_source(parent_module, net, + parent_module, 0, + parent_addr_port, + parent_addr_port_info.get_msb() - ipin); + } + /* Configure the net sink */ + module_manager.add_module_net_sink(parent_module, net, + decoder_module, 0, + decoder_addr_port, + decoder_addr_port_info.get_msb() - ipin); + } + + /* Connect the address port of the parent module to the address port of configurable children + * Note that we only connect to the last few bits of address port + */ + for (size_t mem_index = 0; mem_index < module_manager.configurable_children(parent_module).size(); ++mem_index) { + ModuleId child_module = module_manager.configurable_children(parent_module)[mem_index]; + ModulePortId child_addr_port = module_manager.find_module_port(child_module, std::string(DECODER_ADDRESS_PORT_NAME)); + BasicPort child_addr_port_info = module_manager.module_port(child_module, child_addr_port); + for (size_t ipin = 0; ipin < child_addr_port_info.get_width(); ++ipin) { + ModuleNetId net = module_manager.module_instance_port_net(parent_module, + parent_module, 0, + parent_addr_port, + parent_addr_port_info.get_lsb() + ipin); + if (ModuleNetId::INVALID() == net) { + net = module_manager.create_module_net(parent_module); + /* Configure the net source */ + module_manager.add_module_net_source(parent_module, net, + parent_module, 0, + parent_addr_port, + parent_addr_port_info.get_lsb() + ipin); + } + /* Configure the net sink */ + module_manager.add_module_net_sink(parent_module, net, + child_module, 0, + child_addr_port, + child_addr_port_info.get_lsb() + ipin); + } + } + + /* Connect the data_in (Din) of parent module to the data_in of the all + * the memory modules + */ + ModulePortId parent_din_port = module_manager.find_module_port(parent_module, std::string(DECODER_DATA_IN_PORT_NAME)); + for (size_t mem_index = 0; mem_index < module_manager.configurable_children(parent_module).size(); ++mem_index) { + ModuleId child_module = module_manager.configurable_children(parent_module)[mem_index]; + ModulePortId child_din_port = module_manager.find_module_port(child_module, std::string(DECODER_DATA_IN_PORT_NAME)); + add_module_bus_nets(module_manager, parent_module, + parent_module, 0, parent_din_port, + child_module, 0, child_din_port); + } + + /* Connect the data_out port of the decoder module + * to the enable port of configurable children + */ + ModulePortId decoder_dout_port = module_manager.find_module_port(decoder_module, std::string(DECODER_DATA_OUT_PORT_NAME)); + BasicPort decoder_dout_port_info = module_manager.module_port(decoder_module, decoder_dout_port); + VTR_ASSERT(decoder_dout_port_info.get_width() == module_manager.configurable_children(parent_module).size()); + for (size_t mem_index = 0; mem_index < module_manager.configurable_children(parent_module).size(); ++mem_index) { + ModuleId child_module = module_manager.configurable_children(parent_module)[mem_index]; + ModulePortId child_en_port = module_manager.find_module_port(child_module, std::string(DECODER_ENABLE_PORT_NAME)); + BasicPort child_en_port_info = module_manager.module_port(child_module, child_en_port); + for (size_t ipin = 0; ipin < child_en_port_info.get_width(); ++ipin) { + ModuleNetId net = module_manager.module_instance_port_net(parent_module, + decoder_module, 0, + decoder_dout_port, + decoder_dout_port_info.pins()[mem_index]); + if (ModuleNetId::INVALID() == net) { + net = module_manager.create_module_net(parent_module); + /* Configure the net source */ + module_manager.add_module_net_source(parent_module, net, + decoder_module, 0, + decoder_dout_port, + decoder_dout_port_info.pins()[mem_index]); + } + /* Configure the net sink */ + module_manager.add_module_net_sink(parent_module, net, + child_module, 0, + child_en_port, + child_en_port_info.pins()[ipin]); + } + } + + /* Add the decoder as the last configurable children */ + module_manager.add_configurable_child(parent_module, decoder_module, 0); +} + /********************************************************************* * Add the port-to-port connection between all the memory modules * and their parent module @@ -796,6 +1003,7 @@ void add_module_nets_cmos_memory_chain_config_bus(ModuleManager& module_manager, **********************************************************************/ static void add_module_nets_cmos_memory_config_bus(ModuleManager& module_manager, + DecoderLibrary& decoder_lib, const ModuleId& parent_module, const e_config_protocol_type& sram_orgz_type) { switch (sram_orgz_type) { @@ -809,6 +1017,10 @@ void add_module_nets_cmos_memory_config_bus(ModuleManager& module_manager, case CONFIG_MEM_MEMORY_BANK: /* TODO: */ break; + case CONFIG_MEM_FRAME_BASED: + /* TODO: */ + add_module_nets_cmos_memory_frame_config_bus(module_manager, decoder_lib, parent_module); + break; default: VTR_LOG_ERROR("Invalid type of SRAM organization!\n"); exit(1); @@ -871,12 +1083,14 @@ void add_module_nets_cmos_memory_config_bus(ModuleManager& module_manager, * and its child module (logic_module and memory_module) is created! *******************************************************************/ void add_module_nets_memory_config_bus(ModuleManager& module_manager, + DecoderLibrary& decoder_lib, const ModuleId& parent_module, const e_config_protocol_type& sram_orgz_type, const e_circuit_model_design_tech& mem_tech) { switch (mem_tech) { case CIRCUIT_MODEL_DESIGN_CMOS: - add_module_nets_cmos_memory_config_bus(module_manager, parent_module, + add_module_nets_cmos_memory_config_bus(module_manager, decoder_lib, + parent_module, sram_orgz_type); break; case CIRCUIT_MODEL_DESIGN_RRAM: @@ -1219,10 +1433,38 @@ size_t find_module_num_config_bits_from_child_modules(ModuleManager& module_mana const e_config_protocol_type& sram_orgz_type) { size_t num_config_bits = 0; - /* Iterate over the child modules */ - for (const ModuleId& child : module_manager.child_modules(module_id)) { - num_config_bits += find_module_num_config_bits(module_manager, child, circuit_lib, sram_model, sram_orgz_type); - } + switch (sram_orgz_type) { + case CONFIG_MEM_STANDALONE: + case CONFIG_MEM_SCAN_CHAIN: + case CONFIG_MEM_MEMORY_BANK: { + /* For scan-chain, standalone and memory bank configuration protocol + * The number of configuration bits is the sum of configuration bits + * per configurable children + */ + for (const ModuleId& child : module_manager.configurable_children(module_id)) { + num_config_bits += find_module_num_config_bits(module_manager, child, circuit_lib, sram_model, sram_orgz_type); + } + break; + } + case CONFIG_MEM_FRAME_BASED: { + /* For frame-based configuration protocol + * The number of configuration bits is the sum of + * - the maximum of configuration bits among configurable children + * - and the number of configurable children + */ + for (const ModuleId& child : module_manager.configurable_children(module_id)) { + size_t temp_num_config_bits = find_module_num_config_bits(module_manager, child, circuit_lib, sram_model, sram_orgz_type); + num_config_bits = std::max((int)temp_num_config_bits, (int)num_config_bits); + } + + num_config_bits += find_mux_local_decoder_addr_size(module_manager.configurable_children(module_id).size()); + + break; + } + default: + VTR_LOG_ERROR("Invalid type of SRAM organization !\n"); + exit(1); + } return num_config_bits; } @@ -1280,9 +1522,9 @@ void add_module_bus_nets(ModuleManager& module_manager, src_module_port_id, src_port.pins()[pin_id]); if (ModuleNetId::INVALID() == net) { net = module_manager.create_module_net(cur_module_id); + /* Configure the net source */ + module_manager.add_module_net_source(cur_module_id, net, src_module_id, src_instance_id, src_module_port_id, src_port.pins()[pin_id]); } - /* Configure the net source */ - module_manager.add_module_net_source(cur_module_id, net, src_module_id, src_instance_id, src_module_port_id, src_port.pins()[pin_id]); /* Configure the net sink */ module_manager.add_module_net_sink(cur_module_id, net, des_module_id, des_instance_id, des_module_port_id, des_port.pins()[pin_id]); } diff --git a/openfpga/src/utils/module_manager_utils.h b/openfpga/src/utils/module_manager_utils.h index 0ecdfd282..d52727a40 100644 --- a/openfpga/src/utils/module_manager_utils.h +++ b/openfpga/src/utils/module_manager_utils.h @@ -20,6 +20,7 @@ #include "circuit_types.h" #include "circuit_library.h" +#include "decoder_library.h" #include "module_manager.h" #include "vpr_device_annotation.h" @@ -94,7 +95,12 @@ void add_module_nets_cmos_memory_chain_config_bus(ModuleManager& module_manager, const ModuleId& parent_module, const e_config_protocol_type& sram_orgz_type); +void add_module_nets_cmos_memory_frame_config_bus(ModuleManager& module_manager, + DecoderLibrary& decoder_lib, + const ModuleId& parent_module); + void add_module_nets_memory_config_bus(ModuleManager& module_manager, + DecoderLibrary& decoder_lib, const ModuleId& parent_module, const e_config_protocol_type& sram_orgz_type, const e_circuit_model_design_tech& mem_tech); From ed2325ec9e696d1c0c84a6c0dcc3010b39d4f38c Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 26 May 2020 19:34:45 -0600 Subject: [PATCH 564/645] add frame decoder build-up to top-level module --- openfpga/src/fabric/build_device_module.cpp | 4 +++- openfpga/src/fabric/build_top_module.cpp | 4 +++- openfpga/src/fabric/build_top_module.h | 2 ++ openfpga/src/fabric/build_top_module_memory.cpp | 6 +++++- openfpga/src/fabric/build_top_module_memory.h | 2 ++ 5 files changed, 15 insertions(+), 3 deletions(-) diff --git a/openfpga/src/fabric/build_device_module.cpp b/openfpga/src/fabric/build_device_module.cpp index ee2547d7d..4b2375057 100644 --- a/openfpga/src/fabric/build_device_module.cpp +++ b/openfpga/src/fabric/build_device_module.cpp @@ -105,7 +105,9 @@ ModuleManager build_device_module_graph(IoLocationMap& io_location_map, } /* Build FPGA fabric top-level module */ - build_top_module(module_manager, io_location_map, + build_top_module(module_manager, + io_location_map, + decoder_lib, openfpga_ctx.arch().circuit_lib, vpr_device_ctx.grid, vpr_device_ctx.rr_graph, diff --git a/openfpga/src/fabric/build_top_module.cpp b/openfpga/src/fabric/build_top_module.cpp index 6124333c0..072548807 100644 --- a/openfpga/src/fabric/build_top_module.cpp +++ b/openfpga/src/fabric/build_top_module.cpp @@ -311,6 +311,7 @@ vtr::Matrix add_top_module_connection_block_instances(ModuleManager& mod *******************************************************************/ void build_top_module(ModuleManager& module_manager, IoLocationMap& io_location_map, + DecoderLibrary& decoder_lib, const CircuitLibrary& circuit_lib, const DeviceGrid& grids, const RRGraph& rr_graph, @@ -390,7 +391,8 @@ void build_top_module(ModuleManager& module_manager, * This is a one-shot addition that covers all the memory modules in this pb module! */ if (0 < module_manager.configurable_children(top_module).size()) { - add_top_module_nets_memory_config_bus(module_manager, top_module, + add_top_module_nets_memory_config_bus(module_manager, decoder_lib, + top_module, sram_orgz_type, circuit_lib.design_tech_type(sram_model)); } } diff --git a/openfpga/src/fabric/build_top_module.h b/openfpga/src/fabric/build_top_module.h index 4ced0735b..d64de2755 100644 --- a/openfpga/src/fabric/build_top_module.h +++ b/openfpga/src/fabric/build_top_module.h @@ -11,6 +11,7 @@ #include "rr_graph_obj.h" #include "device_rr_gsb.h" #include "circuit_library.h" +#include "decoder_library.h" #include "tile_direct.h" #include "arch_direct.h" #include "module_manager.h" @@ -25,6 +26,7 @@ namespace openfpga { void build_top_module(ModuleManager& module_manager, IoLocationMap& io_location_map, + DecoderLibrary& decoder_lib, const CircuitLibrary& circuit_lib, const DeviceGrid& grids, const RRGraph& rr_graph, diff --git a/openfpga/src/fabric/build_top_module_memory.cpp b/openfpga/src/fabric/build_top_module_memory.cpp index 28c003957..c488599a9 100644 --- a/openfpga/src/fabric/build_top_module_memory.cpp +++ b/openfpga/src/fabric/build_top_module_memory.cpp @@ -408,6 +408,7 @@ void organize_top_module_memory_modules(ModuleManager& module_manager, **********************************************************************/ static void add_top_module_nets_cmos_memory_config_bus(ModuleManager& module_manager, + DecoderLibrary& decoder_lib, const ModuleId& parent_module, const e_config_protocol_type& sram_orgz_type) { switch (sram_orgz_type) { @@ -423,6 +424,7 @@ void add_top_module_nets_cmos_memory_config_bus(ModuleManager& module_manager, break; case CONFIG_MEM_FRAME_BASED: /* TODO: */ + add_module_nets_cmos_memory_frame_config_bus(module_manager, decoder_lib, parent_module); break; default: VTR_LOGF_ERROR(__FILE__, __LINE__, @@ -464,12 +466,14 @@ void add_top_module_nets_cmos_memory_config_bus(ModuleManager& module_manager, * and its child module (logic_module and memory_module) is created! *******************************************************************/ void add_top_module_nets_memory_config_bus(ModuleManager& module_manager, + DecoderLibrary& decoder_lib, const ModuleId& parent_module, const e_config_protocol_type& sram_orgz_type, const e_circuit_model_design_tech& mem_tech) { switch (mem_tech) { case CIRCUIT_MODEL_DESIGN_CMOS: - add_top_module_nets_cmos_memory_config_bus(module_manager, parent_module, + add_top_module_nets_cmos_memory_config_bus(module_manager, decoder_lib, + parent_module, sram_orgz_type); break; case CIRCUIT_MODEL_DESIGN_RRAM: diff --git a/openfpga/src/fabric/build_top_module_memory.h b/openfpga/src/fabric/build_top_module_memory.h index dfa9a5c78..1d0295b87 100644 --- a/openfpga/src/fabric/build_top_module_memory.h +++ b/openfpga/src/fabric/build_top_module_memory.h @@ -11,6 +11,7 @@ #include "module_manager.h" #include "circuit_types.h" #include "circuit_library.h" +#include "decoder_library.h" #include "device_grid.h" #include "device_rr_gsb.h" @@ -34,6 +35,7 @@ void organize_top_module_memory_modules(ModuleManager& module_manager, const bool& compact_routing_hierarchy); void add_top_module_nets_memory_config_bus(ModuleManager& module_manager, + DecoderLibrary& decoder_lib, const ModuleId& parent_module, const e_config_protocol_type& sram_orgz_type, const e_circuit_model_design_tech& mem_tech); From c696e3d20f8fd063ba167ff8cbd855fd8287a868 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 26 May 2020 20:01:02 -0600 Subject: [PATCH 565/645] refine frame-based memory addition to compact the area --- .../src/fabric/build_top_module_memory.cpp | 1 - openfpga/src/utils/module_manager_utils.cpp | 100 ++++++++++++++++-- 2 files changed, 91 insertions(+), 10 deletions(-) diff --git a/openfpga/src/fabric/build_top_module_memory.cpp b/openfpga/src/fabric/build_top_module_memory.cpp index c488599a9..c15070488 100644 --- a/openfpga/src/fabric/build_top_module_memory.cpp +++ b/openfpga/src/fabric/build_top_module_memory.cpp @@ -423,7 +423,6 @@ void add_top_module_nets_cmos_memory_config_bus(ModuleManager& module_manager, /* TODO: */ break; case CONFIG_MEM_FRAME_BASED: - /* TODO: */ add_module_nets_cmos_memory_frame_config_bus(module_manager, decoder_lib, parent_module); break; default: diff --git a/openfpga/src/utils/module_manager_utils.cpp b/openfpga/src/utils/module_manager_utils.cpp index 2819cc0b4..27231aaf3 100644 --- a/openfpga/src/utils/module_manager_utils.cpp +++ b/openfpga/src/utils/module_manager_utils.cpp @@ -769,6 +769,58 @@ void add_module_nets_cmos_memory_chain_config_bus(ModuleManager& module_manager, } } +/******************************************************************** + * This function will create nets for the following types of connections: + * - Connect the enable signal to the EN of memory module + * - Connect the address port to the address port of memory module + * - Connect the data_in (Din) to the data_in of the memory module + * + * EN ADDR DATA_IN + * | | | + * v v v + * +-----------------------------+ + * | Memory Module | + * | [0] | + * | | + * +-----------------------------+ + * + * Note: + * - This function is ONLY applicable to single configurable child case!!! + * + *********************************************************************/ +static +void add_module_nets_cmos_memory_frame_short_config_bus(ModuleManager& module_manager, + const ModuleId& parent_module) { + std::vector configurable_children = module_manager.configurable_children(parent_module); + + VTR_ASSERT(1 == configurable_children.size()); + ModuleId child_module = configurable_children[0]; + + /* Connect the enable (EN) port of the parent module + * to the EN port of memory module + */ + ModulePortId parent_en_port = module_manager.find_module_port(parent_module, std::string(DECODER_ENABLE_PORT_NAME)); + ModulePortId child_en_port = module_manager.find_module_port(child_module, std::string(DECODER_ENABLE_PORT_NAME)); + add_module_bus_nets(module_manager, parent_module, + parent_module, 0, parent_en_port, + child_module, 0, child_en_port); + + /* Connect the address port of the parent module to the child module address port */ + ModulePortId parent_addr_port = module_manager.find_module_port(parent_module, std::string(DECODER_ADDRESS_PORT_NAME)); + ModulePortId child_addr_port = module_manager.find_module_port(child_module, std::string(DECODER_ADDRESS_PORT_NAME)); + add_module_bus_nets(module_manager, parent_module, + parent_module, 0, parent_addr_port, + child_module, 0, child_addr_port); + + /* Connect the data_in (Din) of parent module to the data_in of the memory module + */ + ModulePortId parent_din_port = module_manager.find_module_port(parent_module, std::string(DECODER_DATA_IN_PORT_NAME)); + ModulePortId child_din_port = module_manager.find_module_port(child_module, std::string(DECODER_DATA_IN_PORT_NAME)); + add_module_bus_nets(module_manager, parent_module, + parent_module, 0, parent_din_port, + child_module, 0, child_din_port); +} + /******************************************************************** * This function will * - Add a frame decoder to the parent module @@ -811,11 +863,13 @@ void add_module_nets_cmos_memory_chain_config_bus(ModuleManager& module_manager, * - X is the port size of address port of the parent module * - the address port of child memory modules may be smaller than * X - log(N)/log2. In such case, we will drop the MSBs until it fit + * - This function is only applicable to 2+ configurable children!!! * *********************************************************************/ -void add_module_nets_cmos_memory_frame_config_bus(ModuleManager& module_manager, - DecoderLibrary& decoder_lib, - const ModuleId& parent_module) { +static +void add_module_nets_cmos_memory_frame_decoder_config_bus(ModuleManager& module_manager, + DecoderLibrary& decoder_lib, + const ModuleId& parent_module) { std::vector configurable_children = module_manager.configurable_children(parent_module); /* Find the decoder specification */ @@ -885,7 +939,7 @@ void add_module_nets_cmos_memory_frame_config_bus(ModuleManager& module_manager, /* Connect the address port of the parent module to the address port of configurable children * Note that we only connect to the last few bits of address port */ - for (size_t mem_index = 0; mem_index < module_manager.configurable_children(parent_module).size(); ++mem_index) { + for (size_t mem_index = 0; mem_index < configurable_children.size(); ++mem_index) { ModuleId child_module = module_manager.configurable_children(parent_module)[mem_index]; ModulePortId child_addr_port = module_manager.find_module_port(child_module, std::string(DECODER_ADDRESS_PORT_NAME)); BasicPort child_addr_port_info = module_manager.module_port(child_module, child_addr_port); @@ -915,7 +969,7 @@ void add_module_nets_cmos_memory_frame_config_bus(ModuleManager& module_manager, */ ModulePortId parent_din_port = module_manager.find_module_port(parent_module, std::string(DECODER_DATA_IN_PORT_NAME)); for (size_t mem_index = 0; mem_index < module_manager.configurable_children(parent_module).size(); ++mem_index) { - ModuleId child_module = module_manager.configurable_children(parent_module)[mem_index]; + ModuleId child_module = configurable_children[mem_index]; ModulePortId child_din_port = module_manager.find_module_port(child_module, std::string(DECODER_DATA_IN_PORT_NAME)); add_module_bus_nets(module_manager, parent_module, parent_module, 0, parent_din_port, @@ -927,9 +981,9 @@ void add_module_nets_cmos_memory_frame_config_bus(ModuleManager& module_manager, */ ModulePortId decoder_dout_port = module_manager.find_module_port(decoder_module, std::string(DECODER_DATA_OUT_PORT_NAME)); BasicPort decoder_dout_port_info = module_manager.module_port(decoder_module, decoder_dout_port); - VTR_ASSERT(decoder_dout_port_info.get_width() == module_manager.configurable_children(parent_module).size()); - for (size_t mem_index = 0; mem_index < module_manager.configurable_children(parent_module).size(); ++mem_index) { - ModuleId child_module = module_manager.configurable_children(parent_module)[mem_index]; + VTR_ASSERT(decoder_dout_port_info.get_width() == configurable_children.size()); + for (size_t mem_index = 0; mem_index < configurable_children.size(); ++mem_index) { + ModuleId child_module = configurable_children[mem_index]; ModulePortId child_en_port = module_manager.find_module_port(child_module, std::string(DECODER_ENABLE_PORT_NAME)); BasicPort child_en_port_info = module_manager.module_port(child_module, child_en_port); for (size_t ipin = 0; ipin < child_en_port_info.get_width(); ++ipin) { @@ -957,6 +1011,29 @@ void add_module_nets_cmos_memory_frame_config_bus(ModuleManager& module_manager, module_manager.add_configurable_child(parent_module, decoder_module, 0); } +/********************************************************************* + * Top-level function to add nets for frame-based memories + * Add nets depending on the need + * - If there is no configurable child, return directly. + * - If there is only one configurable child, short wire the EN, ADDR and DATA_IN to it + * - If there are more than two configurable childern, add a decoder and build interconnection + * between it and the children + **********************************************************************/ +void add_module_nets_cmos_memory_frame_config_bus(ModuleManager& module_manager, + DecoderLibrary& decoder_lib, + const ModuleId& parent_module) { + if (0 == module_manager.configurable_children(parent_module).size()) { + return; + } + + if (1 == module_manager.configurable_children(parent_module).size()) { + add_module_nets_cmos_memory_frame_short_config_bus(module_manager, parent_module); + } else { + VTR_ASSERT (1 < module_manager.configurable_children(parent_module).size()); + add_module_nets_cmos_memory_frame_decoder_config_bus(module_manager, decoder_lib, parent_module); + } +} + /********************************************************************* * Add the port-to-port connection between all the memory modules * and their parent module @@ -1457,7 +1534,12 @@ size_t find_module_num_config_bits_from_child_modules(ModuleManager& module_mana num_config_bits = std::max((int)temp_num_config_bits, (int)num_config_bits); } - num_config_bits += find_mux_local_decoder_addr_size(module_manager.configurable_children(module_id).size()); + /* If there are more than 2 configurable children, we need a decoder + * Otherwise, we can just short wire the address port to the children + */ + if (1 < module_manager.configurable_children(module_id).size()) { + num_config_bits += find_mux_local_decoder_addr_size(module_manager.configurable_children(module_id).size()); + } break; } From 5c5a044c68a9af946fe68e79e23335718d34e671 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 27 May 2020 14:25:06 -0600 Subject: [PATCH 566/645] add architecture decoder (for frame-based config memory) to Verilog writer --- openfpga/src/base/openfpga_verilog.cpp | 1 + openfpga/src/fpga_verilog/verilog_api.cpp | 3 +- openfpga/src/fpga_verilog/verilog_api.h | 2 + openfpga/src/fpga_verilog/verilog_constants.h | 1 + .../src/fpga_verilog/verilog_decoders.cpp | 178 +++++++++++++++++- openfpga/src/fpga_verilog/verilog_decoders.h | 7 + .../src/fpga_verilog/verilog_submodule.cpp | 7 + openfpga/src/fpga_verilog/verilog_submodule.h | 2 + 8 files changed, 198 insertions(+), 3 deletions(-) diff --git a/openfpga/src/base/openfpga_verilog.cpp b/openfpga/src/base/openfpga_verilog.cpp index 481bd2866..c67c270eb 100644 --- a/openfpga/src/base/openfpga_verilog.cpp +++ b/openfpga/src/base/openfpga_verilog.cpp @@ -48,6 +48,7 @@ int write_fabric_verilog(OpenfpgaContext& openfpga_ctx, openfpga_ctx.mutable_verilog_netlists(), openfpga_ctx.arch().circuit_lib, openfpga_ctx.mux_lib(), + openfpga_ctx.decoder_lib(), g_vpr_ctx.device(), openfpga_ctx.vpr_device_annotation(), openfpga_ctx.device_rr_gsb(), diff --git a/openfpga/src/fpga_verilog/verilog_api.cpp b/openfpga/src/fpga_verilog/verilog_api.cpp index 06ef0e58e..2f92156fb 100644 --- a/openfpga/src/fpga_verilog/verilog_api.cpp +++ b/openfpga/src/fpga_verilog/verilog_api.cpp @@ -54,6 +54,7 @@ void fpga_fabric_verilog(ModuleManager& module_manager, NetlistManager& netlist_manager, const CircuitLibrary& circuit_lib, const MuxLibrary& mux_lib, + const DecoderLibrary& decoder_lib, const DeviceContext& device_ctx, const VprDeviceAnnotation& device_annotation, const DeviceRRGSB& device_rr_gsb, @@ -90,7 +91,7 @@ void fpga_fabric_verilog(ModuleManager& module_manager, * Without the modules in the module manager, core logic generation is not possible!!! */ print_verilog_submodule(module_manager, netlist_manager, - mux_lib, circuit_lib, + mux_lib, decoder_lib, circuit_lib, submodule_dir_path, options); diff --git a/openfpga/src/fpga_verilog/verilog_api.h b/openfpga/src/fpga_verilog/verilog_api.h index 301e68b3e..3076beb47 100644 --- a/openfpga/src/fpga_verilog/verilog_api.h +++ b/openfpga/src/fpga_verilog/verilog_api.h @@ -8,6 +8,7 @@ #include #include #include "mux_library.h" +#include "decoder_library.h" #include "circuit_library.h" #include "vpr_context.h" #include "vpr_device_annotation.h" @@ -32,6 +33,7 @@ void fpga_fabric_verilog(ModuleManager& module_manager, NetlistManager& netlist_manager, const CircuitLibrary& circuit_lib, const MuxLibrary& mux_lib, + const DecoderLibrary& decoder_lib, const DeviceContext& device_ctx, const VprDeviceAnnotation& device_annotation, const DeviceRRGSB& device_rr_gsb, diff --git a/openfpga/src/fpga_verilog/verilog_constants.h b/openfpga/src/fpga_verilog/verilog_constants.h index 4456a1048..3266fe2f6 100644 --- a/openfpga/src/fpga_verilog/verilog_constants.h +++ b/openfpga/src/fpga_verilog/verilog_constants.h @@ -37,6 +37,7 @@ constexpr char* LUTS_VERILOG_FILE_NAME = "luts.v"; constexpr char* ROUTING_VERILOG_FILE_NAME = "routing.v"; constexpr char* MUXES_VERILOG_FILE_NAME = "muxes.v"; constexpr char* LOCAL_ENCODER_VERILOG_FILE_NAME = "local_encoder.v"; +constexpr char* ARCH_ENCODER_VERILOG_FILE_NAME = "arch_encoder.v"; constexpr char* MEMORIES_VERILOG_FILE_NAME = "memories.v"; constexpr char* WIRES_VERILOG_FILE_NAME = "wires.v"; constexpr char* ESSENTIALS_VERILOG_FILE_NAME = "inv_buf_passgate.v"; diff --git a/openfpga/src/fpga_verilog/verilog_decoders.cpp b/openfpga/src/fpga_verilog/verilog_decoders.cpp index 30789e36f..66a1a8ed8 100644 --- a/openfpga/src/fpga_verilog/verilog_decoders.cpp +++ b/openfpga/src/fpga_verilog/verilog_decoders.cpp @@ -1,7 +1,6 @@ /*************************************************************************************** * This file includes functions to generate Verilog modules of decoders ***************************************************************************************/ -/* TODO: merge verilog_decoder.c to this source file and rename to verilog_decoder.cpp */ #include /* Headers from vtrutil library */ @@ -15,6 +14,7 @@ #include "decoder_library_utils.h" #include "module_manager.h" +#include "openfpga_reserved_words.h" #include "openfpga_naming.h" #include "verilog_constants.h" @@ -133,7 +133,6 @@ void print_verilog_mux_local_decoder_module(std::fstream& fp, print_verilog_module_end(fp, module_name); } - /*************************************************************************************** * This function will generate all the unique Verilog modules of local decoders for * the multiplexers used in a FPGA fabric @@ -227,4 +226,179 @@ void print_verilog_submodule_mux_local_decoders(const ModuleManager& module_mana VTR_LOG("Done\n"); } +/*************************************************************************************** + * Create a Verilog module for a decoder used as a configuration protocol + * in FPGA architecture + * + * Address + * | | ... | + * v v v + * +-----------+ + * Enable->/ \ + * / Decoder \ + * +-----------------+ + * | | | ... | | | + * v v v v v v + * Data output + * + * The outputs are assumes to be one-hot codes (at most only one '1' exist) + * Considering this fact, there are only num_of_outputs conditions to be encoded. + * Therefore, the number of inputs is ceil(log(num_of_outputs)/log(2)) + * + * The decoder has an enable signal which is active at logic '1'. + * When activated, the decoder will output decoding results to the data output port + * Otherwise, the data output port will be always all-zero + ***************************************************************************************/ +static +void print_verilog_arch_decoder_module(std::fstream& fp, + const ModuleManager& module_manager, + const DecoderLibrary& decoder_lib, + const DecoderId& decoder) { + /* Get the number of inputs */ + size_t addr_size = decoder_lib.addr_size(decoder); + size_t data_size = decoder_lib.data_size(decoder); + + /* Validate the FILE handler */ + VTR_ASSERT(true == valid_file_stream(fp)); + + /* Create a name for the decoder */ + std::string module_name = generate_frame_memory_decoder_subckt_name(addr_size, data_size); + + /* Create a Verilog Module based on the circuit model, and add to module manager */ + ModuleId module_id = module_manager.find_module(module_name); + VTR_ASSERT(true == module_manager.valid_module_id(module_id)); + /* Find module ports */ + /* Enable port */ + ModulePortId enable_port_id = module_manager.find_module_port(module_id, std::string(DECODER_ENABLE_PORT_NAME)); + BasicPort enable_port = module_manager.module_port(module_id, enable_port_id); + /* Address port */ + ModulePortId addr_port_id = module_manager.find_module_port(module_id, std::string(DECODER_ADDRESS_PORT_NAME)); + BasicPort addr_port = module_manager.module_port(module_id, addr_port_id); + /* Find each output port */ + ModulePortId data_port_id = module_manager.find_module_port(module_id, std::string(DECODER_DATA_OUT_PORT_NAME)); + BasicPort data_port = module_manager.module_port(module_id, data_port_id); + /* Data port is registered. It should be outputted as + * output reg [lsb:msb] data + */ + BasicPort data_inv_port(std::string(DECODER_DATA_OUT_INV_PORT_NAME), data_size); + if (true == decoder_lib.use_data_inv_port(decoder)) { + ModulePortId data_inv_port_id = module_manager.find_module_port(module_id, std::string(DECODER_DATA_OUT_INV_PORT_NAME)); + data_inv_port = module_manager.module_port(module_id, data_inv_port_id); + } + + /* dump module definition + ports */ + print_verilog_module_declaration(fp, module_manager, module_id); + /* Finish dumping ports */ + + print_verilog_comment(fp, std::string("----- BEGIN Verilog codes for Decoder convert " + std::to_string(addr_size) + "-bit addr to " + std::to_string(data_size) + "-bit data -----")); + + /* Print the truth table of this decoder */ + /* Internal logics */ + /* Early exit: Corner case for data size = 1 the logic is very simple: + * data = addr; + * data_inv = ~data_inv + */ + if (1 == data_size) { + print_verilog_wire_connection(fp, data_port, addr_port, false); + + /* Depend on if the inverted data output port is needed or not */ + if (true == decoder_lib.use_data_inv_port(decoder)) { + print_verilog_wire_connection(fp, data_inv_port, addr_port, true); + } + + print_verilog_comment(fp, std::string("----- END Verilog codes for Decoder convert " + std::to_string(addr_size) + "-bit addr to " + std::to_string(data_size) + "-bit data -----")); + + /* Put an end to the Verilog module */ + print_verilog_module_end(fp, module_name); + return; + } + + /* We use a magic number -1 as the addr=1 should be mapped to ...1 + * Otherwise addr will map addr=1 to ..10 + * Note that there should be a range for the shift operators + * We should narrow the encoding to be applied to a given set of data + * This will lead to that any addr which falls out of the op code of data + * will give a all-zero code + * For example: + * data is 5-bit while addr is 3-bit + * data=8'b0_0000 will be encoded to addr=3'b001; + * data=8'b0_0001 will be encoded to addr=3'b010; + * data=8'b0_0010 will be encoded to addr=3'b011; + * data=8'b0_0100 will be encoded to addr=3'b100; + * data=8'b0_1000 will be encoded to addr=3'b101; + * data=8'b1_0000 will be encoded to addr=3'b110; + * The rest of addr codes 3'b110, 3'b111 will be decoded to data=8'b0_0000; + */ + + fp << "\t" << "always@(" << generate_verilog_port(VERILOG_PORT_CONKT, addr_port) << ")" << std::endl; + fp << "\tif (" << generate_verilog_port(VERILOG_PORT_CONKT, enable_port) << "1'b1) begin" << std::endl; + fp << "\t\t" << "case (" << generate_verilog_port(VERILOG_PORT_CONKT, addr_port) << ")" << std::endl; + /* Create a string for addr and data */ + for (size_t i = 0; i < data_size; ++i) { + fp << "\t\t\t" << generate_verilog_constant_values(itobin_vec(i, addr_size)); + fp << " : "; + fp << generate_verilog_port_constant_values(data_port, ito1hot_vec(i, data_size)); + fp << ";" << std::endl; + } + fp << "\t\t\t" << "default : "; + fp << generate_verilog_port_constant_values(data_port, ito1hot_vec(data_size - 1, data_size)); + fp << ";" << std::endl; + fp << "\t\t" << "endcase" << std::endl; + fp << "\t" << "end" << std::endl; + + if (true == decoder_lib.use_data_inv_port(decoder)) { + print_verilog_wire_connection(fp, data_inv_port, data_port, true); + } + + print_verilog_comment(fp, std::string("----- END Verilog codes for Decoder convert " + std::to_string(addr_size) + "-bit addr to " + std::to_string(data_size) + "-bit data -----")); + + /* Put an end to the Verilog module */ + print_verilog_module_end(fp, module_name); +} + +/*************************************************************************************** + * This function will generate all the unique Verilog modules of decoders for + * configuration protocols in a FPGA fabric + * It will generate these decoder Verilog modules using behavioral description. + * Note that the implementation of local decoders can be dependent on the technology + * and standard cell libraries. + * Therefore, behavioral Verilog is used and the local decoders should be synthesized + * before running the back-end flow for FPGA fabric + * See more details in the function print_verilog_arch_decoder() for more details + ***************************************************************************************/ +void print_verilog_submodule_arch_decoders(const ModuleManager& module_manager, + NetlistManager& netlist_manager, + const DecoderLibrary& decoder_lib, + const std::string& submodule_dir) { + std::string verilog_fname(submodule_dir + std::string(ARCH_ENCODER_VERILOG_FILE_NAME)); + + /* Create the file stream */ + std::fstream fp; + fp.open(verilog_fname, std::fstream::out | std::fstream::trunc); + + check_file_stream(verilog_fname.c_str(), fp); + + /* Print out debugging information for if the file is not opened/created properly */ + VTR_LOG("Writing Verilog netlist for configuration decoders '%s'...", + verilog_fname.c_str()); + + print_verilog_file_header(fp, "Decoders for fabric configuration protocol "); + + /* Generate Verilog modules for the found unique local encoders */ + for (const auto& decoder : decoder_lib.decoders()) { + print_verilog_arch_decoder_module(fp, module_manager, decoder_lib, decoder); + } + + /* Close the file stream */ + fp.close(); + + /* Add fname to the netlist name list */ + NetlistId nlist_id = netlist_manager.add_netlist(verilog_fname); + VTR_ASSERT(NetlistId::INVALID() != nlist_id); + netlist_manager.set_netlist_type(nlist_id, NetlistManager::SUBMODULE_NETLIST); + + VTR_LOG("Done\n"); +} + + } /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_decoders.h b/openfpga/src/fpga_verilog/verilog_decoders.h index 9b2bbb6b1..e15d5299a 100644 --- a/openfpga/src/fpga_verilog/verilog_decoders.h +++ b/openfpga/src/fpga_verilog/verilog_decoders.h @@ -11,6 +11,7 @@ #include "circuit_library.h" #include "mux_graph.h" #include "mux_library.h" +#include "decoder_library.h" #include "module_manager.h" #include "netlist_manager.h" @@ -27,6 +28,12 @@ void print_verilog_submodule_mux_local_decoders(const ModuleManager& module_mana const CircuitLibrary& circuit_lib, const std::string& submodule_dir); +void print_verilog_submodule_arch_decoders(const ModuleManager& module_manager, + NetlistManager& netlist_manager, + const DecoderLibrary& decoder_lib, + const std::string& submodule_dir); + + } /* end namespace openfpga */ #endif diff --git a/openfpga/src/fpga_verilog/verilog_submodule.cpp b/openfpga/src/fpga_verilog/verilog_submodule.cpp index 37df77c60..6756824ab 100644 --- a/openfpga/src/fpga_verilog/verilog_submodule.cpp +++ b/openfpga/src/fpga_verilog/verilog_submodule.cpp @@ -34,6 +34,7 @@ namespace openfpga { void print_verilog_submodule(ModuleManager& module_manager, NetlistManager& netlist_manager, const MuxLibrary& mux_lib, + const DecoderLibrary& decoder_lib, const CircuitLibrary& circuit_lib, const std::string& submodule_dir, const FabricVerilogOption& fpga_verilog_opts) { @@ -49,6 +50,12 @@ void print_verilog_submodule(ModuleManager& module_manager, submodule_dir, circuit_lib); + /* Decoders for architecture */ + print_verilog_submodule_arch_decoders(const_cast(module_manager), + netlist_manager, + decoder_lib, + submodule_dir); + /* Routing multiplexers */ /* NOTE: local decoders generation must go before the MUX generation!!! * because local decoders modules will be instanciated in the MUX modules diff --git a/openfpga/src/fpga_verilog/verilog_submodule.h b/openfpga/src/fpga_verilog/verilog_submodule.h index 06bb7aeef..27bf7fdba 100644 --- a/openfpga/src/fpga_verilog/verilog_submodule.h +++ b/openfpga/src/fpga_verilog/verilog_submodule.h @@ -7,6 +7,7 @@ #include "module_manager.h" #include "netlist_manager.h" #include "mux_library.h" +#include "decoder_library.h" #include "fabric_verilog_options.h" /******************************************************************** @@ -19,6 +20,7 @@ namespace openfpga { void print_verilog_submodule(ModuleManager& module_manager, NetlistManager& netlist_manager, const MuxLibrary& mux_lib, + const DecoderLibrary& decoder_lib, const CircuitLibrary& circuit_lib, const std::string& submodule_dir, const FabricVerilogOption& fpga_verilog_opts); From 8c14cced84deddb8b8d522bd2ec5af6716d5cac3 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 27 May 2020 15:09:18 -0600 Subject: [PATCH 567/645] start improve fabric bitstream database to support frame-based configuration protocol --- .../src/fpga_bitstream/fabric_bitstream.cpp | 77 ++++++++++++++++ .../src/fpga_bitstream/fabric_bitstream.h | 90 +++++++++++++++++++ .../src/fpga_bitstream/fabric_bitstream_fwd.h | 23 +++++ 3 files changed, 190 insertions(+) create mode 100644 openfpga/src/fpga_bitstream/fabric_bitstream.cpp create mode 100644 openfpga/src/fpga_bitstream/fabric_bitstream.h create mode 100644 openfpga/src/fpga_bitstream/fabric_bitstream_fwd.h diff --git a/openfpga/src/fpga_bitstream/fabric_bitstream.cpp b/openfpga/src/fpga_bitstream/fabric_bitstream.cpp new file mode 100644 index 000000000..581a466d5 --- /dev/null +++ b/openfpga/src/fpga_bitstream/fabric_bitstream.cpp @@ -0,0 +1,77 @@ +/****************************************************************************** + * This file includes member functions for data structure FabricBitstream + ******************************************************************************/ +#include + +#include "vtr_assert.h" +#include "fabric_bitstream.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/************************************************** + * Public Accessors : Aggregates + *************************************************/ +/* Find all the configuration bits */ +FabricBitstream::fabric_bit_range FabricBitstream::bits() const { + return vtr::make_range(bit_ids_.begin(), bit_ids_.end()); +} + +/****************************************************************************** + * Public Accessors + ******************************************************************************/ +ConfigBitId FabricBitstream::config_bit(const FabricBitId& bit_id) const { + /* Ensure a valid id */ + VTR_ASSERT(true == valid_bit_id(bit_id)); + + return config_bit_ids_[bit_id]; +} + +std::vector FabricBitstream::bit_address(const FabricBitId& bit_id) const { + /* Ensure a valid id */ + VTR_ASSERT(true == valid_bit_id(bit_id)); + + return bit_addresses_[bit_id]; +} + +bool FabricBitstream::bit_din(const FabricBitId& bit_id) const { + /* Ensure a valid id */ + VTR_ASSERT(true == valid_bit_id(bit_id)); + + return bit_dins_[bit_id]; +} + +/****************************************************************************** + * Public Mutators + ******************************************************************************/ +FabricBitId FabricBitstream::add_bit(const ConfigBitId& config_bit_id) { + FabricBitId bit = FabricBitId(bit_ids_.size()); + /* Add a new bit, and allocate associated data structures */ + bit_ids_.push_back(bit); + config_bit_ids_.push_back(config_bit_id); + bit_addresses_.emplace_back(); + bit_dins_.push_back(false); + + return bit; +} + +void FabricBitstream::set_bit_address(const FabricBitId& bit_id, + const std::vector& address) { + VTR_ASSERT(true == valid_bit_id(bit_id)); + bit_addresses_[bit_id] = address; +} + +void FabricBitstream::set_bit_din(const FabricBitId& bit_id, + const bool& din) { + VTR_ASSERT(true == valid_bit_id(bit_id)); + bit_dins_[bit_id] = din; +} + +/****************************************************************************** + * Public Validators + ******************************************************************************/ +bool FabricBitstream::valid_bit_id(const FabricBitId& bit_id) const { + return (size_t(bit_id) < bit_ids_.size()) && (bit_id == bit_ids_[bit_id]); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_bitstream/fabric_bitstream.h b/openfpga/src/fpga_bitstream/fabric_bitstream.h new file mode 100644 index 000000000..3f13406c4 --- /dev/null +++ b/openfpga/src/fpga_bitstream/fabric_bitstream.h @@ -0,0 +1,90 @@ +/****************************************************************************** + * This file introduces a data structure to store fabric-dependent bitstream information + * + * General concept + * --------------- + * The idea is to create a unified data structure that stores the sequence of configuration + * bit in the architecture bitstream database + * as well as the information (such as address of each bit) required by a specific + * configuration protocol + * + * Cross-reference + * --------------- + * By using the link between ArchBitstreamManager and FabricBitstream, + * we can build a sequence of configuration bits to fit different configuration protocols. + * + * +----------------------+ +--------------------------+ + * | | ConfigBitId | | + * | ArchBitstreamManager |---------------->| FabricBitstream | + * | | | | + * +----------------------+ +--------------------------+ + * + * Restrictions: + * 1. Each block inside BitstreamManager should have only 1 parent block + * and multiple child block + * 2. Each bit inside BitstreamManager should have only 1 parent block + * + ******************************************************************************/ +#ifndef FABRIC_BITSTREAM_H +#define FABRIC_BITSTREAM_H + +#include +#include "vtr_vector.h" + +#include "bitstream_manager_fwd.h" +#include "fabric_bitstream_fwd.h" + +/* begin namespace openfpga */ +namespace openfpga { + +class FabricBitstream { + public: /* Types and ranges */ + typedef vtr::vector::const_iterator fabric_bit_iterator; + + typedef vtr::Range fabric_bit_range; + + public: /* Public aggregators */ + /* Find all the configuration bits */ + fabric_bit_range bits() const; + + public: /* Public Accessors */ + /* Find the configuration bit id in architecture bitstream database */ + ConfigBitId config_bit(const FabricBitId& bit_id) const; + + /* Find the address of bitstream */ + std::vector bit_address(const FabricBitId& bit_id) const; + + /* Find the data-in of bitstream */ + bool bit_din(const FabricBitId& bit_id) const; + + public: /* Public Mutators */ + /* Add a new configuration bit to the bitstream manager */ + FabricBitId add_bit(const ConfigBitId& config_bit_id); + + void set_bit_address(const FabricBitId& bit_id, + const std::vector& address); + + void set_bit_din(const FabricBitId& bit_id, + const bool& din); + + public: /* Public Validators */ + bool valid_bit_id(const FabricBitId& bit_id) const; + + private: /* Internal data */ + /* Unique id of a bit in the Bitstream */ + vtr::vector bit_ids_; + vtr::vector config_bit_ids_; + + /* Address bits: this is designed for memory decoders + * Here we store the binary format of the address, which can be loaded + * to the configuration protocol directly + */ + vtr::vector> bit_addresses_; + + /* Data input (Din) bits: this is designed for memory decoders */ + vtr::vector bit_dins_; +}; + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_bitstream/fabric_bitstream_fwd.h b/openfpga/src/fpga_bitstream/fabric_bitstream_fwd.h new file mode 100644 index 000000000..d93eec638 --- /dev/null +++ b/openfpga/src/fpga_bitstream/fabric_bitstream_fwd.h @@ -0,0 +1,23 @@ +/************************************************** + * This file includes only declarations for + * the data structures for fabric-dependent bitstream database + * Please refer to fabric_bitstream.h for more details + *************************************************/ +#ifndef FABRIC_BITSTREAM_FWD_H +#define FABRIC_BITSTREAM_FWD_H + +#include "vtr_strong_id.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/* Strong Ids for BitstreamContext */ +struct fabric_bit_id_tag; + +typedef vtr::StrongId FabricBitId; + +class FabricBitstream; + +} /* end namespace openfpga */ + +#endif From 4a0e1cd90890faa3e0c71b03e85f98fa4b752958 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 27 May 2020 15:53:40 -0600 Subject: [PATCH 568/645] add fabric bitstream data structure and deploy it to Verilog testbench generation --- openfpga/src/base/openfpga_bitstream.cpp | 1 + openfpga/src/base/openfpga_context.h | 7 +- .../fpga_bitstream/build_fabric_bitstream.cpp | 127 ++++++++++++------ .../fpga_bitstream/build_fabric_bitstream.h | 9 +- .../src/fpga_bitstream/fabric_bitstream.cpp | 10 +- .../src/fpga_bitstream/fabric_bitstream.h | 5 + .../fabric_bitstream_writer.cpp | 8 +- .../fpga_bitstream/fabric_bitstream_writer.h | 3 +- openfpga/src/fpga_verilog/verilog_api.cpp | 2 +- openfpga/src/fpga_verilog/verilog_api.h | 3 +- .../fpga_verilog/verilog_top_testbench.cpp | 14 +- .../src/fpga_verilog/verilog_top_testbench.h | 3 +- 12 files changed, 125 insertions(+), 67 deletions(-) diff --git a/openfpga/src/base/openfpga_bitstream.cpp b/openfpga/src/base/openfpga_bitstream.cpp index e131a2bfc..b1af639b5 100644 --- a/openfpga/src/base/openfpga_bitstream.cpp +++ b/openfpga/src/base/openfpga_bitstream.cpp @@ -62,6 +62,7 @@ int build_fabric_bitstream(OpenfpgaContext& openfpga_ctx, /* Build fabric bitstream here */ openfpga_ctx.mutable_fabric_bitstream() = build_fabric_dependent_bitstream(openfpga_ctx.bitstream_manager(), openfpga_ctx.module_graph(), + openfpga_ctx.arch().config_protocol, cmd_context.option_enable(cmd, opt_verbose)); /* Write fabric bitstream if required */ diff --git a/openfpga/src/base/openfpga_context.h b/openfpga/src/base/openfpga_context.h index 8c025687f..0eb34c029 100644 --- a/openfpga/src/base/openfpga_context.h +++ b/openfpga/src/base/openfpga_context.h @@ -16,6 +16,7 @@ #include "netlist_manager.h" #include "openfpga_flow_manager.h" #include "bitstream_manager.h" +#include "fabric_bitstream.h" #include "device_rr_gsb.h" #include "io_location_map.h" @@ -61,7 +62,7 @@ class OpenfpgaContext : public Context { const openfpga::ModuleManager& module_graph() const { return module_graph_; } const openfpga::FlowManager& flow_manager() const { return flow_manager_; } const openfpga::BitstreamManager& bitstream_manager() const { return bitstream_manager_; } - const std::vector& fabric_bitstream() const { return fabric_bitstream_; } + const openfpga::FabricBitstream& fabric_bitstream() const { return fabric_bitstream_; } const openfpga::IoLocationMap& io_location_map() const { return io_location_map_; } const std::unordered_map& net_activity() const { return net_activity_; } const openfpga::NetlistManager& verilog_netlists() const { return verilog_netlists_; } @@ -79,7 +80,7 @@ class OpenfpgaContext : public Context { openfpga::ModuleManager& mutable_module_graph() { return module_graph_; } openfpga::FlowManager& mutable_flow_manager() { return flow_manager_; } openfpga::BitstreamManager& mutable_bitstream_manager() { return bitstream_manager_; } - std::vector& mutable_fabric_bitstream() { return fabric_bitstream_; } + openfpga::FabricBitstream& mutable_fabric_bitstream() { return fabric_bitstream_; } openfpga::IoLocationMap& mutable_io_location_map() { return io_location_map_; } std::unordered_map& mutable_net_activity() { return net_activity_; } openfpga::NetlistManager& mutable_verilog_netlists() { return verilog_netlists_; } @@ -120,7 +121,7 @@ class OpenfpgaContext : public Context { /* Bitstream database */ openfpga::BitstreamManager bitstream_manager_; - std::vector fabric_bitstream_; + openfpga::FabricBitstream fabric_bitstream_; /* Netlist database * TODO: Each format should have an independent entry diff --git a/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp b/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp index 0a15f6930..3953e0c16 100644 --- a/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp +++ b/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp @@ -28,11 +28,11 @@ namespace openfpga { * in the configurable_children) and configurable_child_instances() of each module of module manager *******************************************************************/ static -void rec_build_module_fabric_dependent_bitstream(const BitstreamManager& bitstream_manager, - const ConfigBlockId& parent_block, - const ModuleManager& module_manager, - const ModuleId& parent_module, - std::vector& fabric_bitstream) { +void rec_build_module_fabric_dependent_chain_bitstream(const BitstreamManager& bitstream_manager, + const ConfigBlockId& parent_block, + const ModuleManager& module_manager, + const ModuleId& parent_module, + FabricBitstream& fabric_bitstream) { /* Depth-first search: if we have any children in the parent_block, * we dive to the next level first! @@ -51,9 +51,9 @@ void rec_build_module_fabric_dependent_bitstream(const BitstreamManager& bitstre VTR_ASSERT(true == bitstream_manager.valid_block_id(child_block)); /* Go recursively */ - rec_build_module_fabric_dependent_bitstream(bitstream_manager, child_block, - module_manager, child_module, - fabric_bitstream); + rec_build_module_fabric_dependent_chain_bitstream(bitstream_manager, child_block, + module_manager, child_module, + fabric_bitstream); } /* Ensure that there should be no configuration bits in the parent block */ VTR_ASSERT(0 == bitstream_manager.block_bits(parent_block).size()); @@ -64,45 +64,47 @@ void rec_build_module_fabric_dependent_bitstream(const BitstreamManager& bitstre * And then, we can return */ for (const ConfigBitId& config_bit : bitstream_manager.block_bits(parent_block)) { - fabric_bitstream.push_back(config_bit); + fabric_bitstream.add_bit(config_bit); } } /******************************************************************** - * A top-level function re-organizes the bitstream for a specific - * FPGA fabric, where configuration bits are organized in the sequence - * that can be directly loaded to the FPGA configuration protocol. - * Support: - * 1. Configuration chain - * 2. Memory decoders - * This function does NOT modify the bitstream database - * Instead, it builds a vector of ids for configuration bits in bitstream manager - * - * This function can be called ONLY after the function build_device_bitstream() - * Note that this function does NOT decode bitstreams from circuit implementation - * It was done in the function build_device_bitstream() + * Main function to build a fabric-dependent bitstream + * by considering the configuration protocol types *******************************************************************/ -std::vector build_fabric_dependent_bitstream(const BitstreamManager& bitstream_manager, - const ModuleManager& module_manager, - const bool& verbose) { - std::vector fabric_bitstream; +static +void build_module_fabric_dependent_bitstream(const ConfigProtocol& config_protocol, + const BitstreamManager& bitstream_manager, + const ConfigBlockId& top_block, + const ModuleManager& module_manager, + const ModuleId& top_module, + FabricBitstream& fabric_bitstream) { - vtr::ScopedStartFinishTimer timer("\nBuild fabric dependent bitstream\n"); - - /* Get the top module name in module manager, which is our starting point */ - std::string top_module_name = generate_fpga_top_module_name(); - ModuleId top_module = module_manager.find_module(top_module_name); - VTR_ASSERT(true == module_manager.valid_module_id(top_module)); - - /* Find the top block in bitstream manager, which has not parents */ - std::vector top_block = find_bitstream_manager_top_blocks(bitstream_manager); - /* Make sure we have only 1 top block and its name matches the top module */ - VTR_ASSERT(1 == top_block.size()); - VTR_ASSERT(0 == top_module_name.compare(bitstream_manager.block_name(top_block[0]))); - - rec_build_module_fabric_dependent_bitstream(bitstream_manager, top_block[0], - module_manager, top_module, - fabric_bitstream); + switch (config_protocol.type()) { + case CONFIG_MEM_STANDALONE: { + rec_build_module_fabric_dependent_chain_bitstream(bitstream_manager, top_block, + module_manager, top_module, + fabric_bitstream); + break; + } + case CONFIG_MEM_SCAN_CHAIN: { + rec_build_module_fabric_dependent_chain_bitstream(bitstream_manager, top_block, + module_manager, top_module, + fabric_bitstream); + fabric_bitstream.reverse(); + break; + } + case CONFIG_MEM_MEMORY_BANK: { + break; + } + case CONFIG_MEM_FRAME_BASED: { + break; + } + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid SRAM organization.\n"); + exit(1); + } /* Time-consuming sanity check: Uncomment these codes only for debugging!!! * Check which configuration bits are not touched @@ -124,11 +126,50 @@ std::vector build_fabric_dependent_bitstream(const BitstreamManager */ /* Ensure our fabric bitstream is in the same size as device bistream */ - VTR_ASSERT(bitstream_manager.bits().size() == fabric_bitstream.size()); + VTR_ASSERT(bitstream_manager.bits().size() == fabric_bitstream.bits().size()); +} + +/******************************************************************** + * A top-level function re-organizes the bitstream for a specific + * FPGA fabric, where configuration bits are organized in the sequence + * that can be directly loaded to the FPGA configuration protocol. + * Support: + * 1. Configuration chain + * 2. Memory decoders + * This function does NOT modify the bitstream database + * Instead, it builds a vector of ids for configuration bits in bitstream manager + * + * This function can be called ONLY after the function build_device_bitstream() + * Note that this function does NOT decode bitstreams from circuit implementation + * It was done in the function build_device_bitstream() + *******************************************************************/ +FabricBitstream build_fabric_dependent_bitstream(const BitstreamManager& bitstream_manager, + const ModuleManager& module_manager, + const ConfigProtocol& config_protocol, + const bool& verbose) { + FabricBitstream fabric_bitstream; + + vtr::ScopedStartFinishTimer timer("\nBuild fabric dependent bitstream\n"); + + /* Get the top module name in module manager, which is our starting point */ + std::string top_module_name = generate_fpga_top_module_name(); + ModuleId top_module = module_manager.find_module(top_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(top_module)); + + /* Find the top block in bitstream manager, which has not parents */ + std::vector top_block = find_bitstream_manager_top_blocks(bitstream_manager); + /* Make sure we have only 1 top block and its name matches the top module */ + VTR_ASSERT(1 == top_block.size()); + VTR_ASSERT(0 == top_module_name.compare(bitstream_manager.block_name(top_block[0]))); + + build_module_fabric_dependent_bitstream(config_protocol, + bitstream_manager, top_block[0], + module_manager, top_module, + fabric_bitstream); VTR_LOGV(verbose, "Built %lu configuration bits for fabric\n", - fabric_bitstream.size()); + fabric_bitstream.bits().size()); return fabric_bitstream; } diff --git a/openfpga/src/fpga_bitstream/build_fabric_bitstream.h b/openfpga/src/fpga_bitstream/build_fabric_bitstream.h index 507454059..3daadde75 100644 --- a/openfpga/src/fpga_bitstream/build_fabric_bitstream.h +++ b/openfpga/src/fpga_bitstream/build_fabric_bitstream.h @@ -5,7 +5,9 @@ * Include header files that are required by function declaration *******************************************************************/ #include +#include "config_protocol.h" #include "bitstream_manager.h" +#include "fabric_bitstream.h" #include "module_manager.h" /******************************************************************** @@ -15,9 +17,10 @@ /* begin namespace openfpga */ namespace openfpga { -std::vector build_fabric_dependent_bitstream(const BitstreamManager& bitstream_manager, - const ModuleManager& module_manager, - const bool& verbose); +FabricBitstream build_fabric_dependent_bitstream(const BitstreamManager& bitstream_manager, + const ModuleManager& module_manager, + const ConfigProtocol& config_protocol, + const bool& verbose); } /* end namespace openfpga */ diff --git a/openfpga/src/fpga_bitstream/fabric_bitstream.cpp b/openfpga/src/fpga_bitstream/fabric_bitstream.cpp index 581a466d5..988839cf8 100644 --- a/openfpga/src/fpga_bitstream/fabric_bitstream.cpp +++ b/openfpga/src/fpga_bitstream/fabric_bitstream.cpp @@ -56,17 +56,23 @@ FabricBitId FabricBitstream::add_bit(const ConfigBitId& config_bit_id) { } void FabricBitstream::set_bit_address(const FabricBitId& bit_id, - const std::vector& address) { + const std::vector& address) { VTR_ASSERT(true == valid_bit_id(bit_id)); bit_addresses_[bit_id] = address; } void FabricBitstream::set_bit_din(const FabricBitId& bit_id, - const bool& din) { + const bool& din) { VTR_ASSERT(true == valid_bit_id(bit_id)); bit_dins_[bit_id] = din; } +void FabricBitstream::reverse() { + std::reverse(config_bit_ids_.begin(), config_bit_ids_.end()); + std::reverse(bit_addresses_.begin(), bit_addresses_.end()); + std::reverse(bit_dins_.begin(), bit_dins_.end()); +} + /****************************************************************************** * Public Validators ******************************************************************************/ diff --git a/openfpga/src/fpga_bitstream/fabric_bitstream.h b/openfpga/src/fpga_bitstream/fabric_bitstream.h index 3f13406c4..0b4842aa8 100644 --- a/openfpga/src/fpga_bitstream/fabric_bitstream.h +++ b/openfpga/src/fpga_bitstream/fabric_bitstream.h @@ -67,6 +67,11 @@ class FabricBitstream { void set_bit_din(const FabricBitId& bit_id, const bool& din); + /* Reverse bit sequence of the fabric bitstream + * This is required by configuration chain protocol + */ + void reverse(); + public: /* Public Validators */ bool valid_bit_id(const FabricBitId& bit_id) const; diff --git a/openfpga/src/fpga_bitstream/fabric_bitstream_writer.cpp b/openfpga/src/fpga_bitstream/fabric_bitstream_writer.cpp index 069a99345..f8ca706fa 100644 --- a/openfpga/src/fpga_bitstream/fabric_bitstream_writer.cpp +++ b/openfpga/src/fpga_bitstream/fabric_bitstream_writer.cpp @@ -31,14 +31,14 @@ namespace openfpga { * in this file *******************************************************************/ void write_fabric_bitstream_to_text_file(const BitstreamManager& bitstream_manager, - const std::vector& fabric_bitstream, + const FabricBitstream& fabric_bitstream, const std::string& fname) { /* Ensure that we have a valid file name */ if (true == fname.empty()) { VTR_LOG_ERROR("Received empty file name to output bitstream!\n\tPlease specify a valid file name.\n"); } - std::string timer_message = std::string("Write ") + std::to_string(fabric_bitstream.size()) + std::string(" fabric bitstream into plain text file '") + fname + std::string("'"); + std::string timer_message = std::string("Write ") + std::to_string(fabric_bitstream.bits().size()) + std::string(" fabric bitstream into plain text file '") + fname + std::string("'"); vtr::ScopedStartFinishTimer timer(timer_message); /* Create the file stream */ @@ -48,8 +48,8 @@ void write_fabric_bitstream_to_text_file(const BitstreamManager& bitstream_manag check_file_stream(fname.c_str(), fp); /* Put down pure 0|1 bitstream here */ - for (const ConfigBitId& fabric_bit : fabric_bitstream) { - fp << bitstream_manager.bit_value(fabric_bit); + for (const FabricBitId& fabric_bit : fabric_bitstream.bits()) { + fp << bitstream_manager.bit_value(fabric_bitstream.config_bit(fabric_bit)); } /* Print an end to the file here */ fp << std::endl; diff --git a/openfpga/src/fpga_bitstream/fabric_bitstream_writer.h b/openfpga/src/fpga_bitstream/fabric_bitstream_writer.h index 6b2c0e771..71c2addd8 100644 --- a/openfpga/src/fpga_bitstream/fabric_bitstream_writer.h +++ b/openfpga/src/fpga_bitstream/fabric_bitstream_writer.h @@ -7,6 +7,7 @@ #include #include #include "bitstream_manager.h" +#include "fabric_bitstream.h" /******************************************************************** * Function declaration @@ -16,7 +17,7 @@ namespace openfpga { void write_fabric_bitstream_to_text_file(const BitstreamManager& bitstream_manager, - const std::vector& fabric_bitstream, + const FabricBitstream& fabric_bitstream, const std::string& fname); } /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_api.cpp b/openfpga/src/fpga_verilog/verilog_api.cpp index 2f92156fb..705cd4a65 100644 --- a/openfpga/src/fpga_verilog/verilog_api.cpp +++ b/openfpga/src/fpga_verilog/verilog_api.cpp @@ -147,7 +147,7 @@ void fpga_fabric_verilog(ModuleManager& module_manager, ********************************************************************/ void fpga_verilog_testbench(const ModuleManager& module_manager, const BitstreamManager& bitstream_manager, - const std::vector& fabric_bitstream, + const FabricBitstream& fabric_bitstream, const AtomContext& atom_ctx, const PlacementContext& place_ctx, const IoLocationMap& io_location_map, diff --git a/openfpga/src/fpga_verilog/verilog_api.h b/openfpga/src/fpga_verilog/verilog_api.h index 3076beb47..94fbf3a29 100644 --- a/openfpga/src/fpga_verilog/verilog_api.h +++ b/openfpga/src/fpga_verilog/verilog_api.h @@ -16,6 +16,7 @@ #include "netlist_manager.h" #include "module_manager.h" #include "bitstream_manager.h" +#include "fabric_bitstream.h" #include "simulation_setting.h" #include "io_location_map.h" #include "vpr_netlist_annotation.h" @@ -41,7 +42,7 @@ void fpga_fabric_verilog(ModuleManager& module_manager, void fpga_verilog_testbench(const ModuleManager& module_manager, const BitstreamManager& bitstream_manager, - const std::vector& fabric_bitstream, + const FabricBitstream& fabric_bitstream, const AtomContext& atom_ctx, const PlacementContext& place_ctx, const IoLocationMap& io_location_map, diff --git a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp index dc4856cb3..9edce0b52 100644 --- a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp +++ b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp @@ -694,7 +694,7 @@ void print_verilog_top_testbench_generic_stimulus(std::fstream& fp, static void print_verilog_top_testbench_configuration_chain_bitstream(std::fstream& fp, const BitstreamManager& bitstream_manager, - const std::vector& fabric_bitstream) { + const FabricBitstream& fabric_bitstream) { /* Validate the file stream */ valid_file_stream(fp); @@ -720,11 +720,9 @@ void print_verilog_top_testbench_configuration_chain_bitstream(std::fstream& fp, /* Attention: the configuration chain protcol requires the last configuration bit is fed first * We will visit the fabric bitstream in a reverse way */ - std::vector cc_bitstream = fabric_bitstream; - std::reverse(cc_bitstream.begin(), cc_bitstream.end()); - for (const ConfigBitId& bit_id : cc_bitstream) { + for (const FabricBitId& bit_id : fabric_bitstream.bits()) { fp << "\t\t" << std::string(TOP_TESTBENCH_CC_PROG_TASK_NAME); - fp << "(1'b" << (size_t)bitstream_manager.bit_value(bit_id) << ");" << std::endl; + fp << "(1'b" << (size_t)bitstream_manager.bit_value(fabric_bitstream.config_bit(bit_id)) << ");" << std::endl; } /* Raise the flag of configuration done when bitstream loading is complete */ @@ -753,7 +751,7 @@ static void print_verilog_top_testbench_bitstream(std::fstream& fp, const e_config_protocol_type& sram_orgz_type, const BitstreamManager& bitstream_manager, - const std::vector& fabric_bitstream) { + const FabricBitstream& fabric_bitstream) { /* Branch on the type of configuration protocol */ switch (sram_orgz_type) { case CONFIG_MEM_STANDALONE: @@ -794,7 +792,7 @@ void print_verilog_top_testbench_bitstream(std::fstream& fp, *******************************************************************/ void print_verilog_top_testbench(const ModuleManager& module_manager, const BitstreamManager& bitstream_manager, - const std::vector& fabric_bitstream, + const FabricBitstream& fabric_bitstream, const e_config_protocol_type& sram_orgz_type, const CircuitLibrary& circuit_lib, const std::vector& global_ports, @@ -842,7 +840,7 @@ void print_verilog_top_testbench(const ModuleManager& module_manager, * by traversing the linked-list and count the number of SRAM=1 or BL=1&WL=1 in it. * We plus 1 additional config clock cycle here because we need to reset everything during the first clock cycle */ - size_t num_config_clock_cycles = 1 + fabric_bitstream.size(); + size_t num_config_clock_cycles = 1 + fabric_bitstream.bits().size(); /* Generate stimuli for general control signals */ print_verilog_top_testbench_generic_stimulus(fp, diff --git a/openfpga/src/fpga_verilog/verilog_top_testbench.h b/openfpga/src/fpga_verilog/verilog_top_testbench.h index 39a2024d1..07e671e57 100644 --- a/openfpga/src/fpga_verilog/verilog_top_testbench.h +++ b/openfpga/src/fpga_verilog/verilog_top_testbench.h @@ -8,6 +8,7 @@ #include #include "module_manager.h" #include "bitstream_manager.h" +#include "fabric_bitstream.h" #include "circuit_library.h" #include "vpr_context.h" #include "io_location_map.h" @@ -23,7 +24,7 @@ namespace openfpga { void print_verilog_top_testbench(const ModuleManager& module_manager, const BitstreamManager& bitstream_manager, - const std::vector& fabric_bitstream, + const FabricBitstream& fabric_bitstream, const e_config_protocol_type& sram_orgz_type, const CircuitLibrary& circuit_lib, const std::vector& global_ports, From 85921dcc05b29b1415332b40faf4f410a43ad4e4 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 27 May 2020 17:04:11 -0600 Subject: [PATCH 569/645] add fabric bitstream builder for frame-based configuration protocol --- .../fpga_bitstream/build_fabric_bitstream.cpp | 134 +++++++++++++++++- 1 file changed, 132 insertions(+), 2 deletions(-) diff --git a/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp b/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp index 3953e0c16..80026cbef 100644 --- a/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp +++ b/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp @@ -9,6 +9,10 @@ #include "vtr_log.h" #include "vtr_time.h" +/* Headers from openfpgautil library */ +#include "openfpga_decode.h" + +#include "openfpga_reserved_words.h" #include "openfpga_naming.h" #include "bitstream_manager_utils.h" @@ -18,14 +22,15 @@ namespace openfpga { /******************************************************************** - * This function will walk through all the configurable children under a module + * This function aims to build a bitstream for configuration chain-like protocol + * It will walk through all the configurable children under a module * in a recursive way, following a Depth-First Search (DFS) strategy * For each configuration child, we use its instance name as a key to spot the * configuration bits in bitstream manager. * Note that it is guarentee that the instance name in module manager is * consistent with the block names in bitstream manager * We use this link to reorganize the bitstream in the sequence of memories as we stored - * in the configurable_children) and configurable_child_instances() of each module of module manager + * in the configurable_children() and configurable_child_instances() of each module of module manager *******************************************************************/ static void rec_build_module_fabric_dependent_chain_bitstream(const BitstreamManager& bitstream_manager, @@ -68,6 +73,125 @@ void rec_build_module_fabric_dependent_chain_bitstream(const BitstreamManager& b } } +/******************************************************************** + * This function aims to build a bitstream for frame-based configuration protocol + * It will walk through all the configurable children under a module + * in a recursive way, following a Depth-First Search (DFS) strategy + * For each configuration child, we use its instance name as a key to spot the + * configuration bits in bitstream manager. + * Note that it is guarentee that the instance name in module manager is + * consistent with the block names in bitstream manager + * We use this link to reorganize the bitstream in the sequence of memories as we stored + * in the configurable_children() and configurable_child_instances() of each module of module manager + * + * For each configuration bits, we will infer its address based on + * - the child index in the configurable children list of current module + * - the child index of all the parent modules in their configurable children list + * until the top in the hierarchy + * + * The address will be organized as follows: + * ... + * The address will be decoded to a binary format + * + * For each configuration bit, the data_in for the frame-based decoders will be + * the same as the configuration bit in bitstream manager. + *******************************************************************/ +static +void rec_build_module_fabric_dependent_frame_bitstream(const BitstreamManager& bitstream_manager, + const std::vector& parent_blocks, + const ModuleManager& module_manager, + const std::vector& parent_modules, + const std::vector& addr_code, + FabricBitstream& fabric_bitstream) { + + /* Depth-first search: if we have any children in the parent_block, + * we dive to the next level first! + */ + if (0 < bitstream_manager.block_children(parent_blocks.back()).size()) { + const ConfigBlockId& parent_block = parent_blocks.back(); + const ModuleId& parent_module = parent_modules.back(); + + size_t num_configurable_children = module_manager.configurable_children(parent_modules.back()).size(); + + bool add_addr_code = true; + ModuleId decoder_module = ModuleId::INVALID(); + + /* Early exit if there is no configurable children */ + if (0 == num_configurable_children) { + return; + } + + /* For only 1 configurable child, there is not frame decoder need, we can pass on addr code directly */ + if (1 == num_configurable_children) { + add_addr_code = false; + } else { + /* For more than 2 children, there is a decoder in the tail of the list + * We will not decode that, but will access the address size from that module + * So, we reduce the number of children by 1 + */ + VTR_ASSERT(2 < num_configurable_children); + num_configurable_children--; + decoder_module = module_manager.configurable_children(parent_module).back(); + } + + for (size_t child_id = 0; child_id < num_configurable_children; ++child_id) { + ModuleId child_module = module_manager.configurable_children(parent_modules.back())[child_id]; + size_t child_instance = module_manager.configurable_child_instances(parent_module)[child_id]; + /* Get the instance name and ensure it is not empty */ + std::string instance_name = module_manager.instance_name(parent_module, child_module, child_instance); + + /* Find the child block that matches the instance name! */ + ConfigBlockId child_block = bitstream_manager.find_child_block(parent_block, instance_name); + /* We must have one valid block id! */ + if (true != bitstream_manager.valid_block_id(child_block)) + VTR_ASSERT(true == bitstream_manager.valid_block_id(child_block)); + + /* Pass on the list of blocks, modules and address lists */ + std::vector child_blocks = parent_blocks; + child_blocks.push_back(child_block); + + std::vector child_modules = parent_modules; + child_modules.push_back(child_module); + + /* Set address, apply binary conversion from the first to the last element in the address list */ + std::vector child_addr_code = addr_code; + + if (true == add_addr_code) { + /* Find the address port from the decoder module */ + const ModulePortId& decoder_addr_port_id = module_manager.find_module_port(decoder_module, std::string(DECODER_ADDRESS_PORT_NAME)); + const BasicPort& decoder_addr_port = module_manager.module_port(decoder_module, decoder_addr_port_id); + std::vector addr_bits_vec = itobin_vec(child_id, decoder_addr_port.get_width()); + for (const size_t& bit : addr_bits_vec) { + VTR_ASSERT((0 == bit) || (1 == bit)); + child_addr_code.push_back(bit); + } + } + + /* Go recursively */ + rec_build_module_fabric_dependent_frame_bitstream(bitstream_manager, child_blocks, + module_manager, child_modules, + child_addr_code, + fabric_bitstream); + } + /* Ensure that there should be no configuration bits in the parent block */ + VTR_ASSERT(0 == bitstream_manager.block_bits(parent_block).size()); + } + + /* Note that, reach here, it means that this is a leaf node. + * We add the configuration bits to the fabric_bitstream, + * And then, we can return + */ + for (const ConfigBitId& config_bit : bitstream_manager.block_bits(parent_blocks.back())) { + const FabricBitId& fabric_bit = fabric_bitstream.add_bit(config_bit); + + /* Set address */ + fabric_bitstream.set_bit_address(fabric_bit, addr_code); + + /* Set data input */ + fabric_bitstream.set_bit_din(fabric_bit, bitstream_manager.bit_value(config_bit)); + } +} + /******************************************************************** * Main function to build a fabric-dependent bitstream * by considering the configuration protocol types @@ -98,6 +222,12 @@ void build_module_fabric_dependent_bitstream(const ConfigProtocol& config_protoc break; } case CONFIG_MEM_FRAME_BASED: { + rec_build_module_fabric_dependent_frame_bitstream(bitstream_manager, + std::vector(1, top_block), + module_manager, + std::vector(1, top_module), + std::vector(), + fabric_bitstream); break; } default: From cff5b5cfc1f52a13c52da0808a3e9d42adfd7fe3 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 27 May 2020 18:40:45 -0600 Subject: [PATCH 570/645] break the configuration testbench. This commit is to spot which modification leads to the problem --- .../fpga_bitstream/build_fabric_bitstream.cpp | 11 +- .../src/fpga_bitstream/fabric_bitstream.cpp | 4 +- .../src/fpga_bitstream/fabric_bitstream.h | 6 +- .../fpga_verilog/verilog_top_testbench.cpp | 217 +++++++++++++++++- 4 files changed, 215 insertions(+), 23 deletions(-) diff --git a/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp b/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp index 80026cbef..4dc8ee70b 100644 --- a/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp +++ b/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp @@ -101,7 +101,7 @@ void rec_build_module_fabric_dependent_frame_bitstream(const BitstreamManager& b const std::vector& parent_blocks, const ModuleManager& module_manager, const std::vector& parent_modules, - const std::vector& addr_code, + const std::vector& addr_code, FabricBitstream& fabric_bitstream) { /* Depth-first search: if we have any children in the parent_block, @@ -154,17 +154,14 @@ void rec_build_module_fabric_dependent_frame_bitstream(const BitstreamManager& b child_modules.push_back(child_module); /* Set address, apply binary conversion from the first to the last element in the address list */ - std::vector child_addr_code = addr_code; + std::vector child_addr_code = addr_code; if (true == add_addr_code) { /* Find the address port from the decoder module */ const ModulePortId& decoder_addr_port_id = module_manager.find_module_port(decoder_module, std::string(DECODER_ADDRESS_PORT_NAME)); const BasicPort& decoder_addr_port = module_manager.module_port(decoder_module, decoder_addr_port_id); std::vector addr_bits_vec = itobin_vec(child_id, decoder_addr_port.get_width()); - for (const size_t& bit : addr_bits_vec) { - VTR_ASSERT((0 == bit) || (1 == bit)); - child_addr_code.push_back(bit); - } + child_addr_code.insert(child_addr_code.end(), addr_bits_vec.begin(), addr_bits_vec.end()); } /* Go recursively */ @@ -226,7 +223,7 @@ void build_module_fabric_dependent_bitstream(const ConfigProtocol& config_protoc std::vector(1, top_block), module_manager, std::vector(1, top_module), - std::vector(), + std::vector(), fabric_bitstream); break; } diff --git a/openfpga/src/fpga_bitstream/fabric_bitstream.cpp b/openfpga/src/fpga_bitstream/fabric_bitstream.cpp index 988839cf8..7306eba01 100644 --- a/openfpga/src/fpga_bitstream/fabric_bitstream.cpp +++ b/openfpga/src/fpga_bitstream/fabric_bitstream.cpp @@ -27,7 +27,7 @@ ConfigBitId FabricBitstream::config_bit(const FabricBitId& bit_id) const { return config_bit_ids_[bit_id]; } -std::vector FabricBitstream::bit_address(const FabricBitId& bit_id) const { +std::vector FabricBitstream::bit_address(const FabricBitId& bit_id) const { /* Ensure a valid id */ VTR_ASSERT(true == valid_bit_id(bit_id)); @@ -56,7 +56,7 @@ FabricBitId FabricBitstream::add_bit(const ConfigBitId& config_bit_id) { } void FabricBitstream::set_bit_address(const FabricBitId& bit_id, - const std::vector& address) { + const std::vector& address) { VTR_ASSERT(true == valid_bit_id(bit_id)); bit_addresses_[bit_id] = address; } diff --git a/openfpga/src/fpga_bitstream/fabric_bitstream.h b/openfpga/src/fpga_bitstream/fabric_bitstream.h index 0b4842aa8..12d1c7885 100644 --- a/openfpga/src/fpga_bitstream/fabric_bitstream.h +++ b/openfpga/src/fpga_bitstream/fabric_bitstream.h @@ -52,7 +52,7 @@ class FabricBitstream { ConfigBitId config_bit(const FabricBitId& bit_id) const; /* Find the address of bitstream */ - std::vector bit_address(const FabricBitId& bit_id) const; + std::vector bit_address(const FabricBitId& bit_id) const; /* Find the data-in of bitstream */ bool bit_din(const FabricBitId& bit_id) const; @@ -62,7 +62,7 @@ class FabricBitstream { FabricBitId add_bit(const ConfigBitId& config_bit_id); void set_bit_address(const FabricBitId& bit_id, - const std::vector& address); + const std::vector& address); void set_bit_din(const FabricBitId& bit_id, const bool& din); @@ -84,7 +84,7 @@ class FabricBitstream { * Here we store the binary format of the address, which can be loaded * to the configuration protocol directly */ - vtr::vector> bit_addresses_; + vtr::vector> bit_addresses_; /* Data input (Din) bits: this is designed for memory decoders */ vtr::vector bit_dins_; diff --git a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp index 9edce0b52..3d2cad1ea 100644 --- a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp +++ b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp @@ -41,7 +41,7 @@ constexpr char* TOP_TESTBENCH_FPGA_OUTPUT_POSTFIX = "_fpga"; constexpr char* TOP_TESTBENCH_CHECKFLAG_PORT_POSTFIX = "_flag"; -constexpr char* TOP_TESTBENCH_CC_PROG_TASK_NAME = "prog_cycle_task"; +constexpr char* TOP_TESTBENCH_PROG_TASK_NAME = "prog_cycle_task"; constexpr char* TOP_TESTBENCH_SIM_START_PORT_NAME = "sim_start"; @@ -79,12 +79,49 @@ void print_verilog_top_testbench_config_chain_port(std::fstream& fp) { fp << generate_verilog_port(VERILOG_PORT_WIRE, config_chain_tail_port) << ";" << std::endl; } +/******************************************************************** + * Print local wires for frame-based decoder protocols + *******************************************************************/ +static +void print_verilog_top_testbench_frame_decoder_port(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module) { + /* Validate the file stream */ + valid_file_stream(fp); + + /* Print the address port for the frame-based decoder here */ + print_verilog_comment(fp, std::string("---- Address port for frame-based decoder -----")); + ModulePortId addr_port_id = module_manager.find_module_port(top_module, + std::string(DECODER_ADDRESS_PORT_NAME)); + BasicPort addr_port = module_manager.module_port(top_module, addr_port_id); + + fp << generate_verilog_port(VERILOG_PORT_REG, addr_port) << ";" << std::endl; + + /* Print the data-input port for the frame-based decoder here */ + print_verilog_comment(fp, std::string("---- Data input port for frame-based decoder -----")); + ModulePortId din_port_id = module_manager.find_module_port(top_module, + std::string(DECODER_DATA_IN_PORT_NAME)); + BasicPort din_port = module_manager.module_port(top_module, din_port_id); + fp << generate_verilog_port(VERILOG_PORT_WIRE, din_port) << ";" << std::endl; + + /* Wire the programming clock to the enable signal */ + print_verilog_comment(fp, std::string("---- Wire enable port of frame-based decoder to programming clock -----")); + ModulePortId en_port_id = module_manager.find_module_port(top_module, + std::string(DECODER_ENABLE_PORT_NAME)); + BasicPort en_port = module_manager.module_port(top_module, en_port_id); + BasicPort prog_clock_port(std::string(TOP_TB_PROG_CLOCK_PORT_NAME), 1); + + print_verilog_wire_connection(fp, en_port, prog_clock_port, false); +} + /******************************************************************** * Print local wires for different types of configuration protocols *******************************************************************/ static void print_verilog_top_testbench_config_protocol_port(std::fstream& fp, - const e_config_protocol_type& sram_orgz_type) { + const e_config_protocol_type& sram_orgz_type, + const ModuleManager& module_manager, + const ModuleId& top_module) { switch(sram_orgz_type) { case CONFIG_MEM_STANDALONE: /* TODO */ @@ -95,6 +132,9 @@ void print_verilog_top_testbench_config_protocol_port(std::fstream& fp, case CONFIG_MEM_MEMORY_BANK: /* TODO */ break; + case CONFIG_MEM_FRAME_BASED: + print_verilog_top_testbench_frame_decoder_port(fp, module_manager, top_module); + break; default: VTR_LOGF_ERROR(__FILE__, __LINE__, "Invalid type of SRAM organization!\n"); @@ -398,7 +438,8 @@ void print_verilog_top_testbench_ports(std::fstream& fp, fp << generate_verilog_port(VERILOG_PORT_REG, set_port) << ";" << std::endl; /* Configuration ports depend on the organization of SRAMs */ - print_verilog_top_testbench_config_protocol_port(fp, sram_orgz_type); + print_verilog_top_testbench_config_protocol_port(fp, sram_orgz_type, + module_manager, top_module); /* Create a clock port if the benchmark have one but not in the default name! * We will wire the clock directly to the operating clock directly @@ -506,15 +547,72 @@ void print_verilog_top_testbench_load_bitstream_task_configuration_chain(std::fs * It aims at avoid racing the programming clock (scan-chain data changes at the rising edge). */ print_verilog_comment(fp, std::string("----- Task: input values during a programming clock cycle -----")); - fp << "task " << std::string(TOP_TESTBENCH_CC_PROG_TASK_NAME) << ";" << std::endl; + fp << "task " << std::string(TOP_TESTBENCH_PROG_TASK_NAME) << ";" << std::endl; fp << generate_verilog_port(VERILOG_PORT_INPUT, cc_head_value) << ";" << std::endl; fp << "\tbegin" << std::endl; fp << "\t\t@(negedge " << generate_verilog_port(VERILOG_PORT_CONKT, prog_clock_port) << ");" << std::endl; fp << "\t\t\t"; - fp << generate_verilog_port(VERILOG_PORT_CONKT, cc_head_port); - fp << " = "; - fp << generate_verilog_port(VERILOG_PORT_CONKT, cc_head_value); - fp << ";" << std::endl; + print_verilog_wire_connection(fp, cc_head_port, cc_head_value, false); + fp << std::endl; + + fp << "\tend" << std::endl; + fp << "endtask" << std::endl; + + /* Add an empty line as splitter */ + fp << std::endl; +} + +/******************************************************************** + * Print tasks (processes) in Verilog format, + * which is very useful in generating stimuli for each clock cycle + * This function is tuned for frame-based memory manipulation: + * During each programming cycle, we feed + * - an address to the address port of top module + * - a data input to the din port of top module + *******************************************************************/ +static +void print_verilog_top_testbench_load_bitstream_task_frame_decoder(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module) { + + /* Validate the file stream */ + valid_file_stream(fp); + + BasicPort prog_clock_port(std::string(TOP_TB_PROG_CLOCK_PORT_NAME), 1); + + ModulePortId addr_port_id = module_manager.find_module_port(top_module, + std::string(DECODER_ADDRESS_PORT_NAME)); + BasicPort addr_port = module_manager.module_port(top_module, addr_port_id); + BasicPort addr_value = addr_port; + addr_value.set_name(std::string(DECODER_ADDRESS_PORT_NAME) + std::string("_val")); + + ModulePortId din_port_id = module_manager.find_module_port(top_module, + std::string(DECODER_DATA_IN_PORT_NAME)); + BasicPort din_port = module_manager.module_port(top_module, din_port_id); + BasicPort din_value = din_port; + din_value.set_name(std::string(DECODER_DATA_IN_PORT_NAME) + std::string("_val")); + + /* Add an empty line as splitter */ + fp << std::endl; + + /* Feed the address and data input at each rising edge of programming clock + * As the enable signal is wired to the programming clock, we should synchronize + * address and data with the enable signal + */ + print_verilog_comment(fp, std::string("----- Task: address and data values during a programming clock cycle -----")); + fp << "task " << std::string(TOP_TESTBENCH_PROG_TASK_NAME) << ";" << std::endl; + fp << generate_verilog_port(VERILOG_PORT_INPUT, addr_value) << ";" << std::endl; + fp << generate_verilog_port(VERILOG_PORT_INPUT, din_value) << ";" << std::endl; + fp << "\tbegin" << std::endl; + fp << "\t\t@(posedge " << generate_verilog_port(VERILOG_PORT_CONKT, prog_clock_port) << ");" << std::endl; + + fp << "\t\t\t"; + print_verilog_wire_connection(fp, addr_port, addr_value, false); + fp << std::endl; + + fp << "\t\t\t"; + print_verilog_wire_connection(fp, din_port, din_value, false); + fp << std::endl; fp << "\tend" << std::endl; fp << "endtask" << std::endl; @@ -528,7 +626,9 @@ void print_verilog_top_testbench_load_bitstream_task_configuration_chain(std::fs *******************************************************************/ static void print_verilog_top_testbench_load_bitstream_task(std::fstream& fp, - const e_config_protocol_type& sram_orgz_type) { + const e_config_protocol_type& sram_orgz_type, + const ModuleManager& module_manager, + const ModuleId& top_module) { switch (sram_orgz_type) { case CONFIG_MEM_STANDALONE: break; @@ -540,6 +640,11 @@ void print_verilog_top_testbench_load_bitstream_task(std::fstream& fp, dump_verilog_top_testbench_stimuli_serial_version_tasks_memory_bank(cur_sram_orgz_info, fp); */ break; + case CONFIG_MEM_FRAME_BASED: + print_verilog_top_testbench_load_bitstream_task_frame_decoder(fp, + module_manager, + top_module); + break; default: VTR_LOGF_ERROR(__FILE__, __LINE__, "Invalid type of SRAM organization!\n"); @@ -721,7 +826,7 @@ void print_verilog_top_testbench_configuration_chain_bitstream(std::fstream& fp, * We will visit the fabric bitstream in a reverse way */ for (const FabricBitId& bit_id : fabric_bitstream.bits()) { - fp << "\t\t" << std::string(TOP_TESTBENCH_CC_PROG_TASK_NAME); + fp << "\t\t" << std::string(TOP_TESTBENCH_PROG_TASK_NAME); fp << "(1'b" << (size_t)bitstream_manager.bit_value(fabric_bitstream.config_bit(bit_id)) << ");" << std::endl; } @@ -741,6 +846,86 @@ void print_verilog_top_testbench_configuration_chain_bitstream(std::fstream& fp, print_verilog_comment(fp, "----- End bitstream loading during configuration phase -----"); } +/******************************************************************** + * Print stimulus for a FPGA fabric with a frame-based configuration protocol + * where configuration bits are programming in serial (one by one) + * + * We will use the programming task function created before + *******************************************************************/ +static +void print_verilog_top_testbench_frame_decoder_bitstream(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module, + const FabricBitstream& fabric_bitstream) { + /* Validate the file stream */ + valid_file_stream(fp); + + /* Feed addresss and data input pair one by one + * Note: the first cycle is reserved for programming reset + * We should give dummy values + */ + ModulePortId addr_port_id = module_manager.find_module_port(top_module, + std::string(DECODER_ADDRESS_PORT_NAME)); + BasicPort addr_port = module_manager.module_port(top_module, addr_port_id); + std::vector initial_addr_values(addr_port.get_width(), 0); + + ModulePortId din_port_id = module_manager.find_module_port(top_module, + std::string(DECODER_DATA_IN_PORT_NAME)); + BasicPort din_port = module_manager.module_port(top_module, din_port_id); + std::vector initial_din_values(din_port.get_width(), 0); + + print_verilog_comment(fp, "----- Begin bitstream loading during configuration phase -----"); + fp << "initial" << std::endl; + fp << "\tbegin" << std::endl; + print_verilog_comment(fp, "----- Address port default input -----"); + fp << "\t\t"; + fp << generate_verilog_port_constant_values(addr_port, initial_addr_values); + fp << ";"; + + print_verilog_comment(fp, "----- Data-input port default input -----"); + fp << "\t\t"; + fp << generate_verilog_port_constant_values(din_port, initial_din_values); + fp << ";"; + + fp << std::endl; + + /* Attention: the configuration chain protcol requires the last configuration bit is fed first + * We will visit the fabric bitstream in a reverse way + */ + for (const FabricBitId& bit_id : fabric_bitstream.bits()) { + fp << "\t\t" << std::string(TOP_TESTBENCH_PROG_TASK_NAME); + fp << "(" << addr_port.get_width() << "'b"; + VTR_ASSERT(addr_port.get_width() == fabric_bitstream.bit_address(bit_id).size()); + for (const size_t& addr_bit : fabric_bitstream.bit_address(bit_id)) { + fp << addr_bit; + } + fp << ", "; + fp <<"1'b"; + if (true == fabric_bitstream.bit_din(bit_id)) { + fp << "1"; + } else { + VTR_ASSERT(false == fabric_bitstream.bit_din(bit_id)); + fp << "0"; + } + fp << ");" << std::endl; + } + + /* Raise the flag of configuration done when bitstream loading is complete */ + BasicPort prog_clock_port(std::string(TOP_TB_PROG_CLOCK_PORT_NAME), 1); + fp << "\t\t@(negedge " << generate_verilog_port(VERILOG_PORT_CONKT, prog_clock_port) << ");" << std::endl; + + BasicPort config_done_port(std::string(TOP_TB_CONFIG_DONE_PORT_NAME), 1); + fp << "\t\t\t"; + fp << generate_verilog_port(VERILOG_PORT_CONKT, config_done_port); + fp << " <= "; + std::vector config_done_enable_values(config_done_port.get_width(), 1); + fp << generate_verilog_constant_values(config_done_enable_values); + fp << ";" << std::endl; + + fp << "\tend" << std::endl; + print_verilog_comment(fp, "----- End bitstream loading during configuration phase -----"); +} + /******************************************************************** * Generate the stimuli for the top-level testbench * The simulation consists of two phases: configuration phase and operation phase @@ -750,6 +935,8 @@ void print_verilog_top_testbench_configuration_chain_bitstream(std::fstream& fp, static void print_verilog_top_testbench_bitstream(std::fstream& fp, const e_config_protocol_type& sram_orgz_type, + const ModuleManager& module_manager, + const ModuleId& top_module, const BitstreamManager& bitstream_manager, const FabricBitstream& fabric_bitstream) { /* Branch on the type of configuration protocol */ @@ -763,6 +950,11 @@ void print_verilog_top_testbench_bitstream(std::fstream& fp, case CONFIG_MEM_MEMORY_BANK: /* TODO */ break; + case CONFIG_MEM_FRAME_BASED: + print_verilog_top_testbench_frame_decoder_bitstream(fp, + module_manager, top_module, + fabric_bitstream); + break; default: VTR_LOGF_ERROR(__FILE__, __LINE__, "Invalid SRAM organization type!\n"); @@ -875,10 +1067,13 @@ void print_verilog_top_testbench(const ModuleManager& module_manager, explicit_port_mapping); /* Print tasks used for loading bitstreams */ - print_verilog_top_testbench_load_bitstream_task(fp, sram_orgz_type); + print_verilog_top_testbench_load_bitstream_task(fp, + sram_orgz_type, + module_manager, top_module); /* load bitstream to FPGA fabric in a configuration phase */ print_verilog_top_testbench_bitstream(fp, sram_orgz_type, + module_manager, top_module, bitstream_manager, fabric_bitstream); /* Add stimuli for reset, set, clock and iopad signals */ From ece651ade20ac48acb0b753d2254f393d766b27c Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 27 May 2020 18:47:27 -0600 Subject: [PATCH 571/645] bug fixed in the configuration chian errrors --- .../src/fpga_verilog/verilog_top_testbench.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp index 3d2cad1ea..9041df361 100644 --- a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp +++ b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp @@ -552,8 +552,10 @@ void print_verilog_top_testbench_load_bitstream_task_configuration_chain(std::fs fp << "\tbegin" << std::endl; fp << "\t\t@(negedge " << generate_verilog_port(VERILOG_PORT_CONKT, prog_clock_port) << ");" << std::endl; fp << "\t\t\t"; - print_verilog_wire_connection(fp, cc_head_port, cc_head_value, false); - fp << std::endl; + fp << generate_verilog_port(VERILOG_PORT_CONKT, cc_head_port); + fp << " = "; + fp << generate_verilog_port(VERILOG_PORT_CONKT, cc_head_value); + fp << ";" << std::endl; fp << "\tend" << std::endl; fp << "endtask" << std::endl; @@ -607,11 +609,17 @@ void print_verilog_top_testbench_load_bitstream_task_frame_decoder(std::fstream& fp << "\t\t@(posedge " << generate_verilog_port(VERILOG_PORT_CONKT, prog_clock_port) << ");" << std::endl; fp << "\t\t\t"; - print_verilog_wire_connection(fp, addr_port, addr_value, false); + fp << generate_verilog_port(VERILOG_PORT_CONKT, addr_port); + fp << " = "; + fp << generate_verilog_port(VERILOG_PORT_CONKT, addr_value); + fp << ";" << std::endl; fp << std::endl; fp << "\t\t\t"; - print_verilog_wire_connection(fp, din_port, din_value, false); + fp << generate_verilog_port(VERILOG_PORT_CONKT, din_port); + fp << " = "; + fp << generate_verilog_port(VERILOG_PORT_CONKT, din_value); + fp << ";" << std::endl; fp << std::endl; fp << "\tend" << std::endl; From 3fa3b170610905711455011781c3207e878b3b64 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 28 May 2020 10:19:04 -0600 Subject: [PATCH 572/645] start testing the frame-based configuration protocol. To distinguish, rename xml to identify between configuration chain and frame-based. This should not impact the pre-config testbenches. --- ...penfpga.xml => k4_N4_40nm_cc_openfpga.xml} | 0 .../k4_N4_40nm_frame_openfpga.xml | 229 ++++++++++++++++++ .../configuration_chain/config/task.conf | 2 +- 3 files changed, 230 insertions(+), 1 deletion(-) rename openfpga_flow/openfpga_arch/{k4_N4_40nm_openfpga.xml => k4_N4_40nm_cc_openfpga.xml} (100%) create mode 100644 openfpga_flow/openfpga_arch/k4_N4_40nm_frame_openfpga.xml diff --git a/openfpga_flow/openfpga_arch/k4_N4_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k4_N4_40nm_cc_openfpga.xml similarity index 100% rename from openfpga_flow/openfpga_arch/k4_N4_40nm_openfpga.xml rename to openfpga_flow/openfpga_arch/k4_N4_40nm_cc_openfpga.xml diff --git a/openfpga_flow/openfpga_arch/k4_N4_40nm_frame_openfpga.xml b/openfpga_flow/openfpga_arch/k4_N4_40nm_frame_openfpga.xml new file mode 100644 index 000000000..e5fe5a89b --- /dev/null +++ b/openfpga_flow/openfpga_arch/k4_N4_40nm_frame_openfpga.xml @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + + + + + 10e-12 5e-12 5e-12 + + + 10e-12 5e-12 5e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/tasks/openfpga_shell/configuration_chain/config/task.conf b/openfpga_flow/tasks/openfpga_shell/configuration_chain/config/task.conf index 9e18efc65..8318c1dea 100644 --- a/openfpga_flow/tasks/openfpga_shell/configuration_chain/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/configuration_chain/config/task.conf @@ -15,7 +15,7 @@ spice_output=false verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif -openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k4_N4_40nm_openfpga.xml +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k4_N4_40nm_cc_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k4_N4_tileable_40nm.xml From 3a0d3b4e95e13b84d83bc649c9d338f4b7e958bd Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 28 May 2020 11:01:08 -0600 Subject: [PATCH 573/645] fix the broken CI/regression tests due to incorrect file path --- .../src/check_circuit_library.cpp | 2 +- .../src/read_xml_circuit_library.cpp | 2 +- .../configuration_frame/config/task.conf | 34 +++++++++++++++++++ .../sdc_time_unit/config/task.conf | 2 +- 4 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 openfpga_flow/tasks/openfpga_shell/configuration_frame/config/task.conf diff --git a/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp b/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp index f37742a5a..11cea2d44 100644 --- a/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp +++ b/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp @@ -282,7 +282,7 @@ size_t check_sram_circuit_model_ports(const CircuitLibrary& circuit_lib, /* Check if we has 1 output with size 2 */ num_err += check_one_circuit_model_port_type_and_size_required(circuit_lib, circuit_model, CIRCUIT_MODEL_PORT_OUTPUT, - 1, 2, false); + 2, 2, false); /* basic check finished here */ if (false == check_blwl) { return num_err; diff --git a/libopenfpga/libarchopenfpga/src/read_xml_circuit_library.cpp b/libopenfpga/libarchopenfpga/src/read_xml_circuit_library.cpp index ba6e6af7f..7a894ab34 100644 --- a/libopenfpga/libarchopenfpga/src/read_xml_circuit_library.cpp +++ b/libopenfpga/libarchopenfpga/src/read_xml_circuit_library.cpp @@ -553,7 +553,7 @@ void read_xml_circuit_port(pugi::xml_node& xml_port, || (CIRCUIT_MODEL_PORT_WL == circuit_lib.port_type(port)) || (CIRCUIT_MODEL_PORT_BLB == circuit_lib.port_type(port)) || (CIRCUIT_MODEL_PORT_WLB == circuit_lib.port_type(port)) ) { - circuit_lib.set_port_inv_model_name(port, get_attribute(xml_port, "inv_circuit_model_name", loc_data, pugiutil::ReqOpt::OPTIONAL).as_string(nullptr)); + circuit_lib.set_port_inv_model_name(port, get_attribute(xml_port, "inv_circuit_model_name", loc_data, pugiutil::ReqOpt::OPTIONAL).as_string()); } } diff --git a/openfpga_flow/tasks/openfpga_shell/configuration_frame/config/task.conf b/openfpga_flow/tasks/openfpga_shell/configuration_frame/config/task.conf new file mode 100644 index 000000000..87c487f63 --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/configuration_frame/config/task.conf @@ -0,0 +1,34 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/configuration_chain_example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k4_N4_40nm_frame_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k4_N4_tileable_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.blif + +[SYNTHESIS_PARAM] +bench0_top = and2 +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.v +bench0_chan_width = 300 + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +end_flow_with_test= +#vpr_fpga_verilog_formal_verification_top_netlist= diff --git a/openfpga_flow/tasks/openfpga_shell/sdc_time_unit/config/task.conf b/openfpga_flow/tasks/openfpga_shell/sdc_time_unit/config/task.conf index 1aeaa036f..332020fab 100644 --- a/openfpga_flow/tasks/openfpga_shell/sdc_time_unit/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/sdc_time_unit/config/task.conf @@ -15,7 +15,7 @@ spice_output=false verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif -openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k4_N4_40nm_openfpga.xml +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k4_N4_40nm_cc_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k4_N4_tileable_40nm.xml From 65df309419c483371cd492c981261d36bf63b28f Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 28 May 2020 12:25:47 -0600 Subject: [PATCH 574/645] bug fixing for frame-based configuration protocol and rename some naming function to be generic --- .../src/check_circuit_library.cpp | 8 ++-- openfpga/src/base/openfpga_naming.cpp | 8 ++-- openfpga/src/base/openfpga_naming.h | 4 +- openfpga/src/fabric/build_grid_modules.cpp | 11 +++-- openfpga/src/fabric/build_memory_modules.cpp | 44 ++++++++++++++----- .../fpga_bitstream/arch_bitstream_writer.cpp | 2 +- .../fpga_bitstream/build_grid_bitstream.cpp | 6 +-- .../build_routing_bitstream.cpp | 4 +- .../verilog_preconfig_top_module.cpp | 8 ++-- openfpga/src/utils/circuit_library_utils.cpp | 3 +- openfpga/src/utils/memory_utils.cpp | 19 +++++--- openfpga/src/utils/module_manager_utils.cpp | 21 ++++++--- openfpga/src/utils/mux_utils.cpp | 1 + 13 files changed, 90 insertions(+), 49 deletions(-) diff --git a/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp b/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp index 11cea2d44..fe364bf0f 100644 --- a/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp +++ b/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp @@ -135,9 +135,11 @@ size_t check_one_circuit_model_port_size_required(const CircuitLibrary& circuit_ size_t num_err = 0; if (port_size_to_check != circuit_lib.port_size(circuit_port)) { - VTR_LOG_ERROR(circuit_lib.model_name(circuit_model).c_str(), + VTR_LOG_ERROR("Expect circuit model %s to have %d %s ports but only see %d!\n", + circuit_lib.model_name(circuit_model).c_str(), + port_size_to_check, CIRCUIT_MODEL_PORT_TYPE_STRING[size_t(circuit_lib.port_type(circuit_port))], - port_size_to_check); + circuit_lib.port_size(circuit_port)); /* Incremental the counter for errors */ num_err++; } @@ -282,7 +284,7 @@ size_t check_sram_circuit_model_ports(const CircuitLibrary& circuit_lib, /* Check if we has 1 output with size 2 */ num_err += check_one_circuit_model_port_type_and_size_required(circuit_lib, circuit_model, CIRCUIT_MODEL_PORT_OUTPUT, - 2, 2, false); + 2, 1, false); /* basic check finished here */ if (false == check_blwl) { return num_err; diff --git a/openfpga/src/base/openfpga_naming.cpp b/openfpga/src/base/openfpga_naming.cpp index 5c9c81eb3..dd5e7df2c 100644 --- a/openfpga/src/base/openfpga_naming.cpp +++ b/openfpga/src/base/openfpga_naming.cpp @@ -687,18 +687,18 @@ std::string generate_configuration_chain_tail_name() { } /********************************************************************* - * Generate the memory output port name of a configuration chain + * Generate the memory output port name of a configurable memory * TODO: This could be replaced as a constexpr string *********************************************************************/ -std::string generate_configuration_chain_data_out_name() { +std::string generate_configurable_memory_data_out_name() { return std::string("mem_out"); } /********************************************************************* - * Generate the inverted memory output port name of a configuration chain + * Generate the inverted memory output port name of a configurable memory * TODO: This could be replaced as a constexpr string *********************************************************************/ -std::string generate_configuration_chain_inverted_data_out_name() { +std::string generate_configurable_memory_inverted_data_out_name() { return std::string("mem_outb"); } diff --git a/openfpga/src/base/openfpga_naming.h b/openfpga/src/base/openfpga_naming.h index d3cfb04f0..4e1c44fa9 100644 --- a/openfpga/src/base/openfpga_naming.h +++ b/openfpga/src/base/openfpga_naming.h @@ -168,9 +168,9 @@ std::string generate_configuration_chain_head_name(); std::string generate_configuration_chain_tail_name(); -std::string generate_configuration_chain_data_out_name(); +std::string generate_configurable_memory_data_out_name(); -std::string generate_configuration_chain_inverted_data_out_name(); +std::string generate_configurable_memory_inverted_data_out_name(); std::string generate_mux_local_decoder_addr_port_name(); diff --git a/openfpga/src/fabric/build_grid_modules.cpp b/openfpga/src/fabric/build_grid_modules.cpp index b189a20fe..7a9e3b2b4 100644 --- a/openfpga/src/fabric/build_grid_modules.cpp +++ b/openfpga/src/fabric/build_grid_modules.cpp @@ -414,7 +414,8 @@ void add_module_pb_graph_pin2pin_net(ModuleManager& module_manager, module_manager.add_module_net_sink(pb_module, pin2pin_net, pin_pb_type_module, pin_pb_type_instance, pin_module_port_id, pin_module_pin_id); break; default: - VTR_LOGF_ERROR(__FILE__, __LINE__, "Invalid pin-to-pin interconnection type!\n"); + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid pin-to-pin interconnection type!\n"); exit(1); } } @@ -598,8 +599,9 @@ void add_module_pb_graph_pin_interc(ModuleManager& module_manager, break; } default: - VTR_LOGF_ERROR(__FILE__, __LINE__, "Invalid interconnection type for %s [at Architecture XML LINE%d]!\n", - cur_interc->name, cur_interc->line_num); + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid interconnection type for %s [at Architecture XML LINE%d]!\n", + cur_interc->name, cur_interc->line_num); exit(1); } } @@ -686,7 +688,8 @@ void add_module_pb_graph_port_interc(ModuleManager& module_manager, break; } default: - VTR_LOGF_ERROR(__FILE__, __LINE__, "Invalid pb port type!\n"); + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid pb port type!\n"); exit(1); } } diff --git a/openfpga/src/fabric/build_memory_modules.cpp b/openfpga/src/fabric/build_memory_modules.cpp index f8db73d7a..58ddd6de6 100644 --- a/openfpga/src/fabric/build_memory_modules.cpp +++ b/openfpga/src/fabric/build_memory_modules.cpp @@ -396,10 +396,10 @@ void build_memory_chain_module(ModuleManager& module_manager, for (size_t iport = 0; iport < sram_output_ports.size(); ++iport) { std::string port_name; if (0 == iport) { - port_name = generate_configuration_chain_data_out_name(); + port_name = generate_configurable_memory_data_out_name(); } else { VTR_ASSERT( 1 == iport); - port_name = generate_configuration_chain_inverted_data_out_name(); + port_name = generate_configurable_memory_inverted_data_out_name(); } BasicPort output_port(port_name, num_mems); module_manager.add_port(mem_module, output_port, ModuleManager::MODULE_OUTPUT_PORT); @@ -421,10 +421,10 @@ void build_memory_chain_module(ModuleManager& module_manager, for (size_t iport = 0; iport < sram_output_ports.size(); ++iport) { std::string port_name; if (0 == iport) { - port_name = generate_configuration_chain_data_out_name(); + port_name = generate_configurable_memory_data_out_name(); } else { VTR_ASSERT( 1 == iport); - port_name = generate_configuration_chain_inverted_data_out_name(); + port_name = generate_configurable_memory_inverted_data_out_name(); } std::vector output_nets = add_module_output_nets_to_chain_mem_modules(module_manager, mem_module, port_name, circuit_lib, sram_output_ports[iport], @@ -657,12 +657,22 @@ void build_frame_memory_module(ModuleManager& module_manager, ModulePortId mem_addr_port = module_manager.add_port(mem_module, addr_port, ModuleManager::MODULE_INPUT_PORT); /* Input: Data port */ - BasicPort data_port(std::string(DECODER_DATA_IN_PORT_NAME), data_size); + BasicPort data_port(std::string(DECODER_DATA_IN_PORT_NAME), 1); ModulePortId mem_data_port = module_manager.add_port(mem_module, data_port, ModuleManager::MODULE_INPUT_PORT); + /* Should have only 1 or 2 output port */ + VTR_ASSERT( (1 == sram_output_ports.size()) || ( 2 == sram_output_ports.size()) ); + /* Add each output port: port width should match the number of memories */ - for (const auto& port : sram_output_ports) { - BasicPort output_port(circuit_lib.port_prefix(port), num_mems * circuit_lib.port_size(port)); + for (size_t iport = 0; iport < sram_output_ports.size(); ++iport) { + std::string port_name; + if (0 == iport) { + port_name = generate_configurable_memory_data_out_name(); + } else { + VTR_ASSERT( 1 == iport); + port_name = generate_configurable_memory_inverted_data_out_name(); + } + BasicPort output_port(port_name, num_mems); module_manager.add_port(mem_module, output_port, ModuleManager::MODULE_OUTPUT_PORT); } @@ -717,7 +727,19 @@ void build_frame_memory_module(ModuleManager& module_manager, } /* Wire inputs of parent module to outputs of child modules */ - add_module_output_nets_to_mem_modules(module_manager, mem_module, circuit_lib, sram_output_ports, sram_mem_module, i, sram_instance); + for (size_t iport = 0; iport < sram_output_ports.size(); ++iport) { + std::string port_name; + if (0 == iport) { + port_name = generate_configurable_memory_data_out_name(); + } else { + VTR_ASSERT( 1 == iport); + port_name = generate_configurable_memory_inverted_data_out_name(); + } + + add_module_output_nets_to_chain_mem_modules(module_manager, mem_module, + port_name, circuit_lib, sram_output_ports[iport], + sram_mem_module, i, sram_instance); + } } /* Add global ports to the pb_module: @@ -727,7 +749,6 @@ void build_frame_memory_module(ModuleManager& module_manager, add_module_global_ports_from_child_modules(module_manager, mem_module); } - /********************************************************************* * Generate Verilog modules for the memories that are used * by a circuit model @@ -818,8 +839,9 @@ void build_mux_memory_module(ModuleManager& module_manager, */ break; default: - VTR_LOGF_ERROR(__FILE__, __LINE__, "Invalid design technology of multiplexer '%s'\n", - circuit_lib.model_name(mux_model).c_str()); + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid design technology of multiplexer '%s'\n", + circuit_lib.model_name(mux_model).c_str()); exit(1); } } diff --git a/openfpga/src/fpga_bitstream/arch_bitstream_writer.cpp b/openfpga/src/fpga_bitstream/arch_bitstream_writer.cpp index 6a87a6768..7f7e8ed07 100644 --- a/openfpga/src/fpga_bitstream/arch_bitstream_writer.cpp +++ b/openfpga/src/fpga_bitstream/arch_bitstream_writer.cpp @@ -85,7 +85,7 @@ void rec_write_block_bitstream_to_xml_file(std::fstream& fp, fp << "\t" << std::endl; for (const ConfigBitId& child_bit : bitstream_manager.block_bits(block)) { fp << "\t\t" << std::endl; bit_counter++; diff --git a/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp b/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp index 204b80260..204d7b31e 100644 --- a/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp +++ b/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp @@ -98,7 +98,7 @@ void build_primitive_bitstream(BitstreamManager& bitstream_manager, std::string mem_block_name = generate_memory_module_name(circuit_lib, primitive_model, sram_models[0], std::string(MEMORY_MODULE_POSTFIX)); ModuleId mem_module = module_manager.find_module(mem_block_name); VTR_ASSERT (true == module_manager.valid_module_id(mem_module)); - ModulePortId mem_out_port_id = module_manager.find_module_port(mem_module, generate_configuration_chain_data_out_name()); + ModulePortId mem_out_port_id = module_manager.find_module_port(mem_module, generate_configurable_memory_data_out_name()); VTR_ASSERT(mode_select_bitstream.size() == module_manager.module_port(mem_module, mem_out_port_id).get_width()); /* Create a block for the bitstream which corresponds to the memory module associated to the LUT */ @@ -194,7 +194,7 @@ void build_physical_block_pin_interc_bitstream(BitstreamManager& bitstream_manag std::string mem_module_name = generate_mux_subckt_name(circuit_lib, mux_model, datapath_mux_size, std::string(MEMORY_MODULE_POSTFIX)); ModuleId mux_mem_module = module_manager.find_module(mem_module_name); VTR_ASSERT (true == module_manager.valid_module_id(mux_mem_module)); - ModulePortId mux_mem_out_port_id = module_manager.find_module_port(mux_mem_module, generate_configuration_chain_data_out_name()); + ModulePortId mux_mem_out_port_id = module_manager.find_module_port(mux_mem_module, generate_configurable_memory_data_out_name()); VTR_ASSERT(mux_bitstream.size() == module_manager.module_port(mux_mem_module, mux_mem_out_port_id).get_width()); /* Add the bistream to the bitstream manager */ @@ -424,7 +424,7 @@ void build_lut_bitstream(BitstreamManager& bitstream_manager, std::string mem_block_name = generate_memory_module_name(circuit_lib, lut_model, sram_models[0], std::string(MEMORY_MODULE_POSTFIX)); ModuleId mem_module = module_manager.find_module(mem_block_name); VTR_ASSERT (true == module_manager.valid_module_id(mem_module)); - ModulePortId mem_out_port_id = module_manager.find_module_port(mem_module, generate_configuration_chain_data_out_name()); + ModulePortId mem_out_port_id = module_manager.find_module_port(mem_module, generate_configurable_memory_data_out_name()); VTR_ASSERT(lut_bitstream.size() == module_manager.module_port(mem_module, mem_out_port_id).get_width()); /* Create a block for the bitstream which corresponds to the memory module associated to the LUT */ diff --git a/openfpga/src/fpga_bitstream/build_routing_bitstream.cpp b/openfpga/src/fpga_bitstream/build_routing_bitstream.cpp index faef05174..0713fcb09 100644 --- a/openfpga/src/fpga_bitstream/build_routing_bitstream.cpp +++ b/openfpga/src/fpga_bitstream/build_routing_bitstream.cpp @@ -76,7 +76,7 @@ void build_switch_block_mux_bitstream(BitstreamManager& bitstream_manager, std::string mem_module_name = generate_mux_subckt_name(circuit_lib, mux_model, datapath_mux_size, std::string(MEMORY_MODULE_POSTFIX)); ModuleId mux_mem_module = module_manager.find_module(mem_module_name); VTR_ASSERT (true == module_manager.valid_module_id(mux_mem_module)); - ModulePortId mux_mem_out_port_id = module_manager.find_module_port(mux_mem_module, generate_configuration_chain_data_out_name()); + ModulePortId mux_mem_out_port_id = module_manager.find_module_port(mux_mem_module, generate_configurable_memory_data_out_name()); VTR_ASSERT(mux_bitstream.size() == module_manager.module_port(mux_mem_module, mux_mem_out_port_id).get_width()); /* Add the bistream to the bitstream manager */ @@ -230,7 +230,7 @@ void build_connection_block_mux_bitstream(BitstreamManager& bitstream_manager, std::string mem_module_name = generate_mux_subckt_name(circuit_lib, mux_model, datapath_mux_size, std::string(MEMORY_MODULE_POSTFIX)); ModuleId mux_mem_module = module_manager.find_module(mem_module_name); VTR_ASSERT (true == module_manager.valid_module_id(mux_mem_module)); - ModulePortId mux_mem_out_port_id = module_manager.find_module_port(mux_mem_module, generate_configuration_chain_data_out_name()); + ModulePortId mux_mem_out_port_id = module_manager.find_module_port(mux_mem_module, generate_configurable_memory_data_out_name()); VTR_ASSERT(mux_bitstream.size() == module_manager.module_port(mux_mem_module, mux_mem_out_port_id).get_width()); /* Add the bistream to the bitstream manager */ diff --git a/openfpga/src/fpga_verilog/verilog_preconfig_top_module.cpp b/openfpga/src/fpga_verilog/verilog_preconfig_top_module.cpp index 8e577dfb4..973f7d0ff 100644 --- a/openfpga/src/fpga_verilog/verilog_preconfig_top_module.cpp +++ b/openfpga/src/fpga_verilog/verilog_preconfig_top_module.cpp @@ -206,7 +206,7 @@ void print_verilog_preconfig_top_module_assign_bitstream(std::fstream& fp, bit_hierarchy_path += std::string("."); /* Find the bit index in the parent block */ - BasicPort config_data_port(bit_hierarchy_path + generate_configuration_chain_data_out_name(), + BasicPort config_data_port(bit_hierarchy_path + generate_configurable_memory_data_out_name(), bitstream_manager.block_bits(config_block_id).size()); /* Wire it to the configuration bit: access both data out and data outb ports */ @@ -239,7 +239,7 @@ void print_verilog_preconfig_top_module_assign_bitstream(std::fstream& fp, bit_hierarchy_path += std::string("."); /* Find the bit index in the parent block */ - BasicPort config_datab_port(bit_hierarchy_path + generate_configuration_chain_inverted_data_out_name(), + BasicPort config_datab_port(bit_hierarchy_path + generate_configurable_memory_inverted_data_out_name(), bitstream_manager.block_bits(config_block_id).size()); std::vector config_datab_values; @@ -290,10 +290,10 @@ void print_verilog_preconfig_top_module_deposit_bitstream(std::fstream& fp, bit_hierarchy_path += std::string("."); /* Find the bit index in the parent block */ - BasicPort config_data_port(bit_hierarchy_path + generate_configuration_chain_data_out_name(), + BasicPort config_data_port(bit_hierarchy_path + generate_configurable_memory_data_out_name(), bitstream_manager.block_bits(config_block_id).size()); - BasicPort config_datab_port(bit_hierarchy_path + generate_configuration_chain_inverted_data_out_name(), + BasicPort config_datab_port(bit_hierarchy_path + generate_configurable_memory_inverted_data_out_name(), bitstream_manager.block_bits(config_block_id).size()); /* Wire it to the configuration bit: access both data out and data outb ports */ diff --git a/openfpga/src/utils/circuit_library_utils.cpp b/openfpga/src/utils/circuit_library_utils.cpp index 082349008..74d0c1e50 100644 --- a/openfpga/src/utils/circuit_library_utils.cpp +++ b/openfpga/src/utils/circuit_library_utils.cpp @@ -143,7 +143,8 @@ size_t find_circuit_num_shared_config_bits(const CircuitLibrary& circuit_lib, num_shared_config_bits = std::max((int)num_shared_config_bits, (int)find_rram_circuit_num_shared_config_bits(circuit_lib, sram_model, sram_orgz_type)); break; default: - VTR_LOG_ERROR("Invalid design technology for SRAM model!\n"); + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid design technology for SRAM model!\n"); exit(1); } } diff --git a/openfpga/src/utils/memory_utils.cpp b/openfpga/src/utils/memory_utils.cpp index ef0433502..05b6e8736 100644 --- a/openfpga/src/utils/memory_utils.cpp +++ b/openfpga/src/utils/memory_utils.cpp @@ -70,8 +70,8 @@ std::map generate_cmos_mem_module_port2port_map(const Ba /* Link the SRAM output ports of the memory module */ VTR_ASSERT( 2 == mem_output_bus_ports.size() ); - port2port_name_map[generate_configuration_chain_data_out_name()] = mem_output_bus_ports[0]; - port2port_name_map[generate_configuration_chain_inverted_data_out_name()] = mem_output_bus_ports[1]; + port2port_name_map[generate_configurable_memory_data_out_name()] = mem_output_bus_ports[0]; + port2port_name_map[generate_configurable_memory_inverted_data_out_name()] = mem_output_bus_ports[1]; break; } case CONFIG_MEM_MEMORY_BANK: @@ -127,15 +127,16 @@ std::map generate_rram_mem_module_port2port_map(const Ba /* Link the SRAM output ports of the memory module */ VTR_ASSERT( 2 == mem_output_bus_ports.size() ); - port2port_name_map[generate_configuration_chain_data_out_name()] = mem_output_bus_ports[0]; - port2port_name_map[generate_configuration_chain_inverted_data_out_name()] = mem_output_bus_ports[1]; + port2port_name_map[generate_configurable_memory_data_out_name()] = mem_output_bus_ports[0]; + port2port_name_map[generate_configurable_memory_inverted_data_out_name()] = mem_output_bus_ports[1]; break; } case CONFIG_MEM_MEMORY_BANK: /* TODO: link BL/WL/Reserved Ports to the inputs of a memory module */ break; default: - VTR_LOG_ERROR("Invalid type of SRAM organization!\n"); + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid type of SRAM organization!\n"); exit(1); } @@ -342,7 +343,8 @@ std::vector generate_sram_port_names(const CircuitLibrary& circuit_ break; } default: - VTR_LOG_ERROR("Invalid type of SRAM organization !\n"); + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid type of SRAM organization !\n"); exit(1); } @@ -378,8 +380,11 @@ size_t generate_sram_port_size(const e_config_protocol_type sram_orgz_type, break; case CONFIG_MEM_MEMORY_BANK: break; + case CONFIG_MEM_FRAME_BASED: + break; default: - VTR_LOG_ERROR("Invalid type of SRAM organization !\n"); + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid type of SRAM organization!\n"); exit(1); } diff --git a/openfpga/src/utils/module_manager_utils.cpp b/openfpga/src/utils/module_manager_utils.cpp index 27231aaf3..85e71de3f 100644 --- a/openfpga/src/utils/module_manager_utils.cpp +++ b/openfpga/src/utils/module_manager_utils.cpp @@ -234,7 +234,8 @@ void add_sram_ports_to_module_manager(ModuleManager& module_manager, break; } default: - VTR_LOG_ERROR("Invalid type of SRAM organization !\n"); + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid type of SRAM organization !\n"); exit(1); } } @@ -613,8 +614,8 @@ void add_module_nets_between_logic_and_memory_sram_bus(ModuleManager& module_man } /* Get the SRAM port name of memory model */ - /* TODO: this should be a constant expression and it should be the same for all the memory module! */ - std::string memory_model_sram_port_name = generate_configuration_chain_data_out_name(); + /* This should be a constant expression and it should be the same for all the memory module! */ + std::string memory_model_sram_port_name = generate_configurable_memory_data_out_name(); /* Find the corresponding ports in memory module */ ModulePortId mem_module_sram_port_id = module_manager.find_module_port(memory_module, memory_model_sram_port_name); @@ -649,7 +650,7 @@ void add_module_nets_between_logic_and_memory_sram_bus(ModuleManager& module_man } /* Get the SRAM port name of memory model */ - std::string memory_model_sramb_port_name = generate_configuration_chain_inverted_data_out_name(); + std::string memory_model_sramb_port_name = generate_configurable_memory_inverted_data_out_name(); /* Find the corresponding ports in memory module */ ModulePortId mem_module_sramb_port_id = module_manager.find_module_port(memory_module, memory_model_sramb_port_name); @@ -1099,7 +1100,8 @@ void add_module_nets_cmos_memory_config_bus(ModuleManager& module_manager, add_module_nets_cmos_memory_frame_config_bus(module_manager, decoder_lib, parent_module); break; default: - VTR_LOG_ERROR("Invalid type of SRAM organization!\n"); + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid type of SRAM organization!\n"); exit(1); } } @@ -1174,7 +1176,8 @@ void add_module_nets_memory_config_bus(ModuleManager& module_manager, /* TODO: */ break; default: - VTR_LOG_ERROR("Invalid type of memory design technology !\n"); + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid type of memory design technology!\n"); exit(1); } } @@ -1593,7 +1596,11 @@ void add_module_bus_nets(ModuleManager& module_manager, if (src_port.get_width() != des_port.get_width()) { VTR_LOGF_ERROR(__FILE__, __LINE__, - "Unmatched port size: src_port is %lu while des_port is %lu!\n"); + "Unmatched port size: src_port %s is %lu while des_port %s is %lu!\n", + src_port.get_name().c_str(), + src_port.get_width(), + des_port.get_name().c_str(), + des_port.get_width()); exit(1); } diff --git a/openfpga/src/utils/mux_utils.cpp b/openfpga/src/utils/mux_utils.cpp index b921f0589..15c7a97d7 100644 --- a/openfpga/src/utils/mux_utils.cpp +++ b/openfpga/src/utils/mux_utils.cpp @@ -260,6 +260,7 @@ size_t find_cmos_mux_num_config_bits(const CircuitLibrary& circuit_lib, case CONFIG_MEM_MEMORY_BANK: case CONFIG_MEM_SCAN_CHAIN: case CONFIG_MEM_STANDALONE: + case CONFIG_MEM_FRAME_BASED: num_config_bits = mux_graph.num_memory_bits(); break; default: From bf9f62f0f7e8e5f21643b6dbc2bbfec73c5bde36 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 28 May 2020 13:09:01 -0600 Subject: [PATCH 575/645] keep bug fixing for frame-based configuration protocol. --- openfpga/src/fabric/build_grid_modules.cpp | 2 +- openfpga/src/utils/circuit_library_utils.cpp | 25 +++++++++++++++++++- openfpga/src/utils/circuit_library_utils.h | 3 ++- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/openfpga/src/fabric/build_grid_modules.cpp b/openfpga/src/fabric/build_grid_modules.cpp index 7a9e3b2b4..ff2fe5d14 100644 --- a/openfpga/src/fabric/build_grid_modules.cpp +++ b/openfpga/src/fabric/build_grid_modules.cpp @@ -270,7 +270,7 @@ void build_primitive_block_module(ModuleManager& module_manager, } /* Regular (independent) SRAM ports */ - size_t num_config_bits = find_circuit_num_config_bits(circuit_lib, primitive_model); + size_t num_config_bits = find_circuit_num_config_bits(sram_orgz_type, circuit_lib, primitive_model); if (0 < num_config_bits) { add_sram_ports_to_module_manager(module_manager, primitive_module, circuit_lib, sram_model, sram_orgz_type, diff --git a/openfpga/src/utils/circuit_library_utils.cpp b/openfpga/src/utils/circuit_library_utils.cpp index 74d0c1e50..07ef140a3 100644 --- a/openfpga/src/utils/circuit_library_utils.cpp +++ b/openfpga/src/utils/circuit_library_utils.cpp @@ -12,6 +12,7 @@ #include "vtr_log.h" #include "check_circuit_library.h" +#include "decoder_library_utils.h" #include "circuit_library_utils.h" /* begin namespace openfpga */ @@ -161,7 +162,8 @@ size_t find_circuit_num_shared_config_bits(const CircuitLibrary& circuit_lib, * for a multiplexer, because the multiplexer size is determined during * the FPGA architecture generation (NOT during the XML parsing). *******************************************************************/ -size_t find_circuit_num_config_bits(const CircuitLibrary& circuit_lib, +size_t find_circuit_num_config_bits(const e_config_protocol_type& config_protocol_type, + const CircuitLibrary& circuit_lib, const CircuitModelId& circuit_model) { size_t num_config_bits = 0; @@ -170,6 +172,27 @@ size_t find_circuit_num_config_bits(const CircuitLibrary& circuit_lib, num_config_bits += circuit_lib.port_size(sram_port); } + switch (config_protocol_type) { + case CONFIG_MEM_STANDALONE: + case CONFIG_MEM_SCAN_CHAIN: + case CONFIG_MEM_MEMORY_BANK: { + break; + } + case CONFIG_MEM_FRAME_BASED: { + /* For frame-based configuration protocol + * The number of configuration bits is the address size + */ + if (0 < num_config_bits) { + num_config_bits = find_mux_local_decoder_addr_size(num_config_bits); + } + break; + } + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid type of SRAM organization !\n"); + exit(1); + } + return num_config_bits; } diff --git a/openfpga/src/utils/circuit_library_utils.h b/openfpga/src/utils/circuit_library_utils.h index b03c03156..255da4f4f 100644 --- a/openfpga/src/utils/circuit_library_utils.h +++ b/openfpga/src/utils/circuit_library_utils.h @@ -31,7 +31,8 @@ size_t find_circuit_num_shared_config_bits(const CircuitLibrary& circuit_lib, const CircuitModelId& circuit_model, const e_config_protocol_type& sram_orgz_type); -size_t find_circuit_num_config_bits(const CircuitLibrary& circuit_lib, +size_t find_circuit_num_config_bits(const e_config_protocol_type& config_protocol_type, + const CircuitLibrary& circuit_lib, const CircuitModelId& circuit_model); std::vector find_circuit_library_global_ports(const CircuitLibrary& circuit_lib); From 8298bbff78cad19f399dab4e052af9820acc25e0 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 28 May 2020 16:55:13 -0600 Subject: [PATCH 576/645] bug fixed in the fabric bitstream for frame-based configurable memories. --- openfpga/src/fabric/build_memory_modules.cpp | 4 + .../fpga_bitstream/build_fabric_bitstream.cpp | 89 +++++++++++++++++-- openfpga/src/utils/module_manager_utils.cpp | 13 +-- 3 files changed, 92 insertions(+), 14 deletions(-) diff --git a/openfpga/src/fabric/build_memory_modules.cpp b/openfpga/src/fabric/build_memory_modules.cpp index 58ddd6de6..bc04dd07d 100644 --- a/openfpga/src/fabric/build_memory_modules.cpp +++ b/openfpga/src/fabric/build_memory_modules.cpp @@ -701,6 +701,7 @@ void build_frame_memory_module(ModuleManager& module_manager, /* Memory seed module instanciation */ size_t sram_instance = module_manager.num_instance(mem_module, sram_mem_module); module_manager.add_child_module(mem_module, sram_mem_module); + module_manager.add_configurable_child(mem_module, sram_mem_module, sram_instance); /* Wire data_in port to SRAM BL port */ ModulePortId sram_bl_port = module_manager.find_module_port(sram_mem_module, circuit_lib.port_lib_name(sram_bl_ports[0])); @@ -747,6 +748,9 @@ void build_frame_memory_module(ModuleManager& module_manager, * we just need to find all the global ports from the child modules and build a list of it */ add_module_global_ports_from_child_modules(module_manager, mem_module); + + /* Add the decoder as the last configurable children */ + module_manager.add_configurable_child(mem_module, decoder_module, 0); } /********************************************************************* diff --git a/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp b/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp index 4dc8ee70b..f281a72dc 100644 --- a/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp +++ b/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp @@ -79,7 +79,7 @@ void rec_build_module_fabric_dependent_chain_bitstream(const BitstreamManager& b * in a recursive way, following a Depth-First Search (DFS) strategy * For each configuration child, we use its instance name as a key to spot the * configuration bits in bitstream manager. - * Note that it is guarentee that the instance name in module manager is + * Note that it is guarenteed that the instance name in module manager is * consistent with the block names in bitstream manager * We use this link to reorganize the bitstream in the sequence of memories as we stored * in the configurable_children() and configurable_child_instances() of each module of module manager @@ -112,16 +112,21 @@ void rec_build_module_fabric_dependent_frame_bitstream(const BitstreamManager& b const ModuleId& parent_module = parent_modules.back(); size_t num_configurable_children = module_manager.configurable_children(parent_modules.back()).size(); - + + size_t max_child_addr_code_size = 0; bool add_addr_code = true; ModuleId decoder_module = ModuleId::INVALID(); /* Early exit if there is no configurable children */ if (0 == num_configurable_children) { + /* Ensure that there should be no configuration bits in the parent block */ + VTR_ASSERT(0 == bitstream_manager.block_bits(parent_block).size()); return; } - /* For only 1 configurable child, there is not frame decoder need, we can pass on addr code directly */ + /* For only 1 configurable child, + * there is no frame decoder here, we can pass on addr code directly + */ if (1 == num_configurable_children) { add_addr_code = false; } else { @@ -132,10 +137,18 @@ void rec_build_module_fabric_dependent_frame_bitstream(const BitstreamManager& b VTR_ASSERT(2 < num_configurable_children); num_configurable_children--; decoder_module = module_manager.configurable_children(parent_module).back(); + + /* The address code size is the max. of address port of all the configurable children */ + for (size_t child_id = 0; child_id < num_configurable_children; ++child_id) { + ModuleId child_module = module_manager.configurable_children(parent_module)[child_id]; + const ModulePortId& child_addr_port_id = module_manager.find_module_port(child_module, std::string(DECODER_ADDRESS_PORT_NAME)); + const BasicPort& child_addr_port = module_manager.module_port(child_module, child_addr_port_id); + max_child_addr_code_size = std::max((int)child_addr_port.get_width(), (int)max_child_addr_code_size); + } } for (size_t child_id = 0; child_id < num_configurable_children; ++child_id) { - ModuleId child_module = module_manager.configurable_children(parent_modules.back())[child_id]; + ModuleId child_module = module_manager.configurable_children(parent_module)[child_id]; size_t child_instance = module_manager.configurable_child_instances(parent_module)[child_id]; /* Get the instance name and ensure it is not empty */ std::string instance_name = module_manager.instance_name(parent_module, child_module, child_instance); @@ -161,7 +174,46 @@ void rec_build_module_fabric_dependent_frame_bitstream(const BitstreamManager& b const ModulePortId& decoder_addr_port_id = module_manager.find_module_port(decoder_module, std::string(DECODER_ADDRESS_PORT_NAME)); const BasicPort& decoder_addr_port = module_manager.module_port(decoder_module, decoder_addr_port_id); std::vector addr_bits_vec = itobin_vec(child_id, decoder_addr_port.get_width()); - child_addr_code.insert(child_addr_code.end(), addr_bits_vec.begin(), addr_bits_vec.end()); + + child_addr_code.insert(child_addr_code.begin(), addr_bits_vec.begin(), addr_bits_vec.end()); + + /* Note that the address port size of the child module may be smaller than the maximum + * of other child modules at this level. + * We will add dummy '0's to the head of addr_bit_vec. + * + * For example: + * Decoder is the decoder to access all the child modules + * whose address is decoded by the addr_bits_vec + * The child modules may use part of the address lines, + * we should add dummy '0' to fill the gap + * + * Addr_code for child[0]: '000' + addr_bits_vec + * Addr_code for child[1]: '0' + addr_bits_vec + * Addr_code for child[2]: addr_bits_vec + * + * Addr[6:8] + * | + * v + * +-------------------------------------------+ + * | Decoder Module | + * +-------------------------------------------+ + * + * Addr[0:2] Addr[0:4] Addr[0:5] + * | | | + * v v v + * +-----------+ +-------------+ +------------+ + * | Child[0] | | Child[1] | | Child[2] | + * +-----------+ +-------------+ +------------+ + * + * Child[2] has the maximum address lines among the children + * + */ + const ModulePortId& child_addr_port_id = module_manager.find_module_port(child_module, std::string(DECODER_ADDRESS_PORT_NAME)); + const BasicPort& child_addr_port = module_manager.module_port(child_module, child_addr_port_id); + if (0 < max_child_addr_code_size - child_addr_port.get_width()) { + std::vector dummy_codes(max_child_addr_code_size - child_addr_port.get_width(), 0); + child_addr_code.insert(child_addr_code.begin(), dummy_codes.begin(), dummy_codes.end()); + } } /* Go recursively */ @@ -172,17 +224,36 @@ void rec_build_module_fabric_dependent_frame_bitstream(const BitstreamManager& b } /* Ensure that there should be no configuration bits in the parent block */ VTR_ASSERT(0 == bitstream_manager.block_bits(parent_block).size()); + + return; } /* Note that, reach here, it means that this is a leaf node. - * We add the configuration bits to the fabric_bitstream, - * And then, we can return + * A leaf node (a memory module) always has a decoder inside + * which is the last of configurable children. + * We will find the address bit and add it to addr_code + * Then we can add the configuration bits to the fabric_bitstream. */ - for (const ConfigBitId& config_bit : bitstream_manager.block_bits(parent_blocks.back())) { + if (!(1 < module_manager.configurable_children(parent_modules.back()).size())) + VTR_ASSERT(1 < module_manager.configurable_children(parent_modules.back()).size()); + ModuleId decoder_module = module_manager.configurable_children(parent_modules.back()).back(); + /* Find the address port from the decoder module */ + const ModulePortId& decoder_addr_port_id = module_manager.find_module_port(decoder_module, std::string(DECODER_ADDRESS_PORT_NAME)); + const BasicPort& decoder_addr_port = module_manager.module_port(decoder_module, decoder_addr_port_id); + + for (size_t ibit = 0; ibit < bitstream_manager.block_bits(parent_blocks.back()).size(); ++ibit) { + + ConfigBitId config_bit = bitstream_manager.block_bits(parent_blocks.back())[ibit]; + std::vector addr_bits_vec = itobin_vec(ibit, decoder_addr_port.get_width()); + + std::vector child_addr_code = addr_code; + + child_addr_code.insert(child_addr_code.begin(), addr_bits_vec.begin(), addr_bits_vec.end()); + const FabricBitId& fabric_bit = fabric_bitstream.add_bit(config_bit); /* Set address */ - fabric_bitstream.set_bit_address(fabric_bit, addr_code); + fabric_bitstream.set_bit_address(fabric_bit, child_addr_code); /* Set data input */ fabric_bitstream.set_bit_din(fabric_bit, bitstream_manager.bit_value(config_bit)); diff --git a/openfpga/src/utils/module_manager_utils.cpp b/openfpga/src/utils/module_manager_utils.cpp index 85e71de3f..eebe9008a 100644 --- a/openfpga/src/utils/module_manager_utils.cpp +++ b/openfpga/src/utils/module_manager_utils.cpp @@ -941,7 +941,8 @@ void add_module_nets_cmos_memory_frame_decoder_config_bus(ModuleManager& module_ * Note that we only connect to the last few bits of address port */ for (size_t mem_index = 0; mem_index < configurable_children.size(); ++mem_index) { - ModuleId child_module = module_manager.configurable_children(parent_module)[mem_index]; + ModuleId child_module = configurable_children[mem_index]; + size_t child_instance = module_manager.configurable_child_instances(parent_module)[mem_index]; ModulePortId child_addr_port = module_manager.find_module_port(child_module, std::string(DECODER_ADDRESS_PORT_NAME)); BasicPort child_addr_port_info = module_manager.module_port(child_module, child_addr_port); for (size_t ipin = 0; ipin < child_addr_port_info.get_width(); ++ipin) { @@ -959,7 +960,7 @@ void add_module_nets_cmos_memory_frame_decoder_config_bus(ModuleManager& module_ } /* Configure the net sink */ module_manager.add_module_net_sink(parent_module, net, - child_module, 0, + child_module, child_instance, child_addr_port, child_addr_port_info.get_lsb() + ipin); } @@ -969,12 +970,13 @@ void add_module_nets_cmos_memory_frame_decoder_config_bus(ModuleManager& module_ * the memory modules */ ModulePortId parent_din_port = module_manager.find_module_port(parent_module, std::string(DECODER_DATA_IN_PORT_NAME)); - for (size_t mem_index = 0; mem_index < module_manager.configurable_children(parent_module).size(); ++mem_index) { + for (size_t mem_index = 0; mem_index < configurable_children.size(); ++mem_index) { ModuleId child_module = configurable_children[mem_index]; + size_t child_instance = module_manager.configurable_child_instances(parent_module)[mem_index]; ModulePortId child_din_port = module_manager.find_module_port(child_module, std::string(DECODER_DATA_IN_PORT_NAME)); add_module_bus_nets(module_manager, parent_module, parent_module, 0, parent_din_port, - child_module, 0, child_din_port); + child_module, child_instance, child_din_port); } /* Connect the data_out port of the decoder module @@ -985,6 +987,7 @@ void add_module_nets_cmos_memory_frame_decoder_config_bus(ModuleManager& module_ VTR_ASSERT(decoder_dout_port_info.get_width() == configurable_children.size()); for (size_t mem_index = 0; mem_index < configurable_children.size(); ++mem_index) { ModuleId child_module = configurable_children[mem_index]; + size_t child_instance = module_manager.configurable_child_instances(parent_module)[mem_index]; ModulePortId child_en_port = module_manager.find_module_port(child_module, std::string(DECODER_ENABLE_PORT_NAME)); BasicPort child_en_port_info = module_manager.module_port(child_module, child_en_port); for (size_t ipin = 0; ipin < child_en_port_info.get_width(); ++ipin) { @@ -1002,7 +1005,7 @@ void add_module_nets_cmos_memory_frame_decoder_config_bus(ModuleManager& module_ } /* Configure the net sink */ module_manager.add_module_net_sink(parent_module, net, - child_module, 0, + child_module, child_instance, child_en_port, child_en_port_info.pins()[ipin]); } From 1e73fd6def99fc5def3011a31fd13509c685607a Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 28 May 2020 16:57:04 -0600 Subject: [PATCH 577/645] create configuration frame example script --- ...onfiguration_frame_example_script.openfpga | 68 +++++++++++++++++++ .../configuration_frame/config/task.conf | 2 +- 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 openfpga_flow/OpenFPGAShellScripts/configuration_frame_example_script.openfpga diff --git a/openfpga_flow/OpenFPGAShellScripts/configuration_frame_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/configuration_frame_example_script.openfpga new file mode 100644 index 000000000..bcc8d36e3 --- /dev/null +++ b/openfpga_flow/OpenFPGAShellScripts/configuration_frame_example_script.openfpga @@ -0,0 +1,68 @@ +# Run VPR for the 'and' design +#--write_rr_graph example_rr_graph.xml +vpr ${VPR_ARCH_FILE} ${VPR_TESTBENCH_BLIF} --clock_modeling route + +# Read OpenFPGA architecture definition +read_openfpga_arch -f ${OPENFPGA_ARCH_FILE} + +# Annotate the OpenFPGA architecture to VPR data base +# to debug use --verbose options +link_openfpga_arch --activity_file ${ACTIVITY_FILE} --sort_gsb_chan_node_in_edges + +# Check and correct any naming conflicts in the BLIF netlist +check_netlist_naming_conflict --fix --report ./netlist_renaming.xml + +# Apply fix-up to clustering nets based on routing results +pb_pin_fixup --verbose + +# Apply fix-up to Look-Up Table truth tables based on packing results +lut_truth_table_fixup + +# Build the module graph +# - Enabled compression on routing architecture modules +# - Enable pin duplication on grid modules +build_fabric --compress_routing #--verbose + +# Write the fabric hierarchy of module graph to a file +# This is used by hierarchical PnR flows +write_fabric_hierarchy --file ./fabric_hierarchy.txt + +# Repack the netlist to physical pbs +# This must be done before bitstream generator and testbench generation +# Strongly recommend it is done after all the fix-up have been applied +repack #--verbose + +# Build the bitstream +# - Output the fabric-independent bitstream to a file +build_architecture_bitstream --verbose --file fabric_indepenent_bitstream.xml + +# Build fabric-dependent bitstream +build_fabric_bitstream --verbose + +# Write the Verilog netlist for FPGA fabric +# - Enable the use of explicit port mapping in Verilog netlist +write_fabric_verilog --file ./SRC --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose + +# Write the Verilog testbench for FPGA fabric +# - We suggest the use of same output directory as fabric Verilog netlists +# - Must specify the reference benchmark file if you want to output any testbenches +# - Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA +# - Enable pre-configured top-level testbench which is a fast verification skipping programming phase +# - Simulation ini file is optional and is needed only when you need to interface different HDL simulators using openfpga flow-run scripts +write_verilog_testbench --file ./SRC --reference_benchmark_file_path ${REFERENCE_VERILOG_TESTBENCH} --print_top_testbench --print_preconfig_top_testbench --print_simulation_ini ./SimulationDeck/simulation_deck.ini --explicit_port_mapping + +# Write the SDC files for PnR backend +# - Turn on every options here +write_pnr_sdc --file ./SDC + +# Write SDC to disable timing for configure ports +write_sdc_disable_timing_configure_ports --file ./SDC/disable_configure_ports.sdc + +# Write the SDC to run timing analysis for a mapped FPGA fabric +write_analysis_sdc --file ./SDC_analysis + +# Finish and exit OpenFPGA +exit + +# Note : +# To run verification at the end of the flow maintain source in ./SRC directory diff --git a/openfpga_flow/tasks/openfpga_shell/configuration_frame/config/task.conf b/openfpga_flow/tasks/openfpga_shell/configuration_frame/config/task.conf index 87c487f63..7a448e3e8 100644 --- a/openfpga_flow/tasks/openfpga_shell/configuration_frame/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/configuration_frame/config/task.conf @@ -8,7 +8,7 @@ [GENERAL] run_engine=openfpga_shell -openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/configuration_chain_example_script.openfpga +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/configuration_frame_example_script.openfpga power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml power_analysis = true spice_output=false From 8aa665b3b2856e1dddf9a6fa430c30486b52b7f5 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 28 May 2020 17:46:14 -0600 Subject: [PATCH 578/645] bug fix in the Verilog codes for frame decoders --- openfpga/src/fpga_verilog/verilog_decoders.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/openfpga/src/fpga_verilog/verilog_decoders.cpp b/openfpga/src/fpga_verilog/verilog_decoders.cpp index 66a1a8ed8..9a37ba46b 100644 --- a/openfpga/src/fpga_verilog/verilog_decoders.cpp +++ b/openfpga/src/fpga_verilog/verilog_decoders.cpp @@ -330,8 +330,8 @@ void print_verilog_arch_decoder_module(std::fstream& fp, * The rest of addr codes 3'b110, 3'b111 will be decoded to data=8'b0_0000; */ - fp << "\t" << "always@(" << generate_verilog_port(VERILOG_PORT_CONKT, addr_port) << ")" << std::endl; - fp << "\tif (" << generate_verilog_port(VERILOG_PORT_CONKT, enable_port) << "1'b1) begin" << std::endl; + fp << "always@(" << generate_verilog_port(VERILOG_PORT_CONKT, addr_port) << ") begin" << std::endl; + fp << "\tif (" << generate_verilog_port(VERILOG_PORT_CONKT, enable_port) << " == 1'b1) begin" << std::endl; fp << "\t\t" << "case (" << generate_verilog_port(VERILOG_PORT_CONKT, addr_port) << ")" << std::endl; /* Create a string for addr and data */ for (size_t i = 0; i < data_size; ++i) { @@ -340,11 +340,19 @@ void print_verilog_arch_decoder_module(std::fstream& fp, fp << generate_verilog_port_constant_values(data_port, ito1hot_vec(i, data_size)); fp << ";" << std::endl; } + /* Different from the MUX encoders, architecture decoders will output all-zero by default!!! */ fp << "\t\t\t" << "default : "; - fp << generate_verilog_port_constant_values(data_port, ito1hot_vec(data_size - 1, data_size)); + fp << generate_verilog_port_constant_values(data_port, ito1hot_vec(data_size, data_size)); fp << ";" << std::endl; fp << "\t\t" << "endcase" << std::endl; fp << "\t" << "end" << std::endl; + + /* If not enabled, we output all-zero */ + fp << "\t" << "else begin" << std::endl; + fp << "\t\t" << generate_verilog_port_constant_values(data_port, ito1hot_vec(data_size, data_size)) << ";"<< std::endl; + fp << "\t" << "end" << std::endl; + + fp << "end" << std::endl; if (true == decoder_lib.use_data_inv_port(decoder)) { print_verilog_wire_connection(fp, data_inv_port, data_port, true); From f5968fda52ea31f21026f2d9c32bfb0f1709e1a7 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 28 May 2020 18:13:06 -0600 Subject: [PATCH 579/645] add configurable latch Verilog codes --- openfpga_flow/VerilogNetlists/config_latch.v | 38 ++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 openfpga_flow/VerilogNetlists/config_latch.v diff --git a/openfpga_flow/VerilogNetlists/config_latch.v b/openfpga_flow/VerilogNetlists/config_latch.v new file mode 100644 index 000000000..6cbe5657e --- /dev/null +++ b/openfpga_flow/VerilogNetlists/config_latch.v @@ -0,0 +1,38 @@ +//----------------------------------------------------- +// Design Name : config_latch +// File Name : config_latch.v +// Function : A Configurable Latch where data storage +// can be updated at rising clock edge +// when wl is enabled +// Coder : Xifan TANG +//----------------------------------------------------- +module config_latch ( + input reset, // Reset input + input clk, // Clock Input + input wl, // Data Enable + input bl, // Data Input + output Q, // Q output + output Qb // Q output +); +//------------Internal Variables-------- +reg q_reg; + +//-------------Code Starts Here--------- +always @ ( posedge clk or posedge reset) begin + if (reset) begin + q_reg <= 1'b0; + end else if (1'b1 == wl) begin + q_reg <= bl; + end +end + +`ifndef ENABLE_FORMAL_VERIFICATION +// Wire q_reg to Q +assign Q = q_reg; +assign Qb = ~q_reg; +`else +assign Q = 1'bZ; +assign Qb = !Q; +`endif + +endmodule From 6a72c66eb8e610bbc300590a35bc8496a0d3c47b Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 28 May 2020 18:22:27 -0600 Subject: [PATCH 580/645] bug fixed for frame-based configuration memory in top-level full testbench --- openfpga/src/fpga_verilog/verilog_decoders.cpp | 5 +++++ openfpga/src/fpga_verilog/verilog_top_testbench.cpp | 3 ++- openfpga_flow/openfpga_arch/k4_N4_40nm_frame_openfpga.xml | 6 +++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/openfpga/src/fpga_verilog/verilog_decoders.cpp b/openfpga/src/fpga_verilog/verilog_decoders.cpp index 9a37ba46b..70b65ee61 100644 --- a/openfpga/src/fpga_verilog/verilog_decoders.cpp +++ b/openfpga/src/fpga_verilog/verilog_decoders.cpp @@ -299,7 +299,12 @@ void print_verilog_arch_decoder_module(std::fstream& fp, * data_inv = ~data_inv */ if (1 == data_size) { + fp << "always@(" << generate_verilog_port(VERILOG_PORT_CONKT, addr_port) << ") begin" << std::endl; + fp << "\tif (" << generate_verilog_port(VERILOG_PORT_CONKT, enable_port) << " == 1'b1) begin" << std::endl; + fp << "\t"; print_verilog_wire_connection(fp, data_port, addr_port, false); + fp << "\t" << "end" << std::endl; + fp << "end" << std::endl; /* Depend on if the inverted data output port is needed or not */ if (true == decoder_lib.use_data_inv_port(decoder)) { diff --git a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp index 9041df361..5a5d98aed 100644 --- a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp +++ b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp @@ -102,7 +102,7 @@ void print_verilog_top_testbench_frame_decoder_port(std::fstream& fp, ModulePortId din_port_id = module_manager.find_module_port(top_module, std::string(DECODER_DATA_IN_PORT_NAME)); BasicPort din_port = module_manager.module_port(top_module, din_port_id); - fp << generate_verilog_port(VERILOG_PORT_WIRE, din_port) << ";" << std::endl; + fp << generate_verilog_port(VERILOG_PORT_REG, din_port) << ";" << std::endl; /* Wire the programming clock to the enable signal */ print_verilog_comment(fp, std::string("---- Wire enable port of frame-based decoder to programming clock -----")); @@ -111,6 +111,7 @@ void print_verilog_top_testbench_frame_decoder_port(std::fstream& fp, BasicPort en_port = module_manager.module_port(top_module, en_port_id); BasicPort prog_clock_port(std::string(TOP_TB_PROG_CLOCK_PORT_NAME), 1); + fp << generate_verilog_port(VERILOG_PORT_WIRE, en_port) << ";" << std::endl; print_verilog_wire_connection(fp, en_port, prog_clock_port, false); } diff --git a/openfpga_flow/openfpga_arch/k4_N4_40nm_frame_openfpga.xml b/openfpga_flow/openfpga_arch/k4_N4_40nm_frame_openfpga.xml index e5fe5a89b..a0e0194b5 100644 --- a/openfpga_flow/openfpga_arch/k4_N4_40nm_frame_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k4_N4_40nm_frame_openfpga.xml @@ -142,7 +142,7 @@ - + @@ -158,13 +158,13 @@ - + - + From 986956e47468c2213912a3886ed2879b69ac141e Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 28 May 2020 18:29:22 -0600 Subject: [PATCH 581/645] bug fix for arch decoder Verilog codes. Now Modelsim compiles ok. --- openfpga/src/fpga_verilog/verilog_decoders.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openfpga/src/fpga_verilog/verilog_decoders.cpp b/openfpga/src/fpga_verilog/verilog_decoders.cpp index 70b65ee61..2637736ea 100644 --- a/openfpga/src/fpga_verilog/verilog_decoders.cpp +++ b/openfpga/src/fpga_verilog/verilog_decoders.cpp @@ -301,8 +301,8 @@ void print_verilog_arch_decoder_module(std::fstream& fp, if (1 == data_size) { fp << "always@(" << generate_verilog_port(VERILOG_PORT_CONKT, addr_port) << ") begin" << std::endl; fp << "\tif (" << generate_verilog_port(VERILOG_PORT_CONKT, enable_port) << " == 1'b1) begin" << std::endl; - fp << "\t"; - print_verilog_wire_connection(fp, data_port, addr_port, false); + fp << "\t\t" << generate_verilog_port(VERILOG_PORT_CONKT, data_port); + fp << " = " << generate_verilog_port(VERILOG_PORT_CONKT, addr_port) << ";" << std::endl; fp << "\t" << "end" << std::endl; fp << "end" << std::endl; From bdc9efb38fe15dd4711b78ad15b86945d002e6c1 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 28 May 2020 18:34:18 -0600 Subject: [PATCH 582/645] bug fix in top-level testbench for frame-based decoders --- openfpga/src/fpga_verilog/verilog_top_testbench.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp index 5a5d98aed..26ce99b92 100644 --- a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp +++ b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp @@ -598,7 +598,7 @@ void print_verilog_top_testbench_load_bitstream_task_frame_decoder(std::fstream& /* Add an empty line as splitter */ fp << std::endl; - /* Feed the address and data input at each rising edge of programming clock + /* Feed the address and data input at each falling edge of programming clock * As the enable signal is wired to the programming clock, we should synchronize * address and data with the enable signal */ @@ -607,7 +607,7 @@ void print_verilog_top_testbench_load_bitstream_task_frame_decoder(std::fstream& fp << generate_verilog_port(VERILOG_PORT_INPUT, addr_value) << ";" << std::endl; fp << generate_verilog_port(VERILOG_PORT_INPUT, din_value) << ";" << std::endl; fp << "\tbegin" << std::endl; - fp << "\t\t@(posedge " << generate_verilog_port(VERILOG_PORT_CONKT, prog_clock_port) << ");" << std::endl; + fp << "\t\t@(negedge " << generate_verilog_port(VERILOG_PORT_CONKT, prog_clock_port) << ");" << std::endl; fp << "\t\t\t"; fp << generate_verilog_port(VERILOG_PORT_CONKT, addr_port); From b8c449d520931882b5771bcf29d5ddbbc29e7a6f Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 28 May 2020 18:46:47 -0600 Subject: [PATCH 583/645] add comments for decoding functions to help debugging the frame-based decoders --- .../libopenfpgautil/src/openfpga_decode.cpp | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/libopenfpga/libopenfpgautil/src/openfpga_decode.cpp b/libopenfpga/libopenfpgautil/src/openfpga_decode.cpp index 9839f13b9..06b3487b0 100644 --- a/libopenfpga/libopenfpgautil/src/openfpga_decode.cpp +++ b/libopenfpga/libopenfpgautil/src/openfpga_decode.cpp @@ -14,6 +14,21 @@ namespace openfpga { /******************************************************************** * Convert an integer to an one-hot encoding integer array + * For example: + * Input integer: 3 + * Binary length : 4 + * Output: + * index | 0 | 1 | 2 | 3 + * ret | 0 | 0 | 0 | 1 + * + * If you need all zero code, set the input integer same as the binary length + * For example: + * Input integer: 4 + * Binary length : 4 + * Output: + * index | 0 | 1 | 2 | 3 + * ret | 0 | 0 | 0 | 0 + * ********************************************************************/ std::vector ito1hot_vec(const size_t& in_int, const size_t& bin_len) { @@ -33,6 +48,12 @@ std::vector ito1hot_vec(const size_t& in_int, /******************************************************************** * Converter an integer to a binary vector + * For example: + * Input integer: 4 + * Binary length : 3 + * Output: + * index | 0 | 1 | 2 + * ret | 0 | 0 | 1 ********************************************************************/ std::vector itobin_vec(const size_t& in_int, const size_t& bin_len) { From 31c9a011dd8b7a13d019e53409c1a678acd0f5c8 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 28 May 2020 21:24:41 -0600 Subject: [PATCH 584/645] keep bug fixing for arch decoders --- .../src/fpga_verilog/verilog_decoders.cpp | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/openfpga/src/fpga_verilog/verilog_decoders.cpp b/openfpga/src/fpga_verilog/verilog_decoders.cpp index 2637736ea..64568515c 100644 --- a/openfpga/src/fpga_verilog/verilog_decoders.cpp +++ b/openfpga/src/fpga_verilog/verilog_decoders.cpp @@ -299,10 +299,13 @@ void print_verilog_arch_decoder_module(std::fstream& fp, * data_inv = ~data_inv */ if (1 == data_size) { - fp << "always@(" << generate_verilog_port(VERILOG_PORT_CONKT, addr_port) << ") begin" << std::endl; + fp << "always@(" << generate_verilog_port(VERILOG_PORT_CONKT, addr_port); + fp << " or " << generate_verilog_port(VERILOG_PORT_CONKT, enable_port); + fp << ") begin" << std::endl; fp << "\tif (" << generate_verilog_port(VERILOG_PORT_CONKT, enable_port) << " == 1'b1) begin" << std::endl; - fp << "\t\t" << generate_verilog_port(VERILOG_PORT_CONKT, data_port); - fp << " = " << generate_verilog_port(VERILOG_PORT_CONKT, addr_port) << ";" << std::endl; + fp << "\t\t" << generate_verilog_port_constant_values(data_port, std::vector(1, 1)) << ";" << std::endl; + fp << "\t" << "end else begin" << std::endl; + fp << "\t\t" << generate_verilog_port_constant_values(data_port, std::vector(1, 0)) << ";" << std::endl; fp << "\t" << "end" << std::endl; fp << "end" << std::endl; @@ -335,7 +338,9 @@ void print_verilog_arch_decoder_module(std::fstream& fp, * The rest of addr codes 3'b110, 3'b111 will be decoded to data=8'b0_0000; */ - fp << "always@(" << generate_verilog_port(VERILOG_PORT_CONKT, addr_port) << ") begin" << std::endl; + fp << "always@(" << generate_verilog_port(VERILOG_PORT_CONKT, addr_port); + fp << " or " << generate_verilog_port(VERILOG_PORT_CONKT, enable_port); + fp << ") begin" << std::endl; fp << "\tif (" << generate_verilog_port(VERILOG_PORT_CONKT, enable_port) << " == 1'b1) begin" << std::endl; fp << "\t\t" << "case (" << generate_verilog_port(VERILOG_PORT_CONKT, addr_port) << ")" << std::endl; /* Create a string for addr and data */ @@ -345,18 +350,21 @@ void print_verilog_arch_decoder_module(std::fstream& fp, fp << generate_verilog_port_constant_values(data_port, ito1hot_vec(i, data_size)); fp << ";" << std::endl; } - /* Different from the MUX encoders, architecture decoders will output all-zero by default!!! */ - fp << "\t\t\t" << "default : "; + /* Different from MUX decoder, we assign default values which is all zero */ + fp << "\t\t\t" << "default"; + fp << " : "; fp << generate_verilog_port_constant_values(data_port, ito1hot_vec(data_size, data_size)); fp << ";" << std::endl; + fp << "\t\t" << "endcase" << std::endl; fp << "\t" << "end" << std::endl; - - /* If not enabled, we output all-zero */ - fp << "\t" << "else begin" << std::endl; - fp << "\t\t" << generate_verilog_port_constant_values(data_port, ito1hot_vec(data_size, data_size)) << ";"<< std::endl; - fp << "\t" << "end" << std::endl; + /* If enable is not active, we should give all zero */ + fp << "\t" << "else begin" << std::endl; + fp << "\t\t" << generate_verilog_port_constant_values(data_port, ito1hot_vec(data_size, data_size)); + fp << ";" << std::endl; + fp << "\t" << "end" << std::endl; + fp << "end" << std::endl; if (true == decoder_lib.use_data_inv_port(decoder)) { From 583c15131bfa991c73d327aa90d982b49869601f Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 28 May 2020 21:33:44 -0600 Subject: [PATCH 585/645] change configuration latch to be triggered at negative edge; Frame-based fabric passed Modelsim verification but failed in iVerilog --- openfpga_flow/VerilogNetlists/config_latch.v | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openfpga_flow/VerilogNetlists/config_latch.v b/openfpga_flow/VerilogNetlists/config_latch.v index 6cbe5657e..e177fe2e7 100644 --- a/openfpga_flow/VerilogNetlists/config_latch.v +++ b/openfpga_flow/VerilogNetlists/config_latch.v @@ -18,7 +18,7 @@ module config_latch ( reg q_reg; //-------------Code Starts Here--------- -always @ ( posedge clk or posedge reset) begin +always @ ( negedge clk or posedge reset) begin if (reset) begin q_reg <= 1'b0; end else if (1'b1 == wl) begin From b5e5182f524713c6c6c7f1172ca1b1561c092ebb Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 28 May 2020 23:05:24 -0600 Subject: [PATCH 586/645] frame-based configuration protocol is working on k4n4 arch now. Spot bugs in iVerilog about negedge flip-flops --- .../fpga_verilog/verilog_top_testbench.cpp | 25 ++++++++++++++----- openfpga_flow/VerilogNetlists/config_latch.v | 2 +- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp index 26ce99b92..91e5eb8cf 100644 --- a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp +++ b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp @@ -104,15 +104,15 @@ void print_verilog_top_testbench_frame_decoder_port(std::fstream& fp, BasicPort din_port = module_manager.module_port(top_module, din_port_id); fp << generate_verilog_port(VERILOG_PORT_REG, din_port) << ";" << std::endl; - /* Wire the programming clock to the enable signal */ - print_verilog_comment(fp, std::string("---- Wire enable port of frame-based decoder to programming clock -----")); + /* Wire the INVERTED programming clock to the enable signal !!! */ + print_verilog_comment(fp, std::string("---- Wire enable port of frame-based decoder to inverted programming clock -----")); ModulePortId en_port_id = module_manager.find_module_port(top_module, std::string(DECODER_ENABLE_PORT_NAME)); BasicPort en_port = module_manager.module_port(top_module, en_port_id); BasicPort prog_clock_port(std::string(TOP_TB_PROG_CLOCK_PORT_NAME), 1); fp << generate_verilog_port(VERILOG_PORT_WIRE, en_port) << ";" << std::endl; - print_verilog_wire_connection(fp, en_port, prog_clock_port, false); + print_verilog_wire_connection(fp, en_port, prog_clock_port, true); } /******************************************************************** @@ -581,7 +581,9 @@ void print_verilog_top_testbench_load_bitstream_task_frame_decoder(std::fstream& /* Validate the file stream */ valid_file_stream(fp); - BasicPort prog_clock_port(std::string(TOP_TB_PROG_CLOCK_PORT_NAME), 1); + ModulePortId en_port_id = module_manager.find_module_port(top_module, + std::string(DECODER_ENABLE_PORT_NAME)); + BasicPort en_port = module_manager.module_port(top_module, en_port_id); ModulePortId addr_port_id = module_manager.find_module_port(top_module, std::string(DECODER_ADDRESS_PORT_NAME)); @@ -602,12 +604,12 @@ void print_verilog_top_testbench_load_bitstream_task_frame_decoder(std::fstream& * As the enable signal is wired to the programming clock, we should synchronize * address and data with the enable signal */ - print_verilog_comment(fp, std::string("----- Task: address and data values during a programming clock cycle -----")); + print_verilog_comment(fp, std::string("----- Task: assign address and data values at rising edge of enable signal -----")); fp << "task " << std::string(TOP_TESTBENCH_PROG_TASK_NAME) << ";" << std::endl; fp << generate_verilog_port(VERILOG_PORT_INPUT, addr_value) << ";" << std::endl; fp << generate_verilog_port(VERILOG_PORT_INPUT, din_value) << ";" << std::endl; fp << "\tbegin" << std::endl; - fp << "\t\t@(negedge " << generate_verilog_port(VERILOG_PORT_CONKT, prog_clock_port) << ");" << std::endl; + fp << "\t\t@(posedge " << generate_verilog_port(VERILOG_PORT_CONKT, en_port) << ");" << std::endl; fp << "\t\t\t"; fp << generate_verilog_port(VERILOG_PORT_CONKT, addr_port); @@ -919,6 +921,17 @@ void print_verilog_top_testbench_frame_decoder_bitstream(std::fstream& fp, fp << ");" << std::endl; } + /* Disable the address and din */ + fp << "\t\t" << std::string(TOP_TESTBENCH_PROG_TASK_NAME); + fp << "(" << addr_port.get_width() << "'b"; + std::vector all_zero_addr(addr_port.get_width(), 0); + for (const size_t& addr_bit : all_zero_addr) { + fp << addr_bit; + } + fp << ", "; + fp <<"1'b0"; + fp << ");" << std::endl; + /* Raise the flag of configuration done when bitstream loading is complete */ BasicPort prog_clock_port(std::string(TOP_TB_PROG_CLOCK_PORT_NAME), 1); fp << "\t\t@(negedge " << generate_verilog_port(VERILOG_PORT_CONKT, prog_clock_port) << ");" << std::endl; diff --git a/openfpga_flow/VerilogNetlists/config_latch.v b/openfpga_flow/VerilogNetlists/config_latch.v index e177fe2e7..6cbe5657e 100644 --- a/openfpga_flow/VerilogNetlists/config_latch.v +++ b/openfpga_flow/VerilogNetlists/config_latch.v @@ -18,7 +18,7 @@ module config_latch ( reg q_reg; //-------------Code Starts Here--------- -always @ ( negedge clk or posedge reset) begin +always @ ( posedge clk or posedge reset) begin if (reset) begin q_reg <= 1'b0; end else if (1'b1 == wl) begin From cdc2237008ad70104bbde858f80bc13f61bfb790 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 28 May 2020 23:07:03 -0600 Subject: [PATCH 587/645] deploy frame-based configuration protocol to travis CI --- .travis/script.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis/script.sh b/.travis/script.sh index ba4c209a1..0cb3326e6 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -51,6 +51,9 @@ echo -e "Testing OpenFPGA Shell"; echo -e "Testing configuration chain of a K4N4 FPGA"; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/configuration_chain --debug --show_thread_logs +echo -e "Testing fram-based configuration protocol of a K4N4 FPGA"; +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/configuration_frame --debug --show_thread_logs + echo -e "Testing user-defined simulation settings: clock frequency and number of cycles"; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/fixed_simulation_settings --debug --show_thread_logs From de07712a3a1bbb5477ee8853c381fce44178dd3b Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 29 May 2020 14:02:33 -0600 Subject: [PATCH 588/645] update documentation about the frame-based configuration protocol --- .../manual/arch_lang/annotate_vpr_arch.rst | 80 ----------- .../arch_lang/circuit_model_examples.rst | 57 +++++++- .../manual/arch_lang/config_protocol.rst | 126 ++++++++++++++++++ .../manual/arch_lang/figures/config_latch.png | Bin 0 -> 16495 bytes .../figures/frame_config_protocol.png | Bin 0 -> 85962 bytes .../figures/frame_config_protocol_example.png | Bin 0 -> 259112 bytes .../manual/arch_lang/figures/sram_blwl.png | Bin 0 -> 41280 bytes docs/source/manual/arch_lang/index.rst | 2 + 8 files changed, 184 insertions(+), 81 deletions(-) create mode 100644 docs/source/manual/arch_lang/config_protocol.rst create mode 100644 docs/source/manual/arch_lang/figures/config_latch.png create mode 100644 docs/source/manual/arch_lang/figures/frame_config_protocol.png create mode 100644 docs/source/manual/arch_lang/figures/frame_config_protocol_example.png create mode 100644 docs/source/manual/arch_lang/figures/sram_blwl.png diff --git a/docs/source/manual/arch_lang/annotate_vpr_arch.rst b/docs/source/manual/arch_lang/annotate_vpr_arch.rst index 69aed061e..39e02269c 100644 --- a/docs/source/manual/arch_lang/annotate_vpr_arch.rst +++ b/docs/source/manual/arch_lang/annotate_vpr_arch.rst @@ -4,86 +4,6 @@ Bind circuit modules to VPR architecture ---------------------------------------- Each defined circuit model should be linked to an FPGA module defined in the original part of architecture descriptions. It helps FPGA-circuit creating the circuit netlists for logic/routing blocks. Since the original part lacks such support, we create a few XML properties to link to Circuit models. -Configuration Protocol -~~~~~~~~~~~~~~~~~~~~~~ - -Configuration protocol is the circuitry designed to program an FPGA. -As an interface, configuration protocol could be really different in FPGAs, depending on the application context. - -Template -```````` - -.. code-block:: xml - - - - - -.. option:: type="scan_chain|memory_bank|standalone" - - Specify the type of configuration circuits. - - OpenFPGA supports different types of configuration protocols to program FPGA fabrics: - - ``scan_chain``: configurable memories are connected in a chain. Bitstream is loaded serially to program a FPGA - - ``memory_bank``: configurable memories are organized in an array, where each element can be accessed by an unique address to the BL/WL decoders - - ``standalone``: configurable memories are directly accessed through ports of FPGA fabrics. In other words, there are no protocol to control the memories. This allows full customization on the configuration protocol for hardware engineers. - - .. note:: Avoid to use ``standalone`` when designing an FPGA chip. It will causes a huge number of I/Os required, far beyond any package size. It is well applicable to eFPGAs, where designers do need customized protocols between FPGA and processors. - -.. warning:: Currently FPGA-SPICE only supports standalone memory organization. - -.. warning:: Currently RRAM-based FPGA only supports memory-bank organization for Verilog Generator. - -.. option:: circuit_model_name="" - - Specify the name of circuit model to be used as configurable memory. - - ``scan_chain`` requires a circuit model type of ``ccff`` - - ``memory_bank`` requires a circuit model type of ``sram`` - - ``standalone`` requires a circuit model type of ``sram`` - -Configuration Chain Example -``````````````````````````` -The following XML code describes a scan-chain circuitry to configure the core logic of FPGA, as illustrated in :numref:`fig_ccff_fpga`. -It will use the circuit model defined in :ref:`circuit_model_examples`. - -.. code-block:: xml - - - - - -.. _fig_ccff_fpga: - -.. figure:: figures/ccff_fpga.png - :scale: 60% - :alt: map to buried treasure - - Example of a configuration chain to program core logic of a FPGA - -Memory bank Example -``````````````````` -The following XML code describes a memory-bank circuitry to configure the core logic of FPGA, as illustrated in :numref:`fig_sram`. -It will use the circuit model defined in :ref:`circuit_model_examples`. - -.. code-block:: xml - - - - - -.. _fig_sram: - -.. figure:: figures/sram.png - :scale: 60% - :alt: map to buried treasure - - Example of a memory organization using memory decoders - -Standalone SRAM Example -``````````````````````` - -.. warning:: TO BE CONSTRUCTED - Switch Blocks ~~~~~~~~~~~~~ diff --git a/docs/source/manual/arch_lang/circuit_model_examples.rst b/docs/source/manual/arch_lang/circuit_model_examples.rst index 9b689d2f7..e93d18e6a 100644 --- a/docs/source/manual/arch_lang/circuit_model_examples.rst +++ b/docs/source/manual/arch_lang/circuit_model_examples.rst @@ -240,7 +240,62 @@ Template .. note:: The information of input and output buffer should be clearly specified according to the customized Verilog/SPICE netlist! The existence of input/output buffers will influence the decision in creating testbenches, which may leads to larger errors in power analysis. -.. note:: The support SRAM modules should have a BL and a WL when the memory-bank-style configuration circuit is declared. Note that the WL should be the write/read enable signal, while BL is the data input. +SRAM with BL/WL +``````````````` +.. _fig_sram_blwl: + +.. figure:: ./figures/sram_blwl.png + :scale: 100% + + An example of a SRAM with Bit-Line (BL) and Word-Line (WL) control signals + +The following XML codes describes the SRAM cell shown in :numref:`fig_sram_blwl`. + +.. code-block:: xml + + + + + + + + + + + + +.. note:: OpenFPGA always assume that a ``WL`` port should be the write/read enable signal, while a ``BL`` port is the data input. + +.. note:: When the ``memory_bank`` type of configuration procotol is specified, SRAM modules should have a BL and a WL. + +Configurable Latch +`````````````````` + +.. _fig_config_latch: + +.. figure:: ./figures/config_latch.png + :scale: 100% + + An example of a SRAM-based configurable latch with Bit-Line (BL) and Word-Line (WL) control signals + +The following XML codes describes the configurable latch shown in :numref:`fig_config_latch`. + +.. code-block:: xml + + + + + + + + + + + + +.. note:: OpenFPGA always assume that a ``WL`` port should be the write/read enable signal, while a ``BL`` port is the data input. + +.. note:: When the ``frame_based`` type of configuration procotol is specified, the configurable latch or a SRAM with ``BL`` and ``WL`` should be specified. Logic gates ~~~~~~~~~~~ diff --git a/docs/source/manual/arch_lang/config_protocol.rst b/docs/source/manual/arch_lang/config_protocol.rst new file mode 100644 index 000000000..f37c7f18b --- /dev/null +++ b/docs/source/manual/arch_lang/config_protocol.rst @@ -0,0 +1,126 @@ +.. _config_protocol: + +Configuration Protocol +---------------------- + +Configuration protocol is the circuitry designed to program an FPGA. +As an interface, configuration protocol could be really different in FPGAs, depending on the application context. +OpenFPGA supports versatile configuration protocol, providing different trade-offs between speed and area. + +Template +~~~~~~~~ + +.. code-block:: xml + + + + + +.. option:: type="scan_chain|memory_bank|standalone" + + Specify the type of configuration circuits. + + OpenFPGA supports different types of configuration protocols to program FPGA fabrics: + - ``scan_chain``: configurable memories are connected in a chain. Bitstream is loaded serially to program a FPGA + - ``frame_based``: configurable memories are organized by frames. Each module of a FPGA fabric, e.g., Configurable Logic Block (CLB), Switch Block (SB) and Connection Block (CB), is considered as a frame of configurable memories. Inside each frame, all the memory banks are accessed through an address decoder. Users can write each memory cell with a specific address. Note that the frame-based memory organization is applid hierarchically. Each frame may consists of a number of sub frames, each of which follows the similar organization. + - ``memory_bank``: configurable memories are organized in an array, where each element can be accessed by an unique address to the BL/WL decoders + - ``standalone``: configurable memories are directly accessed through ports of FPGA fabrics. In other words, there are no protocol to control the memories. This allows full customization on the configuration protocol for hardware engineers. + + .. note:: Avoid to use ``standalone`` when designing an FPGA chip. It will causes a huge number of I/Os required, far beyond any package size. It is well applicable to eFPGAs, where designers do need customized protocols between FPGA and processors. + +.. warning:: Currently FPGA-SPICE only supports standalone memory organization. + +.. warning:: Currently RRAM-based FPGA only supports memory-bank organization for Verilog Generator. + +.. option:: circuit_model_name="" + + Specify the name of circuit model to be used as configurable memory. + - ``scan_chain`` requires a circuit model type of ``ccff`` + - ``frame_based`` requires a circuit model type of ``sram`` + - ``memory_bank`` requires a circuit model type of ``sram`` + - ``standalone`` requires a circuit model type of ``sram`` + +Configuration Chain Example +~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The following XML code describes a scan-chain circuitry to configure the core logic of FPGA, as illustrated in :numref:`fig_ccff_fpga`. +It will use the circuit model defined in :numref:`fig_ccff`. + +.. code-block:: xml + + + + + +.. _fig_ccff_fpga: + +.. figure:: figures/ccff_fpga.png + :scale: 60% + :alt: map to buried treasure + + Example of a configuration chain to program core logic of a FPGA + +Frame-based Example +~~~~~~~~~~~~~~~~~~~ +The following XML code describes frame-based memory banks to configure the core logic of FPGA. +It will use the circuit model defined in :numref:`fig_config_latch`. + +.. code-block:: xml + + + + + +Through frame-based configuration protocol, each memory cell can be accessed with an unique address given to decoders. +:numref:`fig_frame_config_protocol_example` illustrates an example about how the configurable memories are organizaed inside a Logic Element (LE) shown in :numref:`fig_k4n4_arch`. +The decoder inside the LE will enable the decoders of the Look-Up Table (LUT) and the routing multiplexer, based on the given address at ``address[2:2]``. +When the decoder of sub block, e.g., the LUT, is enabled, each memory cells can be accessed throught the ``address[1:0]`` and the data to write is provided at ``data_in``. + +.. _fig_frame_config_protocol_example: + +.. figure:: figures/frame_config_protocol_example.png + :scale: 25% + :alt: map to buried treasure + + Example of a frame-based memory organization inside a Logic Element + +:numref:`fig_frame_config_protocol` shows a hierarchical view on how the frame-based decoders across a FPGA fabric. + +.. _fig_frame_config_protocol: + +.. figure:: figures/frame_config_protocol.png + :scale: 60% + :alt: map to buried treasure + + Frame-based memory organization in a hierarchical view + +.. note:: Frame-based decoders does require a memory cell to have + + - two outputs (one regular and another inverted) + - a Bit-Line input to load the data + - a Word-Line input to enable data write + +Memory bank Example +~~~~~~~~~~~~~~~~~~~ +The following XML code describes a memory-bank circuitry to configure the core logic of FPGA, as illustrated in :numref:`fig_sram`. +It will use the circuit model defined in :numref:`fig_sram_blwl`. + +.. code-block:: xml + + + + + +.. _fig_sram: + +.. figure:: figures/sram.png + :scale: 60% + :alt: map to buried treasure + + Example of a memory organization using memory decoders + +.. warning:: THIS IS STILL UNDER CONSTRUCTION + +Standalone SRAM Example +~~~~~~~~~~~~~~~~~~~~~~~ + +.. warning:: TO BE CONSTRUCTED diff --git a/docs/source/manual/arch_lang/figures/config_latch.png b/docs/source/manual/arch_lang/figures/config_latch.png new file mode 100644 index 0000000000000000000000000000000000000000..33506a49f37328fee0bd8e3dd0df9f29ca27c7ef GIT binary patch literal 16495 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

5I**XzdMK+jJEzHxTn-( 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

?ZS4%7X?UaX93x*VbTh` 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

1;e<5?|D-ccD7*HdKCMX__t$YmM3(T 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 LISlIuznEn*4?o+Z*Z%d2T@E}Pyc+hn)#Fac< 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 TO0R!_fS(t#$}*+Ti~|2ZLV;dA literal 0 HcmV?d00001 diff --git a/docs/source/manual/arch_lang/figures/sram_blwl.png b/docs/source/manual/arch_lang/figures/sram_blwl.png new file mode 100644 index 0000000000000000000000000000000000000000..243420fbc0ff508bf6945db9b15d2e09afba4db6 GIT binary patch literal 41280 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 n83&gSL%D_Re|wpirX Date: Fri, 29 May 2020 15:59:57 -0600 Subject: [PATCH 589/645] file moving in regression tests --- .travis/script.sh | 4 ++-- .../{ => full_testbench}/configuration_chain/config/task.conf | 0 .../{ => full_testbench}/configuration_frame/config/task.conf | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename openfpga_flow/tasks/openfpga_shell/{ => full_testbench}/configuration_chain/config/task.conf (100%) rename openfpga_flow/tasks/openfpga_shell/{ => full_testbench}/configuration_frame/config/task.conf (100%) diff --git a/.travis/script.sh b/.travis/script.sh index 0cb3326e6..8a716156b 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -49,10 +49,10 @@ python3 openfpga_flow/scripts/run_fpga_task.py duplicate_grid_pin --debug --show echo -e "Testing OpenFPGA Shell"; echo -e "Testing configuration chain of a K4N4 FPGA"; -python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/configuration_chain --debug --show_thread_logs +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/full_testbench/configuration_chain --debug --show_thread_logs echo -e "Testing fram-based configuration protocol of a K4N4 FPGA"; -python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/configuration_frame --debug --show_thread_logs +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/full_testbench/configuration_frame --debug --show_thread_logs echo -e "Testing user-defined simulation settings: clock frequency and number of cycles"; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/fixed_simulation_settings --debug --show_thread_logs diff --git a/openfpga_flow/tasks/openfpga_shell/configuration_chain/config/task.conf b/openfpga_flow/tasks/openfpga_shell/full_testbench/configuration_chain/config/task.conf similarity index 100% rename from openfpga_flow/tasks/openfpga_shell/configuration_chain/config/task.conf rename to openfpga_flow/tasks/openfpga_shell/full_testbench/configuration_chain/config/task.conf diff --git a/openfpga_flow/tasks/openfpga_shell/configuration_frame/config/task.conf b/openfpga_flow/tasks/openfpga_shell/full_testbench/configuration_frame/config/task.conf similarity index 100% rename from openfpga_flow/tasks/openfpga_shell/configuration_frame/config/task.conf rename to openfpga_flow/tasks/openfpga_shell/full_testbench/configuration_frame/config/task.conf From 05aa166a9e852277174add04e19ec75e987ba90e Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 29 May 2020 16:12:51 -0600 Subject: [PATCH 590/645] add preconfig testbench cases to regression tests for different configuration protocols --- .../configuration_chain/config/task.conf | 34 +++++++++++++++++++ .../configuration_frame/config/task.conf | 34 +++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 openfpga_flow/tasks/openfpga_shell/preconfig_testbench/configuration_chain/config/task.conf create mode 100644 openfpga_flow/tasks/openfpga_shell/preconfig_testbench/configuration_frame/config/task.conf diff --git a/openfpga_flow/tasks/openfpga_shell/preconfig_testbench/configuration_chain/config/task.conf b/openfpga_flow/tasks/openfpga_shell/preconfig_testbench/configuration_chain/config/task.conf new file mode 100644 index 000000000..3fd340a7b --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/preconfig_testbench/configuration_chain/config/task.conf @@ -0,0 +1,34 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k4_N4_40nm_cc_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k4_N4_tileable_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.blif + +[SYNTHESIS_PARAM] +bench0_top = and2 +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.v +bench0_chan_width = 300 + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +end_flow_with_test= +vpr_fpga_verilog_formal_verification_top_netlist= diff --git a/openfpga_flow/tasks/openfpga_shell/preconfig_testbench/configuration_frame/config/task.conf b/openfpga_flow/tasks/openfpga_shell/preconfig_testbench/configuration_frame/config/task.conf new file mode 100644 index 000000000..4a41fc671 --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/preconfig_testbench/configuration_frame/config/task.conf @@ -0,0 +1,34 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k4_N4_40nm_frame_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k4_N4_tileable_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.blif + +[SYNTHESIS_PARAM] +bench0_top = and2 +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.v +bench0_chan_width = 300 + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +end_flow_with_test= +vpr_fpga_verilog_formal_verification_top_netlist= From 22648cdb9cabaa4e37b5c41d548aef87c1707f8f Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 29 May 2020 16:14:22 -0600 Subject: [PATCH 591/645] deploy the preconfig testbench cases to CI --- .travis/script.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis/script.sh b/.travis/script.sh index 8a716156b..37ad55006 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -50,9 +50,11 @@ echo -e "Testing OpenFPGA Shell"; echo -e "Testing configuration chain of a K4N4 FPGA"; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/full_testbench/configuration_chain --debug --show_thread_logs +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/preconfig_testbench/configuration_chain --debug --show_thread_logs echo -e "Testing fram-based configuration protocol of a K4N4 FPGA"; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/full_testbench/configuration_frame --debug --show_thread_logs +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/preconfig_testbench/configuration_frame --debug --show_thread_logs echo -e "Testing user-defined simulation settings: clock frequency and number of cycles"; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/fixed_simulation_settings --debug --show_thread_logs From 8b3e79766c4c0a78212fbc51ffb948daffb4ffac Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 29 May 2020 18:07:21 -0600 Subject: [PATCH 592/645] add fast configuration option to fpga_verilog to speed up full testbench simulation --- openfpga/src/base/openfpga_verilog.cpp | 2 + .../src/base/openfpga_verilog_command.cpp | 3 + openfpga/src/fpga_verilog/verilog_api.cpp | 1 + .../verilog_testbench_options.cpp | 8 +++ .../fpga_verilog/verilog_testbench_options.h | 3 + .../fpga_verilog/verilog_top_testbench.cpp | 63 +++++++++++++++-- .../src/fpga_verilog/verilog_top_testbench.h | 1 + ...onfiguration_frame_example_script.openfpga | 2 +- .../full_testbench_example_script.openfpga | 68 +++++++++++++++++++ .../configuration_frame/config/task.conf | 2 +- 10 files changed, 145 insertions(+), 8 deletions(-) create mode 100644 openfpga_flow/OpenFPGAShellScripts/full_testbench_example_script.openfpga diff --git a/openfpga/src/base/openfpga_verilog.cpp b/openfpga/src/base/openfpga_verilog.cpp index c67c270eb..926e0f8d3 100644 --- a/openfpga/src/base/openfpga_verilog.cpp +++ b/openfpga/src/base/openfpga_verilog.cpp @@ -67,6 +67,7 @@ int write_verilog_testbench(OpenfpgaContext& openfpga_ctx, CommandOptionId opt_output_dir = cmd.option("file"); CommandOptionId opt_reference_benchmark = cmd.option("reference_benchmark_file_path"); CommandOptionId opt_print_top_testbench = cmd.option("print_top_testbench"); + CommandOptionId opt_fast_configuration = cmd.option("fast_configuration"); CommandOptionId opt_print_formal_verification_top_netlist = cmd.option("print_formal_verification_top_netlist"); CommandOptionId opt_print_preconfig_top_testbench = cmd.option("print_preconfig_top_testbench"); CommandOptionId opt_print_simulation_ini = cmd.option("print_simulation_ini"); @@ -81,6 +82,7 @@ int write_verilog_testbench(OpenfpgaContext& openfpga_ctx, options.set_reference_benchmark_file_path(cmd_context.option_value(cmd, opt_reference_benchmark)); options.set_print_formal_verification_top_netlist(cmd_context.option_enable(cmd, opt_print_formal_verification_top_netlist)); options.set_print_preconfig_top_testbench(cmd_context.option_enable(cmd, opt_print_preconfig_top_testbench)); + options.set_fast_configuration(cmd_context.option_enable(cmd, opt_fast_configuration)); options.set_print_top_testbench(cmd_context.option_enable(cmd, opt_print_top_testbench)); options.set_print_simulation_ini(cmd_context.option_value(cmd, opt_print_simulation_ini)); options.set_explicit_port_mapping(cmd_context.option_enable(cmd, opt_explicit_port_mapping)); diff --git a/openfpga/src/base/openfpga_verilog_command.cpp b/openfpga/src/base/openfpga_verilog_command.cpp index 99ee7a319..89c4e27e3 100644 --- a/openfpga/src/base/openfpga_verilog_command.cpp +++ b/openfpga/src/base/openfpga_verilog_command.cpp @@ -79,6 +79,9 @@ ShellCommandId add_openfpga_write_verilog_testbench_command(openfpga::Shell Date: Fri, 29 May 2020 18:08:16 -0600 Subject: [PATCH 593/645] add fast configuration testcase --- .../fast_configuration_frame/config/task.conf | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 openfpga_flow/tasks/openfpga_shell/full_testbench/fast_configuration_frame/config/task.conf diff --git a/openfpga_flow/tasks/openfpga_shell/full_testbench/fast_configuration_frame/config/task.conf b/openfpga_flow/tasks/openfpga_shell/full_testbench/fast_configuration_frame/config/task.conf new file mode 100644 index 000000000..7a448e3e8 --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/full_testbench/fast_configuration_frame/config/task.conf @@ -0,0 +1,34 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/configuration_frame_example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k4_N4_40nm_frame_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k4_N4_tileable_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.blif + +[SYNTHESIS_PARAM] +bench0_top = and2 +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.v +bench0_chan_width = 300 + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +end_flow_with_test= +#vpr_fpga_verilog_formal_verification_top_netlist= From 9e176b8d383da30f1880267b30eed967963928fe Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 29 May 2020 18:22:36 -0600 Subject: [PATCH 594/645] add fast configuration stats to log --- openfpga/src/fpga_verilog/verilog_top_testbench.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp index 5c6795132..4a1072c70 100644 --- a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp +++ b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp @@ -505,12 +505,17 @@ size_t calculate_num_config_clock_cycles(const e_config_protocol_type& sram_orgz case CONFIG_MEM_FRAME_BASED: { /* For fast configuration, we will skip all the zero data points */ if (true == fast_configuration) { + size_t full_num_config_clock_cycles = num_config_clock_cycles; num_config_clock_cycles = 1; for (const FabricBitId& bit_id : fabric_bitstream.bits()) { if (true == fabric_bitstream.bit_din(bit_id)) { num_config_clock_cycles++; } } + VTR_LOG("Fast configuration reduces number of configuration clock cycles from %lu to %lu (compression_rate = %f%)\n", + full_num_config_clock_cycles, + num_config_clock_cycles, + 100. * ((float)num_config_clock_cycles / (float)full_num_config_clock_cycles - 1.)); } break; } @@ -520,6 +525,9 @@ size_t calculate_num_config_clock_cycles(const e_config_protocol_type& sram_orgz exit(1); } + VTR_LOG("Will use %ld configuration clock cycles to top testbench\n", + num_config_clock_cycles); + return num_config_clock_cycles; } From 9a6a5e331024b2fa5f7759b198aa0eba4b167f45 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 29 May 2020 18:23:12 -0600 Subject: [PATCH 595/645] deploy fast configuration testcase to CI --- .travis/script.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis/script.sh b/.travis/script.sh index 37ad55006..9d222c17a 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -54,6 +54,7 @@ python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/preconfig_testbenc echo -e "Testing fram-based configuration protocol of a K4N4 FPGA"; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/full_testbench/configuration_frame --debug --show_thread_logs +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/full_testbench/fast_configuration_frame --debug --show_thread_logs python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/preconfig_testbench/configuration_frame --debug --show_thread_logs echo -e "Testing user-defined simulation settings: clock frequency and number of cycles"; From d2d443a988d65ec5a0677a990f1581e5594ea024 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 29 May 2020 20:55:45 -0600 Subject: [PATCH 596/645] start developing memory bank and standalone configuration protocol --- openfpga/src/base/openfpga_reserved_words.h | 2 + openfpga/src/fabric/build_memory_modules.cpp | 276 +++++++------------ 2 files changed, 100 insertions(+), 178 deletions(-) diff --git a/openfpga/src/base/openfpga_reserved_words.h b/openfpga/src/base/openfpga_reserved_words.h index 132858687..40a2af96b 100644 --- a/openfpga/src/base/openfpga_reserved_words.h +++ b/openfpga/src/base/openfpga_reserved_words.h @@ -23,6 +23,8 @@ constexpr char* GRID_MEM_INSTANCE_PREFIX = "mem_"; constexpr char* SWITCH_BLOCK_MEM_INSTANCE_PREFIX = "mem_"; constexpr char* CONNECTION_BLOCK_MEM_INSTANCE_PREFIX = "mem_"; constexpr char* MEMORY_MODULE_POSTFIX = "_mem"; +constexpr char* MEMORY_BL_PORT_NAME = "bl"; +constexpr char* MEMORY_WL_PORT_NAME = "wl"; /* Multiplexer naming constant strings */ constexpr char* MUX_BASIS_MODULE_POSTFIX = "_basis"; diff --git a/openfpga/src/fabric/build_memory_modules.cpp b/openfpga/src/fabric/build_memory_modules.cpp index bc04dd07d..f634408b1 100644 --- a/openfpga/src/fabric/build_memory_modules.cpp +++ b/openfpga/src/fabric/build_memory_modules.cpp @@ -40,59 +40,31 @@ namespace openfpga { static void add_module_input_nets_to_mem_modules(ModuleManager& module_manager, const ModuleId& mem_module, + const ModulePortId& module_port, const CircuitLibrary& circuit_lib, - const std::vector& circuit_ports, + const CircuitPortId& circuit_port, const ModuleId& child_module, const size_t& child_index, const size_t& child_instance) { /* Wire inputs of parent module to inputs of child modules */ - for (const auto& port : circuit_ports) { - ModulePortId src_port_id = module_manager.find_module_port(mem_module, circuit_lib.port_prefix(port)); - ModulePortId sink_port_id = module_manager.find_module_port(child_module, circuit_lib.port_prefix(port)); - for (size_t pin_id = 0; pin_id < module_manager.module_port(mem_module, sink_port_id).pins().size(); ++pin_id) { - ModuleNetId net = module_manager.create_module_net(mem_module); - /* Source pin is shifted by the number of memories */ - size_t src_pin_id = child_index * circuit_lib.port_size(port) + module_manager.module_port(mem_module, src_port_id).pins()[pin_id]; - /* Source node of the input net is the input of memory module */ - module_manager.add_module_net_source(mem_module, net, mem_module, 0, src_port_id, src_pin_id); - /* Sink node of the input net is the input of sram module */ - size_t sink_pin_id = module_manager.module_port(child_module, sink_port_id).pins()[pin_id]; - module_manager.add_module_net_sink(mem_module, net, child_module, child_instance, sink_port_id, sink_pin_id); - } - } -} + ModulePortId src_port_id = module_port; + ModulePortId sink_port_id = module_manager.find_module_port(child_module, circuit_lib.port_prefix(circuit_port)); -/********************************************************************* - * Add module nets to connect an output port of a memory module to - * an output port of its child module - * Restriction: this function is really designed for memory modules - * 1. It assumes that output port name of child module is the same as memory module - * 2. It assumes exact pin-to-pin mapping: - * j-th pin of output port of the i-th child module is wired to the j + i*W -th - * pin of output port of the memory module, where W is the size of port - ********************************************************************/ -static -void add_module_output_nets_to_mem_modules(ModuleManager& module_manager, - const ModuleId& mem_module, - const CircuitLibrary& circuit_lib, - const std::vector& circuit_ports, - const ModuleId& child_module, - const size_t& child_index, - const size_t& child_instance) { - /* Wire inputs of parent module to inputs of child modules */ - for (const auto& port : circuit_ports) { - ModulePortId src_port_id = module_manager.find_module_port(child_module, circuit_lib.port_prefix(port)); - ModulePortId sink_port_id = module_manager.find_module_port(mem_module, circuit_lib.port_prefix(port)); - for (size_t pin_id = 0; pin_id < module_manager.module_port(child_module, src_port_id).pins().size(); ++pin_id) { - ModuleNetId net = module_manager.create_module_net(mem_module); - /* Source pin is shifted by the number of memories */ - size_t src_pin_id = module_manager.module_port(child_module, src_port_id).pins()[pin_id]; - /* Source node of the input net is the input of memory module */ - module_manager.add_module_net_source(mem_module, net, child_module, child_instance, src_port_id, src_pin_id); - /* Sink node of the input net is the input of sram module */ - size_t sink_pin_id = child_index * circuit_lib.port_size(port) + module_manager.module_port(mem_module, sink_port_id).pins()[pin_id]; - module_manager.add_module_net_sink(mem_module, net, mem_module, 0, sink_port_id, sink_pin_id); - } + /* Source pin is shifted by the number of memories */ + size_t src_pin_id = module_manager.module_port(mem_module, src_port_id).pins()[child_index]; + + ModuleNetId net = module_manager.module_instance_port_net(mem_module, + mem_module, 0, + src_port_id, src_pin_id); + if (ModuleNetId::INVALID() == net) { + net = module_manager.create_module_net(mem_module); + module_manager.add_module_net_source(mem_module, net, mem_module, 0, src_port_id, src_pin_id); + } + + for (size_t pin_id = 0; pin_id < module_manager.module_port(mem_module, sink_port_id).pins().size(); ++pin_id) { + /* Sink node of the input net is the input of sram module */ + size_t sink_pin_id = module_manager.module_port(child_module, sink_port_id).pins()[pin_id]; + module_manager.add_module_net_sink(mem_module, net, child_module, child_instance, sink_port_id, sink_pin_id); } } @@ -139,6 +111,38 @@ std::vector add_module_output_nets_to_chain_mem_modules(ModuleManag return module_nets; } +/********************************************************************* + * Add module nets to connect an output port of a memory module to + * an output port of its child module + * Restriction: this function is really designed for memory modules + * 1. It assumes that output port name of child module is the same as memory module + * 2. It assumes exact pin-to-pin mapping: + * j-th pin of output port of the i-th child module is wired to the j + i*W -th + * pin of output port of the memory module, where W is the size of port + ********************************************************************/ +static +void add_module_output_nets_to_mem_modules(ModuleManager& module_manager, + const ModuleId& mem_module, + const CircuitLibrary& circuit_lib, + const std::vector& sram_output_ports, + const ModuleId& child_module, + const size_t& child_index, + const size_t& child_instance) { + + for (size_t iport = 0; iport < sram_output_ports.size(); ++iport) { + std::string port_name; + if (0 == iport) { + port_name = generate_configurable_memory_data_out_name(); + } else { + VTR_ASSERT( 1 == iport); + port_name = generate_configurable_memory_inverted_data_out_name(); + } + add_module_output_nets_to_chain_mem_modules(module_manager, mem_module, + port_name, circuit_lib, sram_output_ports[iport], + child_module, child_index, child_instance); + } +} + /******************************************************************** * Connect all the memory modules under the parent module in a chain * @@ -273,9 +277,14 @@ void add_module_nets_to_cmos_memory_chain_module(ModuleManager& module_manager, } /********************************************************************* - * Flat memory modules + * Flatten memory organization * - * in[0] in[1] in[N] + * Bit lines(BL/BLB) Word lines (WL/WLB) + * | | + * v v + * +------------------------------------+ + * | Memory Module Configuration port | + * +------------------------------------+ * | | | * v v v * +-------+ +-------+ +-------+ @@ -289,33 +298,49 @@ void add_module_nets_to_cmos_memory_chain_module(ModuleManager& module_manager, * ********************************************************************/ static -void build_memory_standalone_module(ModuleManager& module_manager, - const CircuitLibrary& circuit_lib, - const std::string& module_name, - const CircuitModelId& sram_model, - const size_t& num_mems) { +void build_memory_flatten_module(ModuleManager& module_manager, + const CircuitLibrary& circuit_lib, + const std::string& module_name, + const CircuitModelId& sram_model, + const size_t& num_mems) { /* Get the global ports required by the SRAM */ std::vector global_port_types; global_port_types.push_back(CIRCUIT_MODEL_PORT_CLOCK); global_port_types.push_back(CIRCUIT_MODEL_PORT_INPUT); std::vector sram_global_ports = circuit_lib.model_global_ports_by_type(sram_model, global_port_types, true, false); - /* Get the input ports from the SRAM */ - std::vector sram_input_ports = circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_INPUT, true); + /* Get the BL/WL ports from the SRAM */ + std::vector sram_bl_ports = circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_BL, true); + std::vector sram_wl_ports = circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_WL, true); /* Get the output ports from the SRAM */ std::vector sram_output_ports = circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_OUTPUT, true); + /* Ensure that we have only 1 BL, 1 WL and 2 output ports*/ + VTR_ASSERT(1 == sram_bl_ports.size()); + VTR_ASSERT(1 == sram_wl_ports.size()); + VTR_ASSERT(2 == sram_output_ports.size()); + /* Create a module and add to the module manager */ ModuleId mem_module = module_manager.add_module(module_name); VTR_ASSERT(true == module_manager.valid_module_id(mem_module)); - /* Add each input port */ - for (const auto& port : sram_input_ports) { - BasicPort input_port(circuit_lib.port_prefix(port), num_mems); - module_manager.add_port(mem_module, input_port, ModuleManager::MODULE_INPUT_PORT); - } + /* Add module ports */ + /* Input: BL port */ + BasicPort bl_port(std::string(MEMORY_BL_PORT_NAME), num_mems); + ModulePortId mem_bl_port = module_manager.add_port(mem_module, bl_port, ModuleManager::MODULE_INPUT_PORT); + + BasicPort wl_port(std::string(MEMORY_BL_PORT_NAME), num_mems); + ModulePortId mem_wl_port = module_manager.add_port(mem_module, wl_port, ModuleManager::MODULE_INPUT_PORT); + /* Add each output port: port width should match the number of memories */ - for (const auto& port : sram_output_ports) { - BasicPort output_port(circuit_lib.port_prefix(port), num_mems); + for (size_t iport = 0; iport < sram_output_ports.size(); ++iport) { + std::string port_name; + if (0 == iport) { + port_name = generate_configurable_memory_data_out_name(); + } else { + VTR_ASSERT( 1 == iport); + port_name = generate_configurable_memory_inverted_data_out_name(); + } + BasicPort output_port(port_name, num_mems); module_manager.add_port(mem_module, output_port, ModuleManager::MODULE_OUTPUT_PORT); } @@ -330,8 +355,13 @@ void build_memory_standalone_module(ModuleManager& module_manager, /* Build module nets */ /* Wire inputs of parent module to inputs of child modules */ - add_module_input_nets_to_mem_modules(module_manager, mem_module, circuit_lib, sram_input_ports, sram_mem_module, i, sram_mem_instance); - /* Wire inputs of parent module to outputs of child modules */ + for (const CircuitPortId& port : sram_bl_ports) { + add_module_input_nets_to_mem_modules(module_manager, mem_module, mem_bl_port, circuit_lib, port, sram_mem_module, i, sram_mem_instance); + } + for (const CircuitPortId& port : sram_wl_ports) { + add_module_input_nets_to_mem_modules(module_manager, mem_module, mem_wl_port, circuit_lib, port, sram_mem_module, i, sram_mem_instance); + } + /* Wire outputs of child module to outputs of parent module */ add_module_output_nets_to_mem_modules(module_manager, mem_module, circuit_lib, sram_output_ports, sram_mem_module, i, sram_mem_instance); } @@ -448,113 +478,6 @@ void build_memory_chain_module(ModuleManager& module_manager, add_module_global_ports_from_child_modules(module_manager, mem_module); } -/********************************************************************* - * Memory bank organization - * - * Bit lines(BL/BLB) Word lines (WL/WLB) - * | | - * v v - * +------------------------------------+ - * | Memory Module Configuration port | - * +------------------------------------+ - * | | | - * v v v - * +-------+ +-------+ +-------+ - * | SRAM | | SRAM | ... | SRAM | - * | [0] | | [1] | | [N-1] | - * +-------+ +-------+ +-------+ - * | | ... | - * v v v - * +------------------------------------+ - * | Multiplexer Configuration port | - * - ********************************************************************/ -static -void build_memory_bank_module(ModuleManager& module_manager, - const CircuitLibrary& circuit_lib, - const std::string& module_name, - const CircuitModelId& sram_model, - const size_t& num_mems) { - /* Get the global ports required by the SRAM */ - std::vector global_port_types; - global_port_types.push_back(CIRCUIT_MODEL_PORT_CLOCK); - global_port_types.push_back(CIRCUIT_MODEL_PORT_INPUT); - std::vector sram_global_ports = circuit_lib.model_global_ports_by_type(sram_model, global_port_types, true, false); - /* Get the input ports from the SRAM */ - std::vector sram_input_ports = circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_INPUT, true); - /* A SRAM cell with BL/WL should not have any input */ - VTR_ASSERT( 0 == sram_input_ports.size() ); - /* Get the output ports from the SRAM */ - std::vector sram_output_ports = circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_OUTPUT, true); - /* Get the BL/WL ports from the SRAM */ - std::vector sram_bl_ports = circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_BL, true); - std::vector sram_blb_ports = circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_BLB, true); - std::vector sram_wl_ports = circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_WL, true); - std::vector sram_wlb_ports = circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_WLB, true); - - /* Create a module and add to the module manager */ - ModuleId mem_module = module_manager.add_module(module_name); - VTR_ASSERT(true == module_manager.valid_module_id(mem_module)); - - /* Add module ports: the ports come from the SRAM modules */ - /* Add each input port */ - for (const auto& port : sram_input_ports) { - BasicPort input_port(circuit_lib.port_prefix(port), num_mems * circuit_lib.port_size(port)); - module_manager.add_port(mem_module, input_port, ModuleManager::MODULE_INPUT_PORT); - } - /* Add each output port: port width should match the number of memories */ - for (const auto& port : sram_output_ports) { - BasicPort output_port(circuit_lib.port_prefix(port), num_mems * circuit_lib.port_size(port)); - module_manager.add_port(mem_module, output_port, ModuleManager::MODULE_OUTPUT_PORT); - } - /* Add each output port: port width should match the number of memories */ - for (const auto& port : sram_bl_ports) { - BasicPort bl_port(circuit_lib.port_prefix(port), num_mems * circuit_lib.port_size(port)); - module_manager.add_port(mem_module, bl_port, ModuleManager::MODULE_INPUT_PORT); - } - for (const auto& port : sram_blb_ports) { - BasicPort blb_port(circuit_lib.port_prefix(port), num_mems * circuit_lib.port_size(port)); - module_manager.add_port(mem_module, blb_port, ModuleManager::MODULE_INPUT_PORT); - } - for (const auto& port : sram_wl_ports) { - BasicPort wl_port(circuit_lib.port_prefix(port), num_mems * circuit_lib.port_size(port)); - module_manager.add_port(mem_module, wl_port, ModuleManager::MODULE_INPUT_PORT); - } - for (const auto& port : sram_wlb_ports) { - BasicPort wlb_port(circuit_lib.port_prefix(port), num_mems * circuit_lib.port_size(port)); - module_manager.add_port(mem_module, wlb_port, ModuleManager::MODULE_INPUT_PORT); - } - - /* Find the sram module in the module manager */ - ModuleId sram_mem_module = module_manager.find_module(circuit_lib.model_name(sram_model)); - - /* Instanciate each submodule */ - for (size_t i = 0; i < num_mems; ++i) { - /* Memory seed module instanciation */ - size_t sram_instance = module_manager.num_instance(mem_module, sram_mem_module); - module_manager.add_child_module(mem_module, sram_mem_module); - - /* Build module nets */ - /* Wire inputs of parent module to inputs of child modules */ - add_module_input_nets_to_mem_modules(module_manager, mem_module, circuit_lib, sram_input_ports, sram_mem_module, i, sram_instance); - /* Wire inputs of parent module to outputs of child modules */ - add_module_output_nets_to_mem_modules(module_manager, mem_module, circuit_lib, sram_output_ports, sram_mem_module, i, sram_instance); - /* Wire BL/WLs of parent module to BL/WLs of child modules */ - add_module_input_nets_to_mem_modules(module_manager, mem_module, circuit_lib, sram_bl_ports, sram_mem_module, i, sram_instance); - add_module_input_nets_to_mem_modules(module_manager, mem_module, circuit_lib, sram_blb_ports, sram_mem_module, i, sram_instance); - add_module_input_nets_to_mem_modules(module_manager, mem_module, circuit_lib, sram_wl_ports, sram_mem_module, i, sram_instance); - add_module_input_nets_to_mem_modules(module_manager, mem_module, circuit_lib, sram_wlb_ports, sram_mem_module, i, sram_instance); - } - - /* TODO: if a local memory decoder is required, instanciate it here */ - - /* Add global ports to the pb_module: - * This is a much easier job after adding sub modules (instances), - * we just need to find all the global ports from the child modules and build a list of it - */ - add_module_global_ports_from_child_modules(module_manager, mem_module); -} - /********************************************************************* * Frame-based Memory organization * @@ -773,17 +696,14 @@ void build_memory_module(ModuleManager& module_manager, const size_t& num_mems) { switch (sram_orgz_type) { case CONFIG_MEM_STANDALONE: - build_memory_standalone_module(module_manager, circuit_lib, - module_name, sram_model, num_mems); + case CONFIG_MEM_MEMORY_BANK: + build_memory_flatten_module(module_manager, circuit_lib, + module_name, sram_model, num_mems); break; case CONFIG_MEM_SCAN_CHAIN: build_memory_chain_module(module_manager, circuit_lib, module_name, sram_model, num_mems); break; - case CONFIG_MEM_MEMORY_BANK: - build_memory_bank_module(module_manager, circuit_lib, - module_name, sram_model, num_mems); - break; case CONFIG_MEM_FRAME_BASED: build_frame_memory_module(module_manager, arch_decoder_lib, circuit_lib, module_name, sram_model, num_mems); From fbe05963e00e7203f2e236643c42198952e63e00 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 29 May 2020 22:41:56 -0600 Subject: [PATCH 597/645] add configuration bus builder for flatten memory organization (applicable to memory bank and standalone configuration protocol) --- openfpga/src/base/openfpga_naming.cpp | 36 ++---- openfpga/src/utils/memory_utils.cpp | 7 +- openfpga/src/utils/module_manager_utils.cpp | 131 +++++++++++++++++--- openfpga/src/utils/module_manager_utils.h | 12 ++ 4 files changed, 139 insertions(+), 47 deletions(-) diff --git a/openfpga/src/base/openfpga_naming.cpp b/openfpga/src/base/openfpga_naming.cpp index dd5e7df2c..5cf00bf7f 100644 --- a/openfpga/src/base/openfpga_naming.cpp +++ b/openfpga/src/base/openfpga_naming.cpp @@ -744,19 +744,6 @@ std::string generate_sram_port_name(const e_config_protocol_type& sram_orgz_type std::string port_name; switch (sram_orgz_type) { - case CONFIG_MEM_STANDALONE: { - /* Two types of ports are available: - * (1) Regular output of a SRAM, enabled by port type of INPUT - * (2) Inverted output of a SRAM, enabled by port type of OUTPUT - */ - if (CIRCUIT_MODEL_PORT_INPUT == port_type) { - port_name = std::string("mem_out"); - } else { - VTR_ASSERT( CIRCUIT_MODEL_PORT_OUTPUT == port_type ); - port_name = std::string("mem_outb"); - } - break; - } case CONFIG_MEM_SCAN_CHAIN: /* Two types of ports are available: * (1) Head of a chain of Configuration-chain Flip-Flops (CCFFs), enabled by port type of INPUT @@ -772,30 +759,25 @@ std::string generate_sram_port_name(const e_config_protocol_type& sram_orgz_type port_name = std::string("ccff_tail"); } break; + case CONFIG_MEM_STANDALONE: case CONFIG_MEM_MEMORY_BANK: - /* Four types of ports are available: + /* Two types of ports are available: * (1) Bit Lines (BLs) of a SRAM cell, enabled by port type of BL * (2) Word Lines (WLs) of a SRAM cell, enabled by port type of WL - * (3) Inverted Bit Lines (BLBs) of a SRAM cell, enabled by port type of BLB - * (4) Inverted Word Lines (WLBs) of a SRAM cell, enabled by port type of WLB * - * BL BLB WL WLB BL BLB WL WLB BL BLB WL WLB - * [0] [0] [0] [0] [1] [1] [1] [1] [i] [i] [i] [i] - * ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ - * | | | | | | | | | | | | + * BL WL BL WL BL WL + * [0] [0] [1] [1] [i] [i] + * ^ ^ ^ ^ ^ ^ + * | | | | | | * +----------+ +----------+ +----------+ * | SRAM | | SRAM | ... | SRAM | * +----------+ +----------+ +----------+ */ if (CIRCUIT_MODEL_PORT_BL == port_type) { - port_name = std::string("bl"); - } else if (CIRCUIT_MODEL_PORT_WL == port_type) { - port_name = std::string("wl"); - } else if (CIRCUIT_MODEL_PORT_BLB == port_type) { - port_name = std::string("blb"); + port_name = std::string(MEMORY_BL_PORT_NAME); } else { - VTR_ASSERT( CIRCUIT_MODEL_PORT_WLB == port_type ); - port_name = std::string("wlb"); + VTR_ASSERT( CIRCUIT_MODEL_PORT_WL == port_type ); + port_name = std::string(MEMORY_WL_PORT_NAME); } break; case CONFIG_MEM_FRAME_BASED: diff --git a/openfpga/src/utils/memory_utils.cpp b/openfpga/src/utils/memory_utils.cpp index 05b6e8736..bb7cefee7 100644 --- a/openfpga/src/utils/memory_utils.cpp +++ b/openfpga/src/utils/memory_utils.cpp @@ -314,20 +314,15 @@ std::vector generate_sram_port_names(const CircuitLibrary& circuit_ std::vector model_port_types; switch (sram_orgz_type) { - case CONFIG_MEM_STANDALONE: - model_port_types.push_back(CIRCUIT_MODEL_PORT_INPUT); - model_port_types.push_back(CIRCUIT_MODEL_PORT_OUTPUT); - break; case CONFIG_MEM_SCAN_CHAIN: model_port_types.push_back(CIRCUIT_MODEL_PORT_INPUT); model_port_types.push_back(CIRCUIT_MODEL_PORT_OUTPUT); break; + case CONFIG_MEM_STANDALONE: case CONFIG_MEM_MEMORY_BANK: { std::vector ports_to_search; ports_to_search.push_back(CIRCUIT_MODEL_PORT_BL); ports_to_search.push_back(CIRCUIT_MODEL_PORT_WL); - ports_to_search.push_back(CIRCUIT_MODEL_PORT_BLB); - ports_to_search.push_back(CIRCUIT_MODEL_PORT_WLB); /* Try to find a BL/WL/BLB/WLB port and update the port types/module port types to be added */ for (const auto& port_to_search : ports_to_search) { std::vector found_port = circuit_lib.model_ports_by_type(sram_model, port_to_search); diff --git a/openfpga/src/utils/module_manager_utils.cpp b/openfpga/src/utils/module_manager_utils.cpp index eebe9008a..cf9808466 100644 --- a/openfpga/src/utils/module_manager_utils.cpp +++ b/openfpga/src/utils/module_manager_utils.cpp @@ -664,6 +664,85 @@ void add_module_nets_between_logic_and_memory_sram_bus(ModuleManager& module_man } } +/******************************************************************** + * Connect all the memory modules under the parent module in a flatten way + * + * BL + * | + * +---------------+--------- ... --------+ + * | | | + * v v v + * +--------+ +--------+ +--------+ + * | Memory | | Memory | ... | Memory | + * | Module | | Module | | Module | + * | [0] | | [1] | | [N-1] | + * +--------+ +--------+ +--------+ + * ^ ^ ^ + * | | | + * +------------+----------------------+ + * | + * WL + * + * Note: + * - This function will do the connection for only one type of the port, + * either BL or WL. So, you should call this function twice to complete + * the configuration bus connection!!! + * + *********************************************************************/ +void add_module_nets_cmos_flatten_memory_config_bus(ModuleManager& module_manager, + const ModuleId& parent_module, + const e_config_protocol_type& sram_orgz_type, + const e_circuit_model_port_type& config_port_type) { + /* A counter for the current pin id for the source port of parent module */ + size_t cur_src_pin_id = 0; + + ModuleId net_src_module_id; + size_t net_src_instance_id; + ModulePortId net_src_port_id; + + /* Find the port name of parent module */ + std::string src_port_name = generate_sram_port_name(sram_orgz_type, config_port_type); + net_src_module_id = parent_module; + net_src_instance_id = 0; + net_src_port_id = module_manager.find_module_port(net_src_module_id, src_port_name); + + /* Get the pin id for source port */ + BasicPort net_src_port = module_manager.module_port(net_src_module_id, net_src_port_id); + + for (size_t mem_index = 0; mem_index < module_manager.configurable_children(parent_module).size(); ++mem_index) { + ModuleId net_sink_module_id; + size_t net_sink_instance_id; + ModulePortId net_sink_port_id; + + /* Find the port name of next memory module */ + std::string sink_port_name = generate_sram_port_name(sram_orgz_type, config_port_type); + net_sink_module_id = module_manager.configurable_children(parent_module)[mem_index]; + net_sink_instance_id = module_manager.configurable_child_instances(parent_module)[mem_index]; + net_sink_port_id = module_manager.find_module_port(net_sink_module_id, sink_port_name); + + /* Get the pin id for sink port */ + BasicPort net_sink_port = module_manager.module_port(net_sink_module_id, net_sink_port_id); + + /* Create a net for each pin */ + for (size_t pin_id = 0; pin_id < net_sink_port.pins().size(); ++pin_id) { + /* Create a net and add source and sink to it */ + ModuleNetId net = create_module_source_pin_net(module_manager, parent_module, + net_src_module_id, net_src_instance_id, + net_src_port_id, net_src_port.pins()[cur_src_pin_id]); + VTR_ASSERT(ModuleNetId::INVALID() != net); + + /* Add net sink */ + module_manager.add_module_net_sink(parent_module, net, net_sink_module_id, net_sink_instance_id, net_sink_port_id, net_sink_port.pins()[pin_id]); + + /* Move to the next src pin */ + cur_src_pin_id++; + } + } + + /* We should used all the pins of the source port!!! */ + VTR_ASSERT(net_src_port.get_width() == cur_src_pin_id); +} + /******************************************************************** * Connect all the memory modules under the parent module in a chain * @@ -1088,18 +1167,19 @@ void add_module_nets_cmos_memory_config_bus(ModuleManager& module_manager, const ModuleId& parent_module, const e_config_protocol_type& sram_orgz_type) { switch (sram_orgz_type) { - case CONFIG_MEM_STANDALONE: - /* Nothing to do */ - break; case CONFIG_MEM_SCAN_CHAIN: { - add_module_nets_cmos_memory_chain_config_bus(module_manager, parent_module, sram_orgz_type); + add_module_nets_cmos_memory_chain_config_bus(module_manager, parent_module, + sram_orgz_type); break; } + case CONFIG_MEM_STANDALONE: case CONFIG_MEM_MEMORY_BANK: - /* TODO: */ + add_module_nets_cmos_flatten_memory_config_bus(module_manager, parent_module, + sram_orgz_type, CIRCUIT_MODEL_PORT_BL); + add_module_nets_cmos_flatten_memory_config_bus(module_manager, parent_module, + sram_orgz_type, CIRCUIT_MODEL_PORT_WL); break; case CONFIG_MEM_FRAME_BASED: - /* TODO: */ add_module_nets_cmos_memory_frame_config_bus(module_manager, decoder_lib, parent_module); break; default: @@ -1557,6 +1637,32 @@ size_t find_module_num_config_bits_from_child_modules(ModuleManager& module_mana return num_config_bits; } +/******************************************************************** + * Try to create a net for the source pin + * This function will try + * - Find if there is already a net created whose source is the pin + * If so, it will return the net id + * - If not, it will create a net and configure its source + *******************************************************************/ +ModuleNetId create_module_source_pin_net(ModuleManager& module_manager, + const ModuleId& cur_module_id, + const ModuleId& src_module_id, + const size_t& src_instance_id, + const ModulePortId& src_module_port_id, + const size_t& src_pin_id) { + ModuleNetId net = module_manager.module_instance_port_net(cur_module_id, + src_module_id, src_instance_id, + src_module_port_id, src_pin_id); + if (ModuleNetId::INVALID() == net) { + net = module_manager.create_module_net(cur_module_id); + module_manager.add_module_net_source(cur_module_id, net, + src_module_id, src_instance_id, + src_module_port_id, src_pin_id); + } + + return net; +} + /******************************************************************** * Add a bus of nets to a module (cur_module_id) * Note: @@ -1609,14 +1715,11 @@ void add_module_bus_nets(ModuleManager& module_manager, /* Create a net for each pin */ for (size_t pin_id = 0; pin_id < src_port.pins().size(); ++pin_id) { - ModuleNetId net = module_manager.module_instance_port_net(cur_module_id, - src_module_id, src_instance_id, - src_module_port_id, src_port.pins()[pin_id]); - if (ModuleNetId::INVALID() == net) { - net = module_manager.create_module_net(cur_module_id); - /* Configure the net source */ - module_manager.add_module_net_source(cur_module_id, net, src_module_id, src_instance_id, src_module_port_id, src_port.pins()[pin_id]); - } + ModuleNetId net = create_module_source_pin_net(module_manager, cur_module_id, + src_module_id, src_instance_id, + src_module_port_id, src_port.pins()[pin_id]); + VTR_ASSERT(ModuleNetId::INVALID() != net); + /* Configure the net sink */ module_manager.add_module_net_sink(cur_module_id, net, des_module_id, des_instance_id, des_module_port_id, des_port.pins()[pin_id]); } diff --git a/openfpga/src/utils/module_manager_utils.h b/openfpga/src/utils/module_manager_utils.h index d52727a40..640d798ab 100644 --- a/openfpga/src/utils/module_manager_utils.h +++ b/openfpga/src/utils/module_manager_utils.h @@ -91,6 +91,11 @@ void add_module_nets_between_logic_and_memory_sram_bus(ModuleManager& module_man const CircuitLibrary& circuit_lib, const CircuitModelId& logic_model); +void add_module_nets_cmos_flatten_memory_config_bus(ModuleManager& module_manager, + const ModuleId& parent_module, + const e_config_protocol_type& sram_orgz_type, + const e_circuit_model_port_type& config_port_type); + void add_module_nets_cmos_memory_chain_config_bus(ModuleManager& module_manager, const ModuleId& parent_module, const e_config_protocol_type& sram_orgz_type); @@ -135,6 +140,13 @@ size_t find_module_num_config_bits_from_child_modules(ModuleManager& module_mana const CircuitModelId& sram_model, const e_config_protocol_type& sram_orgz_type); +ModuleNetId create_module_source_pin_net(ModuleManager& module_manager, + const ModuleId& cur_module_id, + const ModuleId& src_module_id, + const size_t& src_instance_id, + const ModulePortId& src_module_port_id, + const size_t& src_pin_id); + void add_module_bus_nets(ModuleManager& module_manager, const ModuleId& cur_module_id, const ModuleId& src_module_id, From b9aac3cbdf70ebe3e17ccf6e1a3ba2710d5757e6 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Fri, 29 May 2020 23:22:01 -0600 Subject: [PATCH 598/645] updated fpga verilog testbench generation to support vanilla (standalone) configuration protocol --- .../src/fabric/build_top_module_memory.cpp | 7 +- .../fpga_verilog/verilog_top_testbench.cpp | 128 +++++++++++++++++- 2 files changed, 131 insertions(+), 4 deletions(-) diff --git a/openfpga/src/fabric/build_top_module_memory.cpp b/openfpga/src/fabric/build_top_module_memory.cpp index c15070488..b5d7f1db1 100644 --- a/openfpga/src/fabric/build_top_module_memory.cpp +++ b/openfpga/src/fabric/build_top_module_memory.cpp @@ -413,14 +413,17 @@ void add_top_module_nets_cmos_memory_config_bus(ModuleManager& module_manager, const e_config_protocol_type& sram_orgz_type) { switch (sram_orgz_type) { case CONFIG_MEM_STANDALONE: - /* Nothing to do */ + add_module_nets_cmos_flatten_memory_config_bus(module_manager, parent_module, + sram_orgz_type, CIRCUIT_MODEL_PORT_BL); + add_module_nets_cmos_flatten_memory_config_bus(module_manager, parent_module, + sram_orgz_type, CIRCUIT_MODEL_PORT_WL); break; case CONFIG_MEM_SCAN_CHAIN: { add_module_nets_cmos_memory_chain_config_bus(module_manager, parent_module, CONFIG_MEM_SCAN_CHAIN); break; } case CONFIG_MEM_MEMORY_BANK: - /* TODO: */ + /* TODO */ break; case CONFIG_MEM_FRAME_BASED: add_module_nets_cmos_memory_frame_config_bus(module_manager, decoder_lib, parent_module); diff --git a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp index 4a1072c70..7ece43460 100644 --- a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp +++ b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp @@ -60,6 +60,30 @@ constexpr char* TOP_TB_CLOCK_REG_POSTFIX = "_reg"; constexpr char* AUTOCHECK_TOP_TESTBENCH_VERILOG_MODULE_POSTFIX = "_autocheck_top_tb"; +/******************************************************************** + * Print local wires for flatten memory (standalone) configuration protocols + *******************************************************************/ +static +void print_verilog_top_testbench_flatten_memory_port(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module) { + /* Validate the file stream */ + valid_file_stream(fp); + + /* Print the port for Bit-Line */ + print_verilog_comment(fp, std::string("---- Bit Line ports -----")); + ModulePortId bl_port_id = module_manager.find_module_port(top_module, std::string(MEMORY_BL_PORT_NAME)); + BasicPort bl_port = module_manager.module_port(top_module, bl_port_id); + fp << generate_verilog_port(VERILOG_PORT_REG, bl_port) << ";" << std::endl; + + /* Print the port for Word-Line */ + print_verilog_comment(fp, std::string("---- Word Line ports -----")); + ModulePortId wl_port_id = module_manager.find_module_port(top_module, std::string(MEMORY_WL_PORT_NAME)); + BasicPort wl_port = module_manager.module_port(top_module, wl_port_id); + fp << generate_verilog_port(VERILOG_PORT_REG, wl_port) << ";" << std::endl; +} + + /******************************************************************** * Print local wires for configuration chain protocols *******************************************************************/ @@ -125,7 +149,7 @@ void print_verilog_top_testbench_config_protocol_port(std::fstream& fp, const ModuleId& top_module) { switch(sram_orgz_type) { case CONFIG_MEM_STANDALONE: - /* TODO */ + print_verilog_top_testbench_flatten_memory_port(fp, module_manager, top_module); break; case CONFIG_MEM_SCAN_CHAIN: print_verilog_top_testbench_config_chain_port(fp); @@ -498,6 +522,11 @@ size_t calculate_num_config_clock_cycles(const e_config_protocol_type& sram_orgz /* Branch on the type of configuration protocol */ switch (sram_orgz_type) { case CONFIG_MEM_STANDALONE: + /* We just need 1 clock cycle to load all the configuration bits + * since all the ports are exposed at the top-level + */ + num_config_clock_cycles = 2; + break; case CONFIG_MEM_SCAN_CHAIN: case CONFIG_MEM_MEMORY_BANK: /* TODO */ @@ -692,6 +721,7 @@ void print_verilog_top_testbench_load_bitstream_task(std::fstream& fp, const ModuleId& top_module) { switch (sram_orgz_type) { case CONFIG_MEM_STANDALONE: + /* No need to have a specific task. Loading is done in 1 clock cycle */ break; case CONFIG_MEM_SCAN_CHAIN: print_verilog_top_testbench_load_bitstream_task_configuration_chain(fp); @@ -845,6 +875,98 @@ void print_verilog_top_testbench_generic_stimulus(std::fstream& fp, fp << std::endl; } +/******************************************************************** + * Print stimulus for a FPGA fabric with a flatten memory (standalone) configuration protocol + * We will load the bitstream in the second clock cycle, right after the first reset cycle + *******************************************************************/ +static +void print_verilog_top_testbench_vanilla_bitstream(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module, + const BitstreamManager& bitstream_manager, + const FabricBitstream& fabric_bitstream) { + /* Validate the file stream */ + valid_file_stream(fp); + + /* Find Bit-Line and Word-Line port */ + BasicPort prog_clock_port(std::string(TOP_TB_PROG_CLOCK_PORT_NAME), 1); + + /* Find Bit-Line and Word-Line port */ + ModulePortId bl_port_id = module_manager.find_module_port(top_module, std::string(MEMORY_BL_PORT_NAME)); + BasicPort bl_port = module_manager.module_port(top_module, bl_port_id); + + ModulePortId wl_port_id = module_manager.find_module_port(top_module, std::string(MEMORY_WL_PORT_NAME)); + BasicPort wl_port = module_manager.module_port(top_module, wl_port_id); + + /* Initial value should be the first configuration bits + * In the rest of programming cycles, + * configuration bits are fed at the falling edge of programming clock. + * We do not care the value of scan_chain head during the first programming cycle + * It is reset anyway + */ + std::vector initial_bl_values(bl_port.get_width(), 0); + std::vector initial_wl_values(wl_port.get_width(), 0); + + print_verilog_comment(fp, "----- Begin bitstream loading during configuration phase -----"); + fp << "initial" << std::endl; + fp << "\tbegin" << std::endl; + print_verilog_comment(fp, "----- Configuration chain default input -----"); + fp << "\t\t"; + fp << generate_verilog_port_constant_values(bl_port, initial_bl_values); + fp << ";"; + fp << "\t\t"; + fp << generate_verilog_port_constant_values(wl_port, initial_wl_values); + fp << ";"; + + fp << std::endl; + + fp << "\t\t@(negedge " << generate_verilog_port(VERILOG_PORT_CONKT, prog_clock_port) << ");" << std::endl; + fp << "\t\t\t"; + + /* Enable all the WLs */ + std::vector enabled_wl_values(wl_port.get_width(), 1); + fp << generate_verilog_port(VERILOG_PORT_CONKT, wl_port); + fp << " = "; + fp << generate_verilog_port_constant_values(wl_port, enabled_wl_values); + + size_t ibit = 0; + for (const FabricBitId& bit_id : fabric_bitstream.bits()) { + BasicPort cur_bl_port(bl_port); + cur_bl_port.set_width(ibit, ibit); + + fp << generate_verilog_port(VERILOG_PORT_CONKT, cur_bl_port); + fp << " = "; + fp << "1'b" << (size_t)bitstream_manager.bit_value(fabric_bitstream.config_bit(bit_id)); + fp << ";" << std::endl; + + ibit++; + } + + fp << "\tend" << std::endl; + + /* Disable all the WLs */ + fp << "\t\t@(negedge " << generate_verilog_port(VERILOG_PORT_CONKT, prog_clock_port) << ");" << std::endl; + fp << generate_verilog_port(VERILOG_PORT_CONKT, wl_port); + fp << " = "; + fp << generate_verilog_port_constant_values(wl_port, initial_wl_values); + fp << "\t\t\t"; + + /* Raise the flag of configuration done when bitstream loading is complete */ + fp << "\t\t@(negedge " << generate_verilog_port(VERILOG_PORT_CONKT, prog_clock_port) << ");" << std::endl; + + BasicPort config_done_port(std::string(TOP_TB_CONFIG_DONE_PORT_NAME), 1); + fp << "\t\t\t"; + fp << generate_verilog_port(VERILOG_PORT_CONKT, config_done_port); + fp << " <= "; + std::vector config_done_enable_values(config_done_port.get_width(), 1); + fp << generate_verilog_constant_values(config_done_enable_values); + fp << ";" << std::endl; + + fp << "\tend" << std::endl; + print_verilog_comment(fp, "----- End bitstream loading during configuration phase -----"); +} + + /******************************************************************** * Print stimulus for a FPGA fabric with a configuration chain protocol * where configuration bits are programming in serial (one by one) @@ -1022,7 +1144,9 @@ void print_verilog_top_testbench_bitstream(std::fstream& fp, /* Branch on the type of configuration protocol */ switch (sram_orgz_type) { case CONFIG_MEM_STANDALONE: - /* TODO */ + print_verilog_top_testbench_vanilla_bitstream(fp, + module_manager, top_module, + bitstream_manager, fabric_bitstream); break; case CONFIG_MEM_SCAN_CHAIN: print_verilog_top_testbench_configuration_chain_bitstream(fp, bitstream_manager, fabric_bitstream); From 8b5b221a211ed2e41cbf4c6395de6c5f88da4635 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 30 May 2020 11:36:00 -0600 Subject: [PATCH 599/645] add new architecture for standalone memory organization --- .../k4_N4_40nm_standalone_openfpga.xml | 229 ++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 openfpga_flow/openfpga_arch/k4_N4_40nm_standalone_openfpga.xml diff --git a/openfpga_flow/openfpga_arch/k4_N4_40nm_standalone_openfpga.xml b/openfpga_flow/openfpga_arch/k4_N4_40nm_standalone_openfpga.xml new file mode 100644 index 000000000..fc97f75c1 --- /dev/null +++ b/openfpga_flow/openfpga_arch/k4_N4_40nm_standalone_openfpga.xml @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + + + + + 10e-12 5e-12 5e-12 + + + 10e-12 5e-12 5e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 5f6a790effffee2dd00a9af8e384ebde442773e3 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 30 May 2020 11:39:24 -0600 Subject: [PATCH 600/645] add new test cases for the standalone memory configuration protocol --- .../flatten_memory/config/task.conf | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 openfpga_flow/tasks/openfpga_shell/full_testbench/flatten_memory/config/task.conf diff --git a/openfpga_flow/tasks/openfpga_shell/full_testbench/flatten_memory/config/task.conf b/openfpga_flow/tasks/openfpga_shell/full_testbench/flatten_memory/config/task.conf new file mode 100644 index 000000000..d2bf1bb9f --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/full_testbench/flatten_memory/config/task.conf @@ -0,0 +1,34 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/full_testbench_example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k4_N4_40nm_standalone_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k4_N4_tileable_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.blif + +[SYNTHESIS_PARAM] +bench0_top = and2 +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.v +bench0_chan_width = 300 + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +end_flow_with_test= +#vpr_fpga_verilog_formal_verification_top_netlist= From 8ec8ac41181b9e628bfa6b37033faff5a2f1e0e1 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 30 May 2020 12:16:41 -0600 Subject: [PATCH 601/645] bug fixed in flatten memory organization. Passed verification --- openfpga/src/fabric/build_memory_modules.cpp | 4 +-- .../fpga_verilog/verilog_top_testbench.cpp | 21 ++++++----- .../src/fpga_verilog/verilog_writer_utils.cpp | 36 ++++++++++++++++--- .../src/fpga_verilog/verilog_writer_utils.h | 3 +- 4 files changed, 45 insertions(+), 19 deletions(-) diff --git a/openfpga/src/fabric/build_memory_modules.cpp b/openfpga/src/fabric/build_memory_modules.cpp index f634408b1..72d1315ad 100644 --- a/openfpga/src/fabric/build_memory_modules.cpp +++ b/openfpga/src/fabric/build_memory_modules.cpp @@ -61,7 +61,7 @@ void add_module_input_nets_to_mem_modules(ModuleManager& module_manager, module_manager.add_module_net_source(mem_module, net, mem_module, 0, src_port_id, src_pin_id); } - for (size_t pin_id = 0; pin_id < module_manager.module_port(mem_module, sink_port_id).pins().size(); ++pin_id) { + for (size_t pin_id = 0; pin_id < module_manager.module_port(child_module, sink_port_id).pins().size(); ++pin_id) { /* Sink node of the input net is the input of sram module */ size_t sink_pin_id = module_manager.module_port(child_module, sink_port_id).pins()[pin_id]; module_manager.add_module_net_sink(mem_module, net, child_module, child_instance, sink_port_id, sink_pin_id); @@ -328,7 +328,7 @@ void build_memory_flatten_module(ModuleManager& module_manager, BasicPort bl_port(std::string(MEMORY_BL_PORT_NAME), num_mems); ModulePortId mem_bl_port = module_manager.add_port(mem_module, bl_port, ModuleManager::MODULE_INPUT_PORT); - BasicPort wl_port(std::string(MEMORY_BL_PORT_NAME), num_mems); + BasicPort wl_port(std::string(MEMORY_WL_PORT_NAME), num_mems); ModulePortId mem_wl_port = module_manager.add_port(mem_module, wl_port, ModuleManager::MODULE_INPUT_PORT); /* Add each output port: port width should match the number of memories */ diff --git a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp index 7ece43460..68d1e3dba 100644 --- a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp +++ b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp @@ -913,27 +913,27 @@ void print_verilog_top_testbench_vanilla_bitstream(std::fstream& fp, print_verilog_comment(fp, "----- Configuration chain default input -----"); fp << "\t\t"; fp << generate_verilog_port_constant_values(bl_port, initial_bl_values); - fp << ";"; + fp << ";" << std::endl; fp << "\t\t"; fp << generate_verilog_port_constant_values(wl_port, initial_wl_values); - fp << ";"; + fp << ";" << std::endl; fp << std::endl; - fp << "\t\t@(negedge " << generate_verilog_port(VERILOG_PORT_CONKT, prog_clock_port) << ");" << std::endl; - fp << "\t\t\t"; + fp << "\t\t@(negedge " << generate_verilog_port(VERILOG_PORT_CONKT, prog_clock_port) << ") begin" << std::endl; /* Enable all the WLs */ std::vector enabled_wl_values(wl_port.get_width(), 1); - fp << generate_verilog_port(VERILOG_PORT_CONKT, wl_port); - fp << " = "; + fp << "\t\t\t"; fp << generate_verilog_port_constant_values(wl_port, enabled_wl_values); + fp << ";" << std::endl; size_t ibit = 0; for (const FabricBitId& bit_id : fabric_bitstream.bits()) { BasicPort cur_bl_port(bl_port); cur_bl_port.set_width(ibit, ibit); + fp << "\t\t\t"; fp << generate_verilog_port(VERILOG_PORT_CONKT, cur_bl_port); fp << " = "; fp << "1'b" << (size_t)bitstream_manager.bit_value(fabric_bitstream.config_bit(bit_id)); @@ -942,14 +942,14 @@ void print_verilog_top_testbench_vanilla_bitstream(std::fstream& fp, ibit++; } - fp << "\tend" << std::endl; + fp << "\t\tend" << std::endl; /* Disable all the WLs */ fp << "\t\t@(negedge " << generate_verilog_port(VERILOG_PORT_CONKT, prog_clock_port) << ");" << std::endl; - fp << generate_verilog_port(VERILOG_PORT_CONKT, wl_port); - fp << " = "; - fp << generate_verilog_port_constant_values(wl_port, initial_wl_values); + fp << "\t\t\t"; + fp << generate_verilog_port_constant_values(wl_port, initial_wl_values); + fp << ";" << std::endl; /* Raise the flag of configuration done when bitstream loading is complete */ fp << "\t\t@(negedge " << generate_verilog_port(VERILOG_PORT_CONKT, prog_clock_port) << ");" << std::endl; @@ -966,7 +966,6 @@ void print_verilog_top_testbench_vanilla_bitstream(std::fstream& fp, print_verilog_comment(fp, "----- End bitstream loading during configuration phase -----"); } - /******************************************************************** * Print stimulus for a FPGA fabric with a configuration chain protocol * where configuration bits are programming in serial (one by one) diff --git a/openfpga/src/fpga_verilog/verilog_writer_utils.cpp b/openfpga/src/fpga_verilog/verilog_writer_utils.cpp index 90ee95e9c..c59d92dd0 100644 --- a/openfpga/src/fpga_verilog/verilog_writer_utils.cpp +++ b/openfpga/src/fpga_verilog/verilog_writer_utils.cpp @@ -644,12 +644,38 @@ std::string generate_verilog_local_wire(const BasicPort& output_port, /******************************************************************** * Generate a string for a constant value in Verilog format: * <#.of bits>'b + * + * Optimization: short_constant + * When this switch is turned on, we will generate short version + * for all-zero/all-one vectors + * {{1'b}} *******************************************************************/ -std::string generate_verilog_constant_values(const std::vector& const_values) { - std::string str = std::to_string(const_values.size()); - str += "'b"; - for (const auto& val : const_values) { - str += std::to_string(val); +std::string generate_verilog_constant_values(const std::vector& const_values, + const bool& short_constant) { + VTR_ASSERT(!const_values.empty()); + + bool same_values = true; + size_t first_val = const_values.back(); + if (true == short_constant) { + for (const auto& val : const_values) { + if (first_val != val) { + same_values = false; + break; + } + } + } + + std::string str; + + if ( (true == short_constant) + && (true == same_values) ) { + str = "{" + std::to_string(const_values.size()) + "{1'b" + std::to_string(first_val) + "}}"; + } else { + str = std::to_string(const_values.size()); + str += "'b"; + for (const auto& val : const_values) { + str += std::to_string(val); + } } return str; } diff --git a/openfpga/src/fpga_verilog/verilog_writer_utils.h b/openfpga/src/fpga_verilog/verilog_writer_utils.h index 8e6cddaaa..0d8219283 100644 --- a/openfpga/src/fpga_verilog/verilog_writer_utils.h +++ b/openfpga/src/fpga_verilog/verilog_writer_utils.h @@ -96,7 +96,8 @@ BasicPort generate_verilog_bus_port(const std::vector& input_ports, std::string generate_verilog_local_wire(const BasicPort& output_port, const std::vector& input_ports); -std::string generate_verilog_constant_values(const std::vector& const_values); +std::string generate_verilog_constant_values(const std::vector& const_values, + const bool& short_constant = false); std::string generate_verilog_port_constant_values(const BasicPort& output_port, const std::vector& const_values); From 1fedd009124770eceeb45388b6b15f61f60de10e Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 30 May 2020 12:23:48 -0600 Subject: [PATCH 602/645] deploy the flatten configuration memory testcase to CI --- .travis/script.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis/script.sh b/.travis/script.sh index 9d222c17a..034704c10 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -57,6 +57,9 @@ python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/full_testbench/con python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/full_testbench/fast_configuration_frame --debug --show_thread_logs python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/preconfig_testbench/configuration_frame --debug --show_thread_logs +echo -e "Testing standalone (flatten memory) configuration protocol of a K4N4 FPGA"; +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/full_testbench/flatten_memory --debug --show_thread_logs + echo -e "Testing user-defined simulation settings: clock frequency and number of cycles"; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/fixed_simulation_settings --debug --show_thread_logs From 2def059b5b6d3663dcde451b823cb646d4818f11 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 30 May 2020 12:25:29 -0600 Subject: [PATCH 603/645] add standalone configuration protocol to pre config test cases --- .../flatten_memory/config/task.conf | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 openfpga_flow/tasks/openfpga_shell/preconfig_testbench/flatten_memory/config/task.conf diff --git a/openfpga_flow/tasks/openfpga_shell/preconfig_testbench/flatten_memory/config/task.conf b/openfpga_flow/tasks/openfpga_shell/preconfig_testbench/flatten_memory/config/task.conf new file mode 100644 index 000000000..1a02eb45f --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/preconfig_testbench/flatten_memory/config/task.conf @@ -0,0 +1,34 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k4_N4_40nm_standalone_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k4_N4_tileable_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.blif + +[SYNTHESIS_PARAM] +bench0_top = and2 +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.v +bench0_chan_width = 300 + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +end_flow_with_test= +vpr_fpga_verilog_formal_verification_top_netlist= From c456ef4d0029979d226dc28a4e1709cada47ef35 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 30 May 2020 12:26:05 -0600 Subject: [PATCH 604/645] deploy the standalone preconfig testcase to CI --- .travis/script.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis/script.sh b/.travis/script.sh index 034704c10..846ec847f 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -59,6 +59,7 @@ python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/preconfig_testbenc echo -e "Testing standalone (flatten memory) configuration protocol of a K4N4 FPGA"; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/full_testbench/flatten_memory --debug --show_thread_logs +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/preconfig_testbench/flatten_memory --debug --show_thread_logs echo -e "Testing user-defined simulation settings: clock frequency and number of cycles"; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/fixed_simulation_settings --debug --show_thread_logs From fe2ba7d50aadc9f398549c579f90dde740cdbf43 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 30 May 2020 12:52:59 -0600 Subject: [PATCH 605/645] update documentation for standalone configuration protocol --- .../manual/arch_lang/config_protocol.rst | 28 ++++++++++++++++-- .../figures/frame_config_protocol_example.png | Bin 259112 -> 263616 bytes .../figures/vanilla_config_protocol.png | Bin 0 -> 184972 bytes 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 docs/source/manual/arch_lang/figures/vanilla_config_protocol.png diff --git a/docs/source/manual/arch_lang/config_protocol.rst b/docs/source/manual/arch_lang/config_protocol.rst index f37c7f18b..3eba78fd4 100644 --- a/docs/source/manual/arch_lang/config_protocol.rst +++ b/docs/source/manual/arch_lang/config_protocol.rst @@ -107,7 +107,7 @@ It will use the circuit model defined in :numref:`fig_sram_blwl`. .. code-block:: xml - + .. _fig_sram: @@ -122,5 +122,29 @@ It will use the circuit model defined in :numref:`fig_sram_blwl`. Standalone SRAM Example ~~~~~~~~~~~~~~~~~~~~~~~ +In the standalone configuration protocol, every memory cell of the core logic of a FPGA fabric can be directly accessed at the top-level module, as illustrated in :numref:`fig_vanilla_config_protocol`. + +.. _fig_vanilla_config_protocol: + +.. figure:: figures/vanilla_config_protocol.png + :scale: 30% + :alt: map to buried treasure + + Vanilla (standalone) memory organization in a hierarchical view + +The following XML code shows an example where we use the circuit model defined in :numref:`fig_sram_blwl`. + +.. code-block:: xml + + + + + +.. note:: The standalone protocol does require a memory cell to have + + - two outputs (one regular and another inverted) + - a Bit-Line input to load the data + - a Word-Line input to enable data write + +.. warning:: This is a vanilla configuration method, which allow users to build their own configuration protocol on top of it. -.. warning:: TO BE CONSTRUCTED diff --git a/docs/source/manual/arch_lang/figures/frame_config_protocol_example.png b/docs/source/manual/arch_lang/figures/frame_config_protocol_example.png index 9cc4e7e4d84b5a48976fe69e60a1d9f4e540d242..cd24907994c64ca4316262564df631854c98eb8e 100644 GIT binary patch literal 263616 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 VY+mW{Clyk_Wp4wsE;{0V?LTdbsX+h$ literal 259112 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

5I**XzdMK+jJEzHxTn-( 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

?ZS4%7X?UaX93x*VbTh` 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

1;e<5?|D-ccD7*HdKCMX__t$YmM3(T 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 LISlIuznEn*4?o+Z*Z%d2T@E}Pyc+hn)#Fac< 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 TO0R!_fS(t#$}*+Ti~|2ZLV;dA diff --git a/docs/source/manual/arch_lang/figures/vanilla_config_protocol.png b/docs/source/manual/arch_lang/figures/vanilla_config_protocol.png new file mode 100644 index 0000000000000000000000000000000000000000..3b62b0df7f85a5c280c7df71933b701422096df0 GIT binary patch literal 184972 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 literal 0 HcmV?d00001 From 0931eccbf685174c589f301def305cf03bdc2eb8 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 30 May 2020 12:58:42 -0600 Subject: [PATCH 606/645] update documentation for the fast configuration options --- docs/source/manual/openfpga_shell/openfpga_commands.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source/manual/openfpga_shell/openfpga_commands.rst b/docs/source/manual/openfpga_shell/openfpga_commands.rst index 1ac4ed69b..6e2f0e490 100644 --- a/docs/source/manual/openfpga_shell/openfpga_commands.rst +++ b/docs/source/manual/openfpga_shell/openfpga_commands.rst @@ -172,6 +172,8 @@ FPGA-Verilog - ``--reference_benchmark_file_path`` Must specify the reference benchmark Verilog file if you want to output any testbenches + - ``--fast_configuration`` Enable fast configuration phase for the top-level testbench in order to reduce runtime of simulations. It is applicable to memory bank and frame-based configuration protocols. When enabled, all the zero configuration bits will be skipped. So ensure that your memory cells can be correctly reset to zero with a reset signal. + - ``--print_top_testbench`` Enable top-level testbench which is a full verification including programming circuit and core logic of FPGA - ``--print_formal_verification_top_netlist`` Generate a top-level module which can be used in formal verification From ad7422359d795a449db21fd686b9dea19fe4db9c Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 30 May 2020 13:04:55 -0600 Subject: [PATCH 607/645] deploy compact constant values in Verilog codes --- openfpga/src/fpga_verilog/verilog_writer_utils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openfpga/src/fpga_verilog/verilog_writer_utils.h b/openfpga/src/fpga_verilog/verilog_writer_utils.h index 0d8219283..21c12509b 100644 --- a/openfpga/src/fpga_verilog/verilog_writer_utils.h +++ b/openfpga/src/fpga_verilog/verilog_writer_utils.h @@ -97,7 +97,7 @@ std::string generate_verilog_local_wire(const BasicPort& output_port, const std::vector& input_ports); std::string generate_verilog_constant_values(const std::vector& const_values, - const bool& short_constant = false); + const bool& short_constant = true); std::string generate_verilog_port_constant_values(const BasicPort& output_port, const std::vector& const_values); From c00653961e732af832e3e7ef7545ddf699a3fc03 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 30 May 2020 15:26:22 -0600 Subject: [PATCH 608/645] minor format fix in documentation --- docs/source/manual/arch_lang/config_protocol.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/source/manual/arch_lang/config_protocol.rst b/docs/source/manual/arch_lang/config_protocol.rst index 3eba78fd4..2fcdae535 100644 --- a/docs/source/manual/arch_lang/config_protocol.rst +++ b/docs/source/manual/arch_lang/config_protocol.rst @@ -35,6 +35,7 @@ Template .. option:: circuit_model_name="" Specify the name of circuit model to be used as configurable memory. + - ``scan_chain`` requires a circuit model type of ``ccff`` - ``frame_based`` requires a circuit model type of ``sram`` - ``memory_bank`` requires a circuit model type of ``sram`` From fa8dfc1fbdc021208a10daac2c9f60f4b223faff Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 30 May 2020 15:49:15 -0600 Subject: [PATCH 609/645] add configuration protocol ports to top module for memory bank organization --- openfpga/src/base/openfpga_reserved_words.h | 2 + openfpga/src/fabric/build_top_module.cpp | 4 +- .../src/fabric/build_top_module_memory.cpp | 100 ++++++++++++++++++ openfpga/src/fabric/build_top_module_memory.h | 7 ++ openfpga/src/utils/decoder_library_utils.cpp | 39 +++++++ openfpga/src/utils/decoder_library_utils.h | 2 + openfpga/src/utils/module_manager_utils.cpp | 2 +- 7 files changed, 154 insertions(+), 2 deletions(-) diff --git a/openfpga/src/base/openfpga_reserved_words.h b/openfpga/src/base/openfpga_reserved_words.h index 40a2af96b..a9b31f64e 100644 --- a/openfpga/src/base/openfpga_reserved_words.h +++ b/openfpga/src/base/openfpga_reserved_words.h @@ -38,6 +38,8 @@ constexpr char* DECODER_ADDRESS_PORT_NAME = "address"; constexpr char* DECODER_DATA_IN_PORT_NAME = "data_in"; constexpr char* DECODER_DATA_OUT_PORT_NAME = "data_out"; constexpr char* DECODER_DATA_OUT_INV_PORT_NAME = "data_out_inv"; +constexpr char* DECODER_BL_ADDRESS_PORT_NAME = "bl_address"; +constexpr char* DECODER_WL_ADDRESS_PORT_NAME = "wl_address"; /* Inverted port naming */ constexpr char* INV_PORT_POSTFIX = "_inv"; diff --git a/openfpga/src/fabric/build_top_module.cpp b/openfpga/src/fabric/build_top_module.cpp index 072548807..7b05a5676 100644 --- a/openfpga/src/fabric/build_top_module.cpp +++ b/openfpga/src/fabric/build_top_module.cpp @@ -384,7 +384,9 @@ void build_top_module(ModuleManager& module_manager, */ size_t module_num_config_bits = find_module_num_config_bits_from_child_modules(module_manager, top_module, circuit_lib, sram_model, sram_orgz_type); if (0 < module_num_config_bits) { - add_sram_ports_to_module_manager(module_manager, top_module, circuit_lib, sram_model, sram_orgz_type, module_num_config_bits); + add_top_module_sram_ports(module_manager, top_module, + circuit_lib, sram_model, + sram_orgz_type, module_num_config_bits); } /* Add module nets to connect memory cells inside diff --git a/openfpga/src/fabric/build_top_module_memory.cpp b/openfpga/src/fabric/build_top_module_memory.cpp index b5d7f1db1..c2a726ea9 100644 --- a/openfpga/src/fabric/build_top_module_memory.cpp +++ b/openfpga/src/fabric/build_top_module_memory.cpp @@ -13,6 +13,8 @@ #include "openfpga_reserved_words.h" #include "openfpga_naming.h" +#include "memory_utils.h" +#include "decoder_library_utils.h" #include "module_manager_utils.h" #include "build_top_module_memory.h" @@ -361,6 +363,104 @@ void organize_top_module_memory_modules(ModuleManager& module_manager, } } +/******************************************************************** + * Add a list of ports that are used for SRAM configuration to the FPGA + * top-level module + * The type and names of added ports strongly depend on the + * organization of SRAMs. + * 1. Standalone SRAMs: + * two ports will be added, which are BL and WL + * 2. Scan-chain Flip-flops: + * two ports will be added, which are the head of scan-chain + * and the tail of scan-chain + * IMPORTANT: the port size will be forced to 1 in this case + * because the head and tail are both 1-bit ports!!! + * 3. Memory decoders: + * - An enable signal + * - A BL address port + * - A WL address port + * - A data-in port for the BL decoder + * 4. Frame-based memory: + * - An Enable signal + * - An address port, whose size depends on the number of config bits + * and the maximum size of address ports of configurable children + * - An data_in port (single-bit) + ********************************************************************/ +void add_top_module_sram_ports(ModuleManager& module_manager, + const ModuleId& module_id, + const CircuitLibrary& circuit_lib, + const CircuitModelId& sram_model, + const e_config_protocol_type sram_orgz_type, + const size_t& num_config_bits) { + std::vector sram_port_names = generate_sram_port_names(circuit_lib, sram_model, sram_orgz_type); + size_t sram_port_size = generate_sram_port_size(sram_orgz_type, num_config_bits); + + /* Add ports to the module manager */ + switch (sram_orgz_type) { + case CONFIG_MEM_STANDALONE: { + for (const std::string& sram_port_name : sram_port_names) { + /* Add generated ports to the ModuleManager */ + BasicPort sram_port(sram_port_name, sram_port_size); + module_manager.add_port(module_id, sram_port, ModuleManager::MODULE_INPUT_PORT); + } + break; + } + case CONFIG_MEM_MEMORY_BANK: { + BasicPort en_port(std::string(DECODER_ENABLE_PORT_NAME), 1); + module_manager.add_port(module_id, en_port, ModuleManager::MODULE_INPUT_PORT); + + size_t bl_addr_size = find_memory_decoder_addr_size(num_config_bits); + BasicPort bl_addr_port(std::string(DECODER_BL_ADDRESS_PORT_NAME), bl_addr_size); + module_manager.add_port(module_id, bl_addr_port, ModuleManager::MODULE_INPUT_PORT); + + size_t wl_addr_size = find_memory_decoder_addr_size(num_config_bits); + BasicPort wl_addr_port(std::string(DECODER_WL_ADDRESS_PORT_NAME), wl_addr_size); + module_manager.add_port(module_id, wl_addr_port, ModuleManager::MODULE_INPUT_PORT); + + BasicPort din_port(std::string(DECODER_DATA_IN_PORT_NAME), 1); + module_manager.add_port(module_id, din_port, ModuleManager::MODULE_INPUT_PORT); + + break; + } + case CONFIG_MEM_SCAN_CHAIN: { + /* Note that configuration chain tail is an output while head is an input + * IMPORTANT: this is co-designed with function generate_sram_port_names() + * If the return vector is changed, the following codes MUST be adapted! + */ + VTR_ASSERT(2 == sram_port_names.size()); + size_t port_counter = 0; + for (const std::string& sram_port_name : sram_port_names) { + /* Add generated ports to the ModuleManager */ + BasicPort sram_port(sram_port_name, sram_port_size); + if (0 == port_counter) { + module_manager.add_port(module_id, sram_port, ModuleManager::MODULE_INPUT_PORT); + } else { + VTR_ASSERT(1 == port_counter); + module_manager.add_port(module_id, sram_port, ModuleManager::MODULE_OUTPUT_PORT); + } + port_counter++; + } + break; + } + case CONFIG_MEM_FRAME_BASED: { + BasicPort en_port(std::string(DECODER_ENABLE_PORT_NAME), 1); + module_manager.add_port(module_id, en_port, ModuleManager::MODULE_INPUT_PORT); + + BasicPort addr_port(std::string(DECODER_ADDRESS_PORT_NAME), num_config_bits); + module_manager.add_port(module_id, addr_port, ModuleManager::MODULE_INPUT_PORT); + + BasicPort din_port(std::string(DECODER_DATA_IN_PORT_NAME), 1); + module_manager.add_port(module_id, din_port, ModuleManager::MODULE_INPUT_PORT); + + break; + } + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid type of SRAM organization !\n"); + exit(1); + } +} + /********************************************************************* * Add the port-to-port connection between all the memory modules diff --git a/openfpga/src/fabric/build_top_module_memory.h b/openfpga/src/fabric/build_top_module_memory.h index 1d0295b87..a912845b2 100644 --- a/openfpga/src/fabric/build_top_module_memory.h +++ b/openfpga/src/fabric/build_top_module_memory.h @@ -34,6 +34,13 @@ void organize_top_module_memory_modules(ModuleManager& module_manager, const std::map>& cb_instance_ids, const bool& compact_routing_hierarchy); +void add_top_module_sram_ports(ModuleManager& module_manager, + const ModuleId& module_id, + const CircuitLibrary& circuit_lib, + const CircuitModelId& sram_model, + const e_config_protocol_type sram_orgz_type, + const size_t& num_config_bits); + void add_top_module_nets_memory_config_bus(ModuleManager& module_manager, DecoderLibrary& decoder_lib, const ModuleId& parent_module, diff --git a/openfpga/src/utils/decoder_library_utils.cpp b/openfpga/src/utils/decoder_library_utils.cpp index a41e439ef..20f7473b2 100644 --- a/openfpga/src/utils/decoder_library_utils.cpp +++ b/openfpga/src/utils/decoder_library_utils.cpp @@ -38,6 +38,45 @@ size_t find_mux_local_decoder_addr_size(const size_t& data_size) { return ceil(log(data_size) / log(2)); } +/*************************************************************************************** + * Find the size of address lines for a memory decoder to access a memory array + * Addr lines + * | | ... | + * v v v + * +-----------+ + * / Local \ + * / Decoder \ + * +-----------------+ + * | | | ... | | | + * v v v v v v + * Data outputs + * + * +------+ +------+ +------+ + * | SRAM | | SRAM | ... | SRAM | + * | [0] | | [1] | | [i] | + * +------+ +------+ +------+ + * + * +------+ +------+ +------+ + * | SRAM | | SRAM | ... | SRAM | + * | [i+1]| | [i+2]| |[2i-1]| + * +------+ +------+ +------+ + * + * ... ... ... + * + * +------+ +------+ +------+ + * | SRAM | | SRAM | ... | SRAM | + * | [x] | | [x+1]| | [N] | + * +------+ +------+ +------+ + * + * Due to the shared lines in the array, + * each memory decoder (BL or WL) will access sqrt(N) control lins (BL or WL) + * Then we can use the function for mux local encoder to compute the address size + * + ***************************************************************************************/ +size_t find_memory_decoder_addr_size(const size_t& num_mems) { + return find_mux_local_decoder_addr_size((size_t)std::ceil(std::sqrt((float)num_mems))); +} + /*************************************************************************************** * Try to find if the decoder already exists in the library, * If there is no such decoder, add it to the library diff --git a/openfpga/src/utils/decoder_library_utils.h b/openfpga/src/utils/decoder_library_utils.h index d55a0750a..e43068f5f 100644 --- a/openfpga/src/utils/decoder_library_utils.h +++ b/openfpga/src/utils/decoder_library_utils.h @@ -13,6 +13,8 @@ bool need_mux_local_decoder(const size_t& data_size); size_t find_mux_local_decoder_addr_size(const size_t& data_size); +size_t find_memory_decoder_addr_size(const size_t& num_mems); + DecoderId add_mux_local_decoder_to_library(DecoderLibrary& decoder_lib, const size_t data_size); diff --git a/openfpga/src/utils/module_manager_utils.cpp b/openfpga/src/utils/module_manager_utils.cpp index cf9808466..ec871368a 100644 --- a/openfpga/src/utils/module_manager_utils.cpp +++ b/openfpga/src/utils/module_manager_utils.cpp @@ -163,7 +163,7 @@ void add_formal_verification_sram_ports_to_module_manager(ModuleManager& module_ * The type and names of added ports strongly depend on the * organization of SRAMs. * 1. Standalone SRAMs: - * two ports will be added, which are regular output and inverted output + * two ports will be added, which are BL and WL * 2. Scan-chain Flip-flops: * two ports will be added, which are the head of scan-chain * and the tail of scan-chain From 0e16ee10300e606b26230f5a472e9174fd0b9907 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 30 May 2020 18:14:36 -0600 Subject: [PATCH 610/645] add configuration bus nets for memory bank decoders at top module --- openfpga/src/base/openfpga_naming.cpp | 28 ++ openfpga/src/base/openfpga_naming.h | 6 + openfpga/src/fabric/build_decoder_modules.cpp | 113 ++++++++ openfpga/src/fabric/build_decoder_modules.h | 8 + .../src/fabric/build_top_module_memory.cpp | 245 ++++++++++++++++++ openfpga/src/utils/decoder_library_utils.cpp | 4 + openfpga/src/utils/decoder_library_utils.h | 2 + 7 files changed, 406 insertions(+) diff --git a/openfpga/src/base/openfpga_naming.cpp b/openfpga/src/base/openfpga_naming.cpp index 5cf00bf7f..1c792d26d 100644 --- a/openfpga/src/base/openfpga_naming.cpp +++ b/openfpga/src/base/openfpga_naming.cpp @@ -156,6 +156,34 @@ std::string generate_frame_memory_decoder_subckt_name(const size_t& addr_size, return subckt_name; } +/************************************************ + * Generate the module name of a bit-line decoder + * for memories + ***********************************************/ +std::string generate_bl_memory_decoder_subckt_name(const size_t& addr_size, + const size_t& data_size) { + std::string subckt_name = "bl_decoder"; + subckt_name += std::to_string(addr_size); + subckt_name += "to"; + subckt_name += std::to_string(data_size); + + return subckt_name; +} + +/************************************************ + * Generate the module name of a word-line decoder + * for memories + ***********************************************/ +std::string generate_wl_memory_decoder_subckt_name(const size_t& addr_size, + const size_t& data_size) { + std::string subckt_name = "wl_decoder"; + subckt_name += std::to_string(addr_size); + subckt_name += "to"; + subckt_name += std::to_string(data_size); + + return subckt_name; +} + /************************************************ * Generate the module name of a routing track wire ***********************************************/ diff --git a/openfpga/src/base/openfpga_naming.h b/openfpga/src/base/openfpga_naming.h index 4e1c44fa9..534bf2a78 100644 --- a/openfpga/src/base/openfpga_naming.h +++ b/openfpga/src/base/openfpga_naming.h @@ -53,6 +53,12 @@ std::string generate_mux_local_decoder_subckt_name(const size_t& addr_size, std::string generate_frame_memory_decoder_subckt_name(const size_t& addr_size, const size_t& data_size); +std::string generate_bl_memory_decoder_subckt_name(const size_t& addr_size, + const size_t& data_size); + +std::string generate_wl_memory_decoder_subckt_name(const size_t& addr_size, + const size_t& data_size); + std::string generate_segment_wire_subckt_name(const std::string& wire_model_name, const size_t& segment_id); diff --git a/openfpga/src/fabric/build_decoder_modules.cpp b/openfpga/src/fabric/build_decoder_modules.cpp index ada36e3e7..fb8a29e5c 100644 --- a/openfpga/src/fabric/build_decoder_modules.cpp +++ b/openfpga/src/fabric/build_decoder_modules.cpp @@ -74,6 +74,119 @@ ModuleId build_frame_memory_decoder_module(ModuleManager& module_manager, return module_id; } +/*************************************************************************************** + * Create a module for a BL decoder with a given output size + * + * BL Address + * | | ... | + * v v v + * +-----------+ + * / \<-- data_in + * enable-->/ Decoder \ + * +-----------------+ + * | | | ... | | | + * v v v v v v + * Data Outputs + * + * The outputs are assumes to be one-hot codes (at most only one '1' exist) + * Considering this fact, there are only num_of_outputs conditions to be encoded. + * Therefore, the number of inputs is ceil(log(num_of_outputs)/log(2)) + ***************************************************************************************/ +ModuleId build_bl_memory_decoder_module(ModuleManager& module_manager, + const DecoderLibrary& decoder_lib, + const DecoderId& decoder) { + /* Get the number of inputs */ + size_t addr_size = decoder_lib.addr_size(decoder); + size_t data_size = decoder_lib.data_size(decoder); + + /* Create a name for the local encoder */ + std::string module_name = generate_bl_memory_decoder_subckt_name(addr_size, data_size); + + /* Create a Verilog Module based on the circuit model, and add to module manager */ + ModuleId module_id = module_manager.add_module(module_name); + VTR_ASSERT(true == module_manager.valid_module_id(module_id)); + + /* Add enable port */ + BasicPort en_port(std::string(DECODER_ENABLE_PORT_NAME), 1); + module_manager.add_port(module_id, en_port, ModuleManager::MODULE_INPUT_PORT); + /* Add each input port */ + BasicPort addr_port(std::string(DECODER_ADDRESS_PORT_NAME), addr_size); + module_manager.add_port(module_id, addr_port, ModuleManager::MODULE_INPUT_PORT); + /* Add each input port */ + BasicPort din_port(std::string(DECODER_DATA_IN_PORT_NAME), 1); + module_manager.add_port(module_id, din_port, ModuleManager::MODULE_INPUT_PORT); + /* Add each output port */ + BasicPort data_port(std::string(DECODER_DATA_OUT_PORT_NAME), data_size); + module_manager.add_port(module_id, data_port, ModuleManager::MODULE_OUTPUT_PORT); + + /* Data port is registered. It should be outputted as + * output reg [lsb:msb] data + */ + module_manager.set_port_is_register(module_id, data_port.get_name(), true); + /* Add data_in port */ + if (true == decoder_lib.use_data_inv_port(decoder)) { + BasicPort data_inv_port(std::string(DECODER_DATA_OUT_INV_PORT_NAME), data_size); + module_manager.add_port(module_id, data_inv_port, ModuleManager::MODULE_OUTPUT_PORT); + } + + return module_id; +} + +/*************************************************************************************** + * Create a module for a Word-line decoder with a given output size + * + * WL Address + * | | ... | + * v v v + * +-----------+ + * / \ + * enable-->/ Decoder \ + * +-----------------+ + * | | | ... | | | + * v v v v v v + * Data Outputs + * + * The outputs are assumes to be one-hot codes (at most only one '1' exist) + * Considering this fact, there are only num_of_outputs conditions to be encoded. + * Therefore, the number of inputs is ceil(log(num_of_outputs)/log(2)) + ***************************************************************************************/ +ModuleId build_wl_memory_decoder_module(ModuleManager& module_manager, + const DecoderLibrary& decoder_lib, + const DecoderId& decoder) { + /* Get the number of inputs */ + size_t addr_size = decoder_lib.addr_size(decoder); + size_t data_size = decoder_lib.data_size(decoder); + + /* Create a name for the local encoder */ + std::string module_name = generate_wl_memory_decoder_subckt_name(addr_size, data_size); + + /* Create a Verilog Module based on the circuit model, and add to module manager */ + ModuleId module_id = module_manager.add_module(module_name); + VTR_ASSERT(true == module_manager.valid_module_id(module_id)); + + /* Add enable port */ + BasicPort en_port(std::string(DECODER_ENABLE_PORT_NAME), 1); + module_manager.add_port(module_id, en_port, ModuleManager::MODULE_INPUT_PORT); + /* Add each input port */ + BasicPort addr_port(std::string(DECODER_ADDRESS_PORT_NAME), addr_size); + module_manager.add_port(module_id, addr_port, ModuleManager::MODULE_INPUT_PORT); + /* Add each output port */ + BasicPort data_port(std::string(DECODER_DATA_OUT_PORT_NAME), data_size); + module_manager.add_port(module_id, data_port, ModuleManager::MODULE_OUTPUT_PORT); + + /* Data port is registered. It should be outputted as + * output reg [lsb:msb] data + */ + module_manager.set_port_is_register(module_id, data_port.get_name(), true); + /* Add data_in port */ + if (true == decoder_lib.use_data_inv_port(decoder)) { + BasicPort data_inv_port(std::string(DECODER_DATA_OUT_INV_PORT_NAME), data_size); + module_manager.add_port(module_id, data_inv_port, ModuleManager::MODULE_OUTPUT_PORT); + } + + return module_id; +} + /*************************************************************************************** * Create a module for a decoder with a given output size * diff --git a/openfpga/src/fabric/build_decoder_modules.h b/openfpga/src/fabric/build_decoder_modules.h index bf18e47d4..13d5091d4 100644 --- a/openfpga/src/fabric/build_decoder_modules.h +++ b/openfpga/src/fabric/build_decoder_modules.h @@ -20,6 +20,14 @@ ModuleId build_frame_memory_decoder_module(ModuleManager& module_manager, const DecoderLibrary& decoder_lib, const DecoderId& decoder); +ModuleId build_bl_memory_decoder_module(ModuleManager& module_manager, + const DecoderLibrary& decoder_lib, + const DecoderId& decoder); + +ModuleId build_wl_memory_decoder_module(ModuleManager& module_manager, + const DecoderLibrary& decoder_lib, + const DecoderId& decoder); + void build_mux_local_decoder_modules(ModuleManager& module_manager, const MuxLibrary& mux_lib, const CircuitLibrary& circuit_lib); diff --git a/openfpga/src/fabric/build_top_module_memory.cpp b/openfpga/src/fabric/build_top_module_memory.cpp index c2a726ea9..278a33f2d 100644 --- a/openfpga/src/fabric/build_top_module_memory.cpp +++ b/openfpga/src/fabric/build_top_module_memory.cpp @@ -16,6 +16,7 @@ #include "memory_utils.h" #include "decoder_library_utils.h" #include "module_manager_utils.h" +#include "build_decoder_modules.h" #include "build_top_module_memory.h" /* begin namespace openfpga */ @@ -461,6 +462,249 @@ void add_top_module_sram_ports(ModuleManager& module_manager, } } +/********************************************************************* + * Top-level function to add nets for memory banks + * - Find the number of BLs and WLs required + * - Create BL and WL decoders, and add them to decoder library + * - Create nets to connect from top-level module inputs to inputs of decoders + * - Create nets to connect from outputs of decoders to BL/WL of configurable children + * + * WL_enable WL address + * | | + * v v + * +-----------------------------------------------+ + * | Word Line Decoder | + * +-----------------------------------------------+ + * +---------+ | | | + * BL | | | | | + * enable ---->| |-----------+--------------+---- ... |------+--> BL[0] + * | | | | | | | | + * | | | v | v | v + * | Bit | | +------+ | +------+ | +------+ + * BL | Line | +-->| SRAM | +-->| SRAM | +->| SRAM | + * address ---->| Decoder | | | [0] | | | [1] | ... | | [i] | + * | | | +------+ | +------+ | +------+ + * | | | | | + * | |-----------+--------------+---- --- | -----+--> BL[1] + * | | | | | | | | + * | | | v | v | v + * | | | +------+ | +------+ | +------+ + * | | +-->| SRAM | | | SRAM | +->| SRAM | + * | | | | [x] | | | [x+1]| ... | | [x+i]| + * | | | +------+ | +------+ | +------+ + * | | | | + * | | | ... ... ... | ... + * | | | | | + * | |-----------+--------------+---- --- | -----+--> BL[y] + * | | | | | | | | + * | | | v | v | v + * | | | +------+ | +------+ | +------+ + * | | +-->| SRAM | +-->| SRAM | +->| SRAM | + * | | | | [y] | | |[y+1] | ... | |[y+i] | + * | | | +------+ | +------+ | +------+ + * BL | | v v v + * data_in ---->| | WL[0] WL[1] WL[i] + * +---------+ + * + **********************************************************************/ +static +void add_top_module_nets_cmos_memory_bank_config_bus(ModuleManager& module_manager, + DecoderLibrary& decoder_lib, + const ModuleId& top_module) { + /* Find Enable port from the top-level module */ + ModulePortId en_port = module_manager.find_module_port(top_module, std::string(DECODER_ENABLE_PORT_NAME)); + BasicPort en_port_info = module_manager.module_port(top_module, en_port); + + /* Find data-in port from the top-level module */ + ModulePortId din_port = module_manager.find_module_port(top_module, std::string(DECODER_DATA_IN_PORT_NAME)); + BasicPort din_port_info = module_manager.module_port(top_module, din_port); + + /* Find BL and WL address port from the top-level module */ + ModulePortId bl_addr_port = module_manager.find_module_port(top_module, std::string(DECODER_BL_ADDRESS_PORT_NAME)); + BasicPort bl_addr_port_info = module_manager.module_port(top_module, bl_addr_port); + + ModulePortId wl_addr_port = module_manager.find_module_port(top_module, std::string(DECODER_WL_ADDRESS_PORT_NAME)); + BasicPort wl_addr_port_info = module_manager.module_port(top_module, wl_addr_port); + + /* Find the number of BLs and WLs required to access each memory bit */ + size_t bl_addr_size = bl_addr_port_info.get_width(); + size_t wl_addr_size = wl_addr_port_info.get_width(); + size_t num_bls = find_memory_decoder_data_size(bl_addr_size); + size_t num_wls = find_memory_decoder_data_size(wl_addr_size); + + /* Add the BL decoder module + * Search the decoder library + * If we find one, we use the module. + * Otherwise, we create one and add it to the decoder library + */ + DecoderId bl_decoder_id = decoder_lib.find_decoder(bl_addr_size, num_bls, + true, true, false); + if (DecoderId::INVALID() == bl_decoder_id) { + bl_decoder_id = decoder_lib.add_decoder(bl_addr_size, num_bls, true, true, false); + } + VTR_ASSERT(DecoderId::INVALID() != bl_decoder_id); + + /* Create a module if not existed yet */ + std::string bl_decoder_module_name = generate_bl_memory_decoder_subckt_name(bl_addr_size, num_bls); + ModuleId bl_decoder_module = module_manager.find_module(bl_decoder_module_name); + if (ModuleId::INVALID() == bl_decoder_module) { + /* BL decoder has the same ports as the frame-based decoders + * We reuse it here + */ + bl_decoder_module = build_bl_memory_decoder_module(module_manager, + decoder_lib, + bl_decoder_id); + } + VTR_ASSERT(ModuleId::INVALID() != bl_decoder_module); + VTR_ASSERT(0 == module_manager.num_instance(top_module, bl_decoder_module)); + module_manager.add_child_module(top_module, bl_decoder_module); + + /* Add the WL decoder module + * Search the decoder library + * If we find one, we use the module. + * Otherwise, we create one and add it to the decoder library + */ + DecoderId wl_decoder_id = decoder_lib.find_decoder(wl_addr_size, num_wls, + true, false, false); + if (DecoderId::INVALID() == wl_decoder_id) { + wl_decoder_id = decoder_lib.add_decoder(wl_addr_size, num_wls, true, false, false); + } + VTR_ASSERT(DecoderId::INVALID() != wl_decoder_id); + + /* Create a module if not existed yet */ + std::string wl_decoder_module_name = generate_bl_memory_decoder_subckt_name(wl_addr_size, num_wls); + ModuleId wl_decoder_module = module_manager.find_module(wl_decoder_module_name); + if (ModuleId::INVALID() == wl_decoder_module) { + /* BL decoder has the same ports as the frame-based decoders + * We reuse it here + */ + wl_decoder_module = build_wl_memory_decoder_module(module_manager, + decoder_lib, + wl_decoder_id); + } + VTR_ASSERT(ModuleId::INVALID() != wl_decoder_module); + VTR_ASSERT(0 == module_manager.num_instance(top_module, wl_decoder_module)); + module_manager.add_child_module(top_module, wl_decoder_module); + + /* Add module nets from the top module to BL decoder's inputs */ + ModulePortId bl_decoder_en_port = module_manager.find_module_port(bl_decoder_module, std::string(DECODER_ENABLE_PORT_NAME)); + BasicPort bl_decoder_en_port_info = module_manager.module_port(bl_decoder_module, bl_decoder_en_port); + + ModulePortId bl_decoder_addr_port = module_manager.find_module_port(bl_decoder_module, std::string(DECODER_ADDRESS_PORT_NAME)); + BasicPort bl_decoder_addr_port_info = module_manager.module_port(bl_decoder_module, bl_decoder_addr_port); + + ModulePortId bl_decoder_din_port = module_manager.find_module_port(bl_decoder_module, std::string(DECODER_DATA_IN_PORT_NAME)); + BasicPort bl_decoder_din_port_info = module_manager.module_port(bl_decoder_module, bl_decoder_din_port); + + /* Top module Enable port -> BL Decoder Enable port */ + add_module_bus_nets(module_manager, + top_module, + top_module, 0, en_port, + bl_decoder_module, 0, bl_decoder_en_port); + + /* Top module Address port -> BL Decoder Address port */ + add_module_bus_nets(module_manager, + top_module, + top_module, 0, bl_addr_port, + bl_decoder_module, 0, bl_decoder_addr_port); + + /* Top module data_in port -> BL Decoder data_in port */ + add_module_bus_nets(module_manager, + top_module, + top_module, 0, din_port, + bl_decoder_module, 0, bl_decoder_din_port); + + /* Add module nets from the top module to WL decoder's inputs */ + ModulePortId wl_decoder_en_port = module_manager.find_module_port(wl_decoder_module, std::string(DECODER_ENABLE_PORT_NAME)); + BasicPort wl_decoder_en_port_info = module_manager.module_port(wl_decoder_module, wl_decoder_en_port); + + ModulePortId wl_decoder_addr_port = module_manager.find_module_port(wl_decoder_module, std::string(DECODER_ADDRESS_PORT_NAME)); + BasicPort wl_decoder_addr_port_info = module_manager.module_port(wl_decoder_module, bl_decoder_addr_port); + + /* Top module Enable port -> WL Decoder Enable port */ + add_module_bus_nets(module_manager, + top_module, + top_module, 0, en_port, + wl_decoder_module, 0, wl_decoder_en_port); + + /* Top module Address port -> WL Decoder Address port */ + add_module_bus_nets(module_manager, + top_module, + top_module, 0, wl_addr_port, + wl_decoder_module, 0, wl_decoder_addr_port); + + /* Add nets from BL data out to each configurable child */ + size_t cur_bl_index = 0; + + ModulePortId bl_decoder_dout_port = module_manager.find_module_port(bl_decoder_module, std::string(DECODER_DATA_OUT_PORT_NAME)); + BasicPort bl_decoder_dout_port_info = module_manager.module_port(bl_decoder_module, bl_decoder_dout_port); + + for (size_t child_id = 0; child_id < module_manager.configurable_children(top_module).size(); ++child_id) { + ModuleId child_module = module_manager.configurable_children(top_module)[child_id]; + size_t child_instance = module_manager.configurable_child_instances(top_module)[child_id]; + + /* Find the BL port */ + ModulePortId child_bl_port = module_manager.find_module_port(child_module, std::string(MEMORY_BL_PORT_NAME)); + BasicPort child_bl_port_info = module_manager.module_port(child_module, child_bl_port); + + for (const size_t& sink_bl_pin : child_bl_port_info.pins()) { + /* Find the BL decoder data index: + * It should be the residual when divided by the number of BLs + */ + size_t bl_pin_id = cur_bl_index / num_bls; + + /* Create net */ + ModuleNetId net = create_module_source_pin_net(module_manager, top_module, + bl_decoder_module, 0, + bl_decoder_dout_port, + bl_decoder_dout_port_info.pins()[bl_pin_id]); + VTR_ASSERT(ModuleNetId::INVALID() != net); + + /* Add net sink */ + module_manager.add_module_net_sink(top_module, net, + child_module, child_instance, child_bl_port, sink_bl_pin); + + /* Increment the BL index */ + cur_bl_index++; + } + } + + /* Add nets from WL data out to each configurable child */ + size_t cur_wl_index = 0; + + ModulePortId wl_decoder_dout_port = module_manager.find_module_port(wl_decoder_module, std::string(DECODER_DATA_OUT_PORT_NAME)); + BasicPort wl_decoder_dout_port_info = module_manager.module_port(wl_decoder_module, wl_decoder_dout_port); + + for (size_t child_id = 0; child_id < module_manager.configurable_children(top_module).size(); ++child_id) { + ModuleId child_module = module_manager.configurable_children(top_module)[child_id]; + size_t child_instance = module_manager.configurable_child_instances(top_module)[child_id]; + + /* Find the WL port */ + ModulePortId child_wl_port = module_manager.find_module_port(child_module, std::string(MEMORY_WL_PORT_NAME)); + BasicPort child_wl_port_info = module_manager.module_port(child_module, child_wl_port); + + for (const size_t& sink_wl_pin : child_wl_port_info.pins()) { + /* Find the BL decoder data index: + * It should be the residual when divided by the number of BLs + */ + size_t wl_pin_id = cur_wl_index % num_wls; + + /* Create net */ + ModuleNetId net = create_module_source_pin_net(module_manager, top_module, + wl_decoder_module, 0, + wl_decoder_dout_port, + wl_decoder_dout_port_info.pins()[wl_pin_id]); + VTR_ASSERT(ModuleNetId::INVALID() != net); + + /* Add net sink */ + module_manager.add_module_net_sink(top_module, net, + child_module, child_instance, child_wl_port, sink_wl_pin); + + /* Increment the WL index */ + cur_wl_index++; + } + } +} /********************************************************************* * Add the port-to-port connection between all the memory modules @@ -524,6 +768,7 @@ void add_top_module_nets_cmos_memory_config_bus(ModuleManager& module_manager, } case CONFIG_MEM_MEMORY_BANK: /* TODO */ + add_top_module_nets_cmos_memory_bank_config_bus(module_manager, decoder_lib, parent_module); break; case CONFIG_MEM_FRAME_BASED: add_module_nets_cmos_memory_frame_config_bus(module_manager, decoder_lib, parent_module); diff --git a/openfpga/src/utils/decoder_library_utils.cpp b/openfpga/src/utils/decoder_library_utils.cpp index 20f7473b2..8fd7e2208 100644 --- a/openfpga/src/utils/decoder_library_utils.cpp +++ b/openfpga/src/utils/decoder_library_utils.cpp @@ -77,6 +77,10 @@ size_t find_memory_decoder_addr_size(const size_t& num_mems) { return find_mux_local_decoder_addr_size((size_t)std::ceil(std::sqrt((float)num_mems))); } +size_t find_memory_decoder_data_size(const size_t& num_addr) { + return (size_t)std::pow(2., num_addr); +} + /*************************************************************************************** * Try to find if the decoder already exists in the library, * If there is no such decoder, add it to the library diff --git a/openfpga/src/utils/decoder_library_utils.h b/openfpga/src/utils/decoder_library_utils.h index e43068f5f..f7f06f733 100644 --- a/openfpga/src/utils/decoder_library_utils.h +++ b/openfpga/src/utils/decoder_library_utils.h @@ -15,6 +15,8 @@ size_t find_mux_local_decoder_addr_size(const size_t& data_size); size_t find_memory_decoder_addr_size(const size_t& num_mems); +size_t find_memory_decoder_data_size(const size_t& num_addr); + DecoderId add_mux_local_decoder_to_library(DecoderLibrary& decoder_lib, const size_t data_size); From 51e1559352c5a64e909ca391622c8550c1e51088 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 30 May 2020 19:12:46 -0600 Subject: [PATCH 611/645] add fabric bitstream support for memory bank configuration protocol --- .../fpga_bitstream/build_fabric_bitstream.cpp | 102 ++++++++++++++++++ .../src/fpga_bitstream/fabric_bitstream.cpp | 26 ++++- .../src/fpga_bitstream/fabric_bitstream.h | 20 +++- 3 files changed, 141 insertions(+), 7 deletions(-) diff --git a/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp b/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp index f281a72dc..1be2025b3 100644 --- a/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp +++ b/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp @@ -15,6 +15,7 @@ #include "openfpga_reserved_words.h" #include "openfpga_naming.h" +#include "decoder_library_utils.h" #include "bitstream_manager_utils.h" #include "build_fabric_bitstream.h" @@ -73,6 +74,90 @@ void rec_build_module_fabric_dependent_chain_bitstream(const BitstreamManager& b } } +/******************************************************************** + * This function aims to build a bitstream for memory-bank protocol + * It will walk through all the configurable children under a module + * in a recursive way, following a Depth-First Search (DFS) strategy + * For each configuration child, we use its instance name as a key to spot the + * configuration bits in bitstream manager. + * Note that it is guarentee that the instance name in module manager is + * consistent with the block names in bitstream manager + * We use this link to reorganize the bitstream in the sequence of memories as we stored + * in the configurable_children() and configurable_child_instances() of each module of module manager + * + * In such configuration organization, each memory cell has an unique index. + * Using this index, we can infer the address codes for both BL and WL decoders. + * Note that, we must get the number of BLs and WLs before using this function! + *******************************************************************/ +static +void rec_build_module_fabric_dependent_memory_bank_bitstream(const BitstreamManager& bitstream_manager, + const ConfigBlockId& parent_block, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const size_t& bl_addr_size, + const size_t& wl_addr_size, + const size_t& num_bls, + const size_t& num_wls, + size_t& cur_mem_index, + FabricBitstream& fabric_bitstream) { + + /* Depth-first search: if we have any children in the parent_block, + * we dive to the next level first! + */ + if (0 < bitstream_manager.block_children(parent_block).size()) { + for (size_t child_id = 0; child_id < module_manager.configurable_children(parent_module).size(); ++child_id) { + ModuleId child_module = module_manager.configurable_children(parent_module)[child_id]; + size_t child_instance = module_manager.configurable_child_instances(parent_module)[child_id]; + /* Get the instance name and ensure it is not empty */ + std::string instance_name = module_manager.instance_name(parent_module, child_module, child_instance); + + /* Find the child block that matches the instance name! */ + ConfigBlockId child_block = bitstream_manager.find_child_block(parent_block, instance_name); + /* We must have one valid block id! */ + if (true != bitstream_manager.valid_block_id(child_block)) + VTR_ASSERT(true == bitstream_manager.valid_block_id(child_block)); + + /* Go recursively */ + rec_build_module_fabric_dependent_memory_bank_bitstream(bitstream_manager, child_block, + module_manager, child_module, + bl_addr_size, wl_addr_size, + num_bls, num_wls, + cur_mem_index, + fabric_bitstream); + } + /* Ensure that there should be no configuration bits in the parent block */ + VTR_ASSERT(0 == bitstream_manager.block_bits(parent_block).size()); + } + + /* Note that, reach here, it means that this is a leaf node. + * We add the configuration bits to the fabric_bitstream, + * And then, we can return + */ + for (const ConfigBitId& config_bit : bitstream_manager.block_bits(parent_block)) { + FabricBitId fabric_bit = fabric_bitstream.add_bit(config_bit); + + /* Find BL address */ + size_t cur_bl_index = cur_mem_index / num_bls; + std::vector bl_addr_bits_vec = itobin_vec(cur_bl_index, bl_addr_size); + + /* Find WL address */ + size_t cur_wl_index = cur_mem_index % num_wls; + std::vector wl_addr_bits_vec = itobin_vec(cur_wl_index, wl_addr_size); + + /* Set BL address */ + fabric_bitstream.set_bit_bl_address(fabric_bit, bl_addr_bits_vec); + + /* Set WL address */ + fabric_bitstream.set_bit_wl_address(fabric_bit, wl_addr_bits_vec); + + /* Set data input */ + fabric_bitstream.set_bit_din(fabric_bit, bitstream_manager.bit_value(config_bit)); + + /* Increase the memory index */ + cur_mem_index++; + } +} + /******************************************************************** * This function aims to build a bitstream for frame-based configuration protocol * It will walk through all the configurable children under a module @@ -287,6 +372,23 @@ void build_module_fabric_dependent_bitstream(const ConfigProtocol& config_protoc break; } case CONFIG_MEM_MEMORY_BANK: { + size_t cur_mem_index = 0; + /* Find BL address port size */ + ModulePortId bl_addr_port = module_manager.find_module_port(top_module, std::string(MEMORY_BL_PORT_NAME)); + BasicPort bl_addr_port_info = module_manager.module_port(top_module, bl_addr_port); + size_t num_bls = find_memory_decoder_data_size(bl_addr_port_info.get_width()); + + /* Find WL address port size */ + ModulePortId wl_addr_port = module_manager.find_module_port(top_module, std::string(MEMORY_WL_PORT_NAME)); + BasicPort wl_addr_port_info = module_manager.module_port(top_module, wl_addr_port); + size_t num_wls = find_memory_decoder_data_size(wl_addr_port_info.get_width()); + + rec_build_module_fabric_dependent_memory_bank_bitstream(bitstream_manager, top_block, + module_manager, top_module, + bl_addr_port_info.get_width(), + wl_addr_port_info.get_width(), + num_bls, num_wls, + cur_mem_index, fabric_bitstream); break; } case CONFIG_MEM_FRAME_BASED: { diff --git a/openfpga/src/fpga_bitstream/fabric_bitstream.cpp b/openfpga/src/fpga_bitstream/fabric_bitstream.cpp index 7306eba01..1e318a530 100644 --- a/openfpga/src/fpga_bitstream/fabric_bitstream.cpp +++ b/openfpga/src/fpga_bitstream/fabric_bitstream.cpp @@ -31,7 +31,18 @@ std::vector FabricBitstream::bit_address(const FabricBitId& bit_id) cons /* Ensure a valid id */ VTR_ASSERT(true == valid_bit_id(bit_id)); - return bit_addresses_[bit_id]; + return bit_addresses_[bit_id][0]; +} + +std::vector FabricBitstream::bit_bl_address(const FabricBitId& bit_id) const { + return bit_address(bit_id); +} + +std::vector FabricBitstream::bit_wl_address(const FabricBitId& bit_id) const { + /* Ensure a valid id */ + VTR_ASSERT(true == valid_bit_id(bit_id)); + + return bit_addresses_[bit_id][1]; } bool FabricBitstream::bit_din(const FabricBitId& bit_id) const { @@ -58,7 +69,18 @@ FabricBitId FabricBitstream::add_bit(const ConfigBitId& config_bit_id) { void FabricBitstream::set_bit_address(const FabricBitId& bit_id, const std::vector& address) { VTR_ASSERT(true == valid_bit_id(bit_id)); - bit_addresses_[bit_id] = address; + bit_addresses_[bit_id][0] = address; +} + +void FabricBitstream::set_bit_bl_address(const FabricBitId& bit_id, + const std::vector& address) { + set_bit_address(bit_id, address); +} + +void FabricBitstream::set_bit_wl_address(const FabricBitId& bit_id, + const std::vector& address) { + VTR_ASSERT(true == valid_bit_id(bit_id)); + bit_addresses_[bit_id][1] = address; } void FabricBitstream::set_bit_din(const FabricBitId& bit_id, diff --git a/openfpga/src/fpga_bitstream/fabric_bitstream.h b/openfpga/src/fpga_bitstream/fabric_bitstream.h index 12d1c7885..519330dae 100644 --- a/openfpga/src/fpga_bitstream/fabric_bitstream.h +++ b/openfpga/src/fpga_bitstream/fabric_bitstream.h @@ -13,11 +13,11 @@ * By using the link between ArchBitstreamManager and FabricBitstream, * we can build a sequence of configuration bits to fit different configuration protocols. * - * +----------------------+ +--------------------------+ - * | | ConfigBitId | | + * +----------------------+ +-------------------+ + * | | ConfigBitId | | * | ArchBitstreamManager |---------------->| FabricBitstream | - * | | | | - * +----------------------+ +--------------------------+ + * | | | | + * +----------------------+ +-------------------+ * * Restrictions: * 1. Each block inside BitstreamManager should have only 1 parent block @@ -53,6 +53,8 @@ class FabricBitstream { /* Find the address of bitstream */ std::vector bit_address(const FabricBitId& bit_id) const; + std::vector bit_bl_address(const FabricBitId& bit_id) const; + std::vector bit_wl_address(const FabricBitId& bit_id) const; /* Find the data-in of bitstream */ bool bit_din(const FabricBitId& bit_id) const; @@ -64,6 +66,12 @@ class FabricBitstream { void set_bit_address(const FabricBitId& bit_id, const std::vector& address); + void set_bit_bl_address(const FabricBitId& bit_id, + const std::vector& address); + + void set_bit_wl_address(const FabricBitId& bit_id, + const std::vector& address); + void set_bit_din(const FabricBitId& bit_id, const bool& din); @@ -83,8 +91,10 @@ class FabricBitstream { /* Address bits: this is designed for memory decoders * Here we store the binary format of the address, which can be loaded * to the configuration protocol directly + * + * We use a 2-element array, as we may have a BL address and a WL address */ - vtr::vector> bit_addresses_; + vtr::vector, 2>> bit_addresses_; /* Data input (Din) bits: this is designed for memory decoders */ vtr::vector bit_dins_; From e14c39e14cc2b8329498866b8b0f6d7d21faed80 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 30 May 2020 20:04:44 -0600 Subject: [PATCH 612/645] update Verilog full testbench generation to support memory bank configuration protocol --- .../fpga_verilog/verilog_top_testbench.cpp | 264 +++++++++++++++++- 1 file changed, 257 insertions(+), 7 deletions(-) diff --git a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp index 68d1e3dba..e741e16ee 100644 --- a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp +++ b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp @@ -103,6 +103,51 @@ void print_verilog_top_testbench_config_chain_port(std::fstream& fp) { fp << generate_verilog_port(VERILOG_PORT_WIRE, config_chain_tail_port) << ";" << std::endl; } +/******************************************************************** + * Print local wires for memory bank configuration protocols + *******************************************************************/ +static +void print_verilog_top_testbench_memory_bank_port(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module) { + /* Validate the file stream */ + valid_file_stream(fp); + + /* Print the address port for the Bit-Line decoder here */ + print_verilog_comment(fp, std::string("---- Address port for Bit-Line decoder -----")); + ModulePortId bl_addr_port_id = module_manager.find_module_port(top_module, + std::string(MEMORY_BL_PORT_NAME)); + BasicPort bl_addr_port = module_manager.module_port(top_module, bl_addr_port_id); + + fp << generate_verilog_port(VERILOG_PORT_REG, bl_addr_port) << ";" << std::endl; + + /* Print the address port for the Word-Line decoder here */ + print_verilog_comment(fp, std::string("---- Address port for Word-Line decoder -----")); + ModulePortId wl_addr_port_id = module_manager.find_module_port(top_module, + std::string(MEMORY_WL_PORT_NAME)); + BasicPort wl_addr_port = module_manager.module_port(top_module, wl_addr_port_id); + + fp << generate_verilog_port(VERILOG_PORT_REG, wl_addr_port) << ";" << std::endl; + + /* Print the data-input port for the frame-based decoder here */ + print_verilog_comment(fp, std::string("---- Data input port for frame-based decoder -----")); + ModulePortId din_port_id = module_manager.find_module_port(top_module, + std::string(DECODER_DATA_IN_PORT_NAME)); + BasicPort din_port = module_manager.module_port(top_module, din_port_id); + fp << generate_verilog_port(VERILOG_PORT_REG, din_port) << ";" << std::endl; + + /* Wire the INVERTED programming clock to the enable signal !!! */ + print_verilog_comment(fp, std::string("---- Wire enable port of frame-based decoder to inverted programming clock -----")); + ModulePortId en_port_id = module_manager.find_module_port(top_module, + std::string(DECODER_ENABLE_PORT_NAME)); + BasicPort en_port = module_manager.module_port(top_module, en_port_id); + BasicPort prog_clock_port(std::string(TOP_TB_PROG_CLOCK_PORT_NAME), 1); + + fp << generate_verilog_port(VERILOG_PORT_WIRE, en_port) << ";" << std::endl; + print_verilog_wire_connection(fp, en_port, prog_clock_port, true); +} + + /******************************************************************** * Print local wires for frame-based decoder protocols *******************************************************************/ @@ -155,7 +200,7 @@ void print_verilog_top_testbench_config_protocol_port(std::fstream& fp, print_verilog_top_testbench_config_chain_port(fp); break; case CONFIG_MEM_MEMORY_BANK: - /* TODO */ + print_verilog_top_testbench_memory_bank_port(fp, module_manager, top_module); break; case CONFIG_MEM_FRAME_BASED: print_verilog_top_testbench_frame_decoder_port(fp, module_manager, top_module); @@ -528,9 +573,9 @@ size_t calculate_num_config_clock_cycles(const e_config_protocol_type& sram_orgz num_config_clock_cycles = 2; break; case CONFIG_MEM_SCAN_CHAIN: - case CONFIG_MEM_MEMORY_BANK: - /* TODO */ + /* Fast configuraiton is not applicable to configuration chain protocol*/ break; + case CONFIG_MEM_MEMORY_BANK: case CONFIG_MEM_FRAME_BASED: { /* For fast configuration, we will skip all the zero data points */ if (true == fast_configuration) { @@ -644,6 +689,89 @@ void print_verilog_top_testbench_load_bitstream_task_configuration_chain(std::fs fp << std::endl; } +/******************************************************************** + * Print tasks (processes) in Verilog format, + * which is very useful in generating stimuli for each clock cycle + * This function is tuned for memory bank manipulation: + * During each programming cycle, we feed + * - an address to the BL address port of top module + * - an address to the WL address port of top module + * - a data input to the din port of top module + *******************************************************************/ +static +void print_verilog_top_testbench_load_bitstream_task_memory_bank(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module) { + + /* Validate the file stream */ + valid_file_stream(fp); + + ModulePortId en_port_id = module_manager.find_module_port(top_module, + std::string(DECODER_ENABLE_PORT_NAME)); + BasicPort en_port = module_manager.module_port(top_module, en_port_id); + + ModulePortId bl_addr_port_id = module_manager.find_module_port(top_module, + std::string(MEMORY_BL_PORT_NAME)); + BasicPort bl_addr_port = module_manager.module_port(top_module, bl_addr_port_id); + BasicPort bl_addr_value = bl_addr_port; + bl_addr_value.set_name(std::string(MEMORY_BL_PORT_NAME) + std::string("_val")); + + ModulePortId wl_addr_port_id = module_manager.find_module_port(top_module, + std::string(MEMORY_WL_PORT_NAME)); + BasicPort wl_addr_port = module_manager.module_port(top_module, wl_addr_port_id); + BasicPort wl_addr_value = wl_addr_port; + wl_addr_value.set_name(std::string(MEMORY_WL_PORT_NAME) + std::string("_val")); + + ModulePortId din_port_id = module_manager.find_module_port(top_module, + std::string(DECODER_DATA_IN_PORT_NAME)); + BasicPort din_port = module_manager.module_port(top_module, din_port_id); + BasicPort din_value = din_port; + din_value.set_name(std::string(DECODER_DATA_IN_PORT_NAME) + std::string("_val")); + + /* Add an empty line as splitter */ + fp << std::endl; + + /* Feed the address and data input at each falling edge of programming clock + * As the enable signal is wired to the programming clock, we should synchronize + * address and data with the enable signal + */ + print_verilog_comment(fp, std::string("----- Task: assign BL and WL address, and data values at rising edge of enable signal -----")); + fp << "task " << std::string(TOP_TESTBENCH_PROG_TASK_NAME) << ";" << std::endl; + fp << generate_verilog_port(VERILOG_PORT_INPUT, bl_addr_value) << ";" << std::endl; + fp << generate_verilog_port(VERILOG_PORT_INPUT, wl_addr_value) << ";" << std::endl; + fp << generate_verilog_port(VERILOG_PORT_INPUT, din_value) << ";" << std::endl; + fp << "\tbegin" << std::endl; + fp << "\t\t@(posedge " << generate_verilog_port(VERILOG_PORT_CONKT, en_port) << ");" << std::endl; + + fp << "\t\t\t"; + fp << generate_verilog_port(VERILOG_PORT_CONKT, bl_addr_port); + fp << " = "; + fp << generate_verilog_port(VERILOG_PORT_CONKT, bl_addr_value); + fp << ";" << std::endl; + fp << std::endl; + + fp << "\t\t\t"; + fp << generate_verilog_port(VERILOG_PORT_CONKT, wl_addr_port); + fp << " = "; + fp << generate_verilog_port(VERILOG_PORT_CONKT, wl_addr_value); + fp << ";" << std::endl; + fp << std::endl; + + fp << "\t\t\t"; + fp << generate_verilog_port(VERILOG_PORT_CONKT, din_port); + fp << " = "; + fp << generate_verilog_port(VERILOG_PORT_CONKT, din_value); + fp << ";" << std::endl; + fp << std::endl; + + fp << "\tend" << std::endl; + fp << "endtask" << std::endl; + + /* Add an empty line as splitter */ + fp << std::endl; +} + + /******************************************************************** * Print tasks (processes) in Verilog format, * which is very useful in generating stimuli for each clock cycle @@ -727,9 +855,9 @@ void print_verilog_top_testbench_load_bitstream_task(std::fstream& fp, print_verilog_top_testbench_load_bitstream_task_configuration_chain(fp); break; case CONFIG_MEM_MEMORY_BANK: - /* TODO: - dump_verilog_top_testbench_stimuli_serial_version_tasks_memory_bank(cur_sram_orgz_info, fp); - */ + print_verilog_top_testbench_load_bitstream_task_memory_bank(fp, + module_manager, + top_module); break; case CONFIG_MEM_FRAME_BASED: print_verilog_top_testbench_load_bitstream_task_frame_decoder(fp, @@ -1028,6 +1156,126 @@ void print_verilog_top_testbench_configuration_chain_bitstream(std::fstream& fp, print_verilog_comment(fp, "----- End bitstream loading during configuration phase -----"); } +/******************************************************************** + * Print stimulus for a FPGA fabric with a memory bank configuration protocol + * where configuration bits are programming in serial (one by one) + * + * We will use the programming task function created before + *******************************************************************/ +static +void print_verilog_top_testbench_memory_bank_bitstream(std::fstream& fp, + const bool& fast_configuration, + const ModuleManager& module_manager, + const ModuleId& top_module, + const FabricBitstream& fabric_bitstream) { + /* Validate the file stream */ + valid_file_stream(fp); + + /* Feed addresss and data input pair one by one + * Note: the first cycle is reserved for programming reset + * We should give dummy values + */ + ModulePortId bl_addr_port_id = module_manager.find_module_port(top_module, + std::string(MEMORY_BL_PORT_NAME)); + BasicPort bl_addr_port = module_manager.module_port(top_module, bl_addr_port_id); + std::vector initial_bl_addr_values(bl_addr_port.get_width(), 0); + + ModulePortId wl_addr_port_id = module_manager.find_module_port(top_module, + std::string(MEMORY_WL_PORT_NAME)); + BasicPort wl_addr_port = module_manager.module_port(top_module, wl_addr_port_id); + std::vector initial_wl_addr_values(wl_addr_port.get_width(), 0); + + ModulePortId din_port_id = module_manager.find_module_port(top_module, + std::string(DECODER_DATA_IN_PORT_NAME)); + BasicPort din_port = module_manager.module_port(top_module, din_port_id); + std::vector initial_din_values(din_port.get_width(), 0); + + print_verilog_comment(fp, "----- Begin bitstream loading during configuration phase -----"); + fp << "initial" << std::endl; + fp << "\tbegin" << std::endl; + print_verilog_comment(fp, "----- Address port default input -----"); + fp << "\t\t"; + fp << generate_verilog_port_constant_values(bl_addr_port, initial_bl_addr_values); + fp << generate_verilog_port_constant_values(wl_addr_port, initial_wl_addr_values); + fp << ";"; + + print_verilog_comment(fp, "----- Data-input port default input -----"); + fp << "\t\t"; + fp << generate_verilog_port_constant_values(din_port, initial_din_values); + fp << ";"; + + fp << std::endl; + + /* Attention: the configuration chain protcol requires the last configuration bit is fed first + * We will visit the fabric bitstream in a reverse way + */ + for (const FabricBitId& bit_id : fabric_bitstream.bits()) { + /* When fast configuration is enabled, we skip zero data_in values */ + if ((true == fast_configuration) + && (false == fabric_bitstream.bit_din(bit_id))) { + continue; + } + + fp << "\t\t" << std::string(TOP_TESTBENCH_PROG_TASK_NAME); + fp << "(" << bl_addr_port.get_width() << "'b"; + VTR_ASSERT(bl_addr_port.get_width() == fabric_bitstream.bit_bl_address(bit_id).size()); + for (const size_t& addr_bit : fabric_bitstream.bit_bl_address(bit_id)) { + fp << addr_bit; + } + + fp << ", "; + fp << wl_addr_port.get_width() << "'b"; + VTR_ASSERT(wl_addr_port.get_width() == fabric_bitstream.bit_wl_address(bit_id).size()); + for (const size_t& addr_bit : fabric_bitstream.bit_wl_address(bit_id)) { + fp << addr_bit; + } + + fp << ", "; + fp <<"1'b"; + if (true == fabric_bitstream.bit_din(bit_id)) { + fp << "1"; + } else { + VTR_ASSERT(false == fabric_bitstream.bit_din(bit_id)); + fp << "0"; + } + fp << ");" << std::endl; + } + + /* Disable the address and din */ + fp << "\t\t" << std::string(TOP_TESTBENCH_PROG_TASK_NAME); + fp << "(" << bl_addr_port.get_width() << "'b"; + std::vector all_zero_bl_addr(bl_addr_port.get_width(), 0); + for (const size_t& addr_bit : all_zero_bl_addr) { + fp << addr_bit; + } + + fp << ", "; + fp << wl_addr_port.get_width() << "'b"; + std::vector all_zero_wl_addr(wl_addr_port.get_width(), 0); + for (const size_t& addr_bit : all_zero_wl_addr) { + fp << addr_bit; + } + + fp << ", "; + fp <<"1'b0"; + fp << ");" << std::endl; + + /* Raise the flag of configuration done when bitstream loading is complete */ + BasicPort prog_clock_port(std::string(TOP_TB_PROG_CLOCK_PORT_NAME), 1); + fp << "\t\t@(negedge " << generate_verilog_port(VERILOG_PORT_CONKT, prog_clock_port) << ");" << std::endl; + + BasicPort config_done_port(std::string(TOP_TB_CONFIG_DONE_PORT_NAME), 1); + fp << "\t\t\t"; + fp << generate_verilog_port(VERILOG_PORT_CONKT, config_done_port); + fp << " <= "; + std::vector config_done_enable_values(config_done_port.get_width(), 1); + fp << generate_verilog_constant_values(config_done_enable_values); + fp << ";" << std::endl; + + fp << "\tend" << std::endl; + print_verilog_comment(fp, "----- End bitstream loading during configuration phase -----"); +} + /******************************************************************** * Print stimulus for a FPGA fabric with a frame-based configuration protocol * where configuration bits are programming in serial (one by one) @@ -1151,7 +1399,9 @@ void print_verilog_top_testbench_bitstream(std::fstream& fp, print_verilog_top_testbench_configuration_chain_bitstream(fp, bitstream_manager, fabric_bitstream); break; case CONFIG_MEM_MEMORY_BANK: - /* TODO */ + print_verilog_top_testbench_memory_bank_bitstream(fp, fast_configuration, + module_manager, top_module, + fabric_bitstream); break; case CONFIG_MEM_FRAME_BASED: print_verilog_top_testbench_frame_decoder_bitstream(fp, fast_configuration, From a1ec6833c29b5c07bcce3522599acb3025e8dad1 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 30 May 2020 20:07:06 -0600 Subject: [PATCH 613/645] add memory bank example arch xml --- .../k4_N4_40nm_bank_openfpga.xml | 229 ++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 openfpga_flow/openfpga_arch/k4_N4_40nm_bank_openfpga.xml diff --git a/openfpga_flow/openfpga_arch/k4_N4_40nm_bank_openfpga.xml b/openfpga_flow/openfpga_arch/k4_N4_40nm_bank_openfpga.xml new file mode 100644 index 000000000..7de3ec8c4 --- /dev/null +++ b/openfpga_flow/openfpga_arch/k4_N4_40nm_bank_openfpga.xml @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + 10e-12 + + + 10e-12 + + + + + + + + + + + + 10e-12 5e-12 5e-12 + + + 10e-12 5e-12 5e-12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 0bee70bee6ee2997551a36f5294bd9c50e4776b5 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 30 May 2020 20:53:19 -0600 Subject: [PATCH 614/645] finish memory bank configuration protocol support. --- openfpga/src/base/openfpga_naming.cpp | 26 +--- openfpga/src/base/openfpga_naming.h | 11 +- openfpga/src/fabric/build_decoder_modules.cpp | 6 +- openfpga/src/fabric/build_memory_modules.cpp | 6 +- .../src/fabric/build_top_module_memory.cpp | 4 +- .../fpga_bitstream/build_fabric_bitstream.cpp | 4 +- .../src/fpga_verilog/verilog_decoders.cpp | 134 +++++++++++++++++- .../fpga_verilog/verilog_top_testbench.cpp | 17 ++- openfpga/src/utils/module_manager_utils.cpp | 6 +- 9 files changed, 166 insertions(+), 48 deletions(-) diff --git a/openfpga/src/base/openfpga_naming.cpp b/openfpga/src/base/openfpga_naming.cpp index 1c792d26d..44eb2c8cd 100644 --- a/openfpga/src/base/openfpga_naming.cpp +++ b/openfpga/src/base/openfpga_naming.cpp @@ -146,23 +146,9 @@ std::string generate_mux_local_decoder_subckt_name(const size_t& addr_size, * Generate the module name of a decoder * for frame-based memories ***********************************************/ -std::string generate_frame_memory_decoder_subckt_name(const size_t& addr_size, - const size_t& data_size) { - std::string subckt_name = "frame_decoder"; - subckt_name += std::to_string(addr_size); - subckt_name += "to"; - subckt_name += std::to_string(data_size); - - return subckt_name; -} - -/************************************************ - * Generate the module name of a bit-line decoder - * for memories - ***********************************************/ -std::string generate_bl_memory_decoder_subckt_name(const size_t& addr_size, - const size_t& data_size) { - std::string subckt_name = "bl_decoder"; +std::string generate_memory_decoder_subckt_name(const size_t& addr_size, + const size_t& data_size) { + std::string subckt_name = "decoder"; subckt_name += std::to_string(addr_size); subckt_name += "to"; subckt_name += std::to_string(data_size); @@ -174,9 +160,9 @@ std::string generate_bl_memory_decoder_subckt_name(const size_t& addr_size, * Generate the module name of a word-line decoder * for memories ***********************************************/ -std::string generate_wl_memory_decoder_subckt_name(const size_t& addr_size, - const size_t& data_size) { - std::string subckt_name = "wl_decoder"; +std::string generate_memory_decoder_with_data_in_subckt_name(const size_t& addr_size, + const size_t& data_size) { + std::string subckt_name = "decoder_with_data_in_"; subckt_name += std::to_string(addr_size); subckt_name += "to"; subckt_name += std::to_string(data_size); diff --git a/openfpga/src/base/openfpga_naming.h b/openfpga/src/base/openfpga_naming.h index 534bf2a78..3634be27a 100644 --- a/openfpga/src/base/openfpga_naming.h +++ b/openfpga/src/base/openfpga_naming.h @@ -50,14 +50,11 @@ std::string generate_mux_branch_subckt_name(const CircuitLibrary& circuit_lib, std::string generate_mux_local_decoder_subckt_name(const size_t& addr_size, const size_t& data_size); -std::string generate_frame_memory_decoder_subckt_name(const size_t& addr_size, - const size_t& data_size); +std::string generate_memory_decoder_subckt_name(const size_t& addr_size, + const size_t& data_size); -std::string generate_bl_memory_decoder_subckt_name(const size_t& addr_size, - const size_t& data_size); - -std::string generate_wl_memory_decoder_subckt_name(const size_t& addr_size, - const size_t& data_size); +std::string generate_memory_decoder_with_data_in_subckt_name(const size_t& addr_size, + const size_t& data_size); std::string generate_segment_wire_subckt_name(const std::string& wire_model_name, const size_t& segment_id); diff --git a/openfpga/src/fabric/build_decoder_modules.cpp b/openfpga/src/fabric/build_decoder_modules.cpp index fb8a29e5c..eecb31344 100644 --- a/openfpga/src/fabric/build_decoder_modules.cpp +++ b/openfpga/src/fabric/build_decoder_modules.cpp @@ -45,7 +45,7 @@ ModuleId build_frame_memory_decoder_module(ModuleManager& module_manager, size_t data_size = decoder_lib.data_size(decoder); /* Create a name for the local encoder */ - std::string module_name = generate_frame_memory_decoder_subckt_name(addr_size, data_size); + std::string module_name = generate_memory_decoder_subckt_name(addr_size, data_size); /* Create a Verilog Module based on the circuit model, and add to module manager */ ModuleId module_id = module_manager.add_module(module_name); @@ -100,7 +100,7 @@ ModuleId build_bl_memory_decoder_module(ModuleManager& module_manager, size_t data_size = decoder_lib.data_size(decoder); /* Create a name for the local encoder */ - std::string module_name = generate_bl_memory_decoder_subckt_name(addr_size, data_size); + std::string module_name = generate_memory_decoder_with_data_in_subckt_name(addr_size, data_size); /* Create a Verilog Module based on the circuit model, and add to module manager */ ModuleId module_id = module_manager.add_module(module_name); @@ -158,7 +158,7 @@ ModuleId build_wl_memory_decoder_module(ModuleManager& module_manager, size_t data_size = decoder_lib.data_size(decoder); /* Create a name for the local encoder */ - std::string module_name = generate_wl_memory_decoder_subckt_name(addr_size, data_size); + std::string module_name = generate_memory_decoder_subckt_name(addr_size, data_size); /* Create a Verilog Module based on the circuit model, and add to module manager */ ModuleId module_id = module_manager.add_module(module_name); diff --git a/openfpga/src/fabric/build_memory_modules.cpp b/openfpga/src/fabric/build_memory_modules.cpp index 72d1315ad..e3c8a0c03 100644 --- a/openfpga/src/fabric/build_memory_modules.cpp +++ b/openfpga/src/fabric/build_memory_modules.cpp @@ -554,14 +554,14 @@ void build_frame_memory_module(ModuleManager& module_manager, * If we find one, we use the module. * Otherwise, we create one and add it to the decoder library */ - DecoderId decoder_id = frame_decoder_lib.find_decoder(addr_size, data_size, true, true, use_data_inv); + DecoderId decoder_id = frame_decoder_lib.find_decoder(addr_size, data_size, true, false, use_data_inv); if (DecoderId::INVALID() == decoder_id) { - decoder_id = frame_decoder_lib.add_decoder(addr_size, data_size, true, true, use_data_inv); + decoder_id = frame_decoder_lib.add_decoder(addr_size, data_size, true, false, use_data_inv); } VTR_ASSERT(DecoderId::INVALID() != decoder_id); /* Create a module if not existed yet */ - std::string decoder_module_name = generate_frame_memory_decoder_subckt_name(addr_size, data_size); + std::string decoder_module_name = generate_memory_decoder_subckt_name(addr_size, data_size); ModuleId decoder_module = module_manager.find_module(decoder_module_name); if (ModuleId::INVALID() == decoder_module) { decoder_module = build_frame_memory_decoder_module(module_manager, diff --git a/openfpga/src/fabric/build_top_module_memory.cpp b/openfpga/src/fabric/build_top_module_memory.cpp index 278a33f2d..0de55ca4e 100644 --- a/openfpga/src/fabric/build_top_module_memory.cpp +++ b/openfpga/src/fabric/build_top_module_memory.cpp @@ -545,7 +545,7 @@ void add_top_module_nets_cmos_memory_bank_config_bus(ModuleManager& module_manag VTR_ASSERT(DecoderId::INVALID() != bl_decoder_id); /* Create a module if not existed yet */ - std::string bl_decoder_module_name = generate_bl_memory_decoder_subckt_name(bl_addr_size, num_bls); + std::string bl_decoder_module_name = generate_memory_decoder_with_data_in_subckt_name(bl_addr_size, num_bls); ModuleId bl_decoder_module = module_manager.find_module(bl_decoder_module_name); if (ModuleId::INVALID() == bl_decoder_module) { /* BL decoder has the same ports as the frame-based decoders @@ -572,7 +572,7 @@ void add_top_module_nets_cmos_memory_bank_config_bus(ModuleManager& module_manag VTR_ASSERT(DecoderId::INVALID() != wl_decoder_id); /* Create a module if not existed yet */ - std::string wl_decoder_module_name = generate_bl_memory_decoder_subckt_name(wl_addr_size, num_wls); + std::string wl_decoder_module_name = generate_memory_decoder_subckt_name(wl_addr_size, num_wls); ModuleId wl_decoder_module = module_manager.find_module(wl_decoder_module_name); if (ModuleId::INVALID() == wl_decoder_module) { /* BL decoder has the same ports as the frame-based decoders diff --git a/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp b/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp index 1be2025b3..a9c5b6ec7 100644 --- a/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp +++ b/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp @@ -374,12 +374,12 @@ void build_module_fabric_dependent_bitstream(const ConfigProtocol& config_protoc case CONFIG_MEM_MEMORY_BANK: { size_t cur_mem_index = 0; /* Find BL address port size */ - ModulePortId bl_addr_port = module_manager.find_module_port(top_module, std::string(MEMORY_BL_PORT_NAME)); + ModulePortId bl_addr_port = module_manager.find_module_port(top_module, std::string(DECODER_BL_ADDRESS_PORT_NAME)); BasicPort bl_addr_port_info = module_manager.module_port(top_module, bl_addr_port); size_t num_bls = find_memory_decoder_data_size(bl_addr_port_info.get_width()); /* Find WL address port size */ - ModulePortId wl_addr_port = module_manager.find_module_port(top_module, std::string(MEMORY_WL_PORT_NAME)); + ModulePortId wl_addr_port = module_manager.find_module_port(top_module, std::string(DECODER_WL_ADDRESS_PORT_NAME)); BasicPort wl_addr_port_info = module_manager.module_port(top_module, wl_addr_port); size_t num_wls = find_memory_decoder_data_size(wl_addr_port_info.get_width()); diff --git a/openfpga/src/fpga_verilog/verilog_decoders.cpp b/openfpga/src/fpga_verilog/verilog_decoders.cpp index 64568515c..6e6025ea6 100644 --- a/openfpga/src/fpga_verilog/verilog_decoders.cpp +++ b/openfpga/src/fpga_verilog/verilog_decoders.cpp @@ -262,7 +262,7 @@ void print_verilog_arch_decoder_module(std::fstream& fp, VTR_ASSERT(true == valid_file_stream(fp)); /* Create a name for the decoder */ - std::string module_name = generate_frame_memory_decoder_subckt_name(addr_size, data_size); + std::string module_name = generate_memory_decoder_subckt_name(addr_size, data_size); /* Create a Verilog Module based on the circuit model, and add to module manager */ ModuleId module_id = module_manager.find_module(module_name); @@ -377,6 +377,132 @@ void print_verilog_arch_decoder_module(std::fstream& fp, print_verilog_module_end(fp, module_name); } +/*************************************************************************************** + * Create a Verilog module for a decoder with data_in used as a configuration protocol + * in FPGA architecture + * + * Address + * | | ... | + * v v v + * +-----------+ + * Enable->/ \<-data_in + * / Decoder \ + * +-----------------+ + * | | | ... | | | + * v v v v v v + * Data output + * + * The outputs are assumes to be one-hot codes (at most only one '1' exist) + * Only the data output at the address bit will show data_in + * + * The decoder has an enable signal which is active at logic '1'. + * When activated, the decoder will output decoding results to the data output port + * Otherwise, the data output port will be always all-zero + ***************************************************************************************/ +static +void print_verilog_arch_decoder_with_data_in_module(std::fstream& fp, + const ModuleManager& module_manager, + const DecoderLibrary& decoder_lib, + const DecoderId& decoder) { + /* Get the number of inputs */ + size_t addr_size = decoder_lib.addr_size(decoder); + size_t data_size = decoder_lib.data_size(decoder); + VTR_ASSERT(true == decoder_lib.use_data_in(decoder)); + + /* Validate the FILE handler */ + VTR_ASSERT(true == valid_file_stream(fp)); + + /* Create a name for the decoder */ + std::string module_name = generate_memory_decoder_with_data_in_subckt_name(addr_size, data_size); + + /* Create a Verilog Module based on the circuit model, and add to module manager */ + ModuleId module_id = module_manager.find_module(module_name); + VTR_ASSERT(true == module_manager.valid_module_id(module_id)); + /* Find module ports */ + /* Enable port */ + ModulePortId enable_port_id = module_manager.find_module_port(module_id, std::string(DECODER_ENABLE_PORT_NAME)); + BasicPort enable_port = module_manager.module_port(module_id, enable_port_id); + /* Address port */ + ModulePortId addr_port_id = module_manager.find_module_port(module_id, std::string(DECODER_ADDRESS_PORT_NAME)); + BasicPort addr_port = module_manager.module_port(module_id, addr_port_id); + /* Find data-in port*/ + ModulePortId din_port_id = module_manager.find_module_port(module_id, std::string(DECODER_DATA_IN_PORT_NAME)); + BasicPort din_port = module_manager.module_port(module_id, din_port_id); + /* Find each output port */ + ModulePortId data_port_id = module_manager.find_module_port(module_id, std::string(DECODER_DATA_OUT_PORT_NAME)); + BasicPort data_port = module_manager.module_port(module_id, data_port_id); + /* Data port is registered. It should be outputted as + * output reg [lsb:msb] data + */ + BasicPort data_inv_port(std::string(DECODER_DATA_OUT_INV_PORT_NAME), data_size); + if (true == decoder_lib.use_data_inv_port(decoder)) { + ModulePortId data_inv_port_id = module_manager.find_module_port(module_id, std::string(DECODER_DATA_OUT_INV_PORT_NAME)); + data_inv_port = module_manager.module_port(module_id, data_inv_port_id); + } + + /* dump module definition + ports */ + print_verilog_module_declaration(fp, module_manager, module_id); + /* Finish dumping ports */ + + print_verilog_comment(fp, std::string("----- BEGIN Verilog codes for Decoder convert " + std::to_string(addr_size) + "-bit addr to " + std::to_string(data_size) + "-bit data -----")); + + /* Print the truth table of this decoder */ + /* Internal logics */ + /* Early exit: Corner case for data size = 1 the logic is very simple: + * data = addr; + * data_inv = ~data_inv + */ + if (1 == data_size) { + fp << "always@(" << generate_verilog_port(VERILOG_PORT_CONKT, addr_port); + fp << " or " << generate_verilog_port(VERILOG_PORT_CONKT, enable_port); + fp << ") begin" << std::endl; + fp << "\tif (" << generate_verilog_port(VERILOG_PORT_CONKT, enable_port) << " == 1'b1) begin" << std::endl; + fp << "\t\t" << generate_verilog_port(VERILOG_PORT_CONKT, din_port) << ";" << std::endl; + fp << "\t" << "end else begin" << std::endl; + fp << "\t\t" << generate_verilog_port_constant_values(data_port, std::vector(1, 0)) << ";" << std::endl; + fp << "\t" << "end" << std::endl; + fp << "end" << std::endl; + + /* Depend on if the inverted data output port is needed or not */ + if (true == decoder_lib.use_data_inv_port(decoder)) { + print_verilog_wire_connection(fp, data_inv_port, addr_port, true); + } + + print_verilog_comment(fp, std::string("----- END Verilog codes for Decoder convert " + std::to_string(addr_size) + "-bit addr to " + std::to_string(data_size) + "-bit data -----")); + + /* Put an end to the Verilog module */ + print_verilog_module_end(fp, module_name); + return; + } + + /* Only the selected data output bit will be set to the value of data_in, + * other data output bits will be '0' + */ + fp << "always@(" << generate_verilog_port(VERILOG_PORT_CONKT, addr_port); + fp << ", " << generate_verilog_port(VERILOG_PORT_CONKT, enable_port); + fp << ", " << generate_verilog_port(VERILOG_PORT_CONKT, din_port); + fp << ") begin" << std::endl; + + fp << "\t" << generate_verilog_port_constant_values(data_port, ito1hot_vec(data_size, data_size)) << ";" << std::endl; + + fp << "\tif (" << generate_verilog_port(VERILOG_PORT_CONKT, enable_port) << " == 1'b1) begin" << std::endl; + fp << "\t\t" << data_port.get_name().c_str() << "[" << addr_port.get_name().c_str() << "]"; + fp << " = " << generate_verilog_port(VERILOG_PORT_CONKT, din_port) << ";" << std::endl; + + fp << "\t" << "end" << std::endl; + + fp << "end" << std::endl; + + if (true == decoder_lib.use_data_inv_port(decoder)) { + print_verilog_wire_connection(fp, data_inv_port, data_port, true); + } + + print_verilog_comment(fp, std::string("----- END Verilog codes for Decoder convert " + std::to_string(addr_size) + "-bit addr to " + std::to_string(data_size) + "-bit data -----")); + + /* Put an end to the Verilog module */ + print_verilog_module_end(fp, module_name); +} + /*************************************************************************************** * This function will generate all the unique Verilog modules of decoders for * configuration protocols in a FPGA fabric @@ -407,7 +533,11 @@ void print_verilog_submodule_arch_decoders(const ModuleManager& module_manager, /* Generate Verilog modules for the found unique local encoders */ for (const auto& decoder : decoder_lib.decoders()) { - print_verilog_arch_decoder_module(fp, module_manager, decoder_lib, decoder); + if (true == decoder_lib.use_data_in(decoder)) { + print_verilog_arch_decoder_with_data_in_module(fp, module_manager, decoder_lib, decoder); + } else { + print_verilog_arch_decoder_module(fp, module_manager, decoder_lib, decoder); + } } /* Close the file stream */ diff --git a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp index e741e16ee..670074d74 100644 --- a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp +++ b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp @@ -116,7 +116,7 @@ void print_verilog_top_testbench_memory_bank_port(std::fstream& fp, /* Print the address port for the Bit-Line decoder here */ print_verilog_comment(fp, std::string("---- Address port for Bit-Line decoder -----")); ModulePortId bl_addr_port_id = module_manager.find_module_port(top_module, - std::string(MEMORY_BL_PORT_NAME)); + std::string(DECODER_BL_ADDRESS_PORT_NAME)); BasicPort bl_addr_port = module_manager.module_port(top_module, bl_addr_port_id); fp << generate_verilog_port(VERILOG_PORT_REG, bl_addr_port) << ";" << std::endl; @@ -124,7 +124,7 @@ void print_verilog_top_testbench_memory_bank_port(std::fstream& fp, /* Print the address port for the Word-Line decoder here */ print_verilog_comment(fp, std::string("---- Address port for Word-Line decoder -----")); ModulePortId wl_addr_port_id = module_manager.find_module_port(top_module, - std::string(MEMORY_WL_PORT_NAME)); + std::string(DECODER_WL_ADDRESS_PORT_NAME)); BasicPort wl_addr_port = module_manager.module_port(top_module, wl_addr_port_id); fp << generate_verilog_port(VERILOG_PORT_REG, wl_addr_port) << ";" << std::endl; @@ -711,13 +711,13 @@ void print_verilog_top_testbench_load_bitstream_task_memory_bank(std::fstream& f BasicPort en_port = module_manager.module_port(top_module, en_port_id); ModulePortId bl_addr_port_id = module_manager.find_module_port(top_module, - std::string(MEMORY_BL_PORT_NAME)); + std::string(DECODER_BL_ADDRESS_PORT_NAME)); BasicPort bl_addr_port = module_manager.module_port(top_module, bl_addr_port_id); BasicPort bl_addr_value = bl_addr_port; bl_addr_value.set_name(std::string(MEMORY_BL_PORT_NAME) + std::string("_val")); ModulePortId wl_addr_port_id = module_manager.find_module_port(top_module, - std::string(MEMORY_WL_PORT_NAME)); + std::string(DECODER_WL_ADDRESS_PORT_NAME)); BasicPort wl_addr_port = module_manager.module_port(top_module, wl_addr_port_id); BasicPort wl_addr_value = wl_addr_port; wl_addr_value.set_name(std::string(MEMORY_WL_PORT_NAME) + std::string("_val")); @@ -1176,12 +1176,12 @@ void print_verilog_top_testbench_memory_bank_bitstream(std::fstream& fp, * We should give dummy values */ ModulePortId bl_addr_port_id = module_manager.find_module_port(top_module, - std::string(MEMORY_BL_PORT_NAME)); + std::string(DECODER_BL_ADDRESS_PORT_NAME)); BasicPort bl_addr_port = module_manager.module_port(top_module, bl_addr_port_id); std::vector initial_bl_addr_values(bl_addr_port.get_width(), 0); ModulePortId wl_addr_port_id = module_manager.find_module_port(top_module, - std::string(MEMORY_WL_PORT_NAME)); + std::string(DECODER_WL_ADDRESS_PORT_NAME)); BasicPort wl_addr_port = module_manager.module_port(top_module, wl_addr_port_id); std::vector initial_wl_addr_values(wl_addr_port.get_width(), 0); @@ -1196,8 +1196,12 @@ void print_verilog_top_testbench_memory_bank_bitstream(std::fstream& fp, print_verilog_comment(fp, "----- Address port default input -----"); fp << "\t\t"; fp << generate_verilog_port_constant_values(bl_addr_port, initial_bl_addr_values); + fp << ";"; + fp << std::endl; + fp << generate_verilog_port_constant_values(wl_addr_port, initial_wl_addr_values); fp << ";"; + fp << std::endl; print_verilog_comment(fp, "----- Data-input port default input -----"); fp << "\t\t"; @@ -1312,6 +1316,7 @@ void print_verilog_top_testbench_frame_decoder_bitstream(std::fstream& fp, fp << "\t\t"; fp << generate_verilog_port_constant_values(addr_port, initial_addr_values); fp << ";"; + fp << std::endl; print_verilog_comment(fp, "----- Data-input port default input -----"); fp << "\t\t"; diff --git a/openfpga/src/utils/module_manager_utils.cpp b/openfpga/src/utils/module_manager_utils.cpp index ec871368a..0fe16987c 100644 --- a/openfpga/src/utils/module_manager_utils.cpp +++ b/openfpga/src/utils/module_manager_utils.cpp @@ -960,14 +960,14 @@ void add_module_nets_cmos_memory_frame_decoder_config_bus(ModuleManager& module_ /* Search the decoder library and try to find one * If not found, create a new module and add it to the module manager */ - DecoderId decoder_id = decoder_lib.find_decoder(addr_size, data_size, true, true, false); + DecoderId decoder_id = decoder_lib.find_decoder(addr_size, data_size, true, false, false); if (DecoderId::INVALID() == decoder_id) { - decoder_id = decoder_lib.add_decoder(addr_size, data_size, true, true, false); + decoder_id = decoder_lib.add_decoder(addr_size, data_size, true, false, false); } VTR_ASSERT(DecoderId::INVALID() != decoder_id); /* Create a module if not existed yet */ - std::string decoder_module_name = generate_frame_memory_decoder_subckt_name(addr_size, data_size); + std::string decoder_module_name = generate_memory_decoder_subckt_name(addr_size, data_size); ModuleId decoder_module = module_manager.find_module(decoder_module_name); if (ModuleId::INVALID() == decoder_module) { decoder_module = build_frame_memory_decoder_module(module_manager, From 73d4c835b739548d9aca2a18421c3b224e1143f9 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 30 May 2020 20:54:05 -0600 Subject: [PATCH 615/645] add regression test case for memory bank --- .../memory_bank/config/task.conf | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 openfpga_flow/tasks/openfpga_shell/full_testbench/memory_bank/config/task.conf diff --git a/openfpga_flow/tasks/openfpga_shell/full_testbench/memory_bank/config/task.conf b/openfpga_flow/tasks/openfpga_shell/full_testbench/memory_bank/config/task.conf new file mode 100644 index 000000000..b000c0ba6 --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/full_testbench/memory_bank/config/task.conf @@ -0,0 +1,34 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/full_testbench_example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k4_N4_40nm_bank_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k4_N4_tileable_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.blif + +[SYNTHESIS_PARAM] +bench0_top = and2 +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.v +bench0_chan_width = 300 + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +end_flow_with_test= +#vpr_fpga_verilog_formal_verification_top_netlist= From 288294c23aed63f3b8170716e77ce3f84b3640cd Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 30 May 2020 20:56:54 -0600 Subject: [PATCH 616/645] add fast configuration test case for memory bank configuration protocol --- ...ast_configuration_example_script.openfpga} | 0 .../fast_configuration_frame/config/task.conf | 2 +- .../fast_memory_bank/config/task.conf | 34 +++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) rename openfpga_flow/OpenFPGAShellScripts/{configuration_frame_example_script.openfpga => fast_configuration_example_script.openfpga} (100%) create mode 100644 openfpga_flow/tasks/openfpga_shell/full_testbench/fast_memory_bank/config/task.conf diff --git a/openfpga_flow/OpenFPGAShellScripts/configuration_frame_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/fast_configuration_example_script.openfpga similarity index 100% rename from openfpga_flow/OpenFPGAShellScripts/configuration_frame_example_script.openfpga rename to openfpga_flow/OpenFPGAShellScripts/fast_configuration_example_script.openfpga diff --git a/openfpga_flow/tasks/openfpga_shell/full_testbench/fast_configuration_frame/config/task.conf b/openfpga_flow/tasks/openfpga_shell/full_testbench/fast_configuration_frame/config/task.conf index 7a448e3e8..55c98dda1 100644 --- a/openfpga_flow/tasks/openfpga_shell/full_testbench/fast_configuration_frame/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/full_testbench/fast_configuration_frame/config/task.conf @@ -8,7 +8,7 @@ [GENERAL] run_engine=openfpga_shell -openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/configuration_frame_example_script.openfpga +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/fast_configuration_example_script.openfpga power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml power_analysis = true spice_output=false diff --git a/openfpga_flow/tasks/openfpga_shell/full_testbench/fast_memory_bank/config/task.conf b/openfpga_flow/tasks/openfpga_shell/full_testbench/fast_memory_bank/config/task.conf new file mode 100644 index 000000000..61b3d8b72 --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/full_testbench/fast_memory_bank/config/task.conf @@ -0,0 +1,34 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/fast_configuration_example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k4_N4_40nm_bank_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k4_N4_tileable_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.blif + +[SYNTHESIS_PARAM] +bench0_top = and2 +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.v +bench0_chan_width = 300 + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +end_flow_with_test= +#vpr_fpga_verilog_formal_verification_top_netlist= From 03e56f5ca6769f069c6cd670695e729f6a8a51e1 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 30 May 2020 20:58:02 -0600 Subject: [PATCH 617/645] deploy memory bank regression tests to CI --- .travis/script.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis/script.sh b/.travis/script.sh index 846ec847f..11056cbed 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -57,6 +57,11 @@ python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/full_testbench/con python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/full_testbench/fast_configuration_frame --debug --show_thread_logs python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/preconfig_testbench/configuration_frame --debug --show_thread_logs +echo -e "Testing memory bank configuration protocol of a K4N4 FPGA"; +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/full_testbench/memory_bank --debug --show_thread_logs +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/full_testbench/fast_memory_bank --debug --show_thread_logs + + echo -e "Testing standalone (flatten memory) configuration protocol of a K4N4 FPGA"; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/full_testbench/flatten_memory --debug --show_thread_logs python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/preconfig_testbench/flatten_memory --debug --show_thread_logs From 3f9afea3e88fa8e96a3c78da3151ab7b9fc01f70 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 30 May 2020 20:59:14 -0600 Subject: [PATCH 618/645] add preconfig testbench test case for memory bank configuration protocol --- .../memory_bank/config/task.conf | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 openfpga_flow/tasks/openfpga_shell/preconfig_testbench/memory_bank/config/task.conf diff --git a/openfpga_flow/tasks/openfpga_shell/preconfig_testbench/memory_bank/config/task.conf b/openfpga_flow/tasks/openfpga_shell/preconfig_testbench/memory_bank/config/task.conf new file mode 100644 index 000000000..75579406c --- /dev/null +++ b/openfpga_flow/tasks/openfpga_shell/preconfig_testbench/memory_bank/config/task.conf @@ -0,0 +1,34 @@ +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# Configuration file for running experiments +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs +# Each job execute fpga_flow script on combination of architecture & benchmark +# timeout_each_job is timeout for each job +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +[GENERAL] +run_engine=openfpga_shell +openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml +power_analysis = true +spice_output=false +verilog_output=true +timeout_each_job = 20*60 +fpga_flow=vpr_blif +openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k4_N4_40nm_bank_openfpga.xml + +[ARCHITECTURES] +arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k4_N4_tileable_40nm.xml + +[BENCHMARKS] +bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.blif + +[SYNTHESIS_PARAM] +bench0_top = and2 +bench0_act = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.act +bench0_verilog = ${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/and2/and2.v +bench0_chan_width = 300 + +[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH] +end_flow_with_test= +vpr_fpga_verilog_formal_verification_top_netlist= From e46651e0c10f55d62ab5904b35fa494db9447a4d Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 30 May 2020 20:59:40 -0600 Subject: [PATCH 619/645] deploy preconfig regression test for memory bank to CI --- .travis/script.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis/script.sh b/.travis/script.sh index 11056cbed..907e1a3cf 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -60,6 +60,7 @@ python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/preconfig_testbenc echo -e "Testing memory bank configuration protocol of a K4N4 FPGA"; python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/full_testbench/memory_bank --debug --show_thread_logs python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/full_testbench/fast_memory_bank --debug --show_thread_logs +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/preconfig_testbench/memory_bank --debug --show_thread_logs echo -e "Testing standalone (flatten memory) configuration protocol of a K4N4 FPGA"; From b9dd47d465fe7fdd5ab54c9f3f71b00083ffc344 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 30 May 2020 21:01:38 -0600 Subject: [PATCH 620/645] update documentation about memory bank configuration protocol --- docs/source/manual/arch_lang/config_protocol.rst | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/source/manual/arch_lang/config_protocol.rst b/docs/source/manual/arch_lang/config_protocol.rst index 2fcdae535..48fd66ac3 100644 --- a/docs/source/manual/arch_lang/config_protocol.rst +++ b/docs/source/manual/arch_lang/config_protocol.rst @@ -100,6 +100,8 @@ When the decoder of sub block, e.g., the LUT, is enabled, each memory cells can - a Bit-Line input to load the data - a Word-Line input to enable data write +.. warning:: Please do NOT add inverted Bit-Line and Word-Line inputs. It is not supported yet! + Memory bank Example ~~~~~~~~~~~~~~~~~~~ The following XML code describes a memory-bank circuitry to configure the core logic of FPGA, as illustrated in :numref:`fig_sram`. @@ -119,7 +121,13 @@ It will use the circuit model defined in :numref:`fig_sram_blwl`. Example of a memory organization using memory decoders -.. warning:: THIS IS STILL UNDER CONSTRUCTION +.. note:: Memory-bank decoders does require a memory cell to have + + - two outputs (one regular and another inverted) + - a Bit-Line input to load the data + - a Word-Line input to enable data write + +.. warning:: Please do NOT add inverted Bit-Line and Word-Line inputs. It is not supported yet! Standalone SRAM Example ~~~~~~~~~~~~~~~~~~~~~~~ @@ -147,5 +155,7 @@ The following XML code shows an example where we use the circuit model defined i - a Bit-Line input to load the data - a Word-Line input to enable data write +.. warning:: Please do NOT add inverted Bit-Line and Word-Line inputs. It is not supported yet! + .. warning:: This is a vanilla configuration method, which allow users to build their own configuration protocol on top of it. From dda6fe19ae975680f8e24bee814ef127ecfecb30 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 30 May 2020 22:28:12 -0600 Subject: [PATCH 621/645] show iverilog version in CI --- .travis/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis/install.sh b/.travis/install.sh index 27d1cad5b..f8860b711 100755 --- a/.travis/install.sh +++ b/.travis/install.sh @@ -32,7 +32,7 @@ set -e # export PATH=${DEPS_DIR}/iverilog-10_3/bin:${PATH} # echo ${PATH} #fi -#iverilog -V +iverilog -V #cd - From b191732d32ce52cdd2e8e7c852bad68d96ba6dd1 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 30 May 2020 22:29:40 -0600 Subject: [PATCH 622/645] show vvp version in CI --- .travis/install.sh | 1 + .travis/script.sh | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis/install.sh b/.travis/install.sh index f8860b711..46d535342 100755 --- a/.travis/install.sh +++ b/.travis/install.sh @@ -33,6 +33,7 @@ set -e # echo ${PATH} #fi iverilog -V +vvp -V #cd - diff --git a/.travis/script.sh b/.travis/script.sh index 907e1a3cf..c16c2eeac 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -58,8 +58,8 @@ python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/full_testbench/fas python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/preconfig_testbench/configuration_frame --debug --show_thread_logs echo -e "Testing memory bank configuration protocol of a K4N4 FPGA"; -python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/full_testbench/memory_bank --debug --show_thread_logs -python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/full_testbench/fast_memory_bank --debug --show_thread_logs +#python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/full_testbench/memory_bank --debug --show_thread_logs +#python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/full_testbench/fast_memory_bank --debug --show_thread_logs python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/preconfig_testbench/memory_bank --debug --show_thread_logs From c85ccceac77cc761078d4e8c082a6a6e49d42a05 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 30 May 2020 23:41:33 -0600 Subject: [PATCH 623/645] try bug fixing in memory bank configuration protocol --- .../src/fpga_verilog/verilog_decoders.cpp | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/openfpga/src/fpga_verilog/verilog_decoders.cpp b/openfpga/src/fpga_verilog/verilog_decoders.cpp index 6e6025ea6..0ae2c70b3 100644 --- a/openfpga/src/fpga_verilog/verilog_decoders.cpp +++ b/openfpga/src/fpga_verilog/verilog_decoders.cpp @@ -483,16 +483,38 @@ void print_verilog_arch_decoder_with_data_in_module(std::fstream& fp, fp << ", " << generate_verilog_port(VERILOG_PORT_CONKT, din_port); fp << ") begin" << std::endl; - fp << "\t" << generate_verilog_port_constant_values(data_port, ito1hot_vec(data_size, data_size)) << ";" << std::endl; - fp << "\tif (" << generate_verilog_port(VERILOG_PORT_CONKT, enable_port) << " == 1'b1) begin" << std::endl; - fp << "\t\t" << data_port.get_name().c_str() << "[" << addr_port.get_name().c_str() << "]"; - fp << " = " << generate_verilog_port(VERILOG_PORT_CONKT, din_port) << ";" << std::endl; + fp << "\t\t" << generate_verilog_port_constant_values(data_port, ito1hot_vec(data_size, data_size)); + fp << ";" << std::endl; + fp << "\t\t" << "case (" << generate_verilog_port(VERILOG_PORT_CONKT, addr_port) << ")" << std::endl; + /* Create a string for addr and data */ + for (size_t i = 0; i < data_size; ++i) { + BasicPort cur_data_port(data_port.get_name(), i, i); + fp << "\t\t\t" << generate_verilog_constant_values(itobin_vec(i, addr_size)); + fp << " : "; + fp << generate_verilog_port(VERILOG_PORT_CONKT, cur_data_port); + fp << " = "; + fp << generate_verilog_port(VERILOG_PORT_CONKT, din_port); + fp << ";" << std::endl; + } + /* Different from MUX decoder, we assign default values which is all zero */ + fp << "\t\t\t" << "default"; + fp << " : "; + fp << generate_verilog_port_constant_values(data_port, ito1hot_vec(data_size, data_size)); + fp << ";" << std::endl; + fp << "\t\t" << "endcase" << std::endl; fp << "\t" << "end" << std::endl; + /* If enable is not active, we should give all zero */ + fp << "\t" << "else begin" << std::endl; + fp << "\t\t" << generate_verilog_port_constant_values(data_port, ito1hot_vec(data_size, data_size)); + fp << ";" << std::endl; + fp << "\t" << "end" << std::endl; + fp << "end" << std::endl; + if (true == decoder_lib.use_data_inv_port(decoder)) { print_verilog_wire_connection(fp, data_inv_port, data_port, true); } From 5368485bd6e17cd2f961fa1088db8473819a4f8b Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 31 May 2020 12:44:38 -0600 Subject: [PATCH 624/645] keep bug fixing for memory bank configuration protocol. Reduce number of BL/WLs at the top-level --- openfpga/src/fabric/build_top_module.cpp | 3 +- .../src/fabric/build_top_module_memory.cpp | 25 ++++++++---- openfpga/src/fabric/build_top_module_memory.h | 3 +- .../fpga_bitstream/build_fabric_bitstream.cpp | 39 +++++++++++++++---- .../fpga_verilog/verilog_top_testbench.cpp | 19 --------- openfpga/src/utils/decoder_library_utils.cpp | 11 ++++-- openfpga/src/utils/decoder_library_utils.h | 2 +- 7 files changed, 61 insertions(+), 41 deletions(-) diff --git a/openfpga/src/fabric/build_top_module.cpp b/openfpga/src/fabric/build_top_module.cpp index 7b05a5676..762ba66f0 100644 --- a/openfpga/src/fabric/build_top_module.cpp +++ b/openfpga/src/fabric/build_top_module.cpp @@ -395,7 +395,8 @@ void build_top_module(ModuleManager& module_manager, if (0 < module_manager.configurable_children(top_module).size()) { add_top_module_nets_memory_config_bus(module_manager, decoder_lib, top_module, - sram_orgz_type, circuit_lib.design_tech_type(sram_model)); + sram_orgz_type, circuit_lib.design_tech_type(sram_model), + module_num_config_bits); } } diff --git a/openfpga/src/fabric/build_top_module_memory.cpp b/openfpga/src/fabric/build_top_module_memory.cpp index 0de55ca4e..568646d57 100644 --- a/openfpga/src/fabric/build_top_module_memory.cpp +++ b/openfpga/src/fabric/build_top_module_memory.cpp @@ -510,7 +510,8 @@ void add_top_module_sram_ports(ModuleManager& module_manager, static void add_top_module_nets_cmos_memory_bank_config_bus(ModuleManager& module_manager, DecoderLibrary& decoder_lib, - const ModuleId& top_module) { + const ModuleId& top_module, + const size_t& num_config_bits) { /* Find Enable port from the top-level module */ ModulePortId en_port = module_manager.find_module_port(top_module, std::string(DECODER_ENABLE_PORT_NAME)); BasicPort en_port_info = module_manager.module_port(top_module, en_port); @@ -529,8 +530,8 @@ void add_top_module_nets_cmos_memory_bank_config_bus(ModuleManager& module_manag /* Find the number of BLs and WLs required to access each memory bit */ size_t bl_addr_size = bl_addr_port_info.get_width(); size_t wl_addr_size = wl_addr_port_info.get_width(); - size_t num_bls = find_memory_decoder_data_size(bl_addr_size); - size_t num_wls = find_memory_decoder_data_size(wl_addr_size); + size_t num_bls = find_memory_decoder_data_size(num_config_bits); + size_t num_wls = find_memory_decoder_data_size(num_config_bits); /* Add the BL decoder module * Search the decoder library @@ -704,6 +705,12 @@ void add_top_module_nets_cmos_memory_bank_config_bus(ModuleManager& module_manag cur_wl_index++; } } + + /* Add the BL and WL decoders to the end of configurable children list + * Note: this MUST be done after adding all the module nets to other regular configurable children + */ + module_manager.add_configurable_child(top_module, bl_decoder_module, 0); + module_manager.add_configurable_child(top_module, wl_decoder_module, 0); } /********************************************************************* @@ -754,7 +761,8 @@ static void add_top_module_nets_cmos_memory_config_bus(ModuleManager& module_manager, DecoderLibrary& decoder_lib, const ModuleId& parent_module, - const e_config_protocol_type& sram_orgz_type) { + const e_config_protocol_type& sram_orgz_type, + const size_t& num_config_bits) { switch (sram_orgz_type) { case CONFIG_MEM_STANDALONE: add_module_nets_cmos_flatten_memory_config_bus(module_manager, parent_module, @@ -767,8 +775,7 @@ void add_top_module_nets_cmos_memory_config_bus(ModuleManager& module_manager, break; } case CONFIG_MEM_MEMORY_BANK: - /* TODO */ - add_top_module_nets_cmos_memory_bank_config_bus(module_manager, decoder_lib, parent_module); + add_top_module_nets_cmos_memory_bank_config_bus(module_manager, decoder_lib, parent_module, num_config_bits); break; case CONFIG_MEM_FRAME_BASED: add_module_nets_cmos_memory_frame_config_bus(module_manager, decoder_lib, parent_module); @@ -816,12 +823,14 @@ void add_top_module_nets_memory_config_bus(ModuleManager& module_manager, DecoderLibrary& decoder_lib, const ModuleId& parent_module, const e_config_protocol_type& sram_orgz_type, - const e_circuit_model_design_tech& mem_tech) { + const e_circuit_model_design_tech& mem_tech, + const size_t& num_config_bits) { switch (mem_tech) { case CIRCUIT_MODEL_DESIGN_CMOS: add_top_module_nets_cmos_memory_config_bus(module_manager, decoder_lib, parent_module, - sram_orgz_type); + sram_orgz_type, + num_config_bits); break; case CIRCUIT_MODEL_DESIGN_RRAM: /* TODO: */ diff --git a/openfpga/src/fabric/build_top_module_memory.h b/openfpga/src/fabric/build_top_module_memory.h index a912845b2..b67389790 100644 --- a/openfpga/src/fabric/build_top_module_memory.h +++ b/openfpga/src/fabric/build_top_module_memory.h @@ -45,7 +45,8 @@ void add_top_module_nets_memory_config_bus(ModuleManager& module_manager, DecoderLibrary& decoder_lib, const ModuleId& parent_module, const e_config_protocol_type& sram_orgz_type, - const e_circuit_model_design_tech& mem_tech); + const e_circuit_model_design_tech& mem_tech, + const size_t& num_config_bits); } /* end namespace openfpga */ diff --git a/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp b/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp index a9c5b6ec7..a69594f48 100644 --- a/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp +++ b/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp @@ -93,6 +93,7 @@ static void rec_build_module_fabric_dependent_memory_bank_bitstream(const BitstreamManager& bitstream_manager, const ConfigBlockId& parent_block, const ModuleManager& module_manager, + const ModuleId& top_module, const ModuleId& parent_module, const size_t& bl_addr_size, const size_t& wl_addr_size, @@ -105,8 +106,17 @@ void rec_build_module_fabric_dependent_memory_bank_bitstream(const BitstreamMana * we dive to the next level first! */ if (0 < bitstream_manager.block_children(parent_block).size()) { - for (size_t child_id = 0; child_id < module_manager.configurable_children(parent_module).size(); ++child_id) { - ModuleId child_module = module_manager.configurable_children(parent_module)[child_id]; + /* For top module, we will skip the two decoders at the end of the configurable children list */ + std::vector configurable_children = module_manager.configurable_children(parent_module); + + size_t num_configurable_children = configurable_children.size(); + if (parent_module == top_module) { + VTR_ASSERT(2 <= configurable_children.size()); + num_configurable_children -= 2; + } + + for (size_t child_id = 0; child_id < num_configurable_children; ++child_id) { + ModuleId child_module = configurable_children[child_id]; size_t child_instance = module_manager.configurable_child_instances(parent_module)[child_id]; /* Get the instance name and ensure it is not empty */ std::string instance_name = module_manager.instance_name(parent_module, child_module, child_instance); @@ -114,12 +124,12 @@ void rec_build_module_fabric_dependent_memory_bank_bitstream(const BitstreamMana /* Find the child block that matches the instance name! */ ConfigBlockId child_block = bitstream_manager.find_child_block(parent_block, instance_name); /* We must have one valid block id! */ - if (true != bitstream_manager.valid_block_id(child_block)) + if (true != bitstream_manager.valid_block_id(child_block)) VTR_ASSERT(true == bitstream_manager.valid_block_id(child_block)); /* Go recursively */ rec_build_module_fabric_dependent_memory_bank_bitstream(bitstream_manager, child_block, - module_manager, child_module, + module_manager, top_module, child_module, bl_addr_size, wl_addr_size, num_bls, num_wls, cur_mem_index, @@ -376,18 +386,31 @@ void build_module_fabric_dependent_bitstream(const ConfigProtocol& config_protoc /* Find BL address port size */ ModulePortId bl_addr_port = module_manager.find_module_port(top_module, std::string(DECODER_BL_ADDRESS_PORT_NAME)); BasicPort bl_addr_port_info = module_manager.module_port(top_module, bl_addr_port); - size_t num_bls = find_memory_decoder_data_size(bl_addr_port_info.get_width()); /* Find WL address port size */ ModulePortId wl_addr_port = module_manager.find_module_port(top_module, std::string(DECODER_WL_ADDRESS_PORT_NAME)); BasicPort wl_addr_port_info = module_manager.module_port(top_module, wl_addr_port); - size_t num_wls = find_memory_decoder_data_size(wl_addr_port_info.get_width()); + + /* Find BL and WL decoders which are the last two configurable children*/ + std::vector configurable_children = module_manager.configurable_children(top_module); + VTR_ASSERT(2 <= configurable_children.size()); + ModuleId bl_decoder_module = configurable_children[configurable_children.size() - 2]; + VTR_ASSERT(0 == module_manager.configurable_child_instances(top_module)[configurable_children.size() - 2]); + ModuleId wl_decoder_module = configurable_children[configurable_children.size() - 1]; + VTR_ASSERT(0 == module_manager.configurable_child_instances(top_module)[configurable_children.size() - 1]); + + ModulePortId bl_port = module_manager.find_module_port(bl_decoder_module, std::string(DECODER_DATA_OUT_PORT_NAME)); + BasicPort bl_port_info = module_manager.module_port(bl_decoder_module, bl_port); + + ModulePortId wl_port = module_manager.find_module_port(wl_decoder_module, std::string(DECODER_DATA_OUT_PORT_NAME)); + BasicPort wl_port_info = module_manager.module_port(wl_decoder_module, wl_port); rec_build_module_fabric_dependent_memory_bank_bitstream(bitstream_manager, top_block, - module_manager, top_module, + module_manager, top_module, top_module, bl_addr_port_info.get_width(), wl_addr_port_info.get_width(), - num_bls, num_wls, + bl_port_info.get_width(), + wl_port_info.get_width(), cur_mem_index, fabric_bitstream); break; } diff --git a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp index 670074d74..5c05d666f 100644 --- a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp +++ b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp @@ -1245,25 +1245,6 @@ void print_verilog_top_testbench_memory_bank_bitstream(std::fstream& fp, fp << ");" << std::endl; } - /* Disable the address and din */ - fp << "\t\t" << std::string(TOP_TESTBENCH_PROG_TASK_NAME); - fp << "(" << bl_addr_port.get_width() << "'b"; - std::vector all_zero_bl_addr(bl_addr_port.get_width(), 0); - for (const size_t& addr_bit : all_zero_bl_addr) { - fp << addr_bit; - } - - fp << ", "; - fp << wl_addr_port.get_width() << "'b"; - std::vector all_zero_wl_addr(wl_addr_port.get_width(), 0); - for (const size_t& addr_bit : all_zero_wl_addr) { - fp << addr_bit; - } - - fp << ", "; - fp <<"1'b0"; - fp << ");" << std::endl; - /* Raise the flag of configuration done when bitstream loading is complete */ BasicPort prog_clock_port(std::string(TOP_TB_PROG_CLOCK_PORT_NAME), 1); fp << "\t\t@(negedge " << generate_verilog_port(VERILOG_PORT_CONKT, prog_clock_port) << ");" << std::endl; diff --git a/openfpga/src/utils/decoder_library_utils.cpp b/openfpga/src/utils/decoder_library_utils.cpp index 8fd7e2208..f4db14ba7 100644 --- a/openfpga/src/utils/decoder_library_utils.cpp +++ b/openfpga/src/utils/decoder_library_utils.cpp @@ -74,11 +74,16 @@ size_t find_mux_local_decoder_addr_size(const size_t& data_size) { * ***************************************************************************************/ size_t find_memory_decoder_addr_size(const size_t& num_mems) { - return find_mux_local_decoder_addr_size((size_t)std::ceil(std::sqrt((float)num_mems))); + return find_mux_local_decoder_addr_size(find_memory_decoder_data_size(num_mems)); } -size_t find_memory_decoder_data_size(const size_t& num_addr) { - return (size_t)std::pow(2., num_addr); +/*************************************************************************************** + * Find the size of data lines (BLs and WLs) for a memory decoder to access a memory array + * As the memory cells are organized in an array with shared bit lines and word lines, + * the number of data lines will be a square root of the number of memory cells. + ***************************************************************************************/ +size_t find_memory_decoder_data_size(const size_t& num_mems) { + return (size_t)std::ceil(std::sqrt((float)num_mems)); } /*************************************************************************************** diff --git a/openfpga/src/utils/decoder_library_utils.h b/openfpga/src/utils/decoder_library_utils.h index f7f06f733..aa25d0df2 100644 --- a/openfpga/src/utils/decoder_library_utils.h +++ b/openfpga/src/utils/decoder_library_utils.h @@ -15,7 +15,7 @@ size_t find_mux_local_decoder_addr_size(const size_t& data_size); size_t find_memory_decoder_addr_size(const size_t& num_mems); -size_t find_memory_decoder_data_size(const size_t& num_addr); +size_t find_memory_decoder_data_size(const size_t& num_mems); DecoderId add_mux_local_decoder_to_library(DecoderLibrary& decoder_lib, const size_t data_size); From 82b04ae3f06e76e1e33e76b0a40e6a1260ba86c4 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 31 May 2020 13:03:54 -0600 Subject: [PATCH 625/645] add SRAM verilog for memory bank usage --- openfpga_flow/VerilogNetlists/sram.v | 49 ++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/openfpga_flow/VerilogNetlists/sram.v b/openfpga_flow/VerilogNetlists/sram.v index 536c1012a..8b2c6c2d6 100644 --- a/openfpga_flow/VerilogNetlists/sram.v +++ b/openfpga_flow/VerilogNetlists/sram.v @@ -1,3 +1,52 @@ +//----------------------------------------------------- +// Design Name : sram_blwl +// File Name : sram.v +// Function : A SRAM cell is is accessible +// when wl is enabled +// Coder : Xifan TANG +//----------------------------------------------------- +module sram_blwl( +input wl, // Word line control signal +input bl, // Bit line control signal +output out, // Data output +output outb, // Data output +); + + //----- local variable need to be registered + reg data; + + //----- when wl is enabled, we can read in data from bl + always @(bl, wl) + begin + //----- Cases to program internal memory bit + //----- case 1: bl = 1, wl = 1, a -> 0 + if ((1'b1 == bl)&&(1'b1 == wl)) begin + data <= 1'b1; + end + //----- case 2: bl = 0, wl = 1, a -> 0 + if ((1'b0 == bl)&&(1'b1 == wl)) begin + data <= 1'b0; + end + end + +`ifdef ENABLE_SIGNAL_INITIALIZATION + initial begin + $deposit(data, $random); + end +`endif + +`ifndef ENABLE_FORMAL_VERIFICATION + // Wire q_reg to Q + assign out = data; + assign outb = ~data; +`else + assign out = 1'bZ; + assign outb = !out; +`endif + +endmodule + + //------ Module: sram6T_blwl -----// //------ Verilog file: sram.v -----// //------ Author: Xifan TANG -----// From aac2e8c805d4b7a6636d24c093b40691184fcd72 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 31 May 2020 13:08:13 -0600 Subject: [PATCH 626/645] update openfpga architecture for memory bank usage --- .../openfpga_arch/k4_N4_40nm_bank_openfpga.xml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/openfpga_flow/openfpga_arch/k4_N4_40nm_bank_openfpga.xml b/openfpga_flow/openfpga_arch/k4_N4_40nm_bank_openfpga.xml index 7de3ec8c4..487cd91e0 100644 --- a/openfpga_flow/openfpga_arch/k4_N4_40nm_bank_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k4_N4_40nm_bank_openfpga.xml @@ -142,29 +142,27 @@ - + - - - - + + - + - + From 8267dad8ef567e7d876cd9ce9de4664ac7a452a7 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 31 May 2020 13:12:00 -0600 Subject: [PATCH 627/645] add decoder support for Z signals --- libopenfpga/libarchopenfpga/src/check_circuit_library.cpp | 1 - openfpga/src/fpga_verilog/verilog_decoders.cpp | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp b/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp index fe364bf0f..b38fbf1b6 100644 --- a/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp +++ b/libopenfpga/libarchopenfpga/src/check_circuit_library.cpp @@ -508,7 +508,6 @@ bool check_circuit_library(const CircuitLibrary& circuit_lib) { /* 6. SRAM must have at least an input and an output ports*/ std::vector sram_port_types_required; - sram_port_types_required.push_back(CIRCUIT_MODEL_PORT_INPUT); sram_port_types_required.push_back(CIRCUIT_MODEL_PORT_OUTPUT); num_err += check_circuit_model_port_required(circuit_lib, CIRCUIT_MODEL_SRAM, sram_port_types_required); diff --git a/openfpga/src/fpga_verilog/verilog_decoders.cpp b/openfpga/src/fpga_verilog/verilog_decoders.cpp index 0ae2c70b3..327cb51b7 100644 --- a/openfpga/src/fpga_verilog/verilog_decoders.cpp +++ b/openfpga/src/fpga_verilog/verilog_decoders.cpp @@ -484,7 +484,10 @@ void print_verilog_arch_decoder_with_data_in_module(std::fstream& fp, fp << ") begin" << std::endl; fp << "\tif (" << generate_verilog_port(VERILOG_PORT_CONKT, enable_port) << " == 1'b1) begin" << std::endl; - fp << "\t\t" << generate_verilog_port_constant_values(data_port, ito1hot_vec(data_size, data_size)); + fp << "\t\t" << generate_verilog_port(VERILOG_PORT_CONKT, data_port); + fp << " = "; + std::string high_res_str = "{" + std::to_string(data_port.get_width()) + "{1'bz}}"; + fp << high_res_str; fp << ";" << std::endl; fp << "\t\t" << "case (" << generate_verilog_port(VERILOG_PORT_CONKT, addr_port) << ")" << std::endl; /* Create a string for addr and data */ From baa2c6b7ef16a7ad054a74a19740a011fe2f570c Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 31 May 2020 13:26:55 -0600 Subject: [PATCH 628/645] update arch to support reset signal for SRAm --- openfpga_flow/VerilogNetlists/sram.v | 16 ++++++---------- .../openfpga_arch/k4_N4_40nm_bank_openfpga.xml | 1 + 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/openfpga_flow/VerilogNetlists/sram.v b/openfpga_flow/VerilogNetlists/sram.v index 8b2c6c2d6..c27dbeebe 100644 --- a/openfpga_flow/VerilogNetlists/sram.v +++ b/openfpga_flow/VerilogNetlists/sram.v @@ -6,10 +6,11 @@ // Coder : Xifan TANG //----------------------------------------------------- module sram_blwl( +input reset, // Word line control signal input wl, // Word line control signal input bl, // Bit line control signal output out, // Data output -output outb, // Data output +output outb // Data output ); //----- local variable need to be registered @@ -18,23 +19,18 @@ output outb, // Data output //----- when wl is enabled, we can read in data from bl always @(bl, wl) begin + if (1'b1 == reset) begin + data <= 1'b0; + end else if ((1'b1 == bl)&&(1'b1 == wl)) begin //----- Cases to program internal memory bit //----- case 1: bl = 1, wl = 1, a -> 0 - if ((1'b1 == bl)&&(1'b1 == wl)) begin data <= 1'b1; - end + end else if ((1'b0 == bl)&&(1'b1 == wl)) begin //----- case 2: bl = 0, wl = 1, a -> 0 - if ((1'b0 == bl)&&(1'b1 == wl)) begin data <= 1'b0; end end -`ifdef ENABLE_SIGNAL_INITIALIZATION - initial begin - $deposit(data, $random); - end -`endif - `ifndef ENABLE_FORMAL_VERIFICATION // Wire q_reg to Q assign out = data; diff --git a/openfpga_flow/openfpga_arch/k4_N4_40nm_bank_openfpga.xml b/openfpga_flow/openfpga_arch/k4_N4_40nm_bank_openfpga.xml index 487cd91e0..fe7d3f1b8 100644 --- a/openfpga_flow/openfpga_arch/k4_N4_40nm_bank_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k4_N4_40nm_bank_openfpga.xml @@ -146,6 +146,7 @@ + From 3c10af7f2bdc0380bbd5d33dbabfba49a5040f1b Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 31 May 2020 16:12:42 -0600 Subject: [PATCH 629/645] bug fixed in memory bank configuration protocol which is due to the wrong Verilog port merging algorithm --- .../src/fabric/build_top_module_memory.cpp | 4 +- .../fpga_bitstream/build_fabric_bitstream.cpp | 12 +++++- .../src/fpga_verilog/verilog_decoders.cpp | 8 +++- .../src/fpga_verilog/verilog_writer_utils.cpp | 40 +++++++++---------- 4 files changed, 38 insertions(+), 26 deletions(-) diff --git a/openfpga/src/fabric/build_top_module_memory.cpp b/openfpga/src/fabric/build_top_module_memory.cpp index 568646d57..6466d98e5 100644 --- a/openfpga/src/fabric/build_top_module_memory.cpp +++ b/openfpga/src/fabric/build_top_module_memory.cpp @@ -2,6 +2,8 @@ * This file includes functions that are used to organize memories * in the top module of FPGA fabric *******************************************************************/ +#include + /* Headers from vtrutil library */ #include "vtr_assert.h" #include "vtr_log.h" @@ -652,7 +654,7 @@ void add_top_module_nets_cmos_memory_bank_config_bus(ModuleManager& module_manag /* Find the BL decoder data index: * It should be the residual when divided by the number of BLs */ - size_t bl_pin_id = cur_bl_index / num_bls; + size_t bl_pin_id = std::floor(cur_bl_index / num_bls); /* Create net */ ModuleNetId net = create_module_source_pin_net(module_manager, top_module, diff --git a/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp b/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp index a69594f48..240f19028 100644 --- a/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp +++ b/openfpga/src/fpga_bitstream/build_fabric_bitstream.cpp @@ -2,6 +2,7 @@ * This file includes functions to build fabric dependent bitstream *******************************************************************/ #include +#include #include /* Headers from vtrutil library */ @@ -115,6 +116,13 @@ void rec_build_module_fabric_dependent_memory_bank_bitstream(const BitstreamMana num_configurable_children -= 2; } + /* Early exit if there is no configurable children */ + if (0 == num_configurable_children) { + /* Ensure that there should be no configuration bits in the parent block */ + VTR_ASSERT(0 == bitstream_manager.block_bits(parent_block).size()); + return; + } + for (size_t child_id = 0; child_id < num_configurable_children; ++child_id) { ModuleId child_module = configurable_children[child_id]; size_t child_instance = module_manager.configurable_child_instances(parent_module)[child_id]; @@ -137,6 +145,8 @@ void rec_build_module_fabric_dependent_memory_bank_bitstream(const BitstreamMana } /* Ensure that there should be no configuration bits in the parent block */ VTR_ASSERT(0 == bitstream_manager.block_bits(parent_block).size()); + + return; } /* Note that, reach here, it means that this is a leaf node. @@ -147,7 +157,7 @@ void rec_build_module_fabric_dependent_memory_bank_bitstream(const BitstreamMana FabricBitId fabric_bit = fabric_bitstream.add_bit(config_bit); /* Find BL address */ - size_t cur_bl_index = cur_mem_index / num_bls; + size_t cur_bl_index = std::floor(cur_mem_index / num_bls); std::vector bl_addr_bits_vec = itobin_vec(cur_bl_index, bl_addr_size); /* Find WL address */ diff --git a/openfpga/src/fpga_verilog/verilog_decoders.cpp b/openfpga/src/fpga_verilog/verilog_decoders.cpp index 327cb51b7..b0259f247 100644 --- a/openfpga/src/fpga_verilog/verilog_decoders.cpp +++ b/openfpga/src/fpga_verilog/verilog_decoders.cpp @@ -503,7 +503,9 @@ void print_verilog_arch_decoder_with_data_in_module(std::fstream& fp, /* Different from MUX decoder, we assign default values which is all zero */ fp << "\t\t\t" << "default"; fp << " : "; - fp << generate_verilog_port_constant_values(data_port, ito1hot_vec(data_size, data_size)); + fp << "\t\t" << generate_verilog_port(VERILOG_PORT_CONKT, data_port); + fp << " = "; + fp << high_res_str; fp << ";" << std::endl; fp << "\t\t" << "endcase" << std::endl; @@ -511,7 +513,9 @@ void print_verilog_arch_decoder_with_data_in_module(std::fstream& fp, /* If enable is not active, we should give all zero */ fp << "\t" << "else begin" << std::endl; - fp << "\t\t" << generate_verilog_port_constant_values(data_port, ito1hot_vec(data_size, data_size)); + fp << "\t\t" << generate_verilog_port(VERILOG_PORT_CONKT, data_port); + fp << " = "; + fp << high_res_str; fp << ";" << std::endl; fp << "\t" << "end" << std::endl; diff --git a/openfpga/src/fpga_verilog/verilog_writer_utils.cpp b/openfpga/src/fpga_verilog/verilog_writer_utils.cpp index c59d92dd0..900c3da37 100644 --- a/openfpga/src/fpga_verilog/verilog_writer_utils.cpp +++ b/openfpga/src/fpga_verilog/verilog_writer_utils.cpp @@ -517,31 +517,27 @@ std::vector combine_verilog_ports(const std::vector& ports if (&port == &ports[0]) { continue; } - /* Identify if the port name can be potentially merged: if the port name is already in the merged port list, it may be merged */ - bool merged = false; - for (auto& merged_port : merged_ports) { - if (false == port.mergeable(merged_port)) { - /* Unable to merge, Go to next */ - continue; - } - /* May be merged, check LSB of port and MSB of merged_port */ - if (merged_port.get_msb() + 1 != port.get_lsb()) { - /* Unable to merge, Go to next */ - continue; - } - /* Reach here, we should merge the ports, - * LSB of merged_port remains the same, - * MSB of merged_port will be updated - * to the MSB of port - */ - merged_port.set_msb(port.get_msb()); - merged = true; - break; - } - if (false == merged) { + /* Identify if the port name can be potentially merged: + * if the port can be merged to the last port in the list, it may be merged + */ + if (false == port.mergeable(merged_ports.back())) { /* Unable to merge, add the port to merged port list */ merged_ports.push_back(port); + continue; } + /* May be merged, check LSB of port and MSB of merged_port */ + if (merged_ports.back().get_msb() + 1 != port.get_lsb()) { + /* Unable to merge, add the port to merged port list */ + merged_ports.push_back(port); + continue; + } + /* Reach here, we should merge the ports, + * LSB of merged_port remains the same, + * MSB of merged_port will be updated + * to the MSB of port + */ + BasicPort& port_to_merge = merged_ports.back(); + port_to_merge.set_msb(port.get_msb()); } return merged_ports; From 0b9971cb5c7a03380dd8e65e726129cd57b35472 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 31 May 2020 16:14:38 -0600 Subject: [PATCH 630/645] try to deploy the memory bank protocol test case to CI --- .travis/script.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis/script.sh b/.travis/script.sh index c16c2eeac..907e1a3cf 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -58,8 +58,8 @@ python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/full_testbench/fas python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/preconfig_testbench/configuration_frame --debug --show_thread_logs echo -e "Testing memory bank configuration protocol of a K4N4 FPGA"; -#python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/full_testbench/memory_bank --debug --show_thread_logs -#python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/full_testbench/fast_memory_bank --debug --show_thread_logs +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/full_testbench/memory_bank --debug --show_thread_logs +python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/full_testbench/fast_memory_bank --debug --show_thread_logs python3 openfpga_flow/scripts/run_fpga_task.py openfpga_shell/preconfig_testbench/memory_bank --debug --show_thread_logs From f73dfa2bccd895d0da3d522e9e0fa09451739e82 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 31 May 2020 18:58:44 -0600 Subject: [PATCH 631/645] bug fixed in k6_n10_40 architecture --- openfpga_flow/arch/vpr_only_templates/k6_N10_40nm.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openfpga_flow/arch/vpr_only_templates/k6_N10_40nm.xml b/openfpga_flow/arch/vpr_only_templates/k6_N10_40nm.xml index e94f339c1..4ab67628f 100644 --- a/openfpga_flow/arch/vpr_only_templates/k6_N10_40nm.xml +++ b/openfpga_flow/arch/vpr_only_templates/k6_N10_40nm.xml @@ -54,7 +54,7 @@ - + @@ -197,7 +197,7 @@ --> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libopenfpga/libarchopenfpga/src/read_xml_openfpga_arch.cpp b/libopenfpga/libarchopenfpga/src/read_xml_openfpga_arch.cpp index 4c773f87f..031946346 100644 --- a/libopenfpga/libarchopenfpga/src/read_xml_openfpga_arch.cpp +++ b/libopenfpga/libarchopenfpga/src/read_xml_openfpga_arch.cpp @@ -113,3 +113,35 @@ openfpga::Arch read_xml_openfpga_arch(const char* arch_file_name) { return openfpga_arch; } + +/******************************************************************** + * Top-level function to parse an XML file and load data to simulation settings + *******************************************************************/ +openfpga::SimulationSetting read_xml_openfpga_simulation_settings(const char* sim_setting_file_name) { + vtr::ScopedStartFinishTimer timer("Read OpenFPGA simulation settings"); + + openfpga::SimulationSetting openfpga_sim_setting; + + pugi::xml_node Next; + + /* Parse the file */ + pugi::xml_document doc; + pugiutil::loc_data loc_data; + + try { + loc_data = pugiutil::load_xml(doc, sim_setting_file_name); + + /* Second node should be */ + auto xml_simulation_settings = get_single_child(doc, "openfpga_simulation_setting", loc_data); + + /* Parse simulation settings to data structure */ + openfpga_sim_setting = read_xml_simulation_setting(xml_simulation_settings, loc_data); + + } catch (pugiutil::XmlError& e) { + archfpga_throw(sim_setting_file_name, e.line(), + "%s", e.what()); + } + + return openfpga_sim_setting; +} + diff --git a/libopenfpga/libarchopenfpga/src/read_xml_openfpga_arch.h b/libopenfpga/libarchopenfpga/src/read_xml_openfpga_arch.h index e23018c8f..660baeec0 100644 --- a/libopenfpga/libarchopenfpga/src/read_xml_openfpga_arch.h +++ b/libopenfpga/libarchopenfpga/src/read_xml_openfpga_arch.h @@ -12,4 +12,6 @@ *******************************************************************/ openfpga::Arch read_xml_openfpga_arch(const char* arch_file_name); +openfpga::SimulationSetting read_xml_openfpga_simulation_settings(const char* sim_setting_file_name); + #endif diff --git a/libopenfpga/libarchopenfpga/src/read_xml_simulation_setting.cpp b/libopenfpga/libarchopenfpga/src/read_xml_simulation_setting.cpp index 60821e2ed..6007a14bf 100644 --- a/libopenfpga/libarchopenfpga/src/read_xml_simulation_setting.cpp +++ b/libopenfpga/libarchopenfpga/src/read_xml_simulation_setting.cpp @@ -40,7 +40,7 @@ e_sim_accuracy_type string_to_sim_accuracy_type(const std::string& type_string) static void read_xml_clock_setting(pugi::xml_node& xml_clock_setting, const pugiutil::loc_data& loc_data, - SimulationSetting& sim_setting) { + openfpga::SimulationSetting& sim_setting) { /* Parse operating clock setting */ pugi::xml_node xml_operating_clock_setting = get_single_child(xml_clock_setting, "operating", loc_data); @@ -73,7 +73,7 @@ void read_xml_clock_setting(pugi::xml_node& xml_clock_setting, static void read_xml_simulator_option(pugi::xml_node& xml_sim_option, const pugiutil::loc_data& loc_data, - SimulationSetting& sim_setting) { + openfpga::SimulationSetting& sim_setting) { pugi::xml_node xml_operating_condition = get_single_child(xml_sim_option, "operating_condition", loc_data); sim_setting.set_simulation_temperature(get_attribute(xml_operating_condition, "temperature", loc_data).as_float(0.)); @@ -119,7 +119,7 @@ void read_xml_simulator_option(pugi::xml_node& xml_sim_option, static void read_xml_monte_carlo(pugi::xml_node& xml_mc, const pugiutil::loc_data& loc_data, - SimulationSetting& sim_setting) { + openfpga::SimulationSetting& sim_setting) { sim_setting.set_monte_carlo_simulation_points(get_attribute(xml_mc, "num_simulation_points", loc_data).as_int(0)); } @@ -129,7 +129,7 @@ void read_xml_monte_carlo(pugi::xml_node& xml_mc, static void read_xml_measurement_setting(pugi::xml_node& xml_measurement, const pugiutil::loc_data& loc_data, - SimulationSetting& sim_setting) { + openfpga::SimulationSetting& sim_setting) { pugi::xml_node xml_slew = get_single_child(xml_measurement, "slew", loc_data); pugi::xml_node xml_slew_rise = get_single_child(xml_slew, "rise", loc_data); sim_setting.set_measure_slew_upper_threshold(SIM_SIGNAL_RISE, get_attribute(xml_slew_rise, "upper_thres_pct", loc_data).as_float(0.)); @@ -155,7 +155,7 @@ void read_xml_measurement_setting(pugi::xml_node& xml_measurement, static void read_xml_stimulus_clock(pugi::xml_node& xml_stimuli_clock, const pugiutil::loc_data& loc_data, - SimulationSetting& sim_setting, + openfpga::SimulationSetting& sim_setting, const e_sim_signal_type& signal_type) { /* Find the type of accuracy */ const char* type_attr = get_attribute(xml_stimuli_clock, "slew_type", loc_data).value(); @@ -188,7 +188,7 @@ void read_xml_stimulus_clock(pugi::xml_node& xml_stimuli_clock, static void read_xml_stimulus_input(pugi::xml_node& xml_stimuli_input, const pugiutil::loc_data& loc_data, - SimulationSetting& sim_setting, + openfpga::SimulationSetting& sim_setting, const e_sim_signal_type& signal_type) { /* Find the type of accuracy */ const char* type_attr = get_attribute(xml_stimuli_input, "slew_type", loc_data).value(); @@ -221,7 +221,7 @@ void read_xml_stimulus_input(pugi::xml_node& xml_stimuli_input, static void read_xml_stimulus(pugi::xml_node& xml_stimulus, const pugiutil::loc_data& loc_data, - SimulationSetting& sim_setting) { + openfpga::SimulationSetting& sim_setting) { pugi::xml_node xml_clock = get_single_child(xml_stimulus, "clock", loc_data); pugi::xml_node xml_clock_rise = get_single_child(xml_clock, "rise", loc_data); read_xml_stimulus_clock(xml_clock_rise, loc_data, sim_setting, SIM_SIGNAL_RISE); @@ -240,9 +240,9 @@ void read_xml_stimulus(pugi::xml_node& xml_stimulus, /******************************************************************** * Parse XML codes about to an object of technology library *******************************************************************/ -SimulationSetting read_xml_simulation_setting(pugi::xml_node& Node, - const pugiutil::loc_data& loc_data) { - SimulationSetting sim_setting; +openfpga::SimulationSetting read_xml_simulation_setting(pugi::xml_node& Node, + const pugiutil::loc_data& loc_data) { + openfpga::SimulationSetting sim_setting; /* Parse clock settings */ pugi::xml_node xml_clock_setting = get_single_child(Node, "clock_setting", loc_data); diff --git a/libopenfpga/libarchopenfpga/src/read_xml_simulation_setting.h b/libopenfpga/libarchopenfpga/src/read_xml_simulation_setting.h index 801caad2e..df6f2856c 100644 --- a/libopenfpga/libarchopenfpga/src/read_xml_simulation_setting.h +++ b/libopenfpga/libarchopenfpga/src/read_xml_simulation_setting.h @@ -11,7 +11,8 @@ /******************************************************************** * Function declaration *******************************************************************/ -SimulationSetting read_xml_simulation_setting(pugi::xml_node& Node, - const pugiutil::loc_data& loc_data); + +openfpga::SimulationSetting read_xml_simulation_setting(pugi::xml_node& Node, + const pugiutil::loc_data& loc_data); #endif diff --git a/libopenfpga/libarchopenfpga/src/simulation_setting.cpp b/libopenfpga/libarchopenfpga/src/simulation_setting.cpp index 2e332a29d..63f20a90c 100644 --- a/libopenfpga/libarchopenfpga/src/simulation_setting.cpp +++ b/libopenfpga/libarchopenfpga/src/simulation_setting.cpp @@ -2,6 +2,9 @@ #include "simulation_setting.h" +/* namespace openfpga begins */ +namespace openfpga { + /************************************************************************ * Member functions for class SimulationSetting ***********************************************************************/ @@ -208,3 +211,5 @@ void SimulationSetting::set_stimuli_input_slew(const e_sim_signal_type& signal_t bool SimulationSetting::valid_signal_threshold(const float& threshold) const { return (0. < threshold) && (threshold < 1); } + +} /* namespace openfpga ends */ diff --git a/libopenfpga/libarchopenfpga/src/simulation_setting.h b/libopenfpga/libarchopenfpga/src/simulation_setting.h index f2e4add5c..7be1294eb 100644 --- a/libopenfpga/libarchopenfpga/src/simulation_setting.h +++ b/libopenfpga/libarchopenfpga/src/simulation_setting.h @@ -34,6 +34,9 @@ enum e_sim_accuracy_type { /* Strings correspond to each accuracy type */ constexpr std::array SIM_ACCURACY_TYPE_STRING = {{"frac", "abs"}}; +/* namespace openfpga begins */ +namespace openfpga { + /******************************************************************** * A data structure to describe simulation settings * @@ -211,4 +214,6 @@ class SimulationSetting { std::array input_slews_; }; +} /* namespace openfpga ends */ + #endif diff --git a/libopenfpga/libarchopenfpga/src/write_xml_openfpga_arch.cpp b/libopenfpga/libarchopenfpga/src/write_xml_openfpga_arch.cpp index 6d0a8ff06..468924ad2 100644 --- a/libopenfpga/libarchopenfpga/src/write_xml_openfpga_arch.cpp +++ b/libopenfpga/libarchopenfpga/src/write_xml_openfpga_arch.cpp @@ -20,7 +20,7 @@ #include "write_xml_openfpga_arch.h" /******************************************************************** - * A writer to output an OpenFPGAArch to XML format + * A writer to output an OpenFPGA arch database to XML format *******************************************************************/ void write_xml_openfpga_arch(const char* fname, const openfpga::Arch& openfpga_arch) { @@ -69,3 +69,25 @@ void write_xml_openfpga_arch(const char* fname, /* Close the file stream */ fp.close(); } + +/******************************************************************** + * A writer to output an OpenFPGA simulation setting database to XML format + *******************************************************************/ +void write_xml_openfpga_simulation_settings(const char* fname, + const openfpga::SimulationSetting& openfpga_sim_setting) { + vtr::ScopedStartFinishTimer timer("Write OpenFPGA simulation settings"); + + /* Create a file handler */ + std::fstream fp; + /* Open the file stream */ + fp.open(std::string(fname), std::fstream::out | std::fstream::trunc); + + /* Validate the file stream */ + openfpga::check_file_stream(fname, fp); + + /* Write the simulation */ + write_xml_simulation_setting(fp, fname, openfpga_sim_setting); + + /* Close the file stream */ + fp.close(); +} diff --git a/libopenfpga/libarchopenfpga/src/write_xml_openfpga_arch.h b/libopenfpga/libarchopenfpga/src/write_xml_openfpga_arch.h index 790d483f4..68611493f 100644 --- a/libopenfpga/libarchopenfpga/src/write_xml_openfpga_arch.h +++ b/libopenfpga/libarchopenfpga/src/write_xml_openfpga_arch.h @@ -13,4 +13,8 @@ void write_xml_openfpga_arch(const char* xml_fname, const openfpga::Arch& openfpga_arch); +void write_xml_openfpga_simulation_settings(const char* xml_fname, + const openfpga::SimulationSetting& openfpga_sim_setting); + + #endif diff --git a/libopenfpga/libarchopenfpga/src/write_xml_simulation_setting.cpp b/libopenfpga/libarchopenfpga/src/write_xml_simulation_setting.cpp index c5ff0beca..9403036c6 100644 --- a/libopenfpga/libarchopenfpga/src/write_xml_simulation_setting.cpp +++ b/libopenfpga/libarchopenfpga/src/write_xml_simulation_setting.cpp @@ -20,7 +20,7 @@ static void write_xml_clock_setting(std::fstream& fp, const char* fname, - const SimulationSetting& sim_setting) { + const openfpga::SimulationSetting& sim_setting) { /* Validate the file stream */ openfpga::check_file_stream(fname, fp); @@ -53,7 +53,7 @@ void write_xml_clock_setting(std::fstream& fp, static void write_xml_simulator_option(std::fstream& fp, const char* fname, - const SimulationSetting& sim_setting) { + const openfpga::SimulationSetting& sim_setting) { /* Validate the file stream */ openfpga::check_file_stream(fname, fp); @@ -87,7 +87,7 @@ void write_xml_simulator_option(std::fstream& fp, static void write_xml_monte_carlo(std::fstream& fp, const char* fname, - const SimulationSetting& sim_setting) { + const openfpga::SimulationSetting& sim_setting) { /* Validate the file stream */ openfpga::check_file_stream(fname, fp); @@ -111,7 +111,7 @@ void write_xml_monte_carlo(std::fstream& fp, static void write_xml_slew_measurement(std::fstream& fp, const char* fname, - const SimulationSetting& sim_setting, + const openfpga::SimulationSetting& sim_setting, const e_sim_signal_type& signal_type) { /* Validate the file stream */ openfpga::check_file_stream(fname, fp); @@ -130,7 +130,7 @@ void write_xml_slew_measurement(std::fstream& fp, static void write_xml_delay_measurement(std::fstream& fp, const char* fname, - const SimulationSetting& sim_setting, + const openfpga::SimulationSetting& sim_setting, const e_sim_signal_type& signal_type) { /* Validate the file stream */ openfpga::check_file_stream(fname, fp); @@ -149,7 +149,7 @@ void write_xml_delay_measurement(std::fstream& fp, static void write_xml_measurement(std::fstream& fp, const char* fname, - const SimulationSetting& sim_setting) { + const openfpga::SimulationSetting& sim_setting) { /* Validate the file stream */ openfpga::check_file_stream(fname, fp); @@ -174,7 +174,7 @@ void write_xml_measurement(std::fstream& fp, static void write_xml_stimulus(std::fstream& fp, const char* fname, - const SimulationSetting& sim_setting) { + const openfpga::SimulationSetting& sim_setting) { /* Validate the file stream */ openfpga::check_file_stream(fname, fp); @@ -216,7 +216,7 @@ void write_xml_stimulus(std::fstream& fp, *******************************************************************/ void write_xml_simulation_setting(std::fstream& fp, const char* fname, - const SimulationSetting& sim_setting) { + const openfpga::SimulationSetting& sim_setting) { /* Validate the file stream */ openfpga::check_file_stream(fname, fp); diff --git a/libopenfpga/libarchopenfpga/src/write_xml_simulation_setting.h b/libopenfpga/libarchopenfpga/src/write_xml_simulation_setting.h index 1c23ef0ca..d0581a8c6 100644 --- a/libopenfpga/libarchopenfpga/src/write_xml_simulation_setting.h +++ b/libopenfpga/libarchopenfpga/src/write_xml_simulation_setting.h @@ -12,6 +12,6 @@ *******************************************************************/ void write_xml_simulation_setting(std::fstream& fp, const char* fname, - const SimulationSetting& sim_setting); + const openfpga::SimulationSetting& sim_setting); #endif diff --git a/libopenfpga/libarchopenfpga/test/main.cpp b/libopenfpga/libarchopenfpga/test/main.cpp deleted file mode 100644 index 7974cf258..000000000 --- a/libopenfpga/libarchopenfpga/test/main.cpp +++ /dev/null @@ -1,37 +0,0 @@ -/******************************************************************** - * 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 readarchopenfpga */ -#include "check_circuit_library.h" -#include "read_xml_openfpga_arch.h" -#include "write_xml_openfpga_arch.h" - -int main(int argc, const char** argv) { - /* Ensure we have only one or two argument */ - VTR_ASSERT((2 == argc) || (3 == argc)); - - /* Parse the circuit library from an XML file */ - const openfpga::Arch& openfpga_arch = read_xml_openfpga_arch(argv[1]); - VTR_LOG("Parsed %lu circuit models from XML into circuit library.\n", - openfpga_arch.circuit_lib.num_models()); - - /* Check the circuit library */ - check_circuit_library(openfpga_arch.circuit_lib); - - /* Output the circuit library to an XML file - * This is optional only used when there is a second argument - */ - if (3 <= argc) { - write_xml_openfpga_arch(argv[2], openfpga_arch); - VTR_LOG("Echo the OpenFPGA architecture to an XML file: %s.\n", - argv[2]); - } -} - - From 4a2f6dfae21cf52fec402eea3a07c827b358912e Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 10 Jun 2020 15:23:32 -0600 Subject: [PATCH 635/645] add read/write simulation setting commands to openfpga shell --- openfpga/src/base/openfpga_context.h | 3 + openfpga/src/base/openfpga_read_arch.cpp | 54 ++++++++++++++++ openfpga/src/base/openfpga_read_arch.h | 6 ++ openfpga/src/base/openfpga_setup_command.cpp | 66 ++++++++++++++++++++ 4 files changed, 129 insertions(+) diff --git a/openfpga/src/base/openfpga_context.h b/openfpga/src/base/openfpga_context.h index 0eb34c029..e18a3fd34 100644 --- a/openfpga/src/base/openfpga_context.h +++ b/openfpga/src/base/openfpga_context.h @@ -50,6 +50,7 @@ class OpenfpgaContext : public Context { public: /* Public accessors */ const openfpga::Arch& arch() const { return arch_; } + const openfpga::SimulationSetting& simulation_setting() const { return sim_setting_; } const openfpga::VprDeviceAnnotation& vpr_device_annotation() const { return vpr_device_annotation_; } const openfpga::VprNetlistAnnotation& vpr_netlist_annotation() const { return vpr_netlist_annotation_; } const openfpga::VprClusteringAnnotation& vpr_clustering_annotation() const { return vpr_clustering_annotation_; } @@ -68,6 +69,7 @@ class OpenfpgaContext : public Context { const openfpga::NetlistManager& verilog_netlists() const { return verilog_netlists_; } public: /* Public mutators */ openfpga::Arch& mutable_arch() { return arch_; } + openfpga::SimulationSetting& mutable_simulation_setting() { return sim_setting_; } openfpga::VprDeviceAnnotation& mutable_vpr_device_annotation() { return vpr_device_annotation_; } openfpga::VprNetlistAnnotation& mutable_vpr_netlist_annotation() { return vpr_netlist_annotation_; } openfpga::VprClusteringAnnotation& mutable_vpr_clustering_annotation() { return vpr_clustering_annotation_; } @@ -87,6 +89,7 @@ class OpenfpgaContext : public Context { private: /* Internal data */ /* Data structure to store information from read_openfpga_arch library */ openfpga::Arch arch_; + openfpga::SimulationSetting sim_setting_; /* Annotation to pb_type of VPR */ openfpga::VprDeviceAnnotation vpr_device_annotation_; diff --git a/openfpga/src/base/openfpga_read_arch.cpp b/openfpga/src/base/openfpga_read_arch.cpp index db9b09b5b..ce7aed6ba 100644 --- a/openfpga/src/base/openfpga_read_arch.cpp +++ b/openfpga/src/base/openfpga_read_arch.cpp @@ -87,5 +87,59 @@ int write_arch(const OpenfpgaContext& openfpga_context, return CMD_EXEC_SUCCESS; } +/******************************************************************** + * Top-level function to read an OpenFPGA simulation setting file + * we use the APIs from the libarchopenfpga library + * + * The command will accept an option '--file' which is the simulation setting + * file provided by users + *******************************************************************/ +int read_simulation_setting(OpenfpgaContext& openfpga_context, + const Command& cmd, const CommandContext& cmd_context) { + /* Check the option '--file' is enabled or not + * Actually, it must be enabled as the shell interface will check + * before reaching this fuction + */ + CommandOptionId opt_file = cmd.option("file"); + VTR_ASSERT(true == cmd_context.option_enable(cmd, opt_file)); + VTR_ASSERT(false == cmd_context.option_value(cmd, opt_file).empty()); + + std::string arch_file_name = cmd_context.option_value(cmd, opt_file); + + VTR_LOG("Reading XML simulation setting '%s'...\n", + arch_file_name.c_str()); + openfpga_context.mutable_simulation_setting() = read_xml_openfpga_simulation_settings(arch_file_name.c_str()); + + /* TODO: should identify the error code from internal function execution */ + return CMD_EXEC_SUCCESS; +} + +/******************************************************************** + * A function to write an OpenFPGA simulation setting file + * we use the APIs from the libarchopenfpga library + * + * The command will accept an option '--file' which is the simulation setting + * file provided by users + *******************************************************************/ +int write_simulation_setting(const OpenfpgaContext& openfpga_context, + const Command& cmd, const CommandContext& cmd_context) { + /* Check the option '--file' is enabled or not + * Actually, it must be enabled as the shell interface will check + * before reaching this fuction + */ + CommandOptionId opt_file = cmd.option("file"); + VTR_ASSERT(true == cmd_context.option_enable(cmd, opt_file)); + VTR_ASSERT(false == cmd_context.option_value(cmd, opt_file).empty()); + + std::string arch_file_name = cmd_context.option_value(cmd, opt_file); + + VTR_LOG("Writing XML simulation setting to '%s'...\n", + arch_file_name.c_str()); + write_xml_openfpga_simulation_settings(arch_file_name.c_str(), openfpga_context.simulation_setting()); + + /* TODO: should identify the error code from internal function execution */ + return CMD_EXEC_SUCCESS; +} + } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_read_arch.h b/openfpga/src/base/openfpga_read_arch.h index b664a7b3e..d5502db56 100644 --- a/openfpga/src/base/openfpga_read_arch.h +++ b/openfpga/src/base/openfpga_read_arch.h @@ -21,6 +21,12 @@ int read_arch(OpenfpgaContext& openfpga_context, int write_arch(const OpenfpgaContext& openfpga_context, const Command& cmd, const CommandContext& cmd_context); +int read_simulation_setting(OpenfpgaContext& openfpga_context, + const Command& cmd, const CommandContext& cmd_context); + +int write_simulation_setting(const OpenfpgaContext& openfpga_context, + const Command& cmd, const CommandContext& cmd_context); + } /* end namespace openfpga */ #endif diff --git a/openfpga/src/base/openfpga_setup_command.cpp b/openfpga/src/base/openfpga_setup_command.cpp index 8af22e7da..b47cfb97a 100644 --- a/openfpga/src/base/openfpga_setup_command.cpp +++ b/openfpga/src/base/openfpga_setup_command.cpp @@ -64,6 +64,55 @@ ShellCommandId add_openfpga_write_arch_command(openfpga::Shell& return shell_cmd_id; } +/******************************************************************** + * - Add a command to Shell environment: read_openfpga_simulation_setting + * - Add associated options + * - Add command dependency + *******************************************************************/ +static +ShellCommandId add_openfpga_read_simulation_setting_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id) { + Command shell_cmd("read_openfpga_simulation_setting"); + + /* Add an option '--file' in short '-f'*/ + CommandOptionId opt_file = shell_cmd.add_option("file", true, "file path to the simulation setting XML"); + shell_cmd.set_option_short_name(opt_file, "f"); + shell_cmd.set_option_require_value(opt_file, openfpga::OPT_STRING); + + /* Add command 'read_openfpga_arch' to the Shell */ + ShellCommandId shell_cmd_id = shell.add_command(shell_cmd, "read OpenFPGA simulation setting file"); + shell.set_command_class(shell_cmd_id, cmd_class_id); + shell.set_command_execute_function(shell_cmd_id, read_simulation_setting); + + return shell_cmd_id; +} + +/******************************************************************** + * - Add a command to Shell environment: write_openfpga_simulation_setting + * - Add associated options + * - Add command dependency + *******************************************************************/ +static +ShellCommandId add_openfpga_write_simulation_setting_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id, + const std::vector& dependent_cmds) { + Command shell_cmd("write_openfpga_simulation_setting"); + /* Add an option '--file' in short '-f'*/ + CommandOptionId opt_file = shell_cmd.add_option("file", true, "file path to the simulation setting XML"); + shell_cmd.set_option_short_name(opt_file, "f"); + shell_cmd.set_option_require_value(opt_file, openfpga::OPT_STRING); + + /* Add command 'write_openfpga_arch' to the Shell */ + ShellCommandId shell_cmd_id = shell.add_command(shell_cmd, "write OpenFPGA simulation setting file"); + shell.set_command_class(shell_cmd_id, cmd_class_id); + shell.set_command_const_execute_function(shell_cmd_id, write_simulation_setting); + + /* Add command dependency to the Shell */ + shell.set_command_dependency(shell_cmd_id, dependent_cmds); + + return shell_cmd_id; +} + /******************************************************************** * - Add a command to Shell environment: link_openfpga_arch * - Add associated options @@ -295,16 +344,33 @@ void add_openfpga_setup_commands(openfpga::Shell& shell) { openfpga_setup_cmd_class, write_arch_dependent_cmds); + /******************************** + * Command 'read_openfpga_simulation_setting' + */ + ShellCommandId read_sim_setting_cmd_id = add_openfpga_read_simulation_setting_command(shell, + openfpga_setup_cmd_class); + + /******************************** + * Command 'write_openfpga_simulation_setting' + */ + /* The 'write_openfpga_simulation_setting' command should NOT be executed before 'read_openfpga_simulation_setting' */ + std::vector write_sim_setting_dependent_cmds(1, read_sim_setting_cmd_id); + add_openfpga_write_simulation_setting_command(shell, + openfpga_setup_cmd_class, + write_sim_setting_dependent_cmds); + /******************************** * Command 'link_openfpga_arch' */ /* The 'link_openfpga_arch' command should NOT be executed before 'vpr' */ std::vector link_arch_dependent_cmds; link_arch_dependent_cmds.push_back(read_arch_cmd_id); + link_arch_dependent_cmds.push_back(read_sim_setting_cmd_id); link_arch_dependent_cmds.push_back(vpr_cmd_id); ShellCommandId link_arch_cmd_id = add_openfpga_link_arch_command(shell, openfpga_setup_cmd_class, link_arch_dependent_cmds); + /******************************** * Command 'write_gsb' */ From 96b58dfdbbb12c14462d067848ae6038cdce4e95 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 10 Jun 2020 15:37:17 -0600 Subject: [PATCH 636/645] use new simulation setting command in openfpga shell --- openfpga/src/base/openfpga_link_arch.cpp | 6 +++++- openfpga/src/base/openfpga_sdc.cpp | 6 +++--- openfpga/src/base/openfpga_setup_command.cpp | 4 +++- openfpga/src/base/openfpga_verilog.cpp | 2 +- .../configuration_chain_example_script.openfpga | 3 +++ .../duplicated_grid_pin_example_script.openfpga | 3 +++ openfpga_flow/OpenFPGAShellScripts/example_script.openfpga | 3 +++ .../fast_configuration_example_script.openfpga | 3 +++ .../flatten_routing_example_script.openfpga | 3 +++ .../full_testbench_example_script.openfpga | 3 +++ .../generate_fabric_example_script.openfpga | 3 +++ .../generate_testbench_example_script.openfpga | 3 +++ .../implicit_verilog_example_script.openfpga | 3 +++ .../OpenFPGAShellScripts/mcnc_example_script.openfpga | 3 +++ .../sdc_time_unit_example_script.openfpga | 3 +++ 15 files changed, 45 insertions(+), 6 deletions(-) diff --git a/openfpga/src/base/openfpga_link_arch.cpp b/openfpga/src/base/openfpga_link_arch.cpp index 65aef7cf8..c67619caa 100644 --- a/openfpga/src/base/openfpga_link_arch.cpp +++ b/openfpga/src/base/openfpga_link_arch.cpp @@ -147,9 +147,13 @@ int link_arch(OpenfpgaContext& openfpga_ctx, * We SHOULD create a new simulation setting for OpenFPGA use only * Avoid overwrite the raw data achieved when parsing!!! */ + /* OVERWRITE the simulation setting in openfpga context from the arch + * TODO: This will be removed when openfpga flow is updated + */ + openfpga_ctx.mutable_simulation_setting() = openfpga_ctx.mutable_arch().sim_setting; annotate_simulation_setting(g_vpr_ctx.atom(), openfpga_ctx.net_activity(), - openfpga_ctx.mutable_arch().sim_setting); + openfpga_ctx.mutable_simulation_setting()); /* TODO: should identify the error code from internal function execution */ return CMD_EXEC_SUCCESS; diff --git a/openfpga/src/base/openfpga_sdc.cpp b/openfpga/src/base/openfpga_sdc.cpp index 2d44520db..9bcbc26df 100644 --- a/openfpga/src/base/openfpga_sdc.cpp +++ b/openfpga/src/base/openfpga_sdc.cpp @@ -88,8 +88,8 @@ int write_pnr_sdc(const OpenfpgaContext& openfpga_ctx, /* Execute only when sdc is enabled */ if (true == options.generate_sdc_pnr()) { print_pnr_sdc(options, - 1./openfpga_ctx.arch().sim_setting.programming_clock_frequency(), - 1./openfpga_ctx.arch().sim_setting.operating_clock_frequency(), + 1./openfpga_ctx.simulation_setting().programming_clock_frequency(), + 1./openfpga_ctx.simulation_setting().operating_clock_frequency(), g_vpr_ctx.device(), openfpga_ctx.vpr_device_annotation(), openfpga_ctx.device_rr_gsb(), @@ -200,7 +200,7 @@ int write_analysis_sdc(const OpenfpgaContext& openfpga_ctx, if (true == options.generate_sdc_analysis()) { print_analysis_sdc(options, - 1./openfpga_ctx.arch().sim_setting.operating_clock_frequency(), + 1./openfpga_ctx.simulation_setting().operating_clock_frequency(), g_vpr_ctx, openfpga_ctx, global_ports, diff --git a/openfpga/src/base/openfpga_setup_command.cpp b/openfpga/src/base/openfpga_setup_command.cpp index b47cfb97a..e9d759b1f 100644 --- a/openfpga/src/base/openfpga_setup_command.cpp +++ b/openfpga/src/base/openfpga_setup_command.cpp @@ -365,7 +365,9 @@ void add_openfpga_setup_commands(openfpga::Shell& shell) { /* The 'link_openfpga_arch' command should NOT be executed before 'vpr' */ std::vector link_arch_dependent_cmds; link_arch_dependent_cmds.push_back(read_arch_cmd_id); - link_arch_dependent_cmds.push_back(read_sim_setting_cmd_id); + /* TODO: This will be uncommented when openfpga flow script is updated + * link_arch_dependent_cmds.push_back(read_sim_setting_cmd_id); + */ link_arch_dependent_cmds.push_back(vpr_cmd_id); ShellCommandId link_arch_cmd_id = add_openfpga_link_arch_command(shell, openfpga_setup_cmd_class, diff --git a/openfpga/src/base/openfpga_verilog.cpp b/openfpga/src/base/openfpga_verilog.cpp index 926e0f8d3..58ea20f2e 100644 --- a/openfpga/src/base/openfpga_verilog.cpp +++ b/openfpga/src/base/openfpga_verilog.cpp @@ -96,7 +96,7 @@ int write_verilog_testbench(OpenfpgaContext& openfpga_ctx, openfpga_ctx.io_location_map(), openfpga_ctx.vpr_netlist_annotation(), openfpga_ctx.arch().circuit_lib, - openfpga_ctx.arch().sim_setting, + openfpga_ctx.simulation_setting(), openfpga_ctx.arch().config_protocol.type(), options); diff --git a/openfpga_flow/OpenFPGAShellScripts/configuration_chain_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/configuration_chain_example_script.openfpga index 11f3ed295..7492854a2 100644 --- a/openfpga_flow/OpenFPGAShellScripts/configuration_chain_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/configuration_chain_example_script.openfpga @@ -5,6 +5,9 @@ vpr ${VPR_ARCH_FILE} ${VPR_TESTBENCH_BLIF} --clock_modeling route # Read OpenFPGA architecture definition read_openfpga_arch -f ${OPENFPGA_ARCH_FILE} +# Read OpenFPGA simulation settings +#read_openfpga_simulation_setting -f OPENFPGA_SIM_SETTING_FILE + # Annotate the OpenFPGA architecture to VPR data base # to debug use --verbose options link_openfpga_arch --activity_file ${ACTIVITY_FILE} --sort_gsb_chan_node_in_edges diff --git a/openfpga_flow/OpenFPGAShellScripts/duplicated_grid_pin_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/duplicated_grid_pin_example_script.openfpga index bcc8d36e3..83707cf53 100644 --- a/openfpga_flow/OpenFPGAShellScripts/duplicated_grid_pin_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/duplicated_grid_pin_example_script.openfpga @@ -5,6 +5,9 @@ vpr ${VPR_ARCH_FILE} ${VPR_TESTBENCH_BLIF} --clock_modeling route # Read OpenFPGA architecture definition read_openfpga_arch -f ${OPENFPGA_ARCH_FILE} +# Read OpenFPGA simulation settings +#read_openfpga_simulation_setting -f OPENFPGA_SIM_SETTING_FILE + # Annotate the OpenFPGA architecture to VPR data base # to debug use --verbose options link_openfpga_arch --activity_file ${ACTIVITY_FILE} --sort_gsb_chan_node_in_edges diff --git a/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga index bcc8d36e3..83707cf53 100644 --- a/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga @@ -5,6 +5,9 @@ vpr ${VPR_ARCH_FILE} ${VPR_TESTBENCH_BLIF} --clock_modeling route # Read OpenFPGA architecture definition read_openfpga_arch -f ${OPENFPGA_ARCH_FILE} +# Read OpenFPGA simulation settings +#read_openfpga_simulation_setting -f OPENFPGA_SIM_SETTING_FILE + # Annotate the OpenFPGA architecture to VPR data base # to debug use --verbose options link_openfpga_arch --activity_file ${ACTIVITY_FILE} --sort_gsb_chan_node_in_edges diff --git a/openfpga_flow/OpenFPGAShellScripts/fast_configuration_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/fast_configuration_example_script.openfpga index 94e16b906..56d05257c 100644 --- a/openfpga_flow/OpenFPGAShellScripts/fast_configuration_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/fast_configuration_example_script.openfpga @@ -5,6 +5,9 @@ vpr ${VPR_ARCH_FILE} ${VPR_TESTBENCH_BLIF} --clock_modeling route # Read OpenFPGA architecture definition read_openfpga_arch -f ${OPENFPGA_ARCH_FILE} +# Read OpenFPGA simulation settings +#read_openfpga_simulation_setting -f OPENFPGA_SIM_SETTING_FILE + # Annotate the OpenFPGA architecture to VPR data base # to debug use --verbose options link_openfpga_arch --activity_file ${ACTIVITY_FILE} --sort_gsb_chan_node_in_edges diff --git a/openfpga_flow/OpenFPGAShellScripts/flatten_routing_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/flatten_routing_example_script.openfpga index 3210b411f..fa376dbe3 100644 --- a/openfpga_flow/OpenFPGAShellScripts/flatten_routing_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/flatten_routing_example_script.openfpga @@ -5,6 +5,9 @@ vpr ${VPR_ARCH_FILE} ${VPR_TESTBENCH_BLIF} --clock_modeling route # Read OpenFPGA architecture definition read_openfpga_arch -f ${OPENFPGA_ARCH_FILE} +# Read OpenFPGA simulation settings +#read_openfpga_simulation_setting -f OPENFPGA_SIM_SETTING_FILE + # Annotate the OpenFPGA architecture to VPR data base # to debug use --verbose options link_openfpga_arch --activity_file ${ACTIVITY_FILE} diff --git a/openfpga_flow/OpenFPGAShellScripts/full_testbench_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/full_testbench_example_script.openfpga index bcc8d36e3..83707cf53 100644 --- a/openfpga_flow/OpenFPGAShellScripts/full_testbench_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/full_testbench_example_script.openfpga @@ -5,6 +5,9 @@ vpr ${VPR_ARCH_FILE} ${VPR_TESTBENCH_BLIF} --clock_modeling route # Read OpenFPGA architecture definition read_openfpga_arch -f ${OPENFPGA_ARCH_FILE} +# Read OpenFPGA simulation settings +#read_openfpga_simulation_setting -f OPENFPGA_SIM_SETTING_FILE + # Annotate the OpenFPGA architecture to VPR data base # to debug use --verbose options link_openfpga_arch --activity_file ${ACTIVITY_FILE} --sort_gsb_chan_node_in_edges diff --git a/openfpga_flow/OpenFPGAShellScripts/generate_fabric_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/generate_fabric_example_script.openfpga index ff40f4c76..434c573e1 100644 --- a/openfpga_flow/OpenFPGAShellScripts/generate_fabric_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/generate_fabric_example_script.openfpga @@ -5,6 +5,9 @@ vpr ${VPR_ARCH_FILE} ${VPR_TESTBENCH_BLIF} --clock_modeling route # Read OpenFPGA architecture definition read_openfpga_arch -f ${OPENFPGA_ARCH_FILE} +# Read OpenFPGA simulation settings +#read_openfpga_simulation_setting -f OPENFPGA_SIM_SETTING_FILE + # Annotate the OpenFPGA architecture to VPR data base # to debug use --verbose options link_openfpga_arch --activity_file ${ACTIVITY_FILE} --sort_gsb_chan_node_in_edges diff --git a/openfpga_flow/OpenFPGAShellScripts/generate_testbench_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/generate_testbench_example_script.openfpga index a426a7833..6fe802168 100644 --- a/openfpga_flow/OpenFPGAShellScripts/generate_testbench_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/generate_testbench_example_script.openfpga @@ -5,6 +5,9 @@ vpr ${VPR_ARCH_FILE} ${VPR_TESTBENCH_BLIF} --clock_modeling route # Read OpenFPGA architecture definition read_openfpga_arch -f ${OPENFPGA_ARCH_FILE} +# Read OpenFPGA simulation settings +#read_openfpga_simulation_setting -f OPENFPGA_SIM_SETTING_FILE + # Annotate the OpenFPGA architecture to VPR data base # to debug use --verbose options link_openfpga_arch --activity_file ${ACTIVITY_FILE} --sort_gsb_chan_node_in_edges diff --git a/openfpga_flow/OpenFPGAShellScripts/implicit_verilog_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/implicit_verilog_example_script.openfpga index 579e113a9..021488557 100644 --- a/openfpga_flow/OpenFPGAShellScripts/implicit_verilog_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/implicit_verilog_example_script.openfpga @@ -5,6 +5,9 @@ vpr ${VPR_ARCH_FILE} ${VPR_TESTBENCH_BLIF} --clock_modeling route # Read OpenFPGA architecture definition read_openfpga_arch -f ${OPENFPGA_ARCH_FILE} +# Read OpenFPGA simulation settings +#read_openfpga_simulation_setting -f OPENFPGA_SIM_SETTING_FILE + # Annotate the OpenFPGA architecture to VPR data base # to debug use --verbose options link_openfpga_arch --activity_file ${ACTIVITY_FILE} --sort_gsb_chan_node_in_edges diff --git a/openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga index 3293979ea..ebd6c217c 100644 --- a/openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga @@ -5,6 +5,9 @@ vpr ${VPR_ARCH_FILE} ${VPR_TESTBENCH_BLIF} --clock_modeling route --absorb_buffe # Read OpenFPGA architecture definition read_openfpga_arch -f ${OPENFPGA_ARCH_FILE} +# Read OpenFPGA simulation settings +#read_openfpga_simulation_setting -f OPENFPGA_SIM_SETTING_FILE + # Annotate the OpenFPGA architecture to VPR data base # to debug use --verbose options link_openfpga_arch --activity_file ${ACTIVITY_FILE} --sort_gsb_chan_node_in_edges diff --git a/openfpga_flow/OpenFPGAShellScripts/sdc_time_unit_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/sdc_time_unit_example_script.openfpga index 6841eb935..3411f6e87 100644 --- a/openfpga_flow/OpenFPGAShellScripts/sdc_time_unit_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/sdc_time_unit_example_script.openfpga @@ -5,6 +5,9 @@ vpr ${VPR_ARCH_FILE} ${VPR_TESTBENCH_BLIF} --clock_modeling route # Read OpenFPGA architecture definition read_openfpga_arch -f ${OPENFPGA_ARCH_FILE} +# Read OpenFPGA simulation settings +#read_openfpga_simulation_setting -f OPENFPGA_SIM_SETTING_FILE + # Annotate the OpenFPGA architecture to VPR data base # to debug use --verbose options link_openfpga_arch --activity_file ${ACTIVITY_FILE} --sort_gsb_chan_node_in_edges From cb09896f23b059a5f617fd689607d210d7eb0449 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 10 Jun 2020 15:41:46 -0600 Subject: [PATCH 637/645] add example simulation setting for openfpga flow --- .../auto_sim_openfpga.xml | 39 +++++++++++++++++++ .../fixed_sim_openfpga.xml | 37 ++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml create mode 100644 openfpga_flow/openfpga_simulation_settings/fixed_sim_openfpga.xml diff --git a/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml b/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml new file mode 100644 index 000000000..99ff97235 --- /dev/null +++ b/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openfpga_flow/openfpga_simulation_settings/fixed_sim_openfpga.xml b/openfpga_flow/openfpga_simulation_settings/fixed_sim_openfpga.xml new file mode 100644 index 000000000..94593863d --- /dev/null +++ b/openfpga_flow/openfpga_simulation_settings/fixed_sim_openfpga.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From dfdfea208149ab99aadf676251a380c1f18bceb1 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 10 Jun 2020 15:49:20 -0600 Subject: [PATCH 638/645] fix the bug in CMake Script due to splitted simulation setting files --- CMakeLists.txt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f46955c8b..6aa930b94 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -258,12 +258,6 @@ set_target_properties(libvpr vpr_shell LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/vpr7_x2p/vpr" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/vpr7_x2p/vpr") -set_target_properties(libarchopenfpga read_arch_openfpga - PROPERTIES - ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/libopenfpga/libarchopenfpga" - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/libopenfpga/libarchopenfpga" - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/libopenfpga/libarchopenfpga") - set_target_properties(libvpr8 vpr8 PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/vpr" From f26550141fd4f1a11ac382b3da6c1f358f23cd9c Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 10 Jun 2020 15:56:37 -0600 Subject: [PATCH 639/645] add missing files --- .../test/read_arch_openfpga.cpp | 37 +++++++++++++++++++ .../test/read_simulation_setting_openfpga.cpp | 31 ++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 libopenfpga/libarchopenfpga/test/read_arch_openfpga.cpp create mode 100644 libopenfpga/libarchopenfpga/test/read_simulation_setting_openfpga.cpp diff --git a/libopenfpga/libarchopenfpga/test/read_arch_openfpga.cpp b/libopenfpga/libarchopenfpga/test/read_arch_openfpga.cpp new file mode 100644 index 000000000..7974cf258 --- /dev/null +++ b/libopenfpga/libarchopenfpga/test/read_arch_openfpga.cpp @@ -0,0 +1,37 @@ +/******************************************************************** + * 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 readarchopenfpga */ +#include "check_circuit_library.h" +#include "read_xml_openfpga_arch.h" +#include "write_xml_openfpga_arch.h" + +int main(int argc, const char** argv) { + /* Ensure we have only one or two argument */ + VTR_ASSERT((2 == argc) || (3 == argc)); + + /* Parse the circuit library from an XML file */ + const openfpga::Arch& openfpga_arch = read_xml_openfpga_arch(argv[1]); + VTR_LOG("Parsed %lu circuit models from XML into circuit library.\n", + openfpga_arch.circuit_lib.num_models()); + + /* Check the circuit library */ + check_circuit_library(openfpga_arch.circuit_lib); + + /* Output the circuit library to an XML file + * This is optional only used when there is a second argument + */ + if (3 <= argc) { + write_xml_openfpga_arch(argv[2], openfpga_arch); + VTR_LOG("Echo the OpenFPGA architecture to an XML file: %s.\n", + argv[2]); + } +} + + diff --git a/libopenfpga/libarchopenfpga/test/read_simulation_setting_openfpga.cpp b/libopenfpga/libarchopenfpga/test/read_simulation_setting_openfpga.cpp new file mode 100644 index 000000000..b8540240b --- /dev/null +++ b/libopenfpga/libarchopenfpga/test/read_simulation_setting_openfpga.cpp @@ -0,0 +1,31 @@ +/******************************************************************** + * 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 readarchopenfpga */ +#include "read_xml_openfpga_arch.h" +#include "write_xml_openfpga_arch.h" + +int main(int argc, const char** argv) { + /* Ensure we have only one or two argument */ + VTR_ASSERT((2 == argc) || (3 == argc)); + + /* Parse the simulation settings from an XML file */ + const openfpga::SimulationSetting& openfpga_sim_setting = read_xml_openfpga_simulation_settings(argv[1]); + VTR_LOG("Parsed simulation settings from XML %s.\n", + argv[1]); + + /* Output the simulation settings to an XML file + * This is optional only used when there is a second argument + */ + if (3 <= argc) { + write_xml_openfpga_simulation_settings(argv[2], openfpga_sim_setting); + VTR_LOG("Echo the OpenFPGA simulation settings to an XML file: %s.\n", + argv[2]); + } +} From 1a006f2ddb484d34610adad212c0bb85a410e33f Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 10 Jun 2020 20:05:43 -0600 Subject: [PATCH 640/645] update documentation for separated XML files --- docs/source/manual/arch_lang/generality.rst | 53 ++++++++++++------- docs/source/manual/fpga_spice/index.rst | 5 +- .../openfpga_shell/openfpga_commands.rst | 18 ++++++- 3 files changed, 53 insertions(+), 23 deletions(-) diff --git a/docs/source/manual/arch_lang/generality.rst b/docs/source/manual/arch_lang/generality.rst index d594ff896..af7719b9a 100644 --- a/docs/source/manual/arch_lang/generality.rst +++ b/docs/source/manual/arch_lang/generality.rst @@ -1,4 +1,4 @@ -.. _generality: +.. _arch_generality: General Hierarchy ----------------- @@ -13,34 +13,49 @@ In the following sub-sections, we will introduce the structures of these XML nod For OpenFPGA using VPR8 ~~~~~~~~~~~~~~~~~~~~~~~ -OpenFPGA uses a separated XML file other than the VPR8 architecture description file. +OpenFPGA uses separated XMLs file other than the VPR8 architecture description file. This is to keep a loose integration to VPR8 so that OpenFPGA can easily integrate any future version of VPR with least engineering effort. However, to implement a physical FPGA, OpenFPGA requires the original VPR XML to include full physical design details. Full syntax can be found in :ref:`addon_vpr_syntax`. -The OpenFPGA architecture description XML file consisting of the following parts: +The OpenFPGA requires two XML files: an architecture description file and a simulation setting description file. - - ```` contains architecture-level information, such as device-level description, circuit-level and architecture annotations to original VPR architecture XML. It consists of the following code blocks +OpenFPGA Architecture Description File +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This file contains device-level and circuit-level details as well as annotations to the original VPR architecture. +It contains a root node called ```` under which architecture-level information, such as device-level description, circuit-level and architecture annotations to original VPR architecture XML are defined. + +It consists of the following code blocks - ```` includes a number of ``circuit_model``, each of which describe a primitive block in FPGA architecture, such as Look-Up Tables and multiplexers. Full syntax can be found in :ref:`circuit_library`. - - ```` includes transistor-level parameters, where users can specify which transistor models are going to be used when building the ``circuit models``. - - ```` includes detailed description on the configuration protocols to be used in FPGA fabric. - - ```` includes annotation on the connection block definition ```` in original VPR XML - - ```` includes annotation on the switch block definition ```` in original VPR XML - - ```` includes annotation on the routing segment definition ```` in original VPR XML - - ```` includes annotation on the inter-tile direct connection definitioin ```` in original VPR XML - - ```` includes annotation on the programmable block architecture ```` in original VPR XML - - - ```` includes all the parameters to be used in generate testbenches in simulation purpose. Full syntax can be found in :ref:`simulation_setting`. - - - ```` defines the clock-related settings in simulation, such as clock frequency and number of clock cycles to be used - - ```` defines universal options available in both HDL and SPICE simulators. This is mainly used by FPGA-SPICE - - ```` defines critical parameters to be used in monte-carlo simulations. This is used by FPGA-SPICE - - ```` defines the parameters used to measure signal slew and delays. This is used by FPGA-SPICE - - ```` defines the parameters used to generate voltage stimuli in testbenches. This is used by FPGA-SPICE + - ```` includes transistor-level parameters, where users can specify which transistor models are going to be used when building the ``circuit models``. Full syntax can be found in :ref:`technology_library`. + - ```` includes detailed description on the configuration protocols to be used in FPGA fabric. Full syntax can be found in :ref:`config_protocol`. + - ```` includes annotation on the connection block definition ```` in original VPR XML. Full syntax can be found in :ref:`annotate_vpr_arch`. + - ```` includes annotation on the switch block definition ```` in original VPR XML. Full syntax can be found in :ref:`annotate_vpr_arch`. + - ```` includes annotation on the routing segment definition ```` in original VPR XML. Full syntax can be found in :ref:`annotate_vpr_arch`. + - ```` includes annotation on the inter-tile direct connection definitioin ```` in original VPR XML. Full syntax can be found in :ref:`direct_interconnect`. + - ```` includes annotation on the programmable block architecture ```` in original VPR XML. Full syntax can be found in :ref:`annotate_vpr_arch`. .. note:: ```` will be applied to ``circuit_model`` when running FPGA-SPICE. It will not impact FPGA-Verilog, FPGA-Bitstream, FPGA-SDC. + +OpenFPGA Simulation Setting File +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This file contains parameters required by testbench generators. +It contains a root node ````, under which all the parameters to be used in generate testbenches in simulation purpose are defined. + +It consists of the following code blocks + + - ```` defines the clock-related settings in simulation, such as clock frequency and number of clock cycles to be used. + - ```` defines universal options available in both HDL and SPICE simulators. This is mainly used by :ref:`fpga_spice`. + - ```` defines critical parameters to be used in monte-carlo simulations. This is used by :ref:`fpga_spice`. + - ```` defines the parameters used to measure signal slew and delays. This is used by :ref:`fpga_spice`. + - ```` defines the parameters used to generate voltage stimuli in testbenches. This is used by :ref:`fpga_spice`. + +Full syntax can be found in :ref:`simulation_setting`. + .. note:: the parameters in ```` will be applied to both FPGA-Verilog and FPGA-SPICE simulations diff --git a/docs/source/manual/fpga_spice/index.rst b/docs/source/manual/fpga_spice/index.rst index 4cdffcc0f..b8ce29223 100644 --- a/docs/source/manual/fpga_spice/index.rst +++ b/docs/source/manual/fpga_spice/index.rst @@ -1,11 +1,10 @@ +.. _fpga_spice: + FPGA-SPICE ---------- .. warning:: FPGA-SPICE has not been integrated to VPR8 version yet. Please the following tool guide is for VPR7 version now -.. _fpga_spice: - FPGA-SPICE - .. toctree:: :maxdepth: 2 diff --git a/docs/source/manual/openfpga_shell/openfpga_commands.rst b/docs/source/manual/openfpga_shell/openfpga_commands.rst index 6e2f0e490..eb5929861 100644 --- a/docs/source/manual/openfpga_shell/openfpga_commands.rst +++ b/docs/source/manual/openfpga_shell/openfpga_commands.rst @@ -28,7 +28,7 @@ Setup OpenFPGA .. option:: read_openfpga_arch - Read the XML architecture file required by OpenFPGA + Read the XML file about architecture description (see details in :ref:`arch_generality`) - ``--file`` or ``-f`` Specify the file name @@ -42,6 +42,22 @@ Setup OpenFPGA - ``--verbose`` Show verbose log +.. option:: read_openfpga_simulation_setting + + Read the XML file about simulation settings (see details in :ref:`simulation_setting`) + + - ``--file`` or ``-f`` Specify the file name + + - ``--verbose`` Show verbose log + +.. option:: write_openfpga_simulation_setting + + Write the OpenFPGA XML simulation settings to a file + + - ``--file`` or ``-f`` Specify the file name + + - ``--verbose`` Show verbose log + .. option:: link_openfpga_arch Annotate the OpenFPGA architecture to VPR data base From 1842bf51e1d37bbc077a3fe211665ff465d1795b Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 11 Jun 2020 11:22:47 -0600 Subject: [PATCH 641/645] deploy read_openfpga_simulation_setting in CI on a single test case --- .../configuration_chain_example_script.openfpga | 2 +- .../full_testbench/configuration_chain/config/task.conf | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/openfpga_flow/OpenFPGAShellScripts/configuration_chain_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/configuration_chain_example_script.openfpga index 7492854a2..0cf8a6448 100644 --- a/openfpga_flow/OpenFPGAShellScripts/configuration_chain_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/configuration_chain_example_script.openfpga @@ -6,7 +6,7 @@ vpr ${VPR_ARCH_FILE} ${VPR_TESTBENCH_BLIF} --clock_modeling route read_openfpga_arch -f ${OPENFPGA_ARCH_FILE} # Read OpenFPGA simulation settings -#read_openfpga_simulation_setting -f OPENFPGA_SIM_SETTING_FILE +read_openfpga_simulation_setting -f ${OPENFPGA_SIM_SETTING_FILE} # Annotate the OpenFPGA architecture to VPR data base # to debug use --verbose options diff --git a/openfpga_flow/tasks/openfpga_shell/full_testbench/configuration_chain/config/task.conf b/openfpga_flow/tasks/openfpga_shell/full_testbench/configuration_chain/config/task.conf index 8318c1dea..9b4921136 100644 --- a/openfpga_flow/tasks/openfpga_shell/full_testbench/configuration_chain/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/full_testbench/configuration_chain/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k4_N4_40nm_cc_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k4_N4_tileable_40nm.xml From 068d9943e746c4d830d9d6c6e709f073512f6d87 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 11 Jun 2020 11:41:36 -0600 Subject: [PATCH 642/645] update all the templates and regression test cases with simulation settings --- .../duplicated_grid_pin_example_script.openfpga | 2 +- openfpga_flow/OpenFPGAShellScripts/example_script.openfpga | 2 +- .../fast_configuration_example_script.openfpga | 2 +- .../flatten_routing_example_script.openfpga | 2 +- .../OpenFPGAShellScripts/full_testbench_example_script.openfpga | 2 +- .../generate_fabric_example_script.openfpga | 2 +- .../generate_testbench_example_script.openfpga | 2 +- .../implicit_verilog_example_script.openfpga | 2 +- openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga | 2 +- .../OpenFPGAShellScripts/sdc_time_unit_example_script.openfpga | 2 +- .../tasks/openfpga_shell/behavioral_verilog/config/task.conf | 1 + .../tasks/openfpga_shell/bram/dpram16k/config/task.conf | 1 + .../tasks/openfpga_shell/bram/wide_dpram16k/config/task.conf | 2 +- .../tasks/openfpga_shell/duplicated_grid_pin/config/task.conf | 1 + .../openfpga_shell/fabric_chain/adder_chain/config/task.conf | 1 + .../openfpga_shell/fabric_chain/register_chain/config/task.conf | 1 + .../openfpga_shell/fabric_chain/scan_chain/config/task.conf | 1 + .../openfpga_shell/fixed_simulation_settings/config/task.conf | 1 + .../tasks/openfpga_shell/flatten_routing/config/task.conf | 1 + .../full_testbench/configuration_frame/config/task.conf | 1 + .../full_testbench/fast_configuration_frame/config/task.conf | 1 + .../full_testbench/fast_memory_bank/config/task.conf | 1 + .../full_testbench/flatten_memory/config/task.conf | 1 + .../openfpga_shell/full_testbench/memory_bank/config/task.conf | 1 + .../tasks/openfpga_shell/generate_fabric/config/task.conf | 1 + .../tasks/openfpga_shell/generate_testbench/config/task.conf | 1 + openfpga_flow/tasks/openfpga_shell/hard_adder/config/task.conf | 1 + .../tasks/openfpga_shell/implicit_verilog/config/task.conf | 1 + openfpga_flow/tasks/openfpga_shell/io/aib/config/task.conf | 1 + .../tasks/openfpga_shell/io/multi_io_capacity/config/task.conf | 1 + .../tasks/openfpga_shell/io/reduced_io/config/task.conf | 1 + .../tasks/openfpga_shell/lut_design/frac_lut/config/task.conf | 1 + .../lut_design/intermediate_buffer/config/task.conf | 1 + .../openfpga_shell/lut_design/single_mode/config/task.conf | 1 + .../openfpga_shell/mux_design/local_encoder/config/task.conf | 1 + .../openfpga_shell/mux_design/stdcell_mux2/config/task.conf | 1 + .../openfpga_shell/mux_design/tree_structure/config/task.conf | 1 + .../preconfig_testbench/configuration_chain/config/task.conf | 1 + .../preconfig_testbench/configuration_frame/config/task.conf | 1 + .../preconfig_testbench/flatten_memory/config/task.conf | 1 + .../preconfig_testbench/memory_bank/config/task.conf | 1 + .../tasks/openfpga_shell/sdc_time_unit/config/task.conf | 1 + openfpga_flow/tasks/openfpga_shell/spypad/config/task.conf | 1 + openfpga_flow/tasks/openfpga_shell/untileable/config/task.conf | 1 + 44 files changed, 44 insertions(+), 11 deletions(-) diff --git a/openfpga_flow/OpenFPGAShellScripts/duplicated_grid_pin_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/duplicated_grid_pin_example_script.openfpga index 83707cf53..ae729290e 100644 --- a/openfpga_flow/OpenFPGAShellScripts/duplicated_grid_pin_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/duplicated_grid_pin_example_script.openfpga @@ -6,7 +6,7 @@ vpr ${VPR_ARCH_FILE} ${VPR_TESTBENCH_BLIF} --clock_modeling route read_openfpga_arch -f ${OPENFPGA_ARCH_FILE} # Read OpenFPGA simulation settings -#read_openfpga_simulation_setting -f OPENFPGA_SIM_SETTING_FILE +read_openfpga_simulation_setting -f ${OPENFPGA_SIM_SETTING_FILE} # Annotate the OpenFPGA architecture to VPR data base # to debug use --verbose options diff --git a/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga index 83707cf53..ae729290e 100644 --- a/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/example_script.openfpga @@ -6,7 +6,7 @@ vpr ${VPR_ARCH_FILE} ${VPR_TESTBENCH_BLIF} --clock_modeling route read_openfpga_arch -f ${OPENFPGA_ARCH_FILE} # Read OpenFPGA simulation settings -#read_openfpga_simulation_setting -f OPENFPGA_SIM_SETTING_FILE +read_openfpga_simulation_setting -f ${OPENFPGA_SIM_SETTING_FILE} # Annotate the OpenFPGA architecture to VPR data base # to debug use --verbose options diff --git a/openfpga_flow/OpenFPGAShellScripts/fast_configuration_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/fast_configuration_example_script.openfpga index 56d05257c..fceabcaaa 100644 --- a/openfpga_flow/OpenFPGAShellScripts/fast_configuration_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/fast_configuration_example_script.openfpga @@ -6,7 +6,7 @@ vpr ${VPR_ARCH_FILE} ${VPR_TESTBENCH_BLIF} --clock_modeling route read_openfpga_arch -f ${OPENFPGA_ARCH_FILE} # Read OpenFPGA simulation settings -#read_openfpga_simulation_setting -f OPENFPGA_SIM_SETTING_FILE +read_openfpga_simulation_setting -f ${OPENFPGA_SIM_SETTING_FILE} # Annotate the OpenFPGA architecture to VPR data base # to debug use --verbose options diff --git a/openfpga_flow/OpenFPGAShellScripts/flatten_routing_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/flatten_routing_example_script.openfpga index fa376dbe3..5ab1c56b6 100644 --- a/openfpga_flow/OpenFPGAShellScripts/flatten_routing_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/flatten_routing_example_script.openfpga @@ -6,7 +6,7 @@ vpr ${VPR_ARCH_FILE} ${VPR_TESTBENCH_BLIF} --clock_modeling route read_openfpga_arch -f ${OPENFPGA_ARCH_FILE} # Read OpenFPGA simulation settings -#read_openfpga_simulation_setting -f OPENFPGA_SIM_SETTING_FILE +read_openfpga_simulation_setting -f ${OPENFPGA_SIM_SETTING_FILE} # Annotate the OpenFPGA architecture to VPR data base # to debug use --verbose options diff --git a/openfpga_flow/OpenFPGAShellScripts/full_testbench_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/full_testbench_example_script.openfpga index 83707cf53..ae729290e 100644 --- a/openfpga_flow/OpenFPGAShellScripts/full_testbench_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/full_testbench_example_script.openfpga @@ -6,7 +6,7 @@ vpr ${VPR_ARCH_FILE} ${VPR_TESTBENCH_BLIF} --clock_modeling route read_openfpga_arch -f ${OPENFPGA_ARCH_FILE} # Read OpenFPGA simulation settings -#read_openfpga_simulation_setting -f OPENFPGA_SIM_SETTING_FILE +read_openfpga_simulation_setting -f ${OPENFPGA_SIM_SETTING_FILE} # Annotate the OpenFPGA architecture to VPR data base # to debug use --verbose options diff --git a/openfpga_flow/OpenFPGAShellScripts/generate_fabric_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/generate_fabric_example_script.openfpga index 434c573e1..38ebc9d28 100644 --- a/openfpga_flow/OpenFPGAShellScripts/generate_fabric_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/generate_fabric_example_script.openfpga @@ -6,7 +6,7 @@ vpr ${VPR_ARCH_FILE} ${VPR_TESTBENCH_BLIF} --clock_modeling route read_openfpga_arch -f ${OPENFPGA_ARCH_FILE} # Read OpenFPGA simulation settings -#read_openfpga_simulation_setting -f OPENFPGA_SIM_SETTING_FILE +read_openfpga_simulation_setting -f ${OPENFPGA_SIM_SETTING_FILE} # Annotate the OpenFPGA architecture to VPR data base # to debug use --verbose options diff --git a/openfpga_flow/OpenFPGAShellScripts/generate_testbench_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/generate_testbench_example_script.openfpga index 6fe802168..971f8d374 100644 --- a/openfpga_flow/OpenFPGAShellScripts/generate_testbench_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/generate_testbench_example_script.openfpga @@ -6,7 +6,7 @@ vpr ${VPR_ARCH_FILE} ${VPR_TESTBENCH_BLIF} --clock_modeling route read_openfpga_arch -f ${OPENFPGA_ARCH_FILE} # Read OpenFPGA simulation settings -#read_openfpga_simulation_setting -f OPENFPGA_SIM_SETTING_FILE +read_openfpga_simulation_setting -f ${OPENFPGA_SIM_SETTING_FILE} # Annotate the OpenFPGA architecture to VPR data base # to debug use --verbose options diff --git a/openfpga_flow/OpenFPGAShellScripts/implicit_verilog_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/implicit_verilog_example_script.openfpga index 021488557..e86ba055e 100644 --- a/openfpga_flow/OpenFPGAShellScripts/implicit_verilog_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/implicit_verilog_example_script.openfpga @@ -6,7 +6,7 @@ vpr ${VPR_ARCH_FILE} ${VPR_TESTBENCH_BLIF} --clock_modeling route read_openfpga_arch -f ${OPENFPGA_ARCH_FILE} # Read OpenFPGA simulation settings -#read_openfpga_simulation_setting -f OPENFPGA_SIM_SETTING_FILE +read_openfpga_simulation_setting -f ${OPENFPGA_SIM_SETTING_FILE} # Annotate the OpenFPGA architecture to VPR data base # to debug use --verbose options diff --git a/openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga index ebd6c217c..dc7af60ae 100644 --- a/openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/mcnc_example_script.openfpga @@ -6,7 +6,7 @@ vpr ${VPR_ARCH_FILE} ${VPR_TESTBENCH_BLIF} --clock_modeling route --absorb_buffe read_openfpga_arch -f ${OPENFPGA_ARCH_FILE} # Read OpenFPGA simulation settings -#read_openfpga_simulation_setting -f OPENFPGA_SIM_SETTING_FILE +read_openfpga_simulation_setting -f ${OPENFPGA_SIM_SETTING_FILE} # Annotate the OpenFPGA architecture to VPR data base # to debug use --verbose options diff --git a/openfpga_flow/OpenFPGAShellScripts/sdc_time_unit_example_script.openfpga b/openfpga_flow/OpenFPGAShellScripts/sdc_time_unit_example_script.openfpga index 3411f6e87..36c3c88b9 100644 --- a/openfpga_flow/OpenFPGAShellScripts/sdc_time_unit_example_script.openfpga +++ b/openfpga_flow/OpenFPGAShellScripts/sdc_time_unit_example_script.openfpga @@ -6,7 +6,7 @@ vpr ${VPR_ARCH_FILE} ${VPR_TESTBENCH_BLIF} --clock_modeling route read_openfpga_arch -f ${OPENFPGA_ARCH_FILE} # Read OpenFPGA simulation settings -#read_openfpga_simulation_setting -f OPENFPGA_SIM_SETTING_FILE +read_openfpga_simulation_setting -f ${OPENFPGA_SIM_SETTING_FILE} # Annotate the OpenFPGA architecture to VPR data base # to debug use --verbose options diff --git a/openfpga_flow/tasks/openfpga_shell/behavioral_verilog/config/task.conf b/openfpga_flow/tasks/openfpga_shell/behavioral_verilog/config/task.conf index e11da844f..3693bd3db 100644 --- a/openfpga_flow/tasks/openfpga_shell/behavioral_verilog/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/behavioral_verilog/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_behavioral_40nm_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml diff --git a/openfpga_flow/tasks/openfpga_shell/bram/dpram16k/config/task.conf b/openfpga_flow/tasks/openfpga_shell/bram/dpram16k/config/task.conf index 4996f42e2..93e491745 100644 --- a/openfpga_flow/tasks/openfpga_shell/bram/dpram16k/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/bram/dpram16k/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_40nm.xml diff --git a/openfpga_flow/tasks/openfpga_shell/bram/wide_dpram16k/config/task.conf b/openfpga_flow/tasks/openfpga_shell/bram/wide_dpram16k/config/task.conf index 5cfc3ce57..3d89335d9 100644 --- a/openfpga_flow/tasks/openfpga_shell/bram/wide_dpram16k/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/bram/wide_dpram16k/config/task.conf @@ -16,7 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml - +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_wide_mem16K_40nm.xml diff --git a/openfpga_flow/tasks/openfpga_shell/duplicated_grid_pin/config/task.conf b/openfpga_flow/tasks/openfpga_shell/duplicated_grid_pin/config/task.conf index f2777f7ee..328203c08 100644 --- a/openfpga_flow/tasks/openfpga_shell/duplicated_grid_pin/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/duplicated_grid_pin/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml diff --git a/openfpga_flow/tasks/openfpga_shell/fabric_chain/adder_chain/config/task.conf b/openfpga_flow/tasks/openfpga_shell/fabric_chain/adder_chain/config/task.conf index 5e9533e36..4ab0447a3 100644 --- a/openfpga_flow/tasks/openfpga_shell/fabric_chain/adder_chain/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/fabric_chain/adder_chain/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_adder_column_chain_40nm_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_40nm.xml diff --git a/openfpga_flow/tasks/openfpga_shell/fabric_chain/register_chain/config/task.conf b/openfpga_flow/tasks/openfpga_shell/fabric_chain/register_chain/config/task.conf index 1b76cb3e9..06441b7f3 100644 --- a/openfpga_flow/tasks/openfpga_shell/fabric_chain/register_chain/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/fabric_chain/register_chain/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_chain_40nm_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_chain_40nm.xml diff --git a/openfpga_flow/tasks/openfpga_shell/fabric_chain/scan_chain/config/task.conf b/openfpga_flow/tasks/openfpga_shell/fabric_chain/scan_chain/config/task.conf index ac398aa4c..0d1f49c94 100644 --- a/openfpga_flow/tasks/openfpga_shell/fabric_chain/scan_chain/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/fabric_chain/scan_chain/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_40nm_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_40nm.xml diff --git a/openfpga_flow/tasks/openfpga_shell/fixed_simulation_settings/config/task.conf b/openfpga_flow/tasks/openfpga_shell/fixed_simulation_settings/config/task.conf index 4875931e3..813aabc03 100644 --- a/openfpga_flow/tasks/openfpga_shell/fixed_simulation_settings/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/fixed_simulation_settings/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k4_N4_40nm_fixed_sim_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/fixed_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k4_N4_tileable_40nm.xml diff --git a/openfpga_flow/tasks/openfpga_shell/flatten_routing/config/task.conf b/openfpga_flow/tasks/openfpga_shell/flatten_routing/config/task.conf index 2879e95ed..36bdb28bd 100644 --- a/openfpga_flow/tasks/openfpga_shell/flatten_routing/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/flatten_routing/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml diff --git a/openfpga_flow/tasks/openfpga_shell/full_testbench/configuration_frame/config/task.conf b/openfpga_flow/tasks/openfpga_shell/full_testbench/configuration_frame/config/task.conf index 2f5baca4d..e99b9c2ab 100644 --- a/openfpga_flow/tasks/openfpga_shell/full_testbench/configuration_frame/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/full_testbench/configuration_frame/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k4_N4_40nm_frame_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k4_N4_tileable_40nm.xml diff --git a/openfpga_flow/tasks/openfpga_shell/full_testbench/fast_configuration_frame/config/task.conf b/openfpga_flow/tasks/openfpga_shell/full_testbench/fast_configuration_frame/config/task.conf index 55c98dda1..0a95022d3 100644 --- a/openfpga_flow/tasks/openfpga_shell/full_testbench/fast_configuration_frame/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/full_testbench/fast_configuration_frame/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k4_N4_40nm_frame_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k4_N4_tileable_40nm.xml diff --git a/openfpga_flow/tasks/openfpga_shell/full_testbench/fast_memory_bank/config/task.conf b/openfpga_flow/tasks/openfpga_shell/full_testbench/fast_memory_bank/config/task.conf index 61b3d8b72..eb3aafbd3 100644 --- a/openfpga_flow/tasks/openfpga_shell/full_testbench/fast_memory_bank/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/full_testbench/fast_memory_bank/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k4_N4_40nm_bank_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k4_N4_tileable_40nm.xml diff --git a/openfpga_flow/tasks/openfpga_shell/full_testbench/flatten_memory/config/task.conf b/openfpga_flow/tasks/openfpga_shell/full_testbench/flatten_memory/config/task.conf index d2bf1bb9f..b893a163f 100644 --- a/openfpga_flow/tasks/openfpga_shell/full_testbench/flatten_memory/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/full_testbench/flatten_memory/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k4_N4_40nm_standalone_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k4_N4_tileable_40nm.xml diff --git a/openfpga_flow/tasks/openfpga_shell/full_testbench/memory_bank/config/task.conf b/openfpga_flow/tasks/openfpga_shell/full_testbench/memory_bank/config/task.conf index b000c0ba6..fadc53968 100644 --- a/openfpga_flow/tasks/openfpga_shell/full_testbench/memory_bank/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/full_testbench/memory_bank/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k4_N4_40nm_bank_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k4_N4_tileable_40nm.xml diff --git a/openfpga_flow/tasks/openfpga_shell/generate_fabric/config/task.conf b/openfpga_flow/tasks/openfpga_shell/generate_fabric/config/task.conf index 75bc6c703..077bafbc5 100644 --- a/openfpga_flow/tasks/openfpga_shell/generate_fabric/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/generate_fabric/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml diff --git a/openfpga_flow/tasks/openfpga_shell/generate_testbench/config/task.conf b/openfpga_flow/tasks/openfpga_shell/generate_testbench/config/task.conf index 9540b767f..02d4fb597 100644 --- a/openfpga_flow/tasks/openfpga_shell/generate_testbench/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/generate_testbench/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml diff --git a/openfpga_flow/tasks/openfpga_shell/hard_adder/config/task.conf b/openfpga_flow/tasks/openfpga_shell/hard_adder/config/task.conf index c0198598e..2d8c162d0 100644 --- a/openfpga_flow/tasks/openfpga_shell/hard_adder/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/hard_adder/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_40nm.xml diff --git a/openfpga_flow/tasks/openfpga_shell/implicit_verilog/config/task.conf b/openfpga_flow/tasks/openfpga_shell/implicit_verilog/config/task.conf index 4040caee1..36cc8208d 100644 --- a/openfpga_flow/tasks/openfpga_shell/implicit_verilog/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/implicit_verilog/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml diff --git a/openfpga_flow/tasks/openfpga_shell/io/aib/config/task.conf b/openfpga_flow/tasks/openfpga_shell/io/aib/config/task.conf index 0f4727657..ab8b7849e 100644 --- a/openfpga_flow/tasks/openfpga_shell/io/aib/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/io/aib/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml ##################################### # Debugging status diff --git a/openfpga_flow/tasks/openfpga_shell/io/multi_io_capacity/config/task.conf b/openfpga_flow/tasks/openfpga_shell/io/multi_io_capacity/config/task.conf index 8ae3286c5..c751ed64c 100644 --- a/openfpga_flow/tasks/openfpga_shell/io/multi_io_capacity/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/io/multi_io_capacity/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_multi_io_capacity_40nm.xml diff --git a/openfpga_flow/tasks/openfpga_shell/io/reduced_io/config/task.conf b/openfpga_flow/tasks/openfpga_shell/io/reduced_io/config/task.conf index 190acfda7..17b2c8564 100644 --- a/openfpga_flow/tasks/openfpga_shell/io/reduced_io/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/io/reduced_io/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_chain_mem16K_reduced_io_40nm.xml diff --git a/openfpga_flow/tasks/openfpga_shell/lut_design/frac_lut/config/task.conf b/openfpga_flow/tasks/openfpga_shell/lut_design/frac_lut/config/task.conf index 030c0d41e..39bbeb784 100644 --- a/openfpga_flow/tasks/openfpga_shell/lut_design/frac_lut/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/lut_design/frac_lut/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml diff --git a/openfpga_flow/tasks/openfpga_shell/lut_design/intermediate_buffer/config/task.conf b/openfpga_flow/tasks/openfpga_shell/lut_design/intermediate_buffer/config/task.conf index b2535bd7c..885569b51 100644 --- a/openfpga_flow/tasks/openfpga_shell/lut_design/intermediate_buffer/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/lut_design/intermediate_buffer/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_N10_intermediate_buffer_40nm_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_N10_tileable_40nm.xml diff --git a/openfpga_flow/tasks/openfpga_shell/lut_design/single_mode/config/task.conf b/openfpga_flow/tasks/openfpga_shell/lut_design/single_mode/config/task.conf index 4216a7482..0c735e07f 100644 --- a/openfpga_flow/tasks/openfpga_shell/lut_design/single_mode/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/lut_design/single_mode/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_N10_40nm_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_N10_tileable_40nm.xml diff --git a/openfpga_flow/tasks/openfpga_shell/mux_design/local_encoder/config/task.conf b/openfpga_flow/tasks/openfpga_shell/mux_design/local_encoder/config/task.conf index 76cf4b589..7b48cf6e2 100644 --- a/openfpga_flow/tasks/openfpga_shell/mux_design/local_encoder/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/mux_design/local_encoder/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_local_encoder_40nm_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml diff --git a/openfpga_flow/tasks/openfpga_shell/mux_design/stdcell_mux2/config/task.conf b/openfpga_flow/tasks/openfpga_shell/mux_design/stdcell_mux2/config/task.conf index 60df151e7..08bebdf04 100644 --- a/openfpga_flow/tasks/openfpga_shell/mux_design/stdcell_mux2/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/mux_design/stdcell_mux2/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_stdcell_mux_40nm_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml diff --git a/openfpga_flow/tasks/openfpga_shell/mux_design/tree_structure/config/task.conf b/openfpga_flow/tasks/openfpga_shell/mux_design/tree_structure/config/task.conf index 3f8b56582..48633266c 100644 --- a/openfpga_flow/tasks/openfpga_shell/mux_design/tree_structure/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/mux_design/tree_structure/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_tree_mux_40nm_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_40nm.xml diff --git a/openfpga_flow/tasks/openfpga_shell/preconfig_testbench/configuration_chain/config/task.conf b/openfpga_flow/tasks/openfpga_shell/preconfig_testbench/configuration_chain/config/task.conf index 3fd340a7b..9c9fc9627 100644 --- a/openfpga_flow/tasks/openfpga_shell/preconfig_testbench/configuration_chain/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/preconfig_testbench/configuration_chain/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k4_N4_40nm_cc_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k4_N4_tileable_40nm.xml diff --git a/openfpga_flow/tasks/openfpga_shell/preconfig_testbench/configuration_frame/config/task.conf b/openfpga_flow/tasks/openfpga_shell/preconfig_testbench/configuration_frame/config/task.conf index 4a41fc671..e55a6ea8e 100644 --- a/openfpga_flow/tasks/openfpga_shell/preconfig_testbench/configuration_frame/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/preconfig_testbench/configuration_frame/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k4_N4_40nm_frame_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k4_N4_tileable_40nm.xml diff --git a/openfpga_flow/tasks/openfpga_shell/preconfig_testbench/flatten_memory/config/task.conf b/openfpga_flow/tasks/openfpga_shell/preconfig_testbench/flatten_memory/config/task.conf index 1a02eb45f..78d1e818c 100644 --- a/openfpga_flow/tasks/openfpga_shell/preconfig_testbench/flatten_memory/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/preconfig_testbench/flatten_memory/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k4_N4_40nm_standalone_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k4_N4_tileable_40nm.xml diff --git a/openfpga_flow/tasks/openfpga_shell/preconfig_testbench/memory_bank/config/task.conf b/openfpga_flow/tasks/openfpga_shell/preconfig_testbench/memory_bank/config/task.conf index 75579406c..c9505c1d5 100644 --- a/openfpga_flow/tasks/openfpga_shell/preconfig_testbench/memory_bank/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/preconfig_testbench/memory_bank/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k4_N4_40nm_bank_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k4_N4_tileable_40nm.xml diff --git a/openfpga_flow/tasks/openfpga_shell/sdc_time_unit/config/task.conf b/openfpga_flow/tasks/openfpga_shell/sdc_time_unit/config/task.conf index 332020fab..11cd98766 100644 --- a/openfpga_flow/tasks/openfpga_shell/sdc_time_unit/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/sdc_time_unit/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k4_N4_40nm_cc_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k4_N4_tileable_40nm.xml diff --git a/openfpga_flow/tasks/openfpga_shell/spypad/config/task.conf b/openfpga_flow/tasks/openfpga_shell/spypad/config/task.conf index b42e17287..d7d55eb62 100644 --- a/openfpga_flow/tasks/openfpga_shell/spypad/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/spypad/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_depop50_spypad_40nm_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_tileable_adder_register_scan_chain_depop50_spypad_40nm.xml diff --git a/openfpga_flow/tasks/openfpga_shell/untileable/config/task.conf b/openfpga_flow/tasks/openfpga_shell/untileable/config/task.conf index 7dcf88915..a7e1939ed 100644 --- a/openfpga_flow/tasks/openfpga_shell/untileable/config/task.conf +++ b/openfpga_flow/tasks/openfpga_shell/untileable/config/task.conf @@ -16,6 +16,7 @@ verilog_output=true timeout_each_job = 20*60 fpga_flow=vpr_blif openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml +openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/auto_sim_openfpga.xml [ARCHITECTURES] arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/arch/vpr_only_templates/k6_frac_N10_40nm.xml From 58807bfcb3227e67e7744291a3b2db61570d0439 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 11 Jun 2020 11:58:18 -0600 Subject: [PATCH 643/645] remove simulation settings from openfpga arch data structure --- libopenfpga/libarchopenfpga/src/openfpga_arch.h | 3 --- libopenfpga/libarchopenfpga/src/read_xml_openfpga_arch.cpp | 6 ------ libopenfpga/libarchopenfpga/src/write_xml_openfpga_arch.cpp | 3 --- openfpga/src/base/openfpga_link_arch.cpp | 2 +- openfpga/src/base/openfpga_setup_command.cpp | 2 +- 5 files changed, 2 insertions(+), 14 deletions(-) diff --git a/libopenfpga/libarchopenfpga/src/openfpga_arch.h b/libopenfpga/libarchopenfpga/src/openfpga_arch.h index a3dd71a12..33d8f1b6c 100644 --- a/libopenfpga/libarchopenfpga/src/openfpga_arch.h +++ b/libopenfpga/libarchopenfpga/src/openfpga_arch.h @@ -29,9 +29,6 @@ struct Arch { /* Technology devices */ TechnologyLibrary tech_lib; - /* Simulation settings */ - SimulationSetting sim_setting; - /* Configuration protocol settings */ ConfigProtocol config_protocol; diff --git a/libopenfpga/libarchopenfpga/src/read_xml_openfpga_arch.cpp b/libopenfpga/libarchopenfpga/src/read_xml_openfpga_arch.cpp index 031946346..1d5f23e9e 100644 --- a/libopenfpga/libarchopenfpga/src/read_xml_openfpga_arch.cpp +++ b/libopenfpga/libarchopenfpga/src/read_xml_openfpga_arch.cpp @@ -100,12 +100,6 @@ openfpga::Arch read_xml_openfpga_arch(const char* arch_file_name) { /* Parse the pb_type annotation */ openfpga_arch.pb_type_annotations = read_xml_pb_type_annotations(xml_openfpga_arch, loc_data); - /* Second node should be */ - auto xml_simulation_settings = get_single_child(doc, "openfpga_simulation_setting", loc_data); - - /* Parse simulation settings to data structure */ - openfpga_arch.sim_setting = read_xml_simulation_setting(xml_simulation_settings, loc_data); - } catch (pugiutil::XmlError& e) { archfpga_throw(arch_file_name, e.line(), "%s", e.what()); diff --git a/libopenfpga/libarchopenfpga/src/write_xml_openfpga_arch.cpp b/libopenfpga/libarchopenfpga/src/write_xml_openfpga_arch.cpp index 468924ad2..2c5134d4e 100644 --- a/libopenfpga/libarchopenfpga/src/write_xml_openfpga_arch.cpp +++ b/libopenfpga/libarchopenfpga/src/write_xml_openfpga_arch.cpp @@ -63,9 +63,6 @@ void write_xml_openfpga_arch(const char* fname, fp << "" << "\n"; - /* Write the simulation */ - write_xml_simulation_setting(fp, fname, openfpga_arch.sim_setting); - /* Close the file stream */ fp.close(); } diff --git a/openfpga/src/base/openfpga_link_arch.cpp b/openfpga/src/base/openfpga_link_arch.cpp index c67619caa..af75ff6a2 100644 --- a/openfpga/src/base/openfpga_link_arch.cpp +++ b/openfpga/src/base/openfpga_link_arch.cpp @@ -150,7 +150,7 @@ int link_arch(OpenfpgaContext& openfpga_ctx, /* OVERWRITE the simulation setting in openfpga context from the arch * TODO: This will be removed when openfpga flow is updated */ - openfpga_ctx.mutable_simulation_setting() = openfpga_ctx.mutable_arch().sim_setting; + //openfpga_ctx.mutable_simulation_setting() = openfpga_ctx.mutable_arch().sim_setting; annotate_simulation_setting(g_vpr_ctx.atom(), openfpga_ctx.net_activity(), openfpga_ctx.mutable_simulation_setting()); diff --git a/openfpga/src/base/openfpga_setup_command.cpp b/openfpga/src/base/openfpga_setup_command.cpp index e9d759b1f..bf2412a6f 100644 --- a/openfpga/src/base/openfpga_setup_command.cpp +++ b/openfpga/src/base/openfpga_setup_command.cpp @@ -366,8 +366,8 @@ void add_openfpga_setup_commands(openfpga::Shell& shell) { std::vector link_arch_dependent_cmds; link_arch_dependent_cmds.push_back(read_arch_cmd_id); /* TODO: This will be uncommented when openfpga flow script is updated - * link_arch_dependent_cmds.push_back(read_sim_setting_cmd_id); */ + link_arch_dependent_cmds.push_back(read_sim_setting_cmd_id); link_arch_dependent_cmds.push_back(vpr_cmd_id); ShellCommandId link_arch_cmd_id = add_openfpga_link_arch_command(shell, openfpga_setup_cmd_class, From 60dd37e08603c5a863651a35ca4aac2037885dcc Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 11 Jun 2020 12:05:12 -0600 Subject: [PATCH 644/645] remove simulation settings from openfpga arch XML update travis to split CI tests fix errors in travis configuration fixing travis errors in scripts keep fixing travis fix travis on build.sh bug fixing in travis CI bug fix in travis regression test run fixing bugs in the travis scripts bug fix in travis script: remove common.sh in regression test call keep bug fixing in travis --- .travis.yml | 180 +++++++++--------- .travis/build.sh | 17 ++ .travis/install.sh | 25 +-- .travis/openfpga_vpr7_reg_test.sh | 30 +++ .../{script.sh => openfpga_vpr8_reg_test.sh} | 39 +--- .../k4_N4_40nm_bank_openfpga.xml | 33 ---- .../openfpga_arch/k4_N4_40nm_cc_openfpga.xml | 33 ---- .../k4_N4_40nm_fixed_sim_openfpga.xml | 33 ---- .../k4_N4_40nm_frame_openfpga.xml | 33 ---- .../k4_N4_40nm_standalone_openfpga.xml | 33 ---- .../openfpga_arch/k6_N10_40nm_openfpga.xml | 33 ---- ..._N10_intermediate_buffer_40nm_openfpga.xml | 33 ---- .../k6_frac_N10_40nm_openfpga.xml | 34 ---- .../k6_frac_N10_adder_chain_40nm_openfpga.xml | 34 ---- ...c_N10_adder_chain_mem16K_40nm_openfpga.xml | 34 ---- ...0_adder_chain_mem16K_aib_40nm_openfpga.xml | 34 ---- ...c_N10_adder_column_chain_40nm_openfpga.xml | 34 ---- ...N10_adder_register_chain_40nm_openfpga.xml | 34 ---- ...dder_register_scan_chain_40nm_openfpga.xml | 34 ---- ...ister_scan_chain_depop50_40nm_openfpga.xml | 34 ---- ...can_chain_depop50_spypad_40nm_openfpga.xml | 34 ---- .../k6_frac_N10_behavioral_40nm_openfpga.xml | 34 ---- ...6_frac_N10_local_encoder_40nm_openfpga.xml | 34 ---- .../k6_frac_N10_spyio_40nm_openfpga.xml | 34 ---- .../k6_frac_N10_stdcell_mux_40nm_openfpga.xml | 34 ---- .../k6_frac_N10_tree_mux_40nm_openfpga.xml | 34 ---- 26 files changed, 148 insertions(+), 850 deletions(-) create mode 100644 .travis/build.sh create mode 100755 .travis/openfpga_vpr7_reg_test.sh rename .travis/{script.sh => openfpga_vpr8_reg_test.sh} (82%) diff --git a/.travis.yml b/.travis.yml index 0b5ce92fb..13b89c162 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,105 +14,99 @@ cache: # Currently sudo is not required, NO ENV is used # Supported Operating systems -#os: -# - linux -# - osx -# Create a matrix to branch the building environment -matrix: - include: - - os: linux - # Compiler is specified in ./travis/common.sh - sudo: false - dist: bionic - compiler: g++-8 - addons: - apt: - sources: - - ubuntu-toolchain-r-test # For newer GCC - - llvm_toolchain-trusty-7 - packages: - - autoconf - - automake - - bash - - bison - - build-essential - - cmake - - ctags - - curl - - doxygen - - flex - - fontconfig - - g++-8 - - gcc-8 - - gdb - - git - - gperf - - iverilog - - libcairo2-dev - - libevent-dev - - libfontconfig1-dev - - liblist-moreutils-perl - - libncurses5-dev - - libx11-dev - - libxft-dev - - libxml++2.6-dev - - perl - - python - - python-lxml - - texinfo - - time - - valgrind - - zip - - qt5-default - - clang-format-7 -# - os: osx -# osx_image: xcode10.2 # we target latest MacOS Mojave -# sudo: true -# compiler: gcc-4.9 # Use clang instead of gcc in MacOS -# addons: -# homebrew: -# packages: -# - bison -# - cmake -# - ctags -# - flex -# - fontconfig -# - git -# - gcc@6 -# - gcc@4.9 -# - gawk -# - icarus-verilog -# - libxml++ -# - qt5 +dist: bionic +compiler: g++-8 +addons: + apt: + sources: + - ubuntu-toolchain-r-test # For newer GCC + - llvm_toolchain-trusty-7 + packages: + - autoconf + - automake + - bash + - bison + - build-essential + - cmake + - ctags + - curl + - doxygen + - flex + - fontconfig + - g++-8 + - gcc-8 + - gdb + - git + - gperf + - iverilog + - libcairo2-dev + - libevent-dev + - libfontconfig1-dev + - liblist-moreutils-perl + - libncurses5-dev + - libx11-dev + - libxft-dev + - libxml++2.6-dev + - perl + - python + - python-lxml + - texinfo + - time + - valgrind + - zip + - qt5-default + - clang-format-7 +#- os: osx +# osx_image: xcode10.2 # we target latest MacOS Mojave +# sudo: true +# compiler: gcc-4.9 # Use clang instead of gcc in MacOS +# addons: +# homebrew: +# packages: +# - bison +# - cmake +# - ctags +# - flex +# - fontconfig +# - git +# - gcc@6 +# - gcc@4.9 +# - gawk +# - icarus-verilog +# - libxml++ +# - qt5 -before_install: +before_script: - source .travis/common.sh - -install: - - DEPS_DIR="${HOME}/deps" - - mkdir -p ${DEPS_DIR} && cd ${DEPS_DIR} - - | - if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then - CMAKE_URL="https://cmake.org/files/v3.16/cmake-3.16.3-Linux-x86_64.tar.gz" - mkdir -p cmake && travis_retry wget --no-clobber --no-check-certificate --quiet -O - ${CMAKE_URL} | tar --strip-components=1 -xz -C cmake - export PATH=${DEPS_DIR}/cmake/bin:${PATH} - echo ${PATH} - else - brew install cmake || brew upgrade cmake - fi - - cmake --version - - cd - - source .travis/install.sh +stages: + - name: Test + if: type != cron + +jobs: + include: + - stage: Test + name: "OpenFPGA + VPR7 regression tests" + script: + - source .travis/build.sh + - source .travis/openfpga_vpr7_reg_test.sh + + - stage: Test + name: "OpenFPGA + VPR8 regression tests" + script: + - source .travis/build.sh + - source .travis/openfpga_vpr8_reg_test.sh + +#after_failure: +# - .travis/after_failure.sh + +#after_success: +# - .travis/after_success.sh + script: - - .travis/script.sh - #- .travis/regression.sh + - true -after_failure: - - .travis/after_failure.sh - -after_success: - - .travis/after_success.sh notifications: slack: secure: L8tzicFh+EKcK21GBA2m3rQ3jmnDdqiRXIZcb0iqYlhT0V5asYvCqwlpPDUDV1wmBXqPgRJBI/jitAJlKFWu74pLTVc6FscUIDYM7S0DJfHEcufLknZx88lMmmV0IsYLQe3/s89tWoudMf1bNBo/8YWzLDffqUQ7s/rTPD9SWLppb01X0Xm158oDlA0rWETs35nuNFgJxWcSyIyIvnRNE3dHjzmBETUR9mYDsUSYlcOI44FMD8rE6emicdkqdn1zVxScobrl4Dt2bPsMfKopgIKK1x+38AuaqQa7t5F5ICnF0WfxmQ6/TcRNwIij0fDu68w/fcU8SyV+Ex5aZBKYUU7PG7ELTOq+q1geDoTlbguvFSIT4EzqErc4hbJmcUn79BKLhdjshZtGihKatntJx2faXYNYGFjwmnPFRYpqsozydztgMjzv4prZ5yoh7jhoDiGj44QcpXlQ9otM17JdfqveowMLHBYzATsxIRG93irZfXG/E3S8FvXg8mYOIEn8UK7H6i8VWL3JHlw8RbpLdNLswZOUlpEaDAeTm5tvYcw7FGH2nlZ2e5aXLxN6dTovSSRztQHbWdJTGG0N+xldBXcCiChmok4nXGReIkMc+99nZjRsiCB0R16tCNb25/p7NAVkItfVe1qRTzdnhi1hdE7LPURK4kxoFRJ6sFVuYjw= diff --git a/.travis/build.sh b/.travis/build.sh new file mode 100644 index 000000000..d7d2fcef9 --- /dev/null +++ b/.travis/build.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +source .travis/common.sh +set -e + +start_section "OpenFPGA.build" "${GREEN}Building..${NC}" +cd ${TRAVIS_BUILD_DIR} +mkdir build +cd build + +if [[ $TRAVIS_OS_NAME == 'osx' ]]; then + cmake .. -DCMAKE_BUILD_TYPE=debug -DENABLE_VPR_GRAPHICS=off +else + cmake .. -DCMAKE_BUILD_TYPE=debug +fi + make -j16 +end_section "OpenFPGA.build" diff --git a/.travis/install.sh b/.travis/install.sh index 46d535342..23841212d 100755 --- a/.travis/install.sh +++ b/.travis/install.sh @@ -4,18 +4,19 @@ source .travis/common.sh set -e ## Install necessary package which is not available on Travis CI -#export DEPS_DIR="${HOME}/deps" -#mkdir -p ${DEPS_DIR} && cd ${DEPS_DIR} -## Install CMake -#if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then -# export CMAKE_URL="https://cmake.org/files/v3.13/cmake-3.13.0-rc3-Linux-x86_64.tar.gz" -# mkdir -p cmake && travis_retry wget --no-check-certificate --quiet -O - ${CMAKE_URL} | tar --strip-components=1 -xz -C cmake -# export PATH=${DEPS_DIR}/cmake/bin:${PATH} -# echo ${PATH} -#else -# brew install cmake || brew upgrade cmake -#fi -# cmake --version +export DEPS_DIR="${HOME}/deps" +mkdir -p ${DEPS_DIR} && cd ${DEPS_DIR} +# Install CMake +if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then + export CMAKE_URL="https://cmake.org/files/v3.16/cmake-3.16.3-Linux-x86_64.tar.gz" + mkdir -p cmake && travis_retry wget --no-check-certificate --quiet -O - ${CMAKE_URL} | tar --strip-components=1 -xz -C cmake + export PATH=${DEPS_DIR}/cmake/bin:${PATH} + echo ${PATH} +else + brew install cmake || brew upgrade cmake +fi +cmake --version +cd - # ## Install latest iVerilog. Since no deb is provided, compile from source codes #if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then diff --git a/.travis/openfpga_vpr7_reg_test.sh b/.travis/openfpga_vpr7_reg_test.sh new file mode 100755 index 000000000..c04d202a4 --- /dev/null +++ b/.travis/openfpga_vpr7_reg_test.sh @@ -0,0 +1,30 @@ +#!/bin/bash +set -e + +start_section "OpenFPGA+VPR7.TaskTun" "${GREEN}..Running_Regression..${NC}" +cd ${TRAVIS_BUILD_DIR} + +############################################### +# OpenFPGA with VPR7 +# TO BE DEPRECATED +############################################## +echo -e "Testing single-mode architectures"; +python3 openfpga_flow/scripts/run_fpga_task.py single_mode --debug --show_thread_logs +#python3 openfpga_flow/scripts/run_fpga_task.py s298 + +echo -e "Testing multi-mode architectures"; +python3 openfpga_flow/scripts/run_fpga_task.py multi_mode --maxthreads 4 --debug --show_thread_logs + +echo -e "Testing compact routing techniques"; +python3 openfpga_flow/scripts/run_fpga_task.py compact_routing --debug --show_thread_logs + +echo -e "Testing tileable architectures"; +python3 openfpga_flow/scripts/run_fpga_task.py tileable_routing --debug --show_thread_logs + +echo -e "Testing Verilog generation with explicit port mapping "; +python3 openfpga_flow/scripts/run_fpga_task.py explicit_verilog --debug --show_thread_logs + +echo -e "Testing Verilog generation with grid pin duplication "; +python3 openfpga_flow/scripts/run_fpga_task.py duplicate_grid_pin --debug --show_thread_logs + +end_section "OpenFPGA+VPR7.TaskTun" diff --git a/.travis/script.sh b/.travis/openfpga_vpr8_reg_test.sh similarity index 82% rename from .travis/script.sh rename to .travis/openfpga_vpr8_reg_test.sh index 907e1a3cf..046f8210f 100755 --- a/.travis/script.sh +++ b/.travis/openfpga_vpr8_reg_test.sh @@ -1,46 +1,9 @@ #!/bin/bash -source .travis/common.sh set -e -start_section "OpenFPGA.build" "${GREEN}Building..${NC}" -mkdir build -cd build - -if [[ $TRAVIS_OS_NAME == 'osx' ]]; then - cmake .. -DCMAKE_BUILD_TYPE=debug -DENABLE_VPR_GRAPHICS=off -else - cmake .. -DCMAKE_BUILD_TYPE=debug -fi - make -j16 -end_section "OpenFPGA.build" - - start_section "OpenFPGA.TaskTun" "${GREEN}..Running_Regression..${NC}" -cd - - -############################################### -# OpenFPGA with VPR7 -# TO BE DEPRECATED -############################################## -echo -e "Testing single-mode architectures"; -python3 openfpga_flow/scripts/run_fpga_task.py single_mode --debug --show_thread_logs -#python3 openfpga_flow/scripts/run_fpga_task.py s298 - -echo -e "Testing multi-mode architectures"; -python3 openfpga_flow/scripts/run_fpga_task.py multi_mode --maxthreads 4 --debug --show_thread_logs - -echo -e "Testing compact routing techniques"; -python3 openfpga_flow/scripts/run_fpga_task.py compact_routing --debug --show_thread_logs - -echo -e "Testing tileable architectures"; -python3 openfpga_flow/scripts/run_fpga_task.py tileable_routing --debug --show_thread_logs - -echo -e "Testing Verilog generation with explicit port mapping "; -python3 openfpga_flow/scripts/run_fpga_task.py explicit_verilog --debug --show_thread_logs - -echo -e "Testing Verilog generation with grid pin duplication "; -python3 openfpga_flow/scripts/run_fpga_task.py duplicate_grid_pin --debug --show_thread_logs +cd ${TRAVIS_BUILD_DIR} ############################################### # OpenFPGA Shell with VPR8 diff --git a/openfpga_flow/openfpga_arch/k4_N4_40nm_bank_openfpga.xml b/openfpga_flow/openfpga_arch/k4_N4_40nm_bank_openfpga.xml index fe7d3f1b8..14a7881ef 100644 --- a/openfpga_flow/openfpga_arch/k4_N4_40nm_bank_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k4_N4_40nm_bank_openfpga.xml @@ -193,36 +193,3 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/openfpga_flow/openfpga_arch/k4_N4_40nm_cc_openfpga.xml b/openfpga_flow/openfpga_arch/k4_N4_40nm_cc_openfpga.xml index 298e0c0c0..13eb292b0 100644 --- a/openfpga_flow/openfpga_arch/k4_N4_40nm_cc_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k4_N4_40nm_cc_openfpga.xml @@ -193,36 +193,3 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/openfpga_flow/openfpga_arch/k4_N4_40nm_fixed_sim_openfpga.xml b/openfpga_flow/openfpga_arch/k4_N4_40nm_fixed_sim_openfpga.xml index 677061785..13eb292b0 100644 --- a/openfpga_flow/openfpga_arch/k4_N4_40nm_fixed_sim_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k4_N4_40nm_fixed_sim_openfpga.xml @@ -193,36 +193,3 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/openfpga_flow/openfpga_arch/k4_N4_40nm_frame_openfpga.xml b/openfpga_flow/openfpga_arch/k4_N4_40nm_frame_openfpga.xml index a0e0194b5..da2322044 100644 --- a/openfpga_flow/openfpga_arch/k4_N4_40nm_frame_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k4_N4_40nm_frame_openfpga.xml @@ -194,36 +194,3 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/openfpga_flow/openfpga_arch/k4_N4_40nm_standalone_openfpga.xml b/openfpga_flow/openfpga_arch/k4_N4_40nm_standalone_openfpga.xml index fc97f75c1..05b096a03 100644 --- a/openfpga_flow/openfpga_arch/k4_N4_40nm_standalone_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k4_N4_40nm_standalone_openfpga.xml @@ -194,36 +194,3 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/openfpga_flow/openfpga_arch/k6_N10_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_N10_40nm_openfpga.xml index e6e53d9f6..9512672a9 100644 --- a/openfpga_flow/openfpga_arch/k6_N10_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_N10_40nm_openfpga.xml @@ -193,36 +193,3 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/openfpga_flow/openfpga_arch/k6_N10_intermediate_buffer_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_N10_intermediate_buffer_40nm_openfpga.xml index b211bd040..2127c0b3c 100644 --- a/openfpga_flow/openfpga_arch/k6_N10_intermediate_buffer_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_N10_intermediate_buffer_40nm_openfpga.xml @@ -194,36 +194,3 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml index 9b20ab2a2..222d0d3e6 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_40nm_openfpga.xml @@ -224,37 +224,3 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml index 10051a9ca..be9bd0d3b 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_40nm_openfpga.xml @@ -249,37 +249,3 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml index 40a451b55..6dfb6ac71 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_40nm_openfpga.xml @@ -266,37 +266,3 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml index 9e608ead9..81dfd17ce 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_chain_mem16K_aib_40nm_openfpga.xml @@ -278,37 +278,3 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_column_chain_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_column_chain_40nm_openfpga.xml index 79b82375f..9a5d94056 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_column_chain_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_column_chain_40nm_openfpga.xml @@ -249,37 +249,3 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_chain_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_chain_40nm_openfpga.xml index 33cfa9e61..d2cb8a163 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_chain_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_chain_40nm_openfpga.xml @@ -252,37 +252,3 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_40nm_openfpga.xml index 460c8743e..129e11f71 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_40nm_openfpga.xml @@ -258,37 +258,3 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_depop50_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_depop50_40nm_openfpga.xml index 4bb4a24a2..f22ec1fc9 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_depop50_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_depop50_40nm_openfpga.xml @@ -253,37 +253,3 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_depop50_spypad_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_depop50_spypad_40nm_openfpga.xml index e1dde16b6..650e30a6b 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_depop50_spypad_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_adder_register_scan_chain_depop50_spypad_40nm_openfpga.xml @@ -337,37 +337,3 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_behavioral_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_behavioral_40nm_openfpga.xml index ebad2d203..2d6a765d0 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_behavioral_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_behavioral_40nm_openfpga.xml @@ -224,37 +224,3 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_local_encoder_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_local_encoder_40nm_openfpga.xml index b32deb8a9..b351666a7 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_local_encoder_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_local_encoder_40nm_openfpga.xml @@ -224,37 +224,3 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml index c980065bb..94c8f9fb8 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_spyio_40nm_openfpga.xml @@ -228,37 +228,3 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_stdcell_mux_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_stdcell_mux_40nm_openfpga.xml index 2b42cdced..794f10d4f 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_stdcell_mux_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_stdcell_mux_40nm_openfpga.xml @@ -216,37 +216,3 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/openfpga_flow/openfpga_arch/k6_frac_N10_tree_mux_40nm_openfpga.xml b/openfpga_flow/openfpga_arch/k6_frac_N10_tree_mux_40nm_openfpga.xml index c26c30f31..e9081fa86 100644 --- a/openfpga_flow/openfpga_arch/k6_frac_N10_tree_mux_40nm_openfpga.xml +++ b/openfpga_flow/openfpga_arch/k6_frac_N10_tree_mux_40nm_openfpga.xml @@ -215,37 +215,3 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From aaa52b6e89241645feec6d4cb35ea708e73d0d1a Mon Sep 17 00:00:00 2001 From: tangxifan Date: Thu, 11 Jun 2020 18:33:47 -0600 Subject: [PATCH 645/645] start using multiple jobs in travis CI --- .travis/build.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis/build.sh b/.travis/build.sh index d7d2fcef9..254133177 100644 --- a/.travis/build.sh +++ b/.travis/build.sh @@ -13,5 +13,9 @@ if [[ $TRAVIS_OS_NAME == 'osx' ]]; then else cmake .. -DCMAKE_BUILD_TYPE=debug fi - make -j16 +make -j16 + +# Return to upper directory +cd ${TRAVIS_BUILD_DIR} + end_section "OpenFPGA.build"