From aa66042dfb0a5063f7b3746f4cdad77c056ce59d Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sat, 29 Feb 2020 15:19:02 -0700 Subject: [PATCH 001/136] 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 002/136] 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 003/136] 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 004/136] 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 005/136] 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 006/136] 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 007/136] 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 008/136] 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 009/136] 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 010/136] 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 011/136] 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 012/136] 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 013/136] 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 014/136] 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 015/136] 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 016/136] 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 017/136] 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 018/136] 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 019/136] 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 020/136] 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 021/136] 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 022/136] 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 023/136] 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 024/136] 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 025/136] 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 026/136] 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 027/136] 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 028/136] 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 029/136] 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 030/136] 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 031/136] 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 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%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 043/136] 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 044/136] 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 045/136] 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 046/136] 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 047/136] 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 048/136] 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 049/136] 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 050/136] 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 051/136] 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 052/136] 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 053/136] 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 054/136] 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 055/136] 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 056/136] 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 057/136] 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 058/136] 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 059/136] 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 060/136] 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 061/136] 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 062/136] 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 063/136] 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 064/136] 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 065/136] 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 066/136] 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 067/136] 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 068/136] 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 069/136] 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 070/136] 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 071/136] 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 072/136] 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 073/136] 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 074/136] 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 075/136] 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 076/136] 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 077/136] 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 081/136] 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 082/136] 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 083/136] 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 084/136] 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 085/136] 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 086/136] 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 087/136] 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 088/136] 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 089/136] 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 090/136] 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 091/136] 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 092/136] 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 093/136] 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 094/136] 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 095/136] 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 096/136] 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 097/136] 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 098/136] 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 099/136] 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 100/136] 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 101/136] 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 102/136] 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 103/136] 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 104/136] 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 105/136] 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 106/136] 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 107/136] 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 108/136] 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 109/136] 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 110/136] 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 111/136] 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 112/136] 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 113/136] 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 114/136] 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 115/136] 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 116/136] 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 117/136] 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 118/136] 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 119/136] 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 120/136] 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 121/136] 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 122/136] 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 123/136] 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 124/136] 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 zcmZ_01ymf%);5e~7$it=w;;g=2{yO~clThyVQ>vj2pZg7g9i%)cXzko4#7fz0Qs78 z&b{aU?^-`=4c+uqRafoWRkh`L2v=5=evV3v3I_-GT;`30DjXaFAMjZMgMcT~{9>ZO z1>RXzS`4mojPwBbBi3A7#zH{>juE&A!-3#IaEMPu;1GacFaEiQ2kwdCp8fS44o(xE z`Xr z_(sPW4h{|H=>rd!mO%&%1HxEZu=DZpLD)DT92_h_2^MEhdlwTA7JFx!zZ>~a zI}+y3rZ6i<7b^#Q%BOZs-a5Ft2vSo&z39I`fA`bH%HrQQ**pI;EMS0;r*9zatZb0~ zwhdGjczOy|f?1gZZ+>cDh+W{XlK-D)|Gww%^2+ZVTpR!?z^qJV>|M-ZjxazXf7Ryr zr}qDUiT~S{s=2d+t?N@|4SOpWq5m}Se;)teTH+3Nz)PK-pL)#o&pZG3*+1n4AW!1@ zAMyP?H-9|^CPxTW0P^377D645{`e6NP6SRyLR8%Y{@4I{VOC;J2+S~5P{NRdaBV13 zGmnR-UcTV1_x@;UZmfUlTSV(IV_+*mwK1f`22n)?8=IMdv6bL5+nqJu!M^P!PZQ6e z0&HAhyfsx`a4&Ok+iZ6vRZ87a{C3Ja+=w;MpOO*`M)>PuMiyWamE>q_o{5udgoTBK z1TQM`*99klZa2z2yxeq?%I}{?z-53OA&MAp*D+~`{6MVc-)6uFh1ghB#jmB}#QtrB z%;Z()vEjV$#LO7@&?T=x#WUg6L&{dN>L;{nlGP|3#54cI#oDX zZma@@zkb^TCg!rneYl!dnrU*jTzdcK!D`Vwxlo$IHzCQ;%j$5xih|2#uAH$>BY!GJ z=QU+#`pR2qEKRY>R7c+7NL8ifA2 zzpO}N(bF~Ui2>x8tzdT;D5}tnlm4!}mY5ih3{b z9&ff{=IX4bKYo5|GQRswCZ28%(tfXso1I8fM}#)X4Ph4P7xN-Mm!(lqJo~lkZuHIW zKC24ScGH!rar*3OE~@)uJ~M0i-tHvV-R`DO<7w%Owm)2xRJ2?ST@BF0V70Ba{dx3$ z3;)5s{lV=Hkm$i-Syhovt%W4bPzdwR2b$;{EkjSqo#yR0xnsx!5vRoji9?G?a;Q#J z3twF)>$hXEA8o(wv2Ez8BJf%A8rQta7=)O38du#&9!?zIM`W(N-b|EA%Xn@w_3m!e z<=2`wgv|G9(jxZeukJC3Q94l}%L(zHex>pKIji*%i}_OE_i?SugedZ)svO?HMk*=v zuq1!~;n(+Kpz}2!)(h-C@PiEfZq2_zCvL~zKgPSQx@z6^3qR)WPk)jo_bj;H-SwK& zv7BuD?ziku!905&nT~4Yce_)nlr8jp%8VB7s{dr_gBGzBtRd)DKGiSN{&=CrWjEFG zP_GH~Nv+pEisV80A_yQwh?oMgz#L$vr+hnYmK!}>fDg5-pViD44FXM)dmozw({0U) zrBt3&wLelDHmeGgfke@p`2YdcU<~#C9OM5{@a;$2a$>TEmVw5BqDiRbUZ#7)GxGVK zSC}(mt{G-UPfE3I8>DC76oSEj%r?J=R@+V}&9=;-_}4lRMtXq<%knYlvT{SXK5kgHpQ+H};e2yjV|KOX{bSNH+qWpeujZ86`MEi*vzmI7_qUe^y#z+;DWrZk8`l@aFBDTbLWJ(m#JPe%f83Vsq?aAqeLux^ z4S((NQgxZt&{{M{4BJW414MKL$nx{%Qk_-)yA_vdg;L1~d^azTP22Rmk3RPP&k}4V zLDR)b#r1Q#a;=0vnG>|;l!R`BLoq2Tm*6ozv#^F)xz9FLtqpnb6fc1n6)Z8srBOzjf`~ZXwdn}xfjyu;%wKgXTu&<;nM9B1 z164zgHffLOfyY{lr<*FdGRs;>;m~Hl=w^W2tqxYTM|iJdKjd9RNN64uXwJ+A(0K^z zvPHTbGdbaj53@R6Z;3k6#nPZW-Ar^t>$2b|6rsV5XVoQMb!5(|1Wk5|d3(`CRD~GB=iD~6S1epL15}}W zEVX8yq|>)a(9Ajq{k7tJa^Gz0d$LfI|MUBw$Hq4*q)zLdE>4Y6I?2h|M(^fAN$g8J z4i;;Rd&TlEf{@h$$W97wO#_yj&$`e~yn`{q}7q>VaV zrTlZ0=X?1b-yQzA#nQq#tWseyoIb0>afY|5b_~jR3bb6U&D(HENk0Dd`(?-;!b!m} z%+T|Ca&t8;iQMugsPFB@oZ@(`IJdzjHWE^R4~=mo=J5_ZsGXM#qEVXsxe`swnGIc+ z2LwHC5c2~=31`@Ntxe#y9=#eOwyMOuW2ap&nZsO%UKQhp2;Zy+xsgBW*4YEwd!C13 z*DEj4FO{)m4yb&DGp>TZzppjSFXnFY0v^8%v({$4fAqxTaoJbq7rYQ12oNVXz^E*W z4Z1On<||^zN(_!TrMmAthh>vtz$2Ze*;LkapG~dnpx}+ke#bXNvS{@n^1VOr$2S~u zEU(tX1>PZ37e(E*_v6?1_l+KWQj;a;(%v>%X9zQ6xG=17PCNXYSziCBbY>uduZqT z5xV}fCqZoYl$4@5(!73mL1B0avx4KwORvMFfnDSx%AXwhg_=Om?hA2CkcBrq^MqTaES$&Q+=~QnBi9-A1_uqe3*~(f82$*^DmOQujJkr*^w}NAJB4Um0DLHgW78DbC!pcba=uWYEsY7Ry?+% zf@yJivsl!$80u}1fAiX744pDNzQC;P6~myr}+7vqHC&EjZ}fea%x^y3`47 zIIy40oz$e*tlrIP3F$9cy^yA4&m?Vx2Q`zJkTx)ji%ep41-b3AGCSuuGbX=XrBFnZ zzIQD-?Pg?8Lx^Ma|5HtZatF7R8uab(MC2fJ$GqKjOhN=RG&<%cc1^b7?rf< z_u*Y)i)*x(aBfSqA;mJ{rEMrX7HZh7`fXe>yR#KWqLYUF1_Y;`p2`m{`dpPMH31JS zCv0V5L>h*n1)O8N;>g1qfIeFz1@t79ow-KRkjRH+lADm~&9lfx(}GT_1y@~6hPf}3 zj{1jj5nJ7OQ4ug|CDO=`gWuXzLS@Xf_Ab&`2_bSLSRFaUNz3G3`&7`Ax@i{|v-y`x zeM*Glq;3-_V^*M!NT&I0+$@v*h(3*W4z7GAwD76-|}a-Jbz z216?%%dI$*7qKi*?V$>^S_}qPl`czZ(=ie5+)RwT=nX> z_3`fLstXN*`{@ujG=ds&CAgPUmdPF&}~4ddkmk7}aqyyv@G3D1p;LwF&m@%U&?T z2KF2Eflhf4ZkR%0*R+ywrpHkY(+*akF}m{mPo|FIUMC|~&eNDCvJi8=DI4BxjIm>fraw<-UUPgFU-JNl{yZ zL2fXLc!G!M5t(M1o7ve#y3@|ORgz88&K3WUQJ*4;gBATIpM2(8pH)n()7dHv!K(>s z#8?j@cJYWs*(+LOd)DwYUs5UDbLNf3zVf02d1%Z18;5I^D?zOCNqLp)k=@!w4=4g-vV%fgYk%Ap zvfzcq$TUX1OzE}oep~oO&PNej+3lr=vRsegc@GXgrod;#zUj9CB8!#*zQpAB<;7WE zvWpWNJ9rTxq!Gx>>8u$aM>%O2A(7!5xV8(ulMT)uWevS@sBT__-^9u8itR2tiXt$& z6DidBF$H`?-l+&gmRR*ed?PGvk39&Rv8OJb#T3 zW%koK%u^Sd=A}9gI*W@8!D&;t^B}3qL?&PGXW!D{$zver$w8@0Jy_pc%*bO~2n+Zm z!Mi@pZvix{7Az`=->iP0sAXu?pUaG|tMPR#U1E6wui3+x>Dj~JAm`l+w72+7lx6xI zpip90XvUl97DoZ?<&6*X+(tv4)0j_2CH2%fL4t$XW&W!-?P5gLeEBugT7(8>Hk+T@ z&*VUZ37mFlR4kBe-IiB5qJDNIlRs|70({;%s2B>6lt<(*VS-Rq`lIFBbtUEKF z>NCO|I%FFMzp`n-a*Rbr_~fTTB?#qWLbQ=OVR~qqW&sEgF zGia1s;NvFN_%PQHL+&tY%wVFI;T$?Oy%l=Ir@^A8BBETCcvg6X(6Fex0SI3|J zni(DBcm;UgN7N@wAPO-18)O=Hwrga06_DXER*^RLQdfi|zu|6;2+f5E;Z7AS5jiNd zQpxBOg@-kP`|`fmedWGhIi|7%6G)gaWV+J*$C~8%Ix^wKX(?#K!|9Sac8D0HB-}D| zlAh+*X!9R?Q#cYeZ5-v^_RnCTiCA&ahNOUa2a#6Gf=6E=*yiz2YrGsVGhv<$eNe!i zV=0i*bu!PZ~6 zjHHC)X5DoBUX;dzAd|TEj6L)m)fe$(Tsm<<(s(7v)jNy*OLtGa5z@q?b<2h@@5)zJ zPf7vBFe|HLfi4(Ml66kUM^~5PiWfY@Y#LF{MeD}s=xX`1ZUShMY=@zR&xOMd9^er{ z3o>k@7{7&5bdmpPKZXy%o63JrN6lv-37t}oDeZqdheE68r;|{~n-bap|G;(u7LVku zWoFc3%~F{)`kYbi6O;|ZlX?o0+&kBYWX8NuvCQzC46`J)9JH(H#lLLC>=xonF9Hh+7yzwe+-;TC}&L#0*I5}{q#5uo4eCrm=rgz zUWg#6$Zf#{IdCm+x*^#{lor8BifjxtV>5gTh12Uvqdb_h zZeaa|{T{x6k@;RrWk4qB@Xp{AA%TWa-Ag#zn(Se z8Y@(rLG)#DdXD|!t-MGnvk13#nyt3p8CIhc2$?>;x!Jz(Kxjy7N4?ltj=`i8yMwq^ z^UWF%1YmxRLFxj|QTP1r`|A}MK?Z&$W-q#-mzS#M9ot!h7g`upO!-B!074>T@x6*~)WwzDo*i-{D+77F8VDU|Dcg zoO6TGd(n--FK^c&Lec$|xX;9=YZsB5Vv>?%oblDpHVB)GT=^gc?uIQ(x=w?XAZ|?Nl zk4nc5%1{DYx2AA84EPqaJY_;j3h6%7}#1dBI>3HeRzWW zT1H;CLf}TjEqN?j*y^vF&l!itJMlDpt5_Y^8k$3D{9jf!Yw0aPW3X2y$YE7<5S0 zE>VxBX?qFNP1#B+fK)1&YcagUt10B+ZXVj(tM5 zQpw5GT|&NaL?Z@vS+m@C99@nJ8=a^4cYmtM?Gqqu1TSzn$VPEie;E(7U!O|s*`mds zeao9p-PkTcC>f%ZRdsWSFm8x3)y(~+pP#WMuGEpxc}Dv@8Z`o$sc-Bav~$QMRWY5 z{5mJiOiU?imHrR{!;&CkeQcw$c32-$38be~!q7V7RiT7^9j$lrYx^M0stvd(nF@o} zv=*SV@(qa6&RE^^xaE`3_QRG#2z|@klH%o2hz2ccezW{Y&pDBcY=C~)&cSE)@_L&E z?jCfsa=mVAXnDT42_a|w0^=CyhS?U)-FYI_iSe0uBZBWx#!g7@TidmXq`uW5F~WC_ zHcYl>6BrOv?TIik4y4zRJx#@m;6jCPfdO7M*>L?v1Dxv*I7qV8a7Q(c_s{myuXDr5 zsWs2ktUQu~o4XEQ&3!dV)aBQ94~+)@sGbsqMG?XKotF^H8-OemC-OH5f>ABtXBsri zrZ=$Uwa4*&Nu%D_8RokG8sQwr!VnLAL}$X7e$KNZG7;ZVd%9zMLaaHU@Cyisr!)>> z4lnS^^HcHyzcpQdKs1%YOn9Tou2E{;n$MzQ*@tV#~f5imIEf zMWM{zOU94=SrfR@In!o4{dzwVUO8uUjp&`t9CRpiY}JM*7@J(Ej1>MdpoP34CF&T4 zlSE7sBFl8w^VA}CiX7y@C8MeqUBt%Z1sP>sXrg{1tacg+O>HB7%sC;%Et18RawThd zrtN7XIl#UwIO+@P*UI@LR*FCVxk!(~yBo^tM!3o7>LW#YQqxaaFRZdcS1b!!um zjv_8ZqB5X>2rp8ILPt(eO-d8|x01$*LZe>ruE9Udm5Th3*E=LPKPyj!?t;wEJ9`gP zP<>j?Ifz7Wo0c%7e_s9go?TrUl)SqS*t(Pr8WJtR#eJ_KcT)ut7{q@smem_|HNTbH zUKUY*QPq_FG8FVkc@u>+qgYv@WTRMXg%o#m?FH1*#y9DnaTnr?7)R47XLW3^UPzYebOEdQIT-|sM{~? zIxBEcR;%xu#x*Xqs4_0}yS&#Z7Jtq=cS4(mpoom?{oSFY^IsaytYCa7T%~AQQl%&~ zylHK9QUiEgo3D! zHomS&K{VVq!?pVo9tD#vV#XDrXv@)nTz=*?y$DfB2u~(B^be?Pfkx%FL2u@n`}Km0lC#yoikjARL$RzUeGWRhp0sxu4@$xuMNnbbE#QK8-Mp*;KjW0Pn& z8|8tdO;C?HXq^GfzE|qqDe*FeODGXAj=MB`T3D2OsT19Y7koog_vadX zcsWd;Y_LTt7Yb@>`}r0$E&_Wn!hL_OYPmA`Sc%E@!x1%ZW!br1vip|JwuNThn z262P)B%;5d3oU-zHuncRJwtj;cW1pB*Eg2cW2|-B?SJr=d8e}tqntb&h>C_Y9~?AH zYt=qCal+d$v>2$VMRbGSyP2l!NbW$2pL9p}BR(_LCKJIo9Jcg6(9&B2Ze~fPv`wzi zJL{>WKA|8QERmxpw=>TA_vCx0a`dT`T1H@^>N#hsrA>3I6d9%#9&^}_Q*v*owGvdu zQabufScEpn7JbS3{*;U;te0RN_i+CHqP3GI+{Rc;j!@O(6uQ;xc~F>)ZwHC4ZObv| zWJJaB<+?1asio&yNW5f&;|GV9uV@y~o4q9tGiuCiOLG-@54TaC@(#o#jDglOr@i(` zxuAc7(gYoTr!Ie2$>?DqFbluoL|pn~qO@TACVGRe08AWM1#}`-$DF!6eJ39fC&py| ze9jg}9{YRsSkZgo?`)%6gIu%AE;bqSySmIbIr~Q0muQR+1JMJWx)l%vjMWq?wrrezIegFmRFFVgK+nF=#HFmhGmDUJg2k_ATD6z z4_N$tD=P->tA*9FUUc~@ZZr1>Wh6=}43W)=>5Xi_; zjchsqmZf#y>}MlDzv%v&BCW9}{hAkG{Dep{N@0|!2LAy|OT=G=UG$5IjHzXeclGcg z+Em$d69hOXUf=nHYPXTT+8G!giHM6-zn76czt_3WN9{Tra$G67b0WGmwBfpIBHKyO z=tLO|+F+W$xv=|z^Yc4faX%+Xh-64ixQy;{dj@-VSxnG(fhZJE_S4?RL&9Y*jJtWW zt?H@{h1f03n$T@NZ*YJsq$6DcA(Ii7hrPAYQB^d}t7=}WbGt4;{>ChT-h6VU(MW}x zjU5UxDDFOfz6ru0-H(*rZp(NQh7ENvsN|(0$s7335T@`xWg}NAs}oK_deI}UdHsAD z4KFj^&}XRqymCXMPaq`Qxv-v~EI(HZ7N7r{eiey2eC zi9YM6iZsEQR)K0P%dbOC%rV`!qLDd{&uSN!L~}2T((UsIjC?-bJd-ImEy#cIEz9rj z;8325RK~y7#Ia!nNJuK$(gb;cA{U~iD0LT9Yq}KZccZIX#+VQ?O~$<2U;8;VlRo_( z7>Dw_QQbCXya%GBG*>tbp$<(uNuR57jNq$I8owL9lE1t@&-0O4lbDT`nK~{T2pbXX%Uyvo9Nk1vZ+hDku)%YU+ zpllpy11Jk7U95h+X#5SLB=z>MgIZu6Z+DPn!agAHGB~3?Lbr2`nEA&&)4>58a z*QMBBY!Bj(d+9E8?!Ly_B?#j%e#LKPsIWA>8}^Ay8`?)CM8}#5b^YQ&r=PhT%-+d| zZ=5mt8$GT-BHPH!emZ15sNQ_Sx%)ZK z!uEQABq6yqwjHG7R?vFyy|l~T63*c z@!N8fh({?gA?7lb&7$i(GAe`}?~bTN;N7`u&QeLhYei(!WV)Em@yb}Wu@r1A?_!Ms zs~+_3-05$Xxzqv}VfChKonpKknd4ll8pc+r(`zjuS_kpQOL)^Yla?#~+A?hI;6ga!etu;yj|# zHLNfD+ZvlS14sj=6_=FgU(M78d`?g${acfsaVvd}Q9Cf{kexJ+T!Pwjs>C45fll0z*S&jx;UeZYME~!OlR& zN36bKSZq0&(}MTB&pS76=d;CV8g{m6VPrePa2@Yxv0pbW>$dR&8N{TVNIVQ~NX;_K zvq)8{4FFahY{x5UYIjm!z$252>}7ego_yVkmJPrUQdv<%EI1kz3!ASCZQ4y8&uJ00 z42k0XoJ{|tlA8Pa4U1lby6mx__q9ui^IW-(Y_9iaPk8VnxGY!sjkFGpR3zp0)}@C2 zB^1%qlgd$djrDM3ZN0a9IF1qheyiAM*J;JnAE~Oy&;SvE6Bnys+^n@dhcECfHP<2{ zcqS*sto0%2f(WHVgIVHo@oZWMK2IAW@d`&=6kFCwSwA=k2C<-Fdt(!pj7f@nRPB7Q z5k@hmqS0>ATB}9Yfs5t8YSuC%X4)TJ=zF~w=E#hkomQE!;+bCS6_9TBHf;5FFIx!! zL15NYPzL&c>BZM8(UEF7JC_7Y>XD+HPl#h;LuyJ^^%+ce616nX7f?9uS9oWOu`z_R z*GU;zs@j=MP;XmS);oh*^Io=yAzdsOF$NbDeUbZZ+1NBo%rdU|a??Xu>*q#-rCrZQ zOILNma+kDI0MMniQC-*#q2y%L6y_=XtYws$c;4U^ksOd!6v>RpCo;G|8APSKSVQJHQOji1d~knmA~ zR(zfCHA}+uN=Bu)(dv{Lm;7ZxmA%jU2aKL{ZoM4lh_-MyF0%2pl6=Zo@)PimF20M# z3sMnOL2wq=D`+agwXDm-yZ0x?@}ELz@rsw}`O zx-IYZjtXgOHi-?po`14QvOU4mPqzQ;LA)6{-DT!p>DSp9f{1i;-NrhVPV~_ zLFH>!>ZUBhhjkUyqnO698X1l9o`;Ex0OqO1Y0DMCSpi@;$B*m&v;hwGaB;&WqswBz zeY=EJ-Ik>#VY$GJ{VNi-RyFH7K_m&XN$&)%KCF2iv1riSG%mY#M`I>&c+B--bWb6T zpDxftFGUOLjw-(`*=$ZE8#4_Jy-CXq7Zgx4UR(0pXFpH!Dn!gf>@Jw~+?EY)D@?4( z{nZJwoqvAR+yGAQnyJ0cQCX%6Gqe`_)UE>8VTj2j73Bjh zl(|+ruI@d)Ueuf542~rQs5eng;p^cX(V&DxZ3S|RHIGtRyCxpXgQuuOJQi-;u#+;l z@03M5n`Nh~fJGyzM>@>^DtW(pN(A|n1WnTlUlcyn&glNE`xvVhBWaxNa^0a^2&Gb_+1nDgC3aH% z4M5HmlJ#NXWY>Vpz$lC%I&%u7Y^fQLl8=n#klkDYzp_~cCD2<9NsHXc-7f4<71zDz z)>HXcW|U}=3SIvB2aX8?fH7nbdX-7qYWloCyn_rjKsB^aasTcQ&>-;|2m{1)TP@2^ ziaNTK>PH&1?qbn$;4XJG!t^VVh7G`ffh42zwc$6pEt-JVC#UtGD zV}^8&8Y2zBHq02&oowXCr;zqqMg;+_@8RyJnl$v?l;bX5InfUQs&uXSusfPQ4fs%2 zWfcvogOuAHvg}ixqGwH4Q*yIo%yyk58@6bu1UtqR+Wf7q2H&@c=_*o{?k6dY8IzA* z0A8rudIf-^8`^BXB8ZZ=GXw;GM=c z#tftA23mcIi%+=t?W7u{6c!feleqZrxjy^&Du`G7awL4W zpMT=Wqm)uNb`Lo9(-tRdgXx~*VxiBNma0`-0ZT=%9MSaW$?|+)ubt4!^kyna#Is}& zbN=d0B$v4_u{kBn(GdgMz(D?U{V!PNow__|(r)}0pJ<0ZawuYO8>G}(2%YfU1{&#m zf;$+SaO4`jwVA?p&daX}a<;b11bQwK%W&xke=TkYS-TTKroLZGd4F*qL=${4qnFO@ zlQ0@hyPR6=j0gN*1)$8U zVIlGMJlA6A!Y$%3Ux9XO%_v0RRe|YG)r7IIiwapn4-L=`6Gt>CvF^s-`>pnO1~v zk@`1Y_D)|Q@HXS&{}|d9hrG94t841FqRqV?&m^BpswoJo_JI=^p`a)fQ< zN*6PGh?s2Prh`4Fn3mU^&25(km54u?NhptTSJTmM8whmXjDMEKl&eCqd$i3T?L84L z?!4%q50#&3Q>`PL^IYa2!M&&J-~Qz|Q?1GE zTb##Ai0HmibpUUKU0E;-NBx28@{tV}c&Bzy|9tMXq$`(a_ZQsvg-n^mRToC@A6z-* ztREEb)a2et1aI_s(2qN=R9?0@=JBo4f;j}LT7G1h#5p}6wlh57{Til%lD!WmKjb`>&-7scEIa7n`n?0WxU{u$8xaY z97myH8@d?w-GcMDV6mTpw!ua)j$+PMG=DVIWgqM1w}MgxguM7I_-1FpaT=m?`&aeP zh!8N}?|9P6*?o;yY7`Z+-;Cki1QTs^}iZWjZZd zy^(2m81gKW0TQOx`2;;O&s<Q2^A^z%s{yc=wF7aVP)6K3n%faHLYBfLHK>Dau=Ovd&6v30PnUPk1<}<2oz}F8h!DQ3Vv4TR#ShT)qGE>;2&#Ze+_v!K;-)OZ?xF@nI$BAsfmw@Os*qf>*IG zJSdI5`6B-)cIO#dl@XoxuteBVAhm;hV061&>GmTj>{&r5XhVgpz)TwporTUvBK zaEH)i6@@;F@*WSTxSaXFKq0b7^?PJ#T$$P|1V^+V)0FZYh-I|U8oZzSSyR)O_t-T& z{OQwr&y?r*w?E}ii1e=OpUnL9MmpQQgW@y46y-g_GJE+U5qDWS^yB+xZ)*c(IDrL~ zHm<+FedgbN-}8gbHHu=nm(uoCkIbw+#vTQk_i>HA%8T7VIAJr%Tt$=?297+Ek=+wu z@~l%kn`Q;_7zHspGIGO)qfF{wg_PmdzUn3-e z1REg;8MCtFW%1dNl+eLb$OAy~-!9E}Nl)?ZWr*)mwc%k?r_xAL5{N~UQ1hk_lxh>I z=Sf2u+S8(wOTl{!yvW*_Aj~(}LcRdmgO%FwaiSCU++ReMlW*R@o$Oo%ZKg;e9Zp>) zu8ul(TOPyEanl+H{y4`qT7eh^JcnhS<9J$6qE(z|h^o&F|-SDpvUtD=Bk93x3HJPvgIb2)`#E4d%OjH~$(2ul7ut zH!Q30s`hn*vQ?VIz^kZFvNg;0-%7`#5tn-;Bm76UB_b@>JR3F&-DR9)d)oiW+vFNsTtKDX5G9}2TM<`l|Fi7JI0AY*St2D%Sa%r zs$S7pKmk%N3xx6yvGH&Nsv(rII7ku$gEHb4sn1gf2VqCFO5d2ZXk(gY7{F$D@Yt{~ zOEA7XJqs9YCIzLMnv)O3pIHNpz$Yr=0)tJ(>R5r_s$oPOQx6b9gR!2a$B>)F0+ zNFx)fOeMAI_PL$~RN#8S6!Q*1(O#ltIPOIF*R?_fQloIG`tuJ8y1n6W#2xiH`KChI zUu&X%j8q$tf%g^*1o%}FAN-lMB*8_v8srF0quUA6QQNZo2u`e8g_-WTN&tMdi;7ZA zk+}G`D;@{2RMiX>OZ%&%2{s zvkANat0e+#v-pKp5ZKvY(zg+Gs$TN6Ki)A_0DOYizIRt3Z4oCwV7m-%qhU1wueauX zXIc8ug4_!o8;`+U+UO3O20-nbLuN7Lg8KmAwxBR+sQ1@5rkEFm=IO+sV>o z{k);vXd0I@3zN-_cnlI^RUfU*75{PBfztkBV&B2<)h6BGbc~)^ZiFVRzxY3g%Z(0p zjRlTRq%(k_WK81-1SO`P5$GF#k0|EGo)A+k+Plh-Q?Fl4=&JvRS0`l*kltnu0Zvnb z6+fR!nsaj7KfFCGL4=#UIip!XLedq0gxUdz0mp5A6iAZAPduF?oQl-em;v%=d=o{# zoVC;KC-Xv%y46m_izs@mx2x(DjWrbRGJnPpvIqJ--dfJn^)&wmI7pdg*UlFXYhgdN zmI&@IwweOQf{cjUzf!g&#Qy=vSo!fvLaDps*)fbC_0zfS_S=4+=09Qb9TR?Ng&C2e z#@?Uah)P#3od#hFrlR32PZ#HNF z!wdd(DM|{P34&eSq*SGm`IE<1Dw+>J%ZB>` z-qI6R&jAStaa%!8ziz!doGInbUvP2-5MruFa}_6S=QFGb*I^?sz#QK~NR8Z|A`?rR z@3{A#u-eEJKGj4oA91PA08(06=V7M%8r8AL;Y2Q?f$OY>%LsdaL98y-tLI?(EP>SO zAP~38oY8yV!|7s7K(sI`N`-fCu;5c(bd+PObv9YsYfU`hTL9f+7EI5og5dwV_J?iu7T z3hz(JK?;HNn11HxsQ9>El)nk1fclEu1Kp`bG9@Go;?nq9qE!7b-~K3xm1Qi`pjBlk zXPpr7j{*aio^asw05w;&TzrH|8HbU%v57JL>XLW_oZiHrJHYbOFw9B#FB=t%Knu`F zEm0(Rubu~jU7V71EK=h-LS)W>U47GjOn~KI3|?pu19kU--n*a z5C8CMsl_N~pOW1N0CBUp6QF4Qd3<;}Kk@*q`>K$`yQ}?4S>9WK8!3uI zulSM~af4E7JHS8qir2ZQvQiO+rCR=jnmOR2)1+6>XZys>={qYtZMBJ&=!!V~L|k7t zkpl)@m@4Yxd~NPr*MStIWV(IxD;Di)|1szhv)C;!jSPQA2%_a!15z%IXJEo=jj8k! zK&C5^OJ@B@F%whBq|=5FxcMJeaKH^#2YjZ_3o)zyT0YI?20M*Az+ssf>p%#vKmIbT z3vg>yOaP<4QI^wjUBm`3sp_>EYP9EjH{*++A{E1LCG{hoVI;Z%Cl3Djh&FweM_#iu zaTfNMrA7yb8sow)W!85Rf5C-7AD7bcQLQwH@%YaG;6*8Q zktiX|x98@A3bBkHO|s_4OZ8{ zqMrbFdC>%5$f}A(<8wR$Y|GEkCm)c1bl&KFMOVYZTQvTWg8;Zb**3{eht{7-z2DNx zg2yP$#lMO#0^3PMFDD;9qx1boLa3}>$l`cE|EP~Mz9VA0pb zn|hAuCrt@h`DWy}guWtK9suap?jeZ^AO2Tr+(<(&U{54x=yt5QZC)rkk(dC9IItz|+D1!xVdX;C^KV?rQz2uhE~YtW=z^re zRMO^d_o53=Tdv*LjnL?nJ3TO;z>x&Nq|QV|m$fYYlKUr!Ob=`Si8@=D9PFKy`?*Tr z=U?JF78*h>ZcNSc0dOR#)KYrQA=Prt8+x;xbHpuGATb(wY&?@Z1_-SB5?43pTeGcM zrJc{CRe|h@uJ=j3)wtlVy6J3ygZIQP=ORJs1fhFlhBP!NcG zG1@-f-(CTWlgXyPJS^WrFK~jW;26+wtrHAK?}^`{dZ^m zfE=JKhGD@=Nx5&gf`t{$w@YX~_zT#{=$H{ZMF%=hO96M2KqZPy-4RQBy*l!VGF?ii zF7o)F{{>*TQq^|%nI-axdo{$t7M$z%aL2_UZH{pkW}uxy)^+U)=3*N{76rMEr32Zs zGhpNRO#5;~v1YS&X^rej$I|usRfrlo{s0584n_4b`Q7@z{Ud-cB-NAs85V+AuT}yi zDDaQIYL^F@p2sEhHq*IjR`hs}@$D^`6d@0_+NSqG0dcV4C(TsS9J_IlCB&r~{6>DZ z+j_kucap;~{p`FQIN9+N5>?hJ?2U>s=A=}dW!J;5Xw5JH+&uN)@7 z2JjhW!B*O4J5<_$LH^9t7ZHTcz=+k%)zX{c!St0wN*nhT&%cUE2?owqG`;{=SY6&J zUCXx)0r=>pD?P5nM^sDZt`I~J1SBus- z0dd0`Yb)t_)<2u9doR%5LK#IqACC7%n^YaExdkWxny1XtI&rsNPf$o66T}5ZiylP>YJLC`)Fd>u|92Y z$a`2EZ^meO9xTQzM|-Ot(=D-yE|S9?+q01Vc0L)>W>3Ho#%dDXb9|480qO5lc>h_E zJjO%Av$&=)jc*4<3Jd6jQK_r;Z;o(0Fd{kxI>3Hi%KzQ(TO{91GPk*mR<*FkL~(!} z>u``$Ao2gxn$IAC`KVVT`HG4T6v|OA+vomMRk9HOo8-mL7Sh{6l*H9$kS|cyB(K%* z^+eWb?=BF~x=)nWuw?zPU~0l!&ed4@BTqd!5*qM;{B?_;liFKKD=hj2iu8BO+FQ)} z9d*Gi-0|-7!iaC~PL189D^-|Jm5>BQ^tbGA^TIh*kOa5$w3eZ`)&XRU57cYvz^OuP zHg=oRUr|{fKcJg(R${(b*|;axSJb_3ng5Z)CA&JD{wuZSe@ks_kxCWbo1+=PF`=Pf zsby2la!C5jNjwBI2dvcjzm*Jtm4J#(carq}EPv{;74A7J^?NJ9>C?=}sr&bXz*VTe z-m?hWL6*Q(>fu9$+Q$w5$I&lJ#`9M!Y?Qy52L6Ew!42eY39jd_;Qc9~{{_g%5Dp=J z@%|1&Nlaw_sqHS2yC)h2!|i?v)|>N>V;%bg!onQ>SNQ0||-5(@v*P{%H( z1umvSN9Eg^7Id)(6c;$F7bC06R~rDa?!W5gp1?efd{LvEi1HglIU5x1W+LCyOdjR| zy^c5fXN(~17Df?pO_boZrD5rGC_2=|BzvmixqJUcj8bk07Mi`Stj%Ftc=vJkLMFQZ zjQCloTVc3IHRk`C8cUd8POM6bRCJ)(e?f^e#0dznJi*iNR)lW$jb=X12EsbP&nb=lB8%H>$doG=~|@KxhVln#Ym z>JjU^D;J^lX8M6cOt(J1v((PqPTKIiCpT@KuhySUfZ~}E6v4T!Ma4N}yKFgWk}SU{ zJyikhdFe2+7iXM@QVe(}XXXs-0${ncEYR}U`(Q-*QbU6BSMDu6W(6g1%p}nW(lLPt zLw0|^^wKi(tV|qpGeebsmbsK2;-tG4hP!d$theSLolqo&d4lCmJ^8;?jRNHg&EbS) zBo3MWKP%K2Er<^SEVsZX+WxPsKXqY#D*$p~c@A8KQp|rW1H@0%zwJ7c-$*z7)74)y zStbTtWfWfSGHVWE*?$Ix4B>^Wm{IZ;h?nyl^?$+n@0rm8r4-zlWUsKvJCNzW6aWGg z=nS6isrpy${g*K*C)uLhn^Q|-Dbx8+%Md?rkV{241c*z1e@Q-oTH)dkgN^V8NPq%3<{E*is~Yfw@R~^s`H7%?~~{X{<#dN zEb}s?-?%Melxt=L^b&@*-&#k!grBaryovJhM=ylZ`iq`lc*>T%$ zDbiAj@tDZ$U{3*|Z|Urj@7Z`H_&WQ(sz_cB5LA^&6{CI*?d1X0NqgqM)oGukJKi7D zsg8_|ig?KOq)fmup`?spfQi!~8Ar zmocKGtoQ{(qe2oG1;)Hl{Ov(dXQCPr#z7zco z{&ww`SKjQ2HJjmi5|eKK^P`|SL>(FHov91d6ENgy!ugFl*f>HbPyHWVkVF6zQ4x`1 z5`{pinRsTi9Kj%0fk#$tQ_cGYH z)zIcSeoJT(<^Fa@J-eFrq5?NrDP&+=gg&t=HLZWrNhu`F_;5PL=lt~Lb?|_fUSY><|xLtX@S=5p9ggdjhISPYwsF0J<7$8k}?m zh!Gjkrq9lJemTb{Pf!B0^VbMI#TC1_Zw(bckbLG{{%45u2h0~QJ@qxW(oBht=H2Dq zko5Lk<0)D$bA~3%_Adp+t5v_bjvr}PqmoPe-cDj@7%bA)|3ri@&$f$ zjhm?YDDBJ7WtIG@G<&YDhs={Ni?;tIf1H;5UJ|z;i#Iu5jS`PLyuMeUlDe^DIv_HKd|W+ zWzDSebj-@6Ga73Zt)@_uDrbKM|8h+cA;`Y@X&7yKvx>NoLyWrxhO0N6Jv7l?7(-4K z2q`XROD9UEQmT&&nJ0K_n~dhHiOeHiG|iSIowDGDwpq(&u`$k)j|eZ5kk%OSGKIP=%+jQ{ELkr~>^zyHqqv1Ss$i&g2=};1OCN^>aT4L>8 zgJg}r!j(3gbtjHBQ92r|-s4XXBi9I0!WQa_6Nq2UW$|04_E*oOhDs|FEC77R(JgfycRo3d;i>F2a@A)$M|bvYNjjAGt)05EMyp(VQWl$K zNayDS6d=wz2eq2i=yvNiSO=gMSiv{fDM}Q{r+hQ#o*%wUL_=r%itO;hstF!BS!rQF z=}h6*9l+o$ihrOi1}0Q&S5OhbBb$PId5^@f!3`shkI4xA%)s|o8Gn0`Vl%ovhjQ*(3eG5M5qrR10$OVxR_bv9Y7m7JC)a1n2aie#I;s5SK6mj z9-E5{V|5tMal({BLqehTh@0)vb!VY-)pR6QKnydY7#5FKm0ozAF;YfhuiT6TMyl{W zLCN8*l(E(({t_CVl3&>wStsqPRh3qBSQ?z2NB7u6H>7k1x^S*STdhN3>RJnH-hQH*rvqgJ&^zKf7AS(e2=esKjWXCPVxNWH7R`h(hm9eE z_?o~ANDnxAT$m8y$SqezHDjLi$XhSGp9}1y1B@`Lf!*U8Y0KlC96_WWsc}145cz^Y z3w}%}1__(^3wMz@!S?_h{L||Lf3oYWrNh1MKq9+=y6G64mmAO_0vJzl=ZxmIYeKuo z`2#ThO*aX^iv#Ds83Y54;bw~8O%L<-a^^RC;uR@O|Ifd!9+pIqaxu_u6vF?cb=99F zlZ1z8sqXG#HYt39{27kSbqmJYzPz|%-#=txB>p}T(}I+W)b(hEx#V<{2$?SQ1OMaA zMT3|h{=%4|E(s~`@u^=*wGO88LIt0I%w9`$>yXgh1~dVlTxWMsG)tYD?B$F!Ukd=* zel9NxkA?dFBIAAFGK$t{rRB@Wj|K_@wSq83@Hf&f2o5oK8wX8X0&1EEKEom@a{ZJ@ z>YitvJp!ip0kZ{TPc05|&KBtFu89f@ealhV#A?c+#Hy#J>*<}ZGzjhfNDmJ-Hi_mw z-N-!+AU{`tB8J6UgK;#~uaF_D*kQt)ORH)_BjbG`Tk`Y1jmFQqWDk3{$wDdm~ej-Q1oo!&nY*Plwe_kGx(;a`3{7_D@k zPW-&>k5_~`(=>2a0xaBbk=>o&M_t}qsiSbedo8r=T-V4%ycAA$$jvoSPj_2}+B|6x zdl7KJGuU6%%*7ow+=e#97VIi}jgL7dHvOu~+xca)Uv_p}WNUFj^W&S9TxX)fh*lb0 zl=wAvz#XSAD&MO*GZ}WFK!%{x_5A&|UVxK#_>C7V6#=6rTMofjzEqzKc537(c+AO? zl)+9xRxH8h_V1k0GTj<}w^*{**x)WP)xZ0Dc*ur+#dEy9xXuuZdb?7s*>=ePROI_n zD=>);m>|4XdCH1PW63()ex)%PrpiTmwY0^A2C3}inAXC&l|`7`b8DUblxmnZ30fL& zCh_>t&6m(4;kjE9)~s028 zjMV!MCg)T^xA|R4o+K((e=RMSfVL^8qZ7J8sq^nsRpo?59fSy-|~WVrMuEUYSUh z2w6C2YoEb z+%qOu!~Uwc*o3ej6%>IrV~1%$@ZyE+-6Jf2qSFR#^;?$IJFFP-vlQl}O}sY#;=@&_ zTj<5+Z8c1z*tR|TXu+}%hP&qkE{$e(G7hUoD$@FSafCTYk+BdrxWv=S@y5u9i*1kG znApb>!EL--JO z|J7@j@2U8N73pE>u~Oe^4JL^;y~N#;LH^?g-P-39xWF|l+{esksq81o>`2*;W1tIc zNrDPa!3(;5l=VG(XpJ(2krjNn0X`}RY`XT$SC~Xz$(YwB#ZZF6iprq?T@|G0wj4bI z-Z^Kfa;*fDInSz2r(to4ne@?m2lM^tMf}nX8D@p{Ks5r@5kGvNL87gUc84a5VhB2w zo^AKEJ60vfjYj8bH5$qD9h#@|5B;D+D*o(Uf>lvU%Je1~7psWigFTtAel$N&9B`cF z6EpreJM55f1_j*hbYad`?7B%=%z?9=EGp=7UoMri;x}v?He7nVmpZe4gin-5cy>?F zFd(+kssmgSv6iie)ZoA-?#x9)2o>gho`=J$8**v*C5oAOl3J zVwy;6^91m>z|1LqSdkFKDxU&zIiY)?`f^)zI>EP2%G0qm0#>MqL<^wc1YA5rBzX5_)`ZUF^|1^Q{#qLM>&uQ zNdGlX-I7L#h=45_hc}fIhPzebw&wSg2qjwKcE%ye`;`-VPdT7rB0}gTzCQFNN92wN zSIF@dPEPPzGhw##`G#&OQ@AhMBcgis9rNWbCC2PgXmz@Za7{A7l~y-w766DX;9d4<1L5k4S+}TlVy=eDMY>)nEPWTJYPPg-F8d*>XbILDEkDF3=t%l~V3FBeWCNga=sUTz6crzJCydO6qr&DpJ z$ZqOe=^p^xZbq#Z3=N95*X>?-3=S=*%puC}j0VnLdUdP6uvU%SbMN5iOR_2}UNKKb zx_t|7bLLgk-{nFu2(pQ+3mjEnPc|6R%g6X623d`o6z*5a+kRD*TG!p3`PuD0PkS9z zG`U4CH~SOS{8+0###>#Z8u$O=cUYjOY_}~;eg!iL7La~-f&`HsudLxZ@MAB1NPyod zjG#}hNOgXjLuJfc>#TcBU zWr~M8JcSNnZjk7Ffw+8=V`4-3q*HI0Sp~CEAvv2g#+1tQ+<&3e7I(5UxyTfnF3HU& zwy@1wZF%Xys-*V`^PEnA32r~qH6?*xSKjwwZ$}e)JXS}9$j{N0Vg`xWVNP(yIGK2* z#S?)^d+d8*_|SmZ;fH*7L985I_z*E@1eLpWv^BV&ULgbVV?WRP3L~m0W~(H&q*4e-QjjW= zb9bZ7QDifVI`i9CD=DoE3qdNS97;POK-FPO^Jy%nCtEHAdt-lgte-*FAJt1$-cd6& z!xS!H8~i~@IV_EYLX8+Q_EW#73o2^zGZKB@sBQoV5St1LX5*06`&b~O^AH6#_NTO7 z{gEd|lGANsJ?pseVKYdv1Y-py-wV~@i2@bx*GU|ZO=phzxSfV*b4zIi$9jEu^qpd4 zJ)G{rrL=SaF#Y6~(JVR~exsrGjGWL?f#IbVB{JmhMgW~2u@@n~G^9_q5%a97*Qes} zbUKkrokmH)1B>{mJCfRv_U;bGe>OwtvW)|iV?ChH|MUUGx#&6u?S)our|)q~+wN*S zgZH2d&%!Hn{mBFbr7y#3s1=r-wy-GyqS0r*pf6aYsYB#JUStRiPAv&^<(WH{C=V@U znN>ZUDjx2uTX#e5^pnSQ4@fw=sr`cC&fB8l{RNOP7Qylqe-;eI-;q_@dJHQq z_Fx%^LdJ#p`PZ9v)B~t#al4otwO@cbJv3n@TcZtWWv{`5xr+&A7u+>fdJz>iTgMlV z*}AIf&h$%qUz7UHCMbA+aA|--Hwy3`z_wi_`KqoXD!)leBKQkF!wfsB^1H;VAWOph z&n^C z;H`DyX{Cq}Oen&cuJY#0N2E`o{smnvxQb!xl>vxj*De4^u&-S8x+EhjhCh?+8I_HY zD#ZpC9iU;Z*O*6z)jk*U;%wSZnRlRANvt%%AlJTM(#agjo)`hvM zvYn8P03F~<$>&L?`~Co;-Qb4uZhgNVrFl&eC0Jh^Wq$86ejFPjKw%Yj?}%1Bk(YJh zo?>oVil_Ko#S;*)p*zha z8+_*M1ICYti&vxkmuF&988x3HQ9{Z|rMpydH4=Vg8#mL;7eOwo1U0kv>hn^SQ)BZW zOqp3Hj!f<_`hc!}!Y`9v+*0Fy-xpN*k@JNGboYS~`tOvKy4D!5?2cFyN~=T7TGr4j z`M2ujvQ)&EzZUddo^w85zS3`ci5U}3A)+hnz*KJLv9C{=dh^`3PLrQ*Sgs>F^C;^E!V?H>$Yj* zj)fxesPAaP!S(|kxT@J7etGtBspW@~gH2H5nQcgcQ0UTvU@&uehyuivpg5>yBo*Ln z-C@SD*&Mbs2rp+FqTNWkVPBRIh*oU_q8o-*@Q>bMRovP?zwn3u;As5TL`@spoZAVY z@4~z64`05gE_j2Kxk+8g+z)Qurwu4Ht1sN*j{h}YBBgTkQ&BcA(ssbD-?N0m+(KxW z+SIQJ+w)C!Ke4oL$9m%|i2=N6+C;_u^~r66-MCGDl)WHpk&53BjkkjCBF$R!NGi>d zwDfOujrvD3&ED{mI@klc7m*oUJx}MrO^ff4{xF-O6qpGE?2)e!y|%9{8tp<_Gy6rjr7+VBmk0$&5Ok|@ zYyJ<)%9JZa;2F>?@WdToexY*SYx)eG_eh(h!^eHA!*X6`!v#_w4fMZZO4=PlpmhX6gemG52q zgJ`N?xO^7>){$&7Ql>BDS&+--cK%3}W7e*}xT635XD;=ZRdt@6XPZ!-F}l@J!WaBR z4(@JX@G%+%p(usb$;=GkR50LP^&+SN>iBDV<7Z063Y+D(LeF};YcL#6V>;cg*Q&Xy zbsUUXUG;?}H~NTlyEKv#`~|Jg_l-vmj0jCP?%U_@1%4W5P1X2FleM-!1z8&Uc~o>W z86()xtUIVH<=^%Xscw8y3{D5dJzEK32(M9SnA?_~qT)=@@zXERM?jqofT2hsFCU#` zD6?FGS)z5LvSO!xilOP{RlTj1rsm(i;Cye-;0+>+x~1m3G-qoFv+@z}yfLM}^2~6Z zQ)#8e4P?(7%uDuo5P@2}Ag)$|qr%owkGwt7IoT8Cgt}L`(4SOmy2bNw->hPOWo9NSCBIkI`!thO z#rt88yW9uYO^JvQMsIk|q$MX7O`5QTV|$cg(Rb`Q>+06wv`RCT`YJr->Ip?Ny*cF5 z^Ah1WKWkB0Iu7V)8^AFBeMw}$z+CrExYRV${JfPu^Cr1Y~4JoPv2J^CZ;)Q_l zWMqfgQn?r(q~xnfRL0sEl<-A>VVZa-+7yv97m_7}-|-65ZsgEMYAlQY2k zW7U++{DACa%7I)vC#7_S-8M&Kfpuun(hAwG^`ozpol%9IM(BO672cfV8y|tglnJ@A zv=chegG7f6zfL43gt^|rX_zbBTun+o1THSKit+KjrT}D6AFr-NvJ+0pq%x z`ClVzrLnKo6su|VYLqhXx?5kxpu)L;!|7pu+s_08<_BiWDf$AZICU*dM1tGeU(RyT{fFNhha};xLLaj z-?~M)gD>+r0&`YC|1Cb5xhVz9_^!1z7U`WWlN$Z4XL}Xben;0IsA9%0K^ZkHtm)p? zG!l0#q_o$g(8AeS2Mmmc4laqCqD&+xc&{Z)|UZRz5vPyepu4lb! zL6O--Z|mJ^rBuU`wyef|8@2mRp*ZeO<8-!SwG}$cP0PtTIo#XrvT-eQueuldKK1BB zT*{c5mEt8jzllTFj(B=n{rdyWEHcbIL9FbQ3ZSr>_R_peHj=PT#(v#3()bdk)~jsY zihLM*uWybd*mO1MpQd)}z;H9Nl2&<5bq-N7=qkY;?KNmM(Py>oYdM`lbC!R_9b4hT zN4(Pc*_=MKrT$3GT7eXgBy%mQTX(zSG=`Q_)++~UI%IRM3t8Y+$|U{%EJ5PURF1ON z9pN~@Q>6^^@t5ym4QpBtGusx<*Y_<`*syWWPH~nWTaXC3OM9nb$;$j2r8Hn7BEw{l z1B@UArh~BU%D&KR+?#G#>=H`@i5GV)-p--VUl-!6FPB5EGjr}LXE(%4c#X%fPEQm= zONM1Ph3#2aEKz6VCn2wK8CAlrK2PA@Po#6$aMj+o#ZJ1H!GDH%_!mw&PEp6x5kL=2`Hh^kvW?TbkHuriR;(k0)4vfKRORTx!95aos=%~=Cz^&W znNNvR*8fWk@Ug-k@3}drLGUBX63<0MRtE99vk2fRPeuUMaA?j{Xt+#=k`hP-fbnp* zNf7)R`Q>yK95SoG>QgvU?^D2F-Cifuo#gDqQ)6ykLQ_ybryO-umC%$Yl=CK;^T`vi zTFrgGT1uK!uL`M7q6FZ7RnDKCq^xs$JDtfV$UEGiuvken+hP~xe5*j_x3%#GywN(7 z@tgy)m-K1u9p>2Q(YZolY=q)lI=TRR0Q8VkYoCQoM7Ka65YDlee{4>6|I`8JeTq>rFN~r2)NIA!VBEL zn4Yxse=$AzVltInjHwC@uN!*Yp&xI+luoRn{5V1lbINbjKFWp*L%b3s%xYfIvWD6S zJDp!fS!Y4ARlSK)=Zd7T*^hUgmv>gJ)}Miws+PYOfmzJ5NK)pyeMu~y$P#|^Rmbo? zoA=f(y9-E6cDXSW&X*>dN@^F}^bs}9(Ufdi1l{<0aHG6HP&s=G4lhm^Nsi;iIc*Fe zor{0`iuVtNgRosn8eTBkRk7oUo@wT8;{O9fPFe*9K`$SspywA{TD(9MK=-%?VzQDu zdMe;9lr!wAPrE#2HsDSL35AdUe;AQ~J8xIF{n)EgWXVq89>B-Lc$P}u%})T=`xiSR zFSE$*kKz8<1rLTekWZ=j8Vl8;SM8g+j;1$?{Dct~g z7F>_+YEg}A)a&kwFg<+r;lulw<*6=0@wpD#>4Ud8dzqs{X#b4o+j45_?RO!?ga#!w zC_bQigaDsm5qfDlg$rm{O)(P=AL5!Z?7Bx%=B@000fx=YdK<1!s6x2mP0?JUoNPLz zHC1O)_^Vx{{bmJBCQCxZKSWDx`vQ~&A1{!6XTEU2EO}-jf;y^FFUOP8NH2UmR0NS# zC6jtY?hn~-VqW;Z{WLp>%=G|ZXa2AU5@AH>5E=l)QTO1SwAxA&jt)%mgbajn8dYam zH-@u4vQ3Z?#F@C8#mbR7r-Oyh!VOE#xq#G00}A|mQGu78=E>uu@nkwmQElGqHW7c! zm;~z*N&R9+tPfxm_$4iR)r;NYm(#Ir+_IPQvx>fK{zaB15zH~o!U(LE{??vmv44Of z>#=G6!Eejc{0i0B%S9VvvC2;%$FZ|Ze_=`iIKL)Wjuf6_#Xh#;=e(UztZ*ON|C2zRIPM;%@5hOIE_ z^C$>Ymp|d9^*Y@T9wzd$nthN7AfukxAXHrAB9F~Yj|Hcbn@n8(b#pl9JwsWbi{N{J zBv2rFj4qX$kpAZfIU!1SyY7-)=0iZ6_}gLggK|erQ0@61BHR&SZx`uW!6?!^nCvsT zDl_oeC?V>)rqxyh-VmgyqLducznpNirwuWWpYxZMCR#PHopB!wnOCkb$t`XN^|Uu{ zwn6-PI=Z3O-GWqxVEzZ8; z+I+G##7dpA_2&{)T?b61PsdO_U+Pl70!K;@Ao;EuWlCW<@WYFUPV?-2^bT4yRu91t zU4Ak+y9bK*zQ*~f=v>$^?O7v%&4-oS*m0)Raan701iNbI*DBGvVbS-tG0@gIBJcP3 z5vDIQKRT(FUY=Yc+m~Nb(PMI+PhfH0S0#BIW_QgSIdsF_nJ?_>ZaPo#B~8D)^I9*M zJ6bH3m7w5;x#FeFj29J_==Qz^lrPI)ByxATtf>Tj>9AUKHXGmnhPmJNQ1z<;=NmV_ zS%JLRwnj&*D04FvTTce@BkH=ZseO<{?Zx7>6_&(guPnBL(?HO@In=sX1E%d(Td}YA z(w1i^Q$+neqG-zI*zL$6N)OA8uMPRiFRbbF%0Rp|u>}Td_vkAO-RvFvm+Z8yD$P-9 zS`2YRDIH(4(?g~;D>JPlN;+t_nZMT~+z)O-kJj5c5pdPN^JYlsHUDK0TOfz9k|%`P zNPG-CS}vTEHxMbzztzt z*0}_feiQdnI8U~ghPKwB+tcamRp4e|8TfHDzIAvArE5l&u5qgXxmt@TQttI4p`uSq9?&8sU?pKQOw;+P&H&Bn0Q3-R3%j)oDv4Vtz zJ{O0bjqk^UYXlKT0bwd;qiUzl3O?!lDXv}PxtAZAasu)hmN`5h0{oS?GKjVt4ZrG3 z&o&wBzf2FVSNxF(&n9&=p?SjA1)q#u7K(onWIB!P&%rco05d5kgKkk;%KOCl#a?y2Z(0V;fi8=03dYnDo>=O3XN`1$a5Lz9WyE#y5Ru+w-}xyE~^zRacG4-zy5X8X8`guWc53YF*_X$DY^=jb9Vh)q5I}c-dD2 zJeKEi!@tg^hUe{2vDrK;@`m4>kS&lB?IlhMHYUJL~m zbhPJ`TZX(YT&D;#Y$D zguxs>S1>Ey77Tu^2mUkq4P6lp4{e^z^<$luwn^uy_UCKur(uGWxqg|4VOBu0Y--?6 zuLLhF7Pp6>M9Xu6WYBHC9(|r=0*~G2o#JY;CzNxrxU&Vhj%_>!ksL#K11U#%^yC%S z2!!Qe^fzz_RvapM1c({AVVKl@r9>nYgOc!=V_z&U_j5Y8>)(spW6>g0!K7H}2kd>k z7Zl&lm-q>J!<%j;+B^fh8@*S%%gzHHxFRDlpBwYG&TQ;1a~Y7kJxAZHbeL1Xe@q^M zDIlDF#VQ)R(zKJM-qJw33$aiZ?p3{4=9Lz_8N1>-dh#5+I1|gkij*E3f`So`MnJZ~ zT_Prt-U0bZowsuj&!6!=AwHFF;5UqP`#J!LooB+z?PDmKz5Pp{OFniY^6twxf5`ID z8SOH>iiQ{5qg)rhh4I3I3-^cp?xWpd_phgb6_%cKlDbi5Kr|HxH+UV(jNzah_%{6; z8N2VPk6FZ@F$Bt{08Hn4_F(vl3&fbMlM1$q_8baI*=+lLCEo9Fpsu?uAs2ZtwV=JL zUDp1~xc;vk*Pz0u9^8uR?htwz^(eqLxBl=~ZHE!xOy(gJg=PZ;1uY4clkl4Mz8#=C zG@;88%UB~B?Rh03IO6oM4{es}!Z%M2C8;MEELTf<;vZ7y;Y1>rCZ!=ZXRd8fNyC#u zQqr(hi!1UvU|ggoxpN63Crza~y^slUDHPXpmmh zV=c{BXxR=|V6=himK$!fcb&!dVEO1k$E;u*%4;>@v&_!U3k0oM+TmB{ti<(!e51>T zg2DV5pDFur>5{*nM$K)n5Ja#Z&t*}5mYm8(`9Zr4yqWqGb3|5u)(WSV#InUtfzyIX44t^NEK6fdjBu5C$2H2Mj0-kc58g6cG?$8^CAN z+5U|CyVe}R4>eXoSGZuAn`&D3)|W5cEc#fV6xh41Kf#w|1`5WL3W5icVb!uffYGI; z#s8?nmfoj@fRlOXNvYrusfcc0U|B!QtjNE~kFBqvGc69ZCytGvjCCFSfiK=6K)9jX z0y&8VPk*rUL%W+6ivT+6R5NEEmi}ips*vA`63G#FMhX39q)*SKHY8aXe@dix1GClf+P zlu7~0Wsm^-3CHgc&-mg7>l=OP{*arn#w(| z2_pFn029E&*82lL($u2->UG}Ojptcd=lex^{Op`8G2#!RAOi3CXrsp;;`{D&o;XBq zbRf><3ctJ^N(0mn=UEE^_X_h=;01)v`E(F*rytEh>nn$%r6s`0pZ4PdC%J`2M7NCV(5b?x(GN11Jp;0>#AY-s4^Kl^b_5+`vm z5KC@a@Rc%{V{5*H2P(oz5hD!r1yR;dszGY{vUt_qv;~-aS1nJN7%vBJzQe`8_IUT4 z=2dKV@m6)1fDe0Jd|MRG#`hrNhVAAkSAi!Oa;zYD^WdIXQZ-9Ku5!QRuTmKg+uOFk zI|G2hHIQ1cLVZ`a^6RF)s}Md}63hVKy<4}^67N3#Q|9%EVnACSPK(%g$%`4N+F5VL zTe;i}XJY+?=z<4VeWdl<5v6jy8K;ibQFvxH;1&j8XQJ`sOX?wnrs^iT$RdFI;j_Vf zbq1q-O|i3648({Gv(<_SBxFqn4nfTT@wZX?nzAA;*0#=C%?!|c1HW!`WsVB-`|o?K zl*Nl8dqH8w61O4IZrqR`EzsC$K&1XK2quSx2=SR}=(idT9&@QB?t_4-kInghl2X|YoB`NOndK!AVoF+F-aQx2l_qzdv)FxiB{=XtLaN>?A32=_mvIqcBD)H z$z)$V)Wya)0}w1!5hQui+RiH`x^6YqRiva|{x#ggy(WZQYj4B-oLPvYCPSJA;({jA z!4A^%5RFnn-u%ue_`WT9a0qZkWhN>NevN@u?usiOF5%bxAwBEiRjsVx-lPr*v3_08 zH#tRt5+Tu+J&*;%dO~JiEJ~p zgY9pkUz2h86v_DGmy(-quFBD8yH&q-%Ngz?`=VLL zP#+1iF1wkU6%*fWwt6+S&-7+k#6-CmrYg@cP+c9pd3aROnTt8nrYRBMFAO zSLT8nF2U&whT&iY(E@{$ zjy2l8;zGJbMU=SCH3St%kR}DQHB}R?Pt&i^=lPmf-Jgly*JA5o-JVbvd@RB?&Z3CY z{}~81kRZA#xvsO5ZA485;l?N(_G)@VV;lYkHh!z!O?Z_WLgAdgkT`6I#Z(O&Gc;=| z992hkgQ}-GGje>SLfYrv@CI;oB+k5>sZ75+$K4bBBykofPA$;utx|rz+;O_r}1o(7DNR~Q$$MlE&uFsc7bDN8H9BsY*kV3XFjR%m5ZE$6kc-zW{;n!)FmPea4u@E98uiS$J9qIVb2+Uli>vc| zN}Ve8TEjX9FX>2LOuKlK38WY(qX<{LO2aN#8oo18hl7j$4t|_PST%nO)c7GKAg*!4 z*$M{@;ShPXn{ZBO5*C393sb$4)N@FQNJldok;e=tJAW%E;@}0R#)oR)#t?9w3%IT) z2-?<_5bjccH)P8;ALzxj%Au@LrlDqlXyY6Ysjfk*G@dF3D!btC{sq?x6+iQDTp1MEL6jjdg|m`Lp|LLH`@iYTDSCFi`9Og zR8DopALv56 zwo`DJAOR|vmKPd55S|c|)fdnQpkc)Jli0wu1Eg;}#}e_rJ#dX6somJQxf~i^9p4~r zrx6k?x^P{r)zbb391_;@%_f+Xa`7I!*C+%XcloJ@x+YG!Y*=H+^=8a{`uSo4R-ElGU8Po{a~s z`lq1mJ~wUw-Jp0uongW8d#J*Gjf59Vs{8D3#Bsch1)@`q9E=FanFE~a$8g7>poMDH z@uVs}hJt?>Vv^EMY<55H746B~46;xNN?f_%+t@D*(Zvj>Q49o%XM98pG(?zbhy>56 zDoCI-NC=IGjn5B?Tgn=D8S^MrkVNFR}W1J z6&A;Tw=3-VJkjUUzt_i6A6{Xb90QRg34=|#RYzbT3lW@7%?%n^5{!<)<121dY}$M3 z>xPxQZI5{9!nbKnvG6?zAA#n*MksaDzwjfKV{Ck;DeH2v7?des=g`MsYFl;MfBfpb zszkWLV;Td$3-nOJgU?37Cs4NJneh?)?Hp9TYmTE2-q^Jn3g&y(ypMfsxBtPGv$Qkb z`kEL~cR8zy!x}l|$4X~!Sm0X|_@HkW&Lh*z>xA=bxZ#zhKyPFQbnln7%iW~|=RDif7udM>W<$R1k+cf; zuhH*US8(y$7lyp3`>YIKYZJ%85s-~YwY@Kr@4n+RDq0eb*$tE*_5)133j z{akj(TH!YsLfk73L1FjDo;O6i&jZ7Q5r#)%doVV}t@!~WCw?|Z}lVmHY2giv3R zy34CNhEPeqlkoz~CZ}7J`EBo>2LQ*DtY$4cpor;)29_qn6cv5ZO7$u3{8T$O@h%Zf z*>A@wRe&%f3)Zlw&hRN9{A|PNW@g8*Mj`!1U!l8cW_Hjnad89uRR$MfAGv{FjIC8q zT>>MEq1Og}FgC?|p%bE+KA&3iO&c=~XeFHRB<=buf}o%-Wj3;~hC&7BtHdWFKU7?&!m2zpd_`snW~SZyv=nU4nQ?TN6}_*%hR zo3PfM-$Dss(S>D|IM>K(?)X0%Xz=YI2_FU%fNY!JX$NG97}QWr&4-}{^fVJwo;vGt z^E>>$bZc0r#UzzbW4!sAw;84urB4;}idRT87Yc4WokbQ~@hf~wM*w5>nU`d_=@P=% z_;DiY(}_SS=iqV$EA`++U?T-gg9kInHUaDC0A4nU1C0_bG81AX)an$uzm`kW2HCb- zezjV6KkAzKsSl;Q|J2`kl%xN$3jG?+c7wG9QeJ% zIBpZ~F2Mf+kGS|)4J7*!m4?otV*_tL=fZNQBNeWveUya?-C!t9A<|!0lmuZ*!zAD= z4KXh&g*f}m#o5$j79*cLCS6aZ-XhNJP$C6${Ltomr}Pc^A=gQ#lSf=JlPmE3)=e5l zBjqPd?>`M8k8OgW?I*t;l>CkO#QObekuj%+R~x{5h}&~uPg2O_q585m4H7TmeB%|; z&+ulD*_7uWprSbUbx0N*j9MZ8Sm;$DW5V@QF@F?-@oq6VGc9@ht`x8S;5xJnEZc6ob(v}lv zvmf~s;!VkzIX$PMtGFfoRfQj(Wp+ zHhk9{#xb#P1os0{AfKO_kqB@3o)6r5&?6n*71hP3TO>YwS<$s^|JeL72k-_}D9-n? zcWeytxh~;n(R02dPAF{+l+sB-E>8GNF?`6^8Jv}*fSC{pdF~AEuc44;LLhleta2aQ z{HjL_o#5_P!6Wt+`*hRFM$L1>cA&eIZh*D=Llz`DJekgG*ZSH^`ESqD2;)XV@wr0* zStVG_pWu_L;A+bL0x2bTZYVXs4$d=QJpF{6oE&a|SX1idyoRfI6ZE=wmZJsynQ+pU zjjPuN&#^>OQ|dKWn=x5{GYLw`kLy1dzfu<4MU6U)^N~%e`35Vmt2~&*Sjy&IPi5c9 z=yv7a3*D#39`)|=Y-FufO0!D!BI8?B*g~uW4fD6~-)5D`bbE}NZ^vTj7RcV1(i&BTXv4X5XUE0#Sv z<*k2y0F1``wwmZ7IzHv(GkEYq8~}NlIW}2|)zK81BQ~bi0E^BZA1r-O?mBp}>hnkY zRd+mBZoz3qp68reCOYHcS#vWE{W8y2?6e;;m8+*TD|>~Go%M$vzyv345ac@jG364#+AE}Exb?eFK9HXLgBO; zObit7V{fYe)V<;-Mz6qr%y#2vH*C*&F0{z+hIt~}Wd%*ho(tDZK*3W#FQ*vO*Qdwm zXq-z-ezHO!r}svL*NU9Du7&MJuR-f?sL}oG+Rmv75;PFnw%4)jRTb|x&=vC6%Wu`k{$W4Hj)`Qp))bI zp@nkTZ$~g}X&AqYmU4nY)~|PBCt%3E$Qv0JF_MnL9=hSZIY%j<(#`#>m-uN3BIn0c zzAoKG^t@GMtRbrE!=d?ds3or09ec~cwd7}+Im0d+C$CZhZN6?-jsW1C8rx^H^R%DpfE@H7!jR$*fpE#%2gjK{h4)ML_CL9_4;AuzWEffrUUkg2e_ z%~9*HyY9w+YDArxmp=kjOBCTZ@65f`iDz}DO47l$uWj~vh&?ZCU8lh`8z+subbkGQ zH~y6Hegrs!3Jt#Q>^=L6@?BYdKW@_N0c4R$g@EJh7h5)ls#bhod zy!`;~y25^t7#uoq#8-o1d$TFm&@h8PiVTc|WZ33f!?%m`Cp_lSD;$_1z;>THRKt-Zx%DGNxK-r~Yea1h%~C|7vBGyZvnXsY}$?90pbJFn~keYs&**gT%V zR5TNQiUlV+F-4oM+w1-3w~0k(sKA+Os&~PLv(FB4IK^b4U;dfjKmK)D*gmSRIWc~E zH+vRGYDU;dJD*COc#@-yBRZq~yIzFk6gj=ZD#(sR;;$!Nh|^QC@^?I~vWx{sd)&o` z714GpuS9sKx(D>n#K1&Uh;#`5UeqfHpF>RU)+1H(vqNy#e9zk7Z8l;SOWvJ_zm6}b z(@*RRazhEydzxwaK9u)E_%vT`PS@Cgwt)1N*FU7g2#rK>x7a?c+TXq*6g?P=;<7p8 zoZQ$ELM#4T<`=p`5+S^c1343_VTw7~BqATke>e7u?3P_2mcIW49hSVF$}G__Tb)A) z3}>Qrpsaw!rsh~vNG@oukSZ{x=%`~cwS=zxIuJA7sjG6Pl`MY|iC>5Rh1gNn^CV2W zuXNSYK|09Ini3bABa58!p{J!`siRU!s#}uo`Rexy8#Wqw_D*OknjBk}TMPy8#-T?9A=WVE^sxz}bXKLyDJJDB2r!XiT^BUC{;D^>K2naw6| zWs$KcDRMTcgV}5<$C^E*AqVS!Gd&gII^1W{?U~A&z7z-c;Q)9#7jPi`AQ{jOMfb7q z8k!!An9)x3C>zgnZK93ZPY4xzObIw$JWT-VhLSoUn=V?HAx;XJnh16>B1S%=Q*r#Q zVI7enrNu8rqwXrs>P)o`7KSiTr6MvlFqyJD*>heY1O83qiu0jZ(!hiK7jnQGn-U)R z<;5NzW{534mQ=bTSK_~Z-fHzvxjNIS^7wBG;GdVyP;UUmcf&%gbD zsb3nP2>yA$A=F#K6{IUfGeaxAZQG;AtQgxjkSF*Lhw#rY^h&k{uN?9XptF??#z*xG z)rL(->ueRJc;N72$439hcajl@S095L6w$I!6K0?%`H}>@@#=%&0Ikp!KI>Q)00}EG zCp`S~2j7dgTGP^byI3!5y)W!BXcKkMpKzN`68~)yi14xQ=zue``bbDNY0dnqJJ}pb zbK`5^6rS}8l;@iBK1qFeOubddMBv?ce9VyySkzQK?ZX2D(VJB^SQyRbq?ir+M;3x~ zQZg>Vb&rOD-oB#Y`MpwVIvq%lnj#m`?bV-;7@jfNLw-%=vbH0#H__LNyD?Pm?*EMFz<;~V0v20)PhjYshvNUi zoNQR-Nk=T`>&KoXsEz$sO#)4`?jKT0q|adj8goVtQTSS~=f07|5fB)?;vgD(D<}_} z#2y-@P_5#^9vqd@8WaHO2s@XhovGo=zB36-dPT{u%CjW3B}+Y{s?s%8=;%NB@~v<~ zz7JXX6(3eWlE!w~?mn9${LV!>b=8hD4ND{6SZT6RE&+wx+^1 zK$y*F{TQf}Ul zz-e4ti2s?&-WLX~ssp{q{*8>fE#!Mu2jtiAaydJ_J~&YvAMYy;%-m5|flz3UG6x*I zNGzeLdc63V1LCdho2wR%B8Bny$=gP+Ci7g;-xGZ=3Q?k9xc$|y#7Y1seB+zc*DEH} zj4{5$r_N|=t4>_<{|Ct8D^taLiD1!rJ=AM{%6y`53+{KorMrK7-k#S{rJL-O2y5p0f;p3t|P z+6X#=tvCN#M1i%`=c||)zHsXxEuA63?pnu|7Y8}}TX%}Ljp{0{|J!N{2jhR~4ceHo zrtRS&SIdt%inAH*L|3y94H(M?^~-fBUw@89Fofp^K2j0vZ7QmqYF0JW6r1ugGu~!I zk!N@Wb0qgw_7T(2!K)I(TpJ7YMIs9Uh>;A>#t(zWu@%WMYSA}Q*svp_45m9LM2#mM z2k+Tq9{jw~|5?$)P=Qu`^wVva{Wv7(7YS?MarK`Z2=q`Zh@3C5marpr4tG|!4i!_L z#DnQ+;14NV=doJCz|nPT@u(I$E1Lc@RE*e?T-dNhREBw*=4!%4oMM-^s6MqaHVqv8 zX`*NN1l^rg6siVKK~FZ?O3_qh*cEQLP4S`8n{@E6Bvx7fhR$_4m%fo2#HI4s<=tQK z6c-uvG<$3{NX@|L&yKcYKFlg22`D{hx zZ_*gwn%2>1s2s@oZYR@^!LN0Ch_Aktmf%Z67`nHn&ELc+H$|pnD77~T`wiKZhRF%(G26Tzv`kB2; zn7s+Q;e`Gckn4wl%@*0cF5*G;OGvG8q~!TGO9V$UwaxDUHOfZSH1;uno|-g*HyRZd;@~hRYQ4fTO>5v9LwRGta@!_-%afH1YG`hZR-bNTpRT zr<67qmrdQ#GEXF#$gEA~<} zRuCbt1q3%Dz6n>&I{wY!*-FmF@RP?2VHOdu_M^!S-z#_{03fMUl6=QkCcY95y8bXj zi6cb;-^KT^vx7rXEL0hiKYCfE@ip*IF&Mc|9-nxa#c8BvrOE2PRkC$#(3zEOZv`+R z2npSJbUzr9Ciewzi}Z}B9H@-^`Kttqi*qeH=kPWncs~GBK|lv3Z-O&CRx)o6u8zpI zoig*t+OenU=6$)Z;Y8%mM=&f@_b0?7h1*NJTj@V?(!K=ieZS|SYR;W<7Y^ZiG$Md;p@vGtnLk)!bkIJO}arSuGO4l z%WoYB3Uv?$!j_`c4=R4tKt~v1$xiVJU5Hwj>Ua5vTc}-!Up>TrXPLjW7VFw2P;aOG zgLq0fjJ##!)bcbkcJT}Q1y907pSw`U;1Fz2FNUkJ!ATZ2Lk|F&R{!?9k-s`aCATR= zIWyv&+xR+g29vH{1R;&$3Duo_U$EiYFF94&K5>nm1i{Z>*cvfWCU9ufh!b&|?0&1c z+tpEy2p8r$DTIGozs?S!w@X78OQg`iv`+L>-cK)imInYV!&<Le^v2tKL)dBc^e(o8HJ_X< z{Arz#ta{XoOojlAM-C{@;8Jrq!*ug^VDiH_at|Y=0PxTu9uzESL15!Zyu~BcWy-ZU z`d9O2<+t7HV;9tDb|K#)y@2KTJ3>vb-5xw+FcG<+I1Uq3z&bxlK|V?JA2jEo6V*Tg zrePm3bvT$6R_|Qe8}X3ZJ5lj4gRV#3hSLY>C}>E|VdwL+b-mpN9^K#1vEjrisw<|h zc~W8+3@-8?-g2^(Kusf@$M0}K2RUee&*-#H08FP1i^Oz#GAStyW#^j+fqu*BXUO+W z!HSj|Y?jBr_6?7rx~o0mD;s{$8xj@hyI(>j9i|Z@go{4KBR3i|RSK`*P%)%c7mu*q z%EOm09{t6mQ2*|GvRhLu{J>t?+u3!#_Btf=4m@d}Fh7R5vHh)qK$mDx#mWGPkZc5( z|5-9?Nx+o^JMa6xWUX&r+Q_@$vbL-e8~Vj4C^j@S9p-$l=;PP(M9wR@;Y+bP`sI{e zb~;KF*>-v0=?(CE?GvIxm^%srxXX7oZ%LUYC6(e*3CQaDA+s4nt*434rxlOzuG%b? z=Let}eZrp)KhZ$T_gZY$8?K8Y_dcr^03uXDG}zUfta_MIzNSI#`nQ6NeQtG9laz;F|Jo)PHm(1*&@NQ#ZbMrNP}Q8h zK3s7xfqEXqDc|YPd63!acjxzq3+$EM2(mR(CPscAx6X0u-*crC#;h*^`ppqZ9)ic) z9BFQ61H1_2MM#Lh@^%`N&Dwca7N;+X>H1tfC~aiQrslC_Ey!&6O-s&v8IM5hd$z(h zCI32U1ORM5rBld)xQ$OsrpYgfer$cb4BydwSMLb((QbH8sY7_T2Q^{zC@*I#*^Z0fI;2*8{E zd#`1oW>Ba^Dwm+M8Ll<9!)2sF9Oi;(P?Bm<$r^k9Z?KK`#@#tx?lE}{Rhc{|X%V** zT2zkmiYW}ncWA*}edV0wiU21~k1O2yyL%QK+gd1i>~xl9xT84Q7$Q#w<}14-C4;H_ zkwz*^)Ya+lJ=OfKtlc9)R$Xj^D($ZB#-4MyOQbvusL;czRSHgUWF$$dpbIImS5*hu zBTb*lSy+7$7Itpa%s!%1zsOY7fD(#$cGJEg5#f~bN$jsO01^1F8|^UwyR(_`^zO~O z^b{&&>gjs_XPF>4Bq^yl+#57#~YPc1vvI(Nbz{v8)BvQME{C<{i`UWC)kVKUOE5 z1AsLJp&O93;vE(IL4uMP*0i{IRvqgXwPc23sXJfG{fD{+fy$}A(OMN}U6(9s(}125 zC}vBHc<2{^Y1KSYC+A0{V4Xu`0+adLAbtFz&%D&KZR5*0Xg0&1OxngSax(@0h^W~v zv5$Api zrIU!rlVCmEXkB~+?GKeJp!(iSS3OBXgFgL+odNdKg}Prj-y;qkP!>J*;Q-;;QV<^s zj3as^4YtGwY}nwpt44xyv6nd`Cu1OSbulQ)hJEv)5`o)~03PJid#<0nqqhy3VAu1+ z5j!q*dqUmxchAV=H+RF6vwO2Wj@09SJ^*sT`Wv+)KW`2AK~qOqO*9mx1SxRjS`__7 z6L`f^yAF+4vlwg*Z8;8ilQ8NN;X~2Vd#t|gxi zzQ%UHg|)um*e2fO^2-lw2EAaUq0Y>H&IErD>aiN6L*1?e=?wSER%H9eMo+g5G^L0q zM}~cMm2B;jyc4{1uxdHX9j7@AQK*5dfgmhjb76eZD5VP}-Uw%aWMdos+<_ibDzkZp z8rZw@+6FY=A5rSXTBaYZdgnbL1cP%AxFxK5Xa=Q8sJ!XBEG;8Ti~n28;mp zJr0_KjxQ zg4JPpUcc^G4X!@Jo{}@4q2#p`2#E>=l%p=%{Vc(V$W=I3&Mu}-3)V$}davG|J`X?A z4bMLypS*Jw^lh{?Prn#JdqTi)BKgp%;@yb+kg0`M6F!f1)Z>N0>)rrbPN~Of)~`i- zZM}rWIdkme#kvSP<9SMQ>G|YUky-IxEkr;ENMlY2-7F2CkN#POj|4OzAmx+>gy!F3 zsD^l}vFu870mM4=4#qTtnki zXC2#G{*|)kclhO$7TLzZ-E1Wd)7{?4Y%n#2nhs4BOuLh4$rK{8*Wm=B;=w5Pr>P+y z+V7TaFpC6cg2(70WJu`gEK6lW-c>qk#FV`f21$;vVXru8q>1E$v0ad3)$yzbdXVY_ z$rLSx#h4O_-lQ*FJ^YmOT&V;R@9<3VAeW#}M;;(wT51xck&K~r%j=LQe>B zX7z{;;I#R&eZgWqkKSYX-zEqg9uVbEAdjQ1xCS7>F71`%8*Z=g|cP2cGUS1rYMIetRWucVlp~-=Q<*#Uv zv;%2C0Kbm8m_Cx!dX2lVUhq~38aeTS-1j0ai6a>s6kxL3+%|kj1whjU^1vetDz|jf zx^4ab7_R+{CD;4{auj5|_4Bi<21~hbrTZp;NG4i(c_Um^(*;u76Q0Ov16v-kC)V%dw`B@x70 zDjG0$&pbF07Skl3W^B}OkBb5YtTB#FAVoJ(yq*E;A&f`A+@L}?xVGrCrLxV}LT57= zr@b>whSDlY*Y2kI-KX2RJ(3G|5zKEQ&7>8}GK1m&fnGNj2E0&fvMqXBDB_f8d8F@n zG%LQq!Aq%*=88S%jeeB|`8b=Vr)2B;awv4tS?KOU1k4{35GW-x3|=5lw`CzA|A$OY z^1x~V4EB;5y8%yoK(^Xcagu?l8`ax-9d6&VhuARLg024~X-7E43lVtWTqzH@Z`PyD zLA=3Ch)ut^Q#!pjK$JX^KdZ%%-QJeWX(JGO!Q&P;X|h;8%;Q|y;KaZP)kzh?z~+yZ zKPKClsJ8=MrAxNcWRm`+dH?xIb-=hL;1f8JF6ea}nXfF`iubn5J4!mH%lhnd8Jc;w z62GkTw~2qw++G+W+i^mrz;FsnF1k;P{g_U;KYsqkjMG@f?7m<9QGC~SE1W#z$GYT# zP5xl%GUNm{{S9u`KpDOfy1a6PxILLRY;9h1pKUrI!uT(!Fb9~t2(`Pi+QuLskdVRm zymOC1vovNpVJgz%nzPm5(S` zWwSg^Z$@W~IDJAjT>s#)x{rIQ6w2gG&<1R=O;iG1mU-ZuyjBbEj66M|493=dfMFNP z2rg1b@bR%hDwL3u8?VH0wmh1XuWwlcxwqJ2xi`Dhd39Fz?9b5iF;@`erH2YqL)DO8 zBzgnBh{(srW#-}Y)EEr>A#yzyRX^8uitgqxB&_Wii~5a@nx{?=D^Nx&jm+|8GtL#T z4I8VUHhR8uGcY>LY_zzO|7KXlm2{iz7A@1?AxCDA|xw&(UfFSeNY1F0;>Lv!#n0h z&kEWum!;%3{?9+Bg`O{VuGnn46pt=66f%UAnouKzP}iCI*ni_&NKcxgFQauKA|v9d zMkONyb!6X{$PI1j9p8+ciL_|j=JRdeV}dr43xn<-Z@gf^mp?)k1zkA+yP0{s{4w8vIyIH z*x)@f{i0$S!7Lu*(|&p$u~oe8;wWNnfjOfTsqDs$>P)FPIaH=r#}pk2mSC}8T{ne4 zOj86WWfL^wEsKkr^|)!i087ko%Z1ER;^P5l*6+G~V_P=TiXT3>rZovY4nGZGqQzSu zI>{)OROJU;`7v^Fc&_EIniXSWIIb-*-A$9yHeh3f-`ABP!Oe4fe$uk5Z53I1#~mYz z>F*XWXVPaee{N3&WPbi}%{wNOR-gMKf|MeC#C|92FZi-Q?^M*Pa$sNsy{?EY067*& zB8nNzU-(8LRPWl>Q)p4N0K^M7h|#A~4;D7motm(iep3NCw{K`dUH)8wP}Y$eQQB|J zNK9TWDcJYxWm9stuhT^XoFZ@IYxdy6IDyG)!LMY}O*~_P%9_TDqf}OgMS?a1SOo}L zuO$~Ub@ZKf{nHDGw|V4DAhGKnh_=Yu<%YzQT_fri?DR~`h29+v2WOiDNfL=NGLPMMHELjqVnLa3W^I}WwN4jr1}!l`QQ_$y@9?0-&MAog$_ zV0^clkv5@)>vyQ%_+f%%)OPK{W_LiE*m9*MB)EqsZJr@f`eqdLx8ru8HR9)*gX_z`U^$AW)8zK?jA zzuP>}RyQYa6?VLGX)S7&3SxN@+e5MXgd`V*|NV2 ztq34D@hQa}JgVfZju4pC^YP(bxsOW8+FhPbszcuWZzcdFtzJ)P9!B3{Y;Tz^yh7ThDpz~~{)0!)TNlN~W$#8z^t(^Mg_=$(eZ1X?vJ9-9! zqwIh)g8d~ui(EX!z9Fi-wG$#EOktX zeH=xUgc7hg0#3<{wHa1I+AHVyiIa8V6uOC`xvzoNW=2&V1^o`pqX3^n4-GLgc%yPl z>!-jK{Bf?p>TVIFMe4?F&EG%-#y|4A#t>Ky@P3!loeh;mD0vfI?Jef&O6=}+Aofxs z+<5ee$9`tfn{K4(yeRCK_+AOM1~d#B_=2CM5XFjlTz?}tov}?xg7PsD3kpC$=5k_- zk#R}_MtUNKRPp9T1{u4*w_qvm4%WBGa1aS)6Neex;M`zy#2!mD3@q!9V{!P|uYq#I zKxtd>z974%U|hD*5Td85g5F}2T%5|5=AR4;HXoI3eD9uctXE=#I9p}Q#6IiR!sy5$ z<;!3J4bR>TtYWy;RkQ_wEr=ZHEL_G7n;6eL_Vb+e zZNfI(m339XT^27+um1Mqo?mp=NT~^Ae_?Z`{3TXeJuSDd=LHxCqq9XgKgwoo`5BKl8B~YN%o^h@N9okVDldWDR2v+qt(_2QUYa74aAj_+gIe| z4}A3bn>kS_IjF)?MylwMC!X-D*Oh!ak&g!V0&T?kgRs5*u>Xx971R)e8>TV6lMH@u zL%U9y;yaKo3A`un>}0JJ(fwrD&>cg_c8t0huW!-A2HO zw4f@wZg{aWR}Jnw|Ke3t*&rSP39ie%0ZemYUHJhoX!T+a6c~D`LmE#d+OQMp0OJto z`0Erlr>`d)+$lA7jUK6C$vH^7V~1t0<2Mixczgj=!$*U6kEWU$L*!1Mf< zU7B8AknV`-lG+qfA_u_z3Yc)=WPA21dlqpdORV(L8w!d4Qp7ObmLGkLOK7R>p{7LR>e+bkL z{w$2xGZHF%fYK~v+7-L9_>!7wkO}Ax+{}x+OBxM_9So7%+#rbRi{ z(zZ231n7|-{9?+wL>OAjojyN?;x{bMLB;RDCwq(v4jQyQv^|Pbir>cp#UkJW2*V@o zep);4XvHMgYqaBjsM$jzob;?9Q=j~J5Sya6ewnY}AN99ay|^&ISlqISZMRpcU2~FJBT!yR&b8 z8~+j3+)EHzX+5b*DzhOdyrX%NuEOp23q8a=WDHRN`;jKi{1OZ}C` z1ROBY#nf4{-uG(SYjeNumtUl%Lls3qx-Wr>E|M=g{g2&S9lR_`)V@lxRKVal3OGH`5P?ZWNY3%)YTdy%3>-Q)~WAA#B|Ot^d!`rGa5 zj>d5rCgl5q8)3+nz9b`cprfXE7|A4Ti2?dl$$*}8mV4yaCwj=)5iKb5Q+5<_?%=BP z7ay@;-V=(V`4kp$odCb(JRj9yiMdVq>-CZqzU0sRL<#FMyT&`;0$IW(ou57K?x#jE zo%x%aoQtCb(o=LE`RS-9H;}I1z0-G|Pr6&?mo5S||Ae)l!f=(vHGpr}j09m2e`3&~ zHmr(69~-BijL3@K)o*CG=@!O*YxxR)G)P7Xc9TB)Wldyc)J5HeL69MR_a>6@4f&b} z-4H|Y!7!K=(>*Y`CbPJJF;IF$Dq8`r2WfC(hJ|PXn{I>-V-~p;ENnii z;6*iH8c=?d&D`we7P_v2f?H1^SIhXHaQDUwNKz*F&sS@X0_)nmJiU-abnZk*+ov>| z9rXMA5Tf=c`^8+;+gX#Z#h>fih(g~X$=P2mh-+8>WwTy3P<7hBDTllsQAHma>bLj# zO|K#ENw!+5B|%l*rr|!5**<7y#8ZX@S@$vgS#N-_I}c|lMDk6eQ5F|wmU);VA5Q2= z2&~pX!m&Vf8;$}pLx;JcBYvanyp}!MV4a6&DJ`G$Pkh5d*o|BRw;1-px4{#Cj(tMT zV*=~zUm;flQyuhwQ7&?%xCQt8G6+Yn)eF*>K(@!c#EMp4QVKd~bwfX15Yd&=d*TRE z6OTM5#fY`apN{LIc@B?30of~T^{*$z{Y^F(oL1Al_%IU!W&%GfV6}FvFbBdl5Ddc< z_(2Ws$OFFHAm+wtu-R-W(w7UMUR9kTBP1*SRKToGHcCjcnt@rRO9@7CmT%*AtY3t& zf5#}xWdAylNO!s`PLrIkYd+coUfqHd*u32M#0*i*8>;OfC222HjxW7mS|x}LB4ch)HVc{ZAPLZBmK2mVK};9eg0=nN!^Aw1aMcsl5z>#`gG zU|Ao}ooy^`RwH&=N0LoD&X;boake8Y=1Exix8;Q9Tr=wiAKuII@O#j%MnaFL*Gj3p zu#cZVJp0dAKX2vKN9IeR9(L^rKT$mY@LzB!{FYdpkQ$7tOzWx1j1g?a+J7840t9Va z`upj+$dKF7LQmnOf^555ykv$DeditV)V0dUWsSWK;0X)sll>u-2Q2J`{lF{*9v(3L_Nk)mH z-H^TqoOdDBv#RB-Ys>qt5i z2G;9uQ(T;$30DzjYvNW5iQ)lF6S*c05+F0N#gj4W3kC^lxXV8`t`1n4wT15d!88Mt z`tp$e8s#b{TK+*NUF{(P#zT1q;yNPnQ7?yw>1W>n!K* zRy7EshR`rTaDM-CI3G?xJQ#)hauUoknxL?N46bmSR(fU)jc3tP2)CNI6Q1%V$J;No zV&dE$x4}pVac1Bk6cQ^p^0Yd52;VOr&@d;_#v#eCtB1r`K@MrmFbT;?DxuM7nR7dA zB&mRv(r7ex9XA)b@e=n3NCjW;hV5mqCr8R0*^?xo2a3c*&$v>Y7pl zikVn`_1kvsPF>fMy6UF3e3fK11;y$7n)YC42)vd73Kg+V6SW$@9!mIe&&;-UCr>=0*0lwO68f9ZB02_WoHS;7 zRU8WL2sq}66Ub%!Pf|yp8azbGYJS2GfIrM1z&k~aD8O5qi7HdVZ zR`qg}7SlI4fj2sD^|^WBce8feX=m zoFCv+aN8Q$#n=kdvYMnRy{W1FV>X0iWu@A2LDHz<;Y!y+r_5FXkTHjOXq<$O_WLNc^C3C$S)de&q2Bm4J1lLV?j`sB%+GROegr+5r zsIFYXw3@0R31O`m6fE9YukARQuQq5FjO-lv-JLcVqJonVW1I+HVA^$?d>&%TS#hSq z3CqxYpr&g0rDR3stl7T{3mXc4K!npj3U@1FI}0qw#6#gjjgw}q^`;wkQ4FJO=|C=7 zaMM<^|Fm}8O;>9H!f>im*0*?}git>++SASaVRKs2zwvz3=VOYHL?ELTr3uLg=aeN{ zc(4Ak#v6r$4f0*|a16krz?{fafQB3g;ZWtoOZy|>tfI=Z`MDQS0%f;2;bdksn4@Y; zUIE{;<@e#zuL|shL}<)L>0|+!OQ;v*2*|aO?p!jPx&&1xkuKbC$^&Bl&s>E(5|pwy z1^($7*W@gL;pWzhg(|0~>ch51vixa+nwa5^R%evIU!rZ^0z<~m01o*4%a~El&HFrz z1AZbCSbW>k-G^DsFiSG>oa#=OroY6JbN*jRB@$*M%ZY#GWCT9NY0vye7J#cgxsPWl z$tuy8z>Evd70dJ@;w%oRe|quw#CS$S7Ag+~eVbCS^x7Y$9I{2yUB8GZu0#CT5C`>pRDrDlYjMp_8|z`bB-G&@4% zIZMyn&$Urz(iAd^#7%&8(YNPpu?VR}KZ277e;z;LekuQZ5r4Fhkm5P#J)*FlW0Ec2 z&hBIt$5GI{d~ajU=D)&P8o~R)KY7rA42svx{5l@<)C!FB|4M41`vW!o&*&1dlkJ|F zrbWTfx{jY7#fB3;;T0RBJd56Cxv%RXJ8!jt`(Sm*I4s^raJelef$-uc%;J=!|F*>4t(8mP)J9#Jqv*sE0u zBSG(<6BV$&)(!<=eEUpK-l9`L-7Um8Av-frR?fieEdK|eTqy{D;!FUKqWlB1D$H#y*@|q!=GRf;W1&*cF!E8H=_5gOU95PCdRmwH z0B=NDhTaeGcrVy}R<9ngp#l64YT>014^n4o9kr~z4Yl5I6B7<#s_A+Mt7&1g#HKYjQ|^T*EKIjAkW{hAOuqEWc{(aAh6ne{gv}YTg5!`*&PQ;fHn<^O!C{^Z z#xDWq%-!>Es9}GIlHuC*Ew@9sQ*vwRqivw6%TQ)h5vhp&9feJGtI_A-zy-v`&(H{= z<2UweIR>>uPwiG;i;JGP?QluyOTDqm_5(vfAF2~hCsorlEF`6DM#99U=dsF_eZV;( zdW6(+eg9kHNC0*>&m<`T$3ZN<+Vu|iuV&>mzK2)8&oZ7Cd`-C`m0nVlaJxI!>nwng zP|rc9+s9svR?N8}P8IlgQHgsyA!fj5s?dI@p7Hi#?p(?6_Lyc=s2t$|Cep=RHQz$v z7rGP6$j??jVGr7G+x#E&A%rb-<3<-8O}|4$^IY#CmR}`m2AA}Z>HcsP=Y?HkIA(Yy z2aHzhh)fR42fFB9chG&TYm4*}!4NgzE|DcDrvt2ALdDTxZ%G7|N}q&NC@gG(Dvvc^ zOkEGm1c(L=*4l}&*G}EV)fh;~C9vDGb9PmT$n5hG`kBo^)XQx=tR10^v-M|p--(EJ zLCiiSD;WAjErACx62ej^I0N0g@|?+iD1bcxLKC=Pk}Z03q?NjZtB88e@~?atBc_Lc z=Zp}}glD#+;cLTnACMo&$WBLu?YY4vj zVO#69papbvweEC+?fn;3Rba_QqpJ~Wm;|lX`Xy8#BniR5w+ss6}3)8 zaaO#C=G|0u@I~6~tt-Y^oa|o9lO~&E@A%?%!1_v-VwrFpctb5629GBH%{@^zzOwse9d-HC$gsC-ht|kms z*W&inlg*Q)Px1QyYj3Hv}@}loce- z`9c7yNP117;8YvV&u->0fXZA+0?yCR@LItX*r)*igF6-G4vLLVJ@=>kO3tWpBe7() zGr~YuHqYs7wx$D_-I*fMux`#$?OETWixj#5OSTUgZWHb|cX=@LNg6^%e-4>v?^=~- zA5YmJrk(6UiN`E2430^wjV7Q9N%p2OJBRIl?NKJDxF)q#WP9`*kW0|an|iZGx})(X zE(8dY8o)DMGy4Y}0wY52i}HO&YQmR& zjD7mv>;KLZ(fiZ5TZ($NSIT zQ^t9l4eyaOHfUi#6Fbj;yE`-XQ42SF}B58jHl_ zTpPiaas@a;#IBLY3e5ptmp0Rzr)jlJjX%2~5xJPFY?&JwU%8s_^&|vcC_L z?^J^Gdq4WItO)>CDSv6x*b>4H#(Ueqo9C?#E}f$R8ef#sgIZwymGCBO=SQl~f3}{( zBz%6fT|6p66WC%CICOUI7Gk~g5;Ch&q>8+K9x#uR~< zXW)Tr?wwI)-IOsX-**-&Jgf|VOx1MW4*D8Em)+4m>DjcZy4e1jb=z0hGJg-o@ri<2 z5VXg3#!m)vO9}|ospkd)^#tT*4IwfQ-6d}@Wu!^-3k$Lz@Iafyt{knMG zVkcw8+Wk))NAKx>;W+C411-r3e0Zw2D=5}y!^=fb))Vz$6O)GRj&baHL?zm3cew3$ zU|TLy_Ngvh+FdLZw{lZOB%!U_v|x%1+yi!!D4eQ1rmC0wZ=n7~L?XPmCsAfW|MP0b zz!&{DkHcR9Mq5`Ct$IgS%aF)psEq46=3xzg3?#K>E+;hO2=oTZO=-FAq<Moz8bl%E2RG z)P^3w$ls$w&okqX0l1D%CMQ&kegW)@;*w0PUMA095bw>3cKcZ< znFfBAs`p9wT$VV|GQa81uayK}qQ~}x_Ijv@(q2Xtlu>DdA7Wrt^mLKxc>T~*$Qbu- z#0o`S`^-J|`u!EY1tbUkR=;w<^|VO}FO>4{<>Ak2h5U~-rSAD^P2EYv9)-2p`u}&9 zW^HROmG2=B2vvWADgS9@z$SF<5@5pBXSxQ~q2>?iG?noQDPjYuZAQlM$WdvIuy4*r z=k`0gsZ}Jb>$k%jJztR()tWv8(EV0ZP#WDLi_RtZ6T}Y0a?}5}D^)t`I_RYeh?TK>N|8RkL27E7`nES`_Z;K=;!I5+|Mnf}_7GU1&Psqak0m&PSlU2?lB zi%1}-rPU8^HuCi4tJ}e#Ed<7eN`mpwp+VX3NGhVti^UEdQl)qet!q1WbK1U6SwCNnV&IymXSWR`1 z6zoq@D!VcIOxEv;(%{l%JQ85ZzNVx2+<}AO(v^^mX6Fa2rzLA9HeXlhc6GukqL^$x z4+)^0@nGq|pV+6sxVa&}1f#~DU@hm>H4s;CIZ&y)=-kJEP-XtKrDQ{&Sx?$wL2MYmw86B07A;G!7u5GcFh>-U@2!HiF z)1_(^q_j#igqEZvK(iYU~V8CA6(>GVg}|s(iax@6AnP9fvhn6ChEKn;j*8r zDVPl&EgT*Km{Kw`^Qf^N{S_}>O%poBk!qooVs1zSeYf-hJv(5kCZQoMiEpOxG=FMq zTIEThwcdYeN_ciHGJ-xUtemH1Hx&3`6!U)Ir1>?F_aiH>?QD7EOjnO=Y(#N9b?f_P zs#|0d}mc3ne|d9__@Jo00?( z4NnZxhm9ccY>_Ug0{YY*tM5$`UXSyNUkFg65Nb3L6CEdD$3?s@ki$_&Xs(+R{iC3E`qQegdmlvcmKNX)K+y#>6VA*q zyGa#yW7YTLe_xSnvjC0bqyRk2Onm2&#FeVdsE^hKStEn0^u+1m9R7c8pXLTgA8y#q zG9q_!+D-(Q5i&@^njU|Jq21mrjk`j3!T4FLW`p=W7KU6q@LO4>zZN*ENOUPejmh1t z&0x|UFV~Dxy6dE%Z449;e0j4JpEJ9ABRE3ugh&V&)Jjo?ZRdxk<(p5YXALu-I=k3( zEVhi+SVK?xEO*AUSQewBNua@CLkQR#2p`_Hvd;x6k}uJi;QR?GLef88(H(rWgxKlZ zc}BzZ{NxKXwm^~jV{ef`pLu~&tL`5arhYVjE!roCD+~Oi!W7n-n|qxQD>Rvv`Bw!J z-!^Rn={5NCil|*I3UFplMQFNiL)F{enXFSaIQ!+B-@k~IYz;d8mKmk{tAK<)v~p0B zj#S{zBcRC$k@|vg75cZpDqh%BuY&oK&~`{7+etSnH!4|`5PTE1Y_tgz2QxoOVNPNauSLqplb{^YQc zC6)Xw%nQOSq++4;IRrPT0h2U6v8ECCFtvyML#?QG+FZySYTpsC?*L#|IpEpouZbEVf02Lh&} zLdLI|3ajhc9zR$+us^+Bu9xBBCaq90H`_|(7Qc76muL6_6ijlvDAcWYqkJK||3#{! z-3&MN(H7a+;F6SU3|W2OA=x>xeJXfT2t?v7Z~?#dYnnJiiapc;%?2cSMD8q|dB_XKtX2;B51smOriMep(ai zCNTUg=n6ihYfxvJG$v0{>xMO8Xo@v;HTW0?fD0u%-{cd)XqCBS-;k4 zaXe}WT6ktd{N->*(8_fPC!9gr_Ziz$0^X5^@nu*}>g~nQPp>R4bJ_ipXp7wr2dVMO z+aNbgbzb^akc5ZLk8P6$+YhkU9h)5j}| zQ4IlbD!6!)DOCnxMXKlY0aDZ<@l*>mL+@2vEE1?{LH{Rj=u^7g})nJY`C#3?23mL|c3 z3wTO%%l2T28sN_#BBu72;tcRYTZnROhirszdL$ueh9qBK3HHKk`;g{sy4+WZZfO|= z&ZUd~5FWu)@fQIKO!v9)zi+CU(BUQ>7Z>#LBZ`hkSex}{n5&h8ZCAVnX72}OedT8&YlE1*Ni+^lOMjTh5IMu;-^Q*(on zQtQdcN<+;e^pu0uijbA)W(N*e{BX=~DoCNWMl)ut`L9ZA)S zreHxWZ_@*^_4{w(5r7WPA|ihti_b10&#!^--`6X|h1$w^3||Fa@dC@;Wf?JLoFGbny%6Vq5u)|A60OG|^q%|ah!*!?&37F0rQn#G$TkH(B->M!RAs<|t_ti?9J^{PAG{ z7g2_VsM9}KrtJdN$Jb^WMnV=}&fW{%bxihcxKyX3YK-91ed5?)nRgZ zMkoP-W+Rc``h0)utLjXfd{l4z7m`6rs@5_Q4vHRx_l#iqhcR+~|HQyput8oaOlS*X zlQOnaRxh}`N2p1iOr}V~6kVFE9&o;c;$~0qT`P$0=Ci~2ZoedswbD;0%di1SZK01A zvyQ+&;yyBLE){vqM&LfkAjvcFTE#E!*^Y_&$_+_Stk(S~a|Mt)%8jiu9a^JD6XdFs zY8lF~EfFJjUcVpCQTh$re1FXZwp}6#4UXej`N%4~07L<&O^1DVeloc{U&bhVxEpJD zBJr5c2XX44UffCdT5vLxPxwXLNP?=anW54{^kE9gFD~?UpI5TW(2?7bla%D&?8CNX z#ZWjJaEE+79|=iBrXi8O(yM0>4~F0N4U2=3gEH4ar-ZSlk7H<&VO5}u;i8q2cF=x& z+)y~~k}1c2lxCFKzDMFHPoA-|jZ4Hl$mRfQSoF3G99wy1OBAH2&j*VgLr{^Hw~T2;T( zeT(Vm%0LT?Faixz!Eleo^9x3X_;$0`!tw&al@;?~)!=~|Xt;yFAK)CG3{AJ!J_zwT zB&>M*`VRD{Mf#MU8U)>J#Exzcw=}6a3C$o3bDKLmPQA zs0+%()Re{QWdu)hCOUudKGjb=7Hvo)1Zo9Oiz$(RrB0pvMAi)J^73}s5`ovRCGp+$ z)6;5K$c-Z*EAY_pvx>^g^W|n)QQYpYQpRH9`#yMo`hX?HT$6cI~ zD*i(^*&4oYAPE^8@^>@EVoO0yQLSBjqAh<#Sf5A6n_DHQo7^y)huPMCTdQRe$mEoO z;wT1DA$jq21E`gd!Rq$=B&YaTLzth6Q#dNuREI;6GuanP+`{BOgz`8TpGIDxYg;1x zz3bd&{D^l2wviHsGF)Fg>@I0$A+rGX3g(^q`i8ZHwNgP^MZA&{z)l&t(|XPZ_)1Cz zalm|)v0tDvSUeb;v#grPvn$%dcF8;(O8uvl(@r$%&C3WwYbKWw(<9G&>^xONT=xK| zw^*FIz_Cf=eBM&;7e*fPtyL zd=9AU#V%88{XtI#Ro{b(5Ttd2f@U=`=}Ft3DEyTQSY7lHYOedL}8%z(cI3j=*J zVtD3uKA}YbC>au%Mf6fue<`xmP6erOMwsJHqH0pJw|9tv!5E-tIrg^Os*}EO?16U| z*ial8uO|x*2A903Je?tm_I|j&M@!IJaEkvbET3*20j;Bim$lUcOU)l@m-N5B_M#L( zL-sQ?=dAuR@-LYMOO^{03W);4q#jtkkY?lyhb3MEsvzf?mBugnjrNG+dM{a(LaJER z17H`+L}7m{>W-YxOMky4AW9X7b7~_jb|4x7I@HC{>!SV07}~YU`h%s@9+llJ8~;Gv zAM|pKe6e-2vE-8dNSW>$rP38N1-eD5_WApb8_vB+SlytY_L3~YIRgnM)-#?IT&+#* zWhh0}&roPdbt4?FEk(L9z%?Y_JDH)YWeG@jfv40>3|U7K3;jzsfzI>DDigwrvzy%j z?wYxBc|QVo9G0$^>{%TiT0;ZkcoBDewT4~H$Oybt7E3@HOBzD9EI&Z35NQR8Pch`U z+yxoh=|N~wXt~L6{>$5G4|5lF4g0qcZNY^SO$eLF(=q;2!uiFXgOWsd`47LSG>i?# z(u?yN5`qtQIAx6vQFPaz@YN*}CPMzaZKU7($gS=+NUz+%1O=b(XnAfL;K1d3Fpq2! z$7a9W)K%5(uoj%m-U30yzP23#A*uYsJ_n4}om=O=XEBNHy@<^UvFek-bXb@&3z$^x zLKn<7+Fj6vvZ`)Cks-{>;FO+m^&M7oK2;V*8?0KGBOd$v15}jQkDbE%1pQ?B>*^Ix zF2p`FF&YVqgbP-_G%R+7^3TDl!Wd`a=HIE#FIBS_Xxoj93_4Tmr-Y zW}*y1HsOvP+o+uSqxMn;0h|#@`b37}>3m;sp~M4w=m=&DP-nVGcaLB+51)59I;dk- zieKir@k=`b<|`P`GqLc*ik%?5gbNut2L=ZB)}FNkoI9s??olCbeR#UJD1C9mLVhi9 zPZsOg)V2R|SrF6s^?$HVmY`T+i9Hs7gmk0C=47^{r`Q|QzstMs6TeUtR8!;u?gmg! zjepW*FI3!m16Jn$@b#xUQ(U>z2JU16wWo$05Ew6mh^QCQ1IJJ~frz6&N6b*#^;(Zt zpWAWr_Y|va{J!P46IPO0eV`2(l^3pRM=@E1%{%BX?%>;b`KOec^US@zqD3?33Ud~; z3v3eC4KHzh8obN4XiJaB9q`vZ-Wn`@!g05a`eL%p;&xA`V|5%_s24_3DBR#nzwFmM zZRjMMZ-&w4ujIPqLO}0d+&RIH`6^XbaC=g@MR7?Zp9XZz{-a%Gb^l?FP_jYv8x_EF z^ybMmyF`vjr2QA(vSn5Slt`yd!S?OEDuX9_k8d0HNp$LWn%)tz75@LgC?Kqof5ujyX@kYTfLC|Y7?Za~&=Z!+KOcc|E;k62jL%>mN zH>KlI+^i*b8(8OYo` z0+F}+5*;%c`Vr<_hJ1aqdN1UT(-I%KSm;;xCP!qh5`LQ1Foj-)pjy2QIBO+xMfEs} zZ&oLE0n5$yN2g)jtg{WGTlhd*MU6IggQXX}=K2jy@I*Z8=E=rT52AU@bWzFVz6VP^ zT{nrjIIkzs@qm9QHQuO=7;e%Eb5$Z9>BA%x9%S_f&Q6@YWg61b(rm8VQspDtgJc5kHmhv(SnS^>lO21JCg+(U@C?wV zOTP1e#GBuBU|fN#o1YAzS|mqrcO0zesnQ7LeQxc9poSGDtE<~9YU5?L11m@1^{Y(qL=LF_Zu==){ z@$GQ|Lek6>MB+3~{eu;}(G+-w?2O22wMA9C`F9Dt36_#2$qy@Xq5G#GmmiDfn+?XY zlD26*m+2Hq5Z-D*iZXNsyxX{+OpmBL!0CQ;e6%ApNP%4Cq7seuC9+0|iuctc6x14x z&3~i>7v4R(ulknRu2_5=cC_;;gbxl^t&1a87x%$=XYZyailTy0FJ0{OmE%W63j(TW zQ=-)Rd!B)N$3e#(3pf&Z@M3t)L(CDltdD=oao2y6R|In@qz@v3KmdG}=o*wKUKv_c zz!lO!Xd_sYohg8o{?isovJDk^Y!d@ipp0+(FTPK zedc-kik$;uGMk+pyQlht=}PO6NTpiYB2*?@sGu*1q7uH>Rg0N|T&1S`H7y_D>u3?K`ci# zTMyeE=^T=zx_qiUY9q}bwNUJ z^`6QC#Xvaz@GFoJOh(Q-P+5X0J;gv#d&<*Z3dfFhoq$T0I>``S;nB^u<7HMn|M4~t z8i&f?r!t5!M5B(T`}qmWdat+F+>8?W1?WwJa=5_obJZ&9gl+?kb3utW(u`VRqSsun zUvYlC{wU!0js`uan~*DS>apx8qcco1g6wexLxH!zV-;zj#Zj#5q}zKTv;q{~7^}5~ zK5vUG-D0&uEgmJ;J!(T;#D+=uCdYa9$Q&~m*u8%A-(TIOHxo&V`@+p-X_(h#|A@X5 zaLT1Bnm3sp(pZvpcRX+c-KFfgOnqOA54~)oCNiC9Tyr^no}YA4X{DjHbqXSjJBo|u zh%y8*g(v1hhbb@deHOEG%rJ6I5JZcqDUUA!#2dA=xS;w_@{^nC`MJHoKWMm46bsbX zyK^BSLmn4Mo4cpQ`dDYVTF?P9?u*S~-Ps)vn@{}qo8#-u=exFbc7(r5-ET-imiI_} zRqwHHQc+}-?&0(h7f9!+?zO_a8;XjZAKc~Zr$s?!1 z68-M$RYW!!tHQF;t$yR~_>)9$!I_APErgx}2uv`Vy`v!5Bb(K=f1fwh)z#Ra57;+N zVUT+9t82rg3shbqnrRux$g#qwbghc-Srb<7Rwk-&@`XS9&IjgclGabRSc!M9Bp}tccfpL^)3csn!r#ZfHYz)EYk(^=yTIR&V zM-z=7L&XpXb&E~7Dd@N-g^BkWpO_mr6W z=IXAchg$RR9|&Jg3ZQtUcWuzZr+++sjnI8^hfwrl`|n96B^3<;%09lZdz&3Y$x_m zNB}x5{`OUyO7P&8ct!-uAoke&w~YKLd2?W`D`{ER3jH)PW*Be~`wk;_d#ax#RS2W{ z=oVxP#RHAE+TiLH{rf#yJJx+uk-cWGRbLE{{@>Hk(W}K5(dUA!;@j85>{mv|AcwWy znRL^swS{+g2yR-SWgFu;>&I-yYW7u0fnn#@fdwwBk-LwAdP)woQP(UJnTDwe0SLI?kXQNF8Mf<=^MRtRy_WdtM-@6iRhy^(J`%4^64~w zt587%tNlXR)TMVZ->&!$AZ4xIiFZtUO+MjQ+aqi|Nu8mSy}(Rk_=Le_kqeF~+)SWE zJ(BNH!#9Mes;5Zk?iqnR29YMx&j-{2rc4R?j&~ucx+YF(5cu{cvH`944(M-+NkRy; zpShbZR3dd42;yAW9@$K&=>u=AHY_91MW?&Li@&+=%uC?;y?YBcL(W!PZdU9mGTH4J zw7EGSX2xr^PFZ7}hC++`)Pv#i(v4OK3@v?Ty^vz!YW3cLP%Byulox=wpz1E)>A z%3>-MlM4sjRXZ2=FGyYhP=-gvU2>Q?Cjq;JQoZ|leocS#l+`K7RCqE#kN=JxrzzPn z2cwjPPRptI zJN!VbRgq2{FU64}%_ltBVYQ+Lephp3cl*`K=5309aMqh7 zy}`ANpCRSvCf!boNcA@}qO3s=A}`R-%SJ^VWV5C0?cVc{(~Z+T%)16*`F=2+rLqPr zs(Za8!E}5achW?KBQVs~l*w+YV(<0tvT!}laW6i_cw@wzxVKZhebT!QBce4pSi47a zs%dZ|SnR~i=39ekx=-o;P<$PT{2=WV_Oh@W;_^`ZCx7(JepStH^}V|3wW6%#L?)rC zDU#7=y|BauvcPZ3hpH-LAtvq>4n9lb7^1{Z8!%W^!T`YXtGxkV#*7Nl?!Xq(1)@XQ zlS56Ccl?Rvipg3w$8`|3(QJ%Ks)kLA z(YD^ETkTS4wE4*#;6zx(Mkyc0Wx1)Y#SqNY!I$N0h#Zk4lzv|B1__jT#GVfqH}&MeHfFcM8s)%am2bkd0z!- zCxX6oNJs&&qni+9G!$iKmc<$ptE#0##ICf|QspDn{Ct(yYrgLfD%g6xrfy}U^QUsG z(XiywYwY%KM|JqHJIuQWdab3S+%+e0rSY4W^yD>g$^asUsr^~zYg)Snm5-2r7HFVt z2&w!H@x^h-xqlCK{L(EvFqlqLq|@Cob}>~T^X){%c@1+YaH5hj-I>><+_?J=%6j!s z^XJz!G?M$aCy=+kW6OvSg=S&LZMwi?=FaNo+?&dIpnU@W+{qA$5y1iHQ3#jyjj+4+ z2=EoDC(w!dx#9wsnB2Ax6Yv7MvL<)hju(~J=Vf2M5$2>G8<`B*@?{OuNAH@&2(b3^ zAzn{^5p@2XbGtjz(Qu>edikruwX3^f$9m^m z*~c2hsRh^WMNJoClL0Kbq1$UY;cWqP@Q1I02sv9%8hu7N$r|um?N9myD(2M?R2Y=& z6Ebd4O!_!SG2HH|Eqs6SGjD2z+Zxt1bDr;1$NBl|+w3M9=hEN);T?x<-58ZFFqqH% z^E`|woQH)+AS%qw;W(Ek?g-jh^_xpUoa_x5e9~J8lU(5rXeV64AT6u@9kqWXho}7G zxKfWQjrpqJ_@3a~`~J{AeGgx?*yc(6M>Hw;tqA(a2Bpu$(i`F+SV7FQ3Ecw2RXLJi zE7*%rJ$}nkC$)Dci7Bez)K9EwGR+I}tkM{eW_gq?KgVMaj>$t;B5WlN^R2#fUTuLK z9^@3x2$e(|PK_By)b4qk}B)WAx}vVWJ-L&dm?0Rm)YSy?XpE zVq+5I{-&owt>rHFNA!%mpE+NjB#dHgPG^a%)?w5T$aYCk$2u6)c|^Xb?h?#rByvJ$ zWpqK5&;;y2zeHpe%4!|GrE=rip!fDej7v~IUG(G=N^YmmCyMF-<>6kyJH&AP*7N+T_-ZF zdZo)H&^xFOQ=Mo-)?18S zzQ_~#E*NoiVWdntr&N4LKyfQAL1t%SE~KX2f_~hH7kl1GA9>=CC(MXXdalNkN@j!> z!6yVBYMwP4;mReQA#Pc>wj9cHvvg=@l7E_ZwV0R4Aofweb5$JN=)Zdxeet?__K^tgxQ^*w5;mF(qV}{^l1|IG$L9^XW=fmr-;t7!4H_f32w!M_ z+;7=J`%D@)?#r7|^&`}q126qDsr|NGaPyD!;OrM;=4TWjj1D!0P6Yk)YaOtXL7D?A zA;Hczp7AO9?YFn>dws`@xDxYB)nn&c#FN69Q00wP(>R$$9_c-P6Zc{r=^-WKKLgx^ zPMYg0aIC7b&}Oxjg9ys~2ip?R0C-CuiBv9RcG!p6?wIA?o#QeOwfYHdsg6?+s*r=!-0l;U+FEUDZc? zNp%-yTI7pOB90w9eC({;>g-u7Xx8cKKsoKzA4Ydv!0_gc%?wj-EXI1iJ1{7rcVvx~ zA7+&Yb_<0#N_+jyLAk^~$gzIH+3JiSn$3Lc<}{oWn^m<3T8{8stqiCzFt2=}63}-w zbqh*myLLejEb{oXc`a^e#=%rVP*v-dEAas@JMP1~l~B-ApC`z5*=_Oj%8)*vSE?=w zYua(o)$qo*DH0g}l-P__KyRcdkD5+)PSPK?WlWFNveOUg^L~t7hi$%=2&i3i)6*kR zAjSXMdN5ZX>xuY##5{JOwCsMFg;DQQLRJ!L8-r(0>xq4yn=!SJ&m(rJb+Rr_N#EkLP=%bzENjiM%VIEDkn?Y!b7t(Aa+r19NR!)yH1Dl>x;SOgDP7s(4dB^oXzsT#9?8uy>#KAHMKH=lmhH~FdjGB4v`^HauoA8LxG zc`h?r6tnpz)yZY#6DRjC3)tT!G0`Y*3Z&FlxoxwFrL~ZF%(~;;$lLmWx2^W>J?O%e zKkreWu_s^$Nc=mUk}patDGJPOoZ##Oe>CYpvC+ifQNxs(%m?$~6S||JVU@f3R)c`3 ze^<5}E`%;e+$;??NgP&;^VZ8AykU?%uU!x0Y))AGT1L5gyxE5tNbPP|U)IuV^<$l= zO98^^$DBap>Gs4Fbs-2Ap&C>nQAYv=eYuU08e0uPyk||EHhkS+Yw+xMxAPLxI5ROx zFDi~^1;@y$)*zFNIFxt$K!e9%8wBAjRktWNb5VZ^w)V4cE>362CYlHGI$gdb_0H3f z=r9gjtTbsvk}$P(btV^@(5B;2No)_*YD{g*E3K~OlG za1=N2qy0cB;HB;TTeqjCUlQ=*q*;{w#d)arJenN}`HxWNTz>~76#0Pluz4V2PJv=C z2aApNfYPnU1nV;DP78?61laL&F+#I@t(`5K1~4Br#CK>mf6L%9ABJtE9C)`=1CKgl z%%9uQ*YZ}*?@B&kbhJOt{wIXZ91XGaFII?^CP-pGuGgAmV|&U2BA!84-KTDcw~Od3 z=tprC_)%bJT=8vj4|s}qwbl&PKn*&A_(+ueZi>t@paRs_9cHt>K=!rfKsNk295eu6 zFuHKYO!qja!uR4XVk2_Dy=CN=`Re6yN#T)70)-(wPa#S|kiWHUB`!J7QXSv)MHUKbpli^9T4uQI+pzz&X_$EshT<4~~Z^7QocA`HBUvhQ7S&+3U{1u;MZMlb?>b;xN z$lSo8k-iMfS{1k0LmS8k>$5sx6u@4s7kw0DckUE5?1A6TCS7-5E6G?NZ zM&H>=gAzPP$F1*V%RDOsDHMoLYmKn27=Tf)l1p9J+ulWF%NYOmO-hwJU&jLfG8&_N zO@WUf(?+dE+o-Aa09vjgRppUpW!Aed`H^uPg+1%d1t{y84MTOfy{J~Z^wbi;DK88rRru? zhpeqT2O`(K1tRC_&A3?M-n&hW6FVV8ERTGbZ^-XLhMpZ)E;(8t3R;D-;D`v0kQe#=tx)w+5;_)N90otu%(B?>+s?k*X1Q*5dq87B8?wMW;IjZRrYv7 z%5R|u4AA%phr~d?55nnI=IPal^>pP z@I1*nY;xrLpKWzh*syIcsZU&!3Zf6J0jjZjs&4UPV`dd%3k9Is)|tkZ8k(V<*H?N_ zQeb{#15o3NT+Fcv%e1Vim;cml&s-*i&JJmJ9S#`3ljLbiCF0J-LN3j8o?39%xdU^rCZoV!O%>?&vw zyAN&ds2KBxKFe;U0-Q_kwCst2kiliQf`j8BfyFmMvVb#$I&ulo7JZ4n5gLabT|ea_ zRB;mL@;ev9-~(8ZZ<%u(@ziAXQ>r-^Ya)a;TA<8DPhh}DmA3w z^D5tKxfPm#;pXBtMZTz_?zn1uv%t1&% zoVMAI??o72@Qw=R$^h?&0fDhY^<6>xAImeF@+nNJ&y9cVWp$;V@RHj&A@a&ffmv<|} zLq2o!hRqL^IJl0>kc>^oWt|?6*6kG^Snd}lEAwcRz=j9unamjle;9jitR@Pc8vhz8 zA#1f8R;xTpR@1H!v6`q_tF9#7kN-F++8-(1_^M%EW@|}5zK#fR>K6`X0R%)p0M=G+ zyRthYIjVKa_jVvOC2sr|lB5xb6@{}NNWs_|%BZpa^R-+3e3U9xWidJLa0H=kTr%Oo zCuc~yPSnjKxxo$;2F6o2xZMY&{k8T&sQzS2MW-CnVCF@SFaJq(PR_b2 z4Xw%7n*_!8i4LFh&$mrRKESgHj>2chpqmMY^eq%-L$&`cPNJa`8l47r_RcVE%~PRt_%CG&eYKbO!|Pzq}>oNQD=prRowwGNE8NdFhcJ z9w90d?Oi4u$gA+th%IsL&SUPTDq<#NUEztWD8e#4B_DPNj(rKD^R^@~n91_+;?pjd zX;zLI1v&&6yhI}erWm3f9VKyoCwDdkrE#73#ZLVyEvi&&4e;H=+wTPI3T;cCYQ(o> zykSUnSqTpcVw#rU$?h+^%uH-oXP+B`u8V0mTWpnkW=LX#$g~v#?}%!&D>xsxPoS+& zy-3#`qP>v$&2g!gV&Dp2Ud?c<4E?d=huN6-{2nKz{oIuG)iA>Y-{5{i;{@Qu?VPoHl zz-a6xtqgLJSg@-3q)e?~P@`lCADF1J^wu9g_uXDsqatg<=Fc8teD8C)X!r1;11FD0v+j4R&5wyz|{Si<+%^Ir2W zZA2#_HXC_(ZSrW}sMRB8IouaFUVe2zi&m#26<-SxDV?~|xI6vSM9%5*L&N#S8;lEE zf=|chN@yc&c{WDh0GF{#5SCmd#igs}Si;C~)5~7qA3#4wscyxLWY-(JKn$cMfA<9$ zvj*T5Gl#7b>be+h%!8-Tg~7jqq~-z_qqxr zFG{JDTH1sk!XDipm`$NV)fU<)5;ni@u-<@3^LS3NDslifJXTto<@VTjG2-&4TQHHH zKO51GdncXSwX-;9P9tVusxl7M*iZz+sE@vspu9U(Y>xqBT!Qu| zD66*IxWBgE#YoDmr4xtYD+E?_HYGrnzWdRh>Co&Z_7EO6nFK^zs8V98V-h1t>F|y) zuas``8-DT}-6`I9H3=Xb#1xR+3H^YM(m{kB0hXmvi8xqKG@-d~mldyiFh39(P&2UWu-@kwE26v|DML1Dc ziK0W-QwF|wJ&5lCPH5rk5l6YUhM6+&P2|0_H__tNKwFnj*!{aEO&BgEq$5=7Lh0rz zsZO34D7DtCIj=SF!9IhGb$tjHI8dL!>siJN4ij`~x;irCnlM7Zge-4UmVZm#VMOzu zO3(<|v}o6bwdd%^)*}I=OaS07GEo6Fieq2DnSVCJu?tR%Pl%DIQ-~5X!PuXxhd3Qr zQ=f2M0o4}47n$}Uut7yZP(e`i2P6?cQ?Nn#N#&}Z7`#e^HU4Aw;NPJq%OqNi;3@_I zvSR-X2?JuIsaOY2<){Ewj7_x9tY3QY6jGyKD+aWh3z;_Oev>4ARpaTsXwpZ>BlE5(9l*X3dw`DxlgfK) zZpu|G93QB1{HU8Fcg{Xb(SE)`u17V z>Z>8Lk!tCOAE+Qsj$WM01MocenireZ3k2BBQjQshJ`IiLsXBgYq8Y-lK$js`1fHTq zI!+-aJ8;f=f8>gD-e+MTvH4ox)Yj{%+R|}YP!n< zx?R$N$o+XCWm!X0Qp3v4wUvR$`F2fL6_K&&w70b17yPRQo_`AEHwGRc^1J4pGX)4D zWQ(6QMKw&Q119bUzLg&ig^FMkn!%Fj_34i$zfUBGb4*gyzY9w;jndA|ehPN+#flXO zd<2uP>wDb%{^QzTBvE&7;4GZ4eiglY&nCN`kzTps_Ey#yd(V4!Y8yDbi;fLIS4pH= z!(}D(q1Im$6_98OGJ1Bu8i;`Is_}9w^o=RvJ7_E`@cE1tDg)HhKDh5bEAh?X*mk~(pf zdV4Xtr?X7fz&hVgv-6l-$~CqBy|gr+nv>JpTDvZUy?W2Izr(|p+*gIj=Cjd9D#I?V zyrP;_zAc94Pt#A!^I%cl$uR3;;k>L6=o%6h&@E1E(Zp{sb*b%$?j5Mp>?f|6?!yZn4&XuGvLD+ zqrD10QpmixQU!WwN@~3;ICt5a0FD!;&j&RuLq~AwUjQS+y{-4U2I5V|3XP#`yJkk~ zM~{ZO4?M>@=sUl=j8Bwfs-jzEI|RYKdpPhvzflXC(46M$wK?)S4A!~A)jL8sB4g*c z;)D@@VwC8H2QC*Sr+Z%*M9zm&CQ;U2zzpHKMUXE9yjysnO{R9S&CGSY?x&4al7y@8 zS)rR|yJ+Cc*jVWhL-+1ImDCOL(OBb5*<6@6>GmR-sLoSHhLzOnRqjTUA@GfX)?rtH zdM(_U*1;dxqyR~rf~oW3QWd_ZOv3IbyVl9?@>;h50>`hRNyPA9uLMUD+H%myp3Wi1 z^#lvj=LQ=xx;3Ai1FSqUBtKg6w~O9%Et>|?y7LoMiC)ehf{p>rb$0N%g5*BnI4{U(vhRtssZOB)A~Gsdl@rCj3Y*-mj0;vYp6mkwGG+#hO9 z@5`9MJ(=9#u`;a8PP=`s1TG6ludVOU%Ktk^^~bj1s4jMr$MB^ z>^>FQ&5Hs^ss}ZsVFB0gp%vS2i{d6LZ4MjJtVmR2gII8Hl1)F4804%QuIQy|+9wonwzM7YKp??z{lPXUR58%gYIs6Iu|L zx4^P0+o(#OG5e3vaOT|@UUwv9Y2{JOb;cX5UeiL!bS*;0SWInR*pIYhSUghQUp1DaSU^H1B*JS%RB6C7>VaJl`S;1?k zpEdcD#uv}?`)w{M{ma^t)QS?v5a>o1Ufg6SwVe_=EL(gpV-q270)aX#7#2YdTJcd} z@mOHxs)+E#uO3e!yQVnj{%jY z`X{^>T*p;3yq%L>3zx6(N(V~@Fz2(DFh1&l!zGo}&MJuTxzA$E?)2E@i;=ICJjB0V zRG0W5fxwRayk&77DUWSvHyP0{O-8=A%VV6`eGO#BJ<5#5HoP&_OO1qIy6%fTWt7q* zNcmi8487?TZp}#^pS5kd&lFV-@`y%8R-?iod8)80VKike!U})|&JAZhF`V|0+^59` z`gpBrYJ&tu&B+2b3`6xK1#7fN&H-5QK2yPcNN0iq^w?0Q#{&9N$ra+51-_qQaT{ys z(uQH;FIr*U4WQyTcM}GJ!n*OqQxq_*yvzYptfCrib@CRfU0N&^o%RS0%{MT&3)4@* zyrPCOz$h9E&>xQxM8Ji7KebPRHRb@o{J&&vsm+z^qK5+UIYAU~i)r;1E5VA^Ar-Zdd;PGaV>{tYcH$Q%|Obn6>iSmq&|U^7 zeO*CdH)Wp5*Hu>6NBtX%0K&Jn8;t}klHilQPav3j!lZgnn~Y+e5xFA{9$Y7v-`Jad zcW1p_o&G9qy4cfb z8~R25d_40k+39mw=%oquP!bjtP_BVBjavt-4bt5|gj#`NTg&)K%f5}CKWi4@m}vu& zSNXOrd4i~`12<`{ysJ~?X}#+zZ_ddR6x6MC9MbtkuypYj9azDh8L@jXy)a;~)s}avv+(E~%>5tDZJxER)kVIWK>b_2^zi2m!X@s2DlfjG*>j)e z?+^ohv{`OGdp#!&j=RV_4q>ug`Y`{mudj}Zy7}UUB?T#ISh`cXdzbE#E~!NUkw&^Z zmImom1VLB@3_wCka92PDq#Hz*6nKdDi_e4p-gDmb?mv6Z?04tRcjn$ZGxu|&uiw9C zSbBfS+b?0imZ^V4HVf3o=xpTHQh$dO4b-?-=@4Bn0`pa8e|YHA>@#0&JQooZSlp9n zqp#m`t>L?0nL~>uqbwR(6e->%X4dd;?^QX)N_A^Ou?r z(+_E2fE^9?#Y6jE?0bK$3i1{?>1iRuO3TKYWVn)$@bDYJianTY`)HA{2avR0?f3VG z2(1Lewl=wun^X7w!Y;z(SNP>>nFI<3;8THtot7;PA@o;!%)LZT_0&~s89M0sf0$7g zMg+cHFbt0-=&t^Ey%H?MPZz66#x}ohYqHqVT{5qF>nqRPn4hY$tQ`Xi#O?p0IYR3i zlWlFpxehY)@ZJq@INj6S{^tOT|#Oi0Hv@YaxR?xR*ohN|WfN#Og z5R?3hJUvcdkPTdCxtU@7ou*B`_aPaOAEtyQHJo_!<_O{4S}+md+SmSwL&@2B5qxjH zJg#9XW@+RM$NnFQh@SMT>IcX7b=gB9XEt9ZNIt4X3A|pOn~>6Z<}^aFJ8vkTnS&&w zdNJ^1-P4!!CPvP1KeN>NJK)B*2m<`3%%3aqQi2!F#ox7YAHRX84ZkXmcr8vizZx0P zl_B`SmGsbkivBrWI7_x!?j*~OJ6(7ld8kfKD3reP?*0YN`4X=b_5ntxJ2g{+EARGq zOasDz5Wg)P^z^~NP8rj4T2>qvamIW3cJw6q@Bja>(lL?}K$Kc#mHvsm#%TN{5wgVyrLE*fEd#>Ec-AnY*%c61an-J87?Erpe1i1h=!J0wxA1(8=4x~oeZ>f~faK4+(;g@Mvo zTk;B)gQWVFV)c|)z%O>#BRXw>dhzyb4Ep1zF9{a%)3nm+6Y^r^!v(p5+c-2-3DY^) zzLs;YE7}o=`iF_L)z2Gnr!M!3GA(J07>)aUk|UmXFi9!_s!I$f?%s_*m-N7HI}M2l zW575K00piZ?jI`<_&s4XJQ-KA66eoVvVSgm17punh^LZJ0yCu)JVKEgH)>7nMe>E( zVKFr>$?K(l-FUO9_Kd?lmzhJ-Rr&6PvvcJVIyZ+*x2nsB;$0>33luAD{wp!Y(9LgL z;-3TN@X^C25$@wWx8L&0i80ZWcLDGpj`-Gl_;y+x?n`=Ao2?8hHtNFpm&B?wahT#r zVwy9pK5Nd&0A&^=@4 zXonq$AxrN^h#`0qeCCT{+1t4X#^tdJ{NW&D(O)FU6lj+2Z)MAyUqXpHU%~pNZAeS81pr-{4?ze&)w<^OR2UwLs zh=9~g^TXlMq3&YimE6n`D@DB3=gA78UvFVHvdWt3*$!q2o-95P;@O|cTY%vlVgj%g zx~FyFSI%(L2OzK~@zU6(o0qo~Q3OX0Rw>d0z1z|P+i{X^n&^5xAXPC7S|FT3u1e6e^P?eqYy zN8IQqWD#v*+C4qo+S7PHEN+Xqo$`_RR`I=d$JXzCTc#qL%P5ObTh_3cuQJH;h{h2xk=bD%_TK$<9nJ9jHw(z+aBKwnSRUc$rwQ}heks%0+h_~Ce^H>0 zRS%Q=0_~M2Z5d8q4bo4hvXr2{Y-NC!t+FszmN}1j zm~grCtNldn^hkDys~Do36fdPK<_+HZGc`GEh@wd0+OtO_(bZzXGZ45?yr! z-WQwLG0!MU*xQ`3PrC7HZRg*xmfzDZE|2XegAhDv68n`93l4;6J#i234rtkNukTVD zdZLgkw^slYGpVXZA^#w6cQ)btB<<06ReHJ4yB2A0ec-kx_b!d}FED*cqqR~@sdhrz z!`dh%XGG!99eeAYfK^QUK0jX%E?o|;C7B&bYY(Sm;T?$Gc9&Bjf6H@1KF+}mR6h&H zYK*YDT2O1l95JKZPhs;mH_Wr(A+~S2-w8hziO~_?69@2$nY@llgP%=sXR@%x0{<1n z4%|USVotH;C(Z3ZF;5ZEuWNY{j*m{sEMsib+P7SS~Xdu42oST?0i$C*#mYqkMhG@HgBufNe0-q|}S zZyp%pxwGK47@94poNbN ze5EN3fRCgO5Zxxc$srbis))5 z0`W?2*in<#3keYenuO`CGpiC{Oc08{!V*+hXBVzemG9aNRcY@1by zt;U&20qUa|))~}HB2Xuw0>z#XE$mjfmJ4+%TYm^%^54xpwCp2Vi#SkGn(SUWi0U(; zd#+xC_OgG(!Yos{NK3Jy()(o6*VF*}t73F$H66um2v33!D%iAeFwvUUAybS-D52yb;VT`%ZEZ|7y*iA@vM-e>Pa)O;6lX&PF>w)-zLcd| zOPx$NJS53oDY3VMGB4+SQ`k-ua8XV}>S9k0xyd&T47P|Te(5|_jJ!9!4x#;oBpU?+ z0C%_?dn-TM<WBE@r=O}4qm5(6AqmvvAh=O)7^gsdUg!?ZbVeiQyZWwKj^CzPBuo|=+P|W7M5wfycu&Q0{A@hCYhkCqH?LloI)B_Z#(6=JvnL_$^y^nrxt!RV+{}~ zE`y43Tl-niptR#iLcz-FJ51RDC%;MvK7sHGO0MWUMRR_R+rV{GW9i6bb0RovkwV{! z!6}PN3$s^7lyY-pv9#)Fy)4z4elD9etmzj-gKDu7^dF<$wFhj*#Wo#3szitEUY0U+ zh_sIm1U}^_WV7_P^`D9IBrWxKUiDQ_Q#vDcrZW9!5K-*=Y;`)JBY^uOoGxqbGhyN=@z?VArgT`dY?Y z#Va$#i*A8be$%Va(R4h3m~F37<<^}@RIKIlpN6Ca8gAEq8cPhAkfTTWL_H8K{P(}E zxviT*m1m~^JYAJ$l%EKpMDk4zbRx;T;1ZFah1NK=A5b$iAl9GldZ~2%kfbA)+Q9bpEneeDNq6-|| zb``H--^fm8U}KlE&M`=!E~;ezMn#d$5_fr$j~jpB8R}4O%pl#OO`)T7*6m{K_VoAj z7{dMTe`UN2pIs$_*trTK8tsmVq!Z=f9MB@(9gPD}y8p!R2LSgmeXCqo=55*FCHZFf zc%>t>e(QC6BibzS&Q7$6G^xAJL|yllXlRhQOBtkxBe zKQNSPum7^NY*_zaDUO_HVP-mPTnmetjh_1t7W_>R9|MHoJCSQ0qV!Z2X%9<}# zbUm5`W@cS8oY2{%g9!c>cu~UmkGt2yywVo-R~cBQ4D`E}9sK{dPvljS6P1lP6tWx= z8MkO+SwZ{BE9}dQBl42np`3GC%ud#M=wrxswZ*&a7hbI4EpMe2w>;`|_Ogd1B7PdM zmb~;6NMt<`D+=uAkVeIw6*ZySp|eE;!y1Bcwl&wr5S%cVPZbrcE1e7#1@*f!;qPmi zj?q{oVcih4ihk`BMvH%c>e-5%Q>*dbP|(0n-@#AyavZcM+7QQAO$ody7Hy@~T(g?P ziHmSC^O11V?AGnP`Y#Jrryx3nP&w$5 ziz$BNKK0pYB#9z=o=Xn<_L&n9t%VPAx$mt8t$2}qJ>38gM0PP7=oA6bL6Ptn+CN$! zm~15#W~k>KZ+7Un!D~(u0&V=xYesZfmHveP#`QJ=u*5AOKaC!DR)oF9-(_L;AjOBX z1MO29Ls7p`-rw7fk{U~yN6B3duF9dJ*gphC@PI5jT-nk>O;x{{?Dz8-o>%J~qNn#d zM`HLtq!=KE3j&&4vPHH349p1(V#)mG&d#+&>Ja%qBM80Xi{qlA10L7hnCq7wkh)40 zW$>Z=JxKHld;WJ+GR&l`EI_+2X{PfZ(V&U^2#NSKI0)3dOuX(k@}n|yKXg1p)4UAG zk?>|%VXcHXnVe#%rRmP@p6I?=PHRb; zMTv`p9zweJF~Gnl53d>6y<#srEP4GVf`?e_*NyzI3R>PPj*@H{1Rzhznn$+(Hxr?= z$P6Me!mLzl1>d`V`SqS1&*`K@H~8kgZimQe?UASdX#<+bn^0!HXO;p_{IMx;jWC)& z`kPVWnFzT&zMj9U*%=d9X892RKP{0MtfZ_ZBXN~L_!I_#8vp%$6FD17-A0T0E1M>DvO)|<8s%$h~{<#Ahg%Vfi8;38#U$N zfxm1BtlB&+=xxxD=(@Dy^ys+gLJb3pN_w6UHIRiO7u?*L-q7*oLq#a(qKTrYo#}I+hPdC@|on^kJ~Ui^?#_} zVGkhBS5mX)0~mocKCC5=4X5l~dQhIK+TR{PhW{wn0S)Z>l;sSSeejeKB3tjHnMovi!DouI%RzsY;xGNvE7B3Oy#qn|7@PZySxky%iA|8rs;>F9fwnOTsY zOY~PrdzI&R)*COsmC|wv^2q6H>_*owA7m_(?os!{r%7gU24dufj*6M`?=t=&8&9fn z@>mXomJhSIbN5G`Ry+RD8?^Zx28zB+a+N~RyeBJLfZqZFrjC{?s8-E5GaP+10n?ac~t!*0?q zsYqffM$=Dz$8UAl)EoQD<(ak}V6tU4iAUN97Q}sI^Hp@vZ(jNg!W4~-^jU` z6YI(cyeNg(BYFhu2(NFe(qHDn2Z8z$^Q%a#>Y1NI8VwV|Fb=6653&sXL9;owPos`@Ssj%BGPg& zz!RF@0J}`g{c`h4jDr^9kHTY@1>xVJb}49$*)-|xfp?{8^aYnAuZ4n6k@$RMq4+W~ z$GGI7!Hn+|lmq zOT0P-UMBKxyI+N4yd)=9+Efh&yahhCgw~|LMTn# z?$(g$zKp7m7mlc5d71+I1^!D6AD@CeYft?%!C11ex19}$jwru=U;99dNxx7KPNYkJ zqtdMVASfyDAiK8Q=~C_Zv(jFfVd`3Sz@)Tl*u$<+(a#~|9xRr!-r)%wB@jL@I3;xQ z9m7&cqy%lHnVy?rtNZd^#`)yPwPYSsMv{t1bU)AB7v%iH2d-K+7vJ+u*3jNkiYez| zpB$)^YgTMG+mNrDb?eV?zqc^_40d?o4d(iOn8BTz@;3d;^Y4iLA1sJHG4Px`moa8^t~`i(6BVlrn>*#TTOV^+KCh!yEh?%tP#-1h8U;sqF$*``{Q;zMrBnd zAF>{xzJ#M!`ua|JXy}xaTwyUE1~Tp(h6?MpqaJdSp9X-%>WqSPk?8-wR|h`d2K|Lc*as*Pq@RHipuHsp=> z7-(l0m1z`!Gv$b6OwRR^#TICgY`%MN-(&Oc-O$O;>t9u2&L8WS-#@&jjwwlo&oRV5 zJCLBE&h&N{##7RSpo&pIsKwQM_^GZvX2_&P_Q>qy2A%J>Vxa>}stY^(Pjzb+Ml^jV zP-?->lIO|VyKX}b0#ipYw^ZWoWnSpxlyV!rsN1_C{CeF0&d|bp;mea|(?0jC;xHA; z2XDD=O81^Gm2+^FEriTr@r3`BA?e5qp;%Fzl$!&_j{I+zP(qWHrQ5JVk_a2fSo%~$ zz-|r0G>9tmjBcMBQ)Wm(i1tHURqZi}<{%6^=;;nhmyM}bD@+?TL)0L2@PeaSrK)_5 z6(P20?*z+U35Ts5H$PT0i=*X8SyE?X@P6byhM0Z{bDK&F?8M!^4|8kCNYK_QcdsAP zWwSz%njDFsdi6RK^!jU=@s6X`Y- z#rl&@y@r|6FT&OGM}fH$J~iYx@SZV;KdzZ{Qtka18cn2WX-{VqmZq|W=bk|%aeWkt z;J{82=vy7=P>QIT(Rj$x=W=_h8nzKq76hZNn2EV_=aFrHz@Mj;YFL!!R3tDV^90Lg z+`_IO7D7o+cj}W|UD>&aEsk8SCkqrEQL~I3P?9qS14k@*NCXu=Atbpv^`pzdM`i5= zL`mHL92nL~kqG8urp`yn7!40B<8p^w@TMLDJ8Cff6gBE1!e0^?5Q*B~4QT+14css- z(R81X@#Fk*wlYTnRG5}hp^Q@GIPZL4`CsY zTYQAXIGd*GPSr8~@WL&rb*B*4PNLIP^iFg*lL*LKu`SHt4Ksf$iTC#WPW;$5L~QEp j!?p4P?hZ;wvg2@H$yIXmeB4(Uz(-S65BgliKI;Df>!opf 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 125/136] 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 126/136] 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 836f722f2038cec4120900d8dfc1ec009e1341ac Mon Sep 17 00:00:00 2001 From: tangxifan Date: Sun, 5 Apr 2020 15:19:46 -0600 Subject: [PATCH 127/136] 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 128/136] 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 129/136] 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 130/136] 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 131/136] 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 132/136] 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 133/136] 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 134/136] 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 135/136] 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 136/136] 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.